@astrale-os/kernel-dsl 0.2.0-beta.36 → 0.2.0-beta.37
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/dist/v1/language/class/property-values.js +5 -2
- package/dist/v1/language/value/profile/json.validate.js +1 -1
- package/dist/value/accept.d.ts +9 -0
- package/dist/value/accept.js +78 -5
- package/dist/value/canonical.js +2 -2
- package/dist/value/immutable.optimization.d.ts +12 -0
- package/dist/value/immutable.optimization.js +28 -0
- package/dist/value/index.d.ts +1 -1
- package/dist/value/index.js +1 -1
- package/dist/value/unicode.d.ts +1 -1
- package/dist/value/unicode.js +1 -0
- package/package.json +1 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { acceptPropertyRoot } from '../../../value/accept.js';
|
|
2
|
+
import { isImmutableSnapshot } from '../../../value/immutable.optimization.js';
|
|
2
3
|
import { PropertyKey as admitPropertyKey } from '../../addressing/index.js';
|
|
3
4
|
export function acceptPropertyValues(input, pointer = '') {
|
|
4
|
-
const admitted =
|
|
5
|
+
const admitted = acceptPropertyRoot(input);
|
|
5
6
|
if (admitted === null || typeof admitted !== 'object' || Array.isArray(admitted)) {
|
|
6
7
|
throw new TypeError(`Expected PropertyValues object at ${pointer || '/'}.`);
|
|
7
8
|
}
|
|
@@ -21,6 +22,8 @@ export const PropertyValues = Object.freeze({
|
|
|
21
22
|
function freeze(value) {
|
|
22
23
|
if (value === null || typeof value !== 'object')
|
|
23
24
|
return value;
|
|
25
|
+
if (isImmutableSnapshot(value))
|
|
26
|
+
return value;
|
|
24
27
|
for (const member of Object.values(value))
|
|
25
28
|
freeze(member);
|
|
26
29
|
return Object.freeze(value);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { acceptForRead as acceptJsonValue, BoundaryError as JsonBoundaryError, } from '@astrale-os/kernel-dsl/value';
|
|
2
2
|
import { diagnostic } from '../../validation/diagnostic.js';
|
|
3
3
|
export function validateCanonicalJsonDomain(value, pointer) {
|
|
4
4
|
try {
|
package/dist/value/accept.d.ts
CHANGED
|
@@ -6,3 +6,12 @@ import { type Options } from './limits.js';
|
|
|
6
6
|
* Every source property is obtained from one descriptor snapshot. Accessors are never invoked.
|
|
7
7
|
*/
|
|
8
8
|
export declare function accept(input: unknown, options?: Options): Value;
|
|
9
|
+
/** Admit unknown data into a deeply immutable snapshot; owned subtrees may be shared. */
|
|
10
|
+
export declare function acceptImmutable(input: unknown, options?: Options): Value;
|
|
11
|
+
/** Admit a read-only snapshot, sharing owned immutable subtrees without freezing new wrappers. */
|
|
12
|
+
export declare function acceptForRead(input: unknown, options?: Options): Value;
|
|
13
|
+
/**
|
|
14
|
+
* Private PropertyValues owner seam. Retain immediate recursive values, not the enclosing
|
|
15
|
+
* dictionary: its canonical coordinates are re-admitted by the Property owner on every use.
|
|
16
|
+
*/
|
|
17
|
+
export declare function acceptPropertyRoot(input: unknown): Value;
|
package/dist/value/accept.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BoundaryError } from './error.js';
|
|
2
|
+
import { includeSnapshot, retainImmutableSnapshot, reusableSnapshot, } from './immutable.optimization.js';
|
|
2
3
|
import { limitsFor } from './limits.js';
|
|
3
4
|
import { assertHostString } from './unicode.js';
|
|
4
5
|
/**
|
|
@@ -7,9 +8,45 @@ import { assertHostString } from './unicode.js';
|
|
|
7
8
|
* Every source property is obtained from one descriptor snapshot. Accessors are never invoked.
|
|
8
9
|
*/
|
|
9
10
|
export function accept(input, options) {
|
|
10
|
-
|
|
11
|
+
return snapshot(input, options, 'copy');
|
|
12
|
+
}
|
|
13
|
+
/** Admit unknown data into a deeply immutable snapshot; owned subtrees may be shared. */
|
|
14
|
+
export function acceptImmutable(input, options) {
|
|
15
|
+
const captured = { propertyRoots: false };
|
|
16
|
+
const value = snapshot(input, options, 'immutable', captured);
|
|
17
|
+
if (value !== null && typeof value === 'object') {
|
|
18
|
+
retainImmutableSnapshot(value, captured.summary);
|
|
19
|
+
}
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
/** Admit a read-only snapshot, sharing owned immutable subtrees without freezing new wrappers. */
|
|
23
|
+
export function acceptForRead(input, options) {
|
|
24
|
+
return snapshot(input, options, 'read');
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Private PropertyValues owner seam. Retain immediate recursive values, not the enclosing
|
|
28
|
+
* dictionary: its canonical coordinates are re-admitted by the Property owner on every use.
|
|
29
|
+
*/
|
|
30
|
+
export function acceptPropertyRoot(input) {
|
|
31
|
+
return snapshot(input, undefined, 'immutable', { propertyRoots: true });
|
|
32
|
+
}
|
|
33
|
+
function snapshot(input, options, mode, captured) {
|
|
34
|
+
const state = {
|
|
35
|
+
active: new WeakSet(),
|
|
36
|
+
limits: limitsFor(options),
|
|
37
|
+
members: 0,
|
|
38
|
+
mode,
|
|
39
|
+
summary: mode === 'immutable' && !captured?.propertyRoots
|
|
40
|
+
? { members: 0, depth: 0, stringBytes: 0 }
|
|
41
|
+
: undefined,
|
|
42
|
+
summaryDepth: 0,
|
|
43
|
+
propertyRoots: captured?.propertyRoots ?? false,
|
|
44
|
+
};
|
|
11
45
|
try {
|
|
12
|
-
|
|
46
|
+
const value = admit(input, '', 0, state);
|
|
47
|
+
if (captured !== undefined)
|
|
48
|
+
captured.summary = state.summary;
|
|
49
|
+
return value;
|
|
13
50
|
}
|
|
14
51
|
catch (error) {
|
|
15
52
|
if (error instanceof BoundaryError)
|
|
@@ -30,12 +67,20 @@ function admit(input, pointer, depth, state) {
|
|
|
30
67
|
return input;
|
|
31
68
|
}
|
|
32
69
|
if (typeof input === 'string') {
|
|
33
|
-
|
|
70
|
+
admitString(input, pointer, state);
|
|
34
71
|
return input;
|
|
35
72
|
}
|
|
36
73
|
if (typeof input !== 'object') {
|
|
37
74
|
throw new BoundaryError('VALUE_TYPE', 'Expected a portable value', pointer);
|
|
38
75
|
}
|
|
76
|
+
if (state.mode !== 'copy' && !(state.propertyRoots && depth === 0)) {
|
|
77
|
+
const summary = reusableSnapshot(input, state.limits, depth, state.members);
|
|
78
|
+
if (summary !== undefined) {
|
|
79
|
+
state.members += summary.members;
|
|
80
|
+
includeSnapshot(state.summary, summary, depth - state.summaryDepth);
|
|
81
|
+
return input;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
39
84
|
if (state.active.has(input)) {
|
|
40
85
|
throw new BoundaryError('VALUE_CYCLE', 'Cyclic object graphs are not portable values', pointer);
|
|
41
86
|
}
|
|
@@ -43,13 +88,33 @@ function admit(input, pointer, depth, state) {
|
|
|
43
88
|
throw new BoundaryError('VALUE_LIMIT', `Container depth exceeds the ${state.limits.maxDepth}-level limit`, pointer);
|
|
44
89
|
}
|
|
45
90
|
state.active.add(input);
|
|
91
|
+
const parent = state.summary;
|
|
92
|
+
const parentDepth = state.summaryDepth;
|
|
93
|
+
const propertyRoot = state.mode === 'immutable' && state.propertyRoots && depth === 1;
|
|
94
|
+
if (propertyRoot) {
|
|
95
|
+
state.summary = { members: 0, depth: 0, stringBytes: 0 };
|
|
96
|
+
state.summaryDepth = depth;
|
|
97
|
+
}
|
|
98
|
+
if (state.summary !== undefined) {
|
|
99
|
+
state.summary.depth = Math.max(state.summary.depth, depth + 1 - state.summaryDepth);
|
|
100
|
+
}
|
|
46
101
|
try {
|
|
47
|
-
|
|
102
|
+
const output = reflect(() => Array.isArray(input), pointer, 'Cannot inspect the host value brand')
|
|
48
103
|
? admitArray(input, pointer, depth + 1, state)
|
|
49
104
|
: admitObject(input, pointer, depth + 1, state);
|
|
105
|
+
if (state.mode === 'immutable') {
|
|
106
|
+
Object.freeze(output);
|
|
107
|
+
if (propertyRoot) {
|
|
108
|
+
retainImmutableSnapshot(output, state.summary);
|
|
109
|
+
includeSnapshot(parent, state.summary, depth - parentDepth);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return output;
|
|
50
113
|
}
|
|
51
114
|
finally {
|
|
52
115
|
state.active.delete(input);
|
|
116
|
+
state.summary = parent;
|
|
117
|
+
state.summaryDepth = parentDepth;
|
|
53
118
|
}
|
|
54
119
|
}
|
|
55
120
|
function admitArray(input, pointer, depth, state) {
|
|
@@ -92,7 +157,7 @@ function admitObject(input, pointer, depth, state) {
|
|
|
92
157
|
throw new BoundaryError('VALUE_OBJECT', 'Portable Objects cannot have symbol members', pointer);
|
|
93
158
|
}
|
|
94
159
|
const memberPointer = child(pointer, key);
|
|
95
|
-
|
|
160
|
+
admitString(key, memberPointer, state);
|
|
96
161
|
const descriptor = ownDescriptor(descriptors, key);
|
|
97
162
|
if (descriptor === undefined || !descriptor.enumerable || !('value' in descriptor)) {
|
|
98
163
|
throw new BoundaryError('VALUE_OBJECT', 'Object members must be enumerable data properties', memberPointer);
|
|
@@ -115,10 +180,18 @@ function ownDescriptor(descriptors, name) {
|
|
|
115
180
|
}
|
|
116
181
|
function addMembers(state, count, pointer) {
|
|
117
182
|
state.members += count;
|
|
183
|
+
if (state.summary !== undefined)
|
|
184
|
+
state.summary.members += count;
|
|
118
185
|
if (state.members > state.limits.maxMembers) {
|
|
119
186
|
throw new BoundaryError('VALUE_LIMIT', `Value exceeds the ${state.limits.maxMembers}-member limit`, pointer);
|
|
120
187
|
}
|
|
121
188
|
}
|
|
189
|
+
function admitString(value, pointer, state) {
|
|
190
|
+
const bytes = assertHostString(value, pointer, state.limits.maxStringBytes);
|
|
191
|
+
if (state.summary !== undefined) {
|
|
192
|
+
state.summary.stringBytes = Math.max(state.summary.stringBytes, bytes);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
122
195
|
function reflect(operation, pointer, message) {
|
|
123
196
|
try {
|
|
124
197
|
return operation();
|
package/dist/value/canonical.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { acceptForRead } from './accept.js';
|
|
2
2
|
import { BoundaryError } from './error.js';
|
|
3
3
|
import { defaultLimits, limitsFor } from './limits.js';
|
|
4
4
|
import { utf8Length } from './unicode.js';
|
|
@@ -6,7 +6,7 @@ const encoder = new TextEncoder();
|
|
|
6
6
|
/** RFC 8785 canonical JSON for one admitted portable snapshot. */
|
|
7
7
|
export function serialize(input, options) {
|
|
8
8
|
const limits = limitsFor(options);
|
|
9
|
-
return serializeSnapshot(
|
|
9
|
+
return serializeSnapshot(acceptForRead(input, options), limits.maxBytes);
|
|
10
10
|
}
|
|
11
11
|
/** Serialize a trusted portable value whose default structural admission proof is still valid. */
|
|
12
12
|
export function serializeAdmitted(input) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Limits } from './limits.js';
|
|
2
|
+
export interface SnapshotSummary {
|
|
3
|
+
members: number;
|
|
4
|
+
depth: number;
|
|
5
|
+
stringBytes: number;
|
|
6
|
+
}
|
|
7
|
+
export declare function isImmutableSnapshot(value: object): boolean;
|
|
8
|
+
/** Called only after admission has constructed and deeply frozen this container. */
|
|
9
|
+
export declare function retainImmutableSnapshot(value: object, summary: SnapshotSummary): void;
|
|
10
|
+
export declare function includeSnapshot(parent: SnapshotSummary | undefined, child: SnapshotSummary, depth: number): void;
|
|
11
|
+
/** A failed fit must use normal admission, preserving its exact error pointer. */
|
|
12
|
+
export declare function reusableSnapshot(value: object, limits: Limits, depth: number, members: number): SnapshotSummary | undefined;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Only admission-owned, deeply immutable snapshots enter this table. A frozen
|
|
2
|
+
// caller object is not a proof. Weak identity ownership retains no caller data.
|
|
3
|
+
const snapshots = new WeakMap();
|
|
4
|
+
export function isImmutableSnapshot(value) {
|
|
5
|
+
return snapshots.has(value);
|
|
6
|
+
}
|
|
7
|
+
/** Called only after admission has constructed and deeply frozen this container. */
|
|
8
|
+
export function retainImmutableSnapshot(value, summary) {
|
|
9
|
+
if (!snapshots.has(value))
|
|
10
|
+
snapshots.set(value, summary);
|
|
11
|
+
}
|
|
12
|
+
export function includeSnapshot(parent, child, depth) {
|
|
13
|
+
if (parent === undefined)
|
|
14
|
+
return;
|
|
15
|
+
parent.members += child.members;
|
|
16
|
+
parent.depth = Math.max(parent.depth, depth + child.depth);
|
|
17
|
+
parent.stringBytes = Math.max(parent.stringBytes, child.stringBytes);
|
|
18
|
+
}
|
|
19
|
+
/** A failed fit must use normal admission, preserving its exact error pointer. */
|
|
20
|
+
export function reusableSnapshot(value, limits, depth, members) {
|
|
21
|
+
const summary = snapshots.get(value);
|
|
22
|
+
if (summary === undefined ||
|
|
23
|
+
summary.members > limits.maxMembers - members ||
|
|
24
|
+
summary.depth > limits.maxDepth - depth ||
|
|
25
|
+
summary.stringBytes > limits.maxStringBytes)
|
|
26
|
+
return undefined;
|
|
27
|
+
return summary;
|
|
28
|
+
}
|
package/dist/value/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Portable values shared by the DSL and kernel contracts. */
|
|
2
|
-
export { accept } from './accept.js';
|
|
2
|
+
export { accept, acceptForRead, acceptImmutable } from './accept.js';
|
|
3
3
|
export { equal } from './equal.js';
|
|
4
4
|
export { BoundaryError, DecodeError } from './error.js';
|
|
5
5
|
export type { BoundaryErrorCode, DecodeErrorCode } from './error.js';
|
package/dist/value/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Portable values shared by the DSL and kernel contracts. */
|
|
2
|
-
export { accept } from './accept.js';
|
|
2
|
+
export { accept, acceptForRead, acceptImmutable } from './accept.js';
|
|
3
3
|
export { equal } from './equal.js';
|
|
4
4
|
export { BoundaryError, DecodeError } from './error.js';
|
|
5
5
|
export { defaultLimits } from './limits.js';
|
package/dist/value/unicode.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export declare function utf8Length(value: string, max?: number): number;
|
|
2
|
-
export declare function assertHostString(value: string, pointer: string, maxBytes: number):
|
|
2
|
+
export declare function assertHostString(value: string, pointer: string, maxBytes: number): number;
|
|
3
3
|
export declare function assertDecodedString(value: string, offset: number, maxBytes: number): void;
|
|
4
4
|
export declare function assertSourceString(value: string, maxBytes: number): void;
|
package/dist/value/unicode.js
CHANGED
|
@@ -34,6 +34,7 @@ export function assertHostString(value, pointer, maxBytes) {
|
|
|
34
34
|
if (length > maxBytes) {
|
|
35
35
|
throw new BoundaryError('VALUE_LIMIT', `String exceeds the ${maxBytes}-byte UTF-8 limit`, pointer);
|
|
36
36
|
}
|
|
37
|
+
return length;
|
|
37
38
|
}
|
|
38
39
|
export function assertDecodedString(value, offset, maxBytes) {
|
|
39
40
|
if (value.includes('\u0000')) {
|