@sdcorejs/angular 20.1.3 → 20.1.5
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/README.md +19 -14
- package/components/audit-diff/index.d.ts +74 -0
- package/components/badge/index.d.ts +1 -1
- package/components/breadcrumb/index.d.ts +53 -0
- package/components/data-state/index.d.ts +44 -0
- package/components/document-builder/index.d.ts +5 -5
- package/components/form-generic/index.d.ts +67 -104
- package/components/import-excel/index.d.ts +4 -14
- package/components/index.d.ts +4 -0
- package/components/job-progress/index.d.ts +38 -0
- package/components/mini-editor/index.d.ts +9 -9
- package/components/modal/index.d.ts +7 -1
- package/components/preview/index.d.ts +181 -28
- package/components/side-drawer/index.d.ts +7 -1
- package/components/tab/index.d.ts +9 -2
- package/components/table/index.d.ts +8 -7
- package/components/tree/index.d.ts +24 -2
- package/components/upload-file/index.d.ts +5 -4
- package/directives/index.d.ts +10 -8
- package/fesm2022/sdcorejs-angular-components-anchor.mjs +14 -14
- package/fesm2022/sdcorejs-angular-components-anchor.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-audit-diff.mjs +332 -0
- package/fesm2022/sdcorejs-angular-components-audit-diff.mjs.map +1 -0
- package/fesm2022/sdcorejs-angular-components-badge.mjs +1 -0
- package/fesm2022/sdcorejs-angular-components-badge.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-breadcrumb.mjs +121 -0
- package/fesm2022/sdcorejs-angular-components-breadcrumb.mjs.map +1 -0
- package/fesm2022/sdcorejs-angular-components-data-state.mjs +68 -0
- package/fesm2022/sdcorejs-angular-components-data-state.mjs.map +1 -0
- package/fesm2022/sdcorejs-angular-components-document-builder.mjs +18 -23
- package/fesm2022/sdcorejs-angular-components-document-builder.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-editor.mjs +6 -4
- package/fesm2022/sdcorejs-angular-components-editor.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-form-generic.mjs +495 -838
- package/fesm2022/sdcorejs-angular-components-form-generic.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-import-excel.mjs +24 -34
- package/fesm2022/sdcorejs-angular-components-import-excel.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-job-progress.mjs +97 -0
- package/fesm2022/sdcorejs-angular-components-job-progress.mjs.map +1 -0
- package/fesm2022/sdcorejs-angular-components-mini-editor.mjs +10 -20
- package/fesm2022/sdcorejs-angular-components-mini-editor.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-modal.mjs +61 -4
- package/fesm2022/sdcorejs-angular-components-modal.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-preview.mjs +1438 -218
- package/fesm2022/sdcorejs-angular-components-preview.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-query-bar.mjs +4 -4
- package/fesm2022/sdcorejs-angular-components-query-bar.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-query-builder.mjs +1 -1
- package/fesm2022/sdcorejs-angular-components-query-builder.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-side-drawer.mjs +39 -2
- package/fesm2022/sdcorejs-angular-components-side-drawer.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-tab-router.mjs +16 -22
- package/fesm2022/sdcorejs-angular-components-tab-router.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-tab.mjs +48 -4
- package/fesm2022/sdcorejs-angular-components-tab.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-table.mjs +96 -63
- package/fesm2022/sdcorejs-angular-components-table.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-tree.mjs +186 -17
- package/fesm2022/sdcorejs-angular-components-tree.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-upload-file.mjs +15 -22
- package/fesm2022/sdcorejs-angular-components-upload-file.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components.mjs +4 -0
- package/fesm2022/sdcorejs-angular-components.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-directives.mjs +29 -41
- package/fesm2022/sdcorejs-angular-directives.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs +7 -7
- package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-checkbox.mjs +20 -83
- package/fesm2022/sdcorejs-angular-forms-checkbox.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-chip-calendar.mjs +6 -3
- package/fesm2022/sdcorejs-angular-forms-chip-calendar.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-chip.mjs +6 -3
- package/fesm2022/sdcorejs-angular-forms-chip.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-date-range.mjs +16 -13
- package/fesm2022/sdcorejs-angular-forms-date-range.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-date.mjs +42 -65
- package/fesm2022/sdcorejs-angular-forms-date.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-datetime.mjs +51 -66
- package/fesm2022/sdcorejs-angular-forms-datetime.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-entity-picker.mjs +384 -0
- package/fesm2022/sdcorejs-angular-forms-entity-picker.mjs.map +1 -0
- package/fesm2022/sdcorejs-angular-forms-input-color.mjs +1 -1
- package/fesm2022/sdcorejs-angular-forms-input-color.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-input-number.mjs +7 -5
- package/fesm2022/sdcorejs-angular-forms-input-number.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-input.mjs +290 -56
- package/fesm2022/sdcorejs-angular-forms-input.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-models.mjs +155 -2
- package/fesm2022/sdcorejs-angular-forms-models.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-radio.mjs +44 -78
- package/fesm2022/sdcorejs-angular-forms-radio.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-select.mjs +6 -20
- package/fesm2022/sdcorejs-angular-forms-select.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-switch.mjs +19 -76
- package/fesm2022/sdcorejs-angular-forms-switch.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-textarea.mjs +6 -5
- package/fesm2022/sdcorejs-angular-forms-textarea.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-time-range.mjs +280 -0
- package/fesm2022/sdcorejs-angular-forms-time-range.mjs.map +1 -0
- package/fesm2022/sdcorejs-angular-forms-time.mjs +310 -0
- package/fesm2022/sdcorejs-angular-forms-time.mjs.map +1 -0
- package/fesm2022/sdcorejs-angular-forms-tree-select.mjs +210 -0
- package/fesm2022/sdcorejs-angular-forms-tree-select.mjs.map +1 -0
- package/fesm2022/sdcorejs-angular-forms.mjs +73 -4
- package/fesm2022/sdcorejs-angular-forms.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-i18n.mjs +375 -10
- package/fesm2022/sdcorejs-angular-i18n.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-interceptors.mjs +12 -17
- package/fesm2022/sdcorejs-angular-interceptors.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-modules-layout.mjs +994 -168
- package/fesm2022/sdcorejs-angular-modules-layout.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-pipes.mjs +5 -7
- package/fesm2022/sdcorejs-angular-pipes.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-api.mjs +407 -137
- package/fesm2022/sdcorejs-angular-services-api.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-cache.mjs +515 -105
- package/fesm2022/sdcorejs-angular-services-cache.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-confirm.mjs +5 -7
- package/fesm2022/sdcorejs-angular-services-confirm.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-docx.mjs +7 -10
- package/fesm2022/sdcorejs-angular-services-docx.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-loading.mjs +339 -74
- package/fesm2022/sdcorejs-angular-services-loading.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-notify.mjs +7 -10
- package/fesm2022/sdcorejs-angular-services-notify.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-persistence.mjs +899 -0
- package/fesm2022/sdcorejs-angular-services-persistence.mjs.map +1 -0
- package/fesm2022/sdcorejs-angular-services-storage.mjs +324 -122
- package/fesm2022/sdcorejs-angular-services-storage.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-task.mjs +389 -0
- package/fesm2022/sdcorejs-angular-services-task.mjs.map +1 -0
- package/fesm2022/sdcorejs-angular-services-unsaved-changes.mjs +255 -0
- package/fesm2022/sdcorejs-angular-services-unsaved-changes.mjs.map +1 -0
- package/fesm2022/sdcorejs-angular-services-viewport.mjs +76 -0
- package/fesm2022/sdcorejs-angular-services-viewport.mjs.map +1 -0
- package/fesm2022/sdcorejs-angular-services.mjs +4 -0
- package/fesm2022/sdcorejs-angular-services.mjs.map +1 -1
- package/forms/autocomplete/index.d.ts +3 -4
- package/forms/checkbox/index.d.ts +4 -8
- package/forms/date/index.d.ts +2 -2
- package/forms/date-range/index.d.ts +6 -7
- package/forms/datetime/index.d.ts +2 -2
- package/forms/entity-picker/index.d.ts +128 -0
- package/forms/index.d.ts +15 -5
- package/forms/input/index.d.ts +74 -11
- package/forms/input-number/index.d.ts +1 -1
- package/forms/models/index.d.ts +86 -3
- package/forms/radio/index.d.ts +12 -8
- package/forms/switch/index.d.ts +4 -7
- package/forms/time/index.d.ts +117 -0
- package/forms/time-range/index.d.ts +98 -0
- package/forms/tree-select/index.d.ts +76 -0
- package/i18n/index.d.ts +72 -2
- package/interceptors/index.d.ts +5 -6
- package/modules/layout/index.d.ts +354 -17
- package/package.json +49 -1
- package/pipes/index.d.ts +2 -2
- package/services/api/index.d.ts +68 -24
- package/services/cache/index.d.ts +52 -16
- package/services/confirm/index.d.ts +3 -2
- package/services/docx/index.d.ts +2 -3
- package/services/index.d.ts +4 -0
- package/services/loading/index.d.ts +16 -8
- package/services/persistence/index.d.ts +159 -0
- package/services/storage/index.d.ts +26 -8
- package/services/task/index.d.ts +104 -0
- package/services/unsaved-changes/index.d.ts +83 -0
- package/services/viewport/index.d.ts +42 -0
|
@@ -0,0 +1,899 @@
|
|
|
1
|
+
import { DOCUMENT, isPlatformBrowser } from '@angular/common';
|
|
2
|
+
import * as i0 from '@angular/core';
|
|
3
|
+
import { inject, PLATFORM_ID, Injectable, InjectionToken } from '@angular/core';
|
|
4
|
+
|
|
5
|
+
const SD_GRAPH_FORMAT = 'sdcorejs.graph';
|
|
6
|
+
const SD_GRAPH_VERSION = 1;
|
|
7
|
+
|
|
8
|
+
const DATE_GET_TIME = Date.prototype.getTime;
|
|
9
|
+
const DATE_TO_ISO_STRING = Date.prototype.toISOString;
|
|
10
|
+
const MAP_ENTRIES = Map.prototype.entries;
|
|
11
|
+
const MAP_SET = Map.prototype.set;
|
|
12
|
+
const SET_VALUES = Set.prototype.values;
|
|
13
|
+
const SET_ADD = Set.prototype.add;
|
|
14
|
+
const SD_GRAPH_HARD_LIMITS = Object.freeze({
|
|
15
|
+
maxDocumentCharacters: 4_000_000,
|
|
16
|
+
maxDepth: 128,
|
|
17
|
+
maxNodes: 50_000,
|
|
18
|
+
maxEntries: 200_000,
|
|
19
|
+
maxStringCharacters: 1_000_000,
|
|
20
|
+
maxKeyCharacters: 16_384,
|
|
21
|
+
maxBigIntDigits: 100_000,
|
|
22
|
+
});
|
|
23
|
+
class SdPersistenceError extends Error {
|
|
24
|
+
code;
|
|
25
|
+
name = 'SdPersistenceError';
|
|
26
|
+
constructor(code, message) {
|
|
27
|
+
super(`${code}: ${message}`);
|
|
28
|
+
this.code = code;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
class SdGraphSerializer {
|
|
32
|
+
format = `${SD_GRAPH_FORMAT}@${SD_GRAPH_VERSION}`;
|
|
33
|
+
limits;
|
|
34
|
+
constructor(limits = {}) {
|
|
35
|
+
const configured = { ...SD_GRAPH_HARD_LIMITS, ...limits };
|
|
36
|
+
for (const key of Object.keys(SD_GRAPH_HARD_LIMITS)) {
|
|
37
|
+
const value = configured[key];
|
|
38
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > SD_GRAPH_HARD_LIMITS[key]) {
|
|
39
|
+
throw new SdPersistenceError('LIMIT_EXCEEDED', `Invalid ${key} limit`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
this.limits = Object.freeze(configured);
|
|
43
|
+
}
|
|
44
|
+
stringify(value) {
|
|
45
|
+
const nodes = [];
|
|
46
|
+
const references = new Map();
|
|
47
|
+
const budget = { entries: 0 };
|
|
48
|
+
const root = this.#encode(value, nodes, references, '$', 0, budget);
|
|
49
|
+
const envelope = {
|
|
50
|
+
format: SD_GRAPH_FORMAT,
|
|
51
|
+
version: SD_GRAPH_VERSION,
|
|
52
|
+
root,
|
|
53
|
+
nodes,
|
|
54
|
+
};
|
|
55
|
+
const serialized = JSON.stringify(envelope);
|
|
56
|
+
this.#ensureDocumentLength(serialized.length);
|
|
57
|
+
return serialized;
|
|
58
|
+
}
|
|
59
|
+
parse(serialized) {
|
|
60
|
+
this.#ensureDocumentLength(serialized.length);
|
|
61
|
+
let document;
|
|
62
|
+
try {
|
|
63
|
+
document = JSON.parse(serialized);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
throw new SdPersistenceError('INVALID_DOCUMENT', 'Serialized value is not valid JSON');
|
|
67
|
+
}
|
|
68
|
+
const envelope = this.#validateEnvelope(document);
|
|
69
|
+
this.#validateDepth(envelope);
|
|
70
|
+
const values = envelope.nodes.map((node, index) => this.#createNode(node, index));
|
|
71
|
+
envelope.nodes.forEach((node, index) => this.#populateNode(node, values[index], values));
|
|
72
|
+
return this.#decode(envelope.root, values, '$.root');
|
|
73
|
+
}
|
|
74
|
+
clone(value) {
|
|
75
|
+
return this.parse(this.stringify(value));
|
|
76
|
+
}
|
|
77
|
+
#encode(value, nodes, references, path, depth, budget) {
|
|
78
|
+
this.#ensureDepth(depth, path);
|
|
79
|
+
if (value === null || typeof value === 'boolean')
|
|
80
|
+
return value;
|
|
81
|
+
if (typeof value === 'string') {
|
|
82
|
+
this.#ensureStringLength(value, path);
|
|
83
|
+
return value;
|
|
84
|
+
}
|
|
85
|
+
if (value === undefined)
|
|
86
|
+
return { $type: 'undefined' };
|
|
87
|
+
if (typeof value === 'number') {
|
|
88
|
+
if (Number.isNaN(value))
|
|
89
|
+
return { $type: 'number', value: 'NaN' };
|
|
90
|
+
if (value === Number.POSITIVE_INFINITY)
|
|
91
|
+
return { $type: 'number', value: 'Infinity' };
|
|
92
|
+
if (value === Number.NEGATIVE_INFINITY)
|
|
93
|
+
return { $type: 'number', value: '-Infinity' };
|
|
94
|
+
if (Object.is(value, -0))
|
|
95
|
+
return { $type: 'number', value: '-0' };
|
|
96
|
+
return value;
|
|
97
|
+
}
|
|
98
|
+
if (typeof value === 'bigint') {
|
|
99
|
+
const bigint = value.toString();
|
|
100
|
+
this.#ensureBigIntLength(bigint, path);
|
|
101
|
+
return { $type: 'bigint', value: bigint };
|
|
102
|
+
}
|
|
103
|
+
if (typeof value === 'function' || typeof value === 'symbol') {
|
|
104
|
+
throw new SdPersistenceError('UNSUPPORTED_VALUE', `Unsupported ${typeof value} at ${path}`);
|
|
105
|
+
}
|
|
106
|
+
const prototype = Object.getPrototypeOf(value);
|
|
107
|
+
if (Array.isArray(value) && prototype !== Array.prototype) {
|
|
108
|
+
throw new SdPersistenceError('UNSUPPORTED_VALUE', `Unsupported array prototype at ${path}`);
|
|
109
|
+
}
|
|
110
|
+
const existingReference = references.get(value);
|
|
111
|
+
if (existingReference !== undefined)
|
|
112
|
+
return { $ref: existingReference };
|
|
113
|
+
const reference = nodes.length;
|
|
114
|
+
references.set(value, reference);
|
|
115
|
+
if (Array.isArray(value)) {
|
|
116
|
+
this.#addNode(nodes, { type: 'array', values: [] }, path);
|
|
117
|
+
this.#consumeEntries(value.length, path, budget);
|
|
118
|
+
const node = nodes[reference];
|
|
119
|
+
if (node.type !== 'array')
|
|
120
|
+
throw new SdPersistenceError('INVALID_NODE', `Invalid array node at ${path}`);
|
|
121
|
+
if (Object.getOwnPropertySymbols(value).length > 0) {
|
|
122
|
+
throw new SdPersistenceError('UNSUPPORTED_VALUE', `Symbol properties are unsupported at ${path}`);
|
|
123
|
+
}
|
|
124
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
125
|
+
for (const key of Object.keys(descriptors)) {
|
|
126
|
+
if (key === 'length')
|
|
127
|
+
continue;
|
|
128
|
+
if (!this.#isArrayIndex(key, value.length)) {
|
|
129
|
+
throw new SdPersistenceError('UNSUPPORTED_VALUE', `Extra array property is unsupported at ${path}.${key}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
133
|
+
const descriptor = descriptors[String(index)];
|
|
134
|
+
if (!descriptor) {
|
|
135
|
+
throw new SdPersistenceError('UNSUPPORTED_VALUE', `Sparse array entry is unsupported at ${path}[${index}]`);
|
|
136
|
+
}
|
|
137
|
+
if (!('value' in descriptor)) {
|
|
138
|
+
throw new SdPersistenceError('UNSUPPORTED_VALUE', `Accessor property is unsupported at ${path}[${index}]`);
|
|
139
|
+
}
|
|
140
|
+
node.values.push(this.#encode(descriptor.value, nodes, references, `${path}[${index}]`, depth + 1, budget));
|
|
141
|
+
}
|
|
142
|
+
return { $ref: reference };
|
|
143
|
+
}
|
|
144
|
+
if (prototype === Date.prototype) {
|
|
145
|
+
this.#ensureNoOwnProperties(value, path, 'Date');
|
|
146
|
+
let time;
|
|
147
|
+
let iso;
|
|
148
|
+
try {
|
|
149
|
+
time = DATE_GET_TIME.call(value);
|
|
150
|
+
iso = DATE_TO_ISO_STRING.call(value);
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
throw new SdPersistenceError('UNSUPPORTED_VALUE', `Invalid Date at ${path}`);
|
|
154
|
+
}
|
|
155
|
+
if (!Number.isFinite(time))
|
|
156
|
+
throw new SdPersistenceError('UNSUPPORTED_VALUE', `Invalid Date at ${path}`);
|
|
157
|
+
this.#addNode(nodes, { type: 'date', value: iso }, path);
|
|
158
|
+
return { $ref: reference };
|
|
159
|
+
}
|
|
160
|
+
if (prototype === Map.prototype) {
|
|
161
|
+
this.#ensureNoOwnProperties(value, path, 'Map');
|
|
162
|
+
let iterator;
|
|
163
|
+
try {
|
|
164
|
+
iterator = MAP_ENTRIES.call(value);
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
throw new SdPersistenceError('UNSUPPORTED_VALUE', `Invalid Map at ${path}`);
|
|
168
|
+
}
|
|
169
|
+
const node = { type: 'map', entries: [] };
|
|
170
|
+
this.#addNode(nodes, node, path);
|
|
171
|
+
let index = 0;
|
|
172
|
+
for (const [key, item] of iterator) {
|
|
173
|
+
this.#consumeEntries(1, path, budget);
|
|
174
|
+
node.entries.push([
|
|
175
|
+
this.#encode(key, nodes, references, `${path}.mapKey[${index}]`, depth + 1, budget),
|
|
176
|
+
this.#encode(item, nodes, references, `${path}.mapValue[${index}]`, depth + 1, budget),
|
|
177
|
+
]);
|
|
178
|
+
index += 1;
|
|
179
|
+
}
|
|
180
|
+
return { $ref: reference };
|
|
181
|
+
}
|
|
182
|
+
if (prototype === Set.prototype) {
|
|
183
|
+
this.#ensureNoOwnProperties(value, path, 'Set');
|
|
184
|
+
let iterator;
|
|
185
|
+
try {
|
|
186
|
+
iterator = SET_VALUES.call(value);
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
throw new SdPersistenceError('UNSUPPORTED_VALUE', `Invalid Set at ${path}`);
|
|
190
|
+
}
|
|
191
|
+
const node = { type: 'set', values: [] };
|
|
192
|
+
this.#addNode(nodes, node, path);
|
|
193
|
+
let index = 0;
|
|
194
|
+
for (const item of iterator) {
|
|
195
|
+
this.#consumeEntries(1, path, budget);
|
|
196
|
+
node.values.push(this.#encode(item, nodes, references, `${path}.set[${index}]`, depth + 1, budget));
|
|
197
|
+
index += 1;
|
|
198
|
+
}
|
|
199
|
+
return { $ref: reference };
|
|
200
|
+
}
|
|
201
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
202
|
+
throw new SdPersistenceError('UNSUPPORTED_VALUE', `Unsupported object prototype at ${path}`);
|
|
203
|
+
}
|
|
204
|
+
if (Object.getOwnPropertySymbols(value).length > 0) {
|
|
205
|
+
throw new SdPersistenceError('UNSUPPORTED_VALUE', `Symbol properties are unsupported at ${path}`);
|
|
206
|
+
}
|
|
207
|
+
const node = {
|
|
208
|
+
type: 'object',
|
|
209
|
+
prototype: prototype === null ? 'null' : 'object',
|
|
210
|
+
entries: [],
|
|
211
|
+
};
|
|
212
|
+
this.#addNode(nodes, node, path);
|
|
213
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
214
|
+
const keys = Object.keys(descriptors).sort();
|
|
215
|
+
this.#consumeEntries(keys.length, path, budget);
|
|
216
|
+
for (const key of keys) {
|
|
217
|
+
this.#ensureKeyLength(key, `${path}.${key}`);
|
|
218
|
+
const descriptor = descriptors[key];
|
|
219
|
+
if (!descriptor || !('value' in descriptor)) {
|
|
220
|
+
throw new SdPersistenceError('UNSUPPORTED_VALUE', `Accessor property is unsupported at ${path}.${key}`);
|
|
221
|
+
}
|
|
222
|
+
if (!descriptor.enumerable) {
|
|
223
|
+
throw new SdPersistenceError('UNSUPPORTED_VALUE', `Non-enumerable property is unsupported at ${path}.${key}`);
|
|
224
|
+
}
|
|
225
|
+
node.entries.push([key, this.#encode(descriptor.value, nodes, references, `${path}.${key}`, depth + 1, budget)]);
|
|
226
|
+
}
|
|
227
|
+
return { $ref: reference };
|
|
228
|
+
}
|
|
229
|
+
#validateEnvelope(value) {
|
|
230
|
+
if (!this.#isRecord(value) || !this.#hasExactKeys(value, ['format', 'version', 'root', 'nodes'])) {
|
|
231
|
+
throw new SdPersistenceError('INVALID_DOCUMENT', 'Graph envelope must have the exact schema');
|
|
232
|
+
}
|
|
233
|
+
if (value['format'] !== SD_GRAPH_FORMAT)
|
|
234
|
+
throw new SdPersistenceError('UNKNOWN_FORMAT', 'Unknown persistence format');
|
|
235
|
+
if (value['version'] !== SD_GRAPH_VERSION)
|
|
236
|
+
throw new SdPersistenceError('UNKNOWN_VERSION', 'Unsupported persistence version');
|
|
237
|
+
if (!Array.isArray(value['nodes']))
|
|
238
|
+
throw new SdPersistenceError('INVALID_DOCUMENT', 'Graph nodes must be an array');
|
|
239
|
+
if (value['nodes'].length > this.limits.maxNodes) {
|
|
240
|
+
throw new SdPersistenceError('LIMIT_EXCEEDED', 'Graph node limit exceeded at $.nodes');
|
|
241
|
+
}
|
|
242
|
+
const budget = { entries: 0 };
|
|
243
|
+
const nodes = value['nodes'].map((node, index) => this.#validateNode(node, index, budget));
|
|
244
|
+
const root = this.#validateGraphValue(value['root'], '$.root');
|
|
245
|
+
return { format: SD_GRAPH_FORMAT, version: SD_GRAPH_VERSION, root, nodes };
|
|
246
|
+
}
|
|
247
|
+
#validateNode(value, index, budget) {
|
|
248
|
+
if (!this.#isRecord(value) || typeof value['type'] !== 'string') {
|
|
249
|
+
throw new SdPersistenceError('INVALID_NODE', `Invalid node at index ${index}`);
|
|
250
|
+
}
|
|
251
|
+
switch (value['type']) {
|
|
252
|
+
case 'array':
|
|
253
|
+
case 'set': {
|
|
254
|
+
if (!this.#hasExactKeys(value, ['type', 'values']) || !Array.isArray(value['values'])) {
|
|
255
|
+
throw new SdPersistenceError('INVALID_NODE', `Invalid ${value['type']} node at index ${index}`);
|
|
256
|
+
}
|
|
257
|
+
this.#consumeEntries(value['values'].length, `$.nodes[${index}]`, budget);
|
|
258
|
+
const values = value['values'].map((item, itemIndex) => this.#validateGraphValue(item, `$.nodes[${index}].values[${itemIndex}]`));
|
|
259
|
+
if (value['type'] === 'set')
|
|
260
|
+
this.#rejectDuplicateValues(values, `set node at index ${index}`);
|
|
261
|
+
return { type: value['type'], values };
|
|
262
|
+
}
|
|
263
|
+
case 'object': {
|
|
264
|
+
if (!this.#hasExactKeys(value, ['type', 'prototype', 'entries']) ||
|
|
265
|
+
(value['prototype'] !== 'object' && value['prototype'] !== 'null') ||
|
|
266
|
+
!Array.isArray(value['entries'])) {
|
|
267
|
+
throw new SdPersistenceError('INVALID_NODE', `Invalid object node at index ${index}`);
|
|
268
|
+
}
|
|
269
|
+
this.#consumeEntries(value['entries'].length, `$.nodes[${index}]`, budget);
|
|
270
|
+
const keys = new Set();
|
|
271
|
+
const entries = value['entries'].map((entry, entryIndex) => {
|
|
272
|
+
if (!Array.isArray(entry) || entry.length !== 2 || typeof entry[0] !== 'string') {
|
|
273
|
+
throw new SdPersistenceError('INVALID_NODE', `Invalid object entry at index ${index}:${entryIndex}`);
|
|
274
|
+
}
|
|
275
|
+
this.#ensureKeyLength(entry[0], `$.nodes[${index}].entries[${entryIndex}]`);
|
|
276
|
+
if (keys.has(entry[0]))
|
|
277
|
+
throw new SdPersistenceError('INVALID_NODE', `Duplicate object key at index ${index}:${entryIndex}`);
|
|
278
|
+
keys.add(entry[0]);
|
|
279
|
+
return [entry[0], this.#validateGraphValue(entry[1], `$.nodes[${index}].entries[${entryIndex}]`)];
|
|
280
|
+
});
|
|
281
|
+
return { type: 'object', prototype: value['prototype'], entries };
|
|
282
|
+
}
|
|
283
|
+
case 'date': {
|
|
284
|
+
if (!this.#hasExactKeys(value, ['type', 'value']) || typeof value['value'] !== 'string') {
|
|
285
|
+
throw new SdPersistenceError('INVALID_NODE', `Invalid date node at index ${index}`);
|
|
286
|
+
}
|
|
287
|
+
this.#ensureStringLength(value['value'], `$.nodes[${index}].value`);
|
|
288
|
+
let canonical = false;
|
|
289
|
+
try {
|
|
290
|
+
const date = new Date(value['value']);
|
|
291
|
+
canonical = Number.isFinite(DATE_GET_TIME.call(date)) && DATE_TO_ISO_STRING.call(date) === value['value'];
|
|
292
|
+
}
|
|
293
|
+
catch {
|
|
294
|
+
canonical = false;
|
|
295
|
+
}
|
|
296
|
+
if (!canonical)
|
|
297
|
+
throw new SdPersistenceError('INVALID_NODE', `Invalid date node at index ${index}`);
|
|
298
|
+
return { type: 'date', value: value['value'] };
|
|
299
|
+
}
|
|
300
|
+
case 'map': {
|
|
301
|
+
if (!this.#hasExactKeys(value, ['type', 'entries']) || !Array.isArray(value['entries'])) {
|
|
302
|
+
throw new SdPersistenceError('INVALID_NODE', `Invalid map node at index ${index}`);
|
|
303
|
+
}
|
|
304
|
+
this.#consumeEntries(value['entries'].length, `$.nodes[${index}]`, budget);
|
|
305
|
+
const seen = new Set();
|
|
306
|
+
const entries = value['entries'].map((entry, entryIndex) => {
|
|
307
|
+
if (!Array.isArray(entry) || entry.length !== 2) {
|
|
308
|
+
throw new SdPersistenceError('INVALID_NODE', `Invalid map entry at index ${index}:${entryIndex}`);
|
|
309
|
+
}
|
|
310
|
+
const key = this.#validateGraphValue(entry[0], `$.nodes[${index}].entries[${entryIndex}].key`);
|
|
311
|
+
const signature = this.#graphValueSignature(key);
|
|
312
|
+
if (seen.has(signature))
|
|
313
|
+
throw new SdPersistenceError('INVALID_NODE', `Duplicate map key at index ${index}:${entryIndex}`);
|
|
314
|
+
seen.add(signature);
|
|
315
|
+
return [key, this.#validateGraphValue(entry[1], `$.nodes[${index}].entries[${entryIndex}].value`)];
|
|
316
|
+
});
|
|
317
|
+
return { type: 'map', entries };
|
|
318
|
+
}
|
|
319
|
+
default:
|
|
320
|
+
throw new SdPersistenceError('INVALID_NODE', `Unknown node type at index ${index}`);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
#validateGraphValue(value, path) {
|
|
324
|
+
if (value === null || typeof value === 'boolean')
|
|
325
|
+
return value;
|
|
326
|
+
if (typeof value === 'string') {
|
|
327
|
+
this.#ensureStringLength(value, path);
|
|
328
|
+
return value;
|
|
329
|
+
}
|
|
330
|
+
if (typeof value === 'number') {
|
|
331
|
+
if (!Number.isFinite(value))
|
|
332
|
+
throw new SdPersistenceError('INVALID_DOCUMENT', `Non-finite number at ${path}`);
|
|
333
|
+
return value;
|
|
334
|
+
}
|
|
335
|
+
if (!this.#isRecord(value))
|
|
336
|
+
throw new SdPersistenceError('INVALID_DOCUMENT', `Invalid graph value at ${path}`);
|
|
337
|
+
const hasReference = '$ref' in value;
|
|
338
|
+
const hasType = '$type' in value;
|
|
339
|
+
if (hasReference === hasType)
|
|
340
|
+
throw new SdPersistenceError('INVALID_DOCUMENT', `Ambiguous graph value tag at ${path}`);
|
|
341
|
+
if (hasReference) {
|
|
342
|
+
if (!this.#hasExactKeys(value, ['$ref']))
|
|
343
|
+
throw new SdPersistenceError('INVALID_DOCUMENT', `Malformed reference tag at ${path}`);
|
|
344
|
+
if (!Number.isInteger(value['$ref']) || value['$ref'] < 0) {
|
|
345
|
+
throw new SdPersistenceError('INVALID_REFERENCE', `Invalid reference at ${path}`);
|
|
346
|
+
}
|
|
347
|
+
return { $ref: value['$ref'] };
|
|
348
|
+
}
|
|
349
|
+
if (value['$type'] === 'undefined' && this.#hasExactKeys(value, ['$type']))
|
|
350
|
+
return { $type: 'undefined' };
|
|
351
|
+
if (value['$type'] === 'number' && this.#hasExactKeys(value, ['$type', 'value']) && this.#isSpecialNumber(value['value'])) {
|
|
352
|
+
return { $type: 'number', value: value['value'] };
|
|
353
|
+
}
|
|
354
|
+
if (value['$type'] === 'bigint' &&
|
|
355
|
+
this.#hasExactKeys(value, ['$type', 'value']) &&
|
|
356
|
+
typeof value['value'] === 'string' &&
|
|
357
|
+
/^-?\d+$/.test(value['value'])) {
|
|
358
|
+
this.#ensureBigIntLength(value['value'], path);
|
|
359
|
+
return { $type: 'bigint', value: value['value'] };
|
|
360
|
+
}
|
|
361
|
+
throw new SdPersistenceError('INVALID_DOCUMENT', `Unknown graph value tag at ${path}`);
|
|
362
|
+
}
|
|
363
|
+
#validateDepth(envelope) {
|
|
364
|
+
const highestDepth = new Map();
|
|
365
|
+
const visit = (value, depth, path, active) => {
|
|
366
|
+
this.#ensureDepth(depth, path);
|
|
367
|
+
if (typeof value !== 'object' || value === null || !('$ref' in value))
|
|
368
|
+
return;
|
|
369
|
+
if (value.$ref >= envelope.nodes.length) {
|
|
370
|
+
throw new SdPersistenceError('INVALID_REFERENCE', `Reference out of bounds at ${path}`);
|
|
371
|
+
}
|
|
372
|
+
if (active.has(value.$ref))
|
|
373
|
+
return;
|
|
374
|
+
const previous = highestDepth.get(value.$ref);
|
|
375
|
+
if (previous !== undefined && previous >= depth)
|
|
376
|
+
return;
|
|
377
|
+
highestDepth.set(value.$ref, depth);
|
|
378
|
+
active.add(value.$ref);
|
|
379
|
+
const node = envelope.nodes[value.$ref];
|
|
380
|
+
switch (node.type) {
|
|
381
|
+
case 'array':
|
|
382
|
+
case 'set':
|
|
383
|
+
node.values.forEach((item, index) => visit(item, depth + 1, `${path}[${index}]`, active));
|
|
384
|
+
break;
|
|
385
|
+
case 'object':
|
|
386
|
+
node.entries.forEach(([key, item]) => visit(item, depth + 1, `${path}.${key}`, active));
|
|
387
|
+
break;
|
|
388
|
+
case 'map':
|
|
389
|
+
node.entries.forEach(([key, item], index) => {
|
|
390
|
+
visit(key, depth + 1, `${path}.mapKey[${index}]`, active);
|
|
391
|
+
visit(item, depth + 1, `${path}.mapValue[${index}]`, active);
|
|
392
|
+
});
|
|
393
|
+
break;
|
|
394
|
+
case 'date':
|
|
395
|
+
break;
|
|
396
|
+
}
|
|
397
|
+
active.delete(value.$ref);
|
|
398
|
+
};
|
|
399
|
+
visit(envelope.root, 0, '$.root', new Set());
|
|
400
|
+
envelope.nodes.forEach((_node, index) => visit({ $ref: index }, 0, `$.nodes[${index}]`, new Set()));
|
|
401
|
+
}
|
|
402
|
+
#createNode(node, index) {
|
|
403
|
+
switch (node.type) {
|
|
404
|
+
case 'array':
|
|
405
|
+
return [];
|
|
406
|
+
case 'object':
|
|
407
|
+
return node.prototype === 'null' ? Object.create(null) : {};
|
|
408
|
+
case 'date':
|
|
409
|
+
return new Date(node.value);
|
|
410
|
+
case 'map':
|
|
411
|
+
return new Map();
|
|
412
|
+
case 'set':
|
|
413
|
+
return new Set();
|
|
414
|
+
default:
|
|
415
|
+
throw new SdPersistenceError('INVALID_NODE', `Unknown node type at index ${index}`);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
#populateNode(node, target, values) {
|
|
419
|
+
switch (node.type) {
|
|
420
|
+
case 'array': {
|
|
421
|
+
const array = target;
|
|
422
|
+
array.length = node.values.length;
|
|
423
|
+
node.values.forEach((value, index) => {
|
|
424
|
+
array[index] = this.#decode(value, values, `array[${index}]`);
|
|
425
|
+
});
|
|
426
|
+
break;
|
|
427
|
+
}
|
|
428
|
+
case 'object': {
|
|
429
|
+
const object = target;
|
|
430
|
+
for (const [key, value] of node.entries) {
|
|
431
|
+
Object.defineProperty(object, key, {
|
|
432
|
+
configurable: true,
|
|
433
|
+
enumerable: true,
|
|
434
|
+
value: this.#decode(value, values, `object.${key}`),
|
|
435
|
+
writable: true,
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
break;
|
|
439
|
+
}
|
|
440
|
+
case 'map': {
|
|
441
|
+
const map = target;
|
|
442
|
+
for (const [key, value] of node.entries) {
|
|
443
|
+
MAP_SET.call(map, this.#decode(key, values, 'map.key'), this.#decode(value, values, 'map.value'));
|
|
444
|
+
}
|
|
445
|
+
break;
|
|
446
|
+
}
|
|
447
|
+
case 'set': {
|
|
448
|
+
const set = target;
|
|
449
|
+
for (const value of node.values)
|
|
450
|
+
SET_ADD.call(set, this.#decode(value, values, 'set.value'));
|
|
451
|
+
break;
|
|
452
|
+
}
|
|
453
|
+
case 'date':
|
|
454
|
+
break;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
#decode(value, values, path) {
|
|
458
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean' || typeof value === 'number')
|
|
459
|
+
return value;
|
|
460
|
+
if ('$ref' in value) {
|
|
461
|
+
if (value.$ref >= values.length)
|
|
462
|
+
throw new SdPersistenceError('INVALID_REFERENCE', `Reference out of bounds at ${path}`);
|
|
463
|
+
return values[value.$ref];
|
|
464
|
+
}
|
|
465
|
+
if (value.$type === 'undefined')
|
|
466
|
+
return undefined;
|
|
467
|
+
if (value.$type === 'bigint')
|
|
468
|
+
return BigInt(value.value);
|
|
469
|
+
switch (value.value) {
|
|
470
|
+
case 'NaN':
|
|
471
|
+
return Number.NaN;
|
|
472
|
+
case 'Infinity':
|
|
473
|
+
return Number.POSITIVE_INFINITY;
|
|
474
|
+
case '-Infinity':
|
|
475
|
+
return Number.NEGATIVE_INFINITY;
|
|
476
|
+
case '-0':
|
|
477
|
+
return -0;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
#addNode(nodes, node, path) {
|
|
481
|
+
if (nodes.length >= this.limits.maxNodes)
|
|
482
|
+
throw new SdPersistenceError('LIMIT_EXCEEDED', `Graph node limit exceeded at ${path}`);
|
|
483
|
+
nodes.push(node);
|
|
484
|
+
}
|
|
485
|
+
#consumeEntries(count, path, budget) {
|
|
486
|
+
if (count > this.limits.maxEntries - budget.entries) {
|
|
487
|
+
throw new SdPersistenceError('LIMIT_EXCEEDED', `Graph entry limit exceeded at ${path}`);
|
|
488
|
+
}
|
|
489
|
+
budget.entries += count;
|
|
490
|
+
}
|
|
491
|
+
#ensureDocumentLength(length) {
|
|
492
|
+
if (length > this.limits.maxDocumentCharacters) {
|
|
493
|
+
throw new SdPersistenceError('LIMIT_EXCEEDED', 'Serialized document character limit exceeded');
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
#ensureDepth(depth, path) {
|
|
497
|
+
if (depth > this.limits.maxDepth)
|
|
498
|
+
throw new SdPersistenceError('LIMIT_EXCEEDED', `Graph depth limit exceeded at ${path}`);
|
|
499
|
+
}
|
|
500
|
+
#ensureStringLength(value, path) {
|
|
501
|
+
if (value.length > this.limits.maxStringCharacters) {
|
|
502
|
+
throw new SdPersistenceError('LIMIT_EXCEEDED', `String character limit exceeded at ${path}`);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
#ensureKeyLength(value, path) {
|
|
506
|
+
if (value.length > this.limits.maxKeyCharacters) {
|
|
507
|
+
throw new SdPersistenceError('LIMIT_EXCEEDED', `Object key character limit exceeded at ${path}`);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
#ensureBigIntLength(value, path) {
|
|
511
|
+
const digits = value.startsWith('-') ? value.length - 1 : value.length;
|
|
512
|
+
if (digits > this.limits.maxBigIntDigits) {
|
|
513
|
+
throw new SdPersistenceError('LIMIT_EXCEEDED', `BigInt digit limit exceeded at ${path}`);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
#ensureNoOwnProperties(value, path, type) {
|
|
517
|
+
if (Reflect.ownKeys(value).length > 0) {
|
|
518
|
+
throw new SdPersistenceError('UNSUPPORTED_VALUE', `Own properties on ${type} are unsupported at ${path}`);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
#rejectDuplicateValues(values, description) {
|
|
522
|
+
const seen = new Set();
|
|
523
|
+
values.forEach((value, index) => {
|
|
524
|
+
const signature = this.#graphValueSignature(value);
|
|
525
|
+
if (seen.has(signature))
|
|
526
|
+
throw new SdPersistenceError('INVALID_NODE', `Duplicate value in ${description}:${index}`);
|
|
527
|
+
seen.add(signature);
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
#graphValueSignature(value) {
|
|
531
|
+
if (value === null)
|
|
532
|
+
return 'null';
|
|
533
|
+
if (typeof value === 'string')
|
|
534
|
+
return `string:${value}`;
|
|
535
|
+
if (typeof value === 'boolean')
|
|
536
|
+
return `boolean:${value}`;
|
|
537
|
+
if (typeof value === 'number')
|
|
538
|
+
return `number:${Object.is(value, -0) ? 0 : value}`;
|
|
539
|
+
if ('$ref' in value)
|
|
540
|
+
return `ref:${value.$ref}`;
|
|
541
|
+
if (value.$type === 'number')
|
|
542
|
+
return value.value === '-0' ? 'number:0' : `number:${value.value}`;
|
|
543
|
+
if (value.$type === 'bigint')
|
|
544
|
+
return `bigint:${BigInt(value.value).toString()}`;
|
|
545
|
+
return `type:${value.$type}`;
|
|
546
|
+
}
|
|
547
|
+
#isArrayIndex(key, length) {
|
|
548
|
+
const index = Number(key);
|
|
549
|
+
return Number.isInteger(index) && index >= 0 && index < length && index <= 4_294_967_294 && String(index) === key;
|
|
550
|
+
}
|
|
551
|
+
#isSpecialNumber(value) {
|
|
552
|
+
return value === 'NaN' || value === 'Infinity' || value === '-Infinity' || value === '-0';
|
|
553
|
+
}
|
|
554
|
+
#hasExactKeys(value, expected) {
|
|
555
|
+
const keys = Object.keys(value);
|
|
556
|
+
return keys.length === expected.length && expected.every(key => Object.prototype.hasOwnProperty.call(value, key));
|
|
557
|
+
}
|
|
558
|
+
#isRecord(value) {
|
|
559
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
class SdBrowserStorageAdapter {
|
|
564
|
+
#document = inject(DOCUMENT);
|
|
565
|
+
#platformId = inject(PLATFORM_ID);
|
|
566
|
+
readItem(area, key) {
|
|
567
|
+
try {
|
|
568
|
+
const storage = this.#getStorage(area);
|
|
569
|
+
if (!storage)
|
|
570
|
+
return { status: 'unavailable' };
|
|
571
|
+
const value = storage.getItem(key);
|
|
572
|
+
return value === null ? { status: 'absent' } : { status: 'found', value };
|
|
573
|
+
}
|
|
574
|
+
catch {
|
|
575
|
+
return { status: 'unavailable' };
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
getItem(area, key) {
|
|
579
|
+
const result = this.readItem(area, key);
|
|
580
|
+
return result.status === 'found' ? result.value : null;
|
|
581
|
+
}
|
|
582
|
+
setItem(area, key, value) {
|
|
583
|
+
try {
|
|
584
|
+
const storage = this.#getStorage(area);
|
|
585
|
+
if (!storage)
|
|
586
|
+
return false;
|
|
587
|
+
storage.setItem(key, value);
|
|
588
|
+
return true;
|
|
589
|
+
}
|
|
590
|
+
catch {
|
|
591
|
+
return false;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
removeItem(area, key) {
|
|
595
|
+
try {
|
|
596
|
+
const storage = this.#getStorage(area);
|
|
597
|
+
if (!storage)
|
|
598
|
+
return false;
|
|
599
|
+
storage.removeItem(key);
|
|
600
|
+
return true;
|
|
601
|
+
}
|
|
602
|
+
catch {
|
|
603
|
+
return false;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
#getStorage(area) {
|
|
607
|
+
if (!isPlatformBrowser(this.#platformId))
|
|
608
|
+
return null;
|
|
609
|
+
const browserWindow = this.#document.defaultView;
|
|
610
|
+
if (!browserWindow)
|
|
611
|
+
return null;
|
|
612
|
+
return area === 'local' ? browserWindow.localStorage : browserWindow.sessionStorage;
|
|
613
|
+
}
|
|
614
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdBrowserStorageAdapter, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
615
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdBrowserStorageAdapter, providedIn: 'root' });
|
|
616
|
+
}
|
|
617
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdBrowserStorageAdapter, decorators: [{
|
|
618
|
+
type: Injectable,
|
|
619
|
+
args: [{ providedIn: 'root' }]
|
|
620
|
+
}] });
|
|
621
|
+
function readSdPersistenceStorageItem(adapter, area, key) {
|
|
622
|
+
try {
|
|
623
|
+
if (adapter.readItem)
|
|
624
|
+
return adapter.readItem(area, key);
|
|
625
|
+
const value = adapter.getItem(area, key);
|
|
626
|
+
return value === null ? { status: 'absent' } : { status: 'found', value };
|
|
627
|
+
}
|
|
628
|
+
catch {
|
|
629
|
+
return { status: 'unavailable' };
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
const SD_PERSISTENCE_STORAGE_ADAPTER = new InjectionToken('sd-persistence.storage-adapter', {
|
|
633
|
+
factory: () => inject(SdBrowserStorageAdapter),
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
class SdPersistenceIdentityError extends Error {
|
|
637
|
+
name = 'SdPersistenceIdentityError';
|
|
638
|
+
constructor(message) {
|
|
639
|
+
super(message);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
function buildSdPersistenceKey(baseKey, fields) {
|
|
643
|
+
const parts = [{ tag: 'base', value: baseKey }, ...fields];
|
|
644
|
+
return `sdcorejs.persistence-key@1|${parts.map(part => `${encode(part.tag)}${encode(part.value)}`).join('|')}`;
|
|
645
|
+
}
|
|
646
|
+
class SdGraphIdentityCanonicalizer {
|
|
647
|
+
format = 'sdcorejs.graph-identity@1';
|
|
648
|
+
#serializer = new SdGraphSerializer();
|
|
649
|
+
canonicalize(value) {
|
|
650
|
+
return this.#serializer.stringify(value);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
function canonicalizeSdPersistenceValue(canonicalizer, value, label) {
|
|
654
|
+
try {
|
|
655
|
+
return canonicalizer.canonicalize(value);
|
|
656
|
+
}
|
|
657
|
+
catch (error) {
|
|
658
|
+
const detail = error instanceof Error ? error.message : 'unknown serialization error';
|
|
659
|
+
throw new SdPersistenceIdentityError(`Cannot canonicalize ${label}: ${detail}`);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
function encode(value) {
|
|
663
|
+
return `${value.length}:${value}`;
|
|
664
|
+
}
|
|
665
|
+
function digestSdPersistenceKey(value) {
|
|
666
|
+
const bytes = encodeUtf8(value);
|
|
667
|
+
const bitLength = bytes.length * 8;
|
|
668
|
+
const paddedLength = Math.ceil((bytes.length + 9) / 64) * 64;
|
|
669
|
+
const padded = new Uint8Array(paddedLength);
|
|
670
|
+
padded.set(bytes);
|
|
671
|
+
padded[bytes.length] = 0x80;
|
|
672
|
+
const high = Math.floor(bitLength / 0x1_0000_0000);
|
|
673
|
+
const low = bitLength >>> 0;
|
|
674
|
+
writeUint32(padded, paddedLength - 8, high);
|
|
675
|
+
writeUint32(padded, paddedLength - 4, low);
|
|
676
|
+
const hash = new Uint32Array([0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19]);
|
|
677
|
+
const schedule = new Uint32Array(64);
|
|
678
|
+
for (let offset = 0; offset < padded.length; offset += 64) {
|
|
679
|
+
for (let index = 0; index < 16; index += 1) {
|
|
680
|
+
const position = offset + index * 4;
|
|
681
|
+
schedule[index] =
|
|
682
|
+
((padded[position] ?? 0) << 24) |
|
|
683
|
+
((padded[position + 1] ?? 0) << 16) |
|
|
684
|
+
((padded[position + 2] ?? 0) << 8) |
|
|
685
|
+
(padded[position + 3] ?? 0);
|
|
686
|
+
}
|
|
687
|
+
for (let index = 16; index < 64; index += 1) {
|
|
688
|
+
const previous15 = schedule[index - 15] ?? 0;
|
|
689
|
+
const previous2 = schedule[index - 2] ?? 0;
|
|
690
|
+
const sigma0 = rotateRight(previous15, 7) ^ rotateRight(previous15, 18) ^ (previous15 >>> 3);
|
|
691
|
+
const sigma1 = rotateRight(previous2, 17) ^ rotateRight(previous2, 19) ^ (previous2 >>> 10);
|
|
692
|
+
schedule[index] = ((schedule[index - 16] ?? 0) + sigma0 + (schedule[index - 7] ?? 0) + sigma1) >>> 0;
|
|
693
|
+
}
|
|
694
|
+
let a = hash[0] ?? 0;
|
|
695
|
+
let b = hash[1] ?? 0;
|
|
696
|
+
let c = hash[2] ?? 0;
|
|
697
|
+
let d = hash[3] ?? 0;
|
|
698
|
+
let e = hash[4] ?? 0;
|
|
699
|
+
let f = hash[5] ?? 0;
|
|
700
|
+
let g = hash[6] ?? 0;
|
|
701
|
+
let h = hash[7] ?? 0;
|
|
702
|
+
for (let index = 0; index < 64; index += 1) {
|
|
703
|
+
const sum1 = rotateRight(e, 6) ^ rotateRight(e, 11) ^ rotateRight(e, 25);
|
|
704
|
+
const choice = (e & f) ^ (~e & g);
|
|
705
|
+
const temp1 = (h + sum1 + choice + SHA256_CONSTANTS[index] + schedule[index]) >>> 0;
|
|
706
|
+
const sum0 = rotateRight(a, 2) ^ rotateRight(a, 13) ^ rotateRight(a, 22);
|
|
707
|
+
const majority = (a & b) ^ (a & c) ^ (b & c);
|
|
708
|
+
const temp2 = (sum0 + majority) >>> 0;
|
|
709
|
+
h = g;
|
|
710
|
+
g = f;
|
|
711
|
+
f = e;
|
|
712
|
+
e = (d + temp1) >>> 0;
|
|
713
|
+
d = c;
|
|
714
|
+
c = b;
|
|
715
|
+
b = a;
|
|
716
|
+
a = (temp1 + temp2) >>> 0;
|
|
717
|
+
}
|
|
718
|
+
hash[0] = (hash[0] + a) >>> 0;
|
|
719
|
+
hash[1] = (hash[1] + b) >>> 0;
|
|
720
|
+
hash[2] = (hash[2] + c) >>> 0;
|
|
721
|
+
hash[3] = (hash[3] + d) >>> 0;
|
|
722
|
+
hash[4] = (hash[4] + e) >>> 0;
|
|
723
|
+
hash[5] = (hash[5] + f) >>> 0;
|
|
724
|
+
hash[6] = (hash[6] + g) >>> 0;
|
|
725
|
+
hash[7] = (hash[7] + h) >>> 0;
|
|
726
|
+
}
|
|
727
|
+
return Array.from(hash, word => word.toString(16).padStart(8, '0')).join('');
|
|
728
|
+
}
|
|
729
|
+
const SHA256_CONSTANTS = new Uint32Array([
|
|
730
|
+
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be,
|
|
731
|
+
0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa,
|
|
732
|
+
0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85,
|
|
733
|
+
0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
|
|
734
|
+
0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f,
|
|
735
|
+
0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
|
|
736
|
+
]);
|
|
737
|
+
function encodeUtf8(value) {
|
|
738
|
+
const bytes = [];
|
|
739
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
740
|
+
const first = value.charCodeAt(index);
|
|
741
|
+
let codePoint = first;
|
|
742
|
+
if (first >= 0xd800 && first <= 0xdbff && index + 1 < value.length) {
|
|
743
|
+
const second = value.charCodeAt(index + 1);
|
|
744
|
+
if (second >= 0xdc00 && second <= 0xdfff) {
|
|
745
|
+
codePoint = 0x10000 + ((first - 0xd800) << 10) + (second - 0xdc00);
|
|
746
|
+
index += 1;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
if (codePoint <= 0x7f)
|
|
750
|
+
bytes.push(codePoint);
|
|
751
|
+
else if (codePoint <= 0x7ff)
|
|
752
|
+
bytes.push(0xc0 | (codePoint >>> 6), 0x80 | (codePoint & 0x3f));
|
|
753
|
+
else if (codePoint <= 0xffff) {
|
|
754
|
+
bytes.push(0xe0 | (codePoint >>> 12), 0x80 | ((codePoint >>> 6) & 0x3f), 0x80 | (codePoint & 0x3f));
|
|
755
|
+
}
|
|
756
|
+
else {
|
|
757
|
+
bytes.push(0xf0 | (codePoint >>> 18), 0x80 | ((codePoint >>> 12) & 0x3f), 0x80 | ((codePoint >>> 6) & 0x3f), 0x80 | (codePoint & 0x3f));
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
return Uint8Array.from(bytes);
|
|
761
|
+
}
|
|
762
|
+
function rotateRight(value, shift) {
|
|
763
|
+
return (value >>> shift) | (value << (32 - shift));
|
|
764
|
+
}
|
|
765
|
+
function writeUint32(target, offset, value) {
|
|
766
|
+
target[offset] = (value >>> 24) & 0xff;
|
|
767
|
+
target[offset + 1] = (value >>> 16) & 0xff;
|
|
768
|
+
target[offset + 2] = (value >>> 8) & 0xff;
|
|
769
|
+
target[offset + 3] = value & 0xff;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
const SD_PERSISTENCE_ENVELOPE_FORMAT = 'sdcorejs.persistence-envelope';
|
|
773
|
+
const SD_PERSISTENCE_ENVELOPE_VERSION = 1;
|
|
774
|
+
const SD_PERSISTENCE_ENVELOPE_HARD_LIMITS = Object.freeze({
|
|
775
|
+
maxDocumentCharacters: 24_020_000,
|
|
776
|
+
maxPayloadCharacters: 4_000_000,
|
|
777
|
+
maxSerializerCharacters: 1_024,
|
|
778
|
+
maxIdentityCharacters: 1_024,
|
|
779
|
+
});
|
|
780
|
+
function stringifySdPersistenceValueEnvelope(identity, serializer, payload, limits) {
|
|
781
|
+
const resolved = resolveLimits(limits);
|
|
782
|
+
ensureFieldLimits(identity, serializer, payload, resolved);
|
|
783
|
+
const envelope = {
|
|
784
|
+
format: SD_PERSISTENCE_ENVELOPE_FORMAT,
|
|
785
|
+
version: SD_PERSISTENCE_ENVELOPE_VERSION,
|
|
786
|
+
kind: 'value',
|
|
787
|
+
identity,
|
|
788
|
+
serializer,
|
|
789
|
+
payload,
|
|
790
|
+
};
|
|
791
|
+
const serialized = JSON.stringify(envelope);
|
|
792
|
+
ensureDocumentLimit(serialized, resolved);
|
|
793
|
+
return serialized;
|
|
794
|
+
}
|
|
795
|
+
function stringifySdPersistenceTombstoneEnvelope(identity, serializer, limits) {
|
|
796
|
+
const resolved = resolveLimits(limits);
|
|
797
|
+
ensureFieldLimits(identity, serializer, undefined, resolved);
|
|
798
|
+
const envelope = {
|
|
799
|
+
format: SD_PERSISTENCE_ENVELOPE_FORMAT,
|
|
800
|
+
version: SD_PERSISTENCE_ENVELOPE_VERSION,
|
|
801
|
+
kind: 'tombstone',
|
|
802
|
+
identity,
|
|
803
|
+
serializer,
|
|
804
|
+
};
|
|
805
|
+
const serialized = JSON.stringify(envelope);
|
|
806
|
+
ensureDocumentLimit(serialized, resolved);
|
|
807
|
+
return serialized;
|
|
808
|
+
}
|
|
809
|
+
function parseSdPersistenceEnvelope(serialized, expectedIdentity, expectedSerializer, limits) {
|
|
810
|
+
const resolved = resolveLimits(limits);
|
|
811
|
+
ensureFieldLimits(expectedIdentity, expectedSerializer, undefined, resolved);
|
|
812
|
+
ensureDocumentLimit(serialized, resolved);
|
|
813
|
+
let value;
|
|
814
|
+
try {
|
|
815
|
+
value = JSON.parse(serialized);
|
|
816
|
+
}
|
|
817
|
+
catch {
|
|
818
|
+
return undefined;
|
|
819
|
+
}
|
|
820
|
+
if (!isRecord(value))
|
|
821
|
+
return undefined;
|
|
822
|
+
if (typeof value['identity'] === 'string' && value['identity'].length > resolved.maxIdentityCharacters) {
|
|
823
|
+
throw new SdPersistenceError('LIMIT_EXCEEDED', 'Persistence envelope identity character limit exceeded');
|
|
824
|
+
}
|
|
825
|
+
if (typeof value['serializer'] === 'string' && value['serializer'].length > resolved.maxSerializerCharacters) {
|
|
826
|
+
throw new SdPersistenceError('LIMIT_EXCEEDED', 'Persistence envelope serializer character limit exceeded');
|
|
827
|
+
}
|
|
828
|
+
if (typeof value['payload'] === 'string' && value['payload'].length > resolved.maxPayloadCharacters) {
|
|
829
|
+
throw new SdPersistenceError('LIMIT_EXCEEDED', 'Persistence envelope payload character limit exceeded');
|
|
830
|
+
}
|
|
831
|
+
if (value['format'] !== SD_PERSISTENCE_ENVELOPE_FORMAT ||
|
|
832
|
+
value['version'] !== SD_PERSISTENCE_ENVELOPE_VERSION ||
|
|
833
|
+
value['identity'] !== expectedIdentity ||
|
|
834
|
+
value['serializer'] !== expectedSerializer) {
|
|
835
|
+
return undefined;
|
|
836
|
+
}
|
|
837
|
+
if (value['kind'] === 'tombstone' && hasExactKeys(value, ['format', 'version', 'kind', 'identity', 'serializer'])) {
|
|
838
|
+
return {
|
|
839
|
+
format: SD_PERSISTENCE_ENVELOPE_FORMAT,
|
|
840
|
+
version: SD_PERSISTENCE_ENVELOPE_VERSION,
|
|
841
|
+
kind: 'tombstone',
|
|
842
|
+
identity: expectedIdentity,
|
|
843
|
+
serializer: expectedSerializer,
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
if (value['kind'] === 'value' &&
|
|
847
|
+
typeof value['payload'] === 'string' &&
|
|
848
|
+
hasExactKeys(value, ['format', 'version', 'kind', 'identity', 'serializer', 'payload'])) {
|
|
849
|
+
return {
|
|
850
|
+
format: SD_PERSISTENCE_ENVELOPE_FORMAT,
|
|
851
|
+
version: SD_PERSISTENCE_ENVELOPE_VERSION,
|
|
852
|
+
kind: 'value',
|
|
853
|
+
identity: expectedIdentity,
|
|
854
|
+
serializer: expectedSerializer,
|
|
855
|
+
payload: value['payload'],
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
return undefined;
|
|
859
|
+
}
|
|
860
|
+
function resolveLimits(limits) {
|
|
861
|
+
const resolved = { ...SD_PERSISTENCE_ENVELOPE_HARD_LIMITS, ...limits };
|
|
862
|
+
for (const key of Object.keys(SD_PERSISTENCE_ENVELOPE_HARD_LIMITS)) {
|
|
863
|
+
const value = resolved[key];
|
|
864
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > SD_PERSISTENCE_ENVELOPE_HARD_LIMITS[key]) {
|
|
865
|
+
throw new SdPersistenceError('LIMIT_EXCEEDED', `Invalid persistence envelope ${key} limit`);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
return resolved;
|
|
869
|
+
}
|
|
870
|
+
function ensureFieldLimits(identity, serializer, payload, limits) {
|
|
871
|
+
if (identity.length > limits.maxIdentityCharacters) {
|
|
872
|
+
throw new SdPersistenceError('LIMIT_EXCEEDED', 'Persistence envelope identity character limit exceeded');
|
|
873
|
+
}
|
|
874
|
+
if (serializer.length > limits.maxSerializerCharacters) {
|
|
875
|
+
throw new SdPersistenceError('LIMIT_EXCEEDED', 'Persistence envelope serializer character limit exceeded');
|
|
876
|
+
}
|
|
877
|
+
if (payload !== undefined && payload.length > limits.maxPayloadCharacters) {
|
|
878
|
+
throw new SdPersistenceError('LIMIT_EXCEEDED', 'Persistence envelope payload character limit exceeded');
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
function ensureDocumentLimit(serialized, limits) {
|
|
882
|
+
if (serialized.length > limits.maxDocumentCharacters) {
|
|
883
|
+
throw new SdPersistenceError('LIMIT_EXCEEDED', 'Persistence envelope document character limit exceeded');
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
function hasExactKeys(value, expected) {
|
|
887
|
+
const keys = Object.keys(value);
|
|
888
|
+
return keys.length === expected.length && expected.every(key => Object.prototype.hasOwnProperty.call(value, key));
|
|
889
|
+
}
|
|
890
|
+
function isRecord(value) {
|
|
891
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
/**
|
|
895
|
+
* Generated bundle index. Do not edit.
|
|
896
|
+
*/
|
|
897
|
+
|
|
898
|
+
export { SD_GRAPH_FORMAT, SD_GRAPH_HARD_LIMITS, SD_GRAPH_VERSION, SD_PERSISTENCE_ENVELOPE_FORMAT, SD_PERSISTENCE_ENVELOPE_HARD_LIMITS, SD_PERSISTENCE_ENVELOPE_VERSION, SD_PERSISTENCE_STORAGE_ADAPTER, SdBrowserStorageAdapter, SdGraphIdentityCanonicalizer, SdGraphSerializer, SdPersistenceError, SdPersistenceIdentityError, buildSdPersistenceKey, canonicalizeSdPersistenceValue, digestSdPersistenceKey, parseSdPersistenceEnvelope, readSdPersistenceStorageItem, stringifySdPersistenceTombstoneEnvelope, stringifySdPersistenceValueEnvelope };
|
|
899
|
+
//# sourceMappingURL=sdcorejs-angular-services-persistence.mjs.map
|