@hraness/slopcamera 3.2.4
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 +21 -0
- package/NOTICE.md +60 -0
- package/PRIVACY.md +46 -0
- package/README.md +311 -0
- package/SECURITY.md +21 -0
- package/apps/desktop/README.md +966 -0
- package/apps/desktop/analysis/protocol.ts +223 -0
- package/apps/desktop/application/capability-binding.ts +594 -0
- package/apps/desktop/application/context.ts +97 -0
- package/apps/desktop/application/creative-iteration.ts +1385 -0
- package/apps/desktop/application/default-registry.ts +146 -0
- package/apps/desktop/application/directing-contract.ts +218 -0
- package/apps/desktop/application/directing-plan.ts +178 -0
- package/apps/desktop/application/errors.ts +18 -0
- package/apps/desktop/application/gateway-port.ts +867 -0
- package/apps/desktop/application/html-overlay-browser-runtime.ts +687 -0
- package/apps/desktop/application/html-overlay-integrity.ts +337 -0
- package/apps/desktop/application/html-overlay-renderer.ts +45 -0
- package/apps/desktop/application/index.ts +13 -0
- package/apps/desktop/application/operation-completion-checkpoint.ts +333 -0
- package/apps/desktop/application/operation-effects.ts +94 -0
- package/apps/desktop/application/operation.ts +224 -0
- package/apps/desktop/application/operations/analysis/faces.ts +244 -0
- package/apps/desktop/application/operations/analysis/music.ts +248 -0
- package/apps/desktop/application/operations/analysis/project-auto-zooms.ts +302 -0
- package/apps/desktop/application/operations/analysis/project-inactivity.ts +381 -0
- package/apps/desktop/application/operations/analysis/scenes.ts +272 -0
- package/apps/desktop/application/operations/analysis/shared.ts +202 -0
- package/apps/desktop/application/operations/derive/edit-batch.ts +967 -0
- package/apps/desktop/application/operations/derive/follow-faces.ts +454 -0
- package/apps/desktop/application/operations/edit/bind-candidate-revision.ts +91 -0
- package/apps/desktop/application/operations/edit/create-candidate-revision.ts +377 -0
- package/apps/desktop/application/operations/edit/create-revision.ts +255 -0
- package/apps/desktop/application/operations/edit/freeze-revision.ts +182 -0
- package/apps/desktop/application/operations/gateway/image.ts +41 -0
- package/apps/desktop/application/operations/gateway/index.ts +17 -0
- package/apps/desktop/application/operations/gateway/shared.ts +122 -0
- package/apps/desktop/application/operations/gateway/speech.ts +41 -0
- package/apps/desktop/application/operations/gateway/transcription.ts +42 -0
- package/apps/desktop/application/operations/gateway/video.ts +41 -0
- package/apps/desktop/application/operations/index.ts +31 -0
- package/apps/desktop/application/operations/iteration/create-candidate.ts +240 -0
- package/apps/desktop/application/operations/iteration/create-matrix.ts +117 -0
- package/apps/desktop/application/operations/iteration/select.ts +155 -0
- package/apps/desktop/application/operations/iteration/shared.ts +250 -0
- package/apps/desktop/application/operations/media/audio-effects.ts +376 -0
- package/apps/desktop/application/operations/media/binding.ts +79 -0
- package/apps/desktop/application/operations/media/capabilities.ts +112 -0
- package/apps/desktop/application/operations/media/color-grade.ts +343 -0
- package/apps/desktop/application/operations/media/html-overlay.ts +802 -0
- package/apps/desktop/application/operations/media/index.ts +8 -0
- package/apps/desktop/application/operations/media/ingest.ts +303 -0
- package/apps/desktop/application/operations/media/overlay.ts +729 -0
- package/apps/desktop/application/operations/media/shared.ts +845 -0
- package/apps/desktop/application/operations/media/transform-platform.ts +120 -0
- package/apps/desktop/application/operations/project/commit-edits.ts +2054 -0
- package/apps/desktop/application/operations/project/promote-selection.ts +495 -0
- package/apps/desktop/application/operations/project/snapshot.ts +95 -0
- package/apps/desktop/application/operations/recording/index.ts +5 -0
- package/apps/desktop/application/operations/recording/pause.ts +34 -0
- package/apps/desktop/application/operations/recording/resume.ts +34 -0
- package/apps/desktop/application/operations/recording/shared.ts +199 -0
- package/apps/desktop/application/operations/recording/start.ts +169 -0
- package/apps/desktop/application/operations/recording/stop.ts +34 -0
- package/apps/desktop/application/operations/render/bind-candidate-output.ts +324 -0
- package/apps/desktop/application/operations/render/materialize-selection.ts +322 -0
- package/apps/desktop/application/operations/render/project-caption-plan.ts +287 -0
- package/apps/desktop/application/operations/render/project-plan.ts +939 -0
- package/apps/desktop/application/operations/render/project-spatial-receipt.ts +70 -0
- package/apps/desktop/application/operations/render/project.ts +1922 -0
- package/apps/desktop/application/operations/shared.ts +67 -0
- package/apps/desktop/application/operations/slopcamera-portable.ts +1188 -0
- package/apps/desktop/application/operations/slopcamera-visuals.ts +784 -0
- package/apps/desktop/application/operations/spatial-project.ts +209 -0
- package/apps/desktop/application/operations/spatial-render.ts +415 -0
- package/apps/desktop/application/operations/spatial-scene.ts +97 -0
- package/apps/desktop/application/operations/studio.ts +143 -0
- package/apps/desktop/application/output-publication-lease.ts +163 -0
- package/apps/desktop/application/project-publication-lease.ts +233 -0
- package/apps/desktop/application/project-store.ts +196 -0
- package/apps/desktop/application/receipts.ts +533 -0
- package/apps/desktop/application/registry.ts +182 -0
- package/apps/desktop/application/spatial-assets.ts +675 -0
- package/apps/desktop/application/spatial-compositor-cadence.ts +23 -0
- package/apps/desktop/application/spatial-project-authority.ts +35 -0
- package/apps/desktop/application/spatial-project-lease.ts +126 -0
- package/apps/desktop/application/spatial-project-render-preparation.ts +225 -0
- package/apps/desktop/application/spatial-project-store.ts +286 -0
- package/apps/desktop/application/spatial-render-projection.ts +201 -0
- package/apps/desktop/application/spatial-render.ts +677 -0
- package/apps/desktop/application/spatial-spz.ts +44 -0
- package/apps/desktop/application/spatial-world-import.ts +135 -0
- package/apps/desktop/application/spatial-world-provenance.ts +46 -0
- package/apps/desktop/application/studio-port.ts +58 -0
- package/apps/desktop/application/verified-receipt-reconciliation.ts +1574 -0
- package/apps/desktop/capture/protocol.ts +689 -0
- package/apps/desktop/cli/analyzer.ts +406 -0
- package/apps/desktop/cli/args.ts +3181 -0
- package/apps/desktop/cli/asset-ingest.ts +747 -0
- package/apps/desktop/cli/atomic-render-effects.ts +210 -0
- package/apps/desktop/cli/atomic-render-platform.ts +205 -0
- package/apps/desktop/cli/atomic-render.ts +50 -0
- package/apps/desktop/cli/audio-analysis.ts +252 -0
- package/apps/desktop/cli/bounded-map.ts +39 -0
- package/apps/desktop/cli/bundle-service.ts +256 -0
- package/apps/desktop/cli/capabilities.ts +212 -0
- package/apps/desktop/cli/capture-bundle.ts +1117 -0
- package/apps/desktop/cli/command-host-resources.ts +251 -0
- package/apps/desktop/cli/commands.ts +6836 -0
- package/apps/desktop/cli/directing-blob.ts +291 -0
- package/apps/desktop/cli/directing-media.ts +517 -0
- package/apps/desktop/cli/directing-quote.ts +89 -0
- package/apps/desktop/cli/directing-service.ts +288 -0
- package/apps/desktop/cli/emoji-assets.ts +538 -0
- package/apps/desktop/cli/errors.ts +29 -0
- package/apps/desktop/cli/face-analysis-service.ts +940 -0
- package/apps/desktop/cli/filter-script.ts +76 -0
- package/apps/desktop/cli/gateway-application-port.ts +1142 -0
- package/apps/desktop/cli/gateway-credential.ts +81 -0
- package/apps/desktop/cli/gateway-media-artifacts.ts +723 -0
- package/apps/desktop/cli/gateway-media-catalog.ts +922 -0
- package/apps/desktop/cli/gateway-media-service.ts +3106 -0
- package/apps/desktop/cli/gateway-media-signature.ts +122 -0
- package/apps/desktop/cli/gateway-provider-options.ts +562 -0
- package/apps/desktop/cli/gateway-scene-provider.ts +391 -0
- package/apps/desktop/cli/help.ts +501 -0
- package/apps/desktop/cli/html-overlay-gpu.ts +97 -0
- package/apps/desktop/cli/html-overlay-renderer.ts +2478 -0
- package/apps/desktop/cli/io.ts +290 -0
- package/apps/desktop/cli/main.ts +123 -0
- package/apps/desktop/cli/media-effects-service.ts +885 -0
- package/apps/desktop/cli/media-ingest-model.ts +364 -0
- package/apps/desktop/cli/media-ingest-platform.ts +227 -0
- package/apps/desktop/cli/media-ingest-program.ts +200 -0
- package/apps/desktop/cli/media-ingest.ts +30 -0
- package/apps/desktop/cli/music-analysis-service.ts +434 -0
- package/apps/desktop/cli/mutation-lock.ts +530 -0
- package/apps/desktop/cli/native-assets.d.ts +12 -0
- package/apps/desktop/cli/native-media-runtime.macos.ts +7 -0
- package/apps/desktop/cli/overlay-playback.ts +177 -0
- package/apps/desktop/cli/paths.ts +265 -0
- package/apps/desktop/cli/portable-surface.ts +242 -0
- package/apps/desktop/cli/project-camera-receipt.ts +112 -0
- package/apps/desktop/cli/project-face-camera.ts +394 -0
- package/apps/desktop/cli/project-inactivity-service.ts +552 -0
- package/apps/desktop/cli/project-media-integrity.ts +121 -0
- package/apps/desktop/cli/project-renderer.ts +1526 -0
- package/apps/desktop/cli/project-service.ts +519 -0
- package/apps/desktop/cli/project-state-transaction.ts +520 -0
- package/apps/desktop/cli/query-limits.ts +1 -0
- package/apps/desktop/cli/recording-controller.ts +1567 -0
- package/apps/desktop/cli/recording-daemon.ts +931 -0
- package/apps/desktop/cli/recording-ref.ts +56 -0
- package/apps/desktop/cli/renamed-environment.ts +6 -0
- package/apps/desktop/cli/renderer.ts +1439 -0
- package/apps/desktop/cli/run-cli-test-helper.ts +60 -0
- package/apps/desktop/cli/scene-analysis-service.ts +879 -0
- package/apps/desktop/cli/spatial-compositor-verifier.ts +213 -0
- package/apps/desktop/cli/spatial-project-service.ts +90 -0
- package/apps/desktop/cli/spatial-scene-service.ts +105 -0
- package/apps/desktop/cli/spatial-world-service.ts +39 -0
- package/apps/desktop/cli/speech-analysis-service.ts +682 -0
- package/apps/desktop/cli/studio-assemble.ts +58 -0
- package/apps/desktop/cli/studio-command.ts +60 -0
- package/apps/desktop/cli/studio-custody.ts +60 -0
- package/apps/desktop/cli/studio-encode.ts +216 -0
- package/apps/desktop/cli/studio-exr.ts +52 -0
- package/apps/desktop/cli/studio-files.ts +179 -0
- package/apps/desktop/cli/studio-output-validation.ts +162 -0
- package/apps/desktop/cli/studio-process.ts +145 -0
- package/apps/desktop/cli/studio-runtime.ts +77 -0
- package/apps/desktop/cli/studio-scaffold.ts +70 -0
- package/apps/desktop/cli/studio-service.ts +301 -0
- package/apps/desktop/cli/studio-spatial-asset.ts +248 -0
- package/apps/desktop/cli/studio-template-names.ts +3 -0
- package/apps/desktop/cli/studio-workflow.ts +19 -0
- package/apps/desktop/cli/time.ts +25 -0
- package/apps/desktop/cli/workflow-code.ts +584 -0
- package/apps/desktop/cli/workflow-runs.ts +576 -0
- package/apps/desktop/code/advanced.ts +139 -0
- package/apps/desktop/code/application-node-planner.ts +1288 -0
- package/apps/desktop/code/compiler.ts +329 -0
- package/apps/desktop/code/contracts.ts +13 -0
- package/apps/desktop/code/creative-recipes.ts +94 -0
- package/apps/desktop/code/define-workflow.ts +115 -0
- package/apps/desktop/code/editing.ts +284 -0
- package/apps/desktop/code/file-candidate-provenance.ts +420 -0
- package/apps/desktop/code/file-candidate.ts +69 -0
- package/apps/desktop/code/graph-builder.ts +10 -0
- package/apps/desktop/code/host-resource-policy.ts +55 -0
- package/apps/desktop/code/plan-contracts.ts +159 -0
- package/apps/desktop/code/planning.ts +361 -0
- package/apps/desktop/code/public.ts +134 -0
- package/apps/desktop/code/reference-projection.ts +62 -0
- package/apps/desktop/code/run-contracts.ts +466 -0
- package/apps/desktop/code/run-store.ts +2478 -0
- package/apps/desktop/code/runtime-identity.ts +328 -0
- package/apps/desktop/code/scheduler.ts +3067 -0
- package/apps/desktop/code/semantic-builder.ts +1436 -0
- package/apps/desktop/code/source-bundle.ts +1641 -0
- package/apps/desktop/code/source-typecheck.ts +211 -0
- package/apps/desktop/code/testing.ts +79 -0
- package/apps/desktop/code/worker-client.ts +1675 -0
- package/apps/desktop/code/worker-effects.ts +141 -0
- package/apps/desktop/code/worker-entry.ts +435 -0
- package/apps/desktop/code/worker-lease-guardian.ts +179 -0
- package/apps/desktop/code/worker-process-identity.ts +484 -0
- package/apps/desktop/code/worker-protocol.ts +211 -0
- package/apps/desktop/code/workflow-effects.ts +132 -0
- package/apps/desktop/contracts/analysis.ts +949 -0
- package/apps/desktop/contracts/edit.ts +576 -0
- package/apps/desktop/contracts/index.ts +9 -0
- package/apps/desktop/contracts/media-effects.ts +139 -0
- package/apps/desktop/contracts/project-render.ts +644 -0
- package/apps/desktop/contracts/project.ts +740 -0
- package/apps/desktop/contracts/recording.ts +1434 -0
- package/apps/desktop/contracts/render.ts +334 -0
- package/apps/desktop/contracts/runtime.ts +155 -0
- package/apps/desktop/contracts/spatial-compositor.ts +58 -0
- package/apps/desktop/contracts/spatial-project.ts +159 -0
- package/apps/desktop/contracts/spatial-world.ts +48 -0
- package/apps/desktop/contracts/video-effects.ts +120 -0
- package/apps/desktop/core/alignment-apply.ts +400 -0
- package/apps/desktop/core/audio-alignment.ts +466 -0
- package/apps/desktop/core/auto-zoom.ts +209 -0
- package/apps/desktop/core/canonical-json.ts +14 -0
- package/apps/desktop/core/emoji.ts +210 -0
- package/apps/desktop/core/events.ts +409 -0
- package/apps/desktop/core/face-framing.ts +533 -0
- package/apps/desktop/core/face-tracks.ts +326 -0
- package/apps/desktop/core/inactivity.ts +137 -0
- package/apps/desktop/core/index.ts +30 -0
- package/apps/desktop/core/intervals.ts +123 -0
- package/apps/desktop/core/montage.ts +236 -0
- package/apps/desktop/core/music-analysis.ts +518 -0
- package/apps/desktop/core/plan.ts +244 -0
- package/apps/desktop/core/project-auto-zoom.ts +75 -0
- package/apps/desktop/core/project-camera-spatial.ts +676 -0
- package/apps/desktop/core/project-camera.ts +403 -0
- package/apps/desktop/core/project-captions.ts +888 -0
- package/apps/desktop/core/project-inactivity.ts +125 -0
- package/apps/desktop/core/project-metadata.ts +927 -0
- package/apps/desktop/core/project-plan.ts +375 -0
- package/apps/desktop/core/project-render-plan.ts +303 -0
- package/apps/desktop/core/project-time.ts +469 -0
- package/apps/desktop/core/render-plan.ts +442 -0
- package/apps/desktop/core/scene-cache.ts +174 -0
- package/apps/desktop/core/scene-sampling.ts +448 -0
- package/apps/desktop/core/spatial-compositor.ts +22 -0
- package/apps/desktop/core/spatial-durability.ts +99 -0
- package/apps/desktop/core/spatial-project.ts +169 -0
- package/apps/desktop/core/spatial-shot-clock.ts +32 -0
- package/apps/desktop/core/speech-analysis.ts +333 -0
- package/apps/desktop/core/storage.ts +970 -0
- package/apps/desktop/core/time-map.ts +91 -0
- package/apps/desktop/core/time.ts +61 -0
- package/apps/desktop/core/video-effects.ts +660 -0
- package/apps/desktop/dist/cli/NebulaSans-Bold-26se8aek.otf +0 -0
- package/apps/desktop/dist/cli/NebulaSans-Bold-bcz7y08t.woff2 +0 -0
- package/apps/desktop/dist/cli/NebulaSans-Book-5ax05zvn.woff2 +0 -0
- package/apps/desktop/dist/cli/NebulaSans-Book-8cenzchw.otf +0 -0
- package/apps/desktop/dist/cli/main.js +5086 -0
- package/apps/desktop/html-overlay/catalog.ts +171 -0
- package/apps/desktop/html-overlay/contracts.ts +372 -0
- package/apps/desktop/html-overlay/execution-profile.ts +77 -0
- package/apps/desktop/html-overlay/index.ts +98 -0
- package/apps/desktop/html-overlay/libraries.ts +263 -0
- package/apps/desktop/html-overlay/random.ts +49 -0
- package/apps/desktop/html-overlay/runtime.ts +813 -0
- package/apps/desktop/html-overlay/scaffolds.ts +792 -0
- package/apps/desktop/html-overlay/spark-runtime.ts +88 -0
- package/apps/desktop/html-overlay/spatial-splat-runtime.ts +60 -0
- package/apps/desktop/html-overlay/spatial.ts +720 -0
- package/apps/desktop/studio/assets/README.md +39 -0
- package/apps/desktop/studio/assets/contracts.ts +129 -0
- package/apps/desktop/studio/assets/network.ts +64 -0
- package/apps/desktop/studio/assets/poly-haven.ts +148 -0
- package/apps/desktop/studio/drivers/blender_driver.py +466 -0
- package/apps/desktop/studio/drivers/cadquery_driver.py +176 -0
- package/apps/desktop/studio/education/driver.py +308 -0
- package/apps/desktop/studio/education/lesson.py +180 -0
- package/apps/desktop/studio/education/toolkit.py +249 -0
- package/apps/desktop/workflows/chaptered-demo.ts +48 -0
- package/apps/desktop/workflows/creative-iteration.ts +107 -0
- package/apps/desktop/workflows/creative-selection.ts +92 -0
- package/apps/desktop/workflows/fragments.ts +486 -0
- package/apps/desktop/workflows/index.ts +93 -0
- package/apps/desktop/workflows/polished-screen-demo.ts +128 -0
- package/apps/desktop/workflows/scene-direction.ts +12 -0
- package/apps/desktop/workflows/social-variants.ts +75 -0
- package/apps/desktop/workflows/talking-head-cleanup.ts +53 -0
- package/dist/NebulaSans-Bold-26se8aek.otf +0 -0
- package/dist/NebulaSans-Bold-bcz7y08t.woff2 +0 -0
- package/dist/NebulaSans-Book-5ax05zvn.woff2 +0 -0
- package/dist/NebulaSans-Book-8cenzchw.otf +0 -0
- package/dist/cli.js +479 -0
- package/dist/code/advanced.js +270 -0
- package/dist/code/index.js +2240 -0
- package/dist/generate.js +24 -0
- package/dist/host-resources.js +26 -0
- package/dist/index-42zsesc1.js +2257 -0
- package/dist/index-7308egqr.js +48 -0
- package/dist/index-8txs6fkn.js +712 -0
- package/dist/index-mcy8z0br.js +192 -0
- package/dist/index-p63wavx0.js +2029 -0
- package/dist/index-r7gdhmsp.js +424 -0
- package/dist/index-sh6xbav6.js +1219 -0
- package/dist/index-z1w83f81.js +4 -0
- package/dist/index-z7239b4h.js +2363 -0
- package/dist/index-zfeg9hmw.js +2013 -0
- package/dist/index.js +241 -0
- package/dist/operations.js +29 -0
- package/dist/skill-install-dh0ntqba.js +10 -0
- package/dist/vectorize/worker.js +142 -0
- package/dist/workflow.js +16 -0
- package/docs/README.md +38 -0
- package/docs/studio.md +215 -0
- package/examples/studio/blender/README.md +26 -0
- package/examples/studio/blender/character.py +152 -0
- package/examples/studio/blender/cloth.py +101 -0
- package/examples/studio/blender/color_chart.py +29 -0
- package/examples/studio/blender/fluid.py +78 -0
- package/examples/studio/blender/import_model.py +74 -0
- package/examples/studio/blender/product.py +53 -0
- package/examples/studio/blender/shaded_street.py +481 -0
- package/examples/studio/blender/studio_scene.py +147 -0
- package/examples/studio/cadquery/README.md +13 -0
- package/examples/studio/cadquery/bracket.py +36 -0
- package/examples/studio/cadquery/import_step.py +12 -0
- package/examples/studio/education/README.md +46 -0
- package/examples/studio/education/lesson.json +99 -0
- package/examples/studio/education/scene.py +7 -0
- package/examples/studio/hybrid-scene.ts +96 -0
- package/examples/studio/native-workflow.ts +14 -0
- package/examples/studio/vgpu/README.md +64 -0
- package/examples/studio/vgpu/heat-field.mjs +290 -0
- package/package.json +304 -0
- package/packages/scene/src/index.ts +1 -0
- package/packages/scene/src/scene.ts +215 -0
- package/schema/diagram.schema.json +334 -0
- package/skills/slopcamera/SKILL.md +45 -0
- package/skills/slopcamera/agents/openai.yaml +4 -0
- package/skills/slopcamera/references/customization.md +77 -0
- package/skills/slopcamera/references/diagrams.md +153 -0
- package/skills/slopcamera/references/directed-scenes.md +42 -0
- package/skills/slopcamera/references/directing-video.md +18 -0
- package/skills/slopcamera/references/educational-video.md +26 -0
- package/skills/slopcamera/references/gateway-media.md +134 -0
- package/skills/slopcamera/references/install.md +62 -0
- package/skills/slopcamera/references/native-studio.md +31 -0
- package/skills/slopcamera/references/reference-led-3d.md +140 -0
- package/skills/slopcamera/references/rubber-stamp-examples/poster-example-1.jpg +0 -0
- package/skills/slopcamera/references/rubber-stamp-examples/poster-example-2.jpg +0 -0
- package/skills/slopcamera/references/rubber-stamp-examples/stamp-style-1.png +0 -0
- package/skills/slopcamera/references/rubber-stamp-examples/stamp-style-2.png +0 -0
- package/skills/slopcamera/references/rubber-stamp-field-notes.md +144 -0
- package/skills/slopcamera/references/vectorization.md +29 -0
- package/skills/slopcamera/references/video-projects.md +187 -0
- package/skills/slopcamera/references/visual-communication.md +120 -0
- package/skills/slopcamera/references/workflows-sdk.md +45 -0
- package/skills/slopcamera/scripts/compose-rubber-stamp-field-note.ts +229 -0
- package/src/artifacts.ts +106 -0
- package/src/assets/fonts/nebula-sans/LICENSE.txt +96 -0
- package/src/assets/fonts/nebula-sans/NebulaSans-Bold.otf +0 -0
- package/src/assets/fonts/nebula-sans/NebulaSans-Bold.woff2 +0 -0
- package/src/assets/fonts/nebula-sans/NebulaSans-Book.otf +0 -0
- package/src/assets/fonts/nebula-sans/NebulaSans-Book.woff2 +0 -0
- package/src/assets/fonts/nebula-sans/PROVENANCE.md +21 -0
- package/src/assets.d.ts +9 -0
- package/src/cli.ts +601 -0
- package/src/cloud-errors.ts +26 -0
- package/src/code/advanced.ts +10 -0
- package/src/code/boundary.ts +21 -0
- package/src/code/canonical-json.ts +129 -0
- package/src/code/compiler.ts +953 -0
- package/src/code/contracts.ts +576 -0
- package/src/code/define-workflow.ts +334 -0
- package/src/code/errors.ts +41 -0
- package/src/code/graph-builder.ts +945 -0
- package/src/code/index.ts +121 -0
- package/src/code/json-snapshot.ts +899 -0
- package/src/code/json-utf8.ts +88 -0
- package/src/code/portable-builder.ts +136 -0
- package/src/code/projection.ts +378 -0
- package/src/code/public-operations.ts +450 -0
- package/src/code/runtime.ts +580 -0
- package/src/code/sha256.ts +36 -0
- package/src/config.ts +180 -0
- package/src/desktop.ts +253 -0
- package/src/fs.ts +10 -0
- package/src/generate.ts +704 -0
- package/src/host-resource-posix.ts +161 -0
- package/src/host-resources.ts +1749 -0
- package/src/icons.ts +58 -0
- package/src/index.ts +200 -0
- package/src/label-layout.ts +130 -0
- package/src/layout.ts +413 -0
- package/src/lint.ts +127 -0
- package/src/mcp/boundary.ts +354 -0
- package/src/mcp/index.ts +33 -0
- package/src/mcp/server.ts +272 -0
- package/src/mcp/tools.ts +963 -0
- package/src/mcp/types.ts +71 -0
- package/src/operations.ts +775 -0
- package/src/parse.ts +861 -0
- package/src/process-environment.ts +17 -0
- package/src/render.ts +437 -0
- package/src/skill-install.ts +49 -0
- package/src/spatial-scene/authoring.ts +27 -0
- package/src/spatial-scene/camera-track.ts +83 -0
- package/src/spatial-scene/contracts.ts +277 -0
- package/src/spatial-scene/evaluate.ts +145 -0
- package/src/spatial-scene/gltf.ts +558 -0
- package/src/spatial-scene/identity.ts +260 -0
- package/src/spatial-scene/index.ts +10 -0
- package/src/spatial-scene/inspect.ts +68 -0
- package/src/spatial-scene/math.ts +280 -0
- package/src/spatial-scene/patch.ts +131 -0
- package/src/spatial-scene/time.ts +53 -0
- package/src/studio/contracts.ts +129 -0
- package/src/studio/index.ts +2 -0
- package/src/studio/plan.ts +156 -0
- package/src/studio/shared.ts +41 -0
- package/src/theme.ts +68 -0
- package/src/tldr.ts +474 -0
- package/src/types.ts +212 -0
- package/src/vectorize/archive.ts +101 -0
- package/src/vectorize/command.ts +797 -0
- package/src/vectorize/index.ts +27 -0
- package/src/vectorize/limits.ts +61 -0
- package/src/vectorize/metrics.ts +351 -0
- package/src/vectorize/pixels.ts +323 -0
- package/src/vectorize/supervisor.ts +339 -0
- package/src/vectorize/svg.ts +188 -0
- package/src/vectorize/tool.ts +542 -0
- package/src/vectorize/types.ts +113 -0
- package/src/vectorize/vectorize.ts +634 -0
- package/src/vectorize/worker-protocol.ts +40 -0
- package/src/vectorize/worker.ts +209 -0
- package/src/version.ts +1 -0
- package/src/workflow.ts +409 -0
|
@@ -0,0 +1,899 @@
|
|
|
1
|
+
import type { JsonValue } from "./contracts.js"
|
|
2
|
+
import { SlopcameraCodeError } from "./errors.js"
|
|
3
|
+
import { jsonStringUtf8ByteLength } from "./json-utf8.js"
|
|
4
|
+
import { createSha256HexHasher } from "./sha256.js"
|
|
5
|
+
|
|
6
|
+
export interface BoundedJsonSnapshot {
|
|
7
|
+
readonly bytes: number
|
|
8
|
+
readonly canonicalText?: string
|
|
9
|
+
readonly sha256: string
|
|
10
|
+
readonly value: JsonValue
|
|
11
|
+
readonly values: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface BoundedJsonValueSnapshot {
|
|
15
|
+
readonly bytes: number
|
|
16
|
+
readonly value: JsonValue
|
|
17
|
+
readonly values: number
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface BoundedCanonicalFingerprint {
|
|
21
|
+
readonly bytes: number
|
|
22
|
+
readonly sha256: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface JsonSnapshotLimits {
|
|
26
|
+
readonly captureCanonicalText?: boolean
|
|
27
|
+
readonly maximumBytes?: number
|
|
28
|
+
readonly maximumDepth?: number
|
|
29
|
+
readonly maximumValues?: number
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const DEFAULT_MAXIMUM_DEPTH = 128
|
|
33
|
+
const DEFAULT_MAXIMUM_VALUES = 1_000_000
|
|
34
|
+
const HASH_BUFFER_CODE_UNITS = 64 * 1024
|
|
35
|
+
|
|
36
|
+
type MutableJsonContainer = JsonValue[] | Record<string, JsonValue>
|
|
37
|
+
type StringPropertyDescriptors = Readonly<
|
|
38
|
+
Record<string, PropertyDescriptor | undefined>
|
|
39
|
+
>
|
|
40
|
+
|
|
41
|
+
interface Assignment {
|
|
42
|
+
readonly key: number | string
|
|
43
|
+
readonly target: MutableJsonContainer
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface VisitFrame {
|
|
47
|
+
readonly assignment?: Assignment
|
|
48
|
+
readonly depth: number
|
|
49
|
+
readonly kind: "visit"
|
|
50
|
+
readonly value: unknown
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface ArrayFrame {
|
|
54
|
+
readonly clone: JsonValue[] | undefined
|
|
55
|
+
readonly depth: number
|
|
56
|
+
readonly descriptors: StringPropertyDescriptors
|
|
57
|
+
readonly index: number
|
|
58
|
+
readonly kind: "array"
|
|
59
|
+
readonly source: object
|
|
60
|
+
readonly values: number
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface ObjectFrame {
|
|
64
|
+
readonly clone: Record<string, JsonValue> | undefined
|
|
65
|
+
readonly depth: number
|
|
66
|
+
readonly descriptors: StringPropertyDescriptors
|
|
67
|
+
readonly index: number
|
|
68
|
+
readonly keys: readonly string[]
|
|
69
|
+
readonly kind: "object"
|
|
70
|
+
readonly source: object
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
type SnapshotFrame = ArrayFrame | ObjectFrame | VisitFrame
|
|
74
|
+
|
|
75
|
+
interface BoundedJsonCapture {
|
|
76
|
+
readonly bytes: number
|
|
77
|
+
readonly canonicalText?: string
|
|
78
|
+
readonly sha256?: string
|
|
79
|
+
readonly value?: JsonValue
|
|
80
|
+
readonly values: number
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
interface JsonCaptureOptions {
|
|
84
|
+
readonly captureText: boolean
|
|
85
|
+
readonly captureValue: boolean
|
|
86
|
+
readonly hash: boolean
|
|
87
|
+
readonly hashPrefix?: string
|
|
88
|
+
/** Preserve canonicalJson's released Array.map domain without cloning extras. */
|
|
89
|
+
readonly ignoreNonIndexArrayProperties: boolean
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
interface FreezeFrame {
|
|
93
|
+
readonly exiting: boolean
|
|
94
|
+
readonly value: unknown
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
type MutableStructureContainer = unknown[] | Record<string, unknown>
|
|
98
|
+
|
|
99
|
+
interface StructureAssignment {
|
|
100
|
+
readonly key: number | string
|
|
101
|
+
readonly target: MutableStructureContainer
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
interface StructureVisitFrame {
|
|
105
|
+
readonly assignment?: StructureAssignment
|
|
106
|
+
readonly depth: number
|
|
107
|
+
readonly kind: "visit"
|
|
108
|
+
readonly value: unknown
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
interface StructureExitFrame {
|
|
112
|
+
readonly kind: "exit"
|
|
113
|
+
readonly source: object
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
type StructureFrame = StructureExitFrame | StructureVisitFrame
|
|
117
|
+
|
|
118
|
+
function invalidJson(
|
|
119
|
+
message: string,
|
|
120
|
+
details?: Readonly<Record<string, unknown>>,
|
|
121
|
+
): never {
|
|
122
|
+
throw new SlopcameraCodeError("invalid-data", message, details)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function positiveLimit(value: number, name: string): number {
|
|
126
|
+
if (!Number.isSafeInteger(value) || value < 1) {
|
|
127
|
+
return invalidJson(`${name} must be a positive safe integer.`)
|
|
128
|
+
}
|
|
129
|
+
return value
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function enumerableSymbolDescriptor(
|
|
133
|
+
descriptors: object,
|
|
134
|
+
): boolean {
|
|
135
|
+
return Object.getOwnPropertySymbols(descriptors).some(
|
|
136
|
+
symbol => (Reflect.get(descriptors, symbol) as PropertyDescriptor | undefined)
|
|
137
|
+
?.enumerable === true,
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Bounds recursive plain-container structure before an extensible user schema
|
|
143
|
+
* runs. Foreign class instances and unsupported scalar kinds remain atomic so
|
|
144
|
+
* schema preprocessors and transforms retain their established input contract.
|
|
145
|
+
*/
|
|
146
|
+
export function captureJsonStructure(
|
|
147
|
+
input: unknown,
|
|
148
|
+
name: string,
|
|
149
|
+
limits: JsonSnapshotLimits = {},
|
|
150
|
+
): unknown {
|
|
151
|
+
const maximumDepth = positiveLimit(
|
|
152
|
+
limits.maximumDepth ?? DEFAULT_MAXIMUM_DEPTH,
|
|
153
|
+
"JSON structure depth limit",
|
|
154
|
+
)
|
|
155
|
+
const maximumValues = positiveLimit(
|
|
156
|
+
limits.maximumValues ?? DEFAULT_MAXIMUM_VALUES,
|
|
157
|
+
"JSON structure value limit",
|
|
158
|
+
)
|
|
159
|
+
const maximumBytes = limits.maximumBytes === undefined
|
|
160
|
+
? undefined
|
|
161
|
+
: positiveLimit(limits.maximumBytes, "JSON structure byte limit")
|
|
162
|
+
const active = new WeakSet<object>()
|
|
163
|
+
const pending: StructureFrame[] = [{ depth: 0, kind: "visit", value: input }]
|
|
164
|
+
let bytes = 0
|
|
165
|
+
let discoveredValues = 1
|
|
166
|
+
let root: unknown
|
|
167
|
+
let rootAssigned = false
|
|
168
|
+
const addBytes = (additional: number): void => {
|
|
169
|
+
if (maximumBytes === undefined) return
|
|
170
|
+
if (additional > maximumBytes - bytes) {
|
|
171
|
+
return invalidJson(
|
|
172
|
+
`${name} contains more than ${String(maximumBytes)} bytes.`,
|
|
173
|
+
{ actualLowerBound: bytes + additional, maximumBytes },
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
bytes += additional
|
|
177
|
+
}
|
|
178
|
+
const addJsonString = (value: string): void => {
|
|
179
|
+
if (maximumBytes === undefined) return
|
|
180
|
+
const additional = jsonStringUtf8ByteLength(value, maximumBytes - bytes)
|
|
181
|
+
if (additional === undefined) {
|
|
182
|
+
return invalidJson(
|
|
183
|
+
`${name} contains more than ${String(maximumBytes)} bytes.`,
|
|
184
|
+
{ actualLowerBound: maximumBytes + 1, maximumBytes },
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
bytes += additional
|
|
188
|
+
}
|
|
189
|
+
const discover = (additional: number): void => {
|
|
190
|
+
if (additional > maximumValues - discoveredValues) {
|
|
191
|
+
return invalidJson(
|
|
192
|
+
`${name} contains more than ${String(maximumValues)} structural values.`,
|
|
193
|
+
{ actualLowerBound: discoveredValues + additional, maximumValues },
|
|
194
|
+
)
|
|
195
|
+
}
|
|
196
|
+
discoveredValues += additional
|
|
197
|
+
}
|
|
198
|
+
const assign = (assignment: StructureAssignment | undefined, value: unknown): void => {
|
|
199
|
+
if (assignment === undefined) {
|
|
200
|
+
root = value
|
|
201
|
+
rootAssigned = true
|
|
202
|
+
return
|
|
203
|
+
}
|
|
204
|
+
Object.defineProperty(assignment.target, assignment.key, {
|
|
205
|
+
configurable: true,
|
|
206
|
+
enumerable: true,
|
|
207
|
+
value,
|
|
208
|
+
writable: true,
|
|
209
|
+
})
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
try {
|
|
213
|
+
while (pending.length > 0) {
|
|
214
|
+
const frame = pending.pop()
|
|
215
|
+
if (frame === undefined) continue
|
|
216
|
+
if (frame.kind === "exit") {
|
|
217
|
+
active.delete(frame.source)
|
|
218
|
+
continue
|
|
219
|
+
}
|
|
220
|
+
if (frame.depth > maximumDepth) {
|
|
221
|
+
return invalidJson(
|
|
222
|
+
`${name} nesting exceeds ${String(maximumDepth)} levels.`,
|
|
223
|
+
{ actual: frame.depth, maximumDepth },
|
|
224
|
+
)
|
|
225
|
+
}
|
|
226
|
+
const value = frame.value
|
|
227
|
+
if (typeof value === "string") {
|
|
228
|
+
addJsonString(value)
|
|
229
|
+
assign(frame.assignment, value)
|
|
230
|
+
continue
|
|
231
|
+
}
|
|
232
|
+
if (value === null) {
|
|
233
|
+
addBytes(4)
|
|
234
|
+
assign(frame.assignment, value)
|
|
235
|
+
continue
|
|
236
|
+
}
|
|
237
|
+
if (typeof value === "boolean") {
|
|
238
|
+
addBytes(value ? 4 : 5)
|
|
239
|
+
assign(frame.assignment, value)
|
|
240
|
+
continue
|
|
241
|
+
}
|
|
242
|
+
if (typeof value === "number") {
|
|
243
|
+
if (Number.isFinite(value)) {
|
|
244
|
+
addBytes(JSON.stringify(Object.is(value, -0) ? 0 : value).length)
|
|
245
|
+
}
|
|
246
|
+
assign(frame.assignment, value)
|
|
247
|
+
continue
|
|
248
|
+
}
|
|
249
|
+
if (typeof value !== "object") {
|
|
250
|
+
assign(frame.assignment, value)
|
|
251
|
+
continue
|
|
252
|
+
}
|
|
253
|
+
const isArray = Array.isArray(value)
|
|
254
|
+
if (!isArray) {
|
|
255
|
+
const prototype: unknown = Object.getPrototypeOf(value)
|
|
256
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
257
|
+
assign(frame.assignment, value)
|
|
258
|
+
continue
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (active.has(value)) {
|
|
262
|
+
return invalidJson(`${name} does not support cyclic plain-container values.`)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const arrayLength = isArray ? (value as unknown[]).length : undefined
|
|
266
|
+
if (arrayLength !== undefined && arrayLength > maximumValues - discoveredValues) {
|
|
267
|
+
return invalidJson(
|
|
268
|
+
`${name} contains more than ${String(maximumValues)} structural values.`,
|
|
269
|
+
{ actualLowerBound: discoveredValues + arrayLength, maximumValues },
|
|
270
|
+
)
|
|
271
|
+
}
|
|
272
|
+
const descriptors = Object.getOwnPropertyDescriptors(value)
|
|
273
|
+
if (Object.getOwnPropertySymbols(descriptors).some(
|
|
274
|
+
symbol => (Reflect.get(descriptors, symbol) as PropertyDescriptor | undefined)
|
|
275
|
+
?.enumerable === true,
|
|
276
|
+
)) {
|
|
277
|
+
return invalidJson(`${name} cannot contain enumerable symbol properties.`)
|
|
278
|
+
}
|
|
279
|
+
const keys = Object.keys(descriptors)
|
|
280
|
+
.filter(key => descriptors[key]?.enumerable === true)
|
|
281
|
+
const namedArrayKeys = arrayLength === undefined
|
|
282
|
+
? []
|
|
283
|
+
: keys.filter((key) => {
|
|
284
|
+
const index = Number(key)
|
|
285
|
+
return !(
|
|
286
|
+
Number.isSafeInteger(index)
|
|
287
|
+
&& index >= 0
|
|
288
|
+
&& index < arrayLength
|
|
289
|
+
&& String(index) === key
|
|
290
|
+
)
|
|
291
|
+
})
|
|
292
|
+
if (arrayLength !== undefined) {
|
|
293
|
+
discover(arrayLength)
|
|
294
|
+
discover(namedArrayKeys.length)
|
|
295
|
+
addBytes(2 + Math.max(0, arrayLength - 1))
|
|
296
|
+
for (const key of namedArrayKeys) {
|
|
297
|
+
addJsonString(key)
|
|
298
|
+
addBytes(1)
|
|
299
|
+
}
|
|
300
|
+
} else {
|
|
301
|
+
discover(keys.length)
|
|
302
|
+
addBytes(2 + Math.max(0, keys.length - 1))
|
|
303
|
+
for (const key of keys) {
|
|
304
|
+
addJsonString(key)
|
|
305
|
+
addBytes(1)
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const clone: MutableStructureContainer = arrayLength === undefined
|
|
310
|
+
? {}
|
|
311
|
+
: new Array<unknown>(arrayLength)
|
|
312
|
+
assign(frame.assignment, clone)
|
|
313
|
+
active.add(value)
|
|
314
|
+
pending.push({ kind: "exit", source: value })
|
|
315
|
+
for (let index = keys.length - 1; index >= 0; index -= 1) {
|
|
316
|
+
const key = keys[index]
|
|
317
|
+
if (key === undefined) continue
|
|
318
|
+
const descriptor = descriptors[key]
|
|
319
|
+
if (
|
|
320
|
+
descriptor === undefined
|
|
321
|
+
|| descriptor.get !== undefined
|
|
322
|
+
|| descriptor.set !== undefined
|
|
323
|
+
) {
|
|
324
|
+
return invalidJson(`${name} properties must be plain data properties.`)
|
|
325
|
+
}
|
|
326
|
+
const numericKey = arrayLength !== undefined
|
|
327
|
+
&& Number.isSafeInteger(Number(key))
|
|
328
|
+
&& Number(key) >= 0
|
|
329
|
+
&& Number(key) < arrayLength
|
|
330
|
+
&& String(Number(key)) === key
|
|
331
|
+
? Number(key)
|
|
332
|
+
: key
|
|
333
|
+
pending.push({
|
|
334
|
+
assignment: { key: numericKey, target: clone },
|
|
335
|
+
depth: frame.depth + 1,
|
|
336
|
+
kind: "visit",
|
|
337
|
+
value: descriptor.value,
|
|
338
|
+
})
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
} catch (error) {
|
|
342
|
+
if (error instanceof SlopcameraCodeError) throw error
|
|
343
|
+
throw new SlopcameraCodeError(
|
|
344
|
+
"invalid-data",
|
|
345
|
+
`${name} could not be safely inspected.`,
|
|
346
|
+
{ cause: error instanceof Error ? error.message : String(error) },
|
|
347
|
+
)
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (!rootAssigned) {
|
|
351
|
+
return invalidJson(`${name} did not contain a capturable value.`)
|
|
352
|
+
}
|
|
353
|
+
return root
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
export function preflightJsonStructure(
|
|
357
|
+
input: unknown,
|
|
358
|
+
name: string,
|
|
359
|
+
limits: JsonSnapshotLimits = {},
|
|
360
|
+
): void {
|
|
361
|
+
void captureJsonStructure(input, name, limits)
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function assignValue(
|
|
365
|
+
assignment: Assignment | undefined,
|
|
366
|
+
value: JsonValue,
|
|
367
|
+
setRoot: (root: JsonValue) => void,
|
|
368
|
+
): void {
|
|
369
|
+
if (assignment === undefined) {
|
|
370
|
+
setRoot(value)
|
|
371
|
+
return
|
|
372
|
+
}
|
|
373
|
+
if (Array.isArray(assignment.target)) {
|
|
374
|
+
assignment.target[assignment.key as number] = value
|
|
375
|
+
} else {
|
|
376
|
+
Object.defineProperty(assignment.target, assignment.key, {
|
|
377
|
+
configurable: true,
|
|
378
|
+
enumerable: true,
|
|
379
|
+
value,
|
|
380
|
+
writable: true,
|
|
381
|
+
})
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function scalarJson(value: boolean | null | number | string): string {
|
|
386
|
+
if (typeof value === "number") {
|
|
387
|
+
if (!Number.isFinite(value)) {
|
|
388
|
+
return invalidJson("JSON snapshots do not support non-finite numbers.")
|
|
389
|
+
}
|
|
390
|
+
return JSON.stringify(Object.is(value, -0) ? 0 : value)
|
|
391
|
+
}
|
|
392
|
+
return JSON.stringify(value)
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Validates, clones, canonically hashes, byte-bounds, and freezes JSON without
|
|
397
|
+
* recursive parsing or traversal. Object accessors are rejected from their
|
|
398
|
+
* descriptors and are never invoked.
|
|
399
|
+
*/
|
|
400
|
+
function captureBoundedJson(
|
|
401
|
+
input: unknown,
|
|
402
|
+
maximumBytesInput: number,
|
|
403
|
+
name: string,
|
|
404
|
+
limits: JsonSnapshotLimits = {},
|
|
405
|
+
capture: JsonCaptureOptions,
|
|
406
|
+
): BoundedJsonCapture {
|
|
407
|
+
const maximumBytes = positiveLimit(maximumBytesInput, "JSON snapshot byte limit")
|
|
408
|
+
const maximumDepth = positiveLimit(
|
|
409
|
+
limits.maximumDepth ?? DEFAULT_MAXIMUM_DEPTH,
|
|
410
|
+
"JSON snapshot depth limit",
|
|
411
|
+
)
|
|
412
|
+
const maximumValues = positiveLimit(
|
|
413
|
+
limits.maximumValues ?? DEFAULT_MAXIMUM_VALUES,
|
|
414
|
+
"JSON snapshot value limit",
|
|
415
|
+
)
|
|
416
|
+
const active = new WeakSet<object>()
|
|
417
|
+
const hash = capture.hash ? createSha256HexHasher() : undefined
|
|
418
|
+
let hashPart = capture.hashPrefix ?? ""
|
|
419
|
+
const flushHash = (): void => {
|
|
420
|
+
if (hash === undefined || hashPart.length === 0) return
|
|
421
|
+
hash.update(hashPart)
|
|
422
|
+
hashPart = ""
|
|
423
|
+
}
|
|
424
|
+
const captureCanonicalText = capture.captureText
|
|
425
|
+
const canonicalParts: string[] = []
|
|
426
|
+
let canonicalPart = ""
|
|
427
|
+
const pending: SnapshotFrame[] = [{ depth: 0, kind: "visit", value: input }]
|
|
428
|
+
let bytes = 0
|
|
429
|
+
let discoveredValues = 1
|
|
430
|
+
let root: JsonValue | undefined
|
|
431
|
+
|
|
432
|
+
const append = (text: string, exactBytes = text.length): void => {
|
|
433
|
+
if (exactBytes > maximumBytes - bytes) {
|
|
434
|
+
return invalidJson(
|
|
435
|
+
`${name} contains more than ${String(maximumBytes)} bytes.`,
|
|
436
|
+
{ actualLowerBound: bytes + exactBytes, maximumBytes },
|
|
437
|
+
)
|
|
438
|
+
}
|
|
439
|
+
if (hash !== undefined) {
|
|
440
|
+
hashPart += text
|
|
441
|
+
if (hashPart.length >= HASH_BUFFER_CODE_UNITS) flushHash()
|
|
442
|
+
}
|
|
443
|
+
if (captureCanonicalText) {
|
|
444
|
+
canonicalPart += text
|
|
445
|
+
if (canonicalPart.length >= 64 * 1024) {
|
|
446
|
+
canonicalParts.push(canonicalPart)
|
|
447
|
+
canonicalPart = ""
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
bytes += exactBytes
|
|
451
|
+
}
|
|
452
|
+
const discover = (additional: number): void => {
|
|
453
|
+
if (additional > maximumValues - discoveredValues) {
|
|
454
|
+
return invalidJson(
|
|
455
|
+
`${name} contains more than ${String(maximumValues)} JSON values.`,
|
|
456
|
+
{ actualLowerBound: discoveredValues + additional, maximumValues },
|
|
457
|
+
)
|
|
458
|
+
}
|
|
459
|
+
discoveredValues += additional
|
|
460
|
+
}
|
|
461
|
+
const setRoot = (value: JsonValue): void => {
|
|
462
|
+
root = value
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
try {
|
|
466
|
+
while (pending.length > 0) {
|
|
467
|
+
const frame = pending.pop()
|
|
468
|
+
if (frame === undefined) break
|
|
469
|
+
|
|
470
|
+
if (frame.kind === "array") {
|
|
471
|
+
if (frame.index === frame.values) {
|
|
472
|
+
append("]")
|
|
473
|
+
active.delete(frame.source)
|
|
474
|
+
if (frame.clone !== undefined) Object.freeze(frame.clone)
|
|
475
|
+
continue
|
|
476
|
+
}
|
|
477
|
+
if (frame.index > 0) append(",")
|
|
478
|
+
const descriptor = frame.descriptors[String(frame.index)]
|
|
479
|
+
if (
|
|
480
|
+
descriptor === undefined
|
|
481
|
+
|| descriptor.get !== undefined
|
|
482
|
+
|| descriptor.set !== undefined
|
|
483
|
+
) {
|
|
484
|
+
return invalidJson(
|
|
485
|
+
`${name} arrays must contain plain data elements.`,
|
|
486
|
+
)
|
|
487
|
+
}
|
|
488
|
+
pending.push({ ...frame, index: frame.index + 1 })
|
|
489
|
+
pending.push({
|
|
490
|
+
...(frame.clone === undefined
|
|
491
|
+
? {}
|
|
492
|
+
: { assignment: { key: frame.index, target: frame.clone } }),
|
|
493
|
+
depth: frame.depth + 1,
|
|
494
|
+
kind: "visit",
|
|
495
|
+
value: descriptor.value,
|
|
496
|
+
})
|
|
497
|
+
continue
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
if (frame.kind === "object") {
|
|
501
|
+
if (frame.index === frame.keys.length) {
|
|
502
|
+
append("}")
|
|
503
|
+
active.delete(frame.source)
|
|
504
|
+
if (frame.clone !== undefined) Object.freeze(frame.clone)
|
|
505
|
+
continue
|
|
506
|
+
}
|
|
507
|
+
const key = frame.keys[frame.index]
|
|
508
|
+
if (key === undefined) {
|
|
509
|
+
return invalidJson(`${name} lost an object key during traversal.`)
|
|
510
|
+
}
|
|
511
|
+
if (frame.index > 0) append(",")
|
|
512
|
+
const keyBytes = jsonStringUtf8ByteLength(key, maximumBytes - bytes)
|
|
513
|
+
if (keyBytes === undefined) {
|
|
514
|
+
return invalidJson(
|
|
515
|
+
`${name} contains more than ${String(maximumBytes)} bytes.`,
|
|
516
|
+
{ actualLowerBound: maximumBytes + 1, maximumBytes },
|
|
517
|
+
)
|
|
518
|
+
}
|
|
519
|
+
append(scalarJson(key), keyBytes)
|
|
520
|
+
append(":")
|
|
521
|
+
const descriptor = frame.descriptors[key]
|
|
522
|
+
if (
|
|
523
|
+
descriptor === undefined
|
|
524
|
+
|| descriptor.get !== undefined
|
|
525
|
+
|| descriptor.set !== undefined
|
|
526
|
+
) {
|
|
527
|
+
return invalidJson(`${name} properties must be plain data properties.`)
|
|
528
|
+
}
|
|
529
|
+
pending.push({ ...frame, index: frame.index + 1 })
|
|
530
|
+
pending.push({
|
|
531
|
+
...(frame.clone === undefined
|
|
532
|
+
? {}
|
|
533
|
+
: { assignment: { key, target: frame.clone } }),
|
|
534
|
+
depth: frame.depth + 1,
|
|
535
|
+
kind: "visit",
|
|
536
|
+
value: descriptor.value,
|
|
537
|
+
})
|
|
538
|
+
continue
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
if (frame.depth > maximumDepth) {
|
|
542
|
+
return invalidJson(
|
|
543
|
+
`${name} nesting exceeds ${String(maximumDepth)} levels.`,
|
|
544
|
+
{ actual: frame.depth, maximumDepth },
|
|
545
|
+
)
|
|
546
|
+
}
|
|
547
|
+
const value = frame.value
|
|
548
|
+
if (
|
|
549
|
+
value === null
|
|
550
|
+
|| typeof value === "boolean"
|
|
551
|
+
|| typeof value === "number"
|
|
552
|
+
|| typeof value === "string"
|
|
553
|
+
) {
|
|
554
|
+
const normalized = typeof value === "number" && Object.is(value, -0)
|
|
555
|
+
? 0
|
|
556
|
+
: value
|
|
557
|
+
if (typeof value === "string") {
|
|
558
|
+
const stringBytes = jsonStringUtf8ByteLength(
|
|
559
|
+
value,
|
|
560
|
+
maximumBytes - bytes,
|
|
561
|
+
)
|
|
562
|
+
if (stringBytes === undefined) {
|
|
563
|
+
return invalidJson(
|
|
564
|
+
`${name} contains more than ${String(maximumBytes)} bytes.`,
|
|
565
|
+
{ actualLowerBound: maximumBytes + 1, maximumBytes },
|
|
566
|
+
)
|
|
567
|
+
}
|
|
568
|
+
append(scalarJson(value), stringBytes)
|
|
569
|
+
} else {
|
|
570
|
+
append(scalarJson(value))
|
|
571
|
+
}
|
|
572
|
+
if (capture.captureValue) {
|
|
573
|
+
assignValue(frame.assignment, normalized, setRoot)
|
|
574
|
+
}
|
|
575
|
+
continue
|
|
576
|
+
}
|
|
577
|
+
if (typeof value !== "object") {
|
|
578
|
+
return invalidJson(`${name} does not support ${typeof value} values.`)
|
|
579
|
+
}
|
|
580
|
+
if (active.has(value)) {
|
|
581
|
+
return invalidJson(`${name} does not support cyclic values.`)
|
|
582
|
+
}
|
|
583
|
+
const arrayLength = Array.isArray(value) ? value.length : undefined
|
|
584
|
+
if (
|
|
585
|
+
arrayLength !== undefined
|
|
586
|
+
&& arrayLength > maximumValues - discoveredValues
|
|
587
|
+
) {
|
|
588
|
+
return invalidJson(
|
|
589
|
+
`${name} contains more than ${String(maximumValues)} JSON values.`,
|
|
590
|
+
{
|
|
591
|
+
actualLowerBound: discoveredValues + arrayLength,
|
|
592
|
+
maximumValues,
|
|
593
|
+
},
|
|
594
|
+
)
|
|
595
|
+
}
|
|
596
|
+
if (arrayLength !== undefined) {
|
|
597
|
+
const punctuationBytes = 2 + Math.max(0, arrayLength - 1)
|
|
598
|
+
if (punctuationBytes > maximumBytes - bytes) {
|
|
599
|
+
return invalidJson(
|
|
600
|
+
`${name} contains more than ${String(maximumBytes)} bytes.`,
|
|
601
|
+
{ actualLowerBound: bytes + punctuationBytes, maximumBytes },
|
|
602
|
+
)
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
active.add(value)
|
|
606
|
+
|
|
607
|
+
if (Array.isArray(value)) {
|
|
608
|
+
const length = arrayLength
|
|
609
|
+
if (length === undefined) {
|
|
610
|
+
return invalidJson(`${name} lost an array length during traversal.`)
|
|
611
|
+
}
|
|
612
|
+
discoveredValues += length
|
|
613
|
+
const descriptors = Object.getOwnPropertyDescriptors(value)
|
|
614
|
+
if (!capture.ignoreNonIndexArrayProperties) {
|
|
615
|
+
if (enumerableSymbolDescriptor(descriptors)) {
|
|
616
|
+
return invalidJson(`${name} cannot contain enumerable symbol properties.`)
|
|
617
|
+
}
|
|
618
|
+
const keys = Object.keys(descriptors)
|
|
619
|
+
.filter(key => descriptors[key]?.enumerable === true)
|
|
620
|
+
if (
|
|
621
|
+
keys.length !== length
|
|
622
|
+
|| keys.some((key, index) => key !== String(index))
|
|
623
|
+
) {
|
|
624
|
+
return invalidJson(
|
|
625
|
+
`${name} arrays must be dense and cannot have named properties.`,
|
|
626
|
+
)
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
const clone = capture.captureValue ? [] as JsonValue[] : undefined
|
|
630
|
+
if (clone !== undefined) assignValue(frame.assignment, clone, setRoot)
|
|
631
|
+
append("[")
|
|
632
|
+
pending.push({
|
|
633
|
+
clone,
|
|
634
|
+
depth: frame.depth,
|
|
635
|
+
descriptors,
|
|
636
|
+
index: 0,
|
|
637
|
+
kind: "array",
|
|
638
|
+
source: value,
|
|
639
|
+
values: length,
|
|
640
|
+
})
|
|
641
|
+
continue
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
const prototype: unknown = Object.getPrototypeOf(value)
|
|
645
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
646
|
+
return invalidJson(`${name} accepts only arrays and plain objects.`)
|
|
647
|
+
}
|
|
648
|
+
const descriptors = Object.getOwnPropertyDescriptors(value)
|
|
649
|
+
if (enumerableSymbolDescriptor(descriptors)) {
|
|
650
|
+
return invalidJson(`${name} cannot contain enumerable symbol properties.`)
|
|
651
|
+
}
|
|
652
|
+
const keys = Object.keys(descriptors)
|
|
653
|
+
.filter(key => descriptors[key]?.enumerable === true)
|
|
654
|
+
.sort()
|
|
655
|
+
discover(keys.length)
|
|
656
|
+
const clone: Record<string, JsonValue> | undefined = capture.captureValue
|
|
657
|
+
? {}
|
|
658
|
+
: undefined
|
|
659
|
+
if (clone !== undefined) assignValue(frame.assignment, clone, setRoot)
|
|
660
|
+
append("{")
|
|
661
|
+
pending.push({
|
|
662
|
+
clone,
|
|
663
|
+
depth: frame.depth,
|
|
664
|
+
descriptors,
|
|
665
|
+
index: 0,
|
|
666
|
+
keys,
|
|
667
|
+
kind: "object",
|
|
668
|
+
source: value,
|
|
669
|
+
})
|
|
670
|
+
}
|
|
671
|
+
} catch (error) {
|
|
672
|
+
if (error instanceof SlopcameraCodeError) throw error
|
|
673
|
+
throw new SlopcameraCodeError(
|
|
674
|
+
"invalid-data",
|
|
675
|
+
`${name} could not be safely inspected.`,
|
|
676
|
+
{ cause: error instanceof Error ? error.message : String(error) },
|
|
677
|
+
)
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
if (capture.captureValue && root === undefined) {
|
|
681
|
+
return invalidJson(`${name} did not contain a JSON value.`)
|
|
682
|
+
}
|
|
683
|
+
if (captureCanonicalText && canonicalPart.length > 0) {
|
|
684
|
+
canonicalParts.push(canonicalPart)
|
|
685
|
+
}
|
|
686
|
+
flushHash()
|
|
687
|
+
return Object.freeze({
|
|
688
|
+
bytes,
|
|
689
|
+
...(captureCanonicalText
|
|
690
|
+
? { canonicalText: canonicalParts.join("") }
|
|
691
|
+
: {}),
|
|
692
|
+
...(hash === undefined ? {} : { sha256: hash.digestHex() }),
|
|
693
|
+
...(capture.captureValue ? { value: root as JsonValue } : {}),
|
|
694
|
+
values: discoveredValues,
|
|
695
|
+
})
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
export function createBoundedJsonValueSnapshot(
|
|
699
|
+
input: unknown,
|
|
700
|
+
maximumBytesInput: number,
|
|
701
|
+
name: string,
|
|
702
|
+
limits: JsonSnapshotLimits = {},
|
|
703
|
+
): BoundedJsonValueSnapshot {
|
|
704
|
+
const captured = captureBoundedJson(input, maximumBytesInput, name, limits, {
|
|
705
|
+
captureText: false,
|
|
706
|
+
captureValue: true,
|
|
707
|
+
hash: false,
|
|
708
|
+
ignoreNonIndexArrayProperties: false,
|
|
709
|
+
})
|
|
710
|
+
if (captured.value === undefined) {
|
|
711
|
+
return invalidJson(`${name} did not produce a complete JSON value snapshot.`)
|
|
712
|
+
}
|
|
713
|
+
return captured as BoundedJsonValueSnapshot
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
export function createBoundedJsonSnapshot(
|
|
717
|
+
input: unknown,
|
|
718
|
+
maximumBytesInput: number,
|
|
719
|
+
name: string,
|
|
720
|
+
limits: JsonSnapshotLimits = {},
|
|
721
|
+
): BoundedJsonSnapshot {
|
|
722
|
+
const captured = captureBoundedJson(input, maximumBytesInput, name, limits, {
|
|
723
|
+
captureText: limits.captureCanonicalText === true,
|
|
724
|
+
captureValue: true,
|
|
725
|
+
hash: true,
|
|
726
|
+
ignoreNonIndexArrayProperties: false,
|
|
727
|
+
})
|
|
728
|
+
if (captured.sha256 === undefined || captured.value === undefined) {
|
|
729
|
+
return invalidJson(`${name} did not produce a complete JSON snapshot.`)
|
|
730
|
+
}
|
|
731
|
+
return captured as BoundedJsonSnapshot
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
export function createBoundedCanonicalJson(
|
|
735
|
+
input: unknown,
|
|
736
|
+
maximumBytesInput: number,
|
|
737
|
+
name: string,
|
|
738
|
+
limits: JsonSnapshotLimits = {},
|
|
739
|
+
): string {
|
|
740
|
+
const captured = captureBoundedJson(input, maximumBytesInput, name, limits, {
|
|
741
|
+
captureText: true,
|
|
742
|
+
captureValue: false,
|
|
743
|
+
hash: false,
|
|
744
|
+
ignoreNonIndexArrayProperties: true,
|
|
745
|
+
})
|
|
746
|
+
if (captured.canonicalText === undefined) {
|
|
747
|
+
return invalidJson(`${name} did not produce canonical JSON text.`)
|
|
748
|
+
}
|
|
749
|
+
return captured.canonicalText
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
export function createBoundedCanonicalFingerprint(
|
|
753
|
+
input: unknown,
|
|
754
|
+
maximumBytesInput: number,
|
|
755
|
+
name: string,
|
|
756
|
+
limits: JsonSnapshotLimits = {},
|
|
757
|
+
hashPrefix?: string,
|
|
758
|
+
): BoundedCanonicalFingerprint {
|
|
759
|
+
const captured = captureBoundedJson(input, maximumBytesInput, name, limits, {
|
|
760
|
+
captureText: false,
|
|
761
|
+
captureValue: false,
|
|
762
|
+
hash: true,
|
|
763
|
+
ignoreNonIndexArrayProperties: true,
|
|
764
|
+
...(hashPrefix === undefined ? {} : { hashPrefix }),
|
|
765
|
+
})
|
|
766
|
+
if (captured.sha256 === undefined) {
|
|
767
|
+
return invalidJson(`${name} did not produce a canonical JSON identity.`)
|
|
768
|
+
}
|
|
769
|
+
return Object.freeze({ bytes: captured.bytes, sha256: captured.sha256 })
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
export function createBoundedCanonicalSha256(
|
|
773
|
+
input: unknown,
|
|
774
|
+
maximumBytesInput: number,
|
|
775
|
+
name: string,
|
|
776
|
+
limits: JsonSnapshotLimits = {},
|
|
777
|
+
hashPrefix?: string,
|
|
778
|
+
): string {
|
|
779
|
+
return createBoundedCanonicalFingerprint(
|
|
780
|
+
input,
|
|
781
|
+
maximumBytesInput,
|
|
782
|
+
name,
|
|
783
|
+
limits,
|
|
784
|
+
hashPrefix,
|
|
785
|
+
).sha256
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
/**
|
|
789
|
+
* Deeply freezes an already JSON-shaped value without consuming the call stack.
|
|
790
|
+
* This is kept for internal values whose byte identity was established earlier.
|
|
791
|
+
*/
|
|
792
|
+
export function deepFreezeJson<Value>(value: Value): Value {
|
|
793
|
+
const active = new WeakSet<object>()
|
|
794
|
+
const completed = new WeakSet<object>()
|
|
795
|
+
const pending: FreezeFrame[] = [{ exiting: false, value }]
|
|
796
|
+
|
|
797
|
+
try {
|
|
798
|
+
while (pending.length > 0) {
|
|
799
|
+
const item = pending.pop()
|
|
800
|
+
if (item === undefined) break
|
|
801
|
+
const current = item.value
|
|
802
|
+
if (
|
|
803
|
+
current === null
|
|
804
|
+
|| typeof current === "boolean"
|
|
805
|
+
|| typeof current === "string"
|
|
806
|
+
) {
|
|
807
|
+
continue
|
|
808
|
+
}
|
|
809
|
+
if (typeof current === "number") {
|
|
810
|
+
if (!Number.isFinite(current)) {
|
|
811
|
+
return invalidJson("JSON snapshots do not support non-finite numbers.")
|
|
812
|
+
}
|
|
813
|
+
continue
|
|
814
|
+
}
|
|
815
|
+
if (typeof current !== "object") {
|
|
816
|
+
return invalidJson(`JSON snapshots do not support ${typeof current} values.`)
|
|
817
|
+
}
|
|
818
|
+
if (item.exiting) {
|
|
819
|
+
active.delete(current)
|
|
820
|
+
completed.add(current)
|
|
821
|
+
Object.freeze(current)
|
|
822
|
+
continue
|
|
823
|
+
}
|
|
824
|
+
if (completed.has(current)) continue
|
|
825
|
+
if (active.has(current)) {
|
|
826
|
+
return invalidJson("JSON snapshots do not support cycles.")
|
|
827
|
+
}
|
|
828
|
+
active.add(current)
|
|
829
|
+
pending.push({ exiting: true, value: current })
|
|
830
|
+
if (Array.isArray(current)) {
|
|
831
|
+
const length = current.length
|
|
832
|
+
const descriptors = Object.getOwnPropertyDescriptors(current)
|
|
833
|
+
if (enumerableSymbolDescriptor(descriptors)) {
|
|
834
|
+
return invalidJson("JSON snapshots cannot contain enumerable symbol properties.")
|
|
835
|
+
}
|
|
836
|
+
const keys = Object.keys(descriptors)
|
|
837
|
+
.filter(key => descriptors[key]?.enumerable === true)
|
|
838
|
+
if (
|
|
839
|
+
keys.length !== length
|
|
840
|
+
|| keys.some((key, index) => key !== String(index))
|
|
841
|
+
) {
|
|
842
|
+
return invalidJson(
|
|
843
|
+
"JSON snapshot arrays must be dense and cannot have named properties.",
|
|
844
|
+
)
|
|
845
|
+
}
|
|
846
|
+
for (let index = length - 1; index >= 0; index -= 1) {
|
|
847
|
+
const descriptor = descriptors[String(index)]
|
|
848
|
+
if (
|
|
849
|
+
descriptor === undefined
|
|
850
|
+
|| descriptor.get !== undefined
|
|
851
|
+
|| descriptor.set !== undefined
|
|
852
|
+
) {
|
|
853
|
+
return invalidJson(
|
|
854
|
+
"JSON snapshot arrays must contain plain data elements.",
|
|
855
|
+
)
|
|
856
|
+
}
|
|
857
|
+
pending.push({ exiting: false, value: descriptor.value })
|
|
858
|
+
}
|
|
859
|
+
continue
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
const prototype: unknown = Object.getPrototypeOf(current)
|
|
863
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
864
|
+
return invalidJson("JSON snapshots accept only arrays and plain objects.")
|
|
865
|
+
}
|
|
866
|
+
const descriptors = Object.getOwnPropertyDescriptors(current)
|
|
867
|
+
if (enumerableSymbolDescriptor(descriptors)) {
|
|
868
|
+
return invalidJson("JSON snapshots cannot contain enumerable symbol properties.")
|
|
869
|
+
}
|
|
870
|
+
const keys = Object.keys(descriptors)
|
|
871
|
+
.filter(key => descriptors[key]?.enumerable === true)
|
|
872
|
+
for (let index = keys.length - 1; index >= 0; index -= 1) {
|
|
873
|
+
const key = keys[index]
|
|
874
|
+
if (key === undefined) continue
|
|
875
|
+
const descriptor = descriptors[key]
|
|
876
|
+
if (
|
|
877
|
+
descriptor === undefined
|
|
878
|
+
|| descriptor.get !== undefined
|
|
879
|
+
|| descriptor.set !== undefined
|
|
880
|
+
) {
|
|
881
|
+
return invalidJson("JSON snapshot properties must be plain data properties.")
|
|
882
|
+
}
|
|
883
|
+
if (descriptor.value === undefined) {
|
|
884
|
+
return invalidJson(`JSON snapshot property ${key} is undefined.`)
|
|
885
|
+
}
|
|
886
|
+
pending.push({ exiting: false, value: descriptor.value })
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
} catch (error) {
|
|
890
|
+
if (error instanceof SlopcameraCodeError) throw error
|
|
891
|
+
throw new SlopcameraCodeError(
|
|
892
|
+
"invalid-data",
|
|
893
|
+
"JSON snapshot could not be safely inspected.",
|
|
894
|
+
{ cause: error instanceof Error ? error.message : String(error) },
|
|
895
|
+
)
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
return value
|
|
899
|
+
}
|