circuitjson-toolkit 1.0.17 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE.md +31 -0
- package/README.md +274 -107
- package/docs/api.md +507 -96
- package/docs/capabilities.md +70 -0
- package/docs/migration/behaviors.md +45 -0
- package/docs/migration/parser.md +60 -0
- package/docs/migration/renderers.md +515 -0
- package/docs/migration/root.md +740 -0
- package/docs/migration.md +126 -0
- package/docs/model-format.md +175 -57
- package/docs/provenance.md +206 -0
- package/docs/release-notes-v1.1.0.md +154 -0
- package/docs/release-notes-v1.1.1.md +24 -0
- package/docs/testing.md +122 -7
- package/package.json +32 -5
- package/spec/api-baseline-v1.0.17.json +1 -0
- package/spec/baseline-provenance-v1.0.17.json +7 -0
- package/spec/circuitjson-schema-snapshot.json +321 -0
- package/spec/circuitjson-schema-source.json +28 -0
- package/spec/feature-preservation.json +1 -0
- package/spec/library-scope.md +27 -20
- package/src/capabilities.mjs +1 -0
- package/src/core/ArchiveEntryPath.mjs +93 -0
- package/src/core/ArchiveLimits.mjs +31 -0
- package/src/core/ArchiveLimitsValidator.mjs +107 -0
- package/src/core/AsyncInputOwnership.mjs +56 -0
- package/src/core/AttachedValueLimits.mjs +67 -0
- package/src/core/CircuitJsonDiagnosticIndexer.mjs +184 -0
- package/src/core/CircuitJsonDocument.mjs +19 -61
- package/src/core/CircuitJsonElementTypes.mjs +10 -0
- package/src/core/CircuitJsonElementValidator.mjs +98 -847
- package/src/core/CircuitJsonIndexer.mjs +274 -194
- package/src/core/CircuitJsonManufacturingBuilder.mjs +167 -164
- package/src/core/CircuitJsonParser.mjs +75 -13
- package/src/core/CircuitJsonPcbClearanceDiagnostics.mjs +12 -6
- package/src/core/CircuitJsonPcbHolePrimitiveModel.mjs +108 -10
- package/src/core/CircuitJsonPcbPadPrimitiveModel.mjs +1 -1
- package/src/core/CircuitJsonPcbPrimitiveArtwork.mjs +44 -38
- package/src/core/CircuitJsonPcbPrimitiveBuilder.mjs +146 -28
- package/src/core/CircuitJsonPcbPrimitiveFields.mjs +70 -4
- package/src/core/CircuitJsonPcbPrimitiveIndex.mjs +18 -2
- package/src/core/CircuitJsonPcbPrimitiveOverlays.mjs +26 -9
- package/src/core/CircuitJsonPcbZonePrimitiveBuilder.mjs +7 -6
- package/src/core/CircuitJsonSerializedInputAudit.mjs +87 -0
- package/src/core/CircuitJsonSourceMetadata.mjs +5 -1
- package/src/core/CircuitJsonSupportMatrixBuilder.mjs +3 -1
- package/src/core/CircuitJsonToolkitElementSchema.mjs +218 -0
- package/src/core/CircuitJsonUnitParsers.mjs +101 -0
- package/src/core/CircuitJsonUnits.mjs +13 -87
- package/src/core/CircuitJsonUpstreamSchema.mjs +9 -0
- package/src/core/CircuitJsonUpstreamValidator.mjs +418 -0
- package/src/core/CircuitJsonValidationUnits.mjs +6 -0
- package/src/core/ManufacturingService.mjs +323 -0
- package/src/core/Parser.mjs +343 -0
- package/src/core/ParserOptions.mjs +333 -0
- package/src/core/PcbBoundsSelectionModel.mjs +55 -19
- package/src/core/PcbDiagnosticFocusModel.mjs +42 -11
- package/src/core/PcbInteractionIndex.mjs +368 -0
- package/src/core/PcbInteractionPrimitiveModel.mjs +393 -62
- package/src/core/ProjectAsyncInputOwner.mjs +70 -0
- package/src/core/ProjectLoader.mjs +975 -0
- package/src/core/SimulationService.mjs +790 -0
- package/src/core/ToolkitCapabilities.mjs +130 -0
- package/src/core/ZipArchiveInspector.mjs +649 -0
- package/src/core/context/BinaryDataSnapshot.mjs +217 -0
- package/src/core/context/CircuitJsonContextIndexes.mjs +96 -0
- package/src/core/context/CircuitJsonDerivedCache.mjs +114 -0
- package/src/core/context/CircuitJsonDocumentContext.mjs +353 -0
- package/src/core/context/CircuitJsonLegacyModel.mjs +147 -0
- package/src/core/context/CircuitJsonLegacyNormalizer.mjs +847 -0
- package/src/core/context/CircuitJsonMetadataBoundary.mjs +76 -0
- package/src/core/context/CircuitJsonModelFreezeTraversal.mjs +179 -0
- package/src/core/context/CircuitJsonReadOnlyDocument.mjs +920 -0
- package/src/core/context/CircuitJsonSchematicTableNormalizer.mjs +314 -0
- package/src/core/context/CircuitJsonValidationAuthority.mjs +39 -0
- package/src/core/context/CircuitJsonValidationProof.mjs +217 -0
- package/src/core/context/PcbPrimitivePreparation.mjs +198 -0
- package/src/core/context/PcbSpatialIndex.mjs +701 -0
- package/src/core/context/ProtectedExtensionBinaryBoundary.mjs +128 -0
- package/src/core/context/StructuredDataSnapshot.mjs +683 -0
- package/src/core/contracts/DocumentResult.mjs +198 -0
- package/src/core/contracts/ProjectResult.mjs +96 -0
- package/src/core/contracts/RuntimeProxyBoundary.mjs +48 -0
- package/src/core/contracts/ToolkitAsset.mjs +493 -0
- package/src/core/contracts/ToolkitDiagnostic.mjs +38 -0
- package/src/core/contracts/ToolkitError.mjs +176 -0
- package/src/core/contracts/ToolkitProgress.mjs +89 -0
- package/src/core/interaction/CanonicalInteractionOptions.mjs +246 -0
- package/src/core/interaction/PcbInteractionBounds.mjs +167 -0
- package/src/core/query/CircuitTraversal.mjs +343 -0
- package/src/core/query/ComponentGrouping.mjs +275 -0
- package/src/core/query/QueryNetlistBuilder.mjs +306 -0
- package/src/core/query/QueryService.mjs +435 -0
- package/src/core/query/RegexPattern.mjs +75 -0
- package/src/core/rendering/CanonicalBomOrder.mjs +81 -0
- package/src/core/rendering/CanonicalBomRows.mjs +92 -0
- package/src/core/rendering/CanonicalRenderOptions.mjs +498 -0
- package/src/core/rendering/CanonicalSvgDocument.mjs +102 -0
- package/src/core/rendering/PcbRenderPlan.mjs +429 -0
- package/src/core/rendering/SchematicSheetSelector.mjs +335 -0
- package/src/core/scene3d/PcbScene3dBuilder.mjs +906 -0
- package/src/core/scene3d/PcbScene3dPreparator.mjs +47 -0
- package/src/core/scene3d/Scene3dAssetIndex.mjs +284 -0
- package/src/core/scene3d/Scene3dBoardModel.mjs +596 -0
- package/src/core/scene3d/Scene3dDocumentMetadata.mjs +167 -0
- package/src/core/scene3d/Scene3dFreeze.mjs +37 -0
- package/src/core/scene3d/Scene3dIdRegistry.mjs +34 -0
- package/src/core/scene3d/Scene3dInputPreflight.mjs +193 -0
- package/src/core/scene3d/Scene3dMaterials.mjs +58 -0
- package/src/core/scene3d/Scene3dModelReference.mjs +134 -0
- package/src/core/scene3d/Scene3dOptions.mjs +363 -0
- package/src/core/scene3d/SceneAssetResolver.mjs +441 -0
- package/src/core/simulation/SimulationParameterCloner.mjs +543 -0
- package/src/core/worker/ParserWorkerClient.mjs +999 -0
- package/src/core/worker/ToolkitWorkerProtocol.mjs +412 -0
- package/src/core/worker/WorkerRequestData.mjs +650 -0
- package/src/core/worker/WorkerResponseData.mjs +905 -0
- package/src/extensions.mjs +37 -0
- package/src/index.mjs +14 -9
- package/src/interaction.mjs +2 -0
- package/src/manufacturing.mjs +1 -0
- package/src/parser.mjs +12 -2
- package/src/project.mjs +5 -0
- package/src/query.mjs +1 -0
- package/src/renderers.mjs +3 -29
- package/src/scene3d.mjs +3 -0
- package/src/simulation.mjs +1 -0
- package/src/styles/renderers.css +24 -0
- package/src/testing/ToolkitContractFixtures.mjs +124 -0
- package/src/testing/ToolkitLoopbackWorker.mjs +174 -0
- package/src/testing/runToolkitContract.mjs +705 -0
- package/src/testing.mjs +3 -0
- package/src/ui/BomTableRenderer.mjs +304 -0
- package/src/ui/CircuitJsonPcbBoardSvgRenderer.mjs +80 -0
- package/src/ui/CircuitJsonPcbPrimitiveAttributeRenderer.mjs +3 -5
- package/src/ui/CircuitJsonPcbSvgRenderer.mjs +63 -43
- package/src/ui/CircuitJsonPcbViaSvgRenderer.mjs +3 -5
- package/src/ui/CircuitJsonSchematicDebugRenderer.mjs +164 -0
- package/src/ui/CircuitJsonSchematicImageSvgRenderer.mjs +210 -0
- package/src/ui/CircuitJsonSchematicLineRenderer.mjs +86 -0
- package/src/ui/CircuitJsonSchematicSheetSymbolSvgRenderer.mjs +98 -0
- package/src/ui/CircuitJsonSchematicSvgArcPath.mjs +117 -17
- package/src/ui/CircuitJsonSchematicSvgPortMetadata.mjs +67 -20
- package/src/ui/CircuitJsonSchematicSvgPrimitiveAttributes.mjs +45 -9
- package/src/ui/CircuitJsonSchematicSvgRenderer.mjs +151 -148
- package/src/ui/CircuitJsonSchematicTableSvgRenderer.mjs +4 -292
- package/src/ui/PcbSvgRenderer.mjs +41 -0
- package/src/ui/SafeSvgPaint.mjs +26 -0
- package/src/ui/SafeXmlText.mjs +60 -0
- package/src/ui/SchematicGeometryBounds.mjs +540 -0
- package/src/ui/SchematicSvgRenderer.mjs +110 -0
- package/src/ui/SchematicTableGeometry.mjs +319 -0
- package/src/ui/SchematicTextAnchor.mjs +55 -0
- package/src/ui/SchematicTextBounds.mjs +98 -0
- package/src/workers/parser.worker.mjs +59 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
const ARRAY_BUFFER_LENGTH = Object.getOwnPropertyDescriptor(
|
|
2
|
+
ArrayBuffer.prototype,
|
|
3
|
+
'byteLength'
|
|
4
|
+
)?.get
|
|
5
|
+
const SHARED_ARRAY_BUFFER_LENGTH =
|
|
6
|
+
typeof SharedArrayBuffer === 'function'
|
|
7
|
+
? Object.getOwnPropertyDescriptor(
|
|
8
|
+
SharedArrayBuffer.prototype,
|
|
9
|
+
'byteLength'
|
|
10
|
+
)?.get
|
|
11
|
+
: null
|
|
12
|
+
const TYPED_ARRAY_PROTOTYPE = Object.getPrototypeOf(Uint8Array.prototype)
|
|
13
|
+
const TYPED_ARRAY_BUFFER = Object.getOwnPropertyDescriptor(
|
|
14
|
+
TYPED_ARRAY_PROTOTYPE,
|
|
15
|
+
'buffer'
|
|
16
|
+
)?.get
|
|
17
|
+
const TYPED_ARRAY_OFFSET = Object.getOwnPropertyDescriptor(
|
|
18
|
+
TYPED_ARRAY_PROTOTYPE,
|
|
19
|
+
'byteOffset'
|
|
20
|
+
)?.get
|
|
21
|
+
const TYPED_ARRAY_LENGTH = Object.getOwnPropertyDescriptor(
|
|
22
|
+
TYPED_ARRAY_PROTOTYPE,
|
|
23
|
+
'byteLength'
|
|
24
|
+
)?.get
|
|
25
|
+
const DATA_VIEW_BUFFER = Object.getOwnPropertyDescriptor(
|
|
26
|
+
DataView.prototype,
|
|
27
|
+
'buffer'
|
|
28
|
+
)?.get
|
|
29
|
+
const DATA_VIEW_OFFSET = Object.getOwnPropertyDescriptor(
|
|
30
|
+
DataView.prototype,
|
|
31
|
+
'byteOffset'
|
|
32
|
+
)?.get
|
|
33
|
+
const DATA_VIEW_LENGTH = Object.getOwnPropertyDescriptor(
|
|
34
|
+
DataView.prototype,
|
|
35
|
+
'byteLength'
|
|
36
|
+
)?.get
|
|
37
|
+
const UINT8_ARRAY_SET = Uint8Array.prototype.set
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Reads and copies binary platform objects through captured intrinsic slots.
|
|
41
|
+
*/
|
|
42
|
+
export class BinaryDataSnapshot {
|
|
43
|
+
/**
|
|
44
|
+
* Describes one ArrayBuffer, SharedArrayBuffer, typed array, or DataView.
|
|
45
|
+
* @param {unknown} value Binary candidate.
|
|
46
|
+
* @returns {{ buffer: ArrayBuffer | SharedArrayBuffer, byteOffset: number, byteLength: number, kind: 'buffer' | 'typed-array' | 'data-view' } | null} Intrinsic binary range.
|
|
47
|
+
*/
|
|
48
|
+
static describe(value) {
|
|
49
|
+
const arrayBufferLength = BinaryDataSnapshot.#callLength(
|
|
50
|
+
ARRAY_BUFFER_LENGTH,
|
|
51
|
+
value
|
|
52
|
+
)
|
|
53
|
+
if (arrayBufferLength !== null) {
|
|
54
|
+
return {
|
|
55
|
+
buffer: value,
|
|
56
|
+
byteOffset: 0,
|
|
57
|
+
byteLength: arrayBufferLength,
|
|
58
|
+
kind: 'buffer'
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const sharedLength = BinaryDataSnapshot.#callLength(
|
|
62
|
+
SHARED_ARRAY_BUFFER_LENGTH,
|
|
63
|
+
value
|
|
64
|
+
)
|
|
65
|
+
if (sharedLength !== null) {
|
|
66
|
+
return {
|
|
67
|
+
buffer: value,
|
|
68
|
+
byteOffset: 0,
|
|
69
|
+
byteLength: sharedLength,
|
|
70
|
+
kind: 'buffer'
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const typed = BinaryDataSnapshot.#view(
|
|
75
|
+
value,
|
|
76
|
+
TYPED_ARRAY_BUFFER,
|
|
77
|
+
TYPED_ARRAY_OFFSET,
|
|
78
|
+
TYPED_ARRAY_LENGTH,
|
|
79
|
+
'typed-array'
|
|
80
|
+
)
|
|
81
|
+
if (typed) return typed
|
|
82
|
+
return BinaryDataSnapshot.#view(
|
|
83
|
+
value,
|
|
84
|
+
DATA_VIEW_BUFFER,
|
|
85
|
+
DATA_VIEW_OFFSET,
|
|
86
|
+
DATA_VIEW_LENGTH,
|
|
87
|
+
'data-view'
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Returns the exact intrinsic binary byte length.
|
|
93
|
+
* @param {unknown} value Binary candidate.
|
|
94
|
+
* @returns {number | null} Byte length or null for non-binary input.
|
|
95
|
+
*/
|
|
96
|
+
static byteLength(value) {
|
|
97
|
+
return BinaryDataSnapshot.describe(value)?.byteLength ?? null
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Copies the exact visible byte range into isolated non-shared memory.
|
|
102
|
+
* @param {unknown} value Binary candidate.
|
|
103
|
+
* @param {{ buffer: ArrayBuffer | SharedArrayBuffer, byteOffset: number, byteLength: number } | null} [capturedRange] Previously captured intrinsic range.
|
|
104
|
+
* @returns {Uint8Array} Isolated bytes.
|
|
105
|
+
*/
|
|
106
|
+
static copyBytes(value, capturedRange = null) {
|
|
107
|
+
const range = capturedRange || BinaryDataSnapshot.describe(value)
|
|
108
|
+
if (!range) throw new TypeError('Expected binary data.')
|
|
109
|
+
try {
|
|
110
|
+
const source = new Uint8Array(
|
|
111
|
+
range.buffer,
|
|
112
|
+
range.byteOffset,
|
|
113
|
+
range.byteLength
|
|
114
|
+
)
|
|
115
|
+
const result = new Uint8Array(range.byteLength)
|
|
116
|
+
UINT8_ARRAY_SET.call(result, source)
|
|
117
|
+
return result
|
|
118
|
+
} catch {
|
|
119
|
+
throw new TypeError('Binary data changed during capture.')
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Copies one binary metadata value while retaining its common view type.
|
|
125
|
+
* @param {unknown} value Binary metadata.
|
|
126
|
+
* @param {{ buffer: ArrayBuffer | SharedArrayBuffer, byteOffset: number, byteLength: number, kind: 'buffer' | 'typed-array' | 'data-view' } | null} [capturedRange] Previously captured intrinsic range.
|
|
127
|
+
* @returns {ArrayBuffer | Uint8Array | DataView} Isolated binary value.
|
|
128
|
+
*/
|
|
129
|
+
static clone(value, capturedRange = null) {
|
|
130
|
+
const range = capturedRange || BinaryDataSnapshot.describe(value)
|
|
131
|
+
if (!range) throw new TypeError('Expected binary data.')
|
|
132
|
+
const bytes = BinaryDataSnapshot.copyBytes(value, range)
|
|
133
|
+
if (range.kind === 'buffer') return bytes.buffer
|
|
134
|
+
if (range.kind === 'data-view') return new DataView(bytes.buffer)
|
|
135
|
+
|
|
136
|
+
const prototype = Object.getPrototypeOf(value)
|
|
137
|
+
const Constructor = BinaryDataSnapshot.#typedArrayConstructor(prototype)
|
|
138
|
+
if (!Constructor || Constructor === Uint8Array) return bytes
|
|
139
|
+
const bytesPerElement = Constructor.BYTES_PER_ELEMENT
|
|
140
|
+
if (bytes.byteLength % bytesPerElement !== 0) return bytes
|
|
141
|
+
return new Constructor(bytes.buffer)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Calls a captured buffer length getter as a brand check.
|
|
146
|
+
* @param {Function | null | undefined} getter Intrinsic getter.
|
|
147
|
+
* @param {unknown} value Candidate value.
|
|
148
|
+
* @returns {number | null} Intrinsic length or null.
|
|
149
|
+
*/
|
|
150
|
+
static #callLength(getter, value) {
|
|
151
|
+
if (typeof getter !== 'function') return null
|
|
152
|
+
try {
|
|
153
|
+
return getter.call(value)
|
|
154
|
+
} catch {
|
|
155
|
+
return null
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Reads captured intrinsic view slots without ordinary property access.
|
|
161
|
+
* @param {unknown} value View candidate.
|
|
162
|
+
* @param {Function | undefined} bufferGetter Buffer getter.
|
|
163
|
+
* @param {Function | undefined} offsetGetter Offset getter.
|
|
164
|
+
* @param {Function | undefined} lengthGetter Length getter.
|
|
165
|
+
* @param {'typed-array' | 'data-view'} kind View kind.
|
|
166
|
+
* @returns {{ buffer: ArrayBuffer | SharedArrayBuffer, byteOffset: number, byteLength: number, kind: 'typed-array' | 'data-view' } | null} Intrinsic view range.
|
|
167
|
+
*/
|
|
168
|
+
static #view(value, bufferGetter, offsetGetter, lengthGetter, kind) {
|
|
169
|
+
if (
|
|
170
|
+
typeof bufferGetter !== 'function' ||
|
|
171
|
+
typeof offsetGetter !== 'function' ||
|
|
172
|
+
typeof lengthGetter !== 'function'
|
|
173
|
+
) {
|
|
174
|
+
return null
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
return {
|
|
178
|
+
buffer: bufferGetter.call(value),
|
|
179
|
+
byteOffset: offsetGetter.call(value),
|
|
180
|
+
byteLength: lengthGetter.call(value),
|
|
181
|
+
kind
|
|
182
|
+
}
|
|
183
|
+
} catch {
|
|
184
|
+
return null
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Maps a genuine typed-array prototype to its platform constructor.
|
|
190
|
+
* @param {object | null} prototype Candidate prototype.
|
|
191
|
+
* @returns {Function | null} Matching typed-array constructor.
|
|
192
|
+
*/
|
|
193
|
+
static #typedArrayConstructor(prototype) {
|
|
194
|
+
const constructors = [
|
|
195
|
+
Int8Array,
|
|
196
|
+
Uint8Array,
|
|
197
|
+
Uint8ClampedArray,
|
|
198
|
+
Int16Array,
|
|
199
|
+
Uint16Array,
|
|
200
|
+
Int32Array,
|
|
201
|
+
Uint32Array,
|
|
202
|
+
Float32Array,
|
|
203
|
+
Float64Array,
|
|
204
|
+
...(typeof BigInt64Array === 'function'
|
|
205
|
+
? [BigInt64Array, BigUint64Array]
|
|
206
|
+
: [])
|
|
207
|
+
]
|
|
208
|
+
return (
|
|
209
|
+
constructors.find(
|
|
210
|
+
(Constructor) => Constructor.prototype === prototype
|
|
211
|
+
) || null
|
|
212
|
+
)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
Object.freeze(BinaryDataSnapshot.prototype)
|
|
217
|
+
Object.freeze(BinaryDataSnapshot)
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { CircuitJsonIndexer } from '../CircuitJsonIndexer.mjs'
|
|
2
|
+
import { CircuitJsonValidationProof } from './CircuitJsonValidationProof.mjs'
|
|
3
|
+
|
|
4
|
+
const INDEX_NAMES = new Set([
|
|
5
|
+
'elements',
|
|
6
|
+
'identifiers',
|
|
7
|
+
'relations',
|
|
8
|
+
'connectivity',
|
|
9
|
+
'spatial'
|
|
10
|
+
])
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Owns lazily prepared named indexes for one document context.
|
|
14
|
+
*/
|
|
15
|
+
export class CircuitJsonContextIndexes {
|
|
16
|
+
#builds
|
|
17
|
+
#document
|
|
18
|
+
#indexes = new Map()
|
|
19
|
+
#model
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Creates a context-owned index registry.
|
|
23
|
+
* @param {Record<string, any>} document Proven document envelope.
|
|
24
|
+
* @param {object[]} model Proven CircuitJSON model.
|
|
25
|
+
* @param {Record<string, number>} builds Named index build counters.
|
|
26
|
+
*/
|
|
27
|
+
constructor(document, model, builds) {
|
|
28
|
+
this.#document = document
|
|
29
|
+
this.#model = model
|
|
30
|
+
this.#builds = builds
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Ensures every requested named index is available.
|
|
35
|
+
* @param {unknown} names Requested index names.
|
|
36
|
+
* @returns {CircuitJsonContextIndexes} This registry.
|
|
37
|
+
*/
|
|
38
|
+
ensure(names = []) {
|
|
39
|
+
for (const name of CircuitJsonContextIndexes.#names(names)) {
|
|
40
|
+
if (!INDEX_NAMES.has(name)) {
|
|
41
|
+
throw new RangeError(
|
|
42
|
+
`Unsupported CircuitJSON context index: ${name}.`
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
if (this.#indexes.has(name)) continue
|
|
46
|
+
|
|
47
|
+
this.#indexes.set(name, this.#build(name))
|
|
48
|
+
this.#builds[name] = (this.#builds[name] || 0) + 1
|
|
49
|
+
}
|
|
50
|
+
return this
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Returns one named index, creating it when first requested.
|
|
55
|
+
* @param {unknown} name Requested index name.
|
|
56
|
+
* @returns {Record<string, any>} Prepared index.
|
|
57
|
+
*/
|
|
58
|
+
get(name) {
|
|
59
|
+
const normalized = String(name || '').trim()
|
|
60
|
+
this.ensure([normalized])
|
|
61
|
+
return this.#indexes.get(normalized)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Returns true when one named index is already prepared.
|
|
66
|
+
* @param {unknown} name Requested index name.
|
|
67
|
+
* @returns {boolean} Whether the named index exists.
|
|
68
|
+
*/
|
|
69
|
+
has(name) {
|
|
70
|
+
return this.#indexes.has(String(name || '').trim())
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Creates only the work family requested by one named index.
|
|
75
|
+
* @param {string} name Requested index name.
|
|
76
|
+
* @returns {Record<string, any>} Named index view.
|
|
77
|
+
*/
|
|
78
|
+
#build(name) {
|
|
79
|
+
return CircuitJsonIndexer.index(
|
|
80
|
+
this.#model,
|
|
81
|
+
CircuitJsonValidationProof.indexOptions(this.#document, [name])
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Normalizes an index name request.
|
|
87
|
+
* @param {unknown} names Requested names.
|
|
88
|
+
* @returns {string[]} Normalized names.
|
|
89
|
+
*/
|
|
90
|
+
static #names(names) {
|
|
91
|
+
const candidates = Array.isArray(names) ? names : [names]
|
|
92
|
+
return [
|
|
93
|
+
...new Set(candidates.map(String).map((name) => name.trim()))
|
|
94
|
+
].filter(Boolean)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
const VALUE_OWNERS = new WeakMap()
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Stores request-scoped derived values by namespace and key.
|
|
5
|
+
*/
|
|
6
|
+
export class CircuitJsonDerivedCache {
|
|
7
|
+
#builds
|
|
8
|
+
#namespaces = new Map()
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Creates a request-scoped derived cache.
|
|
12
|
+
* @param {Record<string, number>} builds Successful build counters.
|
|
13
|
+
*/
|
|
14
|
+
constructor(builds) {
|
|
15
|
+
this.#builds = builds
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Returns an existing value or creates and caches one successful result.
|
|
20
|
+
* @param {string} namespace Cache namespace.
|
|
21
|
+
* @param {string} key Cache key.
|
|
22
|
+
* @param {() => any} factory Value factory.
|
|
23
|
+
* @returns {any} Cached or newly built value.
|
|
24
|
+
*/
|
|
25
|
+
getOrCreate(namespace, key, factory) {
|
|
26
|
+
if (typeof factory !== 'function') {
|
|
27
|
+
throw new TypeError('A derived value factory is required.')
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const entries = this.#entries(namespace)
|
|
31
|
+
if (entries.has(key)) return entries.get(key)
|
|
32
|
+
|
|
33
|
+
const value = factory()
|
|
34
|
+
if (
|
|
35
|
+
value &&
|
|
36
|
+
(typeof value === 'object' || typeof value === 'function') &&
|
|
37
|
+
typeof value.then === 'function'
|
|
38
|
+
) {
|
|
39
|
+
throw new TypeError(
|
|
40
|
+
'Derived value factories must return synchronous values.'
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
CircuitJsonDerivedCache.#bindValue(this, namespace, key, value)
|
|
44
|
+
entries.set(key, value)
|
|
45
|
+
const statisticKey = `${namespace}:${key}`
|
|
46
|
+
this.#builds[statisticKey] = (this.#builds[statisticKey] || 0) + 1
|
|
47
|
+
return value
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Returns whether an object value belongs to this exact cache entry.
|
|
52
|
+
* @param {string} namespace Cache namespace.
|
|
53
|
+
* @param {string} key Cache key.
|
|
54
|
+
* @param {unknown} value Derived value candidate.
|
|
55
|
+
* @returns {boolean} Whether ownership matches cache, namespace, and key.
|
|
56
|
+
*/
|
|
57
|
+
owns(namespace, key, value) {
|
|
58
|
+
if (!CircuitJsonDerivedCache.#isObject(value)) return false
|
|
59
|
+
const owner = VALUE_OWNERS.get(value)
|
|
60
|
+
return (
|
|
61
|
+
owner?.cache === this &&
|
|
62
|
+
owner.namespace === namespace &&
|
|
63
|
+
owner.key === key
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Returns the cache map for one namespace.
|
|
69
|
+
* @param {string} namespace Cache namespace.
|
|
70
|
+
* @returns {Map<string, any>} Namespace entries.
|
|
71
|
+
*/
|
|
72
|
+
#entries(namespace) {
|
|
73
|
+
if (!this.#namespaces.has(namespace)) {
|
|
74
|
+
this.#namespaces.set(namespace, new Map())
|
|
75
|
+
}
|
|
76
|
+
return this.#namespaces.get(namespace)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Binds one object value to exactly one request-scoped cache entry.
|
|
81
|
+
* @param {CircuitJsonDerivedCache} cache Owning cache.
|
|
82
|
+
* @param {string} namespace Cache namespace.
|
|
83
|
+
* @param {string} key Cache key.
|
|
84
|
+
* @param {unknown} value Derived value.
|
|
85
|
+
* @returns {void}
|
|
86
|
+
*/
|
|
87
|
+
static #bindValue(cache, namespace, key, value) {
|
|
88
|
+
if (!CircuitJsonDerivedCache.#isObject(value)) return
|
|
89
|
+
const owner = VALUE_OWNERS.get(value)
|
|
90
|
+
if (
|
|
91
|
+
owner &&
|
|
92
|
+
(owner.cache !== cache ||
|
|
93
|
+
owner.namespace !== namespace ||
|
|
94
|
+
owner.key !== key)
|
|
95
|
+
) {
|
|
96
|
+
throw new TypeError(
|
|
97
|
+
'Derived object values cannot be transplanted between cache entries.'
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
VALUE_OWNERS.set(value, { cache, namespace, key })
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Returns whether a value can be owned through a WeakMap.
|
|
105
|
+
* @param {unknown} value Value candidate.
|
|
106
|
+
* @returns {boolean} Whether the value is an object or function.
|
|
107
|
+
*/
|
|
108
|
+
static #isObject(value) {
|
|
109
|
+
return (
|
|
110
|
+
value !== null &&
|
|
111
|
+
(typeof value === 'object' || typeof value === 'function')
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
}
|