adaptive-extender 0.10.5 → 0.11.0
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 +13 -1
- package/README.md +3 -3
- package/dist/core/array.js +17 -0
- package/dist/core/date.js +33 -0
- package/dist/core/global.js +0 -1
- package/dist/core/map.js +47 -1
- package/dist/core/math.js +17 -6
- package/dist/core/portable.js +249 -315
- package/dist/core/reflect.js +0 -15
- package/dist/core/set.js +17 -1
- package/dist/web/archive.js +59 -44
- package/dist/web/metadata-injector.js +16 -16
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## 0.11.0 (21.06.2026)
|
|
2
|
+
- Added `Array.Of(type)` portable adapter for typed arrays, alongside the existing `Array.import`/`Array.export`.
|
|
3
|
+
- Added `Set.Of(type)` portable adapter for typed sets.
|
|
4
|
+
- Added `Map.AsRecord(type)` and `Map.AsTuples(typeKey, typeValue)` portable adapters (moved from a separate module into [map](./src/core/map.ts)).
|
|
5
|
+
- Added `Date.AsTimestamp` and `Date.AsUnixSeconds` portable adapters converting `Date` to millisecond and Unix-second numbers respectively.
|
|
6
|
+
- Added `Number.prototype.snap(step)` — snaps a number to the nearest multiple of the given step.
|
|
7
|
+
- **Breaking:** Removed `Reflect.mapNull`, `Reflect.mapUndefined`, and `Reflect.mapNullable`. Use `Nullable.map`, `Optional.map` from the portable module instead.
|
|
8
|
+
- **Breaking:** Renamed `Archive`/`ArchiveManager`/`ArchiveRepository` to `Cell`/`PortableCell`/`BufferedCell`. Construction is now done via `Storage` factory methods (`localStorage.openCell`, `localStorage.openPortableCell`, `localStorage.openBufferedCell`) — the storage backend is no longer hardcoded to `localStorage`.
|
|
9
|
+
- **Breaking:** `@Field` decorator no longer accepts a positional string as the second argument. Use `{ name: "..." }` options object: `@Field(String, { name: "fieldName" })`.
|
|
10
|
+
- `PortableCell.save()` returns `Promise<boolean>` — resolves `true` when persisted, `false` if cancelled (superseded or aborted); rejects only on serialization failure.
|
|
11
|
+
- Internal: `portable.ts` de-anemia refactor — `FieldDescriptor`, `DescendantDescriptor`, and `ModelSchema` are now rich objects with their own behavior (`importInto`, `exportFrom`, `accepts`, `owns`); `SchemaResolver` absorbed into `ModelSchema.resolve()`; `Model.import`/`Model.export` are thin facades.
|
|
12
|
+
|
|
1
13
|
## 0.10.5 (13.06.2026)
|
|
2
14
|
- Bugfix at [portable](./src/core/portable.ts).
|
|
3
15
|
|
|
@@ -16,7 +28,7 @@
|
|
|
16
28
|
## 0.9.13 (06.04.2026)
|
|
17
29
|
- Added `Version` class for semantic versioning with `parse()`, `tryParse()` support.
|
|
18
30
|
- Added `BigInt` portable support via `BigInt.import()` and `BigInt.export()`.
|
|
19
|
-
- `ArchiveRepository.save()` is now `async` and returns `Promise<
|
|
31
|
+
- `ArchiveRepository.save()` is now `async` and returns `Promise<boolean>`. The promise resolves `true` when the save completes, `false` if cancelled (superseded by a subsequent call or aborted via `abort()`), and rejects only on serialization failure.
|
|
20
32
|
- `Promise.withSignal` now has a default type parameter `T = void`.
|
|
21
33
|
- Renamed `EnumFrom` adapter to `EnumAs`.
|
|
22
34
|
|
package/README.md
CHANGED
|
@@ -412,10 +412,10 @@ const repository = new ArchiveRepository("settings", Settings, new Settings());
|
|
|
412
412
|
const settings = repository.content;
|
|
413
413
|
settings.volume = 0.5;
|
|
414
414
|
|
|
415
|
-
// save()
|
|
416
|
-
await repository.save(); // save immediately
|
|
415
|
+
// save() resolves true when persisted, false if cancelled (superseded or aborted), rejects only on serialization failure
|
|
416
|
+
const saved = await repository.save(); // save immediately
|
|
417
417
|
await repository.save(3000); // debounced — save after 3 seconds
|
|
418
|
-
// A pending save
|
|
418
|
+
// A pending save resolves false when superseded by a new call or cancelled by abort()
|
|
419
419
|
repository.reset(); // abort pending save and revert to initial state
|
|
420
420
|
```
|
|
421
421
|
|
package/dist/core/array.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
import "./global.js";
|
|
3
3
|
import {} from "./promise.js";
|
|
4
|
+
import {} from "./portable.js";
|
|
4
5
|
const { trunc } = Math;
|
|
5
6
|
Array.import = function (source, name) {
|
|
6
7
|
if (!Array.isArray(source))
|
|
@@ -10,6 +11,22 @@ Array.import = function (source, name) {
|
|
|
10
11
|
Array.export = function (source) {
|
|
11
12
|
return source;
|
|
12
13
|
};
|
|
14
|
+
Array.Of = function (type) {
|
|
15
|
+
return {
|
|
16
|
+
[Symbol.hasInstance](instance) {
|
|
17
|
+
return Array[Symbol.hasInstance](instance);
|
|
18
|
+
},
|
|
19
|
+
get name() {
|
|
20
|
+
return `${type.name}[]`;
|
|
21
|
+
},
|
|
22
|
+
import(source, name) {
|
|
23
|
+
return Array.import(source, name).map((item, index) => type.import(item, `${name}[${index}]`));
|
|
24
|
+
},
|
|
25
|
+
export(source) {
|
|
26
|
+
return source.map(item => type.export(item));
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
};
|
|
13
30
|
Array.range = function (min, max) {
|
|
14
31
|
min = trunc(min);
|
|
15
32
|
max = trunc(max);
|
package/dist/core/date.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
import "./global.js";
|
|
3
|
+
import {} from "./portable.js";
|
|
3
4
|
Date.import = function (source, name) {
|
|
4
5
|
if (typeof (source) !== "string")
|
|
5
6
|
throw new TypeError(`Unable to import date from ${name} due its ${typename(source)} type`);
|
|
@@ -13,6 +14,38 @@ Date.isInvalid = function (date) {
|
|
|
13
14
|
return false;
|
|
14
15
|
return Number.isNaN(date.getTime());
|
|
15
16
|
};
|
|
17
|
+
Date.AsTimestamp = {
|
|
18
|
+
[Symbol.hasInstance](instance) {
|
|
19
|
+
return Date[Symbol.hasInstance](instance);
|
|
20
|
+
},
|
|
21
|
+
get name() {
|
|
22
|
+
return "Timestamp";
|
|
23
|
+
},
|
|
24
|
+
import(source, name) {
|
|
25
|
+
if (typeof (source) !== "number")
|
|
26
|
+
throw new TypeError(`Unable to import date from ${name} due its ${typename(source)} type`);
|
|
27
|
+
return new Date(source);
|
|
28
|
+
},
|
|
29
|
+
export(source) {
|
|
30
|
+
return source.getTime();
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
Date.AsUnixSeconds = {
|
|
34
|
+
[Symbol.hasInstance](instance) {
|
|
35
|
+
return Date[Symbol.hasInstance](instance);
|
|
36
|
+
},
|
|
37
|
+
get name() {
|
|
38
|
+
return "UnixSeconds";
|
|
39
|
+
},
|
|
40
|
+
import(source, name) {
|
|
41
|
+
if (typeof (source) !== "number")
|
|
42
|
+
throw new TypeError(`Unable to import date from ${name} due its ${typename(source)} type`);
|
|
43
|
+
return new Date(source * 1000);
|
|
44
|
+
},
|
|
45
|
+
export(source) {
|
|
46
|
+
return Math.trunc(source.getTime() / 1000);
|
|
47
|
+
},
|
|
48
|
+
};
|
|
16
49
|
Date.prototype.insteadInvalid = function (value) {
|
|
17
50
|
if (Date.isInvalid(this))
|
|
18
51
|
return value;
|
package/dist/core/global.js
CHANGED
package/dist/core/map.js
CHANGED
|
@@ -1,8 +1,54 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
import {} from "./portable.js";
|
|
3
|
+
Map.AsRecord = function (type) {
|
|
4
|
+
return {
|
|
5
|
+
[Symbol.hasInstance](instance) {
|
|
6
|
+
return Map[Symbol.hasInstance](instance);
|
|
7
|
+
},
|
|
8
|
+
get name() {
|
|
9
|
+
return `Map<string, ${type.name}>`;
|
|
10
|
+
},
|
|
11
|
+
import(source, name) {
|
|
12
|
+
const record = Object.import(source, name);
|
|
13
|
+
const map = new Map();
|
|
14
|
+
for (const key of Object.keys(record)) {
|
|
15
|
+
map.set(key, type.import(Reflect.get(record, key), `${name}[${JSON.stringify(key)}]`));
|
|
16
|
+
}
|
|
17
|
+
return map;
|
|
18
|
+
},
|
|
19
|
+
export(source) {
|
|
20
|
+
const record = {};
|
|
21
|
+
for (const [key, value] of source) {
|
|
22
|
+
Reflect.set(record, key, type.export(value));
|
|
23
|
+
}
|
|
24
|
+
return record;
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
Map.AsTuples = function (typeKey, typeValue) {
|
|
29
|
+
return {
|
|
30
|
+
[Symbol.hasInstance](instance) {
|
|
31
|
+
return Map[Symbol.hasInstance](instance);
|
|
32
|
+
},
|
|
33
|
+
get name() {
|
|
34
|
+
return `Map<${typeKey.name}, ${typeValue.name}>`;
|
|
35
|
+
},
|
|
36
|
+
import(source, name) {
|
|
37
|
+
return new Map(Array.import(source, name).map((item, index) => {
|
|
38
|
+
const tuple = Array.import(item, `${name}[${index}]`);
|
|
39
|
+
const key = typeKey.import(tuple[0], `${name}[${index}][0]`);
|
|
40
|
+
const value = typeValue.import(tuple[1], `${name}[${index}][1]`);
|
|
41
|
+
return [key, value];
|
|
42
|
+
}));
|
|
43
|
+
},
|
|
44
|
+
export(source) {
|
|
45
|
+
return Array.from(source, ([key, value]) => [typeKey.export(key), typeValue.export(value)]);
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
};
|
|
2
49
|
Map.prototype.add = function (key, value) {
|
|
3
50
|
if (this.has(key))
|
|
4
51
|
return false;
|
|
5
52
|
this.set(key, value);
|
|
6
53
|
return true;
|
|
7
54
|
};
|
|
8
|
-
export {};
|
package/dist/core/math.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
const { PI, trunc, pow } = Math;
|
|
2
|
+
const { PI, trunc, pow, round } = Math;
|
|
3
3
|
Number.prototype.clamp = function (min, max) {
|
|
4
4
|
let result = this.valueOf();
|
|
5
5
|
if (result < min)
|
|
@@ -8,6 +8,11 @@ Number.prototype.clamp = function (min, max) {
|
|
|
8
8
|
return max;
|
|
9
9
|
return result;
|
|
10
10
|
};
|
|
11
|
+
function normalize(value, min, max) {
|
|
12
|
+
if (min === max)
|
|
13
|
+
throw new Error("Minimum and maximum of the original range cant be equal");
|
|
14
|
+
return (value - min) / (max - min);
|
|
15
|
+
}
|
|
11
16
|
function lerp(value, min1, max1, min2, max2) {
|
|
12
17
|
if (min1 === max1)
|
|
13
18
|
throw new Error("Minimum and maximum of the original range cant be equal");
|
|
@@ -17,18 +22,24 @@ function lerp(value, min1, max1, min2, max2) {
|
|
|
17
22
|
}
|
|
18
23
|
Number.prototype.lerp = function (min1, max1, min2, max2) {
|
|
19
24
|
if (min2 === undefined || max2 === undefined)
|
|
20
|
-
return
|
|
25
|
+
return normalize(this.valueOf(), min1, max1);
|
|
21
26
|
return lerp(this.valueOf(), min1, max1, min2, max2);
|
|
22
27
|
};
|
|
23
|
-
function
|
|
28
|
+
function repeat(value, length) {
|
|
24
29
|
if (length === 0)
|
|
25
30
|
throw new RangeError("Length must not be zero");
|
|
26
|
-
return (
|
|
31
|
+
return (value % length + length) % length;
|
|
32
|
+
}
|
|
33
|
+
function wrap(value, start, length) {
|
|
34
|
+
return repeat(value - start, length) + start;
|
|
27
35
|
}
|
|
28
36
|
Number.prototype.mod = function (arg1, arg2) {
|
|
29
37
|
if (arg2 === undefined)
|
|
30
|
-
return
|
|
31
|
-
return
|
|
38
|
+
return repeat(this.valueOf(), arg1);
|
|
39
|
+
return wrap(this.valueOf(), arg1, arg2);
|
|
40
|
+
};
|
|
41
|
+
Number.prototype.snap = function (step) {
|
|
42
|
+
return round(this.valueOf() / step) * step;
|
|
32
43
|
};
|
|
33
44
|
Math.split = function (x) {
|
|
34
45
|
const integer = trunc(x);
|
package/dist/core/portable.js
CHANGED
|
@@ -9,28 +9,35 @@ import "./reflect.js";
|
|
|
9
9
|
import "./error.js";
|
|
10
10
|
import {} from "./global.js";
|
|
11
11
|
//#endregion
|
|
12
|
-
//#region
|
|
12
|
+
//#region Descriptors
|
|
13
|
+
Reflect.set(Symbol, "metadata", Reflect.get(Symbol, "metadata") ?? Symbol.for("Symbol.metadata"));
|
|
13
14
|
class FieldDescriptor {
|
|
14
15
|
#key;
|
|
15
16
|
#association;
|
|
16
17
|
#type;
|
|
17
|
-
|
|
18
|
+
#hasFallback = false;
|
|
19
|
+
#fallback;
|
|
20
|
+
constructor(key, type, options = {}) {
|
|
18
21
|
this.#key = key;
|
|
19
|
-
this.#association =
|
|
22
|
+
this.#association = options.name ?? key;
|
|
20
23
|
this.#type = type;
|
|
24
|
+
if (!("fallback" in options))
|
|
25
|
+
return;
|
|
26
|
+
this.#hasFallback = true;
|
|
27
|
+
this.#fallback = type.export(options.fallback);
|
|
21
28
|
}
|
|
22
|
-
get key() {
|
|
23
|
-
|
|
29
|
+
get key() { return this.#key; }
|
|
30
|
+
get association() { return this.#association; }
|
|
31
|
+
import(object, name) {
|
|
32
|
+
const association = this.#association;
|
|
33
|
+
const raw = Reflect.get(object, association);
|
|
34
|
+
const source = raw === undefined && this.#hasFallback ? this.#fallback : raw;
|
|
35
|
+
return this.#type.import(source, `${name}.${association}`);
|
|
24
36
|
}
|
|
25
|
-
|
|
26
|
-
return this.#
|
|
27
|
-
}
|
|
28
|
-
get type() {
|
|
29
|
-
return this.#type;
|
|
37
|
+
export(source) {
|
|
38
|
+
return this.#type.export(Reflect.get(source, this.#key));
|
|
30
39
|
}
|
|
31
40
|
}
|
|
32
|
-
//#endregion
|
|
33
|
-
//#region Descendant descriptor
|
|
34
41
|
class DescendantDescriptor {
|
|
35
42
|
#type;
|
|
36
43
|
#discriminator;
|
|
@@ -38,15 +45,99 @@ class DescendantDescriptor {
|
|
|
38
45
|
this.#type = type;
|
|
39
46
|
this.#discriminator = discriminator;
|
|
40
47
|
}
|
|
41
|
-
get
|
|
42
|
-
|
|
48
|
+
get discriminator() { return this.#discriminator ?? this.#type.name; }
|
|
49
|
+
accepts(discriminator) {
|
|
50
|
+
return this.discriminator === discriminator;
|
|
51
|
+
}
|
|
52
|
+
owns(instance) {
|
|
53
|
+
return instance instanceof this.#type;
|
|
43
54
|
}
|
|
44
|
-
|
|
45
|
-
return this.#
|
|
55
|
+
import(source, name) {
|
|
56
|
+
return this.#type.import(source, name);
|
|
57
|
+
}
|
|
58
|
+
export(source) {
|
|
59
|
+
return this.#type.export(source);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
class ModelSchema {
|
|
63
|
+
static #schemas = new WeakMap();
|
|
64
|
+
static #collect(object) {
|
|
65
|
+
const fields = new Map();
|
|
66
|
+
let current = object;
|
|
67
|
+
while (current !== null) {
|
|
68
|
+
for (const [key, descriptor] of PortabilityMetadata.for(current).fields)
|
|
69
|
+
fields.add(key, descriptor);
|
|
70
|
+
current = Object.getPrototypeOf(current);
|
|
71
|
+
}
|
|
72
|
+
return fields;
|
|
73
|
+
}
|
|
74
|
+
static resolve(model) {
|
|
75
|
+
const schemas = ModelSchema.#schemas;
|
|
76
|
+
let schema = schemas.get(model);
|
|
77
|
+
if (schema !== undefined)
|
|
78
|
+
return schema;
|
|
79
|
+
const object = ReferenceError.suppress(model[Symbol.metadata], `Required an implementation of Symbol.metadata in '${model.name}' to use portability`);
|
|
80
|
+
const { discriminator, descendants } = Object.hasOwn(model, Symbol.metadata)
|
|
81
|
+
? PortabilityMetadata.for(object)
|
|
82
|
+
: new PortabilityMetadata();
|
|
83
|
+
schema = descendants.length > 0
|
|
84
|
+
? new PolymorphicSchema(discriminator, descendants)
|
|
85
|
+
: new ConcreteSchema(model, ModelSchema.#collect(object));
|
|
86
|
+
schemas.set(model, schema);
|
|
87
|
+
return schema;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
class ConcreteSchema extends ModelSchema {
|
|
91
|
+
#model;
|
|
92
|
+
#fields;
|
|
93
|
+
constructor(model, fields) {
|
|
94
|
+
super();
|
|
95
|
+
this.#model = model;
|
|
96
|
+
this.#fields = fields;
|
|
97
|
+
}
|
|
98
|
+
import(source, name) {
|
|
99
|
+
const object = Object.import(source, name);
|
|
100
|
+
const instance = Reflect.construct(this.#model, []);
|
|
101
|
+
for (const descriptor of this.#fields.values())
|
|
102
|
+
Reflect.set(instance, descriptor.key, descriptor.import(object, name));
|
|
103
|
+
return instance;
|
|
104
|
+
}
|
|
105
|
+
export(source) {
|
|
106
|
+
const target = new Object();
|
|
107
|
+
for (const descriptor of this.#fields.values())
|
|
108
|
+
Reflect.set(target, descriptor.association, descriptor.export(source));
|
|
109
|
+
return target;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
class PolymorphicSchema extends ModelSchema {
|
|
113
|
+
#discriminator;
|
|
114
|
+
#descendants;
|
|
115
|
+
constructor(discriminator, descendants) {
|
|
116
|
+
super();
|
|
117
|
+
this.#discriminator = discriminator;
|
|
118
|
+
this.#descendants = descendants;
|
|
119
|
+
}
|
|
120
|
+
import(source, name) {
|
|
121
|
+
const key = this.#discriminator;
|
|
122
|
+
const object = Object.import(source, name);
|
|
123
|
+
const value = Reflect.get(object, key);
|
|
124
|
+
if (value === undefined)
|
|
125
|
+
throw new TypeError(`Missing '${key}' discriminator in ${name}`);
|
|
126
|
+
const discriminator = String.import(value, `${name}.${key}`);
|
|
127
|
+
const descriptor = this.#descendants.find(descendant => descendant.accepts(discriminator));
|
|
128
|
+
if (descriptor === undefined)
|
|
129
|
+
throw new TypeError(`Unknown '${discriminator}' discriminator for ${name}`);
|
|
130
|
+
return descriptor.import(source, name);
|
|
131
|
+
}
|
|
132
|
+
export(source) {
|
|
133
|
+
const descriptor = this.#descendants.find(descendant => descendant.owns(source));
|
|
134
|
+
if (descriptor === undefined)
|
|
135
|
+
throw new TypeError(`Invalid '${typename(source)}' type for source`);
|
|
136
|
+
const exported = descriptor.export(source);
|
|
137
|
+
Reflect.set(exported, this.#discriminator, descriptor.discriminator);
|
|
138
|
+
return exported;
|
|
46
139
|
}
|
|
47
140
|
}
|
|
48
|
-
//#endregion
|
|
49
|
-
//#region Portability metadata
|
|
50
141
|
class PortabilityMetadata {
|
|
51
142
|
static #registry = new WeakMap();
|
|
52
143
|
#fields = new Map();
|
|
@@ -61,40 +152,10 @@ class PortabilityMetadata {
|
|
|
61
152
|
registry.set(metadata, entry);
|
|
62
153
|
return entry;
|
|
63
154
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
let current = object;
|
|
69
|
-
while (true) {
|
|
70
|
-
if (current === null)
|
|
71
|
-
break;
|
|
72
|
-
for (const [key, descriptor] of PortabilityMetadata.for(current).#fields)
|
|
73
|
-
fields.add(key, descriptor);
|
|
74
|
-
current = Object.getPrototypeOf(current);
|
|
75
|
-
}
|
|
76
|
-
return metadata;
|
|
77
|
-
}
|
|
78
|
-
static descendantsOf(model) {
|
|
79
|
-
if (!Object.hasOwn(model, Symbol.metadata))
|
|
80
|
-
return [];
|
|
81
|
-
const object = model[Symbol.metadata];
|
|
82
|
-
if (object === null || object === undefined)
|
|
83
|
-
return [];
|
|
84
|
-
return PortabilityMetadata.for(object).#descendants;
|
|
85
|
-
}
|
|
86
|
-
get fields() {
|
|
87
|
-
return this.#fields;
|
|
88
|
-
}
|
|
89
|
-
get descendants() {
|
|
90
|
-
return this.#descendants;
|
|
91
|
-
}
|
|
92
|
-
get discriminator() {
|
|
93
|
-
return this.#discriminator;
|
|
94
|
-
}
|
|
95
|
-
set discriminator(value) {
|
|
96
|
-
this.#discriminator = value;
|
|
97
|
-
}
|
|
155
|
+
get fields() { return this.#fields; }
|
|
156
|
+
get descendants() { return this.#descendants; }
|
|
157
|
+
get discriminator() { return this.#discriminator; }
|
|
158
|
+
set discriminator(value) { this.#discriminator = value; }
|
|
98
159
|
}
|
|
99
160
|
//#endregion
|
|
100
161
|
//#region Model
|
|
@@ -110,89 +171,28 @@ export class Model {
|
|
|
110
171
|
* @throws {TypeError} If validation fails or types do not match.
|
|
111
172
|
*/
|
|
112
173
|
static import(source, name) {
|
|
113
|
-
|
|
114
|
-
const descendants = PortabilityMetadata.descendantsOf(model);
|
|
115
|
-
if (descendants.length > 0) {
|
|
116
|
-
const { discriminator: key } = PortabilityMetadata.read(model);
|
|
117
|
-
const object = Object.import(source, name);
|
|
118
|
-
const value = Reflect.get(object, key);
|
|
119
|
-
if (value === undefined)
|
|
120
|
-
throw new TypeError(`Missing '${key}' discriminator in ${name}`);
|
|
121
|
-
const discriminator = String.import(value, `${name}.${key}`);
|
|
122
|
-
const descriptor = descendants.find(descriptor => descriptor.discriminator === discriminator);
|
|
123
|
-
if (descriptor === undefined)
|
|
124
|
-
throw new TypeError(`Unknown '${discriminator}' discriminator for ${name}`);
|
|
125
|
-
return descriptor.type.import(source, name);
|
|
126
|
-
}
|
|
127
|
-
const object = Object.import(source, name);
|
|
128
|
-
const instance = Reflect.construct(this, []);
|
|
129
|
-
const { fields } = PortabilityMetadata.read(model);
|
|
130
|
-
for (const { key, association, type } of fields.values()) {
|
|
131
|
-
const raw = Reflect.get(object, association);
|
|
132
|
-
const value = type.import(raw, `${name}.${association}`);
|
|
133
|
-
Reflect.set(instance, key, value);
|
|
134
|
-
}
|
|
135
|
-
return instance;
|
|
174
|
+
return ModelSchema.resolve(this).import(source, name);
|
|
136
175
|
}
|
|
137
176
|
/**
|
|
138
177
|
* Serializes the model instance to a raw object.
|
|
139
178
|
* @param source The model instance to export.
|
|
140
179
|
*/
|
|
141
180
|
static export(source) {
|
|
142
|
-
|
|
143
|
-
const descendants = PortabilityMetadata.descendantsOf(model);
|
|
144
|
-
if (descendants.length > 0) {
|
|
145
|
-
const { discriminator: key } = PortabilityMetadata.read(model);
|
|
146
|
-
const descriptor = descendants.find(descriptor => source instanceof descriptor.type);
|
|
147
|
-
if (descriptor === undefined)
|
|
148
|
-
throw new TypeError(`Invalid '${typename(source)}' type for source`);
|
|
149
|
-
const descendant = descriptor.type;
|
|
150
|
-
const exported = descendant.export(source);
|
|
151
|
-
Reflect.set(exported, key, descriptor.discriminator);
|
|
152
|
-
return exported;
|
|
153
|
-
}
|
|
154
|
-
const object = new Object();
|
|
155
|
-
const { fields } = PortabilityMetadata.read(model);
|
|
156
|
-
for (const { key, association, type } of fields.values()) {
|
|
157
|
-
const value = Reflect.get(source, key);
|
|
158
|
-
const raw = type.export(value);
|
|
159
|
-
Reflect.set(object, association, raw);
|
|
160
|
-
}
|
|
161
|
-
return object;
|
|
181
|
+
return ModelSchema.resolve(this).export(source);
|
|
162
182
|
}
|
|
163
183
|
}
|
|
164
|
-
export function Field(type,
|
|
184
|
+
export function Field(type, options = {}) {
|
|
165
185
|
return function (_, context) {
|
|
166
186
|
if (context.static)
|
|
167
187
|
throw new TypeError("Portable fields cannot be static");
|
|
168
188
|
const key = context.name;
|
|
169
189
|
if (typeof (key) === "symbol")
|
|
170
190
|
throw new TypeError("Symbols are not supported as portable keys");
|
|
171
|
-
const association = name ?? key;
|
|
172
191
|
const { fields } = PortabilityMetadata.for(context.metadata);
|
|
173
192
|
if (!fields.has(key))
|
|
174
|
-
fields.set(key, new FieldDescriptor(key,
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
export function Descendant(descendant, discriminator) {
|
|
178
|
-
return function (model, context) {
|
|
179
|
-
void model;
|
|
180
|
-
const { descendants } = PortabilityMetadata.for(context.metadata);
|
|
181
|
-
descendants.push(new DescendantDescriptor(descendant, discriminator));
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
|
-
/**
|
|
185
|
-
* Decorator to register a custom discriminator key for the polymorphic model.
|
|
186
|
-
* @param key The property key to use for the discriminator.
|
|
187
|
-
*/
|
|
188
|
-
export function DiscriminatorKey(key) {
|
|
189
|
-
return function (model, context) {
|
|
190
|
-
void model;
|
|
191
|
-
PortabilityMetadata.for(context.metadata).discriminator = key;
|
|
193
|
+
fields.set(key, new FieldDescriptor(key, type, options));
|
|
192
194
|
};
|
|
193
195
|
}
|
|
194
|
-
//#endregion
|
|
195
|
-
//#region Adapters
|
|
196
196
|
/**
|
|
197
197
|
* Creates a wrapper for circular or deferred type references.
|
|
198
198
|
* @param resolver Function that returns the actual type constructor.
|
|
@@ -213,224 +213,158 @@ export function Deferred(resolver) {
|
|
|
213
213
|
},
|
|
214
214
|
};
|
|
215
215
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
return {
|
|
222
|
-
[Symbol.hasInstance](instance) {
|
|
223
|
-
return instance === undefined || type[Symbol.hasInstance](instance);
|
|
224
|
-
},
|
|
225
|
-
get name() {
|
|
226
|
-
return `${type.name} | undefined`;
|
|
227
|
-
},
|
|
228
|
-
import(source, name) {
|
|
229
|
-
return Reflect.mapUndefined(source, source => type.import(source, name));
|
|
230
|
-
},
|
|
231
|
-
export(source) {
|
|
232
|
-
return Reflect.mapUndefined(source, source => type.export(source));
|
|
233
|
-
},
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
/**
|
|
237
|
-
* Creates a portable wrapper for nullable types.
|
|
238
|
-
* @param type The inner portable type.
|
|
239
|
-
*/
|
|
240
|
-
export function Nullable(type) {
|
|
241
|
-
return {
|
|
242
|
-
[Symbol.hasInstance](instance) {
|
|
243
|
-
return instance === null || type[Symbol.hasInstance](instance);
|
|
244
|
-
},
|
|
245
|
-
get name() {
|
|
246
|
-
return `${type.name} | null`;
|
|
247
|
-
},
|
|
248
|
-
import(source, name) {
|
|
249
|
-
return Reflect.mapNull(source, source => type.import(source, name));
|
|
250
|
-
},
|
|
251
|
-
export(source) {
|
|
252
|
-
return Reflect.mapNull(source, source => type.export(source));
|
|
253
|
-
},
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
/**
|
|
257
|
-
* Creates a portable wrapper for array types.
|
|
258
|
-
* @param type The portable type of the array elements.
|
|
259
|
-
*/
|
|
260
|
-
export function ArrayOf(type) {
|
|
261
|
-
return {
|
|
262
|
-
[Symbol.hasInstance](instance) {
|
|
263
|
-
return Array[Symbol.hasInstance](instance);
|
|
264
|
-
},
|
|
265
|
-
get name() {
|
|
266
|
-
return `${type.name}[]`;
|
|
267
|
-
},
|
|
268
|
-
import(source, name) {
|
|
269
|
-
return Array.import(source, name).map((item, index) => type.import(item, `${name}[${index}]`));
|
|
270
|
-
},
|
|
271
|
-
export(source) {
|
|
272
|
-
return source.map(item => type.export(item));
|
|
273
|
-
},
|
|
216
|
+
export function Descendant(descendant, discriminator) {
|
|
217
|
+
return function (model, context) {
|
|
218
|
+
void model;
|
|
219
|
+
const { descendants } = PortabilityMetadata.for(context.metadata);
|
|
220
|
+
descendants.push(new DescendantDescriptor(descendant, discriminator));
|
|
274
221
|
};
|
|
275
222
|
}
|
|
276
223
|
/**
|
|
277
|
-
*
|
|
278
|
-
* @param
|
|
224
|
+
* Decorator to register a custom discriminator key for the polymorphic model.
|
|
225
|
+
* @param key The property key to use for the discriminator.
|
|
279
226
|
*/
|
|
280
|
-
export function
|
|
281
|
-
return {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
},
|
|
285
|
-
get name() {
|
|
286
|
-
return `Set<${type.name}>`;
|
|
287
|
-
},
|
|
288
|
-
import(source, name) {
|
|
289
|
-
return new Set(Array.import(source, name).map((item, index) => type.import(item, `${name}[${index}]`)));
|
|
290
|
-
},
|
|
291
|
-
export(source) {
|
|
292
|
-
return Array.from(source, item => type.export(item));
|
|
293
|
-
},
|
|
227
|
+
export function DiscriminatorKey(key) {
|
|
228
|
+
return function (model, context) {
|
|
229
|
+
void model;
|
|
230
|
+
PortabilityMetadata.for(context.metadata).discriminator = key;
|
|
294
231
|
};
|
|
295
232
|
}
|
|
233
|
+
//#endregion
|
|
234
|
+
//#region Adapters
|
|
296
235
|
/**
|
|
297
|
-
*
|
|
298
|
-
*
|
|
236
|
+
* Portable adapter class for optional (possibly-undefined) types.
|
|
237
|
+
* Use `Optional.Of(type)` to wrap an adapter, and `Optional.map` for conditional mapping.
|
|
299
238
|
*/
|
|
300
|
-
export
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
}
|
|
323
|
-
}
|
|
239
|
+
export class Optional {
|
|
240
|
+
constructor() {
|
|
241
|
+
throw new TypeError("Unable to create an instance of a static class");
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Creates a portable wrapper for optional types.
|
|
245
|
+
* @param type The inner portable type.
|
|
246
|
+
*/
|
|
247
|
+
static Of(type) {
|
|
248
|
+
return {
|
|
249
|
+
[Symbol.hasInstance](instance) {
|
|
250
|
+
return type[Symbol.hasInstance](instance);
|
|
251
|
+
},
|
|
252
|
+
get name() {
|
|
253
|
+
return `${type.name} | undefined`;
|
|
254
|
+
},
|
|
255
|
+
import(source, name) {
|
|
256
|
+
return Optional.map(source, source => type.import(source, name));
|
|
257
|
+
},
|
|
258
|
+
export(source) {
|
|
259
|
+
return Optional.map(source, source => type.export(source));
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Applies a callback to a non-undefined value, or passes through `undefined` unchanged.
|
|
265
|
+
* @param value The value to map.
|
|
266
|
+
* @param callback The function to apply if the value is not undefined.
|
|
267
|
+
*/
|
|
268
|
+
static map(value, callback) {
|
|
269
|
+
if (value === undefined)
|
|
270
|
+
return value;
|
|
271
|
+
return callback(value);
|
|
272
|
+
}
|
|
324
273
|
}
|
|
325
274
|
/**
|
|
326
|
-
*
|
|
327
|
-
*
|
|
328
|
-
* @param value The portable type of the map values.
|
|
275
|
+
* Portable adapter class for nullable (possibly-null) types.
|
|
276
|
+
* Use `Nullable.Of(type)` to wrap an adapter, and `Nullable.map` for conditional mapping.
|
|
329
277
|
*/
|
|
330
|
-
export
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
278
|
+
export class Nullable {
|
|
279
|
+
constructor() {
|
|
280
|
+
throw new TypeError("Unable to create an instance of a static class");
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Creates a portable wrapper for nullable types.
|
|
284
|
+
* @param type The inner portable type.
|
|
285
|
+
*/
|
|
286
|
+
static Of(type) {
|
|
287
|
+
return {
|
|
288
|
+
[Symbol.hasInstance](instance) {
|
|
289
|
+
return type[Symbol.hasInstance](instance);
|
|
290
|
+
},
|
|
291
|
+
get name() {
|
|
292
|
+
return `${type.name} | null`;
|
|
293
|
+
},
|
|
294
|
+
import(source, name) {
|
|
295
|
+
return Nullable.map(source, source => type.import(source, name));
|
|
296
|
+
},
|
|
297
|
+
export(source) {
|
|
298
|
+
return Nullable.map(source, source => type.export(source));
|
|
299
|
+
},
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Applies a callback to a non-null value, or passes through `null` unchanged.
|
|
304
|
+
* @param value The value to map.
|
|
305
|
+
* @param callback The function to apply if the value is not null.
|
|
306
|
+
*/
|
|
307
|
+
static map(value, callback) {
|
|
308
|
+
if (value === null)
|
|
309
|
+
return value;
|
|
310
|
+
return callback(value);
|
|
311
|
+
}
|
|
350
312
|
}
|
|
351
313
|
/**
|
|
352
|
-
*
|
|
353
|
-
*
|
|
314
|
+
* Portable adapter class for enum types.
|
|
315
|
+
* Use `Enum.Of(reference)` to create an adapter that validates against enum values.
|
|
354
316
|
*/
|
|
355
|
-
export
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
317
|
+
export class Enum {
|
|
318
|
+
constructor() {
|
|
319
|
+
throw new TypeError("Unable to create an instance of a static class");
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Creates a portable wrapper for enum types, strictly operating only on enum values.
|
|
323
|
+
* @param reference The enum object reference.
|
|
324
|
+
*/
|
|
325
|
+
static Of(reference) {
|
|
326
|
+
const values = new Set();
|
|
327
|
+
for (const [key, value] of Object.entries(reference)) {
|
|
328
|
+
const index = Number(key);
|
|
329
|
+
if (String(index) === key && typeof value === "string" && Reflect.get(reference, value) === index)
|
|
330
|
+
continue;
|
|
331
|
+
values.add(value);
|
|
332
|
+
}
|
|
333
|
+
return {
|
|
334
|
+
[Symbol.hasInstance](instance) {
|
|
335
|
+
return values.has(instance);
|
|
336
|
+
},
|
|
337
|
+
get name() {
|
|
338
|
+
return "Enum";
|
|
339
|
+
},
|
|
340
|
+
import(source, name) {
|
|
341
|
+
if (!values.has(source))
|
|
342
|
+
throw new TypeError(`Unable to import enum from ${name} due to invalid value`);
|
|
343
|
+
return source;
|
|
344
|
+
},
|
|
345
|
+
export(source) {
|
|
346
|
+
return source;
|
|
347
|
+
},
|
|
348
|
+
};
|
|
362
349
|
}
|
|
363
|
-
return {
|
|
364
|
-
[Symbol.hasInstance](instance) {
|
|
365
|
-
return values.has(instance);
|
|
366
|
-
},
|
|
367
|
-
get name() {
|
|
368
|
-
return "Enum";
|
|
369
|
-
},
|
|
370
|
-
import(source, name) {
|
|
371
|
-
if (!values.has(source))
|
|
372
|
-
throw new TypeError(`Unable to import enum from ${name} due to invalid value`);
|
|
373
|
-
return source;
|
|
374
|
-
},
|
|
375
|
-
export(source) {
|
|
376
|
-
return source;
|
|
377
|
-
},
|
|
378
|
-
};
|
|
379
350
|
}
|
|
380
351
|
/**
|
|
381
|
-
*
|
|
382
|
-
|
|
383
|
-
export const Timestamp = {
|
|
384
|
-
[Symbol.hasInstance](instance) {
|
|
385
|
-
return Date[Symbol.hasInstance](instance);
|
|
386
|
-
},
|
|
387
|
-
get name() {
|
|
388
|
-
return "Timestamp";
|
|
389
|
-
},
|
|
390
|
-
import(source, name) {
|
|
391
|
-
if (typeof (source) !== "number")
|
|
392
|
-
throw new TypeError(`Unable to import date from ${name} due its ${typename(source)} type`);
|
|
393
|
-
return new Date(source);
|
|
394
|
-
},
|
|
395
|
-
export(source) {
|
|
396
|
-
return source.getTime();
|
|
397
|
-
},
|
|
398
|
-
};
|
|
399
|
-
/**
|
|
400
|
-
* A portable adapter that facilitates the conversion between `Date` instances and Unix second timestamps.
|
|
401
|
-
*/
|
|
402
|
-
export const UnixSeconds = {
|
|
403
|
-
[Symbol.hasInstance](instance) {
|
|
404
|
-
return Date[Symbol.hasInstance](instance);
|
|
405
|
-
},
|
|
406
|
-
get name() {
|
|
407
|
-
return "UnixSeconds";
|
|
408
|
-
},
|
|
409
|
-
import(source, name) {
|
|
410
|
-
if (typeof (source) !== "number")
|
|
411
|
-
throw new TypeError(`Unable to import date from ${name} due its ${typename(source)} type`);
|
|
412
|
-
return new Date(source * 1000);
|
|
413
|
-
},
|
|
414
|
-
export(source) {
|
|
415
|
-
return Math.trunc(source.getTime() / 1000);
|
|
416
|
-
},
|
|
417
|
-
};
|
|
418
|
-
/**
|
|
419
|
-
* A portable adapter that allows any value to pass through without validation or transformation.
|
|
352
|
+
* Portable adapter class for unknown/any types.
|
|
353
|
+
* Pass `Any` directly to `@Field` to allow any value without validation or transformation.
|
|
420
354
|
*/
|
|
421
|
-
export
|
|
422
|
-
|
|
355
|
+
export class Any {
|
|
356
|
+
constructor() {
|
|
357
|
+
throw new TypeError("Unable to create an instance of a static class");
|
|
358
|
+
}
|
|
359
|
+
static [Symbol.hasInstance](instance) {
|
|
423
360
|
void instance;
|
|
424
361
|
return true;
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
return "Any";
|
|
428
|
-
},
|
|
429
|
-
import(source, name) {
|
|
362
|
+
}
|
|
363
|
+
static import(source, name) {
|
|
430
364
|
void name;
|
|
431
365
|
return source;
|
|
432
|
-
}
|
|
433
|
-
export(source) {
|
|
366
|
+
}
|
|
367
|
+
static export(source) {
|
|
434
368
|
return source;
|
|
435
|
-
}
|
|
436
|
-
}
|
|
369
|
+
}
|
|
370
|
+
}
|
package/dist/core/reflect.js
CHANGED
|
@@ -1,17 +1,2 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
Reflect.mapNull = function (value, callback) {
|
|
3
|
-
if (value === null)
|
|
4
|
-
return value;
|
|
5
|
-
return callback(value);
|
|
6
|
-
};
|
|
7
|
-
Reflect.mapUndefined = function (value, callback) {
|
|
8
|
-
if (value === undefined)
|
|
9
|
-
return value;
|
|
10
|
-
return callback(value);
|
|
11
|
-
};
|
|
12
|
-
Reflect.mapNullable = function (value, callback) {
|
|
13
|
-
if (value === null || value === undefined)
|
|
14
|
-
return value;
|
|
15
|
-
return callback(value);
|
|
16
|
-
};
|
|
17
2
|
export {};
|
package/dist/core/set.js
CHANGED
|
@@ -1,4 +1,21 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
import {} from "./portable.js";
|
|
3
|
+
Set.Of = function (type) {
|
|
4
|
+
return {
|
|
5
|
+
[Symbol.hasInstance](instance) {
|
|
6
|
+
return Set[Symbol.hasInstance](instance);
|
|
7
|
+
},
|
|
8
|
+
get name() {
|
|
9
|
+
return `Set<${type.name}>`;
|
|
10
|
+
},
|
|
11
|
+
import(source, name) {
|
|
12
|
+
return new Set(Array.import(source, name).map((item, index) => type.import(item, `${name}[${index}]`)));
|
|
13
|
+
},
|
|
14
|
+
export(source) {
|
|
15
|
+
return Array.from(source, item => type.export(item));
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
};
|
|
2
19
|
Set.prototype.toggle = function (value, force) {
|
|
3
20
|
if (force === undefined) {
|
|
4
21
|
if (this.has(value)) {
|
|
@@ -15,4 +32,3 @@ Set.prototype.toggle = function (value, force) {
|
|
|
15
32
|
this.delete(value);
|
|
16
33
|
return false;
|
|
17
34
|
};
|
|
18
|
-
export {};
|
package/dist/web/archive.js
CHANGED
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
import "../core/index.js";
|
|
3
3
|
import {} from "../core/index.js";
|
|
4
|
-
//#region
|
|
4
|
+
//#region Cell
|
|
5
5
|
/**
|
|
6
|
-
* Low-level interface for
|
|
7
|
-
* Handles direct serialization and retrieval
|
|
6
|
+
* Low-level interface for a single keyed entry in a {@link Storage} backend.
|
|
7
|
+
* Handles direct serialization and retrieval of raw data.
|
|
8
8
|
*/
|
|
9
|
-
class
|
|
9
|
+
export class Cell {
|
|
10
|
+
#storage;
|
|
10
11
|
#key;
|
|
11
12
|
/**
|
|
13
|
+
* @param storage The underlying storage backend.
|
|
12
14
|
* @param key Unique storage identifier.
|
|
13
|
-
* @param initial Default value
|
|
15
|
+
* @param initial Default value written if no data exists at the specified key.
|
|
14
16
|
*/
|
|
15
|
-
constructor(key, initial) {
|
|
17
|
+
constructor(storage, key, initial) {
|
|
18
|
+
this.#storage = storage;
|
|
16
19
|
this.#key = key;
|
|
17
20
|
this.#initialize(initial);
|
|
18
21
|
}
|
|
19
22
|
#initialize(value) {
|
|
20
|
-
if (
|
|
23
|
+
if (this.#storage.getItem(this.#key) !== null)
|
|
21
24
|
return;
|
|
22
25
|
this.data = value;
|
|
23
26
|
}
|
|
@@ -26,7 +29,7 @@ class Archive {
|
|
|
26
29
|
return JSON.stringify(value);
|
|
27
30
|
}
|
|
28
31
|
catch {
|
|
29
|
-
throw new SyntaxError(`
|
|
32
|
+
throw new SyntaxError(`Cell [${key}]: Serialization failed.`);
|
|
30
33
|
}
|
|
31
34
|
}
|
|
32
35
|
static #decompress(key, text) {
|
|
@@ -34,7 +37,7 @@ class Archive {
|
|
|
34
37
|
return JSON.parse(text);
|
|
35
38
|
}
|
|
36
39
|
catch {
|
|
37
|
-
throw new SyntaxError(`
|
|
40
|
+
throw new SyntaxError(`Cell [${key}]: Data corrupted.`);
|
|
38
41
|
}
|
|
39
42
|
}
|
|
40
43
|
/**
|
|
@@ -49,10 +52,10 @@ class Archive {
|
|
|
49
52
|
* @throws {SyntaxError} If the data is corrupted and cannot be parsed.
|
|
50
53
|
*/
|
|
51
54
|
get data() {
|
|
52
|
-
const text =
|
|
55
|
+
const text = this.#storage.getItem(this.#key);
|
|
53
56
|
if (text === null)
|
|
54
|
-
throw new ReferenceError(`
|
|
55
|
-
return
|
|
57
|
+
throw new ReferenceError(`Cell [${this.#key}]: Entry not found.`);
|
|
58
|
+
return Cell.#decompress(this.#key, text);
|
|
56
59
|
}
|
|
57
60
|
/**
|
|
58
61
|
* Overwrites the raw data in the underlying storage.
|
|
@@ -60,28 +63,29 @@ class Archive {
|
|
|
60
63
|
* @throws {SyntaxError} If the value cannot be serialized.
|
|
61
64
|
*/
|
|
62
65
|
set data(value) {
|
|
63
|
-
const text =
|
|
64
|
-
|
|
66
|
+
const text = Cell.#compress(this.#key, value);
|
|
67
|
+
this.#storage.setItem(this.#key, text);
|
|
65
68
|
}
|
|
66
69
|
}
|
|
67
70
|
//#endregion
|
|
68
|
-
//#region
|
|
71
|
+
//#region Portable cell
|
|
69
72
|
/**
|
|
70
73
|
* Orchestrates the lifecycle and state-to-model mapping for a specific data type.
|
|
71
74
|
*/
|
|
72
|
-
export class
|
|
73
|
-
#
|
|
75
|
+
export class PortableCell {
|
|
76
|
+
#cell;
|
|
74
77
|
#model;
|
|
75
78
|
#initial;
|
|
76
79
|
/**
|
|
80
|
+
* @param storage The underlying storage backend.
|
|
77
81
|
* @param key Unique storage identifier.
|
|
78
82
|
* @param model Constructor with import/export capabilities.
|
|
79
83
|
* @param instance Baseline object state for initialization and resets.
|
|
80
84
|
* @throws {TypeError} If the provided instance is incompatible with the model schema.
|
|
81
85
|
*/
|
|
82
|
-
constructor(key, model, instance) {
|
|
83
|
-
const scheme =
|
|
84
|
-
this.#
|
|
86
|
+
constructor(storage, key, model, instance) {
|
|
87
|
+
const scheme = PortableCell.#ensureCompatibility(key, model, instance);
|
|
88
|
+
this.#cell = new Cell(storage, key, scheme);
|
|
85
89
|
this.#model = model;
|
|
86
90
|
this.#initial = scheme;
|
|
87
91
|
}
|
|
@@ -95,14 +99,14 @@ export class ArchiveManager {
|
|
|
95
99
|
}
|
|
96
100
|
catch (reason) {
|
|
97
101
|
const { message } = Error.from(reason);
|
|
98
|
-
throw new TypeError(`
|
|
102
|
+
throw new TypeError(`PortableCell [${key}]: Schema validation failed: ${message}`);
|
|
99
103
|
}
|
|
100
104
|
}
|
|
101
105
|
/**
|
|
102
106
|
* The storage identifier managed by this instance.
|
|
103
107
|
*/
|
|
104
108
|
get key() {
|
|
105
|
-
return this.#
|
|
109
|
+
return this.#cell.key;
|
|
106
110
|
}
|
|
107
111
|
/**
|
|
108
112
|
* Deserializes and reconstructs the model instance from the storage.
|
|
@@ -110,12 +114,12 @@ export class ArchiveManager {
|
|
|
110
114
|
*/
|
|
111
115
|
get content() {
|
|
112
116
|
try {
|
|
113
|
-
return this.#model.import(this.#
|
|
117
|
+
return this.#model.import(this.#cell.data, this.#cell.key);
|
|
114
118
|
}
|
|
115
119
|
catch (error) {
|
|
116
120
|
if (!(error instanceof TypeError))
|
|
117
121
|
throw error;
|
|
118
|
-
throw new SyntaxError(`
|
|
122
|
+
throw new SyntaxError(`PortableCell [${this.#cell.key}]: Content restoration failed.`);
|
|
119
123
|
}
|
|
120
124
|
}
|
|
121
125
|
/**
|
|
@@ -124,17 +128,17 @@ export class ArchiveManager {
|
|
|
124
128
|
* @throws {SyntaxError} If the instance state cannot be serialized.
|
|
125
129
|
*/
|
|
126
130
|
set content(value) {
|
|
127
|
-
this.#
|
|
131
|
+
this.#cell.data = this.#model.export(value);
|
|
128
132
|
}
|
|
129
133
|
/**
|
|
130
134
|
* Reverts the storage to the original state provided at construction.
|
|
131
135
|
*/
|
|
132
136
|
reset() {
|
|
133
|
-
this.#
|
|
137
|
+
this.#cell.data = this.#initial;
|
|
134
138
|
}
|
|
135
139
|
}
|
|
136
140
|
//#endregion
|
|
137
|
-
//#region
|
|
141
|
+
//#region Buffered cell
|
|
138
142
|
class SaveTransaction {
|
|
139
143
|
#idTimeout;
|
|
140
144
|
#resolve;
|
|
@@ -144,14 +148,14 @@ class SaveTransaction {
|
|
|
144
148
|
this.#resolve = resolve;
|
|
145
149
|
this.#reject = reject;
|
|
146
150
|
}
|
|
147
|
-
cancel(
|
|
151
|
+
cancel() {
|
|
148
152
|
clearTimeout(this.#idTimeout);
|
|
149
|
-
this.#
|
|
153
|
+
this.#resolve(false);
|
|
150
154
|
}
|
|
151
155
|
settle(callback) {
|
|
152
156
|
try {
|
|
153
|
-
|
|
154
|
-
this.#resolve(
|
|
157
|
+
callback();
|
|
158
|
+
this.#resolve(true);
|
|
155
159
|
}
|
|
156
160
|
catch (reason) {
|
|
157
161
|
this.#reject(Error.from(reason));
|
|
@@ -159,20 +163,21 @@ class SaveTransaction {
|
|
|
159
163
|
}
|
|
160
164
|
}
|
|
161
165
|
/**
|
|
162
|
-
* A high-level
|
|
166
|
+
* A high-level cell providing buffered access to persistent data with auto-save management.
|
|
163
167
|
*/
|
|
164
|
-
export class
|
|
165
|
-
#
|
|
168
|
+
export class BufferedCell {
|
|
169
|
+
#cell;
|
|
166
170
|
#content;
|
|
167
171
|
#transaction = null;
|
|
168
172
|
/**
|
|
173
|
+
* @param storage The underlying storage backend.
|
|
169
174
|
* @param key Unique storage identifier.
|
|
170
175
|
* @param model Constructor for state transformation.
|
|
171
176
|
* @param instance Initial state template.
|
|
172
177
|
*/
|
|
173
|
-
constructor(key, model, instance) {
|
|
174
|
-
this.#
|
|
175
|
-
this.#content = this.#
|
|
178
|
+
constructor(storage, key, model, instance) {
|
|
179
|
+
this.#cell = new PortableCell(storage, key, model, instance);
|
|
180
|
+
this.#content = this.#cell.content;
|
|
176
181
|
this.#initializeUnloadHandler();
|
|
177
182
|
}
|
|
178
183
|
#initializeUnloadHandler() {
|
|
@@ -187,7 +192,7 @@ export class ArchiveRepository {
|
|
|
187
192
|
* The unique storage identifier.
|
|
188
193
|
*/
|
|
189
194
|
get key() {
|
|
190
|
-
return this.#
|
|
195
|
+
return this.#cell.key;
|
|
191
196
|
}
|
|
192
197
|
/**
|
|
193
198
|
* Retrieves the in-memory instance of the content.
|
|
@@ -200,22 +205,23 @@ export class ArchiveRepository {
|
|
|
200
205
|
const transaction = this.#transaction;
|
|
201
206
|
this.#transaction = null;
|
|
202
207
|
transaction?.settle(() => {
|
|
203
|
-
this.#
|
|
208
|
+
this.#cell.content = this.#content;
|
|
204
209
|
});
|
|
205
210
|
}
|
|
206
211
|
async save(delay) {
|
|
207
212
|
const transaction = this.#transaction;
|
|
208
|
-
transaction?.cancel(
|
|
213
|
+
transaction?.cancel();
|
|
209
214
|
return await new Promise((resolve, reject) => {
|
|
210
215
|
this.#transaction = new SaveTransaction(this.#handler.bind(this), delay, resolve, reject);
|
|
211
216
|
});
|
|
212
217
|
}
|
|
213
218
|
/**
|
|
214
219
|
* Cancels any scheduled save operations.
|
|
220
|
+
* Any pending {@linkcode save} promise resolves to `false`.
|
|
215
221
|
*/
|
|
216
222
|
abort() {
|
|
217
223
|
const transaction = this.#transaction;
|
|
218
|
-
transaction?.cancel(
|
|
224
|
+
transaction?.cancel();
|
|
219
225
|
this.#transaction = null;
|
|
220
226
|
}
|
|
221
227
|
/**
|
|
@@ -223,8 +229,17 @@ export class ArchiveRepository {
|
|
|
223
229
|
*/
|
|
224
230
|
reset() {
|
|
225
231
|
this.abort();
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
this.#content =
|
|
232
|
+
const cell = this.#cell;
|
|
233
|
+
cell.reset();
|
|
234
|
+
this.#content = cell.content;
|
|
229
235
|
}
|
|
230
236
|
}
|
|
237
|
+
Storage.prototype.openCell = function (key, initial) {
|
|
238
|
+
return new Cell(this, key, initial);
|
|
239
|
+
};
|
|
240
|
+
Storage.prototype.openPortableCell = function (key, model, instance) {
|
|
241
|
+
return new PortableCell(this, key, model, instance);
|
|
242
|
+
};
|
|
243
|
+
Storage.prototype.openBufferedCell = function (key, model, instance) {
|
|
244
|
+
return new BufferedCell(this, key, model, instance);
|
|
245
|
+
};
|
|
@@ -34,7 +34,7 @@ var __runInitializers = (this && this.__runInitializers) || function (thisArg, i
|
|
|
34
34
|
return useValue ? value : void 0;
|
|
35
35
|
};
|
|
36
36
|
import "adaptive-extender/web";
|
|
37
|
-
import { Model, Field, Optional,
|
|
37
|
+
import { Model, Field, Optional, DiscriminatorKey, Descendant, Deferred } from "adaptive-extender/web";
|
|
38
38
|
//#region URL adapter
|
|
39
39
|
const URLAdapter = {
|
|
40
40
|
[Symbol.hasInstance](instance) {
|
|
@@ -80,12 +80,12 @@ let Metadata = (() => {
|
|
|
80
80
|
static { _classThis = this; }
|
|
81
81
|
static {
|
|
82
82
|
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
83
|
-
_context_decorators = [Field(String, "@context")];
|
|
84
|
-
_name_decorators = [Field(String, "name")];
|
|
85
|
-
_webpage_decorators = [Field(URLAdapter, "url")];
|
|
86
|
-
_preview_decorators = [Field(Optional(URLAdapter), "image")];
|
|
87
|
-
_description_decorators = [Field(Optional(String), "description")];
|
|
88
|
-
_keywords_decorators = [Field(Optional(
|
|
83
|
+
_context_decorators = [Field(String, { name: "@context" })];
|
|
84
|
+
_name_decorators = [Field(String, { name: "name" })];
|
|
85
|
+
_webpage_decorators = [Field(URLAdapter, { name: "url" })];
|
|
86
|
+
_preview_decorators = [Field(Optional.Of(URLAdapter), { name: "image" })];
|
|
87
|
+
_description_decorators = [Field(Optional.Of(String), { name: "description" })];
|
|
88
|
+
_keywords_decorators = [Field(Optional.Of(Array.Of(String)), { name: "keywords" })];
|
|
89
89
|
__esDecorate(null, null, _context_decorators, { kind: "field", name: "context", static: false, private: false, access: { has: obj => "context" in obj, get: obj => obj.context, set: (obj, value) => { obj.context = value; } }, metadata: _metadata }, _context_initializers, _context_extraInitializers);
|
|
90
90
|
__esDecorate(null, null, _name_decorators, { kind: "field", name: "name", static: false, private: false, access: { has: obj => "name" in obj, get: obj => obj.name, set: (obj, value) => { obj.name = value; } }, metadata: _metadata }, _name_initializers, _name_extraInitializers);
|
|
91
91
|
__esDecorate(null, null, _webpage_decorators, { kind: "field", name: "webpage", static: false, private: false, access: { has: obj => "webpage" in obj, get: obj => obj.webpage, set: (obj, value) => { obj.webpage = value; } }, metadata: _metadata }, _webpage_initializers, _webpage_extraInitializers);
|
|
@@ -135,9 +135,9 @@ let PersonMetadata = (() => {
|
|
|
135
135
|
return class PersonMetadata extends _classSuper {
|
|
136
136
|
static {
|
|
137
137
|
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
138
|
-
_associations_decorators = [Field(Optional(
|
|
139
|
-
_job_decorators = [Field(Optional(String), "jobTitle")];
|
|
140
|
-
_knowledge_decorators = [Field(Optional(
|
|
138
|
+
_associations_decorators = [Field(Optional.Of(Array.Of(URLAdapter)), { name: "sameAs" })];
|
|
139
|
+
_job_decorators = [Field(Optional.Of(String), { name: "jobTitle" })];
|
|
140
|
+
_knowledge_decorators = [Field(Optional.Of(Array.Of(String)), { name: "knowsAbout" })];
|
|
141
141
|
__esDecorate(null, null, _associations_decorators, { kind: "field", name: "associations", static: false, private: false, access: { has: obj => "associations" in obj, get: obj => obj.associations, set: (obj, value) => { obj.associations = value; } }, metadata: _metadata }, _associations_initializers, _associations_extraInitializers);
|
|
142
142
|
__esDecorate(null, null, _job_decorators, { kind: "field", name: "job", static: false, private: false, access: { has: obj => "job" in obj, get: obj => obj.job, set: (obj, value) => { obj.job = value; } }, metadata: _metadata }, _job_initializers, _job_extraInitializers);
|
|
143
143
|
__esDecorate(null, null, _knowledge_decorators, { kind: "field", name: "knowledge", static: false, private: false, access: { has: obj => "knowledge" in obj, get: obj => obj.knowledge, set: (obj, value) => { obj.knowledge = value; } }, metadata: _metadata }, _knowledge_initializers, _knowledge_extraInitializers);
|
|
@@ -173,9 +173,9 @@ let ApplicationMetadata = (() => {
|
|
|
173
173
|
return class ApplicationMetadata extends _classSuper {
|
|
174
174
|
static {
|
|
175
175
|
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
176
|
-
_category_decorators = [Field(String, "applicationCategory")];
|
|
177
|
-
_os_decorators = [Field(String, "operatingSystem")];
|
|
178
|
-
_version_decorators = [Field(Optional(String), "softwareVersion")];
|
|
176
|
+
_category_decorators = [Field(String, { name: "applicationCategory" })];
|
|
177
|
+
_os_decorators = [Field(String, { name: "operatingSystem" })];
|
|
178
|
+
_version_decorators = [Field(Optional.Of(String), { name: "softwareVersion" })];
|
|
179
179
|
__esDecorate(null, null, _category_decorators, { kind: "field", name: "category", static: false, private: false, access: { has: obj => "category" in obj, get: obj => obj.category, set: (obj, value) => { obj.category = value; } }, metadata: _metadata }, _category_initializers, _category_extraInitializers);
|
|
180
180
|
__esDecorate(null, null, _os_decorators, { kind: "field", name: "os", static: false, private: false, access: { has: obj => "os" in obj, get: obj => obj.os, set: (obj, value) => { obj.os = value; } }, metadata: _metadata }, _os_initializers, _os_extraInitializers);
|
|
181
181
|
__esDecorate(null, null, _version_decorators, { kind: "field", name: "version", static: false, private: false, access: { has: obj => "version" in obj, get: obj => obj.version, set: (obj, value) => { obj.version = value; } }, metadata: _metadata }, _version_initializers, _version_extraInitializers);
|
|
@@ -211,9 +211,9 @@ let OrganizationMetadata = (() => {
|
|
|
211
211
|
return class OrganizationMetadata extends _classSuper {
|
|
212
212
|
static {
|
|
213
213
|
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
214
|
-
_logo_decorators = [Field(Optional(URLAdapter), "logo")];
|
|
215
|
-
_email_decorators = [Field(Optional(String), "email")];
|
|
216
|
-
_foundation_decorators = [Field(Optional(Date), "foundingDate")];
|
|
214
|
+
_logo_decorators = [Field(Optional.Of(URLAdapter), { name: "logo" })];
|
|
215
|
+
_email_decorators = [Field(Optional.Of(String), { name: "email" })];
|
|
216
|
+
_foundation_decorators = [Field(Optional.Of(Date), { name: "foundingDate" })];
|
|
217
217
|
__esDecorate(null, null, _logo_decorators, { kind: "field", name: "logo", static: false, private: false, access: { has: obj => "logo" in obj, get: obj => obj.logo, set: (obj, value) => { obj.logo = value; } }, metadata: _metadata }, _logo_initializers, _logo_extraInitializers);
|
|
218
218
|
__esDecorate(null, null, _email_decorators, { kind: "field", name: "email", static: false, private: false, access: { has: obj => "email" in obj, get: obj => obj.email, set: (obj, value) => { obj.email = value; } }, metadata: _metadata }, _email_initializers, _email_extraInitializers);
|
|
219
219
|
__esDecorate(null, null, _foundation_decorators, { kind: "field", name: "foundation", static: false, private: false, access: { has: obj => "foundation" in obj, get: obj => obj.foundation, set: (obj, value) => { obj.foundation = value; } }, metadata: _metadata }, _foundation_initializers, _foundation_extraInitializers);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "adaptive-extender",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "Adaptive library for JS/TS development environments",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/core/index.js",
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"build:web": "tsc -p ./src/web/tsconfig.json",
|
|
58
58
|
"build:worker": "tsc -p ./src/worker/tsconfig.json",
|
|
59
59
|
"build": "npm run build:core && npm run build:node && npm run build:web && npm run build:worker",
|
|
60
|
+
"prepublishOnly": "npm run build",
|
|
60
61
|
"test": "npm run build & vitest run",
|
|
61
62
|
"coverage": "vitest run --coverage"
|
|
62
63
|
},
|