@nodaro/shared 1.4.2
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 -0
- package/README.md +32 -0
- package/dist/index.cjs +12506 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +12269 -0
- package/dist/index.d.ts +12269 -0
- package/dist/index.js +11831 -0
- package/dist/index.js.map +1 -0
- package/package.json +54 -0
- package/src/__tests__/__snapshots__/prompt-builder-segments.test.ts.snap +65 -0
- package/src/__tests__/ai-avatar-pricing.test.ts +321 -0
- package/src/__tests__/apply-video-negative-prompt.test.ts +76 -0
- package/src/__tests__/attach-columns.test.ts +28 -0
- package/src/__tests__/audio-crossfade-curves.test.ts +33 -0
- package/src/__tests__/build-model-menu.test.ts +58 -0
- package/src/__tests__/character-aspect-defaults.test.ts +142 -0
- package/src/__tests__/character-mention-slug.test.ts +199 -0
- package/src/__tests__/character-reference-photo-kinds.test.ts +32 -0
- package/src/__tests__/character-usage-mode.test.ts +147 -0
- package/src/__tests__/character-variant-assets.test.ts +142 -0
- package/src/__tests__/character-voice.test.ts +130 -0
- package/src/__tests__/cinematic-avatar-pricing.test.ts +119 -0
- package/src/__tests__/combine-transitions.test.ts +137 -0
- package/src/__tests__/component-types.test.ts +87 -0
- package/src/__tests__/condition-variables.test.ts +98 -0
- package/src/__tests__/credit-identifiers.test.ts +811 -0
- package/src/__tests__/default-video-provider.test.ts +63 -0
- package/src/__tests__/entity-image-handle.test.ts +43 -0
- package/src/__tests__/entity-variants.test.ts +33 -0
- package/src/__tests__/fal-lip-sync-sets.test.ts +31 -0
- package/src/__tests__/featured-entities.test.ts +33 -0
- package/src/__tests__/film-pricing.test.ts +64 -0
- package/src/__tests__/filter-condition.test.ts +285 -0
- package/src/__tests__/freecut-protocol.test.ts +35 -0
- package/src/__tests__/generate-text-items.test.ts +19 -0
- package/src/__tests__/generated-results.test.ts +50 -0
- package/src/__tests__/graph-utils.test.ts +618 -0
- package/src/__tests__/group-aggregation.test.ts +83 -0
- package/src/__tests__/i18n-locale-completeness.test.ts +156 -0
- package/src/__tests__/image-critic-node.test.ts +86 -0
- package/src/__tests__/image-mask-mode.test.ts +16 -0
- package/src/__tests__/image-ref-limit.test.ts +78 -0
- package/src/__tests__/inpaint-region.test.ts +24 -0
- package/src/__tests__/input-field-map-parity.test.ts +30 -0
- package/src/__tests__/json-evaluator.test.ts +289 -0
- package/src/__tests__/json-path.test.ts +64 -0
- package/src/__tests__/lip-sync-pricing.test.ts +142 -0
- package/src/__tests__/list-merge.test.ts +96 -0
- package/src/__tests__/list-sort.test.ts +134 -0
- package/src/__tests__/llm-models.test.ts +445 -0
- package/src/__tests__/location-mention-slug.test.ts +246 -0
- package/src/__tests__/locations-coverage-critic-schema.test.ts +92 -0
- package/src/__tests__/loop-delimiter.test.ts +39 -0
- package/src/__tests__/lottie-slots.test.ts +245 -0
- package/src/__tests__/model-catalog-flux2-resolutions.test.ts +21 -0
- package/src/__tests__/model-constants-location.test.ts +20 -0
- package/src/__tests__/model-constants.test.ts +122 -0
- package/src/__tests__/model-tree.test.ts +86 -0
- package/src/__tests__/no-internal-imports.test.ts +25 -0
- package/src/__tests__/node-default-mappings.test.ts +175 -0
- package/src/__tests__/node-mappable-fields.test.ts +7 -0
- package/src/__tests__/node-preset-export.test.ts +46 -0
- package/src/__tests__/node-preset-extract.test.ts +166 -0
- package/src/__tests__/node-refs-canonical.test.ts +54 -0
- package/src/__tests__/node-refs-combine.test.ts +54 -0
- package/src/__tests__/node-refs.test.ts +187 -0
- package/src/__tests__/node-runtime-keys.test.ts +99 -0
- package/src/__tests__/object-aspect-defaults.test.ts +59 -0
- package/src/__tests__/parameter-node-value.test.ts +137 -0
- package/src/__tests__/pinterest-url.test.ts +35 -0
- package/src/__tests__/pipeline-chat.test.ts +110 -0
- package/src/__tests__/pipeline-validation.test.ts +98 -0
- package/src/__tests__/presentation-utils.test.ts +1137 -0
- package/src/__tests__/producer-types.test.ts +47 -0
- package/src/__tests__/prompt-length-limits.test.ts +156 -0
- package/src/__tests__/provider-directive-defaults.test.ts +46 -0
- package/src/__tests__/reduce-strategy-registry.test.ts +43 -0
- package/src/__tests__/reference-board-templates.test.ts +33 -0
- package/src/__tests__/reference-roles.test.ts +36 -0
- package/src/__tests__/resolve-field-mappings.test.ts +110 -0
- package/src/__tests__/resolve-pipeline-model.test.ts +76 -0
- package/src/__tests__/scene-node-types.test.ts +80 -0
- package/src/__tests__/scraper-actors.test.ts +67 -0
- package/src/__tests__/seedance-2-catalog.test.ts +18 -0
- package/src/__tests__/seedance-2-credit-id.test.ts +23 -0
- package/src/__tests__/seedance-aspects.test.ts +52 -0
- package/src/__tests__/selector.test.ts +1151 -0
- package/src/__tests__/settled-with-limit.test.ts +214 -0
- package/src/__tests__/social-media-specs.test.ts +129 -0
- package/src/__tests__/sub-workflow-validation.test.ts +88 -0
- package/src/__tests__/switchx-pricing.test.ts +28 -0
- package/src/__tests__/to-connected-references.test.ts +153 -0
- package/src/__tests__/utilities.test.ts +541 -0
- package/src/__tests__/video-analysis-pricing.test.ts +58 -0
- package/src/__tests__/video-analysis.test.ts +63 -0
- package/src/__tests__/video-audio-capability.test.ts +204 -0
- package/src/__tests__/video-gen-providers.test.ts +28 -0
- package/src/__tests__/video-mode-aliases.test.ts +150 -0
- package/src/__tests__/video-ref-limits.test.ts +72 -0
- package/src/__tests__/video-utils.test.ts +303 -0
- package/src/__tests__/voice-changer-models.test.ts +33 -0
- package/src/__tests__/workflow-export-strip.test.ts +46 -0
- package/src/ai-avatar-pricing.ts +213 -0
- package/src/ancestor-refs.ts +48 -0
- package/src/animals.ts +231 -0
- package/src/audio-crossfade-curves.ts +73 -0
- package/src/audio-fx-presets.ts +64 -0
- package/src/avatar-payload-validators.ts +214 -0
- package/src/caption-styles.ts +31 -0
- package/src/character-aspect-defaults.ts +102 -0
- package/src/character-facets.ts +37 -0
- package/src/character-mention-slug.ts +166 -0
- package/src/character-usage-mode.ts +109 -0
- package/src/character-variant-assets.ts +121 -0
- package/src/character-voice.ts +151 -0
- package/src/cinematic-avatar-pricing.ts +111 -0
- package/src/clone-utils.ts +46 -0
- package/src/combine-transitions.ts +482 -0
- package/src/community.ts +112 -0
- package/src/component-types.ts +127 -0
- package/src/condition-variables.ts +65 -0
- package/src/credit-estimators/index.ts +15 -0
- package/src/credit-estimators/video-utils.ts +165 -0
- package/src/credit-identifiers.ts +304 -0
- package/src/entity-approval-types.ts +31 -0
- package/src/entity-aspect-defaults.ts +82 -0
- package/src/entity-asset-types.ts +296 -0
- package/src/entity-image-handle.ts +36 -0
- package/src/extra-refs.ts +139 -0
- package/src/featured-entities.ts +136 -0
- package/src/film-pricing.ts +117 -0
- package/src/filter-condition.ts +250 -0
- package/src/flux2-pricing.ts +18 -0
- package/src/freecut-protocol.ts +52 -0
- package/src/furniture.ts +167 -0
- package/src/generate-text-items.ts +15 -0
- package/src/generated-results.ts +63 -0
- package/src/group-aggregation.ts +75 -0
- package/src/hint-graph-types.ts +18 -0
- package/src/i18n/action-fx.ar.ts +89 -0
- package/src/i18n/action-fx.de.ts +89 -0
- package/src/i18n/action-fx.es.ts +89 -0
- package/src/i18n/action-fx.fr.ts +89 -0
- package/src/i18n/action-fx.he.ts +89 -0
- package/src/i18n/action-fx.hi.ts +89 -0
- package/src/i18n/action-fx.ja.ts +89 -0
- package/src/i18n/action-fx.ko.ts +89 -0
- package/src/i18n/action-fx.pt-BR.ts +89 -0
- package/src/i18n/action-fx.ru.ts +89 -0
- package/src/i18n/action-fx.zh-CN.ts +89 -0
- package/src/i18n/aesthetic.ar.ts +170 -0
- package/src/i18n/aesthetic.de.ts +48 -0
- package/src/i18n/aesthetic.es.ts +169 -0
- package/src/i18n/aesthetic.fr.ts +52 -0
- package/src/i18n/aesthetic.he.ts +169 -0
- package/src/i18n/aesthetic.hi.ts +55 -0
- package/src/i18n/aesthetic.ja.ts +165 -0
- package/src/i18n/aesthetic.ko.ts +169 -0
- package/src/i18n/aesthetic.pt-BR.ts +148 -0
- package/src/i18n/aesthetic.ru.ts +155 -0
- package/src/i18n/aesthetic.zh-CN.ts +172 -0
- package/src/i18n/animals.ar.ts +145 -0
- package/src/i18n/animals.de.ts +144 -0
- package/src/i18n/animals.es.ts +158 -0
- package/src/i18n/animals.fr.ts +144 -0
- package/src/i18n/animals.he.ts +155 -0
- package/src/i18n/animals.hi.ts +157 -0
- package/src/i18n/animals.ja.ts +159 -0
- package/src/i18n/animals.ko.ts +159 -0
- package/src/i18n/animals.pt-BR.ts +157 -0
- package/src/i18n/animals.ru.ts +155 -0
- package/src/i18n/animals.zh-CN.ts +155 -0
- package/src/i18n/atmosphere.ar.ts +49 -0
- package/src/i18n/atmosphere.de.ts +47 -0
- package/src/i18n/atmosphere.es.ts +50 -0
- package/src/i18n/atmosphere.fr.ts +47 -0
- package/src/i18n/atmosphere.he.ts +47 -0
- package/src/i18n/atmosphere.hi.ts +49 -0
- package/src/i18n/atmosphere.ja.ts +49 -0
- package/src/i18n/atmosphere.ko.ts +49 -0
- package/src/i18n/atmosphere.pt-BR.ts +49 -0
- package/src/i18n/atmosphere.ru.ts +47 -0
- package/src/i18n/atmosphere.zh-CN.ts +152 -0
- package/src/i18n/backdrop.ar.ts +53 -0
- package/src/i18n/backdrop.de.ts +46 -0
- package/src/i18n/backdrop.es.ts +54 -0
- package/src/i18n/backdrop.fr.ts +52 -0
- package/src/i18n/backdrop.he.ts +51 -0
- package/src/i18n/backdrop.hi.ts +59 -0
- package/src/i18n/backdrop.ja.ts +61 -0
- package/src/i18n/backdrop.ko.ts +59 -0
- package/src/i18n/backdrop.pt-BR.ts +59 -0
- package/src/i18n/backdrop.ru.ts +57 -0
- package/src/i18n/backdrop.zh-CN.ts +57 -0
- package/src/i18n/camera-format.ar.ts +40 -0
- package/src/i18n/camera-format.de.ts +37 -0
- package/src/i18n/camera-format.es.ts +41 -0
- package/src/i18n/camera-format.fr.ts +37 -0
- package/src/i18n/camera-format.he.ts +37 -0
- package/src/i18n/camera-format.hi.ts +42 -0
- package/src/i18n/camera-format.ja.ts +37 -0
- package/src/i18n/camera-format.ko.ts +37 -0
- package/src/i18n/camera-format.pt-BR.ts +46 -0
- package/src/i18n/camera-format.ru.ts +42 -0
- package/src/i18n/camera-format.zh-CN.ts +42 -0
- package/src/i18n/camera-motions.ar.ts +86 -0
- package/src/i18n/camera-motions.de.ts +74 -0
- package/src/i18n/camera-motions.es.ts +86 -0
- package/src/i18n/camera-motions.fr.ts +86 -0
- package/src/i18n/camera-motions.he.ts +86 -0
- package/src/i18n/camera-motions.hi.ts +99 -0
- package/src/i18n/camera-motions.ja.ts +99 -0
- package/src/i18n/camera-motions.ko.ts +99 -0
- package/src/i18n/camera-motions.pt-BR.ts +99 -0
- package/src/i18n/camera-motions.ru.ts +99 -0
- package/src/i18n/camera-motions.zh-CN.ts +99 -0
- package/src/i18n/character-fx.ar.ts +72 -0
- package/src/i18n/character-fx.de.ts +72 -0
- package/src/i18n/character-fx.es.ts +72 -0
- package/src/i18n/character-fx.fr.ts +72 -0
- package/src/i18n/character-fx.he.ts +72 -0
- package/src/i18n/character-fx.hi.ts +72 -0
- package/src/i18n/character-fx.ja.ts +72 -0
- package/src/i18n/character-fx.ko.ts +72 -0
- package/src/i18n/character-fx.pt-BR.ts +72 -0
- package/src/i18n/character-fx.ru.ts +72 -0
- package/src/i18n/character-fx.zh-CN.ts +72 -0
- package/src/i18n/color-look.ar.ts +50 -0
- package/src/i18n/color-look.de.ts +47 -0
- package/src/i18n/color-look.es.ts +50 -0
- package/src/i18n/color-look.fr.ts +50 -0
- package/src/i18n/color-look.he.ts +49 -0
- package/src/i18n/color-look.hi.ts +52 -0
- package/src/i18n/color-look.ja.ts +54 -0
- package/src/i18n/color-look.ko.ts +54 -0
- package/src/i18n/color-look.pt-BR.ts +54 -0
- package/src/i18n/color-look.ru.ts +52 -0
- package/src/i18n/color-look.zh-CN.ts +53 -0
- package/src/i18n/composition-effects.ar.ts +26 -0
- package/src/i18n/composition-effects.de.ts +25 -0
- package/src/i18n/composition-effects.es.ts +27 -0
- package/src/i18n/composition-effects.fr.ts +25 -0
- package/src/i18n/composition-effects.he.ts +25 -0
- package/src/i18n/composition-effects.hi.ts +27 -0
- package/src/i18n/composition-effects.ja.ts +27 -0
- package/src/i18n/composition-effects.ko.ts +27 -0
- package/src/i18n/composition-effects.pt-BR.ts +25 -0
- package/src/i18n/composition-effects.ru.ts +82 -0
- package/src/i18n/composition-effects.zh-CN.ts +25 -0
- package/src/i18n/era.ar.ts +37 -0
- package/src/i18n/era.de.ts +34 -0
- package/src/i18n/era.es.ts +37 -0
- package/src/i18n/era.fr.ts +37 -0
- package/src/i18n/era.he.ts +36 -0
- package/src/i18n/era.hi.ts +39 -0
- package/src/i18n/era.ja.ts +41 -0
- package/src/i18n/era.ko.ts +39 -0
- package/src/i18n/era.pt-BR.ts +41 -0
- package/src/i18n/era.ru.ts +121 -0
- package/src/i18n/era.zh-CN.ts +39 -0
- package/src/i18n/exposure-settings.ar.ts +28 -0
- package/src/i18n/exposure-settings.de.ts +26 -0
- package/src/i18n/exposure-settings.es.ts +28 -0
- package/src/i18n/exposure-settings.fr.ts +29 -0
- package/src/i18n/exposure-settings.he.ts +28 -0
- package/src/i18n/exposure-settings.hi.ts +31 -0
- package/src/i18n/exposure-settings.ja.ts +31 -0
- package/src/i18n/exposure-settings.ko.ts +31 -0
- package/src/i18n/exposure-settings.pt-BR.ts +31 -0
- package/src/i18n/exposure-settings.ru.ts +31 -0
- package/src/i18n/exposure-settings.zh-CN.ts +31 -0
- package/src/i18n/framing.ar.ts +78 -0
- package/src/i18n/framing.de.ts +73 -0
- package/src/i18n/framing.es.ts +78 -0
- package/src/i18n/framing.fr.ts +78 -0
- package/src/i18n/framing.he.ts +77 -0
- package/src/i18n/framing.hi.ts +82 -0
- package/src/i18n/framing.ja.ts +84 -0
- package/src/i18n/framing.ko.ts +82 -0
- package/src/i18n/framing.pt-BR.ts +84 -0
- package/src/i18n/framing.ru.ts +82 -0
- package/src/i18n/framing.zh-CN.ts +82 -0
- package/src/i18n/furniture.ar.ts +92 -0
- package/src/i18n/furniture.de.ts +93 -0
- package/src/i18n/furniture.es.ts +101 -0
- package/src/i18n/furniture.fr.ts +93 -0
- package/src/i18n/furniture.he.ts +101 -0
- package/src/i18n/furniture.hi.ts +101 -0
- package/src/i18n/furniture.ja.ts +101 -0
- package/src/i18n/furniture.ko.ts +101 -0
- package/src/i18n/furniture.pt-BR.ts +101 -0
- package/src/i18n/furniture.ru.ts +101 -0
- package/src/i18n/furniture.zh-CN.ts +101 -0
- package/src/i18n/held-prop.ar.ts +74 -0
- package/src/i18n/held-prop.de.ts +65 -0
- package/src/i18n/held-prop.es.ts +83 -0
- package/src/i18n/held-prop.fr.ts +74 -0
- package/src/i18n/held-prop.he.ts +73 -0
- package/src/i18n/held-prop.hi.ts +82 -0
- package/src/i18n/held-prop.ja.ts +84 -0
- package/src/i18n/held-prop.ko.ts +84 -0
- package/src/i18n/held-prop.pt-BR.ts +84 -0
- package/src/i18n/held-prop.ru.ts +82 -0
- package/src/i18n/held-prop.zh-CN.ts +82 -0
- package/src/i18n/index.ts +208 -0
- package/src/i18n/instrumentation.ar.ts +195 -0
- package/src/i18n/instrumentation.de.ts +195 -0
- package/src/i18n/instrumentation.es.ts +195 -0
- package/src/i18n/instrumentation.fr.ts +195 -0
- package/src/i18n/instrumentation.he.ts +195 -0
- package/src/i18n/instrumentation.hi.ts +195 -0
- package/src/i18n/instrumentation.ja.ts +195 -0
- package/src/i18n/instrumentation.ko.ts +195 -0
- package/src/i18n/instrumentation.pt-BR.ts +195 -0
- package/src/i18n/instrumentation.ru.ts +195 -0
- package/src/i18n/instrumentation.zh-CN.ts +195 -0
- package/src/i18n/lens.ar.ts +24 -0
- package/src/i18n/lens.de.ts +24 -0
- package/src/i18n/lens.es.ts +24 -0
- package/src/i18n/lens.fr.ts +24 -0
- package/src/i18n/lens.he.ts +24 -0
- package/src/i18n/lens.hi.ts +24 -0
- package/src/i18n/lens.ja.ts +24 -0
- package/src/i18n/lens.ko.ts +24 -0
- package/src/i18n/lens.pt-BR.ts +24 -0
- package/src/i18n/lens.ru.ts +25 -0
- package/src/i18n/lens.zh-CN.ts +24 -0
- package/src/i18n/lighting.ar.ts +86 -0
- package/src/i18n/lighting.de.ts +84 -0
- package/src/i18n/lighting.es.ts +89 -0
- package/src/i18n/lighting.fr.ts +84 -0
- package/src/i18n/lighting.he.ts +88 -0
- package/src/i18n/lighting.hi.ts +90 -0
- package/src/i18n/lighting.ja.ts +90 -0
- package/src/i18n/lighting.ko.ts +90 -0
- package/src/i18n/lighting.pt-BR.ts +90 -0
- package/src/i18n/lighting.ru.ts +88 -0
- package/src/i18n/lighting.zh-CN.ts +92 -0
- package/src/i18n/loop-subject.ar.ts +39 -0
- package/src/i18n/loop-subject.de.ts +39 -0
- package/src/i18n/loop-subject.es.ts +39 -0
- package/src/i18n/loop-subject.fr.ts +39 -0
- package/src/i18n/loop-subject.he.ts +39 -0
- package/src/i18n/loop-subject.hi.ts +39 -0
- package/src/i18n/loop-subject.ja.ts +39 -0
- package/src/i18n/loop-subject.ko.ts +39 -0
- package/src/i18n/loop-subject.pt-BR.ts +39 -0
- package/src/i18n/loop-subject.ru.ts +39 -0
- package/src/i18n/loop-subject.zh-CN.ts +39 -0
- package/src/i18n/materials.ar.ts +80 -0
- package/src/i18n/materials.de.ts +80 -0
- package/src/i18n/materials.es.ts +87 -0
- package/src/i18n/materials.fr.ts +80 -0
- package/src/i18n/materials.he.ts +86 -0
- package/src/i18n/materials.hi.ts +86 -0
- package/src/i18n/materials.ja.ts +88 -0
- package/src/i18n/materials.ko.ts +88 -0
- package/src/i18n/materials.pt-BR.ts +88 -0
- package/src/i18n/materials.ru.ts +86 -0
- package/src/i18n/materials.zh-CN.ts +86 -0
- package/src/i18n/mood.ar.ts +61 -0
- package/src/i18n/mood.de.ts +61 -0
- package/src/i18n/mood.es.ts +66 -0
- package/src/i18n/mood.fr.ts +61 -0
- package/src/i18n/mood.he.ts +64 -0
- package/src/i18n/mood.hi.ts +66 -0
- package/src/i18n/mood.ja.ts +66 -0
- package/src/i18n/mood.ko.ts +66 -0
- package/src/i18n/mood.pt-BR.ts +64 -0
- package/src/i18n/mood.ru.ts +64 -0
- package/src/i18n/mood.zh-CN.ts +64 -0
- package/src/i18n/music-genre.ar.ts +302 -0
- package/src/i18n/music-genre.de.ts +302 -0
- package/src/i18n/music-genre.es.ts +302 -0
- package/src/i18n/music-genre.fr.ts +302 -0
- package/src/i18n/music-genre.he.ts +302 -0
- package/src/i18n/music-genre.hi.ts +302 -0
- package/src/i18n/music-genre.ja.ts +302 -0
- package/src/i18n/music-genre.ko.ts +302 -0
- package/src/i18n/music-genre.pt-BR.ts +302 -0
- package/src/i18n/music-genre.ru.ts +302 -0
- package/src/i18n/music-genre.zh-CN.ts +302 -0
- package/src/i18n/music-mood.ar.ts +75 -0
- package/src/i18n/music-mood.de.ts +75 -0
- package/src/i18n/music-mood.es.ts +75 -0
- package/src/i18n/music-mood.fr.ts +75 -0
- package/src/i18n/music-mood.he.ts +75 -0
- package/src/i18n/music-mood.hi.ts +75 -0
- package/src/i18n/music-mood.ja.ts +75 -0
- package/src/i18n/music-mood.ko.ts +75 -0
- package/src/i18n/music-mood.pt-BR.ts +75 -0
- package/src/i18n/music-mood.ru.ts +75 -0
- package/src/i18n/music-mood.zh-CN.ts +75 -0
- package/src/i18n/person.ar.ts +638 -0
- package/src/i18n/person.de.ts +629 -0
- package/src/i18n/person.es.ts +645 -0
- package/src/i18n/person.fr.ts +621 -0
- package/src/i18n/person.he.ts +656 -0
- package/src/i18n/person.hi.ts +654 -0
- package/src/i18n/person.ja.ts +662 -0
- package/src/i18n/person.ko.ts +662 -0
- package/src/i18n/person.pt-BR.ts +656 -0
- package/src/i18n/person.ru.ts +656 -0
- package/src/i18n/person.zh-CN.ts +656 -0
- package/src/i18n/photo-genre.ar.ts +58 -0
- package/src/i18n/photo-genre.de.ts +52 -0
- package/src/i18n/photo-genre.es.ts +64 -0
- package/src/i18n/photo-genre.fr.ts +58 -0
- package/src/i18n/photo-genre.he.ts +62 -0
- package/src/i18n/photo-genre.hi.ts +63 -0
- package/src/i18n/photo-genre.ja.ts +65 -0
- package/src/i18n/photo-genre.ko.ts +63 -0
- package/src/i18n/photo-genre.pt-BR.ts +65 -0
- package/src/i18n/photo-genre.ru.ts +63 -0
- package/src/i18n/photo-genre.zh-CN.ts +63 -0
- package/src/i18n/photographer.ar.ts +75 -0
- package/src/i18n/photographer.de.ts +74 -0
- package/src/i18n/photographer.es.ts +77 -0
- package/src/i18n/photographer.fr.ts +77 -0
- package/src/i18n/photographer.he.ts +78 -0
- package/src/i18n/photographer.hi.ts +80 -0
- package/src/i18n/photographer.ja.ts +84 -0
- package/src/i18n/photographer.ko.ts +84 -0
- package/src/i18n/photographer.pt-BR.ts +83 -0
- package/src/i18n/photographer.ru.ts +78 -0
- package/src/i18n/photographer.zh-CN.ts +78 -0
- package/src/i18n/pose.ar.ts +95 -0
- package/src/i18n/pose.de.ts +96 -0
- package/src/i18n/pose.es.ts +104 -0
- package/src/i18n/pose.fr.ts +96 -0
- package/src/i18n/pose.he.ts +104 -0
- package/src/i18n/pose.hi.ts +104 -0
- package/src/i18n/pose.ja.ts +104 -0
- package/src/i18n/pose.ko.ts +104 -0
- package/src/i18n/pose.pt-BR.ts +104 -0
- package/src/i18n/pose.ru.ts +104 -0
- package/src/i18n/pose.zh-CN.ts +104 -0
- package/src/i18n/post-process-effects.ar.ts +25 -0
- package/src/i18n/post-process-effects.de.ts +24 -0
- package/src/i18n/post-process-effects.es.ts +26 -0
- package/src/i18n/post-process-effects.fr.ts +24 -0
- package/src/i18n/post-process-effects.he.ts +24 -0
- package/src/i18n/post-process-effects.hi.ts +26 -0
- package/src/i18n/post-process-effects.ja.ts +26 -0
- package/src/i18n/post-process-effects.ko.ts +26 -0
- package/src/i18n/post-process-effects.pt-BR.ts +24 -0
- package/src/i18n/post-process-effects.ru.ts +24 -0
- package/src/i18n/post-process-effects.zh-CN.ts +24 -0
- package/src/i18n/render-quality.ar.ts +34 -0
- package/src/i18n/render-quality.de.ts +30 -0
- package/src/i18n/render-quality.es.ts +35 -0
- package/src/i18n/render-quality.fr.ts +34 -0
- package/src/i18n/render-quality.he.ts +33 -0
- package/src/i18n/render-quality.hi.ts +39 -0
- package/src/i18n/render-quality.ja.ts +39 -0
- package/src/i18n/render-quality.ko.ts +39 -0
- package/src/i18n/render-quality.pt-BR.ts +37 -0
- package/src/i18n/render-quality.ru.ts +37 -0
- package/src/i18n/render-quality.zh-CN.ts +37 -0
- package/src/i18n/seasons.ar.ts +10 -0
- package/src/i18n/seasons.de.ts +10 -0
- package/src/i18n/seasons.es.ts +10 -0
- package/src/i18n/seasons.fr.ts +10 -0
- package/src/i18n/seasons.he.ts +10 -0
- package/src/i18n/seasons.hi.ts +10 -0
- package/src/i18n/seasons.ja.ts +10 -0
- package/src/i18n/seasons.ko.ts +10 -0
- package/src/i18n/seasons.pt-BR.ts +10 -0
- package/src/i18n/seasons.ru.ts +10 -0
- package/src/i18n/seasons.zh-CN.ts +10 -0
- package/src/i18n/setting.ar.ts +73 -0
- package/src/i18n/setting.de.ts +73 -0
- package/src/i18n/setting.es.ts +77 -0
- package/src/i18n/setting.fr.ts +73 -0
- package/src/i18n/setting.he.ts +76 -0
- package/src/i18n/setting.hi.ts +76 -0
- package/src/i18n/setting.ja.ts +78 -0
- package/src/i18n/setting.ko.ts +76 -0
- package/src/i18n/setting.pt-BR.ts +78 -0
- package/src/i18n/setting.ru.ts +76 -0
- package/src/i18n/setting.zh-CN.ts +76 -0
- package/src/i18n/style.ar.ts +55 -0
- package/src/i18n/style.de.ts +54 -0
- package/src/i18n/style.es.ts +54 -0
- package/src/i18n/style.fr.ts +54 -0
- package/src/i18n/style.he.ts +54 -0
- package/src/i18n/style.hi.ts +54 -0
- package/src/i18n/style.ja.ts +54 -0
- package/src/i18n/style.ko.ts +54 -0
- package/src/i18n/style.pt-BR.ts +56 -0
- package/src/i18n/style.ru.ts +54 -0
- package/src/i18n/style.zh-CN.ts +54 -0
- package/src/i18n/styling.ar.ts +295 -0
- package/src/i18n/styling.de.ts +285 -0
- package/src/i18n/styling.es.ts +317 -0
- package/src/i18n/styling.fr.ts +285 -0
- package/src/i18n/styling.he.ts +307 -0
- package/src/i18n/styling.hi.ts +323 -0
- package/src/i18n/styling.ja.ts +317 -0
- package/src/i18n/styling.ko.ts +311 -0
- package/src/i18n/styling.pt-BR.ts +301 -0
- package/src/i18n/styling.ru.ts +301 -0
- package/src/i18n/styling.zh-CN.ts +307 -0
- package/src/i18n/temporal.ar.ts +31 -0
- package/src/i18n/temporal.de.ts +24 -0
- package/src/i18n/temporal.es.ts +27 -0
- package/src/i18n/temporal.fr.ts +28 -0
- package/src/i18n/temporal.he.ts +31 -0
- package/src/i18n/temporal.hi.ts +31 -0
- package/src/i18n/temporal.ja.ts +31 -0
- package/src/i18n/temporal.ko.ts +31 -0
- package/src/i18n/temporal.pt-BR.ts +31 -0
- package/src/i18n/temporal.ru.ts +31 -0
- package/src/i18n/temporal.zh-CN.ts +31 -0
- package/src/i18n/transitions.ar.ts +97 -0
- package/src/i18n/transitions.de.ts +97 -0
- package/src/i18n/transitions.es.ts +97 -0
- package/src/i18n/transitions.fr.ts +97 -0
- package/src/i18n/transitions.he.ts +97 -0
- package/src/i18n/transitions.hi.ts +97 -0
- package/src/i18n/transitions.ja.ts +97 -0
- package/src/i18n/transitions.ko.ts +97 -0
- package/src/i18n/transitions.pt-BR.ts +97 -0
- package/src/i18n/transitions.ru.ts +97 -0
- package/src/i18n/transitions.zh-CN.ts +97 -0
- package/src/i18n/types.ts +158 -0
- package/src/i18n/vehicles.ar.ts +137 -0
- package/src/i18n/vehicles.de.ts +125 -0
- package/src/i18n/vehicles.es.ts +138 -0
- package/src/i18n/vehicles.fr.ts +125 -0
- package/src/i18n/vehicles.he.ts +136 -0
- package/src/i18n/vehicles.hi.ts +138 -0
- package/src/i18n/vehicles.ja.ts +138 -0
- package/src/i18n/vehicles.ko.ts +138 -0
- package/src/i18n/vehicles.pt-BR.ts +136 -0
- package/src/i18n/vehicles.ru.ts +136 -0
- package/src/i18n/vehicles.zh-CN.ts +136 -0
- package/src/i18n/voice-character.ar.ts +126 -0
- package/src/i18n/voice-character.de.ts +126 -0
- package/src/i18n/voice-character.es.ts +126 -0
- package/src/i18n/voice-character.fr.ts +126 -0
- package/src/i18n/voice-character.he.ts +126 -0
- package/src/i18n/voice-character.hi.ts +126 -0
- package/src/i18n/voice-character.ja.ts +126 -0
- package/src/i18n/voice-character.ko.ts +126 -0
- package/src/i18n/voice-character.pt-BR.ts +126 -0
- package/src/i18n/voice-character.ru.ts +126 -0
- package/src/i18n/voice-character.zh-CN.ts +126 -0
- package/src/i18n/voice-delivery.ar.ts +77 -0
- package/src/i18n/voice-delivery.de.ts +77 -0
- package/src/i18n/voice-delivery.es.ts +77 -0
- package/src/i18n/voice-delivery.fr.ts +77 -0
- package/src/i18n/voice-delivery.he.ts +77 -0
- package/src/i18n/voice-delivery.hi.ts +77 -0
- package/src/i18n/voice-delivery.ja.ts +77 -0
- package/src/i18n/voice-delivery.ko.ts +77 -0
- package/src/i18n/voice-delivery.pt-BR.ts +77 -0
- package/src/i18n/voice-delivery.ru.ts +77 -0
- package/src/i18n/voice-delivery.zh-CN.ts +77 -0
- package/src/i18n/weapons.ar.ts +115 -0
- package/src/i18n/weapons.de.ts +114 -0
- package/src/i18n/weapons.es.ts +116 -0
- package/src/i18n/weapons.fr.ts +103 -0
- package/src/i18n/weapons.he.ts +114 -0
- package/src/i18n/weapons.hi.ts +116 -0
- package/src/i18n/weapons.ja.ts +116 -0
- package/src/i18n/weapons.ko.ts +116 -0
- package/src/i18n/weapons.pt-BR.ts +114 -0
- package/src/i18n/weapons.ru.ts +114 -0
- package/src/i18n/weapons.zh-CN.ts +114 -0
- package/src/image-critic-node.ts +61 -0
- package/src/image-critic-types.ts +40 -0
- package/src/index.ts +866 -0
- package/src/inject-upstream.ts +15 -0
- package/src/inpaint-region.ts +51 -0
- package/src/json-evaluator.ts +732 -0
- package/src/json-path.ts +52 -0
- package/src/lip-sync-pricing.ts +90 -0
- package/src/list-merge.ts +40 -0
- package/src/list-sort.ts +122 -0
- package/src/list-source-resolver.ts +50 -0
- package/src/llm-models.ts +245 -0
- package/src/location-mention-slug.ts +291 -0
- package/src/location-preset-catalog-map.ts +128 -0
- package/src/loop-delimiter.ts +73 -0
- package/src/lora-routing.ts +95 -0
- package/src/lottie-overlay-catalog.ts +175 -0
- package/src/lottie-slots.ts +309 -0
- package/src/model-catalog.ts +2386 -0
- package/src/model-constants.ts +1910 -0
- package/src/model-tree.ts +120 -0
- package/src/monetization.ts +15 -0
- package/src/multi-pick.ts +42 -0
- package/src/node-default-mappings.ts +262 -0
- package/src/node-mappable-fields.ts +70 -0
- package/src/node-preset-export.ts +56 -0
- package/src/node-preset-extract.ts +147 -0
- package/src/node-refs.ts +253 -0
- package/src/node-runtime-keys.ts +117 -0
- package/src/object-aspect-defaults.ts +72 -0
- package/src/object-picker-types.ts +22 -0
- package/src/parameter-node-value.ts +284 -0
- package/src/pinterest-url.ts +28 -0
- package/src/pipeline-chat.ts +72 -0
- package/src/pipeline-defaults.ts +318 -0
- package/src/pipeline-events.ts +162 -0
- package/src/pipeline-state-types.ts +196 -0
- package/src/pipeline-types.ts +647 -0
- package/src/pipeline-validation.ts +87 -0
- package/src/presentation-types.ts +23 -0
- package/src/presentation-utils.ts +397 -0
- package/src/producer-types.ts +188 -0
- package/src/progress-curve.ts +99 -0
- package/src/provider-directive-defaults.ts +69 -0
- package/src/reduce-strategy-registry.ts +121 -0
- package/src/reference-board-templates.ts +81 -0
- package/src/reference-roles.ts +153 -0
- package/src/reference-sheet/__tests__/board-to-column.test.ts +20 -0
- package/src/reference-sheet/__tests__/catalog.test.ts +50 -0
- package/src/reference-sheet/__tests__/cost.test.ts +38 -0
- package/src/reference-sheet/__tests__/no-drift.test.ts +28 -0
- package/src/reference-sheet/__tests__/panel-plan.test.ts +61 -0
- package/src/reference-sheet/__tests__/panel-prompts.test.ts +17 -0
- package/src/reference-sheet/__tests__/plan-generation.test.ts +72 -0
- package/src/reference-sheet/__tests__/presets.test.ts +54 -0
- package/src/reference-sheet/__tests__/types.test.ts +24 -0
- package/src/reference-sheet/catalog.ts +144 -0
- package/src/reference-sheet/cost.ts +37 -0
- package/src/reference-sheet/credit-id.ts +7 -0
- package/src/reference-sheet/index.ts +8 -0
- package/src/reference-sheet/panel-plan.ts +65 -0
- package/src/reference-sheet/panel-prompts.ts +14 -0
- package/src/reference-sheet/plan-generation.ts +79 -0
- package/src/reference-sheet/presets.ts +77 -0
- package/src/reference-sheet/types.ts +92 -0
- package/src/repeat-types.ts +97 -0
- package/src/resolve-field-mappings.ts +119 -0
- package/src/route-filter.ts +80 -0
- package/src/scene-helper-types.ts +249 -0
- package/src/scene-node-types.ts +281 -0
- package/src/scraper-actors.ts +57 -0
- package/src/scraper-output-schemas.ts +25 -0
- package/src/seasons.ts +45 -0
- package/src/selector.ts +864 -0
- package/src/settled-with-limit.ts +42 -0
- package/src/shot-element.ts +43 -0
- package/src/social-media-specs.ts +121 -0
- package/src/social-post.ts +24 -0
- package/src/sub-workflow-validation.ts +78 -0
- package/src/supported-fonts.ts +40 -0
- package/src/surround.ts +113 -0
- package/src/switchx-pricing.ts +33 -0
- package/src/text-separators.ts +59 -0
- package/src/to-connected-references.ts +77 -0
- package/src/types.ts +272 -0
- package/src/variant-job-id.ts +17 -0
- package/src/vehicles.ts +213 -0
- package/src/video-analysis-pricing.ts +74 -0
- package/src/video-analysis.ts +127 -0
- package/src/video-duration.ts +25 -0
- package/src/voice-changer-models.ts +36 -0
- package/src/voice-matcher-types.ts +37 -0
- package/src/voices.ts +92 -0
- package/src/weapons.ts +190 -0
- package/src/workflow-export.ts +153 -0
|
@@ -0,0 +1,732 @@
|
|
|
1
|
+
// --- Types ---
|
|
2
|
+
|
|
3
|
+
export type FilterOperator =
|
|
4
|
+
| "equals" | "not_equals"
|
|
5
|
+
| "contains" | "not_contains"
|
|
6
|
+
| "starts_with" | "ends_with"
|
|
7
|
+
| "greater_than" | "less_than"
|
|
8
|
+
| "is_empty" | "is_not_empty"
|
|
9
|
+
| "matches_regex"
|
|
10
|
+
| "in_list"
|
|
11
|
+
|
|
12
|
+
export interface JsonFilter {
|
|
13
|
+
id: string
|
|
14
|
+
field: string
|
|
15
|
+
operator: FilterOperator
|
|
16
|
+
value: string | string[]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type JsonEvalResult =
|
|
20
|
+
| { ok: true; value: unknown }
|
|
21
|
+
| { ok: false; error: string }
|
|
22
|
+
|
|
23
|
+
// --- Helpers ---
|
|
24
|
+
|
|
25
|
+
const BARE_IDENT = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/
|
|
26
|
+
|
|
27
|
+
function escapeString(v: string): string {
|
|
28
|
+
return v.replace(/\\/g, "\\\\").replace(/"/g, '\\"')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function fieldRef(field: string): string {
|
|
32
|
+
return BARE_IDENT.test(field) ? `.${field}` : `.["${escapeString(field)}"]`
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function coerceValue(v: string): string {
|
|
36
|
+
const trimmed = v.trim()
|
|
37
|
+
if (trimmed === "" || trimmed === "Infinity" || trimmed === "-Infinity" || trimmed === "NaN") return `"${trimmed}"`
|
|
38
|
+
const n = Number(trimmed)
|
|
39
|
+
return Number.isFinite(n) ? String(n) : `"${escapeString(trimmed)}"`
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function buildFilterExpr(f: JsonFilter): string {
|
|
43
|
+
const ref = fieldRef(f.field)
|
|
44
|
+
switch (f.operator) {
|
|
45
|
+
case "equals": return `select(${ref} == ${coerceValue(f.value as string)})`
|
|
46
|
+
case "not_equals": return `select(${ref} != ${coerceValue(f.value as string)})`
|
|
47
|
+
case "contains": return `select(${ref} | contains("${escapeString(f.value as string)}"))`
|
|
48
|
+
case "not_contains": return `select(${ref} | contains("${escapeString(f.value as string)}") | not)`
|
|
49
|
+
case "starts_with": return `select(${ref} | startswith("${escapeString(f.value as string)}"))`
|
|
50
|
+
case "ends_with": return `select(${ref} | endswith("${escapeString(f.value as string)}"))`
|
|
51
|
+
case "greater_than": {
|
|
52
|
+
const n = Number((f.value as string).trim())
|
|
53
|
+
if (!Number.isFinite(n)) return `select(${ref} > 0)`
|
|
54
|
+
return `select(${ref} > ${n})`
|
|
55
|
+
}
|
|
56
|
+
case "less_than": {
|
|
57
|
+
const n = Number((f.value as string).trim())
|
|
58
|
+
if (!Number.isFinite(n)) return `select(${ref} < 0)`
|
|
59
|
+
return `select(${ref} < ${n})`
|
|
60
|
+
}
|
|
61
|
+
case "is_empty": return `select(${ref} == null or ${ref} == "")`
|
|
62
|
+
case "is_not_empty": return `select(${ref} != null and ${ref} != "")`
|
|
63
|
+
case "matches_regex": return `select(${ref} | test("${escapeString(f.value as string)}"))`
|
|
64
|
+
case "in_list": {
|
|
65
|
+
// Tolerate string form: users can switch operator to "in_list" while the
|
|
66
|
+
// previous operator's string value is still in place. Comma-split so the
|
|
67
|
+
// config panel's "a, b, c" shape works before it's persisted as an array.
|
|
68
|
+
const raw = Array.isArray(f.value)
|
|
69
|
+
? f.value
|
|
70
|
+
: String(f.value ?? "").split(",").map((s) => s.trim()).filter(Boolean)
|
|
71
|
+
if (raw.length === 0) return `select(false)`
|
|
72
|
+
const items = raw.map((v) => `${ref} == ${coerceValue(v)}`)
|
|
73
|
+
return `select(${items.join(" or ")})`
|
|
74
|
+
}
|
|
75
|
+
default: return `select(true)`
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// --- Public API ---
|
|
80
|
+
|
|
81
|
+
export function buildExpressionFromVisual(config: {
|
|
82
|
+
inputPath?: string
|
|
83
|
+
filters?: JsonFilter[]
|
|
84
|
+
projections?: string[]
|
|
85
|
+
}): string {
|
|
86
|
+
const { inputPath, filters = [], projections = [] } = config
|
|
87
|
+
const hasFilters = filters.length > 0
|
|
88
|
+
const hasProjections = projections.length > 0
|
|
89
|
+
const hasPath = !!inputPath && inputPath.trim().length > 0
|
|
90
|
+
|
|
91
|
+
// Stage 1: path
|
|
92
|
+
let path = hasPath ? `.${inputPath}` : "."
|
|
93
|
+
|
|
94
|
+
// Append [] if filters or projections exist
|
|
95
|
+
if (hasFilters || hasProjections) {
|
|
96
|
+
path += "[]"
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// If nothing configured, return identity
|
|
100
|
+
if (!hasPath && !hasFilters && !hasProjections) return "."
|
|
101
|
+
|
|
102
|
+
const parts: string[] = [path]
|
|
103
|
+
|
|
104
|
+
// Stage 2: filters (AND via separate select pipes)
|
|
105
|
+
for (const f of filters) {
|
|
106
|
+
parts.push(buildFilterExpr(f))
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Stage 3: projections
|
|
110
|
+
if (hasProjections) {
|
|
111
|
+
const fields = projections.map((p) => {
|
|
112
|
+
if (BARE_IDENT.test(p)) return p
|
|
113
|
+
return `"${p}": .["${p}"]`
|
|
114
|
+
})
|
|
115
|
+
parts.push(`{${fields.join(", ")}}`)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return parts.join(" | ")
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// --- Tokenizer ---
|
|
122
|
+
|
|
123
|
+
type TokType =
|
|
124
|
+
| "DOT" | "PIPE" | "LBRACE" | "RBRACE" | "LBRACKET" | "RBRACKET"
|
|
125
|
+
| "LPAREN" | "RPAREN" | "COMMA" | "COLON"
|
|
126
|
+
| "EMPTY_ITER" // []
|
|
127
|
+
| "INDEX" // [N] or [-N]
|
|
128
|
+
| "BRACKET_KEY" // ["str"]
|
|
129
|
+
| "IDENT" // bare identifier or keyword
|
|
130
|
+
| "STRING" // "..."
|
|
131
|
+
| "NUMBER" // 42 / 3.14
|
|
132
|
+
| "OP" // == != > < >= <=
|
|
133
|
+
|
|
134
|
+
interface Token { type: TokType; value: string | number }
|
|
135
|
+
|
|
136
|
+
function tokenize(expr: string): Token[] {
|
|
137
|
+
const tokens: Token[] = []
|
|
138
|
+
let i = 0
|
|
139
|
+
|
|
140
|
+
while (i < expr.length) {
|
|
141
|
+
// whitespace
|
|
142
|
+
if (/\s/.test(expr[i])) { i++; continue }
|
|
143
|
+
|
|
144
|
+
const ch = expr[i]
|
|
145
|
+
|
|
146
|
+
// single-char structural
|
|
147
|
+
if (ch === ".") { tokens.push({ type: "DOT", value: "." }); i++; continue }
|
|
148
|
+
if (ch === "|") { tokens.push({ type: "PIPE", value: "|" }); i++; continue }
|
|
149
|
+
if (ch === "{") { tokens.push({ type: "LBRACE", value: "{" }); i++; continue }
|
|
150
|
+
if (ch === "}") { tokens.push({ type: "RBRACE", value: "}" }); i++; continue }
|
|
151
|
+
if (ch === "(") { tokens.push({ type: "LPAREN", value: "(" }); i++; continue }
|
|
152
|
+
if (ch === ")") { tokens.push({ type: "RPAREN", value: ")" }); i++; continue }
|
|
153
|
+
if (ch === ",") { tokens.push({ type: "COMMA", value: "," }); i++; continue }
|
|
154
|
+
if (ch === ":") { tokens.push({ type: "COLON", value: ":" }); i++; continue }
|
|
155
|
+
|
|
156
|
+
// bracket forms: [], [N], [-N], ["str"]
|
|
157
|
+
if (ch === "[") {
|
|
158
|
+
const rest = expr.slice(i)
|
|
159
|
+
// ["str"]
|
|
160
|
+
const bkStr = rest.match(/^\["((?:[^"\\]|\\.)*)"\]/)
|
|
161
|
+
if (bkStr) {
|
|
162
|
+
const unescaped = bkStr[1].replace(/\\"/g, '"').replace(/\\\\/g, "\\").replace(/\\n/g, "\n").replace(/\\t/g, "\t")
|
|
163
|
+
tokens.push({ type: "BRACKET_KEY", value: unescaped })
|
|
164
|
+
i += bkStr[0].length; continue
|
|
165
|
+
}
|
|
166
|
+
// []
|
|
167
|
+
const bkEmpty = rest.match(/^\[\]/)
|
|
168
|
+
if (bkEmpty) {
|
|
169
|
+
tokens.push({ type: "EMPTY_ITER", value: "[]" })
|
|
170
|
+
i += 2; continue
|
|
171
|
+
}
|
|
172
|
+
// [-N] or [N]
|
|
173
|
+
const bkIdx = rest.match(/^\[-?(\d+)\]/)
|
|
174
|
+
if (bkIdx) {
|
|
175
|
+
const raw = rest.match(/^\[(-?\d+)\]/)!
|
|
176
|
+
tokens.push({ type: "INDEX", value: parseInt(raw[1], 10) })
|
|
177
|
+
i += raw[0].length; continue
|
|
178
|
+
}
|
|
179
|
+
// standalone [ (for parser completeness — treated as error later)
|
|
180
|
+
tokens.push({ type: "LBRACKET", value: "[" }); i++; continue
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// two-char operators
|
|
184
|
+
if (i + 1 < expr.length) {
|
|
185
|
+
const two = expr.slice(i, i + 2)
|
|
186
|
+
if (two === "==" || two === "!=" || two === ">=" || two === "<=") {
|
|
187
|
+
tokens.push({ type: "OP", value: two }); i += 2; continue
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (ch === ">" || ch === "<") {
|
|
191
|
+
tokens.push({ type: "OP", value: ch }); i++; continue
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// string literal
|
|
195
|
+
if (ch === '"') {
|
|
196
|
+
let s = ""
|
|
197
|
+
i++ // skip opening "
|
|
198
|
+
while (i < expr.length && expr[i] !== '"') {
|
|
199
|
+
if (expr[i] === "\\") {
|
|
200
|
+
i++
|
|
201
|
+
const esc = expr[i]
|
|
202
|
+
if (esc === '"') s += '"'
|
|
203
|
+
else if (esc === "\\") s += "\\"
|
|
204
|
+
else if (esc === "n") s += "\n"
|
|
205
|
+
else if (esc === "t") s += "\t"
|
|
206
|
+
else s += esc
|
|
207
|
+
} else {
|
|
208
|
+
s += expr[i]
|
|
209
|
+
}
|
|
210
|
+
i++
|
|
211
|
+
}
|
|
212
|
+
if (expr[i] !== '"') throw new Error("Unterminated string literal")
|
|
213
|
+
i++ // skip closing "
|
|
214
|
+
tokens.push({ type: "STRING", value: s }); continue
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// number
|
|
218
|
+
if (/[0-9]/.test(ch) || (ch === "-" && /[0-9]/.test(expr[i + 1] ?? ""))) {
|
|
219
|
+
const m = expr.slice(i).match(/^-?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?/)
|
|
220
|
+
if (m) {
|
|
221
|
+
const n = Number(m[0])
|
|
222
|
+
if (!Number.isFinite(n)) throw new Error(`Invalid number: ${m[0]}`)
|
|
223
|
+
tokens.push({ type: "NUMBER", value: n })
|
|
224
|
+
i += m[0].length; continue
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// identifier / keyword
|
|
229
|
+
const idm = expr.slice(i).match(/^[a-zA-Z_$][a-zA-Z0-9_$]*/)
|
|
230
|
+
if (idm) {
|
|
231
|
+
tokens.push({ type: "IDENT", value: idm[0] })
|
|
232
|
+
i += idm[0].length; continue
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
throw new Error(`Unexpected character '${ch}' at position ${i}`)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return tokens
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// --- AST Node Types ---
|
|
242
|
+
|
|
243
|
+
type AstNode =
|
|
244
|
+
| { k: "identity" }
|
|
245
|
+
| { k: "dot"; field: string }
|
|
246
|
+
| { k: "bracket"; key: string }
|
|
247
|
+
| { k: "iterate" }
|
|
248
|
+
| { k: "index"; index: number }
|
|
249
|
+
| { k: "pipe"; left: AstNode; right: AstNode }
|
|
250
|
+
| { k: "select"; cond: AstNode }
|
|
251
|
+
| { k: "object"; fields: Array<{ key: string; valueExpr: AstNode | null }> }
|
|
252
|
+
| { k: "compare"; op: string; left: AstNode; right: AstNode }
|
|
253
|
+
| { k: "bool"; op: "and" | "or"; left: AstNode; right: AstNode }
|
|
254
|
+
| { k: "not" }
|
|
255
|
+
| { k: "func"; name: string; arg: string }
|
|
256
|
+
| { k: "literal"; value: string | number }
|
|
257
|
+
| { k: "path"; steps: AstNode[] } // chained dot/bracket/iterate/index
|
|
258
|
+
|
|
259
|
+
// --- Parser ---
|
|
260
|
+
|
|
261
|
+
class Parser {
|
|
262
|
+
private pos = 0
|
|
263
|
+
constructor(private tokens: Token[]) {}
|
|
264
|
+
|
|
265
|
+
private peek(): Token | undefined { return this.tokens[this.pos] }
|
|
266
|
+
private eat(): Token {
|
|
267
|
+
const t = this.tokens[this.pos++]
|
|
268
|
+
if (!t) throw new Error("Unexpected end of expression")
|
|
269
|
+
return t
|
|
270
|
+
}
|
|
271
|
+
private expect(type: TokType): Token {
|
|
272
|
+
const t = this.eat()
|
|
273
|
+
if (t.type !== type) throw new Error(`Expected ${type}, got ${t.type} ('${t.value}')`)
|
|
274
|
+
return t
|
|
275
|
+
}
|
|
276
|
+
private at(type: TokType): boolean { return this.peek()?.type === type }
|
|
277
|
+
private atIdent(v: string): boolean { return this.peek()?.type === "IDENT" && this.peek()?.value === v }
|
|
278
|
+
private done(): boolean { return this.pos >= this.tokens.length }
|
|
279
|
+
|
|
280
|
+
parse(): AstNode {
|
|
281
|
+
const node = this.parsePipe()
|
|
282
|
+
if (!this.done()) throw new Error(`Unexpected token '${this.peek()!.value}' at position ${this.pos}`)
|
|
283
|
+
return node
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
private parsePipe(): AstNode {
|
|
287
|
+
let left = this.parseBoolExpr()
|
|
288
|
+
while (this.at("PIPE")) {
|
|
289
|
+
this.eat() // consume |
|
|
290
|
+
const right = this.parseBoolExpr()
|
|
291
|
+
left = { k: "pipe", left, right }
|
|
292
|
+
}
|
|
293
|
+
return left
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
private parseBoolExpr(): AstNode {
|
|
297
|
+
let left = this.parseCompare()
|
|
298
|
+
while (this.atIdent("and") || this.atIdent("or")) {
|
|
299
|
+
const op = this.eat().value as "and" | "or"
|
|
300
|
+
const right = this.parseCompare()
|
|
301
|
+
left = { k: "bool", op, left, right }
|
|
302
|
+
}
|
|
303
|
+
return left
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
private parseCompare(): AstNode {
|
|
307
|
+
const left = this.parseAtom()
|
|
308
|
+
if (this.at("OP")) {
|
|
309
|
+
const op = this.eat().value as string
|
|
310
|
+
const right = this.parseAtom()
|
|
311
|
+
return { k: "compare", op, left, right }
|
|
312
|
+
}
|
|
313
|
+
return left
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// parseAtom handles all leaf-level filter constructs
|
|
317
|
+
private parseAtom(): AstNode {
|
|
318
|
+
// select(...)
|
|
319
|
+
if (this.atIdent("select")) {
|
|
320
|
+
this.eat()
|
|
321
|
+
this.expect("LPAREN")
|
|
322
|
+
// Allow full pipe expressions inside select (e.g., select(.name | contains("x")))
|
|
323
|
+
const cond = this.parsePipe()
|
|
324
|
+
this.expect("RPAREN")
|
|
325
|
+
return { k: "select", cond }
|
|
326
|
+
}
|
|
327
|
+
// not (postfix — standalone in pipe context)
|
|
328
|
+
if (this.atIdent("not")) {
|
|
329
|
+
this.eat()
|
|
330
|
+
return { k: "not" }
|
|
331
|
+
}
|
|
332
|
+
// object construction
|
|
333
|
+
if (this.at("LBRACE")) {
|
|
334
|
+
return this.parseObjectCtor()
|
|
335
|
+
}
|
|
336
|
+
// string function calls (contains, startswith, endswith, test) with arg
|
|
337
|
+
if (this.at("IDENT")) {
|
|
338
|
+
const name = this.peek()!.value as string
|
|
339
|
+
if (["contains", "startswith", "endswith", "test"].includes(name)) {
|
|
340
|
+
// look ahead for (
|
|
341
|
+
if (this.tokens[this.pos + 1]?.type === "LPAREN") {
|
|
342
|
+
this.eat() // name
|
|
343
|
+
this.eat() // (
|
|
344
|
+
const argTok = this.eat()
|
|
345
|
+
if (argTok.type !== "STRING") throw new Error(`${name}() argument must be a string`)
|
|
346
|
+
this.expect("RPAREN")
|
|
347
|
+
return { k: "func", name, arg: argTok.value as string }
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
// parenthesised expression
|
|
352
|
+
if (this.at("LPAREN")) {
|
|
353
|
+
this.eat()
|
|
354
|
+
const inner = this.parseBoolExpr()
|
|
355
|
+
this.expect("RPAREN")
|
|
356
|
+
return inner
|
|
357
|
+
}
|
|
358
|
+
// literals and paths
|
|
359
|
+
if (this.at("STRING")) {
|
|
360
|
+
return { k: "literal", value: this.eat().value as string }
|
|
361
|
+
}
|
|
362
|
+
if (this.at("NUMBER")) {
|
|
363
|
+
return { k: "literal", value: this.eat().value as number }
|
|
364
|
+
}
|
|
365
|
+
return this.parsePath()
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
private parsePath(): AstNode {
|
|
369
|
+
if (!this.at("DOT")) throw new Error(`Expected path starting with '.', got '${this.peek()?.value ?? "EOF"}'`)
|
|
370
|
+
this.eat() // consume .
|
|
371
|
+
|
|
372
|
+
// bare "." — identity
|
|
373
|
+
const steps: AstNode[] = []
|
|
374
|
+
|
|
375
|
+
// after dot, there may be an identifier or a bracket form
|
|
376
|
+
if (this.at("IDENT") && !["select","not","contains","startswith","endswith","test","and","or","null","true","false"].includes(this.peek()!.value as string)) {
|
|
377
|
+
steps.push({ k: "dot", field: this.eat().value as string })
|
|
378
|
+
} else if (this.at("BRACKET_KEY")) {
|
|
379
|
+
steps.push({ k: "bracket", key: this.eat().value as string })
|
|
380
|
+
} else if (this.at("EMPTY_ITER")) {
|
|
381
|
+
this.eat()
|
|
382
|
+
steps.push({ k: "iterate" })
|
|
383
|
+
} else if (this.at("INDEX")) {
|
|
384
|
+
steps.push({ k: "index", index: this.eat().value as number })
|
|
385
|
+
} else {
|
|
386
|
+
// just "."
|
|
387
|
+
if (steps.length === 0) return { k: "identity" }
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// chain more accesses: .foo.bar, .foo[0], .foo[]
|
|
391
|
+
while (true) {
|
|
392
|
+
if (this.at("DOT")) {
|
|
393
|
+
this.eat()
|
|
394
|
+
if (this.at("IDENT") && !["select","not","contains","startswith","endswith","test","and","or"].includes(this.peek()!.value as string)) {
|
|
395
|
+
steps.push({ k: "dot", field: this.eat().value as string })
|
|
396
|
+
} else if (this.at("BRACKET_KEY")) {
|
|
397
|
+
steps.push({ k: "bracket", key: this.eat().value as string })
|
|
398
|
+
} else if (this.at("EMPTY_ITER")) {
|
|
399
|
+
this.eat(); steps.push({ k: "iterate" })
|
|
400
|
+
} else if (this.at("INDEX")) {
|
|
401
|
+
steps.push({ k: "index", index: this.eat().value as number })
|
|
402
|
+
} else {
|
|
403
|
+
// trailing dot — treat as identity at end (shouldn't normally happen)
|
|
404
|
+
break
|
|
405
|
+
}
|
|
406
|
+
} else if (this.at("BRACKET_KEY")) {
|
|
407
|
+
steps.push({ k: "bracket", key: this.eat().value as string })
|
|
408
|
+
} else if (this.at("EMPTY_ITER")) {
|
|
409
|
+
this.eat(); steps.push({ k: "iterate" })
|
|
410
|
+
} else if (this.at("INDEX")) {
|
|
411
|
+
steps.push({ k: "index", index: this.eat().value as number })
|
|
412
|
+
} else {
|
|
413
|
+
break
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (steps.length === 0) return { k: "identity" }
|
|
418
|
+
if (steps.length === 1) return steps[0]
|
|
419
|
+
return { k: "path", steps }
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
private parseObjectCtor(): AstNode {
|
|
423
|
+
this.eat() // {
|
|
424
|
+
const fields: Array<{ key: string; valueExpr: AstNode | null }> = []
|
|
425
|
+
|
|
426
|
+
while (!this.at("RBRACE") && !this.done()) {
|
|
427
|
+
let key: string
|
|
428
|
+
let valueExpr: AstNode | null = null
|
|
429
|
+
|
|
430
|
+
if (this.at("IDENT")) {
|
|
431
|
+
key = this.eat().value as string
|
|
432
|
+
if (this.at("COLON")) {
|
|
433
|
+
this.eat() // :
|
|
434
|
+
valueExpr = this.parsePipe()
|
|
435
|
+
}
|
|
436
|
+
// else shorthand: {name} => {name: .name}
|
|
437
|
+
} else if (this.at("STRING")) {
|
|
438
|
+
key = this.eat().value as string
|
|
439
|
+
this.expect("COLON")
|
|
440
|
+
valueExpr = this.parsePipe()
|
|
441
|
+
} else {
|
|
442
|
+
throw new Error(`Expected field name in object constructor, got '${this.peek()?.value}'`)
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
fields.push({ key, valueExpr })
|
|
446
|
+
if (this.at("COMMA")) this.eat()
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
this.expect("RBRACE")
|
|
450
|
+
return { k: "object", fields }
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// --- Evaluator ---
|
|
455
|
+
|
|
456
|
+
// Sentinel to represent "this item was dropped by select"
|
|
457
|
+
const DROPPED = Symbol("DROPPED")
|
|
458
|
+
// Sentinel to track that we're in iterate mode (stream of items)
|
|
459
|
+
type EvalValue = unknown | typeof DROPPED
|
|
460
|
+
|
|
461
|
+
function isTruthy(v: unknown): boolean {
|
|
462
|
+
if (v === null || v === undefined || v === false || v === 0 || v === "") return false
|
|
463
|
+
return true
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// --- Per-call evaluation context ---
|
|
467
|
+
|
|
468
|
+
interface EvalCtx {
|
|
469
|
+
expandedArrays: WeakSet<object>
|
|
470
|
+
objectSourcedArrays: WeakSet<object>
|
|
471
|
+
regexCache: Map<string, RegExp>
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function evalNode(node: AstNode, input: unknown, ctx: EvalCtx): EvalValue | EvalValue[] {
|
|
475
|
+
switch (node.k) {
|
|
476
|
+
case "identity":
|
|
477
|
+
return input
|
|
478
|
+
|
|
479
|
+
case "dot": {
|
|
480
|
+
if (input === null || input === undefined) return null
|
|
481
|
+
if (typeof input !== "object" || Array.isArray(input)) {
|
|
482
|
+
throw new Error(`Cannot access field '.${node.field}' on ${Array.isArray(input) ? "array" : typeof input}`)
|
|
483
|
+
}
|
|
484
|
+
const obj = input as Record<string, unknown>
|
|
485
|
+
if (!(node.field in obj)) {
|
|
486
|
+
const available = Object.keys(obj).join(", ")
|
|
487
|
+
throw new Error(`Field '${node.field}' not found. Available fields: ${available || "(none)"}`)
|
|
488
|
+
}
|
|
489
|
+
return obj[node.field]
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
case "bracket": {
|
|
493
|
+
if (input === null || input === undefined) return null
|
|
494
|
+
if (typeof input !== "object" || Array.isArray(input)) {
|
|
495
|
+
throw new Error(`Cannot bracket-access on ${Array.isArray(input) ? "array" : typeof input}`)
|
|
496
|
+
}
|
|
497
|
+
const obj = input as Record<string, unknown>
|
|
498
|
+
if (!(node.key in obj)) {
|
|
499
|
+
const available = Object.keys(obj).join(", ")
|
|
500
|
+
throw new Error(`Field '${node.key}' not found. Available fields: ${available || "(none)"}`)
|
|
501
|
+
}
|
|
502
|
+
return obj[node.key]
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
case "index": {
|
|
506
|
+
if (!Array.isArray(input)) throw new Error(`Cannot index non-array with .[${node.index}]`)
|
|
507
|
+
const idx = node.index < 0 ? input.length + node.index : node.index
|
|
508
|
+
return idx >= 0 && idx < input.length ? input[idx] : null
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
case "iterate": {
|
|
512
|
+
if (Array.isArray(input)) {
|
|
513
|
+
return input // return as array — caller handles fan-out
|
|
514
|
+
}
|
|
515
|
+
if (typeof input === "object" && input !== null) {
|
|
516
|
+
// treat object as single-item sequence per spec
|
|
517
|
+
const arr = [input]
|
|
518
|
+
ctx.objectSourcedArrays.add(arr)
|
|
519
|
+
return arr
|
|
520
|
+
}
|
|
521
|
+
throw new Error(`Cannot iterate over ${typeof input}`)
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
case "path": {
|
|
525
|
+
let cur: unknown = input
|
|
526
|
+
for (let i = 0; i < node.steps.length; i++) {
|
|
527
|
+
const step = node.steps[i]
|
|
528
|
+
const res = evalNode(step, cur, ctx)
|
|
529
|
+
// if a step returns an array (from iterate), fan out remaining steps
|
|
530
|
+
if (step.k === "iterate" && Array.isArray(res)) {
|
|
531
|
+
const wasObjSourced = ctx.objectSourcedArrays.has(res as object)
|
|
532
|
+
// fan out remaining steps over each element
|
|
533
|
+
const remaining = node.steps.slice(i + 1)
|
|
534
|
+
if (remaining.length === 0) return res
|
|
535
|
+
const fanned: unknown[] = []
|
|
536
|
+
for (const item of res) {
|
|
537
|
+
const subNode: AstNode = remaining.length === 1 ? remaining[0] : { k: "path", steps: remaining }
|
|
538
|
+
const subRes = evalNode(subNode, item, ctx)
|
|
539
|
+
if (Array.isArray(subRes)) fanned.push(...subRes)
|
|
540
|
+
else if (subRes !== DROPPED) fanned.push(subRes)
|
|
541
|
+
}
|
|
542
|
+
return markExpanded(fanned, wasObjSourced, ctx)
|
|
543
|
+
}
|
|
544
|
+
cur = res
|
|
545
|
+
}
|
|
546
|
+
return cur
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
case "pipe": {
|
|
550
|
+
// Evaluate left side; if it produces an array (from iterate), fan-out over right side
|
|
551
|
+
const leftVal = evalNode(node.left, input, ctx)
|
|
552
|
+
|
|
553
|
+
// Check if left side was an iterate that produced array expansion
|
|
554
|
+
const leftIsExpanded = isExpandedArray(node.left, leftVal, ctx)
|
|
555
|
+
|
|
556
|
+
if (leftIsExpanded && Array.isArray(leftVal)) {
|
|
557
|
+
const wasObjectSourced = ctx.objectSourcedArrays.has(leftVal as object)
|
|
558
|
+
const results: unknown[] = []
|
|
559
|
+
for (const item of leftVal) {
|
|
560
|
+
const r = evalNode(node.right, item, ctx)
|
|
561
|
+
if (r === DROPPED) continue
|
|
562
|
+
if (Array.isArray(r) && isExpandedArray(node.right, r, ctx)) {
|
|
563
|
+
results.push(...r)
|
|
564
|
+
} else if (r !== DROPPED) {
|
|
565
|
+
results.push(r)
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
// mark as expanded, preserving object-sourced flag
|
|
569
|
+
return markExpanded(results, wasObjectSourced, ctx)
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
if (leftVal === DROPPED) return DROPPED
|
|
573
|
+
|
|
574
|
+
// scalar left — apply right once
|
|
575
|
+
return evalNode(node.right, leftVal, ctx)
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
case "select": {
|
|
579
|
+
const condVal = evalNode(node.cond, input, ctx)
|
|
580
|
+
// If condition is a pipe that produced an expanded array (e.g., select(.name | contains("x"))),
|
|
581
|
+
// the truthiness is based on the final scalar result (the pipe's output).
|
|
582
|
+
// An expanded array here means the condition pipeline returned array results — use last item.
|
|
583
|
+
if (Array.isArray(condVal) && ctx.expandedArrays.has(condVal)) {
|
|
584
|
+
const last = condVal[condVal.length - 1]
|
|
585
|
+
return isTruthy(last) ? input : DROPPED
|
|
586
|
+
}
|
|
587
|
+
return isTruthy(condVal as unknown) ? input : DROPPED
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
case "not": {
|
|
591
|
+
return !isTruthy(input)
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
case "object": {
|
|
595
|
+
const result: Record<string, unknown> = {}
|
|
596
|
+
for (const { key, valueExpr } of node.fields) {
|
|
597
|
+
let fieldVal: unknown
|
|
598
|
+
if (valueExpr === null) {
|
|
599
|
+
// shorthand: {name} => .name
|
|
600
|
+
fieldVal = evalNode({ k: "dot", field: key }, input, ctx)
|
|
601
|
+
} else {
|
|
602
|
+
fieldVal = evalNode(valueExpr, input, ctx)
|
|
603
|
+
}
|
|
604
|
+
if (fieldVal === DROPPED) return DROPPED
|
|
605
|
+
result[key] = fieldVal
|
|
606
|
+
}
|
|
607
|
+
return result
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
case "compare": {
|
|
611
|
+
const lv = evalNode(node.left, input, ctx) as unknown
|
|
612
|
+
const rv = evalNode(node.right, input, ctx) as unknown
|
|
613
|
+
switch (node.op) {
|
|
614
|
+
case "==": return lv === rv
|
|
615
|
+
case "!=": return lv !== rv
|
|
616
|
+
case ">": return typeof lv === "number" && typeof rv === "number" ? lv > rv : false
|
|
617
|
+
case "<": return typeof lv === "number" && typeof rv === "number" ? lv < rv : false
|
|
618
|
+
case ">=": return typeof lv === "number" && typeof rv === "number" ? lv >= rv : false
|
|
619
|
+
case "<=": return typeof lv === "number" && typeof rv === "number" ? lv <= rv : false
|
|
620
|
+
default: return false
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
case "bool": {
|
|
625
|
+
const lv = evalNode(node.left, input, ctx)
|
|
626
|
+
if (node.op === "and") {
|
|
627
|
+
if (!isTruthy(lv as unknown)) return false
|
|
628
|
+
return isTruthy(evalNode(node.right, input, ctx) as unknown)
|
|
629
|
+
} else {
|
|
630
|
+
if (isTruthy(lv as unknown)) return true
|
|
631
|
+
return isTruthy(evalNode(node.right, input, ctx) as unknown)
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
case "func": {
|
|
636
|
+
if (typeof input !== "string") throw new Error(`${node.name}() requires a string input`)
|
|
637
|
+
switch (node.name) {
|
|
638
|
+
case "contains": return input.includes(node.arg)
|
|
639
|
+
case "startswith": return input.startsWith(node.arg)
|
|
640
|
+
case "endswith": return input.endsWith(node.arg)
|
|
641
|
+
case "test": {
|
|
642
|
+
let re = ctx.regexCache.get(node.arg)
|
|
643
|
+
if (!re) {
|
|
644
|
+
if (/(\([^)]*[+*][^)]*\))[+*]/.test(node.arg)) {
|
|
645
|
+
throw new Error("Regex pattern too complex — avoid nested quantifiers")
|
|
646
|
+
}
|
|
647
|
+
re = new RegExp(node.arg)
|
|
648
|
+
ctx.regexCache.set(node.arg, re)
|
|
649
|
+
}
|
|
650
|
+
return re.test(input.slice(0, 100000))
|
|
651
|
+
}
|
|
652
|
+
default: throw new Error(`Unknown function: ${node.name}`)
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
case "literal":
|
|
657
|
+
return node.value
|
|
658
|
+
|
|
659
|
+
default: {
|
|
660
|
+
const _exhaustive: never = node
|
|
661
|
+
throw new Error(`Unknown AST node`)
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// --- Expanded array tracking ---
|
|
667
|
+
// expandedArrays: arrays produced by iterate fan-out (pipe needs to fan-out over them)
|
|
668
|
+
// objectSourcedArrays: expanded arrays whose origin was object iteration (unwrap at output)
|
|
669
|
+
|
|
670
|
+
function markExpanded(arr: unknown[], objectSourced: boolean, ctx: EvalCtx): unknown[] {
|
|
671
|
+
ctx.expandedArrays.add(arr)
|
|
672
|
+
if (objectSourced) ctx.objectSourcedArrays.add(arr)
|
|
673
|
+
return arr
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
function isExpandedArray(node: AstNode, val: unknown, ctx: EvalCtx): boolean {
|
|
677
|
+
if (!Array.isArray(val)) return false
|
|
678
|
+
// If left side is an iterate node directly, it's always expanded
|
|
679
|
+
if (node.k === "iterate") return true
|
|
680
|
+
// If left side is a path ending in iterate, check the last step
|
|
681
|
+
if (node.k === "path") {
|
|
682
|
+
const last = node.steps[node.steps.length - 1]
|
|
683
|
+
if (last?.k === "iterate") return true
|
|
684
|
+
}
|
|
685
|
+
// If left side is a pipe that produced an expanded array
|
|
686
|
+
if (node.k === "pipe") return ctx.expandedArrays.has(val as object)
|
|
687
|
+
// Check WeakSet
|
|
688
|
+
return ctx.expandedArrays.has(val as object)
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// --- Public API ---
|
|
692
|
+
|
|
693
|
+
export function jsonResultToList(value: unknown): string[] {
|
|
694
|
+
if (Array.isArray(value)) {
|
|
695
|
+
return value.map((v) => typeof v === "string" ? v : JSON.stringify(v))
|
|
696
|
+
}
|
|
697
|
+
if (value !== null && value !== undefined) {
|
|
698
|
+
return [typeof value === "string" ? value : JSON.stringify(value)]
|
|
699
|
+
}
|
|
700
|
+
return []
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
export function evaluateJsonExpression(input: unknown, expression: string): JsonEvalResult {
|
|
704
|
+
try {
|
|
705
|
+
const tokens = tokenize(expression)
|
|
706
|
+
const ast = new Parser(tokens).parse()
|
|
707
|
+
const ctx: EvalCtx = {
|
|
708
|
+
expandedArrays: new WeakSet(),
|
|
709
|
+
objectSourcedArrays: new WeakSet(),
|
|
710
|
+
regexCache: new Map(),
|
|
711
|
+
}
|
|
712
|
+
const raw = evalNode(ast, input, ctx)
|
|
713
|
+
|
|
714
|
+
// If result is an expanded array (from iterate fan-out):
|
|
715
|
+
if (Array.isArray(raw) && isExpandedArray(ast, raw, ctx)) {
|
|
716
|
+
// Object-sourced: spec says treat as single-element sequence — unwrap (0 items → null, 1 → item)
|
|
717
|
+
// Array-sourced: always return as array (jq model)
|
|
718
|
+
const isObjSourced = ctx.objectSourcedArrays.has(raw as object)
|
|
719
|
+
if (isObjSourced) {
|
|
720
|
+
if (raw.length === 0) return { ok: true, value: null }
|
|
721
|
+
if (raw.length === 1) return { ok: true, value: raw[0] }
|
|
722
|
+
}
|
|
723
|
+
if (raw.length === 0) return { ok: true, value: [] }
|
|
724
|
+
return { ok: true, value: raw }
|
|
725
|
+
}
|
|
726
|
+
// Non-expanded: return as-is
|
|
727
|
+
return { ok: true, value: raw === DROPPED ? [] : raw }
|
|
728
|
+
} catch (err) {
|
|
729
|
+
const msg = err instanceof Error ? err.message : String(err)
|
|
730
|
+
return { ok: false, error: msg }
|
|
731
|
+
}
|
|
732
|
+
}
|