pcb-scene3d-viewer 1.1.21 → 1.1.31
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 +3 -2
- package/docs/api.md +27 -1
- package/docs/circuitjson.md +63 -18
- package/docs/model-format.md +13 -2
- package/package.json +1 -1
- package/spec/library-scope.md +2 -2
- package/src/PcbAssemblyBoardSubstrateBuilder.mjs +25 -5
- package/src/PcbAssemblyComponentMeshBuilder.mjs +762 -0
- package/src/PcbAssemblyFillGeometryResolver.mjs +579 -0
- package/src/PcbAssemblyFillRingNormalizer.mjs +209 -0
- package/src/PcbAssemblyGeometryBuilder.mjs +41 -301
- package/src/PcbAssemblyGltfModelMeshParser.mjs +912 -0
- package/src/PcbAssemblyGltfValidator.mjs +460 -0
- package/src/PcbAssemblyGltfWriter.mjs +801 -0
- package/src/PcbAssemblyMeshUtils.mjs +117 -0
- package/src/PcbAssemblyModelMeshLoader.mjs +18 -0
- package/src/PcbAssemblyPadMeshBuilder.mjs +394 -0
- package/src/PcbAssemblyStepWriter.mjs +24 -2
- package/src/PcbAssemblyTextModelMeshParser.mjs +602 -0
- package/src/PcbModelArchiveExporter.mjs +521 -7
- package/src/PcbScene3dCircuitJsonAdapter.mjs +409 -7
- package/src/PcbScene3dCircuitJsonModelTransform.mjs +232 -0
- package/src/PcbScene3dCompanionBasePlacementAdjuster.mjs +242 -0
- package/src/PcbScene3dComponentVisibility.mjs +100 -5
- package/src/PcbScene3dController.mjs +25 -55
- package/src/PcbScene3dCopperDetailFilter.mjs +86 -9
- package/src/PcbScene3dCopperDetailGroupBuilder.mjs +186 -15
- package/src/PcbScene3dCopperDrillCutoutBuilder.mjs +258 -0
- package/src/PcbScene3dCopperFactory.mjs +99 -85
- package/src/PcbScene3dCopperFillMeshBuilder.mjs +393 -0
- package/src/PcbScene3dCopperLayerFilter.mjs +32 -3
- package/src/PcbScene3dCutoutGeometryFilter.mjs +17 -14
- package/src/PcbScene3dExternalCompanionFallback.mjs +202 -0
- package/src/PcbScene3dExternalModelCenteringPolicy.mjs +21 -0
- package/src/PcbScene3dExternalModelOpacity.mjs +51 -0
- package/src/PcbScene3dExternalModelPlacementRepair.mjs +5 -2
- package/src/PcbScene3dExternalModelSourceOriginPolicy.mjs +41 -0
- package/src/PcbScene3dExternalModels.mjs +16 -7
- package/src/PcbScene3dFallbackBodyFactory.mjs +105 -0
- package/src/PcbScene3dFallbackVisibility.mjs +58 -1
- package/src/PcbScene3dMaskCoveredCopperSideGroupBuilder.mjs +68 -0
- package/src/PcbScene3dPadFactory.mjs +35 -2
- package/src/PcbScene3dRenderGroupVisibility.mjs +8 -1
- package/src/PcbScene3dRuntime.mjs +94 -100
- package/src/PcbScene3dRuntimeHelpers.mjs +39 -0
- package/src/PcbScene3dSelectionIndexBuilder.mjs +87 -0
- package/src/PcbScene3dSelectionInspectorRenderer.mjs +50 -11
- package/src/PcbScene3dSelectionMarkerFactory.mjs +333 -0
- package/src/PcbScene3dSelectionMarkerOverlay.mjs +70 -0
- package/src/PcbScene3dSelectionStyler.mjs +52 -2
- package/src/PcbScene3dStaticBodyFactory.mjs +263 -0
- package/src/PcbScene3dText.mjs +1 -0
- package/src/PcbScene3dTransparentMeshSplitter.mjs +623 -0
- package/src/PcbScene3dViaFactory.mjs +27 -7
- package/src/scene3d.mjs +3 -0
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
import { PcbAssemblyGltfModelMeshParser } from './PcbAssemblyGltfModelMeshParser.mjs'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Performs structural validation for generated GLTF and GLB assembly payloads.
|
|
5
|
+
*/
|
|
6
|
+
export class PcbAssemblyGltfValidator {
|
|
7
|
+
/**
|
|
8
|
+
* Validates a GLTF JSON object, GLTF JSON string, or GLB byte payload.
|
|
9
|
+
* @param {object | string | Uint8Array} payload GLTF or GLB payload.
|
|
10
|
+
* @returns {{ severity: 'error', code: string, message: string }[]}
|
|
11
|
+
*/
|
|
12
|
+
static validate(payload) {
|
|
13
|
+
const issues = []
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const parsed = PcbAssemblyGltfValidator.#parsePayload(payload)
|
|
17
|
+
PcbAssemblyGltfValidator.#validateDocument(parsed, issues)
|
|
18
|
+
} catch (error) {
|
|
19
|
+
issues.push(
|
|
20
|
+
PcbAssemblyGltfValidator.#issue(
|
|
21
|
+
'gltf_parse_failed',
|
|
22
|
+
String(error?.message || error || 'Invalid GLTF payload.')
|
|
23
|
+
)
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return issues
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Parses a supported validation payload.
|
|
32
|
+
* @param {object | string | Uint8Array} payload GLTF or GLB payload.
|
|
33
|
+
* @returns {{ gltf: object, binaryBuffer: Uint8Array | null }}
|
|
34
|
+
*/
|
|
35
|
+
static #parsePayload(payload) {
|
|
36
|
+
if (payload instanceof Uint8Array) {
|
|
37
|
+
return PcbAssemblyGltfModelMeshParser.parseGlb(payload)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (typeof payload === 'string') {
|
|
41
|
+
return { gltf: JSON.parse(payload), binaryBuffer: null }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (payload && typeof payload === 'object') {
|
|
45
|
+
return { gltf: payload, binaryBuffer: null }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
throw new Error('Unsupported GLTF validation payload.')
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Validates the main GLTF document graph.
|
|
53
|
+
* @param {{ gltf: object, binaryBuffer: Uint8Array | null }} parsed Parsed payload.
|
|
54
|
+
* @param {{ severity: 'error', code: string, message: string }[]} issues Mutable issues.
|
|
55
|
+
* @returns {void}
|
|
56
|
+
*/
|
|
57
|
+
static #validateDocument(parsed, issues) {
|
|
58
|
+
const gltf = parsed.gltf || {}
|
|
59
|
+
if (String(gltf?.asset?.version || '') !== '2.0') {
|
|
60
|
+
issues.push(
|
|
61
|
+
PcbAssemblyGltfValidator.#issue(
|
|
62
|
+
'gltf_asset_version_invalid',
|
|
63
|
+
'GLTF asset version must be 2.0.'
|
|
64
|
+
)
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
PcbAssemblyGltfValidator.#validateScenes(gltf, issues)
|
|
69
|
+
PcbAssemblyGltfValidator.#validateNodes(gltf, issues)
|
|
70
|
+
PcbAssemblyGltfValidator.#validateMeshes(gltf, issues)
|
|
71
|
+
PcbAssemblyGltfValidator.#validateBufferViews(
|
|
72
|
+
gltf,
|
|
73
|
+
parsed.binaryBuffer,
|
|
74
|
+
issues
|
|
75
|
+
)
|
|
76
|
+
PcbAssemblyGltfValidator.#validateAccessors(gltf, issues)
|
|
77
|
+
PcbAssemblyGltfValidator.#validateTextures(gltf, issues)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Validates scenes and root node references.
|
|
82
|
+
* @param {object} gltf GLTF JSON.
|
|
83
|
+
* @param {{ severity: 'error', code: string, message: string }[]} issues Mutable issues.
|
|
84
|
+
* @returns {void}
|
|
85
|
+
*/
|
|
86
|
+
static #validateScenes(gltf, issues) {
|
|
87
|
+
const scenes = PcbAssemblyGltfValidator.#array(gltf?.scenes)
|
|
88
|
+
if (!scenes.length) {
|
|
89
|
+
issues.push(
|
|
90
|
+
PcbAssemblyGltfValidator.#issue(
|
|
91
|
+
'gltf_scene_missing',
|
|
92
|
+
'GLTF document must include at least one scene.'
|
|
93
|
+
)
|
|
94
|
+
)
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
scenes.forEach((scene, sceneIndex) => {
|
|
99
|
+
PcbAssemblyGltfValidator.#array(scene?.nodes).forEach(
|
|
100
|
+
(nodeIndex) => {
|
|
101
|
+
if (
|
|
102
|
+
!PcbAssemblyGltfValidator.#hasIndex(
|
|
103
|
+
gltf?.nodes,
|
|
104
|
+
nodeIndex
|
|
105
|
+
)
|
|
106
|
+
) {
|
|
107
|
+
issues.push(
|
|
108
|
+
PcbAssemblyGltfValidator.#issue(
|
|
109
|
+
'gltf_scene_node_invalid',
|
|
110
|
+
'Scene ' +
|
|
111
|
+
sceneIndex +
|
|
112
|
+
' references a missing node.'
|
|
113
|
+
)
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
)
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Validates node mesh and child references.
|
|
123
|
+
* @param {object} gltf GLTF JSON.
|
|
124
|
+
* @param {{ severity: 'error', code: string, message: string }[]} issues Mutable issues.
|
|
125
|
+
* @returns {void}
|
|
126
|
+
*/
|
|
127
|
+
static #validateNodes(gltf, issues) {
|
|
128
|
+
PcbAssemblyGltfValidator.#array(gltf?.nodes).forEach(
|
|
129
|
+
(node, nodeIndex) => {
|
|
130
|
+
if (
|
|
131
|
+
Number.isInteger(node?.mesh) &&
|
|
132
|
+
!PcbAssemblyGltfValidator.#hasIndex(gltf?.meshes, node.mesh)
|
|
133
|
+
) {
|
|
134
|
+
issues.push(
|
|
135
|
+
PcbAssemblyGltfValidator.#issue(
|
|
136
|
+
'gltf_node_mesh_invalid',
|
|
137
|
+
'Node ' + nodeIndex + ' references a missing mesh.'
|
|
138
|
+
)
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
PcbAssemblyGltfValidator.#array(node?.children).forEach(
|
|
143
|
+
(childIndex) => {
|
|
144
|
+
if (
|
|
145
|
+
!PcbAssemblyGltfValidator.#hasIndex(
|
|
146
|
+
gltf?.nodes,
|
|
147
|
+
childIndex
|
|
148
|
+
)
|
|
149
|
+
) {
|
|
150
|
+
issues.push(
|
|
151
|
+
PcbAssemblyGltfValidator.#issue(
|
|
152
|
+
'gltf_node_child_invalid',
|
|
153
|
+
'Node ' +
|
|
154
|
+
nodeIndex +
|
|
155
|
+
' references a missing child node.'
|
|
156
|
+
)
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
)
|
|
161
|
+
}
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Validates mesh primitive references.
|
|
167
|
+
* @param {object} gltf GLTF JSON.
|
|
168
|
+
* @param {{ severity: 'error', code: string, message: string }[]} issues Mutable issues.
|
|
169
|
+
* @returns {void}
|
|
170
|
+
*/
|
|
171
|
+
static #validateMeshes(gltf, issues) {
|
|
172
|
+
PcbAssemblyGltfValidator.#array(gltf?.meshes).forEach(
|
|
173
|
+
(mesh, meshIndex) => {
|
|
174
|
+
PcbAssemblyGltfValidator.#array(mesh?.primitives).forEach(
|
|
175
|
+
(primitive, primitiveIndex) => {
|
|
176
|
+
PcbAssemblyGltfValidator.#validatePrimitive(
|
|
177
|
+
gltf,
|
|
178
|
+
meshIndex,
|
|
179
|
+
primitiveIndex,
|
|
180
|
+
primitive,
|
|
181
|
+
issues
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Validates one primitive.
|
|
191
|
+
* @param {object} gltf GLTF JSON.
|
|
192
|
+
* @param {number} meshIndex Mesh index.
|
|
193
|
+
* @param {number} primitiveIndex Primitive index.
|
|
194
|
+
* @param {object} primitive Primitive JSON.
|
|
195
|
+
* @param {{ severity: 'error', code: string, message: string }[]} issues Mutable issues.
|
|
196
|
+
* @returns {void}
|
|
197
|
+
*/
|
|
198
|
+
static #validatePrimitive(
|
|
199
|
+
gltf,
|
|
200
|
+
meshIndex,
|
|
201
|
+
primitiveIndex,
|
|
202
|
+
primitive,
|
|
203
|
+
issues
|
|
204
|
+
) {
|
|
205
|
+
const positionAccessor = primitive?.attributes?.POSITION
|
|
206
|
+
if (
|
|
207
|
+
!Number.isInteger(positionAccessor) ||
|
|
208
|
+
!PcbAssemblyGltfValidator.#hasIndex(
|
|
209
|
+
gltf?.accessors,
|
|
210
|
+
positionAccessor
|
|
211
|
+
)
|
|
212
|
+
) {
|
|
213
|
+
issues.push(
|
|
214
|
+
PcbAssemblyGltfValidator.#issue(
|
|
215
|
+
'gltf_primitive_position_invalid',
|
|
216
|
+
'Mesh ' +
|
|
217
|
+
meshIndex +
|
|
218
|
+
' primitive ' +
|
|
219
|
+
primitiveIndex +
|
|
220
|
+
' must reference a POSITION accessor.'
|
|
221
|
+
)
|
|
222
|
+
)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (
|
|
226
|
+
Number.isInteger(primitive?.indices) &&
|
|
227
|
+
!PcbAssemblyGltfValidator.#hasIndex(
|
|
228
|
+
gltf?.accessors,
|
|
229
|
+
primitive.indices
|
|
230
|
+
)
|
|
231
|
+
) {
|
|
232
|
+
issues.push(
|
|
233
|
+
PcbAssemblyGltfValidator.#issue(
|
|
234
|
+
'gltf_primitive_indices_invalid',
|
|
235
|
+
'Mesh ' +
|
|
236
|
+
meshIndex +
|
|
237
|
+
' primitive ' +
|
|
238
|
+
primitiveIndex +
|
|
239
|
+
' references a missing index accessor.'
|
|
240
|
+
)
|
|
241
|
+
)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (
|
|
245
|
+
Number.isInteger(primitive?.material) &&
|
|
246
|
+
!PcbAssemblyGltfValidator.#hasIndex(
|
|
247
|
+
gltf?.materials,
|
|
248
|
+
primitive.material
|
|
249
|
+
)
|
|
250
|
+
) {
|
|
251
|
+
issues.push(
|
|
252
|
+
PcbAssemblyGltfValidator.#issue(
|
|
253
|
+
'gltf_primitive_material_invalid',
|
|
254
|
+
'Mesh ' +
|
|
255
|
+
meshIndex +
|
|
256
|
+
' primitive ' +
|
|
257
|
+
primitiveIndex +
|
|
258
|
+
' references a missing material.'
|
|
259
|
+
)
|
|
260
|
+
)
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Validates buffer view ranges.
|
|
266
|
+
* @param {object} gltf GLTF JSON.
|
|
267
|
+
* @param {Uint8Array | null} binaryBuffer GLB binary chunk.
|
|
268
|
+
* @param {{ severity: 'error', code: string, message: string }[]} issues Mutable issues.
|
|
269
|
+
* @returns {void}
|
|
270
|
+
*/
|
|
271
|
+
static #validateBufferViews(gltf, binaryBuffer, issues) {
|
|
272
|
+
const buffers = PcbAssemblyGltfValidator.#array(gltf?.buffers)
|
|
273
|
+
PcbAssemblyGltfValidator.#array(gltf?.bufferViews).forEach(
|
|
274
|
+
(bufferView, index) => {
|
|
275
|
+
const bufferIndex = Number(bufferView?.buffer || 0)
|
|
276
|
+
const buffer = buffers[bufferIndex]
|
|
277
|
+
if (!buffer) {
|
|
278
|
+
issues.push(
|
|
279
|
+
PcbAssemblyGltfValidator.#issue(
|
|
280
|
+
'gltf_buffer_view_buffer_invalid',
|
|
281
|
+
'Buffer view ' +
|
|
282
|
+
index +
|
|
283
|
+
' references a missing buffer.'
|
|
284
|
+
)
|
|
285
|
+
)
|
|
286
|
+
return
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const bufferLength =
|
|
290
|
+
Number(buffer.byteLength || 0) ||
|
|
291
|
+
PcbAssemblyGltfValidator.#bufferUriLength(buffer?.uri) ||
|
|
292
|
+
Number(binaryBuffer?.byteLength || 0)
|
|
293
|
+
const end =
|
|
294
|
+
Number(bufferView.byteOffset || 0) +
|
|
295
|
+
Number(bufferView.byteLength || 0)
|
|
296
|
+
if (end > bufferLength) {
|
|
297
|
+
issues.push(
|
|
298
|
+
PcbAssemblyGltfValidator.#issue(
|
|
299
|
+
'gltf_buffer_view_range_invalid',
|
|
300
|
+
'Buffer view ' + index + ' exceeds its buffer.'
|
|
301
|
+
)
|
|
302
|
+
)
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Validates accessor buffer-view references.
|
|
310
|
+
* @param {object} gltf GLTF JSON.
|
|
311
|
+
* @param {{ severity: 'error', code: string, message: string }[]} issues Mutable issues.
|
|
312
|
+
* @returns {void}
|
|
313
|
+
*/
|
|
314
|
+
static #validateAccessors(gltf, issues) {
|
|
315
|
+
PcbAssemblyGltfValidator.#array(gltf?.accessors).forEach(
|
|
316
|
+
(accessor, index) => {
|
|
317
|
+
if (
|
|
318
|
+
!PcbAssemblyGltfValidator.#hasIndex(
|
|
319
|
+
gltf?.bufferViews,
|
|
320
|
+
accessor?.bufferView
|
|
321
|
+
)
|
|
322
|
+
) {
|
|
323
|
+
issues.push(
|
|
324
|
+
PcbAssemblyGltfValidator.#issue(
|
|
325
|
+
'gltf_accessor_buffer_view_invalid',
|
|
326
|
+
'Accessor ' +
|
|
327
|
+
index +
|
|
328
|
+
' references a missing buffer view.'
|
|
329
|
+
)
|
|
330
|
+
)
|
|
331
|
+
}
|
|
332
|
+
if (!(Number(accessor?.count || 0) > 0)) {
|
|
333
|
+
issues.push(
|
|
334
|
+
PcbAssemblyGltfValidator.#issue(
|
|
335
|
+
'gltf_accessor_count_invalid',
|
|
336
|
+
'Accessor ' + index + ' must contain values.'
|
|
337
|
+
)
|
|
338
|
+
)
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
)
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Validates texture, sampler, and image references.
|
|
346
|
+
* @param {object} gltf GLTF JSON.
|
|
347
|
+
* @param {{ severity: 'error', code: string, message: string }[]} issues Mutable issues.
|
|
348
|
+
* @returns {void}
|
|
349
|
+
*/
|
|
350
|
+
static #validateTextures(gltf, issues) {
|
|
351
|
+
PcbAssemblyGltfValidator.#array(gltf?.textures).forEach(
|
|
352
|
+
(texture, index) => {
|
|
353
|
+
if (
|
|
354
|
+
!PcbAssemblyGltfValidator.#hasIndex(
|
|
355
|
+
gltf?.images,
|
|
356
|
+
texture?.source
|
|
357
|
+
)
|
|
358
|
+
) {
|
|
359
|
+
issues.push(
|
|
360
|
+
PcbAssemblyGltfValidator.#issue(
|
|
361
|
+
'gltf_texture_image_invalid',
|
|
362
|
+
'Texture ' + index + ' references a missing image.'
|
|
363
|
+
)
|
|
364
|
+
)
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
PcbAssemblyGltfValidator.#array(gltf?.materials).forEach(
|
|
370
|
+
(material, index) => {
|
|
371
|
+
const textureIndex =
|
|
372
|
+
material?.pbrMetallicRoughness?.baseColorTexture?.index
|
|
373
|
+
if (
|
|
374
|
+
Number.isInteger(textureIndex) &&
|
|
375
|
+
!PcbAssemblyGltfValidator.#hasIndex(
|
|
376
|
+
gltf?.textures,
|
|
377
|
+
textureIndex
|
|
378
|
+
)
|
|
379
|
+
) {
|
|
380
|
+
issues.push(
|
|
381
|
+
PcbAssemblyGltfValidator.#issue(
|
|
382
|
+
'gltf_material_texture_invalid',
|
|
383
|
+
'Material ' +
|
|
384
|
+
index +
|
|
385
|
+
' references a missing texture.'
|
|
386
|
+
)
|
|
387
|
+
)
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
)
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Returns embedded buffer URI byte length.
|
|
395
|
+
* @param {string | undefined} uri Buffer URI.
|
|
396
|
+
* @returns {number}
|
|
397
|
+
*/
|
|
398
|
+
static #bufferUriLength(uri) {
|
|
399
|
+
const text = String(uri || '')
|
|
400
|
+
const commaIndex = text.indexOf(',')
|
|
401
|
+
if (!text.startsWith('data:') || commaIndex < 0) {
|
|
402
|
+
return 0
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const payload = text.slice(commaIndex + 1)
|
|
406
|
+
if (text.slice(0, commaIndex).includes(';base64')) {
|
|
407
|
+
return PcbAssemblyGltfValidator.#base64Length(payload)
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return decodeURIComponent(payload).length
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Computes decoded base64 byte length.
|
|
415
|
+
* @param {string} value Base64 string.
|
|
416
|
+
* @returns {number}
|
|
417
|
+
*/
|
|
418
|
+
static #base64Length(value) {
|
|
419
|
+
const normalized = String(value || '').replace(/=+$/u, '')
|
|
420
|
+
return Math.floor((normalized.length * 3) / 4)
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Returns true when an array contains an integer index.
|
|
425
|
+
* @param {unknown} value Candidate array.
|
|
426
|
+
* @param {unknown} index Candidate index.
|
|
427
|
+
* @returns {boolean}
|
|
428
|
+
*/
|
|
429
|
+
static #hasIndex(value, index) {
|
|
430
|
+
return (
|
|
431
|
+
Array.isArray(value) &&
|
|
432
|
+
Number.isInteger(index) &&
|
|
433
|
+
index >= 0 &&
|
|
434
|
+
index < value.length
|
|
435
|
+
)
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Creates one validation issue.
|
|
440
|
+
* @param {string} code Issue code.
|
|
441
|
+
* @param {string} message Human-readable message.
|
|
442
|
+
* @returns {{ severity: 'error', code: string, message: string }}
|
|
443
|
+
*/
|
|
444
|
+
static #issue(code, message) {
|
|
445
|
+
return {
|
|
446
|
+
severity: 'error',
|
|
447
|
+
code,
|
|
448
|
+
message
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Normalizes a value to an array.
|
|
454
|
+
* @param {unknown} value Candidate value.
|
|
455
|
+
* @returns {any[]}
|
|
456
|
+
*/
|
|
457
|
+
static #array(value) {
|
|
458
|
+
return Array.isArray(value) ? value : []
|
|
459
|
+
}
|
|
460
|
+
}
|