@faicad/faijs 0.19.0 → 0.29.3
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/LICENSE +202 -28
- package/dist/api/api-namespace.d.ts +5 -5
- package/dist/api/api-namespace.d.ts.map +1 -1
- package/dist/api/api-namespace.js +4 -4
- package/dist/api/api-namespace.js.map +1 -1
- package/dist/api/assert.d.ts +2 -2
- package/dist/api/assert.js +8 -8
- package/dist/api/assert.js.map +1 -1
- package/dist/api/asset.d.ts +1 -1
- package/dist/api/asset.js +2 -2
- package/dist/api/asset.js.map +1 -1
- package/dist/api/boolean.d.ts +1 -1
- package/dist/api/boolean.d.ts.map +1 -1
- package/dist/api/boolean.js +128 -16
- package/dist/api/boolean.js.map +1 -1
- package/dist/api/brep-mirror/joinery-brep.d.ts.map +1 -1
- package/dist/api/brep-mirror/joinery-brep.js +3 -1
- package/dist/api/brep-mirror/joinery-brep.js.map +1 -1
- package/dist/api/cadquery-selectors/borrow-bridge.d.ts +14 -0
- package/dist/api/cadquery-selectors/borrow-bridge.d.ts.map +1 -0
- package/dist/api/cadquery-selectors/borrow-bridge.js +52 -0
- package/dist/api/cadquery-selectors/borrow-bridge.js.map +1 -0
- package/dist/api/cadquery-selectors/edge.d.ts +40 -0
- package/dist/api/cadquery-selectors/edge.d.ts.map +1 -0
- package/dist/api/cadquery-selectors/edge.js +70 -0
- package/dist/api/cadquery-selectors/edge.js.map +1 -0
- package/dist/api/cadquery-selectors/entity.d.ts +121 -0
- package/dist/api/cadquery-selectors/entity.d.ts.map +1 -0
- package/dist/api/cadquery-selectors/entity.js +167 -0
- package/dist/api/cadquery-selectors/entity.js.map +1 -0
- package/dist/api/cadquery-selectors/face.d.ts +33 -0
- package/dist/api/cadquery-selectors/face.d.ts.map +1 -0
- package/dist/api/cadquery-selectors/face.js +353 -0
- package/dist/api/cadquery-selectors/face.js.map +1 -0
- package/dist/api/cadquery-selectors/grammar.d.ts +50 -0
- package/dist/api/cadquery-selectors/grammar.d.ts.map +1 -0
- package/dist/api/cadquery-selectors/grammar.js +345 -0
- package/dist/api/cadquery-selectors/grammar.js.map +1 -0
- package/dist/api/cadquery-selectors/index.d.ts +18 -0
- package/dist/api/cadquery-selectors/index.d.ts.map +1 -0
- package/dist/api/cadquery-selectors/index.js +17 -0
- package/dist/api/cadquery-selectors/index.js.map +1 -0
- package/dist/api/cadquery-selectors/predicates.d.ts +76 -0
- package/dist/api/cadquery-selectors/predicates.d.ts.map +1 -0
- package/dist/api/cadquery-selectors/predicates.js +85 -0
- package/dist/api/cadquery-selectors/predicates.js.map +1 -0
- package/dist/api/cadquery-selectors/resolve.d.ts +41 -0
- package/dist/api/cadquery-selectors/resolve.d.ts.map +1 -0
- package/dist/api/cadquery-selectors/resolve.js +243 -0
- package/dist/api/cadquery-selectors/resolve.js.map +1 -0
- package/dist/api/cadquery-selectors/types.d.ts +96 -0
- package/dist/api/cadquery-selectors/types.d.ts.map +1 -0
- package/dist/api/cadquery-selectors/types.js +11 -0
- package/dist/api/cadquery-selectors/types.js.map +1 -0
- package/dist/api/chamfer.d.ts +13 -5
- package/dist/api/chamfer.d.ts.map +1 -1
- package/dist/api/chamfer.js +63 -7
- package/dist/api/chamfer.js.map +1 -1
- package/dist/api/compound-geom.d.ts +1 -1
- package/dist/api/compound-geom.js +1 -1
- package/dist/api/compound-geom.js.map +1 -1
- package/dist/api/draft.d.ts +1 -1
- package/dist/api/draft.js +1 -1
- package/dist/api/draft.js.map +1 -1
- package/dist/api/edge-ref.d.ts +1 -1
- package/dist/api/edge-ref.d.ts.map +1 -1
- package/dist/api/edge-ref.js +11 -3
- package/dist/api/edge-ref.js.map +1 -1
- package/dist/api/engrave.d.ts +1 -1
- package/dist/api/engrave.js +7 -7
- package/dist/api/engrave.js.map +1 -1
- package/dist/api/extrude.d.ts +6 -1
- package/dist/api/extrude.d.ts.map +1 -1
- package/dist/api/extrude.js +59 -5
- package/dist/api/extrude.js.map +1 -1
- package/dist/api/face-ref.d.ts +1 -1
- package/dist/api/face-ref.js +1 -1
- package/dist/api/face-ref.js.map +1 -1
- package/dist/api/feature-repair.d.ts +1 -1
- package/dist/api/feature-repair.js +1 -1
- package/dist/api/feature-repair.js.map +1 -1
- package/dist/api/fillet-variable.d.ts +1 -1
- package/dist/api/fillet-variable.js +1 -1
- package/dist/api/fillet-variable.js.map +1 -1
- package/dist/api/fillet.d.ts +28 -8
- package/dist/api/fillet.d.ts.map +1 -1
- package/dist/api/fillet.js +64 -9
- package/dist/api/fillet.js.map +1 -1
- package/dist/api/generated/script-face-manifest.d.ts +4 -0
- package/dist/api/generated/script-face-manifest.d.ts.map +1 -1
- package/dist/api/generated/script-face-manifest.js.map +1 -1
- package/dist/api/geom.d.ts +1 -1
- package/dist/api/geom.js +1 -1
- package/dist/api/geom.js.map +1 -1
- package/dist/api/helix.d.ts +1 -1
- package/dist/api/helix.js +1 -1
- package/dist/api/helix.js.map +1 -1
- package/dist/api/import-brep.d.ts +20 -2
- package/dist/api/import-brep.d.ts.map +1 -1
- package/dist/api/import-brep.js +28 -3
- package/dist/api/import-brep.js.map +1 -1
- package/dist/api/import-step.d.ts +17 -2
- package/dist/api/import-step.d.ts.map +1 -1
- package/dist/api/import-step.js +23 -3
- package/dist/api/import-step.js.map +1 -1
- package/dist/api/index.d.ts +2 -1
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +5 -2
- package/dist/api/index.js.map +1 -1
- package/dist/api/internal/compat-op.js +1 -1
- package/dist/api/internal/compat-op.js.map +1 -1
- package/dist/api/internal/mesh-solid-op.d.ts +236 -0
- package/dist/api/internal/mesh-solid-op.d.ts.map +1 -0
- package/dist/api/internal/mesh-solid-op.js +352 -0
- package/dist/api/internal/mesh-solid-op.js.map +1 -0
- package/dist/api/internal/svg-asset-resolver.d.ts +1 -1
- package/dist/api/internal/svg-asset-resolver.js +1 -1
- package/dist/api/internal/svg-asset-resolver.js.map +1 -1
- package/dist/api/knurl.d.ts +1 -1
- package/dist/api/knurl.d.ts.map +1 -1
- package/dist/api/knurl.js +3 -2
- package/dist/api/knurl.js.map +1 -1
- package/dist/api/loft.d.ts +1 -1
- package/dist/api/loft.js +1 -1
- package/dist/api/loft.js.map +1 -1
- package/dist/api/measurement/index.d.ts +9 -5
- package/dist/api/measurement/index.d.ts.map +1 -1
- package/dist/api/measurement/index.js +47 -20
- package/dist/api/measurement/index.js.map +1 -1
- package/dist/api/pattern.d.ts +5 -3
- package/dist/api/pattern.d.ts.map +1 -1
- package/dist/api/pattern.js +32 -4
- package/dist/api/pattern.js.map +1 -1
- package/dist/api/place.d.ts +1 -1
- package/dist/api/place.js +1 -1
- package/dist/api/place.js.map +1 -1
- package/dist/api/primitives.d.ts +5 -4
- package/dist/api/primitives.d.ts.map +1 -1
- package/dist/api/primitives.js +11 -5
- package/dist/api/primitives.js.map +1 -1
- package/dist/api/profile.d.ts +64 -8
- package/dist/api/profile.d.ts.map +1 -1
- package/dist/api/profile.js +61 -10
- package/dist/api/profile.js.map +1 -1
- package/dist/api/punch-hole.d.ts +3 -0
- package/dist/api/punch-hole.d.ts.map +1 -1
- package/dist/api/punch-hole.js.map +1 -1
- package/dist/api/reconcile.d.ts +1 -1
- package/dist/api/reconcile.js +1 -1
- package/dist/api/reconcile.js.map +1 -1
- package/dist/api/replicate.d.ts.map +1 -1
- package/dist/api/replicate.js +122 -1
- package/dist/api/replicate.js.map +1 -1
- package/dist/api/screw.d.ts +1 -1
- package/dist/api/screw.js +3 -3
- package/dist/api/screw.js.map +1 -1
- package/dist/api/sdf.d.ts +1 -1
- package/dist/api/sdf.js +2 -2
- package/dist/api/sdf.js.map +1 -1
- package/dist/api/section-by-plane.d.ts +1 -1
- package/dist/api/section-by-plane.d.ts.map +1 -1
- package/dist/api/section-by-plane.js +2 -1
- package/dist/api/section-by-plane.js.map +1 -1
- package/dist/api/shell.d.ts +11 -4
- package/dist/api/shell.d.ts.map +1 -1
- package/dist/api/shell.js +53 -7
- package/dist/api/shell.js.map +1 -1
- package/dist/api/sketch-on-face.d.ts +41 -0
- package/dist/api/sketch-on-face.d.ts.map +1 -1
- package/dist/api/sketch-on-face.js +100 -1
- package/dist/api/sketch-on-face.js.map +1 -1
- package/dist/api/sketch-on-plane.d.ts +17 -9
- package/dist/api/sketch-on-plane.d.ts.map +1 -1
- package/dist/api/sketch-on-plane.js +12 -10
- package/dist/api/sketch-on-plane.js.map +1 -1
- package/dist/api/split-by-plane.d.ts +1 -1
- package/dist/api/split-by-plane.d.ts.map +1 -1
- package/dist/api/split-by-plane.js +2 -1
- package/dist/api/split-by-plane.js.map +1 -1
- package/dist/api/split.d.ts +1 -1
- package/dist/api/split.js +2 -2
- package/dist/api/split.js.map +1 -1
- package/dist/api/surface/arg-spec.d.ts +8 -0
- package/dist/api/surface/arg-spec.d.ts.map +1 -1
- package/dist/api/surface/arg-spec.js.map +1 -1
- package/dist/api/sweep.d.ts +1 -1
- package/dist/api/sweep.js +1 -1
- package/dist/api/sweep.js.map +1 -1
- package/dist/api/thicken.d.ts +1 -1
- package/dist/api/thicken.js +1 -1
- package/dist/api/thicken.js.map +1 -1
- package/dist/api/topo-resolve.d.ts +74 -1
- package/dist/api/topo-resolve.d.ts.map +1 -1
- package/dist/api/topo-resolve.js +180 -2
- package/dist/api/topo-resolve.js.map +1 -1
- package/dist/api/transform.d.ts +10 -6
- package/dist/api/transform.d.ts.map +1 -1
- package/dist/api/transform.js +74 -21
- package/dist/api/transform.js.map +1 -1
- package/dist/api/wire.d.ts +1 -1
- package/dist/api/wire.js +1 -1
- package/dist/api/wire.js.map +1 -1
- package/dist/api/xml-dom.d.ts +27 -0
- package/dist/api/xml-dom.d.ts.map +1 -0
- package/dist/api/xml-dom.js +45 -0
- package/dist/api/xml-dom.js.map +1 -0
- package/dist/brep/brep-ops.d.ts +13 -11
- package/dist/brep/brep-ops.d.ts.map +1 -1
- package/dist/brep/brep-ops.js +31 -30
- package/dist/brep/brep-ops.js.map +1 -1
- package/dist/brep/brep-topology.d.ts +5 -4
- package/dist/brep/brep-topology.d.ts.map +1 -1
- package/dist/brep/brep-topology.js +9 -6
- package/dist/brep/brep-topology.js.map +1 -1
- package/dist/brep/effective-deflection.d.ts +35 -0
- package/dist/brep/effective-deflection.d.ts.map +1 -0
- package/dist/brep/effective-deflection.js +49 -0
- package/dist/brep/effective-deflection.js.map +1 -0
- package/dist/brep/engine/adapters/brep-mock.d.ts.map +1 -1
- package/dist/brep/engine/adapters/brep-mock.js +27 -0
- package/dist/brep/engine/adapters/brep-mock.js.map +1 -1
- package/dist/brep/engine/adapters/brepkit.d.ts +18 -0
- package/dist/brep/engine/adapters/brepkit.d.ts.map +1 -1
- package/dist/brep/engine/adapters/brepkit.js +46 -1
- package/dist/brep/engine/adapters/brepkit.js.map +1 -1
- package/dist/brep/engine/adapters/occt.d.ts +1 -1
- package/dist/brep/engine/adapters/occt.d.ts.map +1 -1
- package/dist/brep/engine/adapters/occt.js +3 -1
- package/dist/brep/engine/adapters/occt.js.map +1 -1
- package/dist/brep/engine/primitives.d.ts +29 -7
- package/dist/brep/engine/primitives.d.ts.map +1 -1
- package/dist/brep/engine/primitives.js.map +1 -1
- package/dist/brep/engine/registry.d.ts +24 -1
- package/dist/brep/engine/registry.d.ts.map +1 -1
- package/dist/brep/engine/registry.js +24 -0
- package/dist/brep/engine/registry.js.map +1 -1
- package/dist/brep/engine/types.d.ts +54 -3
- package/dist/brep/engine/types.d.ts.map +1 -1
- package/dist/brep/engine/types.js +10 -0
- package/dist/brep/engine/types.js.map +1 -1
- package/dist/brep/export/export-model.d.ts +79 -0
- package/dist/brep/export/export-model.d.ts.map +1 -0
- package/dist/brep/export/export-model.js +336 -0
- package/dist/brep/export/export-model.js.map +1 -0
- package/dist/brep/face-evolution.d.ts +1 -1
- package/dist/brep/face-evolution.d.ts.map +1 -1
- package/dist/brep/face-evolution.js +20 -1
- package/dist/brep/face-evolution.js.map +1 -1
- package/dist/brep/handle-bridge.d.ts +1 -1
- package/dist/brep/handle-bridge.js +1 -1
- package/dist/brep/handle-bridge.js.map +1 -1
- package/dist/brep/mesh-solid.d.ts +218 -0
- package/dist/brep/mesh-solid.d.ts.map +1 -0
- package/dist/brep/mesh-solid.js +229 -0
- package/dist/brep/mesh-solid.js.map +1 -0
- package/dist/brep/primitives-brep.d.ts.map +1 -1
- package/dist/brep/primitives-brep.js +3 -1
- package/dist/brep/primitives-brep.js.map +1 -1
- package/dist/brep/text/fontRegistry.d.ts +32 -1
- package/dist/brep/text/fontRegistry.d.ts.map +1 -1
- package/dist/brep/text/fontRegistry.js +51 -1
- package/dist/brep/text/fontRegistry.js.map +1 -1
- package/dist/brep/text/text-to-solid.d.ts.map +1 -1
- package/dist/brep/text/text-to-solid.js +8 -3
- package/dist/brep/text/text-to-solid.js.map +1 -1
- package/dist/brepkit-kernel/brepkitKernel.d.ts +18 -2
- package/dist/brepkit-kernel/brepkitKernel.d.ts.map +1 -1
- package/dist/brepkit-kernel/brepkitKernel.js +453 -125
- package/dist/brepkit-kernel/brepkitKernel.js.map +1 -1
- package/dist/brepkit-kernel/brepkitWasm.d.ts +8 -0
- package/dist/brepkit-kernel/brepkitWasm.d.ts.map +1 -1
- package/dist/brepkit-kernel/brepkitWasm.js +10 -0
- package/dist/brepkit-kernel/brepkitWasm.js.map +1 -1
- package/dist/browser-host/browser-font-provider.d.ts +12 -0
- package/dist/browser-host/browser-font-provider.d.ts.map +1 -1
- package/dist/browser-host/browser-font-provider.js +25 -0
- package/dist/browser-host/browser-font-provider.js.map +1 -1
- package/dist/browser.d.ts +6 -3
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +10 -3
- package/dist/browser.js.map +1 -1
- package/dist/cad-runtime/backend-dispatch.d.ts +28 -3
- package/dist/cad-runtime/backend-dispatch.d.ts.map +1 -1
- package/dist/cad-runtime/backend-dispatch.js +119 -4
- package/dist/cad-runtime/backend-dispatch.js.map +1 -1
- package/dist/cad-runtime/browser-lib-loader.js.map +1 -1
- package/dist/cad-runtime/direct-executor.d.ts +19 -3
- package/dist/cad-runtime/direct-executor.d.ts.map +1 -1
- package/dist/cad-runtime/direct-executor.js +49 -1
- package/dist/cad-runtime/direct-executor.js.map +1 -1
- package/dist/cad-runtime/ports.d.ts +3 -3
- package/dist/cad-runtime/ports.d.ts.map +1 -1
- package/dist/cad-runtime/ports.js.map +1 -1
- package/dist/cad-runtime/preview-exec.d.ts +4 -4
- package/dist/cad-runtime/preview-exec.js +2 -2
- package/dist/cad-runtime/preview-exec.js.map +1 -1
- package/dist/cad-runtime/runtime.d.ts +66 -8
- package/dist/cad-runtime/runtime.d.ts.map +1 -1
- package/dist/cad-runtime/runtime.js +121 -8
- package/dist/cad-runtime/runtime.js.map +1 -1
- package/dist/define-op.d.ts +37 -1
- package/dist/define-op.d.ts.map +1 -1
- package/dist/define-op.js +25 -1
- package/dist/define-op.js.map +1 -1
- package/dist/env-agnostic.d.ts +7 -7
- package/dist/env-agnostic.d.ts.map +1 -1
- package/dist/env-agnostic.js +3 -3
- package/dist/env-agnostic.js.map +1 -1
- package/dist/geometry2d/adapt.d.ts +39 -4
- package/dist/geometry2d/adapt.d.ts.map +1 -1
- package/dist/geometry2d/adapt.js +14 -3
- package/dist/geometry2d/adapt.js.map +1 -1
- package/dist/geometry2d/blueprint.d.ts +2 -2
- package/dist/geometry2d/blueprint.js +2 -2
- package/dist/geometry2d/blueprint.js.map +1 -1
- package/dist/geometry2d/bridge/lift-on-plane.d.ts +18 -8
- package/dist/geometry2d/bridge/lift-on-plane.d.ts.map +1 -1
- package/dist/geometry2d/bridge/lift-on-plane.js +71 -12
- package/dist/geometry2d/bridge/lift-on-plane.js.map +1 -1
- package/dist/geometry2d/bridge/plane.d.ts +17 -0
- package/dist/geometry2d/bridge/plane.d.ts.map +1 -1
- package/dist/geometry2d/bridge/plane.js +22 -0
- package/dist/geometry2d/bridge/plane.js.map +1 -1
- package/dist/geometry2d/curve2d.d.ts +25 -0
- package/dist/geometry2d/curve2d.d.ts.map +1 -1
- package/dist/geometry2d/curve2d.js +41 -5
- package/dist/geometry2d/curve2d.js.map +1 -1
- package/dist/geometry2d/pen-sketcher.d.ts +25 -0
- package/dist/geometry2d/pen-sketcher.d.ts.map +1 -1
- package/dist/geometry2d/pen-sketcher.js +43 -0
- package/dist/geometry2d/pen-sketcher.js.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -3
- package/dist/index.js.map +1 -1
- package/dist/io/bytes-node.d.ts +11 -0
- package/dist/io/bytes-node.d.ts.map +1 -0
- package/dist/io/bytes-node.js +28 -0
- package/dist/io/bytes-node.js.map +1 -0
- package/dist/io/bytes.d.ts +33 -0
- package/dist/io/bytes.d.ts.map +1 -0
- package/dist/io/bytes.js +41 -0
- package/dist/io/bytes.js.map +1 -0
- package/dist/io/fai-zip/container-read.d.ts +102 -0
- package/dist/io/fai-zip/container-read.d.ts.map +1 -0
- package/dist/io/fai-zip/container-read.js +305 -0
- package/dist/io/fai-zip/container-read.js.map +1 -0
- package/dist/io/fai-zip/container-write.d.ts +70 -0
- package/dist/io/fai-zip/container-write.d.ts.map +1 -0
- package/dist/io/fai-zip/container-write.js +186 -0
- package/dist/io/fai-zip/container-write.js.map +1 -0
- package/dist/io/fai-zip/container.d.ts +107 -0
- package/dist/io/fai-zip/container.d.ts.map +1 -0
- package/dist/io/fai-zip/container.js +89 -0
- package/dist/io/fai-zip/container.js.map +1 -0
- package/dist/io/fai-zip/index.d.ts +18 -0
- package/dist/io/fai-zip/index.d.ts.map +1 -0
- package/dist/io/fai-zip/index.js +4 -0
- package/dist/io/fai-zip/index.js.map +1 -0
- package/dist/io/index.d.ts +10 -0
- package/dist/io/index.d.ts.map +1 -0
- package/dist/io/index.js +13 -0
- package/dist/io/index.js.map +1 -0
- package/dist/io/zip.d.ts +68 -0
- package/dist/io/zip.d.ts.map +1 -0
- package/dist/io/zip.js +147 -0
- package/dist/io/zip.js.map +1 -0
- package/dist/lang/code-to-args.d.ts +30 -4
- package/dist/lang/code-to-args.d.ts.map +1 -1
- package/dist/lang/code-to-args.js +42 -3
- package/dist/lang/code-to-args.js.map +1 -1
- package/dist/lang/codegen.d.ts +45 -1
- package/dist/lang/codegen.d.ts.map +1 -1
- package/dist/lang/codegen.js +135 -6
- package/dist/lang/codegen.js.map +1 -1
- package/dist/lang/host-arg.d.ts +6 -0
- package/dist/lang/host-arg.d.ts.map +1 -1
- package/dist/lang/host-arg.js.map +1 -1
- package/dist/lang/metadata-extractor.d.ts +25 -0
- package/dist/lang/metadata-extractor.d.ts.map +1 -1
- package/dist/lang/metadata-extractor.js +289 -25
- package/dist/lang/metadata-extractor.js.map +1 -1
- package/dist/lang/parse-error.d.ts +7 -1
- package/dist/lang/parse-error.d.ts.map +1 -1
- package/dist/lang/parse-error.js.map +1 -1
- package/dist/lang/security-scanner.d.ts +9 -1
- package/dist/lang/security-scanner.d.ts.map +1 -1
- package/dist/lang/security-scanner.js +38 -1
- package/dist/lang/security-scanner.js.map +1 -1
- package/dist/lang/statement-summary.d.ts +1 -1
- package/dist/lang/statement-summary.js +1 -1
- package/dist/lang/statement-summary.js.map +1 -1
- package/dist/lang/types.d.ts +1 -1
- package/dist/lang/types.js +1 -1
- package/dist/lang/types.js.map +1 -1
- package/dist/mesh/index.d.ts +2 -1
- package/dist/mesh/index.d.ts.map +1 -1
- package/dist/mesh/index.js +2 -1
- package/dist/mesh/index.js.map +1 -1
- package/dist/mesh/io.d.ts +69 -9
- package/dist/mesh/io.d.ts.map +1 -1
- package/dist/mesh/io.js +121 -17
- package/dist/mesh/io.js.map +1 -1
- package/dist/mesh/reconcile.d.ts +1 -1
- package/dist/mesh/reconcile.js +1 -1
- package/dist/mesh/reconcile.js.map +1 -1
- package/dist/mesh/rigid-transform.d.ts +3 -3
- package/dist/mesh/rigid-transform.js +3 -3
- package/dist/mesh/rigid-transform.js.map +1 -1
- package/dist/mesh/stl-unit.d.ts +24 -0
- package/dist/mesh/stl-unit.d.ts.map +1 -0
- package/dist/mesh/stl-unit.js +17 -0
- package/dist/mesh/stl-unit.js.map +1 -0
- package/dist/mesh/threemf-bambu.d.ts +142 -0
- package/dist/mesh/threemf-bambu.d.ts.map +1 -0
- package/dist/mesh/threemf-bambu.js +334 -0
- package/dist/mesh/threemf-bambu.js.map +1 -0
- package/dist/mesh/threemf-loader.d.ts +50 -0
- package/dist/mesh/threemf-loader.d.ts.map +1 -0
- package/dist/mesh/threemf-loader.js +628 -0
- package/dist/mesh/threemf-loader.js.map +1 -0
- package/dist/mesh/threemf-view.d.ts +71 -0
- package/dist/mesh/threemf-view.d.ts.map +1 -0
- package/dist/mesh/threemf-view.js +100 -0
- package/dist/mesh/threemf-view.js.map +1 -0
- package/dist/mesh/transform.d.ts +2 -2
- package/dist/mesh/transform.d.ts.map +1 -1
- package/dist/mesh/transform.js +3 -3
- package/dist/mesh/transform.js.map +1 -1
- package/dist/mesh/types.d.ts +1 -1
- package/dist/mesh/types.d.ts.map +1 -1
- package/dist/mesh/types.js.map +1 -1
- package/dist/node-host/cli.d.ts +44 -6
- package/dist/node-host/cli.d.ts.map +1 -1
- package/dist/node-host/cli.js +127 -50
- package/dist/node-host/cli.js.map +1 -1
- package/dist/node-host/font-family-index.d.ts +93 -0
- package/dist/node-host/font-family-index.d.ts.map +1 -0
- package/dist/node-host/font-family-index.js +273 -0
- package/dist/node-host/font-family-index.js.map +1 -0
- package/dist/node-host/fs-asset-resolver.d.ts +8 -3
- package/dist/node-host/fs-asset-resolver.d.ts.map +1 -1
- package/dist/node-host/fs-asset-resolver.js +13 -5
- package/dist/node-host/fs-asset-resolver.js.map +1 -1
- package/dist/node-host/index.d.ts +8 -0
- package/dist/node-host/index.d.ts.map +1 -1
- package/dist/node-host/index.js +1 -0
- package/dist/node-host/index.js.map +1 -1
- package/dist/node-host/node-font-provider.d.ts +28 -0
- package/dist/node-host/node-font-provider.d.ts.map +1 -1
- package/dist/node-host/node-font-provider.js +49 -0
- package/dist/node-host/node-font-provider.js.map +1 -1
- package/dist/node.d.ts +1 -0
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +1 -0
- package/dist/node.js.map +1 -1
- package/dist/occt-kernel/occt-primitives.d.ts.map +1 -1
- package/dist/occt-kernel/occt-primitives.js +35 -2
- package/dist/occt-kernel/occt-primitives.js.map +1 -1
- package/dist/occt-kernel/occtKernel.d.ts +18 -13
- package/dist/occt-kernel/occtKernel.d.ts.map +1 -1
- package/dist/occt-kernel/occtKernel.js +20 -31
- package/dist/occt-kernel/occtKernel.js.map +1 -1
- package/dist/occt-kernel/stepColorParser.d.ts +18 -0
- package/dist/occt-kernel/stepColorParser.d.ts.map +1 -1
- package/dist/occt-kernel/stepColorParser.js +38 -0
- package/dist/occt-kernel/stepColorParser.js.map +1 -1
- package/dist/occt-kernel/topologyExt.d.ts +1 -1
- package/dist/occt-kernel/topologyExt.d.ts.map +1 -1
- package/dist/occt-kernel/topologyExt.js +8 -6
- package/dist/occt-kernel/topologyExt.js.map +1 -1
- package/dist/primitives/text/cjk-font.d.ts +2 -15
- package/dist/primitives/text/cjk-font.d.ts.map +1 -1
- package/dist/primitives/text/cjk-font.js +13 -1
- package/dist/primitives/text/cjk-font.js.map +1 -1
- package/dist/runtime-state.d.ts +103 -9
- package/dist/runtime-state.d.ts.map +1 -1
- package/dist/runtime-state.js +95 -2
- package/dist/runtime-state.js.map +1 -1
- package/dist/sdk.d.ts +4 -4
- package/dist/sdk.d.ts.map +1 -1
- package/dist/sdk.js +6 -6
- package/dist/sdk.js.map +1 -1
- package/dist/shape.d.ts +97 -2
- package/dist/shape.d.ts.map +1 -1
- package/dist/shape.js +126 -1
- package/dist/shape.js.map +1 -1
- package/dist/tolerance.d.ts +38 -0
- package/dist/tolerance.d.ts.map +1 -0
- package/dist/tolerance.js +40 -0
- package/dist/tolerance.js.map +1 -0
- package/dist/topology/naming/lineage.d.ts +6 -0
- package/dist/topology/naming/lineage.d.ts.map +1 -1
- package/dist/topology/naming/lineage.js +8 -0
- package/dist/topology/naming/lineage.js.map +1 -1
- package/dist/topology/naming/resolve-edge.d.ts +20 -1
- package/dist/topology/naming/resolve-edge.d.ts.map +1 -1
- package/dist/topology/naming/resolve-edge.js +54 -20
- package/dist/topology/naming/resolve-edge.js.map +1 -1
- package/dist/topology/naming/score.js +3 -3
- package/dist/topology/naming/score.js.map +1 -1
- package/dist/units.d.ts +248 -0
- package/dist/units.d.ts.map +1 -0
- package/dist/units.js +397 -0
- package/dist/units.js.map +1 -0
- package/dist/weapp.d.ts +6 -2
- package/dist/weapp.d.ts.map +1 -1
- package/dist/weapp.js +7 -1
- package/dist/weapp.js.map +1 -1
- package/package.json +28 -3
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified `.fai.zip` read API (docs/fai-zip-format.md §4–§9).
|
|
3
|
+
*
|
|
4
|
+
* Environment-independent by constraint: reading goes through `io/zip.ts`
|
|
5
|
+
* (`readZipEntries`) plus JSON parsing — no `node:*`, no DOM, no fs, and no
|
|
6
|
+
* direct ZIP library. Web workers, the weapp worker and node share this code.
|
|
7
|
+
*
|
|
8
|
+
* Contract highlights (spec §9 Reader obligations):
|
|
9
|
+
* - a missing/malformed manifest, `format !== 3`, a malformed `models` array,
|
|
10
|
+
* a missing entry, a missing named data member and a duplicate asset key are
|
|
11
|
+
* all errors carrying the offending value;
|
|
12
|
+
* - entry selection is `active` or `models[0]` — never a file-name heuristic;
|
|
13
|
+
* - members outside the table (§3), unknown manifest fields and unknown
|
|
14
|
+
* data-member keys are ignored, never errors, never decision inputs.
|
|
15
|
+
*/
|
|
16
|
+
import { readZipEntries } from '../zip.js';
|
|
17
|
+
import { assetKeyOf, assertDataMemberPath, assertModelMemberPath, assertSafeMemberPath, decodeMemberText, } from './container.js';
|
|
18
|
+
/** Path prefix of every model script member. */
|
|
19
|
+
const MODEL_PREFIX = 'model/';
|
|
20
|
+
/** Path prefix of the imported-file payloads. */
|
|
21
|
+
const FILES_PREFIX = 'files/';
|
|
22
|
+
/** Path prefix of the script-consumed payloads. */
|
|
23
|
+
const ASSETS_PREFIX = 'assets/';
|
|
24
|
+
/**
|
|
25
|
+
* Unpack the archive; a non-ZIP input is a hard error carrying the original
|
|
26
|
+
* failure as `cause`. Caps violations keep `io/zip.ts`'s wording.
|
|
27
|
+
*/
|
|
28
|
+
function unzipMembers(bytes, caps) {
|
|
29
|
+
try {
|
|
30
|
+
return readZipEntries(bytes, caps);
|
|
31
|
+
}
|
|
32
|
+
catch (e) {
|
|
33
|
+
const inner = (e instanceof Error ? e.message : String(e)).replace(/^zip read failed: /, '');
|
|
34
|
+
const err = new Error(`[fai-zip] cannot open container as ZIP: ${inner}`);
|
|
35
|
+
err.cause = e;
|
|
36
|
+
throw err;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/** Normalize a manifest field error into one message naming the offending value. */
|
|
40
|
+
function manifestError(reason, value) {
|
|
41
|
+
const shown = value === undefined ? '' : ` (observed: ${JSON.stringify(value)})`;
|
|
42
|
+
return new Error(`[fai-zip] invalid manifest: ${reason}${shown}`);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Read and validate the container manifest from already-unpacked members.
|
|
46
|
+
* @param members the unpacked container members
|
|
47
|
+
* @returns the validated manifest
|
|
48
|
+
*/
|
|
49
|
+
function readManifestFrom(members) {
|
|
50
|
+
const raw = members.get('manifest.json');
|
|
51
|
+
if (raw === undefined)
|
|
52
|
+
throw new Error('[fai-zip] invalid manifest: missing manifest.json — not a .fai.zip container');
|
|
53
|
+
let parsed;
|
|
54
|
+
try {
|
|
55
|
+
parsed = JSON.parse(decodeMemberText(raw));
|
|
56
|
+
}
|
|
57
|
+
catch (e) {
|
|
58
|
+
throw new Error(`[fai-zip] invalid manifest: manifest.json is not valid JSON: ${e instanceof Error ? e.message : String(e)}`, { cause: e });
|
|
59
|
+
}
|
|
60
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
61
|
+
throw manifestError('manifest.json must be a JSON object');
|
|
62
|
+
}
|
|
63
|
+
const manifest = parsed;
|
|
64
|
+
if (manifest.format !== 3) {
|
|
65
|
+
throw manifestError('format must be the integer 3 (this revision); no other value is interpretable', manifest.format);
|
|
66
|
+
}
|
|
67
|
+
if (manifest.units !== 'mm') {
|
|
68
|
+
throw manifestError('units must be "mm"', manifest.units);
|
|
69
|
+
}
|
|
70
|
+
if (!Array.isArray(manifest.models) || manifest.models.length === 0) {
|
|
71
|
+
throw manifestError('models must be a non-empty array', manifest.models);
|
|
72
|
+
}
|
|
73
|
+
const models = manifest.models;
|
|
74
|
+
const seenIds = new Set();
|
|
75
|
+
const seenEntries = new Set();
|
|
76
|
+
const normalized = [];
|
|
77
|
+
for (let i = 0; i < models.length; i++) {
|
|
78
|
+
const m = models[i];
|
|
79
|
+
if (typeof m !== 'object' || m === null || Array.isArray(m)) {
|
|
80
|
+
throw manifestError(`models[${i}] must be an object`, m);
|
|
81
|
+
}
|
|
82
|
+
const rec = m;
|
|
83
|
+
const id = typeof rec.id === 'string' ? rec.id : '';
|
|
84
|
+
if (id === '')
|
|
85
|
+
throw manifestError(`models[${i}].id must be a non-empty string`, rec.id);
|
|
86
|
+
if (id.includes('/') || id.includes('\\'))
|
|
87
|
+
throw manifestError(`models[${i}].id must not contain "/" or "\\"`, id);
|
|
88
|
+
if (seenIds.has(id))
|
|
89
|
+
throw manifestError('models[].id must be unique', id);
|
|
90
|
+
seenIds.add(id);
|
|
91
|
+
const entry = typeof rec.entry === 'string' ? rec.entry : '';
|
|
92
|
+
if (entry === '')
|
|
93
|
+
throw manifestError(`models[${i}].entry must be a non-empty string`, rec.entry);
|
|
94
|
+
assertModelMemberPath(entry, `models[${i}].entry`);
|
|
95
|
+
if (seenEntries.has(entry))
|
|
96
|
+
throw manifestError('models[].entry must be unique', entry);
|
|
97
|
+
seenEntries.add(entry);
|
|
98
|
+
if (!members.has(entry)) {
|
|
99
|
+
throw manifestError(`models[${i}].entry does not exist in the container`, entry);
|
|
100
|
+
}
|
|
101
|
+
const data = typeof rec.data === 'string' ? rec.data : undefined;
|
|
102
|
+
if (data !== undefined) {
|
|
103
|
+
assertSafeMemberPath(data, `models[${i}].data`);
|
|
104
|
+
if (!members.has(data)) {
|
|
105
|
+
throw manifestError(`models[${i}].data names a member that does not exist`, data);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
normalized.push({
|
|
109
|
+
id,
|
|
110
|
+
entry,
|
|
111
|
+
...(typeof rec.label === 'string' ? { label: rec.label } : {}),
|
|
112
|
+
...(data !== undefined ? { data } : {}),
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
let active;
|
|
116
|
+
if (manifest.active !== undefined) {
|
|
117
|
+
if (typeof manifest.active !== 'string' || !seenIds.has(manifest.active)) {
|
|
118
|
+
throw manifestError('active must equal some models[].id', manifest.active);
|
|
119
|
+
}
|
|
120
|
+
active = manifest.active;
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
format: 3,
|
|
124
|
+
units: 'mm',
|
|
125
|
+
models: normalized,
|
|
126
|
+
...(active !== undefined ? { active } : {}),
|
|
127
|
+
...(typeof manifest.createdAt === 'string' ? { createdAt: manifest.createdAt } : {}),
|
|
128
|
+
...(typeof manifest.appVersion === 'string' ? { appVersion: manifest.appVersion } : {}),
|
|
129
|
+
...(typeof manifest.label === 'string' ? { label: manifest.label } : {}),
|
|
130
|
+
...(typeof manifest.source === 'object' && manifest.source !== null ? { source: manifest.source } : {}),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Collect the payload bytes of already-unpacked members.
|
|
135
|
+
* Keys are unique across `files/**` and `assets/**` together (spec §7.1);
|
|
136
|
+
* a duplicate key is an error rather than a pick.
|
|
137
|
+
* @param members the unpacked container members
|
|
138
|
+
* @returns the payload entries keyed by asset key
|
|
139
|
+
*/
|
|
140
|
+
function readAssetEntriesFrom(members) {
|
|
141
|
+
const files = {};
|
|
142
|
+
const assets = {};
|
|
143
|
+
const claimed = new Set();
|
|
144
|
+
for (const [path, body] of members) {
|
|
145
|
+
const prefix = path.startsWith(FILES_PREFIX)
|
|
146
|
+
? FILES_PREFIX
|
|
147
|
+
: path.startsWith(ASSETS_PREFIX)
|
|
148
|
+
? ASSETS_PREFIX
|
|
149
|
+
: undefined;
|
|
150
|
+
if (prefix === undefined)
|
|
151
|
+
continue;
|
|
152
|
+
const key = assetKeyOf(path, prefix);
|
|
153
|
+
if (claimed.has(key))
|
|
154
|
+
throw new Error(`[fai-zip] duplicate asset key across files//assets/: "${key}"`);
|
|
155
|
+
claimed.add(key);
|
|
156
|
+
if (prefix === FILES_PREFIX)
|
|
157
|
+
files[key] = body;
|
|
158
|
+
else
|
|
159
|
+
assets[key] = body;
|
|
160
|
+
}
|
|
161
|
+
return { files, assets };
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Read and validate the container manifest (spec §4).
|
|
165
|
+
* @param bytes the .fai.zip archive bytes
|
|
166
|
+
* @returns the validated manifest
|
|
167
|
+
* @throws when the archive is not a ZIP, the manifest is missing/malformed,
|
|
168
|
+
* `format !== 3`, `units !== "mm"`, `models` is empty/malformed, ids/entries
|
|
169
|
+
* are duplicated, an entry is not under `model/` or does not exist, `active`
|
|
170
|
+
* is not in `models[].id`, or a named `data` member does not exist.
|
|
171
|
+
*/
|
|
172
|
+
export function readManifest(bytes) {
|
|
173
|
+
return readManifestFrom(unzipMembers(bytes));
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* List the container models (validated). Same validation as readManifest.
|
|
177
|
+
* @param bytes the .fai.zip archive bytes
|
|
178
|
+
* @returns the validated model list
|
|
179
|
+
*/
|
|
180
|
+
export function listModels(bytes) {
|
|
181
|
+
return readManifest(bytes).models;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* List every module under `model/` (keys relative to `model/`, sorted).
|
|
185
|
+
* Members outside the table are ignored (§3), never an error.
|
|
186
|
+
* @param bytes the .fai.zip archive bytes
|
|
187
|
+
* @returns module keys relative to `model/`, sorted
|
|
188
|
+
*/
|
|
189
|
+
export function listModules(bytes) {
|
|
190
|
+
return listModulesFrom(unzipMembers(bytes));
|
|
191
|
+
}
|
|
192
|
+
/** Module keys (relative to `model/`, sorted) of already-unpacked members. */
|
|
193
|
+
function listModulesFrom(members) {
|
|
194
|
+
const keys = [];
|
|
195
|
+
for (const path of members.keys()) {
|
|
196
|
+
if (path.startsWith(MODEL_PREFIX) && path.endsWith('.fai.js') && path.length > MODEL_PREFIX.length) {
|
|
197
|
+
keys.push(path.slice(MODEL_PREFIX.length));
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return keys.sort();
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Read one module's source text by module key (relative to `model/`).
|
|
204
|
+
* @param bytes the .fai.zip archive bytes
|
|
205
|
+
* @param key the module key relative to `model/` (e.g. `main.fai.js`)
|
|
206
|
+
* @returns the module source text
|
|
207
|
+
* @throws for a key outside `model/` or that does not exist
|
|
208
|
+
*/
|
|
209
|
+
export function readModule(bytes, key) {
|
|
210
|
+
if (typeof key !== 'string' || key === '') {
|
|
211
|
+
throw new Error('[fai-zip] readModule: key must be a non-empty string');
|
|
212
|
+
}
|
|
213
|
+
assertSafeMemberPath(key, 'readModule key');
|
|
214
|
+
if (!key.endsWith('.fai.js')) {
|
|
215
|
+
throw new Error(`[fai-zip] readModule: key must name a module under model/ (ends with .fai.js): "${key}"`);
|
|
216
|
+
}
|
|
217
|
+
const member = unzipMembers(bytes).get(MODEL_PREFIX + key);
|
|
218
|
+
if (member === undefined) {
|
|
219
|
+
throw new Error(`[fai-zip] readModule: module "${key}" does not exist under model/`);
|
|
220
|
+
}
|
|
221
|
+
return decodeMemberText(member);
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Read one model data member's JSON text by member path (e.g. `data/main.json`).
|
|
225
|
+
*
|
|
226
|
+
* The path must be exactly what `models[].data` declares (spec §4): a safe,
|
|
227
|
+
* relative member under `data/` ending with `.json`. Content is validated to
|
|
228
|
+
* parse as JSON — the editor keeps data members pure JSON (§6.1), so a
|
|
229
|
+
* non-JSON member is a format violation and an error, never a silent skip.
|
|
230
|
+
*
|
|
231
|
+
* @param bytes the .fai.zip archive bytes
|
|
232
|
+
* @param memberPath the member path named by `models[].data`
|
|
233
|
+
* @returns the raw JSON text
|
|
234
|
+
* @throws for a non-string/unsafe path, a path outside `data/`, a missing
|
|
235
|
+
* member, or content that does not parse as JSON
|
|
236
|
+
*/
|
|
237
|
+
export function readDataMember(bytes, memberPath) {
|
|
238
|
+
if (typeof memberPath !== 'string' || memberPath === '') {
|
|
239
|
+
throw new Error('[fai-zip] readDataMember: memberPath must be a non-empty string');
|
|
240
|
+
}
|
|
241
|
+
assertDataMemberPath(memberPath, 'readDataMember path');
|
|
242
|
+
const member = unzipMembers(bytes).get(memberPath);
|
|
243
|
+
if (member === undefined) {
|
|
244
|
+
throw new Error(`[fai-zip] readDataMember: member "${memberPath}" does not exist in the container`);
|
|
245
|
+
}
|
|
246
|
+
const text = decodeMemberText(member);
|
|
247
|
+
try {
|
|
248
|
+
JSON.parse(text);
|
|
249
|
+
}
|
|
250
|
+
catch (e) {
|
|
251
|
+
throw new Error(`[fai-zip] readDataMember: "${memberPath}" is not valid JSON: ${e instanceof Error ? e.message : String(e)}`, { cause: e });
|
|
252
|
+
}
|
|
253
|
+
return text;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Collect the payload bytes of a container.
|
|
257
|
+
* Keys are unique across `files/**` and `assets/**` together (spec §7.1);
|
|
258
|
+
* a duplicate key is an error rather than a pick.
|
|
259
|
+
* @param bytes the .fai.zip archive bytes
|
|
260
|
+
* @returns the payload entries keyed by asset key
|
|
261
|
+
*/
|
|
262
|
+
export function readAssetEntries(bytes) {
|
|
263
|
+
return readAssetEntriesFrom(unzipMembers(bytes));
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Host entry point: open a container and return the manifest, the active
|
|
267
|
+
* model and a ProjectLoader over its module graph, plus the payloads.
|
|
268
|
+
*
|
|
269
|
+
* The archive is unpacked exactly once for the whole call.
|
|
270
|
+
*
|
|
271
|
+
* @param bytes the .fai.zip archive bytes
|
|
272
|
+
* @param opts optional active-model override and `io/zip.ts` read caps
|
|
273
|
+
* @returns manifest, active model, project loader and payload entries
|
|
274
|
+
* @throws when the container is invalid, the requested model id is unknown, or
|
|
275
|
+
* the read caps are exceeded
|
|
276
|
+
*/
|
|
277
|
+
export function openContainer(bytes, opts = {}) {
|
|
278
|
+
const members = unzipMembers(bytes, opts.caps);
|
|
279
|
+
const manifest = readManifestFrom(members);
|
|
280
|
+
const activeModel = (() => {
|
|
281
|
+
if (opts.modelId === undefined) {
|
|
282
|
+
const byActive = manifest.models.find((m) => m.id === manifest.active);
|
|
283
|
+
return byActive ?? manifest.models[0];
|
|
284
|
+
}
|
|
285
|
+
const byParam = manifest.models.find((m) => m.id === opts.modelId);
|
|
286
|
+
if (byParam === undefined) {
|
|
287
|
+
throw new Error(`[fai-zip] openContainer: model "${opts.modelId}" is not in models[]`);
|
|
288
|
+
}
|
|
289
|
+
return byParam;
|
|
290
|
+
})();
|
|
291
|
+
const keys = listModulesFrom(members);
|
|
292
|
+
const loader = {
|
|
293
|
+
listModules: () => [...keys],
|
|
294
|
+
readSource: async (moduleKey) => {
|
|
295
|
+
const member = members.get(MODEL_PREFIX + moduleKey);
|
|
296
|
+
if (member === undefined) {
|
|
297
|
+
throw new Error(`[fai-zip] loader: module "${moduleKey}" does not exist under model/`);
|
|
298
|
+
}
|
|
299
|
+
return decodeMemberText(member);
|
|
300
|
+
},
|
|
301
|
+
};
|
|
302
|
+
const { files, assets } = readAssetEntriesFrom(members);
|
|
303
|
+
return { manifest, activeModel, loader, files, assets };
|
|
304
|
+
}
|
|
305
|
+
//# sourceMappingURL=container-read.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"container-read.js","sourceRoot":"","sources":["../../../src/io/fai-zip/container-read.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Unified `.fai.zip` read API (docs/fai-zip-format.md §4–§9).\n *\n * Environment-independent by constraint: reading goes through `io/zip.ts`\n * (`readZipEntries`) plus JSON parsing — no `node:*`, no DOM, no fs, and no\n * direct ZIP library. Web workers, the weapp worker and node share this code.\n *\n * Contract highlights (spec §9 Reader obligations):\n * - a missing/malformed manifest, `format !== 3`, a malformed `models` array,\n * a missing entry, a missing named data member and a duplicate asset key are\n * all errors carrying the offending value;\n * - entry selection is `active` or `models[0]` — never a file-name heuristic;\n * - members outside the table (§3), unknown manifest fields and unknown\n * data-member keys are ignored, never errors, never decision inputs.\n */\nimport { readZipEntries, type ZipReadOptions } from '../zip'\nimport type { ProjectLoader } from '../../cad-runtime/ports'\nimport {\n assetKeyOf,\n assertDataMemberPath,\n assertModelMemberPath,\n assertSafeMemberPath,\n decodeMemberText,\n type ContainerAssetEntries,\n type ContainerManifest,\n type ContainerModel,\n} from './container'\n\n/** openContainer options. */\nexport interface OpenContainerOptions {\n /** override `manifest.active`; default = `manifest.active`, else `models[0]` */\n modelId?: string\n /** override the `io/zip.ts` read caps (maxEntries / maxTotalBytes) */\n caps?: ZipReadOptions\n}\n\n/** openContainer result: manifest, active model, loader and payloads. */\nexport interface OpenContainerResult {\n manifest: ContainerManifest\n activeModel: ContainerModel\n /** project loader over the container's module graph (listModules/readSource) */\n loader: ProjectLoader\n files: Record<string, Uint8Array>\n assets: Record<string, Uint8Array>\n}\n\n/** Unzipped members, keyed by container path. */\ntype MemberMap = Map<string, Uint8Array>\n\n/** Path prefix of every model script member. */\nconst MODEL_PREFIX = 'model/'\n/** Path prefix of the imported-file payloads. */\nconst FILES_PREFIX = 'files/'\n/** Path prefix of the script-consumed payloads. */\nconst ASSETS_PREFIX = 'assets/'\n\n/**\n * Unpack the archive; a non-ZIP input is a hard error carrying the original\n * failure as `cause`. Caps violations keep `io/zip.ts`'s wording.\n */\nfunction unzipMembers(bytes: Uint8Array, caps?: ZipReadOptions): MemberMap {\n try {\n return readZipEntries(bytes, caps)\n } catch (e) {\n const inner = (e instanceof Error ? e.message : String(e)).replace(/^zip read failed: /, '')\n const err = new Error(`[fai-zip] cannot open container as ZIP: ${inner}`)\n ;(err as Error & { cause?: unknown }).cause = e\n throw err\n }\n}\n\n/** Normalize a manifest field error into one message naming the offending value. */\nfunction manifestError(reason: string, value?: unknown): Error {\n const shown = value === undefined ? '' : ` (observed: ${JSON.stringify(value)})`\n return new Error(`[fai-zip] invalid manifest: ${reason}${shown}`)\n}\n\n/**\n * Read and validate the container manifest from already-unpacked members.\n * @param members the unpacked container members\n * @returns the validated manifest\n */\nfunction readManifestFrom(members: MemberMap): ContainerManifest {\n const raw = members.get('manifest.json')\n if (raw === undefined) throw new Error('[fai-zip] invalid manifest: missing manifest.json — not a .fai.zip container')\n let parsed: unknown\n try {\n parsed = JSON.parse(decodeMemberText(raw))\n } catch (e) {\n throw new Error(`[fai-zip] invalid manifest: manifest.json is not valid JSON: ${e instanceof Error ? e.message : String(e)}`, { cause: e })\n }\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw manifestError('manifest.json must be a JSON object')\n }\n const manifest = parsed as Record<string, unknown>\n\n if (manifest.format !== 3) {\n throw manifestError('format must be the integer 3 (this revision); no other value is interpretable', manifest.format)\n }\n if (manifest.units !== 'mm') {\n throw manifestError('units must be \"mm\"', manifest.units)\n }\n if (!Array.isArray(manifest.models) || manifest.models.length === 0) {\n throw manifestError('models must be a non-empty array', manifest.models)\n }\n\n const models = manifest.models as unknown[]\n const seenIds = new Set<string>()\n const seenEntries = new Set<string>()\n const normalized: ContainerModel[] = []\n for (let i = 0; i < models.length; i++) {\n const m = models[i]\n if (typeof m !== 'object' || m === null || Array.isArray(m)) {\n throw manifestError(`models[${i}] must be an object`, m)\n }\n const rec = m as Record<string, unknown>\n const id = typeof rec.id === 'string' ? rec.id : ''\n if (id === '') throw manifestError(`models[${i}].id must be a non-empty string`, rec.id)\n if (id.includes('/') || id.includes('\\\\')) throw manifestError(`models[${i}].id must not contain \"/\" or \"\\\\\"`, id)\n if (seenIds.has(id)) throw manifestError('models[].id must be unique', id)\n seenIds.add(id)\n\n const entry = typeof rec.entry === 'string' ? rec.entry : ''\n if (entry === '') throw manifestError(`models[${i}].entry must be a non-empty string`, rec.entry)\n assertModelMemberPath(entry, `models[${i}].entry`)\n if (seenEntries.has(entry)) throw manifestError('models[].entry must be unique', entry)\n seenEntries.add(entry)\n if (!members.has(entry)) {\n throw manifestError(`models[${i}].entry does not exist in the container`, entry)\n }\n\n const data = typeof rec.data === 'string' ? rec.data : undefined\n if (data !== undefined) {\n assertSafeMemberPath(data, `models[${i}].data`)\n if (!members.has(data)) {\n throw manifestError(`models[${i}].data names a member that does not exist`, data)\n }\n }\n\n normalized.push({\n id,\n entry,\n ...(typeof rec.label === 'string' ? { label: rec.label } : {}),\n ...(data !== undefined ? { data } : {}),\n })\n }\n\n let active: string | undefined\n if (manifest.active !== undefined) {\n if (typeof manifest.active !== 'string' || !seenIds.has(manifest.active)) {\n throw manifestError('active must equal some models[].id', manifest.active)\n }\n active = manifest.active\n }\n\n return {\n format: 3,\n units: 'mm',\n models: normalized,\n ...(active !== undefined ? { active } : {}),\n ...(typeof manifest.createdAt === 'string' ? { createdAt: manifest.createdAt } : {}),\n ...(typeof manifest.appVersion === 'string' ? { appVersion: manifest.appVersion } : {}),\n ...(typeof manifest.label === 'string' ? { label: manifest.label } : {}),\n ...(typeof manifest.source === 'object' && manifest.source !== null ? { source: manifest.source as ContainerManifest['source'] } : {}),\n }\n}\n\n/**\n * Collect the payload bytes of already-unpacked members.\n * Keys are unique across `files/**` and `assets/**` together (spec §7.1);\n * a duplicate key is an error rather than a pick.\n * @param members the unpacked container members\n * @returns the payload entries keyed by asset key\n */\nfunction readAssetEntriesFrom(members: MemberMap): ContainerAssetEntries {\n const files: Record<string, Uint8Array> = {}\n const assets: Record<string, Uint8Array> = {}\n const claimed = new Set<string>()\n for (const [path, body] of members) {\n const prefix = path.startsWith(FILES_PREFIX)\n ? FILES_PREFIX\n : path.startsWith(ASSETS_PREFIX)\n ? ASSETS_PREFIX\n : undefined\n if (prefix === undefined) continue\n const key = assetKeyOf(path, prefix)\n if (claimed.has(key)) throw new Error(`[fai-zip] duplicate asset key across files//assets/: \"${key}\"`)\n claimed.add(key)\n if (prefix === FILES_PREFIX) files[key] = body\n else assets[key] = body\n }\n return { files, assets }\n}\n\n/**\n * Read and validate the container manifest (spec §4).\n * @param bytes the .fai.zip archive bytes\n * @returns the validated manifest\n * @throws when the archive is not a ZIP, the manifest is missing/malformed,\n * `format !== 3`, `units !== \"mm\"`, `models` is empty/malformed, ids/entries\n * are duplicated, an entry is not under `model/` or does not exist, `active`\n * is not in `models[].id`, or a named `data` member does not exist.\n */\nexport function readManifest(bytes: Uint8Array): ContainerManifest {\n return readManifestFrom(unzipMembers(bytes))\n}\n\n/**\n * List the container models (validated). Same validation as readManifest.\n * @param bytes the .fai.zip archive bytes\n * @returns the validated model list\n */\nexport function listModels(bytes: Uint8Array): ContainerModel[] {\n return readManifest(bytes).models\n}\n\n/**\n * List every module under `model/` (keys relative to `model/`, sorted).\n * Members outside the table are ignored (§3), never an error.\n * @param bytes the .fai.zip archive bytes\n * @returns module keys relative to `model/`, sorted\n */\nexport function listModules(bytes: Uint8Array): string[] {\n return listModulesFrom(unzipMembers(bytes))\n}\n\n/** Module keys (relative to `model/`, sorted) of already-unpacked members. */\nfunction listModulesFrom(members: MemberMap): string[] {\n const keys: string[] = []\n for (const path of members.keys()) {\n if (path.startsWith(MODEL_PREFIX) && path.endsWith('.fai.js') && path.length > MODEL_PREFIX.length) {\n keys.push(path.slice(MODEL_PREFIX.length))\n }\n }\n return keys.sort()\n}\n\n/**\n * Read one module's source text by module key (relative to `model/`).\n * @param bytes the .fai.zip archive bytes\n * @param key the module key relative to `model/` (e.g. `main.fai.js`)\n * @returns the module source text\n * @throws for a key outside `model/` or that does not exist\n */\nexport function readModule(bytes: Uint8Array, key: string): string {\n if (typeof key !== 'string' || key === '') {\n throw new Error('[fai-zip] readModule: key must be a non-empty string')\n }\n assertSafeMemberPath(key, 'readModule key')\n if (!key.endsWith('.fai.js')) {\n throw new Error(`[fai-zip] readModule: key must name a module under model/ (ends with .fai.js): \"${key}\"`)\n }\n const member = unzipMembers(bytes).get(MODEL_PREFIX + key)\n if (member === undefined) {\n throw new Error(`[fai-zip] readModule: module \"${key}\" does not exist under model/`)\n }\n return decodeMemberText(member)\n}\n\n/**\n * Read one model data member's JSON text by member path (e.g. `data/main.json`).\n *\n * The path must be exactly what `models[].data` declares (spec §4): a safe,\n * relative member under `data/` ending with `.json`. Content is validated to\n * parse as JSON — the editor keeps data members pure JSON (§6.1), so a\n * non-JSON member is a format violation and an error, never a silent skip.\n *\n * @param bytes the .fai.zip archive bytes\n * @param memberPath the member path named by `models[].data`\n * @returns the raw JSON text\n * @throws for a non-string/unsafe path, a path outside `data/`, a missing\n * member, or content that does not parse as JSON\n */\nexport function readDataMember(bytes: Uint8Array, memberPath: string): string {\n if (typeof memberPath !== 'string' || memberPath === '') {\n throw new Error('[fai-zip] readDataMember: memberPath must be a non-empty string')\n }\n assertDataMemberPath(memberPath, 'readDataMember path')\n const member = unzipMembers(bytes).get(memberPath)\n if (member === undefined) {\n throw new Error(`[fai-zip] readDataMember: member \"${memberPath}\" does not exist in the container`)\n }\n const text = decodeMemberText(member)\n try {\n JSON.parse(text)\n } catch (e) {\n throw new Error(`[fai-zip] readDataMember: \"${memberPath}\" is not valid JSON: ${e instanceof Error ? e.message : String(e)}`, { cause: e })\n }\n return text\n}\n\n/**\n * Collect the payload bytes of a container.\n * Keys are unique across `files/**` and `assets/**` together (spec §7.1);\n * a duplicate key is an error rather than a pick.\n * @param bytes the .fai.zip archive bytes\n * @returns the payload entries keyed by asset key\n */\nexport function readAssetEntries(bytes: Uint8Array): ContainerAssetEntries {\n return readAssetEntriesFrom(unzipMembers(bytes))\n}\n\n/**\n * Host entry point: open a container and return the manifest, the active\n * model and a ProjectLoader over its module graph, plus the payloads.\n *\n * The archive is unpacked exactly once for the whole call.\n *\n * @param bytes the .fai.zip archive bytes\n * @param opts optional active-model override and `io/zip.ts` read caps\n * @returns manifest, active model, project loader and payload entries\n * @throws when the container is invalid, the requested model id is unknown, or\n * the read caps are exceeded\n */\nexport function openContainer(bytes: Uint8Array, opts: OpenContainerOptions = {}): OpenContainerResult {\n const members = unzipMembers(bytes, opts.caps)\n const manifest = readManifestFrom(members)\n\n const activeModel: ContainerModel = (() => {\n if (opts.modelId === undefined) {\n const byActive = manifest.models.find((m) => m.id === manifest.active)\n return byActive ?? manifest.models[0]\n }\n const byParam = manifest.models.find((m) => m.id === opts.modelId)\n if (byParam === undefined) {\n throw new Error(`[fai-zip] openContainer: model \"${opts.modelId}\" is not in models[]`)\n }\n return byParam\n })()\n\n const keys = listModulesFrom(members)\n\n const loader: ProjectLoader = {\n listModules: () => [...keys],\n readSource: async (moduleKey) => {\n const member = members.get(MODEL_PREFIX + moduleKey)\n if (member === undefined) {\n throw new Error(`[fai-zip] loader: module \"${moduleKey}\" does not exist under model/`)\n }\n return decodeMemberText(member)\n },\n }\n\n const { files, assets } = readAssetEntriesFrom(members)\n return { manifest, activeModel, loader, files, assets }\n}\n"]}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { type ContainerManifest, type ContainerModel } from './container.js';
|
|
2
|
+
/** Assembly input of a container: no producer concept, only format concepts. */
|
|
3
|
+
export interface ContainerAssembly {
|
|
4
|
+
/** `manifest.models` — the complete model list (spec §4) */
|
|
5
|
+
models: ContainerModel[];
|
|
6
|
+
/** `manifest.active`; must equal some `models[].id` when present */
|
|
7
|
+
active?: string;
|
|
8
|
+
/** display-only manifest fields plus §8.2 conversion provenance */
|
|
9
|
+
meta?: {
|
|
10
|
+
createdAt?: string;
|
|
11
|
+
appVersion?: string;
|
|
12
|
+
label?: string;
|
|
13
|
+
source?: ContainerManifest['source'];
|
|
14
|
+
};
|
|
15
|
+
/** `model/**` members: full container path → source text. Every `models[].entry` must be present */
|
|
16
|
+
modules: Record<string, string>;
|
|
17
|
+
/** `data/**` members: full container path → JSON text (spec §6) */
|
|
18
|
+
dataMembers: Record<string, string>;
|
|
19
|
+
/** `files/**` payloads: fileId → bytes. Written as `files/<fileId>.bin` */
|
|
20
|
+
files: Record<string, Uint8Array>;
|
|
21
|
+
/** `assets/**` payloads: full container path → bytes (e.g. `assets/bracket.brp`) */
|
|
22
|
+
assets: Record<string, Uint8Array>;
|
|
23
|
+
/** producer-defined members (spec §3, e.g. `mapping.json`, `freecad/**`), written as-is */
|
|
24
|
+
producerMembers?: Record<string, Uint8Array>;
|
|
25
|
+
}
|
|
26
|
+
/** Result of `writeContainer`. */
|
|
27
|
+
export interface WriteContainerResult {
|
|
28
|
+
/** the assembled container bytes */
|
|
29
|
+
bytes: Uint8Array;
|
|
30
|
+
/** the manifest that was serialized into the container */
|
|
31
|
+
manifest: ContainerManifest;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Build and validate the container manifest — the single implementation of the
|
|
35
|
+
* §4 schema.
|
|
36
|
+
*
|
|
37
|
+
* Structural only: it cannot check that `models[].entry` and `models[].data`
|
|
38
|
+
* resolve to emitted members, because it never sees the members. That is
|
|
39
|
+
* `writeContainer`'s job, and it is why conversion pipelines that legitimately
|
|
40
|
+
* assemble a container in two passes can still reuse this function.
|
|
41
|
+
*
|
|
42
|
+
* @param input the model list, the optional active id and display metadata
|
|
43
|
+
* @returns the manifest, with `format: 3` and `units: "mm"` fixed
|
|
44
|
+
* @throws when `models` is empty, an id/entry is empty, unsafe or duplicated,
|
|
45
|
+
* an `entry` is not a module path under `model/` ending with `.fai.js`, a
|
|
46
|
+
* `data` is not a JSON path under `data/`, or `active` names no model
|
|
47
|
+
*/
|
|
48
|
+
export declare function createManifest(input: Pick<ContainerAssembly, 'models' | 'active' | 'meta'>): ContainerManifest;
|
|
49
|
+
/**
|
|
50
|
+
* Assemble a container from an assembly, enforcing spec §10 before writing any
|
|
51
|
+
* bytes.
|
|
52
|
+
*
|
|
53
|
+
* Enforced (each throws, never silently produces an unreconstructable
|
|
54
|
+
* container):
|
|
55
|
+
* 1. `models` is non-empty, ids and entries are unique (`createManifest`);
|
|
56
|
+
* 2. every `models[].entry` is emitted in `modules`;
|
|
57
|
+
* 3. every `models[].data` is emitted in `dataMembers`;
|
|
58
|
+
* 4. `active`, when present, equals some `models[].id` (`createManifest`);
|
|
59
|
+
* 5. asset keys are unique across `files/**`, `assets/**` and any
|
|
60
|
+
* producer-defined member in those namespaces (spec §7.1) — a fileId that
|
|
61
|
+
* would not survive the base-name key rule is an error, not a silent rename;
|
|
62
|
+
* 6. every `entry`/`data`/module/asset path is a safe relative member path in
|
|
63
|
+
* its namespace, and no path is emitted twice.
|
|
64
|
+
*
|
|
65
|
+
* @param assembly the assembly input (§3.4)
|
|
66
|
+
* @returns the container bytes and the manifest that was written
|
|
67
|
+
* @throws when any of the obligations above is violated
|
|
68
|
+
*/
|
|
69
|
+
export declare function writeContainer(assembly: ContainerAssembly): WriteContainerResult;
|
|
70
|
+
//# sourceMappingURL=container-write.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"container-write.d.ts","sourceRoot":"","sources":["../../../src/io/fai-zip/container-write.ts"],"names":[],"mappings":"AAgBA,OAAO,EAML,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACpB,MAAM,aAAa,CAAA;AAEpB,gFAAgF;AAChF,MAAM,WAAW,iBAAiB;IAChC,4DAA4D;IAC5D,MAAM,EAAE,cAAc,EAAE,CAAA;IACxB,oEAAoE;IACpE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,mEAAmE;IACnE,IAAI,CAAC,EAAE;QACL,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,UAAU,CAAC,EAAE,MAAM,CAAA;QACnB,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,MAAM,CAAC,EAAE,iBAAiB,CAAC,QAAQ,CAAC,CAAA;KACrC,CAAA;IACD,oGAAoG;IACpG,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/B,mEAAmE;IACnE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACnC,2EAA2E;IAC3E,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;IACjC,oFAAoF;IACpF,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;IAClC,2FAA2F;IAC3F,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;CAC7C;AAED,kCAAkC;AAClC,MAAM,WAAW,oBAAoB;IACnC,oCAAoC;IACpC,KAAK,EAAE,UAAU,CAAA;IACjB,0DAA0D;IAC1D,QAAQ,EAAE,iBAAiB,CAAA;CAC5B;AAKD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,IAAI,CAAC,iBAAiB,EAAE,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAC,GAAG,iBAAiB,CAoD9G;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,iBAAiB,GAAG,oBAAoB,CAuFhF"}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified `.fai.zip` write API (docs/fai-zip-format.md §10, Writer obligations).
|
|
3
|
+
*
|
|
4
|
+
* The input is an *assembly* — pure format concepts plus opaque
|
|
5
|
+
* producer-defined members (§3). Nothing here knows about FCStd, editors or
|
|
6
|
+
* filesystems.
|
|
7
|
+
*
|
|
8
|
+
* Environment-independent: ZIP bytes go through `io/zip.ts`, text goes through
|
|
9
|
+
* the UTF-8 codec in `./container.js`. No `node:*`, no fflate, no fs.
|
|
10
|
+
*
|
|
11
|
+
* The point of this layer is that a container which a reader cannot
|
|
12
|
+
* reconstruct is never produced: every §10 obligation that can be checked
|
|
13
|
+
* statically is checked here and throws. A thrown error is always preferable
|
|
14
|
+
* to a container with a dangling reference (spec §10.7, fail loudly).
|
|
15
|
+
*/
|
|
16
|
+
import { writeZipEntries } from '../zip.js';
|
|
17
|
+
import { assetKeyOf, assertDataMemberPath, assertModelMemberPath, assertSafeMemberPath, encodeMemberText, } from './container.js';
|
|
18
|
+
const FILES_PREFIX = 'files/';
|
|
19
|
+
const ASSETS_PREFIX = 'assets/';
|
|
20
|
+
/**
|
|
21
|
+
* Build and validate the container manifest — the single implementation of the
|
|
22
|
+
* §4 schema.
|
|
23
|
+
*
|
|
24
|
+
* Structural only: it cannot check that `models[].entry` and `models[].data`
|
|
25
|
+
* resolve to emitted members, because it never sees the members. That is
|
|
26
|
+
* `writeContainer`'s job, and it is why conversion pipelines that legitimately
|
|
27
|
+
* assemble a container in two passes can still reuse this function.
|
|
28
|
+
*
|
|
29
|
+
* @param input the model list, the optional active id and display metadata
|
|
30
|
+
* @returns the manifest, with `format: 3` and `units: "mm"` fixed
|
|
31
|
+
* @throws when `models` is empty, an id/entry is empty, unsafe or duplicated,
|
|
32
|
+
* an `entry` is not a module path under `model/` ending with `.fai.js`, a
|
|
33
|
+
* `data` is not a JSON path under `data/`, or `active` names no model
|
|
34
|
+
*/
|
|
35
|
+
export function createManifest(input) {
|
|
36
|
+
const { models, active, meta = {} } = input;
|
|
37
|
+
if (!Array.isArray(models) || models.length === 0) {
|
|
38
|
+
throw new Error('[fai-zip] createManifest: models must be a non-empty array');
|
|
39
|
+
}
|
|
40
|
+
const seenIds = new Set();
|
|
41
|
+
const seenEntries = new Set();
|
|
42
|
+
const normalized = [];
|
|
43
|
+
for (let i = 0; i < models.length; i++) {
|
|
44
|
+
const m = models[i];
|
|
45
|
+
const id = typeof m.id === 'string' ? m.id : '';
|
|
46
|
+
if (id === '')
|
|
47
|
+
throw new Error(`[fai-zip] createManifest: models[${i}].id must be a non-empty string`);
|
|
48
|
+
if (id.includes('/') || id.includes('\\')) {
|
|
49
|
+
throw new Error(`[fai-zip] createManifest: models[${i}].id must not contain "/" or "\\": "${id}"`);
|
|
50
|
+
}
|
|
51
|
+
if (seenIds.has(id))
|
|
52
|
+
throw new Error(`[fai-zip] createManifest: models[].id must be unique: "${id}"`);
|
|
53
|
+
seenIds.add(id);
|
|
54
|
+
if (typeof m.entry !== 'string' || m.entry === '') {
|
|
55
|
+
throw new Error(`[fai-zip] createManifest: models[${i}].entry must be a non-empty string`);
|
|
56
|
+
}
|
|
57
|
+
assertModelMemberPath(m.entry, `createManifest: models[${i}].entry`);
|
|
58
|
+
if (seenEntries.has(m.entry)) {
|
|
59
|
+
throw new Error(`[fai-zip] createManifest: models[].entry must be unique: "${m.entry}"`);
|
|
60
|
+
}
|
|
61
|
+
seenEntries.add(m.entry);
|
|
62
|
+
if (m.data !== undefined)
|
|
63
|
+
assertDataMemberPath(m.data, `createManifest: models[${i}].data`);
|
|
64
|
+
normalized.push({
|
|
65
|
+
id,
|
|
66
|
+
entry: m.entry,
|
|
67
|
+
...(m.label !== undefined ? { label: m.label } : {}),
|
|
68
|
+
...(m.data !== undefined ? { data: m.data } : {}),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
if (active !== undefined && !seenIds.has(active)) {
|
|
72
|
+
throw new Error(`[fai-zip] createManifest: active must equal some models[].id: "${active}"`);
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
format: 3,
|
|
76
|
+
units: 'mm',
|
|
77
|
+
models: normalized,
|
|
78
|
+
...(active !== undefined ? { active } : {}),
|
|
79
|
+
...(meta.createdAt !== undefined ? { createdAt: meta.createdAt } : {}),
|
|
80
|
+
...(meta.appVersion !== undefined ? { appVersion: meta.appVersion } : {}),
|
|
81
|
+
...(meta.label !== undefined ? { label: meta.label } : {}),
|
|
82
|
+
...(meta.source !== undefined ? { source: meta.source } : {}),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Assemble a container from an assembly, enforcing spec §10 before writing any
|
|
87
|
+
* bytes.
|
|
88
|
+
*
|
|
89
|
+
* Enforced (each throws, never silently produces an unreconstructable
|
|
90
|
+
* container):
|
|
91
|
+
* 1. `models` is non-empty, ids and entries are unique (`createManifest`);
|
|
92
|
+
* 2. every `models[].entry` is emitted in `modules`;
|
|
93
|
+
* 3. every `models[].data` is emitted in `dataMembers`;
|
|
94
|
+
* 4. `active`, when present, equals some `models[].id` (`createManifest`);
|
|
95
|
+
* 5. asset keys are unique across `files/**`, `assets/**` and any
|
|
96
|
+
* producer-defined member in those namespaces (spec §7.1) — a fileId that
|
|
97
|
+
* would not survive the base-name key rule is an error, not a silent rename;
|
|
98
|
+
* 6. every `entry`/`data`/module/asset path is a safe relative member path in
|
|
99
|
+
* its namespace, and no path is emitted twice.
|
|
100
|
+
*
|
|
101
|
+
* @param assembly the assembly input (§3.4)
|
|
102
|
+
* @returns the container bytes and the manifest that was written
|
|
103
|
+
* @throws when any of the obligations above is violated
|
|
104
|
+
*/
|
|
105
|
+
export function writeContainer(assembly) {
|
|
106
|
+
const { modules, dataMembers, files, assets, producerMembers = {} } = assembly;
|
|
107
|
+
const manifest = createManifest(assembly);
|
|
108
|
+
const members = new Map();
|
|
109
|
+
const claimedKeys = new Map();
|
|
110
|
+
/** Record an asset key; a second member claiming it is an error (spec §7.1). */
|
|
111
|
+
const claim = (key, path) => {
|
|
112
|
+
const prev = claimedKeys.get(key);
|
|
113
|
+
if (prev !== undefined) {
|
|
114
|
+
throw new Error(`[fai-zip] writeContainer: duplicate asset key "${key}" (members "${prev}" and "${path}")`);
|
|
115
|
+
}
|
|
116
|
+
claimedKeys.set(key, path);
|
|
117
|
+
};
|
|
118
|
+
/** Add one member; a duplicate member path is an error. */
|
|
119
|
+
const put = (path, body) => {
|
|
120
|
+
if (members.has(path)) {
|
|
121
|
+
throw new Error(`[fai-zip] writeContainer: duplicate member path "${path}"`);
|
|
122
|
+
}
|
|
123
|
+
members.set(path, body);
|
|
124
|
+
};
|
|
125
|
+
// manifest.json — always first, always present (spec §10.1)
|
|
126
|
+
put('manifest.json', encodeMemberText(JSON.stringify(manifest, null, 2)));
|
|
127
|
+
// model/** — every declared entry must be delivered (spec §10.2, §10.3)
|
|
128
|
+
for (const [path, source] of Object.entries(modules)) {
|
|
129
|
+
assertModelMemberPath(path, 'writeContainer: modules key');
|
|
130
|
+
put(path, encodeMemberText(source));
|
|
131
|
+
}
|
|
132
|
+
for (const m of manifest.models) {
|
|
133
|
+
if (!members.has(m.entry)) {
|
|
134
|
+
throw new Error(`[fai-zip] writeContainer: models[].entry "${m.entry}" has no module in the assembly`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// data/** — a declared data member must be delivered, and must be JSON (spec §6.1)
|
|
138
|
+
for (const [path, json] of Object.entries(dataMembers)) {
|
|
139
|
+
assertDataMemberPath(path, 'writeContainer: dataMembers key');
|
|
140
|
+
try {
|
|
141
|
+
JSON.parse(json);
|
|
142
|
+
}
|
|
143
|
+
catch (e) {
|
|
144
|
+
throw new Error(`[fai-zip] writeContainer: data member "${path}" is not valid JSON: ${e instanceof Error ? e.message : String(e)}`, { cause: e });
|
|
145
|
+
}
|
|
146
|
+
put(path, encodeMemberText(json));
|
|
147
|
+
}
|
|
148
|
+
for (const m of manifest.models) {
|
|
149
|
+
if (m.data !== undefined && !members.has(m.data)) {
|
|
150
|
+
throw new Error(`[fai-zip] writeContainer: models[].data "${m.data}" has no member in the assembly`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// files/** — keyed by fileId, stored as files/<fileId>.bin so the key
|
|
154
|
+
// round-trips through the base-name rule (spec §7). A fileId containing a
|
|
155
|
+
// separator would be silently re-keyed on read, so it is rejected here.
|
|
156
|
+
for (const [fileId, body] of Object.entries(files)) {
|
|
157
|
+
if (fileId === '' || fileId.includes('/') || fileId.includes('\\')) {
|
|
158
|
+
throw new Error(`[fai-zip] writeContainer: fileId must be a non-empty base name without separators: "${fileId}"`);
|
|
159
|
+
}
|
|
160
|
+
const path = `${FILES_PREFIX}${fileId}.bin`;
|
|
161
|
+
put(path, body);
|
|
162
|
+
claim(assetKeyOf(path, FILES_PREFIX), path);
|
|
163
|
+
}
|
|
164
|
+
// assets/** — the key is the base name, so two paths may collide (spec §7.1)
|
|
165
|
+
for (const [path, body] of Object.entries(assets)) {
|
|
166
|
+
assertSafeMemberPath(path, 'writeContainer: assets key');
|
|
167
|
+
if (!path.startsWith(ASSETS_PREFIX)) {
|
|
168
|
+
throw new Error(`[fai-zip] writeContainer: assets key must be under assets/: "${path}"`);
|
|
169
|
+
}
|
|
170
|
+
put(path, body);
|
|
171
|
+
claim(assetKeyOf(path, ASSETS_PREFIX), path);
|
|
172
|
+
}
|
|
173
|
+
// producer-defined members (spec §3) — opaque payload, but they still live
|
|
174
|
+
// in the archive, so a member dropped under files//assets/ takes part in the
|
|
175
|
+
// key uniqueness rule whether or not the producer thought of it that way.
|
|
176
|
+
for (const [path, body] of Object.entries(producerMembers)) {
|
|
177
|
+
assertSafeMemberPath(path, 'writeContainer: producerMembers key');
|
|
178
|
+
put(path, body);
|
|
179
|
+
if (path.startsWith(FILES_PREFIX))
|
|
180
|
+
claim(assetKeyOf(path, FILES_PREFIX), path);
|
|
181
|
+
else if (path.startsWith(ASSETS_PREFIX))
|
|
182
|
+
claim(assetKeyOf(path, ASSETS_PREFIX), path);
|
|
183
|
+
}
|
|
184
|
+
return { bytes: writeZipEntries(Object.fromEntries(members)), manifest };
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=container-write.js.map
|