@routier/core 0.0.1-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/package.json +91 -0
- package/rspack.config.mjs +65 -0
- package/src/assertions/index.ts +37 -0
- package/src/codegen/SlotPath.ts +20 -0
- package/src/codegen/blocks.ts +578 -0
- package/src/codegen/handlers/CloneHandlerBuilder.ts +13 -0
- package/src/codegen/handlers/CompareHandlerBuilder.ts +13 -0
- package/src/codegen/handlers/DeserializeHandlerBuilder.ts +20 -0
- package/src/codegen/handlers/EnableChangeTrackingHandlerBuilder.ts +13 -0
- package/src/codegen/handlers/EnrichmentHandlerBuilder.ts +28 -0
- package/src/codegen/handlers/FreezeHandlerBuilder.ts +13 -0
- package/src/codegen/handlers/HashHandlerBuilder.ts +25 -0
- package/src/codegen/handlers/HashTypeHandlerBuilder.ts +11 -0
- package/src/codegen/handlers/IdSelectorHandlerBuilder.ts +10 -0
- package/src/codegen/handlers/MergeHandlerBuilder.ts +19 -0
- package/src/codegen/handlers/PrepareHandlerBuilder.ts +23 -0
- package/src/codegen/handlers/SerializeHandlerBuilder.ts +15 -0
- package/src/codegen/handlers/StripHandlerBuilder.ts +18 -0
- package/src/codegen/handlers/clone/CloneObjectHandler.ts +29 -0
- package/src/codegen/handlers/clone/CloneValueHandler.ts +33 -0
- package/src/codegen/handlers/compare/CompareObjectHandler.ts +15 -0
- package/src/codegen/handlers/compare/CompareValueHandler.ts +27 -0
- package/src/codegen/handlers/deserialize/DeserializeComputedValueHandler.ts +16 -0
- package/src/codegen/handlers/deserialize/DeserializeDateHandler.ts +45 -0
- package/src/codegen/handlers/deserialize/DeserializeFunctionHandler.ts +16 -0
- package/src/codegen/handlers/deserialize/DeserializeObjectHandler.ts +29 -0
- package/src/codegen/handlers/deserialize/DeserializeValueHandler.ts +33 -0
- package/src/codegen/handlers/enableChangeTracking/EnableChangeTrackingObjectHandler.ts +20 -0
- package/src/codegen/handlers/enableChangeTracking/EnableChangeTrackingPrimitiveValueHandler.ts +15 -0
- package/src/codegen/handlers/enrichment/EnrichmentComputedValueHandler.ts +37 -0
- package/src/codegen/handlers/enrichment/EnrichmentDefaultFunctionHandler.ts +50 -0
- package/src/codegen/handlers/enrichment/EnrichmentDefaultValueHandler.ts +18 -0
- package/src/codegen/handlers/enrichment/EnrichmentFunctionHandler.ts +38 -0
- package/src/codegen/handlers/enrichment/EnrichmentNullableObjectHandler.ts +44 -0
- package/src/codegen/handlers/enrichment/EnrichmentObjectHandler.ts +29 -0
- package/src/codegen/handlers/enrichment/EnrichmentObjectIdentityHandler.ts +16 -0
- package/src/codegen/handlers/enrichment/EnrichmentPrimitiveHandler.ts +13 -0
- package/src/codegen/handlers/enrichment/EnrichmentPrimitiveIdentityHandler.ts +21 -0
- package/src/codegen/handlers/freeze/FreezeObjectHandler.ts +20 -0
- package/src/codegen/handlers/freeze/FreezePrimitiveValueHandler.ts +15 -0
- package/src/codegen/handlers/hash/HashComputedValueHandler.ts +15 -0
- package/src/codegen/handlers/hash/HashDateHandler.ts +26 -0
- package/src/codegen/handlers/hash/HashFunctionHandler.ts +16 -0
- package/src/codegen/handlers/hash/HashIdentityHandler.ts +15 -0
- package/src/codegen/handlers/hash/HashKeyHandler.ts +26 -0
- package/src/codegen/handlers/hash/HashValueHandler.ts +26 -0
- package/src/codegen/handlers/hashType/HashTypeValueHandler.ts +20 -0
- package/src/codegen/handlers/idSelector/IdSelectorValueHandler.ts +28 -0
- package/src/codegen/handlers/index.ts +13 -0
- package/src/codegen/handlers/merge/MergeComputedValueHandler.ts +37 -0
- package/src/codegen/handlers/merge/MergeDefaultFunctionHandler.ts +40 -0
- package/src/codegen/handlers/merge/MergeDefaultValueHandler.ts +32 -0
- package/src/codegen/handlers/merge/MergeFunctionHandler.ts +16 -0
- package/src/codegen/handlers/merge/MergePrimitiveHandler.ts +21 -0
- package/src/codegen/handlers/prepare/PrepareComputedValueHandler.ts +16 -0
- package/src/codegen/handlers/prepare/PrepareFunctionHandler.ts +16 -0
- package/src/codegen/handlers/prepare/PrepareIdentityHandler.ts +21 -0
- package/src/codegen/handlers/prepare/PrepareKeyHandler.ts +21 -0
- package/src/codegen/handlers/prepare/PrepareObjectHandler.ts +38 -0
- package/src/codegen/handlers/prepare/PrepareValueHandler.ts +36 -0
- package/src/codegen/handlers/serialize/SerializeDateHandler.ts +45 -0
- package/src/codegen/handlers/serialize/SerializeObjectHandler.ts +28 -0
- package/src/codegen/handlers/serialize/SerializeValueHandler.ts +33 -0
- package/src/codegen/handlers/strip/StripIdentityHandler.ts +15 -0
- package/src/codegen/handlers/strip/StripKeyHandler.ts +15 -0
- package/src/codegen/handlers/strip/StripObjectHandler.ts +35 -0
- package/src/codegen/handlers/strip/StripValueHandler.ts +36 -0
- package/src/codegen/handlers/types.ts +119 -0
- package/src/codegen/index.ts +2 -0
- package/src/codegen/types.ts +2 -0
- package/src/codegen/utils.ts +74 -0
- package/src/collections/Changes.test.ts +337 -0
- package/src/collections/Changes.ts +177 -0
- package/src/collections/IdSet.ts +28 -0
- package/src/collections/MemoryDataCollection.test.ts +424 -0
- package/src/collections/MemoryDataCollection.ts +134 -0
- package/src/collections/SchemaCollection.ts +22 -0
- package/src/collections/TagCollection.test.ts +443 -0
- package/src/collections/TagCollection.ts +62 -0
- package/src/collections/index.ts +5 -0
- package/src/errors/SchemaError.ts +6 -0
- package/src/errors/index.ts +1 -0
- package/src/expressions/index.ts +3 -0
- package/src/expressions/parser.test.ts +913 -0
- package/src/expressions/parser.ts +661 -0
- package/src/expressions/types.ts +184 -0
- package/src/expressions/utils.test.ts +346 -0
- package/src/expressions/utils.ts +59 -0
- package/src/index.ts +12 -0
- package/src/performance/index.ts +26 -0
- package/src/pipeline/SyncronousQueue.test.ts +269 -0
- package/src/pipeline/SyncronousQueue.ts +30 -0
- package/src/pipeline/TrampolinePipeline.test.ts +374 -0
- package/src/pipeline/TrampolinePipeline.ts +437 -0
- package/src/pipeline/index.ts +2 -0
- package/src/plugins/EphemeralDataPlugin.ts +132 -0
- package/src/plugins/capabilities/DbPluginCapability.ts +111 -0
- package/src/plugins/capabilities/index.ts +2 -0
- package/src/plugins/capabilities/logging/DbPluginLoggingCapability.ts +261 -0
- package/src/plugins/capabilities/logging/index.ts +1 -0
- package/src/plugins/index.ts +6 -0
- package/src/plugins/query/Query.ts +67 -0
- package/src/plugins/query/QueryOptionsCollection.test.ts +86 -0
- package/src/plugins/query/QueryOptionsCollection.ts +154 -0
- package/src/plugins/query/index.ts +3 -0
- package/src/plugins/query/types.ts +46 -0
- package/src/plugins/replication/OptimisticReplicationDbPlugin.ts +202 -0
- package/src/plugins/replication/ReplicationDbPlugin.ts +137 -0
- package/src/plugins/replication/index.ts +3 -0
- package/src/plugins/replication/types.ts +5 -0
- package/src/plugins/translators/DataTranslator.ts +46 -0
- package/src/plugins/translators/JsonTranslator.test.ts +618 -0
- package/src/plugins/translators/JsonTranslator.ts +211 -0
- package/src/plugins/translators/SqlTranslator.ts +45 -0
- package/src/plugins/translators/index.ts +3 -0
- package/src/plugins/types.ts +114 -0
- package/src/results/Result.ts +91 -0
- package/src/results/index.ts +3 -0
- package/src/results/types.ts +17 -0
- package/src/results/utils.ts +15 -0
- package/src/schema/PropertyInfo.test.ts +479 -0
- package/src/schema/PropertyInfo.ts +374 -0
- package/src/schema/SchemaDefinition.ts +626 -0
- package/src/schema/builder.ts +18 -0
- package/src/schema/index.ts +7 -0
- package/src/schema/property/base/SchemaBase.ts +49 -0
- package/src/schema/property/base/index.ts +1 -0
- package/src/schema/property/modifiers/SchemaDefault.ts +29 -0
- package/src/schema/property/modifiers/SchemaDeserialize.ts +23 -0
- package/src/schema/property/modifiers/SchemaDistinct.ts +14 -0
- package/src/schema/property/modifiers/SchemaFrom.ts +54 -0
- package/src/schema/property/modifiers/SchemaIdentity.ts +13 -0
- package/src/schema/property/modifiers/SchemaIndex.ts +60 -0
- package/src/schema/property/modifiers/SchemaKey.ts +28 -0
- package/src/schema/property/modifiers/SchemaNullable.ts +18 -0
- package/src/schema/property/modifiers/SchemaOptional.ts +19 -0
- package/src/schema/property/modifiers/SchemaReadonly.ts +34 -0
- package/src/schema/property/modifiers/SchemaSerialize.ts +18 -0
- package/src/schema/property/modifiers/SchemaTracked.ts +14 -0
- package/src/schema/property/modifiers/index.ts +12 -0
- package/src/schema/property/types/SchemaArray.ts +50 -0
- package/src/schema/property/types/SchemaBoolean.ts +59 -0
- package/src/schema/property/types/SchemaDate.ts +58 -0
- package/src/schema/property/types/SchemaNumber.ts +69 -0
- package/src/schema/property/types/SchemaObject.ts +44 -0
- package/src/schema/property/types/SchemaString.ts +69 -0
- package/src/schema/property/types/index.ts +6 -0
- package/src/schema/table/SchemaComputed.ts +20 -0
- package/src/schema/table/SchemaFunction.ts +15 -0
- package/src/schema/table/index.ts +2 -0
- package/src/schema/types.ts +238 -0
- package/src/types/index.ts +5 -0
- package/src/utilities/arrays.test.ts +312 -0
- package/src/utilities/arrays.ts +12 -0
- package/src/utilities/dates.test.ts +388 -0
- package/src/utilities/dates.ts +11 -0
- package/src/utilities/dbPluginEventUtils.ts +44 -0
- package/src/utilities/index.ts +10 -0
- package/src/utilities/objects.ts +7 -0
- package/src/utilities/queryOptionsCollection.ts +16 -0
- package/src/utilities/replication.ts +23 -0
- package/src/utilities/runtime.ts +3 -0
- package/src/utilities/strings.ts +18 -0
- package/src/utilities/types.ts +1 -0
- package/src/utilities/uuid.ts +56 -0
- package/tsconfig.json +28 -0
- package/vitest.config.ts +11 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { CompiledSchema, IdType, InferType, PropertyInfo, SchemaTypes } from "../schema";
|
|
2
|
+
import { CallbackResult, Result } from "../results";
|
|
3
|
+
import { uuidv4 } from "../utilities";
|
|
4
|
+
import { IdSet } from "./IdSet";
|
|
5
|
+
|
|
6
|
+
export class MemoryDataCollection {
|
|
7
|
+
|
|
8
|
+
protected readonly nextNumericalIds: Map<string, number>;
|
|
9
|
+
protected readonly data: Map<string, Record<string, unknown>>;
|
|
10
|
+
protected readonly schema: CompiledSchema<any>;
|
|
11
|
+
protected readonly idProperties: PropertyInfo<any>[];
|
|
12
|
+
|
|
13
|
+
get size() {
|
|
14
|
+
return this.data.size;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
get records() {
|
|
18
|
+
return [...this.data.values()];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
constructor(schema: CompiledSchema<any>) {
|
|
22
|
+
this.data = new Map<string, Record<string, unknown>>();
|
|
23
|
+
this.nextNumericalIds = new Map<string, number>();
|
|
24
|
+
this.schema = schema;
|
|
25
|
+
this.idProperties = schema.idProperties;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private _getNextId(property: PropertyInfo<any>) {
|
|
29
|
+
|
|
30
|
+
let nextId = 1;
|
|
31
|
+
|
|
32
|
+
if (this.nextNumericalIds.has(property.id) === true) {
|
|
33
|
+
nextId = this.nextNumericalIds.get(property.id) + 1;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
this.nextNumericalIds.set(property.id, nextId);
|
|
37
|
+
|
|
38
|
+
return nextId;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private getAndSetId(item: Record<string, unknown>, property: PropertyInfo<any>): IdType {
|
|
42
|
+
if (item[property.name] != null) {
|
|
43
|
+
return item[property.name] as IdType;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (property.type === SchemaTypes.String) {
|
|
47
|
+
const uuid = uuidv4()
|
|
48
|
+
item[property.name] = uuid;
|
|
49
|
+
return uuid;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (property.type === SchemaTypes.Number) {
|
|
53
|
+
const id = this._getNextId(property);
|
|
54
|
+
item[property.name] = id;
|
|
55
|
+
return id;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
throw new Error(`Id Property '${property.name}' must be string or number, found '${property.type}'`)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
seed(items: Record<string, unknown>[]) {
|
|
62
|
+
for (let i = 0, length = items.length; i < length; i++) {
|
|
63
|
+
const id = this.resolveIdSet(items[i] as InferType<unknown>);
|
|
64
|
+
this.data.set(id.toString(), items[i]);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private resolveCurrentIdSet(item: Record<string, unknown>): IdSet {
|
|
69
|
+
const idProperties = this.schema.idProperties;
|
|
70
|
+
const ids: IdType[] = [];
|
|
71
|
+
// ensure keys
|
|
72
|
+
for (let j = 0, l = idProperties.length; j < l; j++) {
|
|
73
|
+
const property = idProperties[j];
|
|
74
|
+
const value = property.getValue(item);
|
|
75
|
+
|
|
76
|
+
if (value == null) {
|
|
77
|
+
throw new Error(`Key cannot be null. Key: ${property.name}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
ids.push(value);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return new IdSet(...ids);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private resolveIdSet(item: Record<string, unknown>): IdSet {
|
|
87
|
+
|
|
88
|
+
if (this.schema.hasIdentityKeys) {
|
|
89
|
+
const ids: IdType[] = [];
|
|
90
|
+
const idProperties = this.schema.idProperties;
|
|
91
|
+
|
|
92
|
+
if (idProperties.length === 1) {
|
|
93
|
+
ids.push(this.getAndSetId(item, idProperties[0]));
|
|
94
|
+
} else {
|
|
95
|
+
for (let j = 0, l = idProperties.length; j < l; j++) {
|
|
96
|
+
ids.push(this.getAndSetId(item, idProperties[j]));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return new IdSet(...ids);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return this.resolveCurrentIdSet(item);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
add(item: Record<string, unknown>) {
|
|
107
|
+
const id = this.resolveIdSet(item);
|
|
108
|
+
this.data.set(id.toString(), item);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
remove(item: Record<string, unknown>) {
|
|
112
|
+
const id = this.resolveCurrentIdSet(item);
|
|
113
|
+
this.data.delete(id.toString());
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
update(item: Record<string, unknown>) {
|
|
117
|
+
const id = this.resolveCurrentIdSet(item);
|
|
118
|
+
this.data.set(id.toString(), item);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
destroy(done: CallbackResult<never>) {
|
|
122
|
+
this.nextNumericalIds.clear();
|
|
123
|
+
this.data.clear();
|
|
124
|
+
done(Result.success());
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
load(done: CallbackResult<never>) {
|
|
128
|
+
done(Result.success())
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
save(done: CallbackResult<never>) {
|
|
132
|
+
done(Result.success())
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { CompiledSchema, SchemaId } from "../schema";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Collection of schemas with generic typing for type-safe schema retrieval
|
|
5
|
+
*/
|
|
6
|
+
export class SchemaCollection extends Map<SchemaId, CompiledSchema<Record<string, unknown>>> {
|
|
7
|
+
|
|
8
|
+
override get<T>(schemaId: SchemaId) {
|
|
9
|
+
return super.get(schemaId) as CompiledSchema<T>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
getByName<T>(collectionName: string): CompiledSchema<T> | undefined {
|
|
13
|
+
const found = [...this].find(x => x[1].collectionName === collectionName);
|
|
14
|
+
|
|
15
|
+
if (found != null) {
|
|
16
|
+
return found[1] as CompiledSchema<T>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
}
|
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { TagCollection } from './TagCollection';
|
|
3
|
+
|
|
4
|
+
describe('TagCollection', () => {
|
|
5
|
+
let tagCollection: TagCollection;
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
tagCollection = new TagCollection();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
describe('constructor', () => {
|
|
12
|
+
it('should create empty tag collection', () => {
|
|
13
|
+
expect(tagCollection.size).toBe(0);
|
|
14
|
+
expect(tagCollection.has('test')).toBe(false);
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe('size', () => {
|
|
19
|
+
it('should return 0 for empty collection', () => {
|
|
20
|
+
expect(tagCollection.size).toBe(0);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should return correct size after adding items', () => {
|
|
24
|
+
tagCollection.set('key1', 'value1');
|
|
25
|
+
expect(tagCollection.size).toBe(1);
|
|
26
|
+
|
|
27
|
+
tagCollection.set('key2', 'value2');
|
|
28
|
+
expect(tagCollection.size).toBe(2);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should return correct size after deleting items', () => {
|
|
32
|
+
tagCollection.set('key1', 'value1');
|
|
33
|
+
tagCollection.set('key2', 'value2');
|
|
34
|
+
expect(tagCollection.size).toBe(2);
|
|
35
|
+
|
|
36
|
+
tagCollection.delete('key1');
|
|
37
|
+
expect(tagCollection.size).toBe(1);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('get', () => {
|
|
42
|
+
it('should return undefined for non-existent key', () => {
|
|
43
|
+
expect(tagCollection.get('non-existent')).toBeUndefined();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should return value for existing key', () => {
|
|
47
|
+
const testValue = { data: 'test' };
|
|
48
|
+
tagCollection.set('test-key', testValue);
|
|
49
|
+
expect(tagCollection.get('test-key')).toBe(testValue);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should return different values for different keys', () => {
|
|
53
|
+
const value1 = 'value1';
|
|
54
|
+
const value2 = { nested: 'value2' };
|
|
55
|
+
|
|
56
|
+
tagCollection.set('key1', value1);
|
|
57
|
+
tagCollection.set('key2', value2);
|
|
58
|
+
|
|
59
|
+
expect(tagCollection.get('key1')).toBe(value1);
|
|
60
|
+
expect(tagCollection.get('key2')).toBe(value2);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('has', () => {
|
|
65
|
+
it('should return false for non-existent key', () => {
|
|
66
|
+
expect(tagCollection.has('non-existent')).toBe(false);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should return true for existing key', () => {
|
|
70
|
+
tagCollection.set('test-key', 'test-value');
|
|
71
|
+
expect(tagCollection.has('test-key')).toBe(true);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should return false after deleting key', () => {
|
|
75
|
+
tagCollection.set('test-key', 'test-value');
|
|
76
|
+
expect(tagCollection.has('test-key')).toBe(true);
|
|
77
|
+
|
|
78
|
+
tagCollection.delete('test-key');
|
|
79
|
+
expect(tagCollection.has('test-key')).toBe(false);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('set', () => {
|
|
84
|
+
it('should add new key-value pair', () => {
|
|
85
|
+
const result = tagCollection.set('new-key', 'new-value');
|
|
86
|
+
expect(tagCollection.has('new-key')).toBe(true);
|
|
87
|
+
expect(tagCollection.get('new-key')).toBe('new-value');
|
|
88
|
+
expect(tagCollection.size).toBe(1);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should overwrite existing key and increment size', () => {
|
|
92
|
+
tagCollection.set('key', 'old-value');
|
|
93
|
+
expect(tagCollection.get('key')).toBe('old-value');
|
|
94
|
+
expect(tagCollection.size).toBe(1);
|
|
95
|
+
|
|
96
|
+
tagCollection.set('key', 'new-value');
|
|
97
|
+
expect(tagCollection.get('key')).toBe('new-value');
|
|
98
|
+
expect(tagCollection.size).toBe(2);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should handle different value types', () => {
|
|
102
|
+
tagCollection.set('string', 'string-value');
|
|
103
|
+
tagCollection.set('number', 42);
|
|
104
|
+
tagCollection.set('object', { prop: 'value' });
|
|
105
|
+
tagCollection.set('array', [1, 2, 3]);
|
|
106
|
+
tagCollection.set('null', null);
|
|
107
|
+
tagCollection.set('undefined', undefined);
|
|
108
|
+
|
|
109
|
+
expect(tagCollection.get('string')).toBe('string-value');
|
|
110
|
+
expect(tagCollection.get('number')).toBe(42);
|
|
111
|
+
expect(tagCollection.get('object')).toEqual({ prop: 'value' });
|
|
112
|
+
expect(tagCollection.get('array')).toEqual([1, 2, 3]);
|
|
113
|
+
expect(tagCollection.get('null')).toBe(null);
|
|
114
|
+
expect(tagCollection.get('undefined')).toBe(undefined);
|
|
115
|
+
expect(tagCollection.size).toBe(6);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe('delete', () => {
|
|
120
|
+
it('should return false for non-existent key', () => {
|
|
121
|
+
expect(tagCollection.delete('non-existent')).toBe(false);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should return true and remove existing key', () => {
|
|
125
|
+
tagCollection.set('test-key', 'test-value');
|
|
126
|
+
expect(tagCollection.has('test-key')).toBe(true);
|
|
127
|
+
|
|
128
|
+
const result = tagCollection.delete('test-key');
|
|
129
|
+
expect(result).toBe(true);
|
|
130
|
+
expect(tagCollection.has('test-key')).toBe(false);
|
|
131
|
+
expect(tagCollection.get('test-key')).toBeUndefined();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('should decrease size when deleting existing key', () => {
|
|
135
|
+
tagCollection.set('key1', 'value1');
|
|
136
|
+
tagCollection.set('key2', 'value2');
|
|
137
|
+
expect(tagCollection.size).toBe(2);
|
|
138
|
+
|
|
139
|
+
tagCollection.delete('key1');
|
|
140
|
+
expect(tagCollection.size).toBe(1);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should not decrease size when deleting non-existent key', () => {
|
|
144
|
+
tagCollection.set('key1', 'value1');
|
|
145
|
+
expect(tagCollection.size).toBe(1);
|
|
146
|
+
|
|
147
|
+
tagCollection.delete('non-existent');
|
|
148
|
+
expect(tagCollection.size).toBe(1);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe('setMany', () => {
|
|
153
|
+
it('should add multiple keys with same value', () => {
|
|
154
|
+
const keys = ['key1', 'key2', 'key3'];
|
|
155
|
+
const value = 'shared-value';
|
|
156
|
+
|
|
157
|
+
tagCollection.setMany(keys, value);
|
|
158
|
+
|
|
159
|
+
expect(tagCollection.size).toBe(6);
|
|
160
|
+
expect(tagCollection.get('key1')).toBe(value);
|
|
161
|
+
expect(tagCollection.get('key2')).toBe(value);
|
|
162
|
+
expect(tagCollection.get('key3')).toBe(value);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should handle empty keys array', () => {
|
|
166
|
+
tagCollection.setMany([], 'value');
|
|
167
|
+
expect(tagCollection.size).toBe(0);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('should handle single key', () => {
|
|
171
|
+
tagCollection.setMany(['single-key'], 'single-value');
|
|
172
|
+
expect(tagCollection.size).toBe(2);
|
|
173
|
+
expect(tagCollection.get('single-key')).toBe('single-value');
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('should overwrite existing keys', () => {
|
|
177
|
+
tagCollection.set('key1', 'old-value');
|
|
178
|
+
tagCollection.set('key2', 'old-value');
|
|
179
|
+
|
|
180
|
+
tagCollection.setMany(['key1', 'key2', 'key3'], 'new-value');
|
|
181
|
+
|
|
182
|
+
expect(tagCollection.size).toBe(8);
|
|
183
|
+
expect(tagCollection.get('key1')).toBe('new-value');
|
|
184
|
+
expect(tagCollection.get('key2')).toBe('new-value');
|
|
185
|
+
expect(tagCollection.get('key3')).toBe('new-value');
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
describe('combine', () => {
|
|
190
|
+
it('should combine two tag collections', () => {
|
|
191
|
+
const otherCollection = new TagCollection();
|
|
192
|
+
otherCollection.set('key1', 'value1');
|
|
193
|
+
otherCollection.set('key2', 'value2');
|
|
194
|
+
|
|
195
|
+
tagCollection.set('key3', 'value3');
|
|
196
|
+
tagCollection.combine(otherCollection);
|
|
197
|
+
|
|
198
|
+
expect(tagCollection.size).toBe(1);
|
|
199
|
+
expect(tagCollection.get('key1')).toBe('value1');
|
|
200
|
+
expect(tagCollection.get('key2')).toBe('value2');
|
|
201
|
+
expect(tagCollection.get('key3')).toBe('value3');
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('should overwrite existing keys when combining', () => {
|
|
205
|
+
tagCollection.set('key1', 'old-value');
|
|
206
|
+
tagCollection.set('key2', 'old-value');
|
|
207
|
+
|
|
208
|
+
const otherCollection = new TagCollection();
|
|
209
|
+
otherCollection.set('key1', 'new-value');
|
|
210
|
+
otherCollection.set('key3', 'new-value');
|
|
211
|
+
|
|
212
|
+
tagCollection.combine(otherCollection);
|
|
213
|
+
|
|
214
|
+
expect(tagCollection.size).toBe(2);
|
|
215
|
+
expect(tagCollection.get('key1')).toBe('new-value');
|
|
216
|
+
expect(tagCollection.get('key2')).toBe('old-value');
|
|
217
|
+
expect(tagCollection.get('key3')).toBe('new-value');
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('should handle empty collection', () => {
|
|
221
|
+
const emptyCollection = new TagCollection();
|
|
222
|
+
tagCollection.set('key1', 'value1');
|
|
223
|
+
|
|
224
|
+
tagCollection.combine(emptyCollection);
|
|
225
|
+
|
|
226
|
+
expect(tagCollection.size).toBe(1);
|
|
227
|
+
expect(tagCollection.get('key1')).toBe('value1');
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('should combine with empty target collection', () => {
|
|
231
|
+
const otherCollection = new TagCollection();
|
|
232
|
+
otherCollection.set('key1', 'value1');
|
|
233
|
+
otherCollection.set('key2', 'value2');
|
|
234
|
+
|
|
235
|
+
tagCollection.combine(otherCollection);
|
|
236
|
+
|
|
237
|
+
expect(tagCollection.size).toBe(0);
|
|
238
|
+
expect(tagCollection.get('key1')).toBe('value1');
|
|
239
|
+
expect(tagCollection.get('key2')).toBe('value2');
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
describe('values', () => {
|
|
244
|
+
it('should return empty iterator for empty collection', () => {
|
|
245
|
+
const values = Array.from(tagCollection.values());
|
|
246
|
+
expect(values).toEqual([]);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('should return all values', () => {
|
|
250
|
+
tagCollection.set('key1', 'value1');
|
|
251
|
+
tagCollection.set('key2', 'value2');
|
|
252
|
+
tagCollection.set('key3', 'value3');
|
|
253
|
+
|
|
254
|
+
const values = Array.from(tagCollection.values());
|
|
255
|
+
expect(values).toHaveLength(3);
|
|
256
|
+
expect(values).toEqual(expect.arrayContaining(['value1', 'value2', 'value3']));
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('should return unique values when keys have same value', () => {
|
|
260
|
+
tagCollection.set('key1', 'shared-value');
|
|
261
|
+
tagCollection.set('key2', 'shared-value');
|
|
262
|
+
tagCollection.set('key3', 'different-value');
|
|
263
|
+
|
|
264
|
+
const values = Array.from(tagCollection.values());
|
|
265
|
+
expect(values).toHaveLength(3);
|
|
266
|
+
expect(values).toEqual(expect.arrayContaining(['shared-value', 'shared-value', 'different-value']));
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
describe('keys', () => {
|
|
271
|
+
it('should return empty iterator for empty collection', () => {
|
|
272
|
+
const keys = Array.from(tagCollection.keys());
|
|
273
|
+
expect(keys).toEqual([]);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('should return all keys', () => {
|
|
277
|
+
tagCollection.set('key1', 'value1');
|
|
278
|
+
tagCollection.set('key2', 'value2');
|
|
279
|
+
tagCollection.set('key3', 'value3');
|
|
280
|
+
|
|
281
|
+
const keys = Array.from(tagCollection.keys());
|
|
282
|
+
expect(keys).toHaveLength(3);
|
|
283
|
+
expect(keys).toEqual(expect.arrayContaining(['key1', 'key2', 'key3']));
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
describe('iterator', () => {
|
|
288
|
+
it('should iterate over key-value pairs', () => {
|
|
289
|
+
tagCollection.set('key1', 'value1');
|
|
290
|
+
tagCollection.set('key2', 'value2');
|
|
291
|
+
|
|
292
|
+
const entries = Array.from(tagCollection);
|
|
293
|
+
expect(entries).toHaveLength(2);
|
|
294
|
+
expect(entries).toEqual(expect.arrayContaining([
|
|
295
|
+
['key1', 'value1'],
|
|
296
|
+
['key2', 'value2']
|
|
297
|
+
]));
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it('should return empty iterator for empty collection', () => {
|
|
301
|
+
const entries = Array.from(tagCollection);
|
|
302
|
+
expect(entries).toEqual([]);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it('should iterate in insertion order', () => {
|
|
306
|
+
tagCollection.set('key1', 'value1');
|
|
307
|
+
tagCollection.set('key2', 'value2');
|
|
308
|
+
tagCollection.set('key3', 'value3');
|
|
309
|
+
|
|
310
|
+
const entries = Array.from(tagCollection);
|
|
311
|
+
expect(entries[0]).toEqual(['key1', 'value1']);
|
|
312
|
+
expect(entries[1]).toEqual(['key2', 'value2']);
|
|
313
|
+
expect(entries[2]).toEqual(['key3', 'value3']);
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
describe('dispose', () => {
|
|
318
|
+
it('should clear data when disposed', () => {
|
|
319
|
+
tagCollection.set('key1', 'value1');
|
|
320
|
+
tagCollection.set('key2', 'value2');
|
|
321
|
+
|
|
322
|
+
tagCollection[Symbol.dispose]();
|
|
323
|
+
|
|
324
|
+
expect(tagCollection.size).toBe(2);
|
|
325
|
+
expect(() => tagCollection.has('key1')).toThrow();
|
|
326
|
+
expect(() => tagCollection.has('key2')).toThrow();
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it('should handle multiple dispose calls', () => {
|
|
330
|
+
tagCollection.set('key1', 'value1');
|
|
331
|
+
tagCollection[Symbol.dispose]();
|
|
332
|
+
tagCollection[Symbol.dispose]();
|
|
333
|
+
|
|
334
|
+
expect(tagCollection.size).toBe(1);
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
describe('Edge Cases and Error Handling', () => {
|
|
339
|
+
it('should handle null and undefined keys', () => {
|
|
340
|
+
tagCollection.set(null as any, 'null-value');
|
|
341
|
+
tagCollection.set(undefined as any, 'undefined-value');
|
|
342
|
+
|
|
343
|
+
expect(tagCollection.has(null as any)).toBe(true);
|
|
344
|
+
expect(tagCollection.has(undefined as any)).toBe(true);
|
|
345
|
+
expect(tagCollection.get(null as any)).toBe('null-value');
|
|
346
|
+
expect(tagCollection.get(undefined as any)).toBe('undefined-value');
|
|
347
|
+
expect(tagCollection.size).toBe(2);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it('should handle object keys', () => {
|
|
351
|
+
const objKey1 = { id: 1 };
|
|
352
|
+
const objKey2 = { id: 2 };
|
|
353
|
+
|
|
354
|
+
tagCollection.set(objKey1, 'object-value-1');
|
|
355
|
+
tagCollection.set(objKey2, 'object-value-2');
|
|
356
|
+
|
|
357
|
+
expect(tagCollection.has(objKey1)).toBe(true);
|
|
358
|
+
expect(tagCollection.has(objKey2)).toBe(true);
|
|
359
|
+
expect(tagCollection.get(objKey1)).toBe('object-value-1');
|
|
360
|
+
expect(tagCollection.get(objKey2)).toBe('object-value-2');
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
it('should handle function keys', () => {
|
|
364
|
+
const funcKey = () => { };
|
|
365
|
+
tagCollection.set(funcKey, 'function-value');
|
|
366
|
+
|
|
367
|
+
expect(tagCollection.has(funcKey)).toBe(true);
|
|
368
|
+
expect(tagCollection.get(funcKey)).toBe('function-value');
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
it('should handle large number of items', () => {
|
|
372
|
+
const largeNumber = 1000;
|
|
373
|
+
for (let i = 0; i < largeNumber; i++) {
|
|
374
|
+
tagCollection.set(`key${i}`, `value${i}`);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
expect(tagCollection.size).toBe(largeNumber);
|
|
378
|
+
expect(tagCollection.get('key0')).toBe('value0');
|
|
379
|
+
expect(tagCollection.get(`key${largeNumber - 1}`)).toBe(`value${largeNumber - 1}`);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
it('should handle rapid add and delete operations', () => {
|
|
383
|
+
for (let i = 0; i < 100; i++) {
|
|
384
|
+
tagCollection.set(`key${i}`, `value${i}`);
|
|
385
|
+
tagCollection.delete(`key${i}`);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
expect(tagCollection.size).toBe(0);
|
|
389
|
+
});
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
describe('Integration Tests', () => {
|
|
393
|
+
it('should handle complex workflow', () => {
|
|
394
|
+
tagCollection.set('initial', 'initial-value');
|
|
395
|
+
expect(tagCollection.size).toBe(1);
|
|
396
|
+
|
|
397
|
+
tagCollection.setMany(['batch1', 'batch2', 'batch3'], 'batch-value');
|
|
398
|
+
expect(tagCollection.size).toBe(7);
|
|
399
|
+
|
|
400
|
+
const otherCollection = new TagCollection();
|
|
401
|
+
otherCollection.set('external1', 'external-value1');
|
|
402
|
+
otherCollection.set('external2', 'external-value2');
|
|
403
|
+
|
|
404
|
+
tagCollection.combine(otherCollection);
|
|
405
|
+
expect(tagCollection.size).toBe(7);
|
|
406
|
+
|
|
407
|
+
tagCollection.delete('initial');
|
|
408
|
+
expect(tagCollection.size).toBe(6);
|
|
409
|
+
|
|
410
|
+
const values = Array.from(tagCollection.values());
|
|
411
|
+
expect(values).toHaveLength(5);
|
|
412
|
+
expect(values).toEqual(expect.arrayContaining([
|
|
413
|
+
'batch-value',
|
|
414
|
+
'external-value1',
|
|
415
|
+
'external-value2'
|
|
416
|
+
]));
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
it('should maintain consistency after multiple operations', () => {
|
|
420
|
+
const keys = ['key1', 'key2', 'key3', 'key4', 'key5'];
|
|
421
|
+
const values = ['value1', 'value2', 'value3', 'value4', 'value5'];
|
|
422
|
+
|
|
423
|
+
for (let i = 0; i < keys.length; i++) {
|
|
424
|
+
tagCollection.set(keys[i], values[i]);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
expect(tagCollection.size).toBe(5);
|
|
428
|
+
|
|
429
|
+
tagCollection.delete('key2');
|
|
430
|
+
tagCollection.delete('key4');
|
|
431
|
+
|
|
432
|
+
expect(tagCollection.size).toBe(3);
|
|
433
|
+
expect(tagCollection.has('key1')).toBe(true);
|
|
434
|
+
expect(tagCollection.has('key2')).toBe(false);
|
|
435
|
+
expect(tagCollection.has('key3')).toBe(true);
|
|
436
|
+
expect(tagCollection.has('key4')).toBe(false);
|
|
437
|
+
expect(tagCollection.has('key5')).toBe(true);
|
|
438
|
+
|
|
439
|
+
const remainingValues = Array.from(tagCollection.values());
|
|
440
|
+
expect(remainingValues).toEqual(expect.arrayContaining(['value1', 'value3', 'value5']));
|
|
441
|
+
});
|
|
442
|
+
});
|
|
443
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export class TagCollection implements Disposable {
|
|
2
|
+
|
|
3
|
+
private data: Map<Object, unknown> = new Map<Object, unknown>();
|
|
4
|
+
private _size: number = 0;
|
|
5
|
+
|
|
6
|
+
get size() {
|
|
7
|
+
return this._size;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
get(key: Object) {
|
|
11
|
+
return this.data.get(key);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
has(key: Object) {
|
|
15
|
+
return this.data.has(key);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
set(key: Object, tag: unknown) {
|
|
19
|
+
this._size++;
|
|
20
|
+
return this.data.set(key, tag);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
delete(key: Object) {
|
|
24
|
+
const result = this.data.delete(key);
|
|
25
|
+
|
|
26
|
+
if (result) {
|
|
27
|
+
this._size--;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
setMany(keys: Object[], tag: unknown) {
|
|
34
|
+
this._size += keys.length;
|
|
35
|
+
for (let i = 0, length = keys.length; i < length; i++) {
|
|
36
|
+
const key = keys[i];
|
|
37
|
+
this.set(key, tag);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
combine(tags: TagCollection) {
|
|
42
|
+
for (const [key, value] of tags) {
|
|
43
|
+
this.data.set(key, value);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
values() {
|
|
48
|
+
return this.data.values();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
keys() {
|
|
52
|
+
return this.data.keys();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
[Symbol.dispose]() {
|
|
56
|
+
this.data = null as any;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
[Symbol.iterator]() {
|
|
60
|
+
return this.data[Symbol.iterator]();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SchemaError } from './SchemaError';
|