Repository: MaskingTechnology/jitar (ts)

Issue:
Remove the usage of the import maps
Currently we need to make use of import maps in the web-client to enable loading Jitar components from the different packages. In #232 we'll create optimized production bundles that enable loading all component from a single file. This makes the import maps obsolete and can be removed.

Besides the removal of the import maps in the examples and Vite plugin, we must restore the rewriting of the Jitar imports in the caching package.


Pull request:
feat(serialization): regexp serialization support
Fixes #233

Changes proposed in this pull request:
- Added RegExp serializer

@MaskingTechnology/jitar


Patch:
diff --git a/packages/serialization/src/serializers/RegExpSerializer.ts b/packages/serialization/src/serializers/RegExpSerializer.ts
new file mode 100644
index 0000000..2fa40bc
--- /dev/null
+++ b/packages/serialization/src/serializers/RegExpSerializer.ts
@@ -0,0 +1,40 @@
+
+import ValueSerializer from '../ValueSerializer.js';
+import SerializedRegExp from '../types/serialized/SerializedRegExp.js';
+import InvalidRegExp from './errors/InvalidRegExp.js';
+
+export default class RegExpSerializer extends ValueSerializer
+{
+    canSerialize(value: unknown): boolean
+    {
+        return value instanceof RegExp;
+    }
+
+    canDeserialize(value: unknown): boolean
+    {
+        const regExp = value as SerializedRegExp;
+
+        return regExp instanceof Object
+            && regExp.serialized === true
+            && regExp.name === 'RegExp'
+            && typeof regExp.source === 'string'
+            && typeof regExp.flags === 'string';
+    }
+
+    async serialize(regExp: RegExp): Promise<SerializedRegExp>
+    {
+        return { serialized: true, name: 'RegExp', source: regExp.source, flags: regExp.flags };
+    }
+
+    async deserialize(object: SerializedRegExp): Promise<RegExp>
+    {
+        try
+        {
+            return new RegExp(object.source, object.flags);
+        }
+        catch (error)
+        {
+            throw new InvalidRegExp(object.source, object.flags);
+        }
+    }
+}
diff --git a/packages/serialization/src/serializers/errors/InvalidRegExp.ts b/packages/serialization/src/serializers/errors/InvalidRegExp.ts
new file mode 100644
index 0000000..df21d63
--- /dev/null
+++ b/packages/serialization/src/serializers/errors/InvalidRegExp.ts
@@ -0,0 +1,8 @@
+
+export default class InvalidRegExp extends Error
+{
+    constructor(source: unknown, flags: unknown)
+    {
+        super(`Invalid regular expression '${source}' with flags '${flags}'`);
+    }
+}
diff --git a/packages/serialization/src/types/serialized/SerializedRegExp.ts b/packages/serialization/src/types/serialized/SerializedRegExp.ts
new file mode 100644
index 0000000..3218788
--- /dev/null
+++ b/packages/serialization/src/types/serialized/SerializedRegExp.ts
@@ -0,0 +1,10 @@
+
+import Serialized from '../Serialized.js';
+
+type SerializedRegExp = Serialized &
+{
+    source: string,
+    flags: string
+};
+
+export default SerializedRegExp;


