circuitjson-toolkit 1.2.0 → 1.3.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.
@@ -0,0 +1,222 @@
1
+ import { RuntimeProxyBoundary } from '../contracts/RuntimeProxyBoundary.mjs'
2
+
3
+ const DOCUMENT_FIELDS = Object.freeze([
4
+ 'schema',
5
+ 'id',
6
+ 'modelSchema',
7
+ 'model',
8
+ 'source',
9
+ 'extensions',
10
+ 'assets',
11
+ 'diagnostics',
12
+ 'statistics'
13
+ ])
14
+ const PROJECT_FIELDS = Object.freeze([
15
+ 'schema',
16
+ 'id',
17
+ 'source',
18
+ 'documents',
19
+ 'project',
20
+ 'extensions',
21
+ 'assets',
22
+ 'diagnostics',
23
+ 'statistics'
24
+ ])
25
+
26
+ /**
27
+ * Classifies canonical result envelopes through data descriptors only.
28
+ */
29
+ export class WorkerResultShape {
30
+ /**
31
+ * Selects the exact property set traversed for one result container.
32
+ * @param {Record<string, PropertyDescriptor>} descriptors Descriptors.
33
+ * @param {boolean} output Whether result-output rules apply.
34
+ * @param {boolean} strictDescriptors Whether hidden properties are traversed.
35
+ * @returns {PropertyKey[]} Traversed keys.
36
+ */
37
+ static keys(descriptors, output, strictDescriptors) {
38
+ return output && !strictDescriptors
39
+ ? Reflect.ownKeys(descriptors).filter(
40
+ (key) =>
41
+ typeof key === 'string' && descriptors[key].enumerable
42
+ )
43
+ : Reflect.ownKeys(descriptors)
44
+ }
45
+
46
+ /**
47
+ * Validates a dense plain array and returns its captured length.
48
+ * @param {object | null} prototype Array prototype.
49
+ * @param {Record<string, PropertyDescriptor>} descriptors Descriptors.
50
+ * @param {boolean} output Whether result-output rules apply.
51
+ * @param {boolean} strictDescriptors Whether hidden properties are traversed.
52
+ * @returns {number} Dense array length.
53
+ */
54
+ static arrayLength(prototype, descriptors, output, strictDescriptors) {
55
+ const keys = Reflect.ownKeys(descriptors)
56
+ const length = WorkerResultShape.#value(descriptors.length)
57
+ const visibleKeys =
58
+ output && !strictDescriptors
59
+ ? keys.filter(
60
+ (key) =>
61
+ key !== 'length' &&
62
+ typeof key === 'string' &&
63
+ descriptors[key].enumerable
64
+ )
65
+ : keys
66
+ if (
67
+ prototype !== Array.prototype ||
68
+ !Number.isSafeInteger(length) ||
69
+ length < 0 ||
70
+ (output && !strictDescriptors
71
+ ? visibleKeys.length !== length
72
+ : keys.length !== length + 1)
73
+ ) {
74
+ throw new TypeError(
75
+ 'Worker request arrays must be bounded, dense, and plain.'
76
+ )
77
+ }
78
+ return length
79
+ }
80
+
81
+ /**
82
+ * Tests one already-inspected canonical document envelope.
83
+ * @param {Record<string, PropertyDescriptor>} descriptors Descriptors.
84
+ * @param {PropertyKey[]} keys Traversed keys.
85
+ * @returns {boolean} Whether the exact structural document shape matches.
86
+ */
87
+ static document(descriptors, keys) {
88
+ if (
89
+ !WorkerResultShape.#exactDataFields(
90
+ descriptors,
91
+ keys,
92
+ DOCUMENT_FIELDS
93
+ )
94
+ ) {
95
+ return false
96
+ }
97
+ return (
98
+ WorkerResultShape.#value(descriptors.schema) ===
99
+ 'ecad-toolkit.document.v1' &&
100
+ typeof WorkerResultShape.#value(descriptors.id) === 'string' &&
101
+ WorkerResultShape.#record(
102
+ WorkerResultShape.#value(descriptors.modelSchema)
103
+ ) &&
104
+ Array.isArray(WorkerResultShape.#value(descriptors.model)) &&
105
+ WorkerResultShape.#record(
106
+ WorkerResultShape.#value(descriptors.source)
107
+ ) &&
108
+ WorkerResultShape.#record(
109
+ WorkerResultShape.#value(descriptors.extensions)
110
+ ) &&
111
+ Array.isArray(WorkerResultShape.#value(descriptors.assets)) &&
112
+ Array.isArray(WorkerResultShape.#value(descriptors.diagnostics)) &&
113
+ WorkerResultShape.#record(
114
+ WorkerResultShape.#value(descriptors.statistics)
115
+ )
116
+ )
117
+ }
118
+
119
+ /**
120
+ * Tests one already-inspected canonical project envelope.
121
+ * @param {Record<string, PropertyDescriptor>} descriptors Descriptors.
122
+ * @param {PropertyKey[]} keys Traversed keys.
123
+ * @returns {boolean} Whether the exact structural project shape matches.
124
+ */
125
+ static project(descriptors, keys) {
126
+ if (
127
+ !WorkerResultShape.#exactDataFields(
128
+ descriptors,
129
+ keys,
130
+ PROJECT_FIELDS
131
+ )
132
+ ) {
133
+ return false
134
+ }
135
+ const project = WorkerResultShape.#value(descriptors.project)
136
+ return (
137
+ WorkerResultShape.#value(descriptors.schema) ===
138
+ 'ecad-toolkit.project.v1' &&
139
+ typeof WorkerResultShape.#value(descriptors.id) === 'string' &&
140
+ WorkerResultShape.#record(
141
+ WorkerResultShape.#value(descriptors.source)
142
+ ) &&
143
+ Array.isArray(WorkerResultShape.#value(descriptors.documents)) &&
144
+ (project === null || WorkerResultShape.#record(project)) &&
145
+ WorkerResultShape.#record(
146
+ WorkerResultShape.#value(descriptors.extensions)
147
+ ) &&
148
+ Array.isArray(WorkerResultShape.#value(descriptors.assets)) &&
149
+ Array.isArray(WorkerResultShape.#value(descriptors.diagnostics)) &&
150
+ WorkerResultShape.#record(
151
+ WorkerResultShape.#value(descriptors.statistics)
152
+ )
153
+ )
154
+ }
155
+
156
+ /**
157
+ * Inspects a nested project document without reading accessor values.
158
+ * @param {unknown} value Candidate document.
159
+ * @param {boolean} strictDescriptors Whether hidden properties are traversed.
160
+ * @returns {boolean} Whether the exact structural document shape matches.
161
+ */
162
+ static documentCandidate(value, strictDescriptors) {
163
+ if (!WorkerResultShape.#record(value)) return false
164
+ RuntimeProxyBoundary.assert(value, 'Worker request data')
165
+ let prototype
166
+ let descriptors
167
+ try {
168
+ prototype = Object.getPrototypeOf(value)
169
+ descriptors = Object.getOwnPropertyDescriptors(value)
170
+ } catch {
171
+ return false
172
+ }
173
+ if (prototype !== Object.prototype && prototype !== null) return false
174
+ const keys = WorkerResultShape.keys(
175
+ descriptors,
176
+ true,
177
+ strictDescriptors
178
+ )
179
+ return WorkerResultShape.document(descriptors, keys)
180
+ }
181
+
182
+ /**
183
+ * Requires the exact enumerable own data field set.
184
+ * @param {Record<string, PropertyDescriptor>} descriptors Descriptors.
185
+ * @param {PropertyKey[]} keys Traversed keys.
186
+ * @param {string[]} fields Expected fields.
187
+ * @returns {boolean} Whether the field set matches.
188
+ */
189
+ static #exactDataFields(descriptors, keys, fields) {
190
+ if (
191
+ keys.length !== fields.length ||
192
+ keys.some((key) => typeof key !== 'string' || !fields.includes(key))
193
+ ) {
194
+ return false
195
+ }
196
+ return fields.every((field) => {
197
+ const descriptor = descriptors[field]
198
+ return Boolean(
199
+ descriptor &&
200
+ descriptor.enumerable === true &&
201
+ Object.hasOwn(descriptor, 'value')
202
+ )
203
+ })
204
+ }
205
+
206
+ /** @param {unknown} value Candidate. @returns {boolean} Whether value is record-shaped. */
207
+ static #record(value) {
208
+ return (
209
+ value !== null && typeof value === 'object' && !Array.isArray(value)
210
+ )
211
+ }
212
+
213
+ /** @param {PropertyDescriptor | undefined} descriptor Descriptor. @returns {unknown} Data value. */
214
+ static #value(descriptor) {
215
+ return descriptor && Object.hasOwn(descriptor, 'value')
216
+ ? descriptor.value
217
+ : undefined
218
+ }
219
+ }
220
+
221
+ Object.freeze(WorkerResultShape.prototype)
222
+ Object.freeze(WorkerResultShape)