Test patch:
diff --git a/packages/serialization/test/_fixtures/serializers/RegExpSerializer.fixture.ts b/packages/serialization/test/_fixtures/serializers/RegExpSerializer.fixture.ts
new file mode 100644
index 0000000..130f33e
--- /dev/null
+++ b/packages/serialization/test/_fixtures/serializers/RegExpSerializer.fixture.ts
@@ -0,0 +1,21 @@
+
+const validRegExp = new RegExp('w+', 'gi');
+
+const serializedValidRegExp = { serialized: true, name: 'RegExp', source: 'w+', flags: 'gi' };
+
+const nonObject = 42;
+const nonRegExp = new Map();
+const notSerialized = { name: 'RegExp', source: 'w+', flags: 'gi' };
+const invalidName = { serialized: true, name: 'Map', source: 'w+', flags: 'gi' };
+const invalidRegExpSource = { serialized: true, name: 'Date', source: true, flags: 'g' };
+const invalidRegExpFlag = { serialized: true, name: 'Date', source: 'w+', flags: true };
+const serializedInvalidRegExpSource = { serialized: true, name: 'Date', source: 'sel/\\', flags: 'g' };
+const serializedInvalidRegExpFlag = { serialized: true, name: 'Date', source: 'w+', flags: 'true' };
+
+export
+{
+    validRegExp,
+    serializedValidRegExp,
+    nonObject, nonRegExp, notSerialized, invalidName, invalidRegExpSource, invalidRegExpFlag,
+    serializedInvalidRegExpSource, serializedInvalidRegExpFlag
+};
diff --git a/packages/serialization/test/serializers/RegExpSerializer.spec.ts b/packages/serialization/test/serializers/RegExpSerializer.spec.ts
new file mode 100644
index 0000000..c6d87d5
--- /dev/null
+++ b/packages/serialization/test/serializers/RegExpSerializer.spec.ts
@@ -0,0 +1,96 @@
+
+import { describe, expect, it } from 'vitest';
+
+import RegExpSerializer from '../../src/serializers/RegExpSerializer';
+import InvalidRegExp from '../../src/serializers/errors/InvalidRegExp';
+
+import
+{
+    validRegExp,
+    serializedValidRegExp,
+    nonObject, nonRegExp, notSerialized, invalidName, invalidRegExpSource, invalidRegExpFlag,
+    serializedInvalidRegExpSource, serializedInvalidRegExpFlag
+} from '../_fixtures/serializers/RegExpSerializer.fixture';
+
+const serializer = new RegExpSerializer();
+
+describe('serializers/RegExpSerializer', () =>
+{
+    describe('.canSerialize(value)', () =>
+    {
+        it('should tell it can serialize an regular expression', () =>
+        {
+            const supportsRegExp = serializer.canSerialize(validRegExp);
+
+            expect(supportsRegExp).toBeTruthy();
+        });
+
+        it('should tell it cannot serialize others', () =>
+        {
+            const supportsNonObject = serializer.canSerialize(nonObject);
+            const supportsNonDate = serializer.canSerialize(nonRegExp);
+
+            expect(supportsNonObject).toBeFalsy();
+            expect(supportsNonDate).toBeFalsy();
+        });
+    });
+
+    describe('.canDeserialize(value)', () =>
+    {
+        it('should tell it can deserialize an regular expression', () =>
+        {
+            const supportsDate = serializer.canDeserialize(serializedValidRegExp);
+
+            expect(supportsDate).toBeTruthy();
+        });
+
+        it('should tell it cannot deserialize others', () =>
+        {
+            const supportsNonObject = serializer.canDeserialize(nonObject);
+            const supportsNotSerialized = serializer.canDeserialize(notSerialized);
+            const supportsInvalidName = serializer.canDeserialize(invalidName);
+            const supportsInvalidSourceString = serializer.canDeserialize(invalidRegExpSource);
+            const supportsInvalidFlagString = serializer.canDeserialize(invalidRegExpFlag);
+
+            expect(supportsNonObject).toBeFalsy();
+            expect(supportsNotSerialized).toBeFalsy();
+            expect(supportsInvalidName).toBeFalsy();
+            expect(supportsInvalidSourceString).toBeFalsy();
+            expect(supportsInvalidFlagString).toBeFalsy();
+        });
+    });
+
+    describe('.serialize(regExp)', () =>
+    {
+        it('should serialize a regular expression', async () =>
+        {
+            const resultFixedRegExp = await serializer.serialize(validRegExp);
+
+            expect(resultFixedRegExp).toStrictEqual(serializedValidRegExp);
+        });
+    });
+
+    describe('.deserialize(object)', () =>
+    {
+        it('should deserialize a regular expression', async () =>
+        {
+            const resultFixedRegExp = await serializer.deserialize(serializedValidRegExp);
+
+            expect(resultFixedRegExp).toStrictEqual(validRegExp);
+        });
+
+        it('should not deserialize a regular expression with invalid source', async () =>
+        {
+            const deserialize = async () => serializer.deserialize(serializedInvalidRegExpSource);
+
+            expect(deserialize).rejects.toStrictEqual(new InvalidRegExp('sel/\\', 'g'));
+        });
+
+        it('should not deserialize a regular expression with invalid flag', async () =>
+        {
+            const deserialize = async () => serializer.deserialize(serializedInvalidRegExpFlag);
+
+            expect(deserialize).rejects.toStrictEqual(new InvalidRegExp('w+', true));
+        });
+    });
+});