@salsita-ai/sdk 0.50.0-develop.da8d27f5d7 → 0.50.0-develop.dfac20c66b

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.
Files changed (169) hide show
  1. package/README.md +19 -11
  2. package/dist/application-spec/errors/diagnostic-codes.d.ts +6 -0
  3. package/dist/application-spec/errors/diagnostic-codes.js +1 -1
  4. package/dist/application-spec/errors/diagnostic-messages.d.ts +6 -0
  5. package/dist/application-spec/errors/diagnostic-messages.js +1 -1
  6. package/dist/application-spec/generate-application/code/domain-runtime-files.js +1 -1
  7. package/dist/application-spec/generate-application/code/templates/application-types.js +6 -6
  8. package/dist/application-spec/generate-application/code/templates/configurator-domain-code.d.ts +1 -0
  9. package/dist/application-spec/generate-application/code/templates/configurator-domain-code.js +11 -8
  10. package/dist/application-spec/generate-application/code/templates/domain-spec/generate-domain-block.d.ts +3 -1
  11. package/dist/application-spec/generate-application/code/templates/domain-spec/generate-domain-block.js +70 -66
  12. package/dist/application-spec/generate-application/code/templates/domain-spec/generate-phx-globals.js +3 -0
  13. package/dist/application-spec/generate-application/code/templates/domain-spec.js +5 -5
  14. package/dist/application-spec/generate-application/code/templates/domain.js +2 -2
  15. package/dist/application-spec/generate-application/code/templates/global-definition/generate-action-types.d.ts +11 -0
  16. package/dist/application-spec/generate-application/code/templates/global-definition/generate-action-types.js +8 -0
  17. package/dist/application-spec/generate-application/code/templates/global-definition/index.d.ts +1 -0
  18. package/dist/application-spec/generate-application/code/templates/global-definition/index.js +1 -1
  19. package/dist/application-spec/generate-application/code/templates/globals.js +1 -0
  20. package/dist/application-spec/generate-application/code/templates/index.d.ts +6 -6
  21. package/dist/application-spec/generate-application/code/templates/index.js +1 -1
  22. package/dist/application-spec/generate-application/code/templates/skills/agents-md.js +2 -0
  23. package/dist/application-spec/generate-application/code/templates/skills/phoenix-cli-skill.js +1 -0
  24. package/dist/application-spec/generate-application/code/templates/{phoenix-create-configurator-skill.d.ts → skills/phoenix-create-configurator-skill.d.ts} +1 -1
  25. package/dist/application-spec/generate-application/code/templates/{phoenix-create-configurator-skill.js → skills/phoenix-create-configurator-skill.js} +4 -2
  26. package/dist/application-spec/generate-application/code/templates/skills/phoenix-feedback-skill.js +54 -0
  27. package/dist/application-spec/generate-application/code/templates/{phoenix-review-skill.js → skills/phoenix-review-skill.js} +6 -4
  28. package/dist/application-spec/generate-application/code/templates/skills/phoenix-skill.js +1 -0
  29. package/dist/application-spec/generate-application/code/templates/{common → skills}/sdk-content.d.ts +24 -1
  30. package/dist/application-spec/generate-application/code/templates/skills/sdk-content.js +67 -0
  31. package/dist/application-spec/generate-application/runtime/configurator-runtime/configurator-runtime.d.ts +2 -0
  32. package/dist/application-spec/generate-application/runtime/configurator-runtime/configurator-runtime.js +1 -1
  33. package/dist/application-spec/generate-application/runtime/sections/actions-runtime.d.ts +14 -0
  34. package/dist/application-spec/generate-application/runtime/sections/actions-runtime.js +1 -0
  35. package/dist/application-spec/generate-application/runtime/sections/index.d.ts +1 -0
  36. package/dist/application-spec/generate-application/runtime/sections/index.js +1 -1
  37. package/dist/application-spec/parse-application-files/categorize/categorize-files.d.ts +3 -1
  38. package/dist/application-spec/parse-application-files/categorize/categorize-files.js +1 -1
  39. package/dist/application-spec/parse-application-files/merge/index.d.ts +1 -0
  40. package/dist/application-spec/parse-application-files/merge/index.js +1 -1
  41. package/dist/application-spec/parse-application-files/merge/merged-actions.d.ts +11 -0
  42. package/dist/application-spec/parse-application-files/merge/merged-actions.js +1 -0
  43. package/dist/application-spec/parse-application-files/merge/merged-spec.d.ts +2 -0
  44. package/dist/application-spec/parse-application-files/merge/merged-spec.js +1 -1
  45. package/dist/application-spec/parse-application-files/raw-spec/index.d.ts +1 -0
  46. package/dist/application-spec/parse-application-files/raw-spec/index.js +1 -1
  47. package/dist/application-spec/parse-application-files/raw-spec/raw-actions.d.ts +18 -0
  48. package/dist/application-spec/parse-application-files/raw-spec/raw-actions.js +1 -0
  49. package/dist/application-spec/parse-application-files/raw-spec/raw-assistant.d.ts +14 -2
  50. package/dist/application-spec/parse-application-files/raw-spec/raw-assistant.js +1 -1
  51. package/dist/application-spec/parse-application-files/raw-spec/raw-spec-file.d.ts +2 -0
  52. package/dist/application-spec/parse-application-files/raw-spec/raw-spec-file.js +1 -1
  53. package/dist/application-spec/parse-application-files/resolve/resolve-import-modules.js +1 -1
  54. package/dist/application-spec/parse-application-files/spec/application/application-spec.js +1 -1
  55. package/dist/application-spec/parse-application-files/spec/application/configurator-spec/configurator-spec.d.ts +4 -2
  56. package/dist/application-spec/parse-application-files/spec/application/configurator-spec/configurator-spec.js +1 -1
  57. package/dist/application-spec/parse-application-files/spec/common/index.d.ts +1 -0
  58. package/dist/application-spec/parse-application-files/spec/common/index.js +1 -1
  59. package/dist/application-spec/parse-application-files/spec/common/schema-ref-resolver.d.ts +23 -0
  60. package/dist/application-spec/parse-application-files/spec/common/schema-ref-resolver.js +1 -0
  61. package/dist/application-spec/parse-application-files/spec/model/names/action-name.d.ts +10 -0
  62. package/dist/application-spec/parse-application-files/spec/model/names/action-name.js +1 -0
  63. package/dist/application-spec/parse-application-files/spec/model/names/index.d.ts +1 -0
  64. package/dist/application-spec/parse-application-files/spec/model/names/index.js +1 -1
  65. package/dist/application-spec/parse-application-files/spec/sections/actions.d.ts +35 -0
  66. package/dist/application-spec/parse-application-files/spec/sections/actions.js +1 -0
  67. package/dist/application-spec/parse-application-files/spec/sections/assistant/assistant-field-type.d.ts +3 -14
  68. package/dist/application-spec/parse-application-files/spec/sections/assistant/assistant-field-type.js +1 -1
  69. package/dist/application-spec/parse-application-files/spec/sections/assistant/assistant-schema.d.ts +12 -3
  70. package/dist/application-spec/parse-application-files/spec/sections/assistant/assistant-schema.js +1 -1
  71. package/dist/application-spec/parse-application-files/spec/sections/assistant/assistant.d.ts +5 -3
  72. package/dist/application-spec/parse-application-files/spec/sections/index.d.ts +1 -0
  73. package/dist/application-spec/parse-application-files/spec/sections/index.js +1 -1
  74. package/dist/client/phoenix-api.d.ts +5 -1
  75. package/dist/configurator/application-runtime/extensions/configurator-extension-systems.d.ts +2 -1
  76. package/dist/configurator/application-runtime/extensions/configurator-extension-systems.js +1 -1
  77. package/dist/configurator/application-runtime/extensions/selectors.d.ts +2 -1
  78. package/dist/configurator/application-runtime/extensions/selectors.js +1 -1
  79. package/dist/configurator/application-runtime/extensions/systems/assistant-system/assistant-system.d.ts +35 -0
  80. package/dist/configurator/application-runtime/extensions/systems/assistant-system/assistant-system.js +1 -0
  81. package/dist/configurator/{assistant → application-runtime/extensions/systems/assistant-system}/browser-details.d.ts +1 -1
  82. package/dist/configurator/application-runtime/extensions/systems/assistant-system/browser-details.js +1 -0
  83. package/dist/configurator/{assistant → application-runtime/extensions/systems/assistant-system}/graphql-tools.d.ts +13 -14
  84. package/dist/configurator/application-runtime/extensions/systems/assistant-system/graphql-tools.js +1 -0
  85. package/dist/configurator/application-runtime/extensions/systems/graphql-system/graphql-system.js +1 -1
  86. package/dist/configurator/application-runtime/extensions/systems/index.d.ts +1 -0
  87. package/dist/configurator/application-runtime/extensions/systems/index.js +1 -1
  88. package/dist/configurator/assistant/use-assistant-chat.js +1 -1
  89. package/dist/docs/index.json +12 -2
  90. package/dist/docs/other-features/domain-actions.mdx +418 -0
  91. package/dist/docs/phoenix/admin.mdx +2 -2
  92. package/dist/docs/phoenix/best-practices.mdx +1 -0
  93. package/dist/docs/phoenix/finalization.mdx +1 -1
  94. package/dist/docs/phoenix/integration/headless-mode-external.mdx +5 -5
  95. package/dist/docs/phoenix/integration/widget-mode-external.mdx +5 -5
  96. package/dist/docs/phoenix/migrations.mdx +45 -5
  97. package/dist/docs/phoenix/phoenix-cli.mdx +60 -20
  98. package/dist/docs/phoenix/project-structure.mdx +4 -1
  99. package/dist/docs/phoenix/sdk-builder-workflow.mdx +9 -6
  100. package/dist/domain/actions/action-factory.d.ts +10 -0
  101. package/dist/domain/actions/action-factory.js +1 -0
  102. package/dist/domain/actions/prepare-action-implementations.d.ts +15 -0
  103. package/dist/domain/actions/prepare-action-implementations.js +5 -0
  104. package/dist/domain/application/application-config.d.ts +3 -1
  105. package/dist/domain/application/assistant-mutations.d.ts +15 -14
  106. package/dist/domain/application/assistant-mutations.js +1 -1
  107. package/dist/domain/core/action.d.ts +67 -0
  108. package/dist/domain/core/action.js +1 -0
  109. package/dist/domain/core/domain-factory.d.ts +4 -0
  110. package/dist/domain/core/index.d.ts +1 -0
  111. package/dist/domain/domain-builder.d.ts +7 -0
  112. package/dist/domain/domain-builder.js +1 -1
  113. package/dist/domain/domain-context/action-system/action-system.d.ts +40 -0
  114. package/dist/domain/domain-context/action-system/action-system.js +1 -0
  115. package/dist/domain/domain-context/domain-context-systems.d.ts +5 -2
  116. package/dist/domain/domain-context/index.d.ts +1 -0
  117. package/dist/domain/domain-context/index.js +1 -1
  118. package/dist/domain/domain-runtime/domain-context/domain-context.js +1 -1
  119. package/dist/domain/domain-runtime/domain-context/selectors.d.ts +4 -0
  120. package/dist/domain/domain-runtime/domain-context/selectors.js +1 -1
  121. package/dist/domain/domain-runtime/domain-runtime.d.ts +12 -0
  122. package/dist/domain/domain-runtime/spec/initialize.js +1 -1
  123. package/dist/domain/domain-runtime/spec/types.d.ts +15 -1
  124. package/dist/domain/index.d.ts +2 -0
  125. package/dist/domain/index.js +1 -1
  126. package/dist/domain/mocks/create-mock-domain-runtime.d.ts +6 -0
  127. package/dist/editor/domain-editors/DomainSpecContext.js +1 -1
  128. package/dist/editor/domain-editors/apply-spec-update.js +1 -1
  129. package/dist/editor/domain-editors/use-implementation-files.d.ts +7 -3
  130. package/dist/editor/domain-editors/use-implementation-files.js +1 -1
  131. package/dist/phoenix/application-spec/generate-assistant-prompt.d.ts +2 -2
  132. package/dist/phoenix/application-spec/generate-assistant-prompt.js +1 -1
  133. package/dist/phoenix/cli.js +1 -1
  134. package/dist/phoenix/commands/build.d.ts +5 -0
  135. package/dist/phoenix/commands/build.js +1 -1
  136. package/dist/phoenix/commands/deploy.js +2 -2
  137. package/dist/phoenix/commands/feedback.d.ts +2 -1
  138. package/dist/phoenix/commands/feedback.js +1 -1
  139. package/dist/phoenix/commands/login.d.ts +24 -6
  140. package/dist/phoenix/commands/login.js +2 -2
  141. package/dist/phoenix/services/index.d.ts +1 -0
  142. package/dist/phoenix/services/index.js +1 -1
  143. package/dist/phoenix/services/phx-banner-plugin.d.ts +32 -0
  144. package/dist/phoenix/services/phx-banner-plugin.js +1 -0
  145. package/dist/phoenix/services/phx-licenses-plugin.d.ts +55 -0
  146. package/dist/phoenix/services/phx-licenses-plugin.js +8 -0
  147. package/dist/phoenix/services/vite-config.d.ts +2 -0
  148. package/dist/phoenix/services/vite-config.js +1 -1
  149. package/dist/phoenix/utils/index.d.ts +1 -0
  150. package/dist/phoenix/utils/index.js +1 -1
  151. package/dist/phoenix/utils/read-git-commit.d.ts +14 -0
  152. package/dist/phoenix/utils/read-git-commit.js +1 -0
  153. package/dist/react-sdk/application-runtime/runtime/create-application-runtime.d.ts +12 -0
  154. package/dist/react-sdk/domain-context/common-hooks/use-domain-context.d.ts +3 -0
  155. package/package.json +3 -3
  156. package/dist/application-spec/generate-application/code/templates/agents-md.js +0 -2
  157. package/dist/application-spec/generate-application/code/templates/common/sdk-content.js +0 -67
  158. package/dist/application-spec/generate-application/code/templates/phoenix-cli-skill.js +0 -1
  159. package/dist/application-spec/generate-application/code/templates/phoenix-feedback-skill.js +0 -31
  160. package/dist/application-spec/generate-application/code/templates/phoenix-skill.js +0 -1
  161. package/dist/configurator/assistant/browser-details.js +0 -1
  162. package/dist/configurator/assistant/graphql-tools.js +0 -1
  163. /package/dist/application-spec/generate-application/code/templates/{common → skills}/agent-skill.d.ts +0 -0
  164. /package/dist/application-spec/generate-application/code/templates/{common → skills}/agent-skill.js +0 -0
  165. /package/dist/application-spec/generate-application/code/templates/{agents-md.d.ts → skills/agents-md.d.ts} +0 -0
  166. /package/dist/application-spec/generate-application/code/templates/{phoenix-cli-skill.d.ts → skills/phoenix-cli-skill.d.ts} +0 -0
  167. /package/dist/application-spec/generate-application/code/templates/{phoenix-feedback-skill.d.ts → skills/phoenix-feedback-skill.d.ts} +0 -0
  168. /package/dist/application-spec/generate-application/code/templates/{phoenix-review-skill.d.ts → skills/phoenix-review-skill.d.ts} +0 -0
  169. /package/dist/application-spec/generate-application/code/templates/{phoenix-skill.d.ts → skills/phoenix-skill.d.ts} +0 -0
@@ -25,6 +25,7 @@ Declarative 3D product configurator built with Phoenix SDK. Domain model defined
25
25
  readonly projectStructure: `## Project Structure
26
26
  - \`spec/\` — Domain YAML specs (entities, relations, expressions, invariants, components, application config)
27
27
  - \`src/expressions/\` — Computed/derived entity properties (\`phx.domain.implementCustomExpressions()\`)
28
+ - \`src/actions/\` — Domain operations declared under \`actions:\` in the spec (\`phx.domain.implementActions()\`), run by controls and exposed to the assistant
28
29
  - \`src/invariants/\` — Business rule constraints (\`phx.domain.implementCustomInvariants()\`)
29
30
  - \`src/transformers/\` — Automatic state fixers (\`phx.frontend.transformerBuilder\`)
30
31
  - \`src/components/\` — ECS system components (\`phx.domain.attachComponent.withEntity()\`)
@@ -48,6 +49,7 @@ Declarative 3D product configurator built with Phoenix SDK. Domain model defined
48
49
  /** Expressions, invariants, transformers, components, webhooks, and filters at a glance. */
49
50
  readonly keyPatterns: `## Key Patterns
50
51
  - Expressions = computed properties on entities (prefix: \`$\`), implemented per-entity in src/expressions/
52
+ - Actions = named domain operations (\`actions:\` in YAML with \`params\` and \`result\` schemas), implemented in src/actions/, prepared through \`actionSystem.prepareAction()\` and exposed as assistant mutations through \`assistant.actions\`
51
53
  - Invariants = validation rules returning violations, static or dynamic
52
54
  - Transformers = auto-fixers maintaining consistency after state changes
53
55
  - Components defined in YAML with \`$read\`/\`$bind\` bindings, or custom in src/components/ using attachComponent builder
@@ -63,7 +65,7 @@ Declarative 3D product configurator built with Phoenix SDK. Domain model defined
63
65
  - Underscore folders (_shared/, _utils/) are NOT auto-imported`;
64
66
  /** The default-export rule of the SDK auto-import system. */
65
67
  readonly exportRules: `## Export Rules
66
- All files in src/expressions/, src/invariants/, src/transformers/, src/components/, src/handlers/, src/webhooks/ MUST use default exports. Can be a single item or an array. Named exports are ignored by the SDK auto-import system.`;
68
+ All files in src/expressions/, src/actions/, src/invariants/, src/transformers/, src/components/, src/handlers/, src/webhooks/ MUST use default exports. Can be a single item or an array. Named exports are ignored by the SDK auto-import system.`;
67
69
  /** Which components and handlers are bundled into the webhook. */
68
70
  readonly webhookMembership: `## Webhook Membership
69
71
  - User-authored components, admin components, and handlers are frontend-only by default.
@@ -76,6 +78,27 @@ All files in src/expressions/, src/invariants/, src/transformers/, src/component
76
78
  - \`.phoenix/\` directory — completely regenerated on each build
77
79
  - \`tsconfig.json\`, \`eslint.config.js\`, \`prettier.config.js\`, \`.prettierignore\`, \`.linguirc\`, \`.nvmrc\`
78
80
  - \`src/locales/\` .ts files — compiled from .po files`;
81
+ /** When and how a deployed domain model gets a new version and a migration, with the recipe for replacing the starter. */
82
+ readonly modelVersioning: `## Version the Domain Model Once It Is Deployed
83
+ A deployed model version cannot change, because saved configurations depend on it. \`pnpm phoenix deploy\` is refused, before anything is uploaded, when the model differs from the one already deployed under the same version. The version is not written anywhere: \`model.latest\` is always the highest numbered version plus one, so a spec with only \`latest\` is v1, and \`applicationVersion\` plays no part. \`pnpm phoenix dev\` accepts every model change, so a working preview does not show that the deploy will pass.
84
+
85
+ To change a model that has been deployed as v<N>:
86
+ 1. Keep the deployed model, unchanged, as \`model.v<N>\` in the spec. A file of its own, such as \`spec/model-v<N>.yaml\`, keeps \`latest\` readable.
87
+ 2. Make the change in \`model.latest\`, which is now v<N+1>.
88
+ 3. Add \`src/migrations/v<N+1>.ts\` exporting \`migrateToV<N+1>\`. It receives the saved entities of v<N> and returns entities that are valid in v<N+1>; \`ModelV<N>\` and \`ModelV<N+1>\` are generated global namespaces.
89
+
90
+ The admin model is versioned on its own, under \`src/admin/migrations/\`, with its types in \`AdminV<N>\`.
91
+
92
+ **Replacing the starter.** When the first deployment of the workspace was the starter, its \`Box\` model is v1 in production. When the real model replaces it, keep the starter's model as \`model.v1\` and define the real model in \`latest\`. Configurations saved from the starter are not worth converting, so the migration returns the starting state of the new model, the \`initialDomainEntities\` of its preset, and drops the boxes:
93
+ \`\`\`typescript
94
+ // src/migrations/v2.ts
95
+ import type { Migration } from '@salsita-ai/sdk/domain'
96
+
97
+ export const migrateToV2: Migration<ModelV1.AnyDomainEntity, ModelV2.AnyDomainEntity> = () => [
98
+ // the entities of the new model's preset
99
+ ]
100
+ \`\`\`
101
+ An entity of a type the new version no longer has must not stay in the returned state. An entity id starts with its type (\`Box-1\`), so an entity that keeps its data under a new type name needs a new id and every relation pointing at it rewritten. Read \`docs/phoenix/migrations.mdx\` before writing any other migration.`;
79
102
  /** How to find and read SDK documentation from the CLI. */
80
103
  readonly sdkDocumentation: `## SDK Documentation
81
104
 
@@ -0,0 +1,67 @@
1
+ export const sdkGuidanceSections={projectIntro:`## Phoenix Configurator Project
2
+
3
+ Declarative 3D product configurator built with Phoenix SDK. Domain model defined in YAML, custom logic in TypeScript.`,buildCommands:"## Build Commands\n- `pnpm phoenix build` — Full production build\n- `pnpm phoenix dev` — Development mode with hot reload\n- `pnpm phoenix sync` — Sync generated files without full build",devLogs:"## Dev Logs\n- `pnpm phoenix dev` writes all logs to `.phoenix/phoenix-dev.log`\n- Contains: spec parse errors, build errors, browser runtime errors, file watcher events\n- After making changes, always check the log for errors: `tail -50 .phoenix/phoenix-dev.log` or `grep ERROR .phoenix/phoenix-dev.log`\n- Rotated automatically at 10MB (`.log.1`, `.log.2` are older rotations)",projectStructure:"## Project Structure\n- `spec/` — Domain YAML specs (entities, relations, expressions, invariants, components, application config)\n- `src/expressions/` — Computed/derived entity properties (`phx.domain.implementCustomExpressions()`)\n- `src/actions/` — Domain operations declared under `actions:` in the spec (`phx.domain.implementActions()`), run by controls and exposed to the assistant\n- `src/invariants/` — Business rule constraints (`phx.domain.implementCustomInvariants()`)\n- `src/transformers/` — Automatic state fixers (`phx.frontend.transformerBuilder`)\n- `src/components/` — ECS system components (`phx.domain.attachComponent.withEntity()`)\n- `src/elements/` — Page-level React components (`phx.frontend.createCustomPhoenixLayoutElement()`)\n- `src/handlers/` — Event & lifecycle handlers\n- `src/webhooks/` — Server-side webhook handlers (`phx.webhooks.*Builder`)\n- `src/migrations/` — Schema version migrations (v*.ts)\n- `src/locales/` — Locale catalogs (authored .po sources; compiled .ts, do not edit)\n- `src/admin/` — Admin interface (mirrors main src/ structure)\n- `src/_shared/` — Shared utilities (NOT auto-imported, requires explicit imports)\n- `.phoenix/` — Auto-generated files (DO NOT EDIT)\n- `public/` — Static assets (3D models, textures, images, HDRI maps, fonts)\n- `assets/` — Uploadable resources (PDF templates, email assets)",architecturePrinciples:"## Architecture Principles\n- YAML-first: 99% of configuration in spec/*.yaml, custom code is last resort\n- ECS pattern: entities (domain objects) + components (behavior/UI) + systems (SDK runtime)\n- Reactive state: immutable domain state flows through RxJS pipelines\n- Data binding: `$read` (read-only), `$write` (write-only), `$bind` (bidirectional) connect components to domain\n- memoEval: all component implementations must use memoEval for stable object references",keyPatterns:"## Key Patterns\n- Expressions = computed properties on entities (prefix: `$`), implemented per-entity in src/expressions/\n- Actions = named domain operations (`actions:` in YAML with `params` and `result` schemas), implemented in src/actions/, prepared through `actionSystem.prepareAction()` and exposed as assistant mutations through `assistant.actions`\n- Invariants = validation rules returning violations, static or dynamic\n- Transformers = auto-fixers maintaining consistency after state changes\n- Components defined in YAML with `$read`/`$bind` bindings, or custom in src/components/ using attachComponent builder\n- Webhooks = server-side finalization tasks (PDF, email, CSV, image rendering)\n- Filters = contextual component activation via navigationStates, viewStates, modes, selectedEntityIds",fileConventions:`## File Conventions
4
+ - Components: PascalCase (.tsx)
5
+ - Utilities: kebab-case (.ts)
6
+ - Expressions: entity-name.ts
7
+ - Migrations: v*.ts (sequential)
8
+ - Specs: kebab-case.yaml
9
+ - Underscore folders (_shared/, _utils/) are NOT auto-imported`,exportRules:`## Export Rules
10
+ All files in src/expressions/, src/actions/, src/invariants/, src/transformers/, src/components/, src/handlers/, src/webhooks/ MUST use default exports. Can be a single item or an array. Named exports are ignored by the SDK auto-import system.`,webhookMembership:"## Webhook Membership\n- User-authored components, admin components, and handlers are frontend-only by default.\n- A handler that registers `createMapPublishedRevisionDataHandler`, `createMapPublishedChunkedDataHandler`, `createDataRowValidatorHandler`, or `createSheetValidatorHandler` is included in the webhook automatically.\n- To include another component or handler in the webhook, put `'use webhook'` as its first statement (leading comments are allowed).\n- A webhook-included file and its runtime local-import closure must not import browser SDK entrypoints (`application`, `admin`, `configurator`, `editor`, `react`, `ui`) or style/font/image assets. Spec parsing reports the unsafe import and inclusion reason as a diagnostic.\n- Keep browser rendering in an unpinned file. Move shared server-safe logic into a separate module when both frontend and webhook code need it.",doNotEdit:"## Do Not Edit\n- `.phoenix/` directory — completely regenerated on each build\n- `tsconfig.json`, `eslint.config.js`, `prettier.config.js`, `.prettierignore`, `.linguirc`, `.nvmrc`\n- `src/locales/` .ts files — compiled from .po files",modelVersioning:"## Version the Domain Model Once It Is Deployed\nA deployed model version cannot change, because saved configurations depend on it. `pnpm phoenix deploy` is refused, before anything is uploaded, when the model differs from the one already deployed under the same version. The version is not written anywhere: `model.latest` is always the highest numbered version plus one, so a spec with only `latest` is v1, and `applicationVersion` plays no part. `pnpm phoenix dev` accepts every model change, so a working preview does not show that the deploy will pass.\n\nTo change a model that has been deployed as v<N>:\n1. Keep the deployed model, unchanged, as `model.v<N>` in the spec. A file of its own, such as `spec/model-v<N>.yaml`, keeps `latest` readable.\n2. Make the change in `model.latest`, which is now v<N+1>.\n3. Add `src/migrations/v<N+1>.ts` exporting `migrateToV<N+1>`. It receives the saved entities of v<N> and returns entities that are valid in v<N+1>; `ModelV<N>` and `ModelV<N+1>` are generated global namespaces.\n\nThe admin model is versioned on its own, under `src/admin/migrations/`, with its types in `AdminV<N>`.\n\n**Replacing the starter.** When the first deployment of the workspace was the starter, its `Box` model is v1 in production. When the real model replaces it, keep the starter's model as `model.v1` and define the real model in `latest`. Configurations saved from the starter are not worth converting, so the migration returns the starting state of the new model, the `initialDomainEntities` of its preset, and drops the boxes:\n```typescript\n// src/migrations/v2.ts\nimport type { Migration } from '@salsita-ai/sdk/domain'\n\nexport const migrateToV2: Migration<ModelV1.AnyDomainEntity, ModelV2.AnyDomainEntity> = () => [\n // the entities of the new model's preset\n]\n```\nAn entity of a type the new version no longer has must not stay in the returned state. An entity id starts with its type (`Box-1`), so an entity that keeps its data under a new type name needs a new id and every relation pointing at it rewritten. Read `docs/phoenix/migrations.mdx` before writing any other migration.",sdkDocumentation:'## SDK Documentation\n\nAlways search SDK docs for relevant examples before implementing Phoenix features.\n\n### Commands\n- `pnpm phoenix docs` — Full documentation index with descriptions\n- `pnpm phoenix docs -s <term>` — Search all doc content and metadata (case-insensitive)\n- `pnpm phoenix docs --dir` — Print the absolute docs directory for grep or ripgrep\n- `pnpm phoenix docs -r <path>` — Read specific doc (raw text to stdout)\n- `pnpm phoenix docs -c <category>` — List docs in a category\n- `pnpm phoenix docs -l` — List all doc paths with titles\n\n### Key Docs\n- `docs/phoenix/getting-started.mdx` — Setup, prerequisites, first project\n- `docs/phoenix/quickstart.mdx` — End-to-end tutorial building a configurator\n- `docs/phoenix/domain-model.mdx` — Entities, attributes, relations, specs\n- `docs/phoenix/components.mdx` — ECS pattern, YAML components, custom components\n- `docs/phoenix/project-structure.mdx` — Files, folders, conventions\n- `docs/phoenix/best-practices.mdx` — YAML-first, code patterns, common pitfalls\n- `docs/phoenix/state-management.mdx` — Reactive state, RxJS pipelines, data flow\n- `docs/phoenix/expression-context.mdx` — Expression API, context object, helpers\n- `docs/phoenix/webhooks.mdx` — Server-side finalization workflows\n- `docs/phoenix/finalization.mdx` — Finalization system, PDF/email/CSV generation\n- `docs/phoenix/migrations.mdx` — Schema versioning, migration files\n\n### Agent Workflow\n1. Find relevant docs by content: `pnpm phoenix docs -s \'camera\'`\n2. Read matching doc: `pnpm phoenix docs -r docs/3d-scene/cameras/transitions.mdx`\n3. Search within a doc: `pnpm phoenix docs -r <path> | grep "example"`\n4. Search titles only as a fallback: `pnpm phoenix docs -l | grep "camera"`\n5. Search generated files directly when needed: `grep -rn "camera" "$(pnpm phoenix docs --dir)"`\n6. Browse a topic area: `pnpm phoenix docs -c "Layouts"`',assetInspection:"## 3D Asset Inspection\n\nRun `pnpm phoenix inspect:assets` to discover all glTF/GLB files in `public/` and get a summary table. Use `--file <path>` for detailed single-file inspection.\n\n**When to use:** before writing YAML/code that references objectName, materialsToOverride, morphTargetInfluences, or object positioning.\n\n**What it shows per node:**\n- Name, type (Mesh/Light/Camera/Empty), children\n- Transform: `pos=`, `rot=`, `scale=` (only non-default values shown)\n- Per-node morph targets: `morphs=Width,Height`\n- Bounding box: `bounds=WxHxD` (width x height x depth in meters)\n- Origin placement: `origin=center`, `bottom-center`, `top-center`, `bottom-corner`, etc. with numerical offset\n\n**Use cases:**\n- Find exact node names for `objectName` in scene entity components\n- Find material names for `materialsToOverride`\n- Find morph target names for `morphTargetInfluences`\n- Check model dimensions (bounds) for positioning, dimension indicators, or invariants\n- Check origin placement to understand how models anchor (center vs bottom-center)\n- Use `--json` for programmatic access to all data\n\n**Flags:** `--objects`, `--materials`, `--morphs`, `--animations`, `--json`",screenshotCli:"## Screenshot CLI\n\nCapture 3D canvas screenshots from a running dev server via `pnpm phoenix screenshot`.\n\n**Prerequisites:**\n1. `phoenix dev` must be running (starts the WebSocket server on port 3020)\n2. Configurator must be open in a browser (the browser does the actual rendering)\n\n**Options:**\n- `--output, -o` — Output file path (default: `./screenshot.png`)\n- `--width, -w` — Width in pixels (default: 1920)\n- `--height` — Height in pixels (default: 1080)\n- `--format, -f` — Image format: PNG or JPEG (default: PNG)\n- `--port` — WebSocket server port (default: 3020)\n\n**Example workflow:**\n1. Start dev server: `pnpm phoenix dev`\n2. Open configurator in browser (or via Playwright MCP)\n3. Take screenshot: `pnpm phoenix screenshot -o ./render.png -w 2560 --height 1440`\n\n**Use cases:** visual regression testing, generating product images, debugging 3D renders",inspectState:"## Inspect: State\n\nGet the current editor runtime state as JSON via `pnpm phoenix inspect:state`.\n\n**Prerequisites:** same as screenshot (dev server running + browser open)\n\n**Options:**\n- `--output, -o` — Output file path (prints to stdout if omitted)\n- `--sections, -s` — Comma-separated: entities, navigation, validation (default: all)\n- `--pretty` — Pretty-print JSON (default: true)\n- `--port` — WebSocket server port (default: 3020)\n\n**Example:** `pnpm phoenix inspect:state -o ./state.json -s entities,navigation`\n\n**Lifecycle status:** Always included in response. Shows which systems are ready:\n- `domainLoaded` — domain data loaded\n- `domainContextReady` — domain context initialized (entities available)\n- `configuratorReady` — 3D configurator initialized\n- `sessionAuthenticated` — session service active\n- `editorMode` — editor mode enabled\n\n**Tips:** Use `--sections` to limit payload size. Pipe through `head`/`tail` for partial output (e.g., `pnpm phoenix inspect:state | head -50`).\n\n**Use cases:** AI agent context, debugging entity state, inspecting navigation/validation",inspectData:`## Inspect: Data
11
+
12
+ Get the current editor context data as JSON via \`pnpm phoenix inspect:data\`.
13
+
14
+ **Prerequisites:** same as screenshot (dev server running + browser open)
15
+
16
+ **Options:**
17
+ - \`--output, -o\` — Output file path (prints to stdout if omitted)
18
+ - \`--pretty\` — Pretty-print JSON (default: true)
19
+ - \`--port\` — WebSocket server port (default: 3020)
20
+
21
+ **Returns:** publishedData, frontendData, backendData, sessionData, accessData, webComponentProperties
22
+
23
+ **Example:** \`pnpm phoenix inspect:data -o ./data.json\`
24
+
25
+ **Tips:** Pipe through \`head\`/\`tail\` for partial output (e.g., \`pnpm phoenix inspect:data | head -30\`).
26
+
27
+ **Use cases:** AI agent context, debugging published/session/access data`,inspectScene:`## Inspect: Scene
28
+
29
+ Get the current Three.js scene graph as JSON via \`pnpm phoenix inspect:scene\`.
30
+
31
+ **Prerequisites:** same as screenshot (dev server running + browser open)
32
+
33
+ **Options:**
34
+ - \`--output, -o\` — Output file path (prints to stdout if omitted)
35
+ - \`--pretty\` — Pretty-print JSON (default: true)
36
+ - \`--port\` — WebSocket server port (default: 3020)
37
+
38
+ **Returns:** Recursive scene tree with per-node: name, type, visible, position, rotation, scale, userData (entityId), and for meshes: geometry name, bounding box, material properties (color, metalness, roughness, opacity, textures), morph target influences. Lights include color, intensity, and type-specific properties.
39
+
40
+ **Example:** \`pnpm phoenix inspect:scene -o ./scene.json\`
41
+
42
+ **Tips:** Scene graphs can be large. Pipe through \`head\`/\`tail\` for partial output.
43
+
44
+ **Use cases:** AI agent context, debugging 3D scene layout, inspecting materials and lighting`,playwrightMcpSetup:`## Playwright MCP Setup (for AI agents)
45
+
46
+ To interact with the 3D configurator in a browser, Playwright MCP needs GPU-enabled flags for WebGL rendering. Add to \`~/.claude/.mcp.json\`:
47
+
48
+ \`\`\`json
49
+ {
50
+ "mcpServers": {
51
+ "playwright": {
52
+ "command": "npx",
53
+ "args": [
54
+ "@playwright/mcp@latest",
55
+ "--headed",
56
+ "--launch-options",
57
+ "{\\"args\\":[\\"--use-gl=swiftshader\\",\\"--enable-webgl\\",\\"--ignore-gpu-blocklist\\"]}"
58
+ ]
59
+ }
60
+ }
61
+ }
62
+ \`\`\`
63
+
64
+ Key flags:
65
+ - \`--headed\` — visible browser (required for GPU rendering)
66
+ - \`--use-gl=swiftshader\` — software GPU fallback for environments without a physical GPU
67
+ - \`--enable-webgl\` + \`--ignore-gpu-blocklist\` — ensure WebGL is available`};
@@ -2,6 +2,7 @@ import { Branded } from '@salsita-ai/common/utils';
2
2
  import type { ImportedTransformer } from '#package/domain';
3
3
  import type { RuntimeReferenceStore } from '../../../caching/index.js';
4
4
  import type { ApplicationSpec } from '../../../parse-application-files/index.js';
5
+ import { ActionsRuntime } from '../sections/actions-runtime.js';
5
6
  import { AssistantRuntime } from '../sections/assistant-runtime.js';
6
7
  import { ComponentOptics } from '../sections/component-optic.js';
7
8
  import { type ExpressionImplementations, ExpressionRuntime, type InvariantImplementations, type MissingExpression } from '../sections/expression-runtime.js';
@@ -20,6 +21,7 @@ export type ConfiguratorRuntime = Branded<'ConfiguratorRuntime', {
20
21
  readonly themeRuntime: ThemeRuntime;
21
22
  readonly transformersRuntime: TransformersRuntime;
22
23
  readonly assistantRuntime: AssistantRuntime;
24
+ readonly actionsRuntime: ActionsRuntime;
23
25
  }>;
24
26
  export type ConfiguratorRuntimeResult = {
25
27
  readonly ok: true;
@@ -1 +1 @@
1
- import{Branded as t}from"@salsita-ai/common/utils";import{AssistantRuntime as e}from"../sections/assistant-runtime.js";import{ComponentOptics as o}from"../sections/component-optic.js";import{ExpressionRuntime as r}from"../sections/expression-runtime.js";import{PresetRuntime as s}from"../sections/preset-runtime.js";import{ThemeRuntime as i}from"../sections/theme-runtime.js";import{TransformersRuntime as n}from"../sections/transformers-runtime.js";import{ConfiguratorModelRuntime as m}from"./configurator-model-runtime.js";import{ConfiguratorStatesRuntime as u}from"./states-runtime.js";export const ConfiguratorRuntime=(p,c,f,a,g)=>{let R=m(p.configurator,c),j=r(p.configurator,c,f,"configuratorExpressionRuntime",g);return j.ok?{ok:!0,runtime:t({modelRuntime:R,componentOptics:o(c,{components:p.configurator.components,referenceScope:"configuratorRuntime/componentOptics",modelRuntime:R}),presetRuntime:s(c,{referenceScope:"configuratorRuntime/presetRuntime",presets:p.configurator.presets.presets}),statesRuntime:u(p,c),expressionRuntime:j.runtime,themeRuntime:i(p,c),transformersRuntime:n(p,c,a),assistantRuntime:e(p,c)})}:{ok:!1,missingExpressions:j.missing}};
1
+ import{Branded as t}from"@salsita-ai/common/utils";import{ActionsRuntime as o}from"../sections/actions-runtime.js";import{AssistantRuntime as e}from"../sections/assistant-runtime.js";import{ComponentOptics as r}from"../sections/component-optic.js";import{ExpressionRuntime as s}from"../sections/expression-runtime.js";import{PresetRuntime as i}from"../sections/preset-runtime.js";import{ThemeRuntime as n}from"../sections/theme-runtime.js";import{TransformersRuntime as m}from"../sections/transformers-runtime.js";import{ConfiguratorModelRuntime as u}from"./configurator-model-runtime.js";import{ConfiguratorStatesRuntime as c}from"./states-runtime.js";export const ConfiguratorRuntime=(p,a,f,g,R)=>{let j=u(p.configurator,a),x=s(p.configurator,a,f,"configuratorExpressionRuntime",R);return x.ok?{ok:!0,runtime:t({modelRuntime:j,componentOptics:r(a,{components:p.configurator.components,referenceScope:"configuratorRuntime/componentOptics",modelRuntime:j}),presetRuntime:i(a,{referenceScope:"configuratorRuntime/presetRuntime",presets:p.configurator.presets.presets}),statesRuntime:c(p,a),expressionRuntime:x.runtime,themeRuntime:n(p,a),transformersRuntime:m(p,a,g),assistantRuntime:e(p,a),actionsRuntime:o(p.configurator.actions,a)})}:{ok:!1,missingExpressions:x.missing}};
@@ -0,0 +1,14 @@
1
+ import { Branded } from '@salsita-ai/common/utils';
2
+ import type { ActionDefinitions } from '#package/domain';
3
+ import type { RuntimeReferenceStore } from '../../../caching/index.js';
4
+ import type { Actions } from '../../../parse-application-files/index.js';
5
+ /**
6
+ * The declared actions as the runtime spec slice holds them, with change tracking: the definitions
7
+ * are rebuilt only when their declaration changes, so a hot reload of an unrelated section keeps
8
+ * the action system's compiled validators.
9
+ */
10
+ export type ActionsRuntime = Branded<'ActionsRuntime', {
11
+ readonly definitions: ActionDefinitions;
12
+ readonly changed: boolean;
13
+ }>;
14
+ export declare const ActionsRuntime: (actions: Actions, referenceStore: RuntimeReferenceStore) => ActionsRuntime;
@@ -0,0 +1 @@
1
+ import i from"lodash-es/isEqual.js";import{Branded as e}from"@salsita-ai/common/utils";export const ActionsRuntime=(t,n)=>{let o=n.createBuilder("configuratorRuntime/actions",{build:i=>i.definition,getKey:i=>i.definition.name,compare:(e,t)=>i(e.definition,t.definition)}),a=!1,r=new Set,d={};for(let i of t.values()){let e={definition:{name:String(i.name),...void 0===i.description?{}:{description:i.description},params:i.paramsSchema}},t=o.build(e);t.changed&&(a=!0),r.add(o.getKey(e)),d[t.output.name]=t.output}return o.cleanup(r)&&(a=!0),e({definitions:d,changed:a})};
@@ -1,3 +1,4 @@
1
+ export { ActionsRuntime } from './actions-runtime.js';
1
2
  export { AssistantRuntime } from './assistant-runtime.js';
2
3
  export { ComponentOptics, type ComponentOptic } from './component-optic.js';
3
4
  export { DataRuntime } from './data-runtime.js';
@@ -1 +1 @@
1
- export{AssistantRuntime}from"./assistant-runtime.js";export{ComponentOptics}from"./component-optic.js";export{DataRuntime}from"./data-runtime.js";export{ExpressionRuntime}from"./expression-runtime.js";export{PresetRuntime}from"./preset-runtime.js";export{ThemeRuntime}from"./theme-runtime.js";export{TransformersRuntime}from"./transformers-runtime.js";export{evaluateStaticMemoEvalPlan,generateMemoEvalPlanRuntime}from"./generate-memo-eval-plan-runtime.js";export{evaluateReflectionConstants,findReflectionConstants}from"./reflection-constants-runtime.js";
1
+ export{ActionsRuntime}from"./actions-runtime.js";export{AssistantRuntime}from"./assistant-runtime.js";export{ComponentOptics}from"./component-optic.js";export{DataRuntime}from"./data-runtime.js";export{ExpressionRuntime}from"./expression-runtime.js";export{PresetRuntime}from"./preset-runtime.js";export{ThemeRuntime}from"./theme-runtime.js";export{TransformersRuntime}from"./transformers-runtime.js";export{evaluateStaticMemoEvalPlan,generateMemoEvalPlanRuntime}from"./generate-memo-eval-plan-runtime.js";export{evaluateReflectionConstants,findReflectionConstants}from"./reflection-constants-runtime.js";
@@ -3,7 +3,7 @@ import { type RuntimeReferenceStore } from '../../caching/index.js';
3
3
  import type { ParseContext, RawProjectFiles, TextFile } from '../../core/index.js';
4
4
  import { type ModuleAnalysis, type ModuleAnalyzer, type WebhookMembership } from './module-analysis.js';
5
5
  /** Known file categories used for path-based sorting. */
6
- export type FileCategory = 'spec' | 'expressions' | 'handlers' | 'elements' | 'transformers' | 'components' | 'invariants' | 'migrations' | 'adminMigrations' | 'adminExpressions' | 'adminInvariants' | 'adminComponents' | 'adminElements' | 'webhooks' | 'locales' | 'mockTemplates' | 'projectFiles' | 'customCode';
6
+ export type FileCategory = 'spec' | 'expressions' | 'actions' | 'handlers' | 'elements' | 'transformers' | 'components' | 'invariants' | 'migrations' | 'adminMigrations' | 'adminExpressions' | 'adminInvariants' | 'adminComponents' | 'adminElements' | 'webhooks' | 'locales' | 'mockTemplates' | 'projectFiles' | 'customCode';
7
7
  /**
8
8
  * A text file categorized by path pattern, with dep-aware change tracking.
9
9
  *
@@ -32,6 +32,8 @@ export type CategorizedFiles = Branded<'CategorizedFiles', {
32
32
  readonly specChanged: boolean;
33
33
  readonly expressions: readonly CategorizedFile[];
34
34
  readonly expressionsChanged: boolean;
35
+ readonly actions: readonly CategorizedFile[];
36
+ readonly actionsChanged: boolean;
35
37
  readonly handlers: readonly CategorizedFile[];
36
38
  readonly handlersChanged: boolean;
37
39
  readonly elements: readonly CategorizedFile[];
@@ -1 +1 @@
1
- import e from"lodash-es/isEqual.js";import{Branded as n}from"@salsita-ai/common/utils";import{computeFileDepHashes as s}from"../../caching/index.js";import{DiagnosticCode as o,diagnosticMessages as a}from"../../errors/index.js";import{findWebhookHandlerRegistration as i}from"./module-analysis.js";import{findWebhookSafetyViolation as t}from"./webhook-safety.js";export const buildProjectFileIndex=e=>new Map([...e.spec,...e.expressions,...e.handlers,...e.elements,...e.transformers,...e.components,...e.invariants,...e.migrations,...e.adminMigrations,...e.adminExpressions,...e.adminInvariants,...e.adminComponents,...e.adminElements,...e.webhooks,...e.locales,...e.mockTemplates,...e.projectFiles,...e.customCode].map(e=>[e.file.name,e]));let r=e=>/\/_[^/]+\//.test(e),m=[{path:"spec/",key:"spec"},{path:"src/admin/expressions/",key:"adminExpressions"},{path:"src/admin/invariants/",key:"adminInvariants"},{path:"src/admin/components/",key:"adminComponents"},{path:"src/admin/elements/",key:"adminElements"},{path:"src/admin/migrations/",key:"adminMigrations"},{path:"src/expressions/",key:"expressions"},{path:"src/handlers/",key:"handlers"},{path:"src/elements/",key:"elements"},{path:"src/transformers/",key:"transformers"},{path:"src/components/",key:"components"},{path:"src/invariants/",key:"invariants"},{path:"src/migrations/",key:"migrations"},{path:"src/webhooks/",key:"webhooks"},{path:"src/locales/",key:"locales"},{path:"mocks/",key:"mockTemplates"}],l=new Set(["package.json",".gitignore","CLAUDE.md",".cursor/rules/phoenix.mdc"]),d=new Set(["migrations","adminMigrations"]),p=m.filter(({path:e,key:n})=>e.startsWith("src/")&&d.has(n)),c=(e,n)=>{let s=e.slice(n.length).split("/");return!(s.length<=1)&&s.slice(0,-1).some(e=>!e.startsWith("_"))},h=/\.(ts|tsx|js|jsx)$/,f=(e,n)=>{if("components"===e||"adminComponents"===e)return n.hasUseWebhookDirective?{membership:"pinned",logSuffix:"webhook ('use webhook' directive)"}:{membership:"frontend",logSuffix:"frontend only (no 'use webhook' directive)"};if("handlers"===e){let e=i(n);return void 0!==e?{membership:"named-handler",logSuffix:`webhook (the ${e} registration)`}:n.hasUseWebhookDirective?{membership:"pinned",logSuffix:"webhook ('use webhook' directive)"}:{membership:"frontend",logSuffix:"frontend only (not a named handler or pin)"}}};export const CategorizedFiles=(i,d,g,u)=>{let k={spec:[],expressions:[],handlers:[],elements:[],transformers:[],components:[],invariants:[],migrations:[],adminMigrations:[],adminExpressions:[],adminInvariants:[],adminComponents:[],adminElements:[],webhooks:[],locales:[],mockTemplates:[],projectFiles:[],customCode:[]},b=[];for(let e of i.files){if("text"!==e.kind)continue;if(r(e.name)){k.customCode.push({file:e,category:"customCode"});continue}if(l.has(e.name)){k.projectFiles.push({file:e,category:"projectFiles"});continue}let n=!1;for(let{path:s,key:o}of m)if(e.name.startsWith(s)){k[o].push({file:e,category:o}),n=!0;break}n||b.push(e.name)}for(let e of b)e.startsWith("src/")&&g.error(o.UNKNOWN_SRC_PATH,a.UNKNOWN_SRC_PATH(e));for(let{path:e,key:n}of p)for(let s of k[n])c(s.file.name,e)&&g.error(o.INVALID_SUBDIRECTORY,a.INVALID_SUBDIRECTORY(s.file.name,e));if(k.webhooks.length>0&&!k.webhooks.some(e=>"src/webhooks/index.ts"===e.file.name)&&g.error(o.MISSING_WEBHOOK_INDEX,a.MISSING_WEBHOOK_INDEX()),g.hasErrors)return;let C=[];for(let e of Object.values(k))for(let n of e)C.push(n.file.name);let v=s(i,C),{analyzer:x,logger:E}=u,y=d.createBuilder("categorizedFiles/moduleAnalysis",{build:e=>{try{return{analysis:x(e.name,e.content)}}catch(e){return{error:e instanceof Error?e.message:String(e)}}},getKey:e=>e.name,compare:(e,n)=>e.content===n.content}),I=new Set,w=e=>{if(!h.test(e.name))return;I.add(e.name);let n=y.build({name:e.name,content:e.content}).output;return void 0!==n.error?void g.error(o.MODULE_ANALYSIS_FAILED,a[o.MODULE_ANALYSIS_FAILED](e.name,n.error)):n.analysis},S={},j={};for(let[e,s]of Object.entries(k)){let o=d.createBuilder(`categorizedFiles/${e}`,{build:e=>e,getKey:e=>e.name,compare:(e,n)=>e.depHash===n.depHash}),a=new Set;S[e]=s.map(e=>{let s=v[e.file.name]??{deps:[],hash:""},{changed:i}=o.build({name:e.file.name,depHash:s.hash});a.add(e.file.name);let t=w(e.file),r=void 0===t?void 0:f(e.category,t);return void 0!==r&&E.debug(`Webhook membership: ${e.file.name} -> ${r.logSuffix}`),n({file:e.file,category:e.category,deps:s.deps,depHash:s.hash,changed:i,analysis:t,webhookMembership:r?.membership})}),j[e]=o.cleanup(a)}y.cleanup(I);let _=(e,n)=>n.some(e=>e.changed)||!0===j[e],F=d.createBuilder("categorizedFiles/envs",{build:e=>e.envs,getKey:()=>"singleton",compare:(n,s)=>e(n.envs,s.envs)}).build({envs:i.envs}),M=n({spec:S.spec,specChanged:_("spec",S.spec),expressions:S.expressions,expressionsChanged:_("expressions",S.expressions),handlers:S.handlers,handlersChanged:_("handlers",S.handlers),elements:S.elements,elementsChanged:_("elements",S.elements),transformers:S.transformers,transformersChanged:_("transformers",S.transformers),components:S.components,componentsChanged:_("components",S.components),invariants:S.invariants,invariantsChanged:_("invariants",S.invariants),migrations:S.migrations,migrationsChanged:_("migrations",S.migrations),adminMigrations:S.adminMigrations,adminMigrationsChanged:_("adminMigrations",S.adminMigrations),adminExpressions:S.adminExpressions,adminExpressionsChanged:_("adminExpressions",S.adminExpressions),adminInvariants:S.adminInvariants,adminInvariantsChanged:_("adminInvariants",S.adminInvariants),adminComponents:S.adminComponents,adminComponentsChanged:_("adminComponents",S.adminComponents),adminElements:S.adminElements,adminElementsChanged:_("adminElements",S.adminElements),webhooks:S.webhooks,webhooksChanged:_("webhooks",S.webhooks),locales:S.locales,localesChanged:_("locales",S.locales),mockTemplates:S.mockTemplates,mockTemplatesChanged:_("mockTemplates",S.mockTemplates),projectFiles:S.projectFiles,projectFilesChanged:_("projectFiles",S.projectFiles),customCode:S.customCode,customCodeChanged:_("customCode",S.customCode),envs:F.output,envsChanged:F.changed}),O=buildProjectFileIndex(M);for(let e of O.values()){if("pinned"!==e.webhookMembership&&"named-handler"!==e.webhookMembership)continue;let n=t(e,O);void 0!==n&&g.error(o.WEBHOOK_UNSAFE_IMPORT,a[o.WEBHOOK_UNSAFE_IMPORT](n.fileName,n.inclusionReason,n.importer,n.specifier))}return M};
1
+ import e from"lodash-es/isEqual.js";import{Branded as n}from"@salsita-ai/common/utils";import{computeFileDepHashes as s}from"../../caching/index.js";import{DiagnosticCode as o,diagnosticMessages as a}from"../../errors/index.js";import{findWebhookHandlerRegistration as i}from"./module-analysis.js";import{findWebhookSafetyViolation as t}from"./webhook-safety.js";export const buildProjectFileIndex=e=>new Map([...e.spec,...e.expressions,...e.actions,...e.handlers,...e.elements,...e.transformers,...e.components,...e.invariants,...e.migrations,...e.adminMigrations,...e.adminExpressions,...e.adminInvariants,...e.adminComponents,...e.adminElements,...e.webhooks,...e.locales,...e.mockTemplates,...e.projectFiles,...e.customCode].map(e=>[e.file.name,e]));let r=e=>/\/_[^/]+\//.test(e),m=[{path:"spec/",key:"spec"},{path:"src/admin/expressions/",key:"adminExpressions"},{path:"src/admin/invariants/",key:"adminInvariants"},{path:"src/admin/components/",key:"adminComponents"},{path:"src/admin/elements/",key:"adminElements"},{path:"src/admin/migrations/",key:"adminMigrations"},{path:"src/expressions/",key:"expressions"},{path:"src/actions/",key:"actions"},{path:"src/handlers/",key:"handlers"},{path:"src/elements/",key:"elements"},{path:"src/transformers/",key:"transformers"},{path:"src/components/",key:"components"},{path:"src/invariants/",key:"invariants"},{path:"src/migrations/",key:"migrations"},{path:"src/webhooks/",key:"webhooks"},{path:"src/locales/",key:"locales"},{path:"mocks/",key:"mockTemplates"}],l=new Set(["package.json",".gitignore","CLAUDE.md",".cursor/rules/phoenix.mdc"]),d=new Set(["migrations","adminMigrations"]),c=m.filter(({path:e,key:n})=>e.startsWith("src/")&&d.has(n)),p=(e,n)=>{let s=e.slice(n.length).split("/");return!(s.length<=1)&&s.slice(0,-1).some(e=>!e.startsWith("_"))},h=/\.(ts|tsx|js|jsx)$/,f=(e,n)=>{if("components"===e||"adminComponents"===e)return n.hasUseWebhookDirective?{membership:"pinned",logSuffix:"webhook ('use webhook' directive)"}:{membership:"frontend",logSuffix:"frontend only (no 'use webhook' directive)"};if("handlers"===e){let e=i(n);return void 0!==e?{membership:"named-handler",logSuffix:`webhook (the ${e} registration)`}:n.hasUseWebhookDirective?{membership:"pinned",logSuffix:"webhook ('use webhook' directive)"}:{membership:"frontend",logSuffix:"frontend only (not a named handler or pin)"}}};export const CategorizedFiles=(i,d,g,u)=>{let k={spec:[],expressions:[],actions:[],handlers:[],elements:[],transformers:[],components:[],invariants:[],migrations:[],adminMigrations:[],adminExpressions:[],adminInvariants:[],adminComponents:[],adminElements:[],webhooks:[],locales:[],mockTemplates:[],projectFiles:[],customCode:[]},b=[];for(let e of i.files){if("text"!==e.kind)continue;if(r(e.name)){k.customCode.push({file:e,category:"customCode"});continue}if(l.has(e.name)){k.projectFiles.push({file:e,category:"projectFiles"});continue}let n=!1;for(let{path:s,key:o}of m)if(e.name.startsWith(s)){k[o].push({file:e,category:o}),n=!0;break}n||b.push(e.name)}for(let e of b)e.startsWith("src/")&&g.error(o.UNKNOWN_SRC_PATH,a.UNKNOWN_SRC_PATH(e));for(let{path:e,key:n}of c)for(let s of k[n])p(s.file.name,e)&&g.error(o.INVALID_SUBDIRECTORY,a.INVALID_SUBDIRECTORY(s.file.name,e));if(k.webhooks.length>0&&!k.webhooks.some(e=>"src/webhooks/index.ts"===e.file.name)&&g.error(o.MISSING_WEBHOOK_INDEX,a.MISSING_WEBHOOK_INDEX()),g.hasErrors)return;let C=[];for(let e of Object.values(k))for(let n of e)C.push(n.file.name);let v=s(i,C),{analyzer:x,logger:y}=u,E=d.createBuilder("categorizedFiles/moduleAnalysis",{build:e=>{try{return{analysis:x(e.name,e.content)}}catch(e){return{error:e instanceof Error?e.message:String(e)}}},getKey:e=>e.name,compare:(e,n)=>e.content===n.content}),I=new Set,w=e=>{if(!h.test(e.name))return;I.add(e.name);let n=E.build({name:e.name,content:e.content}).output;return void 0!==n.error?void g.error(o.MODULE_ANALYSIS_FAILED,a[o.MODULE_ANALYSIS_FAILED](e.name,n.error)):n.analysis},S={},j={};for(let[e,s]of Object.entries(k)){let o=d.createBuilder(`categorizedFiles/${e}`,{build:e=>e,getKey:e=>e.name,compare:(e,n)=>e.depHash===n.depHash}),a=new Set;S[e]=s.map(e=>{let s=v[e.file.name]??{deps:[],hash:""},{changed:i}=o.build({name:e.file.name,depHash:s.hash});a.add(e.file.name);let t=w(e.file),r=void 0===t?void 0:f(e.category,t);return void 0!==r&&y.debug(`Webhook membership: ${e.file.name} -> ${r.logSuffix}`),n({file:e.file,category:e.category,deps:s.deps,depHash:s.hash,changed:i,analysis:t,webhookMembership:r?.membership})}),j[e]=o.cleanup(a)}E.cleanup(I);let _=(e,n)=>n.some(e=>e.changed)||!0===j[e],F=d.createBuilder("categorizedFiles/envs",{build:e=>e.envs,getKey:()=>"singleton",compare:(n,s)=>e(n.envs,s.envs)}).build({envs:i.envs}),M=n({spec:S.spec,specChanged:_("spec",S.spec),expressions:S.expressions,expressionsChanged:_("expressions",S.expressions),actions:S.actions,actionsChanged:_("actions",S.actions),handlers:S.handlers,handlersChanged:_("handlers",S.handlers),elements:S.elements,elementsChanged:_("elements",S.elements),transformers:S.transformers,transformersChanged:_("transformers",S.transformers),components:S.components,componentsChanged:_("components",S.components),invariants:S.invariants,invariantsChanged:_("invariants",S.invariants),migrations:S.migrations,migrationsChanged:_("migrations",S.migrations),adminMigrations:S.adminMigrations,adminMigrationsChanged:_("adminMigrations",S.adminMigrations),adminExpressions:S.adminExpressions,adminExpressionsChanged:_("adminExpressions",S.adminExpressions),adminInvariants:S.adminInvariants,adminInvariantsChanged:_("adminInvariants",S.adminInvariants),adminComponents:S.adminComponents,adminComponentsChanged:_("adminComponents",S.adminComponents),adminElements:S.adminElements,adminElementsChanged:_("adminElements",S.adminElements),webhooks:S.webhooks,webhooksChanged:_("webhooks",S.webhooks),locales:S.locales,localesChanged:_("locales",S.locales),mockTemplates:S.mockTemplates,mockTemplatesChanged:_("mockTemplates",S.mockTemplates),projectFiles:S.projectFiles,projectFilesChanged:_("projectFiles",S.projectFiles),customCode:S.customCode,customCodeChanged:_("customCode",S.customCode),envs:F.output,envsChanged:F.changed}),O=buildProjectFileIndex(M);for(let e of O.values()){if("pinned"!==e.webhookMembership&&"named-handler"!==e.webhookMembership)continue;let n=t(e,O);void 0!==n&&g.error(o.WEBHOOK_UNSAFE_IMPORT,a[o.WEBHOOK_UNSAFE_IMPORT](n.fileName,n.inclusionReason,n.importer,n.specifier))}return M};
@@ -1,4 +1,5 @@
1
1
  export * from './classified-spec-file.js';
2
+ export * from './merged-actions.js';
2
3
  export * from './merged-admin-components.js';
3
4
  export * from './merged-admin-expressions.js';
4
5
  export * from './merged-admin-invariants.js';
@@ -1 +1 @@
1
- export*from"./classified-spec-file.js";export*from"./merged-admin-components.js";export*from"./merged-admin-expressions.js";export*from"./merged-admin-invariants.js";export*from"./merged-admin-states.js";export*from"./merged-admin-model.js";export*from"./merged-components.js";export*from"./merged-configurator-states.js";export*from"./merged-defaults.js";export*from"./merged-model.js";export*from"./merged-admin-pages.js";export*from"./merged-admin-presets.js";export*from"./merged-admin-relations.js";export*from"./merged-assistant.js";export*from"./merged-budgets.js";export*from"./merged-data-schemas.js";export*from"./merged-data-spec.js";export*from"./merged-expressions.js";export*from"./merged-presets.js";export*from"./merged-relations.js";export*from"./merged-spec.js";export*from"./merged-theme.js";export*from"./merged-ui.js";export*from"./merged-transformers.js";export*from"./merged-app-metadata.js";export*from"./merged-infra.js";export*from"./merged-invariants.js";export*from"./merged-chunks.js";
1
+ export*from"./classified-spec-file.js";export*from"./merged-actions.js";export*from"./merged-admin-components.js";export*from"./merged-admin-expressions.js";export*from"./merged-admin-invariants.js";export*from"./merged-admin-states.js";export*from"./merged-admin-model.js";export*from"./merged-components.js";export*from"./merged-configurator-states.js";export*from"./merged-defaults.js";export*from"./merged-model.js";export*from"./merged-admin-pages.js";export*from"./merged-admin-presets.js";export*from"./merged-admin-relations.js";export*from"./merged-assistant.js";export*from"./merged-budgets.js";export*from"./merged-data-schemas.js";export*from"./merged-data-spec.js";export*from"./merged-expressions.js";export*from"./merged-presets.js";export*from"./merged-relations.js";export*from"./merged-spec.js";export*from"./merged-theme.js";export*from"./merged-ui.js";export*from"./merged-transformers.js";export*from"./merged-app-metadata.js";export*from"./merged-infra.js";export*from"./merged-invariants.js";export*from"./merged-chunks.js";
@@ -0,0 +1,11 @@
1
+ import { Branded } from '@salsita-ai/common/utils';
2
+ import type { ParseContext } from '../../core/index.js';
3
+ import type { RawActions } from '../raw-spec/index.js';
4
+ import type { ClassifiedSpecFile } from './classified-spec-file.js';
5
+ /**
6
+ * Merged actions from all spec files; an action name declared in two files is a merge conflict.
7
+ * Actions belong to the configurator domain, so an `actions` block in an admin spec file is reported
8
+ * rather than merged.
9
+ */
10
+ export type MergedActions = Branded<'MergedActions', RawActions>;
11
+ export declare const MergedActions: (files: readonly ClassifiedSpecFile[], ctx: ParseContext) => MergedActions;
@@ -0,0 +1 @@
1
+ import{Branded as e}from"@salsita-ai/common/utils";import{DiagnosticCode as n,diagnosticMessages as o}from"../../errors/index.js";export const MergedActions=(t,i)=>{let r={},a={};for(let e of t)if(e.content.actions){if(e.isAdmin){i.inFile(e.name).at("actions").error(n.ACTIONS_IN_ADMIN_FILE,o.ACTIONS_IN_ADMIN_FILE(e.name));continue}for(let[n,o]of Object.entries(e.content.actions)){let t=a[n];if(t){i.error("MERGE_CONFLICT",`Action "${n}" is declared in both "${t}" and "${e.name}". Each action can only be declared once. Keep one declaration and remove the other.`);continue}r[n]=o,a[n]=e.name}}return e(r)};
@@ -1,6 +1,7 @@
1
1
  import { Branded } from '@salsita-ai/common/utils';
2
2
  import type { ParseContext } from '../../core/index.js';
3
3
  import type { ResolvedSpecFiles } from '../resolve/index.js';
4
+ import { MergedActions } from './merged-actions.js';
4
5
  import { MergedAdminPages } from './merged-admin-pages.js';
5
6
  import { MergedAdminStates } from './merged-admin-states.js';
6
7
  import { MergedAppMetadata } from './merged-app-metadata.js';
@@ -31,6 +32,7 @@ export type MergedSpec = Branded<'MergedSpec', {
31
32
  readonly adminPages: MergedAdminPages;
32
33
  readonly expressions: MergedExpressions;
33
34
  readonly adminExpressions: MergedExpressions;
35
+ readonly actions: MergedActions;
34
36
  readonly invariants: MergedInvariants;
35
37
  readonly adminInvariants: MergedInvariants;
36
38
  readonly relations: MergedRelations;
@@ -1 +1 @@
1
- import{Branded as e}from"@salsita-ai/common/utils";import{classifySpecFile as r}from"./classified-spec-file.js";import{MergedAdminComponents as m}from"./merged-admin-components.js";import{MergedAdminExpressions as o}from"./merged-admin-expressions.js";import{MergedAdminInvariants as s}from"./merged-admin-invariants.js";import{MergedAdminModel as t}from"./merged-admin-model.js";import{MergedAdminPages as d}from"./merged-admin-pages.js";import{MergedAdminPresets as i}from"./merged-admin-presets.js";import{MergedAdminRelations as p}from"./merged-admin-relations.js";import{MergedAdminStates as a}from"./merged-admin-states.js";import{MergedAppMetadata as n}from"./merged-app-metadata.js";import{MergedAssistant as g}from"./merged-assistant.js";import{MergedBudgets as f}from"./merged-budgets.js";import{MergedChunks as j}from"./merged-chunks.js";import{MergedComponents as l}from"./merged-components.js";import{MergedConfiguratorStates as c}from"./merged-configurator-states.js";import{MergedDataSchemas as M}from"./merged-data-schemas.js";import{MergedDataSpec as S}from"./merged-data-spec.js";import{MergedDefaults as u}from"./merged-defaults.js";import{MergedExpressions as h}from"./merged-expressions.js";import{MergedInfra as A}from"./merged-infra.js";import{MergedInvariants as x}from"./merged-invariants.js";import{MergedModel as k}from"./merged-model.js";import{MergedPresets as v}from"./merged-presets.js";import{MergedRelations as C}from"./merged-relations.js";import{MergedTheme as b}from"./merged-theme.js";import{MergedTransformers as q}from"./merged-transformers.js";import{MergedUi as w}from"./merged-ui.js";export const MergedSpec=(y,z)=>{let B=y.map(e=>r(e)),D=B.filter(e=>!e.isAdmin),E=B.filter(e=>e.isAdmin),F=z.startSpan("MergedModel","MergedSpec","merged-model.ts"),G=k(D,z);F();let H=z.startSpan("MergedAdminModel","MergedSpec","merged-admin-model.ts"),I=t(E,z);H();let J=z.startSpan("MergedComponents","MergedSpec","merged-components.ts"),K=l(D,z);J();let L=z.startSpan("MergedAdminComponents","MergedSpec","merged-admin-components.ts"),N=m(E,z);L();let O=v(D),P=i(E,z),Q=d(E,z),R=h(D,z),T=o(E,z),U=x(D,z),V=s(E,z),W=C(D,z),X=p(E,z),Y=u(D,z),Z=u(E,z),$=M(B,z),_=S(B,z),ee=c(D,z),er=a(E,z),em=b(B,z),eo=w(B,z),es=q(B,z),et=n(B,z),ed=A(B,z),ei=f(B,z);return e({model:G,adminModel:I,components:K,adminComponents:N,presets:O,adminPresets:P,adminPages:Q,expressions:R,adminExpressions:T,invariants:U,adminInvariants:V,relations:W,adminRelations:X,defaults:Y,adminDefaults:Z,dataSchemas:$,dataSpec:_,configuratorStates:ee,adminStates:er,theme:em,ui:eo,transformers:es,appMetadata:et,infra:ed,budgets:ei,chunks:j(B,z),assistant:g(B,z)})};
1
+ import{Branded as e}from"@salsita-ai/common/utils";import{classifySpecFile as r}from"./classified-spec-file.js";import{MergedActions as m}from"./merged-actions.js";import{MergedAdminComponents as o}from"./merged-admin-components.js";import{MergedAdminExpressions as s}from"./merged-admin-expressions.js";import{MergedAdminInvariants as t}from"./merged-admin-invariants.js";import{MergedAdminModel as i}from"./merged-admin-model.js";import{MergedAdminPages as d}from"./merged-admin-pages.js";import{MergedAdminPresets as p}from"./merged-admin-presets.js";import{MergedAdminRelations as a}from"./merged-admin-relations.js";import{MergedAdminStates as n}from"./merged-admin-states.js";import{MergedAppMetadata as g}from"./merged-app-metadata.js";import{MergedAssistant as f}from"./merged-assistant.js";import{MergedBudgets as j}from"./merged-budgets.js";import{MergedChunks as l}from"./merged-chunks.js";import{MergedComponents as c}from"./merged-components.js";import{MergedConfiguratorStates as M}from"./merged-configurator-states.js";import{MergedDataSchemas as S}from"./merged-data-schemas.js";import{MergedDataSpec as u}from"./merged-data-spec.js";import{MergedDefaults as h}from"./merged-defaults.js";import{MergedExpressions as A}from"./merged-expressions.js";import{MergedInfra as x}from"./merged-infra.js";import{MergedInvariants as k}from"./merged-invariants.js";import{MergedModel as v}from"./merged-model.js";import{MergedPresets as C}from"./merged-presets.js";import{MergedRelations as b}from"./merged-relations.js";import{MergedTheme as q}from"./merged-theme.js";import{MergedTransformers as w}from"./merged-transformers.js";import{MergedUi as y}from"./merged-ui.js";export const MergedSpec=(z,B)=>{let D=z.map(e=>r(e)),E=D.filter(e=>!e.isAdmin),F=D.filter(e=>e.isAdmin),G=B.startSpan("MergedModel","MergedSpec","merged-model.ts"),H=v(E,B);G();let I=B.startSpan("MergedAdminModel","MergedSpec","merged-admin-model.ts"),J=i(F,B);I();let K=B.startSpan("MergedComponents","MergedSpec","merged-components.ts"),L=c(E,B);K();let N=B.startSpan("MergedAdminComponents","MergedSpec","merged-admin-components.ts"),O=o(F,B);N();let P=C(E),Q=p(F,B),R=d(F,B),T=A(E,B),U=s(F,B),V=m(D,B),W=k(E,B),X=t(F,B),Y=b(E,B),Z=a(F,B),$=h(E,B),_=h(F,B),ee=S(D,B),er=u(D,B),em=M(E,B),eo=n(F,B),es=q(D,B),et=y(D,B),ei=w(D,B),ed=g(D,B),ep=x(D,B),ea=j(D,B);return e({model:H,adminModel:J,components:L,adminComponents:O,presets:P,adminPresets:Q,adminPages:R,expressions:T,adminExpressions:U,actions:V,invariants:W,adminInvariants:X,relations:Y,adminRelations:Z,defaults:$,adminDefaults:_,dataSchemas:ee,dataSpec:er,configuratorStates:em,adminStates:eo,theme:es,ui:et,transformers:ei,appMetadata:ed,infra:ep,budgets:ea,chunks:l(D,B),assistant:f(D,B)})};
@@ -1,5 +1,6 @@
1
1
  export * from './raw-admin-pages.js';
2
2
  export * from './raw-acl.js';
3
+ export * from './raw-actions.js';
3
4
  export * from './raw-assistant.js';
4
5
  export * from './raw-budgets.js';
5
6
  export * from './raw-chunks.js';
@@ -1 +1 @@
1
- export*from"./raw-admin-pages.js";export*from"./raw-acl.js";export*from"./raw-assistant.js";export*from"./raw-budgets.js";export*from"./raw-chunks.js";export*from"./raw-components.js";export*from"./raw-constants.js";export*from"./raw-data.js";export*from"./raw-defaults.js";export*from"./raw-env.js";export*from"./raw-expressions.js";export*from"./raw-invariants.js";export*from"./raw-model.js";export*from"./raw-mock-templates.js";export*from"./raw-presets.js";export*from"./raw-relations.js";export*from"./raw-resources.js";export*from"./raw-spec-file.js";export*from"./raw-states.js";export*from"./raw-transformers.js";export*from"./parse-raw-spec.js";
1
+ export*from"./raw-admin-pages.js";export*from"./raw-acl.js";export*from"./raw-actions.js";export*from"./raw-assistant.js";export*from"./raw-budgets.js";export*from"./raw-chunks.js";export*from"./raw-components.js";export*from"./raw-constants.js";export*from"./raw-data.js";export*from"./raw-defaults.js";export*from"./raw-env.js";export*from"./raw-expressions.js";export*from"./raw-invariants.js";export*from"./raw-model.js";export*from"./raw-mock-templates.js";export*from"./raw-presets.js";export*from"./raw-relations.js";export*from"./raw-resources.js";export*from"./raw-spec-file.js";export*from"./raw-states.js";export*from"./raw-transformers.js";export*from"./parse-raw-spec.js";
@@ -0,0 +1,18 @@
1
+ import { z } from 'zod';
2
+ import { type EnhancedJsonSchemaType } from '../../core/enhanced-json-schema.js';
3
+ /**
4
+ * One action declared under the top-level `actions:` block: a domain operation the project
5
+ * implements in `src/actions/` and the application runs through the action system. `params` and
6
+ * `result` are maps of named enhanced JSON schemas; a `phx:DomainEntityId` param with `references`
7
+ * is typed by the referenced entity type or alias, and a `phx:$DomainEntity` result member with
8
+ * `references` is typed as the enhanced entity. The `description` of the action and of each param
9
+ * and result member documents it in the generated types; whoever exposes the action can reuse it.
10
+ */
11
+ export interface RawActionEntry {
12
+ readonly description?: string;
13
+ readonly params: Readonly<Record<string, EnhancedJsonSchemaType>>;
14
+ readonly result: Readonly<Record<string, EnhancedJsonSchemaType>>;
15
+ }
16
+ /** Record of all action entries keyed by action name, produced by `rawSpecFileSchema` for the `actions:` block. */
17
+ export type RawActions = Readonly<Record<string, RawActionEntry>>;
18
+ export declare const rawActionsSchema: z.ZodType<RawActions, z.ZodTypeDef, unknown>;
@@ -0,0 +1 @@
1
+ import{z as e}from"zod";import{enhancedJsonSchema as r}from"../../core/enhanced-json-schema.js";let a=r=>e.string().regex(/^[A-Za-z][A-Za-z0-9_]*$/,`is not a valid ${r} name: use letters, digits and underscores, starting with a letter, e.g. "partId"`),t=e.object({description:e.string().optional(),params:e.record(a("parameter"),r,{required_error:"is required: declare the parameters of the action as a map of named schemas, empty when it takes none",invalid_type_error:"must be a map of parameter names to JSON schemas"}),result:e.record(a("result member"),r,{required_error:"is required: declare the result of the action as a map of named schemas",invalid_type_error:"must be a map of result member names to JSON schemas"})}).strict();export const rawActionsSchema=e.record(e.string(),t).readonly();
@@ -68,6 +68,16 @@ export interface RawAssistantRecipes {
68
68
  readonly hints?: readonly AssistantHint[];
69
69
  readonly groups: Readonly<Record<string, RawAssistantRecipeGroup | null>>;
70
70
  }
71
+ /**
72
+ * One entry of the assistant `actions` map, keyed by the name of an action declared under the
73
+ * top-level `actions` block: it exposes the action as a `Mutation` field named `as` (the action name
74
+ * by default) with `description` (the action's own by default). An empty entry (`movePartBy:` in
75
+ * YAML) exposes the action with its defaults.
76
+ */
77
+ export interface RawAssistantAction {
78
+ readonly description?: string;
79
+ readonly as?: string;
80
+ }
71
81
  /**
72
82
  * Experimental assistant manifest authored under the `assistant` root key. `entities` whitelists the
73
83
  * stored fields the generated GraphQL schema exposes, keyed by entity type or alias name and then by
@@ -77,8 +87,9 @@ export interface RawAssistantRecipes {
77
87
  * have to be exposed in `entities`. `childRecipes` names, per
78
88
  * entity type or alias, the `childEntityRecipes` groups whose recipes the agent may list and add
79
89
  * with `addChild`. `replaceRecipes` names, per entity type or alias, the `replaceEntityRecipes`
80
- * groups whose recipes the agent may list and replace the entity by with `replaceEntity`. Nothing is
81
- * exposed without an entry.
90
+ * groups whose recipes the agent may list and replace the entity by with `replaceEntity`. `actions`
91
+ * names the declared actions the agent may run as custom mutations. Nothing is exposed without an
92
+ * entry.
82
93
  *
83
94
  * `ai` and `instructions` are for the server only: the provider, model tier and reasoning level the
84
95
  * deployment's assistant runs with, and the integrator's own guidance for it, appended to the system
@@ -90,6 +101,7 @@ export interface RawAssistant {
90
101
  readonly writable?: Readonly<Record<string, Readonly<Record<string, RawAssistantWritableField | null>>>>;
91
102
  readonly childRecipes?: Readonly<Record<string, RawAssistantRecipes>>;
92
103
  readonly replaceRecipes?: Readonly<Record<string, RawAssistantRecipes>>;
104
+ readonly actions?: Readonly<Record<string, RawAssistantAction | null>>;
93
105
  readonly instructions?: string;
94
106
  readonly ai: AssistantAiSettings;
95
107
  }
@@ -1 +1 @@
1
- import{z as t}from"zod";import{ASSISTANT_AI_PROVIDERS as e,ASSISTANT_MODEL_TIERS as o,ASSISTANT_REASONING_LEVELS as i}from"@salsita-ai/contracts/assistant";export const ASSISTANT_OPTION_COMPONENT_TYPES=["materialSwatch","attributeSelection","distanceDimensionSelect"];export const ASSISTANT_HINTS=["sameForAllInstances","constantDuringSession"];let n=t.string().regex(/^(?!__)[_A-Za-z][_0-9A-Za-z]*$/,'must be a GraphQL name: letters, digits and "_", not starting with a digit or "__"'),r=e=>t.enum(e,{errorMap:()=>({message:`must be one of: ${e.join(", ")}`})}),s=t.object({description:t.string().optional(),as:n.optional()}).strict(),a=t.array(r(ASSISTANT_HINTS)),c=t.object({options:t.object({componentType:r(ASSISTANT_OPTION_COMPONENT_TYPES),instanceName:t.string().min(1,"must not be empty; name the component instance that holds the choices"),as:n.optional(),validate:t.boolean().optional(),allowDryRuns:t.boolean().optional(),hints:a.optional()}).strict().optional()}).strict(),l=t.object({as:t.string().trim().min(1,"must not be empty; remove the key to use the instance name").optional(),description:t.string().optional(),allowDryRuns:t.boolean().optional()}).strict(),p=e=>t.object({hints:a.optional(),groups:t.record(t.string(),l.nullable(),{required_error:`is required: the recipe groups of the type go under "groups", keyed by the instance name of a "${e}" component`})}).strict(),m=`is required: declare provider (${e.join(", ")}), model (${o.join(", ")}) and reasoning (${i.join(", ")})`,d=t.object({provider:r(e),model:r(o),reasoning:r(i)},{required_error:m,invalid_type_error:m}).strict();export const rawAssistantSchema=t.object({entities:t.record(t.string(),t.record(t.string(),s.nullable())).optional(),deletable:t.record(t.string(),t.object({}).strict().nullable()).optional(),writable:t.record(t.string(),t.record(t.string(),c.nullable())).optional(),childRecipes:t.record(t.string(),p("childEntityRecipes")).optional(),replaceRecipes:t.record(t.string(),p("replaceEntityRecipes")).optional(),instructions:t.string().trim().min(1,"must not be empty; remove the key when there are no instructions").optional(),ai:d}).strict();
1
+ import{z as t}from"zod";import{ASSISTANT_AI_PROVIDERS as e,ASSISTANT_MODEL_TIERS as o,ASSISTANT_REASONING_LEVELS as i}from"@salsita-ai/contracts/assistant";export const ASSISTANT_OPTION_COMPONENT_TYPES=["materialSwatch","attributeSelection","distanceDimensionSelect"];export const ASSISTANT_HINTS=["sameForAllInstances","constantDuringSession"];let n=t.string().regex(/^(?!__)[_A-Za-z][_0-9A-Za-z]*$/,'must be a GraphQL name: letters, digits and "_", not starting with a digit or "__"'),r=e=>t.enum(e,{errorMap:()=>({message:`must be one of: ${e.join(", ")}`})}),s=t.object({description:t.string().optional(),as:n.optional()}).strict(),a=t.array(r(ASSISTANT_HINTS)),c=t.object({options:t.object({componentType:r(ASSISTANT_OPTION_COMPONENT_TYPES),instanceName:t.string().min(1,"must not be empty; name the component instance that holds the choices"),as:n.optional(),validate:t.boolean().optional(),allowDryRuns:t.boolean().optional(),hints:a.optional()}).strict().optional()}).strict(),l=t.object({as:t.string().trim().min(1,"must not be empty; remove the key to use the instance name").optional(),description:t.string().optional(),allowDryRuns:t.boolean().optional()}).strict(),p=e=>t.object({hints:a.optional(),groups:t.record(t.string(),l.nullable(),{required_error:`is required: the recipe groups of the type go under "groups", keyed by the instance name of a "${e}" component`})}).strict(),d=t.object({description:t.string().optional(),as:n.optional()}).strict(),m=`is required: declare provider (${e.join(", ")}), model (${o.join(", ")}) and reasoning (${i.join(", ")})`,g=t.object({provider:r(e),model:r(o),reasoning:r(i)},{required_error:m,invalid_type_error:m}).strict();export const rawAssistantSchema=t.object({entities:t.record(t.string(),t.record(t.string(),s.nullable())).optional(),deletable:t.record(t.string(),t.object({}).strict().nullable()).optional(),writable:t.record(t.string(),t.record(t.string(),c.nullable())).optional(),childRecipes:t.record(t.string(),p("childEntityRecipes")).optional(),replaceRecipes:t.record(t.string(),p("replaceEntityRecipes")).optional(),actions:t.record(t.string(),d.nullable()).optional(),instructions:t.string().trim().min(1,"must not be empty; remove the key when there are no instructions").optional(),ai:g}).strict();
@@ -2,6 +2,7 @@ import { Branded } from '@salsita-ai/common/utils';
2
2
  import { type EnhancedJsonObjectSchemaType, type ParseContext } from '../../core/index.js';
3
3
  import type { ParsedYamlFile } from './parse-yaml-spec-file.js';
4
4
  import { type RawAcl } from './raw-acl.js';
5
+ import { type RawActions } from './raw-actions.js';
5
6
  import { type RawAdminPages } from './raw-admin-pages.js';
6
7
  import { type RawAssistant } from './raw-assistant.js';
7
8
  import { type RawBudgets } from './raw-budgets.js';
@@ -24,6 +25,7 @@ export interface RawSpecContent {
24
25
  readonly presets?: RawPresets;
25
26
  readonly $data?: RawDataSpec;
26
27
  readonly expressions?: RawExpressions;
28
+ readonly actions?: RawActions;
27
29
  readonly invariants?: RawInvariants;
28
30
  readonly relations?: RawRelations;
29
31
  readonly defaults?: RawDefaults;
@@ -1 +1 @@
1
- import{z as o}from"zod";import{Branded as a}from"@salsita-ai/common/utils";import{enhancedJsonObjectSchema as t,validateNoLegacyCodeRef as e}from"../../core/index.js";import{DiagnosticCode as n,diagnosticMessages as r}from"../../errors/index.js";import{rawAclSchema as i}from"./raw-acl.js";import{rawAdminPagesSchema as s}from"./raw-admin-pages.js";import{rawAssistantSchema as l}from"./raw-assistant.js";import{rawBudgetsSchema as p}from"./raw-budgets.js";import{rawChunksSchema as m}from"./raw-chunks.js";import{rawComponentsSchema as c}from"./raw-components.js";import{rawConstantsSchema as f}from"./raw-constants.js";import{rawDataSpecSchema as d}from"./raw-data.js";import{rawDefaultsSchema as u}from"./raw-defaults.js";import{rawEnvSchema as j}from"./raw-env.js";import{rawExpressionsSchema as w}from"./raw-expressions.js";import{rawInvariantsSchema as b}from"./raw-invariants.js";import{rawModelSchema as g}from"./raw-model.js";import{rawPresetsSchema as S}from"./raw-presets.js";import{rawRelationsSchema as D}from"./raw-relations.js";import{rawResourcesConfigSchema as h,rawResourcesSchema as v}from"./raw-resources.js";import{rawStateValuesSchema as O}from"./raw-states.js";import{rawTransformersSchema as E}from"./raw-transformers.js";export const DATA_SCHEMA_KEYS=["publishedData","sessionData","frontendData","backendData","finalizeData","prefetchedFinalizeData","finalizeResult","callbackData"];let R=o.object({$admin:o.boolean().optional(),model:g.optional(),components:c.optional(),presets:S.optional(),$data:d.optional(),expressions:w.optional(),invariants:b.optional(),constants:f.optional(),relations:D.optional(),defaults:u.optional(),publishedData:t.optional(),sessionData:t.optional(),frontendData:t.optional(),backendData:t.optional(),finalizeData:t.optional(),prefetchedFinalizeData:t.optional(),finalizeResult:t.optional(),callbackData:t.optional(),navigationStates:O.optional(),viewStates:O.optional(),toolStates:O.optional(),exportStates:O.optional(),themePreset:o.string().optional(),themeOptions:o.record(o.unknown()).optional(),preview:o.enum(["always","never","auto","hide"]).optional(),alwaysExpand:o.boolean().optional(),availableLocales:o.array(o.string()).optional(),textLanguages:o.array(o.string()).optional(),transformers:E.optional(),project:o.string().regex(/^[a-z0-9-]{3,}$/,"must be a project slug (lowercase letters, digits and dashes, at least 3) or `default`").optional(),applicationVersion:o.string().optional(),workspace:o.boolean().optional(),webComponentTag:o.string().optional(),pageTitle:o.string().optional(),useAuthHook:o.boolean().optional(),useClerk:o.boolean().optional(),enableAdminRelease:o.boolean().optional(),jsonSchemaValidator:o.enum(["ajv","csp-compatible"]).optional(),gltfDecoder:o.enum(["js","wasm"]).optional(),appLoader:o.enum(["legacy","universal"]).optional(),collectFinalizeData:o.boolean().optional(),env:j.optional(),acl:i.optional(),resources:v.optional(),resourcesConfig:h.optional(),budgets:p.optional(),chunks:m.optional(),assistant:l.optional(),$adminPages:s.optional()}).strict();export const parseRawSpecFile=(o,t)=>{e(o.content,t);let i=R.safeParse(o.content);if(!i.success){for(let o of i.error.issues)t.error(n.SCHEMA_PARSE_ERROR,r.SCHEMA_PARSE_ERROR(o.path.join("."),o.message));return}let{constants:s,...l}=i.data,p=((o,a)=>{let t=Object.fromEntries(Object.entries(o.expressions??{}).map(([o,a])=>[o,{...a}]));if(void 0===o.constants)return Object.keys(t).length>0?t:void 0;for(let[e,i]of Object.entries(o.constants))for(let[o,s]of(t[e]||(t[e]={}),Object.entries(i))){if(void 0!==t[e][o]){a.at("constants",e,o).error(n.CONSTANTS_EXPRESSION_CONFLICT,r[n.CONSTANTS_EXPRESSION_CONFLICT](e,o));continue}t[e][o]={kind:"value",constant:!0,value:s,schema:{}}}return Object.keys(t).length>0?t:void 0})(i.data,t);return a({name:o.name,content:{...l,expressions:p}})};
1
+ import{z as o}from"zod";import{Branded as a}from"@salsita-ai/common/utils";import{enhancedJsonObjectSchema as t,validateNoLegacyCodeRef as e}from"../../core/index.js";import{DiagnosticCode as n,diagnosticMessages as i}from"../../errors/index.js";import{rawAclSchema as r}from"./raw-acl.js";import{rawActionsSchema as s}from"./raw-actions.js";import{rawAdminPagesSchema as l}from"./raw-admin-pages.js";import{rawAssistantSchema as p}from"./raw-assistant.js";import{rawBudgetsSchema as m}from"./raw-budgets.js";import{rawChunksSchema as c}from"./raw-chunks.js";import{rawComponentsSchema as f}from"./raw-components.js";import{rawConstantsSchema as d}from"./raw-constants.js";import{rawDataSpecSchema as u}from"./raw-data.js";import{rawDefaultsSchema as j}from"./raw-defaults.js";import{rawEnvSchema as w}from"./raw-env.js";import{rawExpressionsSchema as b}from"./raw-expressions.js";import{rawInvariantsSchema as g}from"./raw-invariants.js";import{rawModelSchema as S}from"./raw-model.js";import{rawPresetsSchema as D}from"./raw-presets.js";import{rawRelationsSchema as h}from"./raw-relations.js";import{rawResourcesConfigSchema as v,rawResourcesSchema as O}from"./raw-resources.js";import{rawStateValuesSchema as E}from"./raw-states.js";import{rawTransformersSchema as R}from"./raw-transformers.js";export const DATA_SCHEMA_KEYS=["publishedData","sessionData","frontendData","backendData","finalizeData","prefetchedFinalizeData","finalizeResult","callbackData"];let k=o.object({$admin:o.boolean().optional(),model:S.optional(),components:f.optional(),presets:D.optional(),$data:u.optional(),expressions:b.optional(),actions:s.optional(),invariants:g.optional(),constants:d.optional(),relations:h.optional(),defaults:j.optional(),publishedData:t.optional(),sessionData:t.optional(),frontendData:t.optional(),backendData:t.optional(),finalizeData:t.optional(),prefetchedFinalizeData:t.optional(),finalizeResult:t.optional(),callbackData:t.optional(),navigationStates:E.optional(),viewStates:E.optional(),toolStates:E.optional(),exportStates:E.optional(),themePreset:o.string().optional(),themeOptions:o.record(o.unknown()).optional(),preview:o.enum(["always","never","auto","hide"]).optional(),alwaysExpand:o.boolean().optional(),availableLocales:o.array(o.string()).optional(),textLanguages:o.array(o.string()).optional(),transformers:R.optional(),project:o.string().regex(/^[a-z0-9-]{3,}$/,"must be a project slug (lowercase letters, digits and dashes, at least 3) or `default`").optional(),applicationVersion:o.string().optional(),workspace:o.boolean().optional(),webComponentTag:o.string().optional(),pageTitle:o.string().optional(),useAuthHook:o.boolean().optional(),useClerk:o.boolean().optional(),enableAdminRelease:o.boolean().optional(),jsonSchemaValidator:o.enum(["ajv","csp-compatible"]).optional(),gltfDecoder:o.enum(["js","wasm"]).optional(),appLoader:o.enum(["legacy","universal"]).optional(),collectFinalizeData:o.boolean().optional(),env:w.optional(),acl:r.optional(),resources:O.optional(),resourcesConfig:v.optional(),budgets:m.optional(),chunks:c.optional(),assistant:p.optional(),$adminPages:l.optional()}).strict();export const parseRawSpecFile=(o,t)=>{e(o.content,t);let r=k.safeParse(o.content);if(!r.success){for(let o of r.error.issues)t.error(n.SCHEMA_PARSE_ERROR,i.SCHEMA_PARSE_ERROR(o.path.join("."),o.message));return}let{constants:s,...l}=r.data,p=((o,a)=>{let t=Object.fromEntries(Object.entries(o.expressions??{}).map(([o,a])=>[o,{...a}]));if(void 0===o.constants)return Object.keys(t).length>0?t:void 0;for(let[e,r]of Object.entries(o.constants))for(let[o,s]of(t[e]||(t[e]={}),Object.entries(r))){if(void 0!==t[e][o]){a.at("constants",e,o).error(n.CONSTANTS_EXPRESSION_CONFLICT,i[n.CONSTANTS_EXPRESSION_CONFLICT](e,o));continue}t[e][o]={kind:"value",constant:!0,value:s,schema:{}}}return Object.keys(t).length>0?t:void 0})(r.data,t);return a({name:o.name,content:{...l,expressions:p}})};
@@ -1 +1 @@
1
- import{mapSchema as e,resolveProjectFileImport as t,resolveProjectPath as r}from"../../core/index.js";import{DiagnosticCode as o,diagnosticMessages as s}from"../../errors/index.js";import{DATA_SCHEMA_KEYS as m}from"../raw-spec/index.js";let n=(m,n,p,i)=>e(m,(e,m)=>e.import?{...e,import:e.import.map((e,a)=>{if(!e.module.startsWith("."))return e;let c=r(n,e.module),O=i.at(...m,"import",String(a),"module");c.escapedProjectRoot?O.warning(o.IMPORT_MODULE_OUTSIDE_PROJECT,s[o.IMPORT_MODULE_OUTSIDE_PROJECT](e.module,c.path)):t(n,e.module,p)||O.error(o.IMPORT_MODULE_NOT_FOUND,s[o.IMPORT_MODULE_NOT_FOUND](e.module,c.path));let d=c.path.startsWith(".")?c.path:`./${c.path}`;return{...e,module:d}})}:e);export const resolveImportModules=(e,t,r)=>{let o=e.content.model?Object.fromEntries(Object.entries(e.content.model).map(([o,s])=>[o,{...s,...s.commonTypes?{commonTypes:Object.fromEntries(Object.entries(s.commonTypes).map(([s,m])=>[s,n(m,e.name,t,r.at("model",o,"commonTypes",s))]))}:{},...s.shapes?{shapes:Object.fromEntries(Object.entries(s.shapes).map(([s,m])=>[s,Object.fromEntries(Object.entries(m).map(([m,p])=>[m,n(p,e.name,t,r.at("model",o,"shapes",s,m))]))]))}:{}}])):void 0,s=e.content.expressions?Object.fromEntries(Object.entries(e.content.expressions).map(([o,s])=>[o,Object.fromEntries(Object.entries(s).map(([s,m])=>[s,{...m,schema:n(m.schema,e.name,t,r.at("expressions",o,s))}]))])):void 0,p=Object.fromEntries(m.flatMap(o=>{let s,m=e.content[o];return m?[[o,{...s=n(m,e.name,t,r.at(o)),type:"object",properties:s.properties??{},required:s.required??[],additionalProperties:!1}]]:[]}));return{...e.content,...o?{model:o}:{},...s?{expressions:s}:{},...p}};
1
+ import{mapSchema as e,resolveProjectFileImport as t,resolveProjectPath as r}from"../../core/index.js";import{DiagnosticCode as o,diagnosticMessages as s}from"../../errors/index.js";import{DATA_SCHEMA_KEYS as n}from"../raw-spec/index.js";let m=(n,m,a,i)=>e(n,(e,n)=>e.import?{...e,import:e.import.map((e,p)=>{if(!e.module.startsWith("."))return e;let c=r(m,e.module),O=i.at(...n,"import",String(p),"module");c.escapedProjectRoot?O.warning(o.IMPORT_MODULE_OUTSIDE_PROJECT,s[o.IMPORT_MODULE_OUTSIDE_PROJECT](e.module,c.path)):t(m,e.module,a)||O.error(o.IMPORT_MODULE_NOT_FOUND,s[o.IMPORT_MODULE_NOT_FOUND](e.module,c.path));let l=c.path.startsWith(".")?c.path:`./${c.path}`;return{...e,module:l}})}:e);export const resolveImportModules=(e,t,r)=>{let o=e.content.model?Object.fromEntries(Object.entries(e.content.model).map(([o,s])=>[o,{...s,...s.commonTypes?{commonTypes:Object.fromEntries(Object.entries(s.commonTypes).map(([s,n])=>[s,m(n,e.name,t,r.at("model",o,"commonTypes",s))]))}:{},...s.shapes?{shapes:Object.fromEntries(Object.entries(s.shapes).map(([s,n])=>[s,Object.fromEntries(Object.entries(n).map(([n,a])=>[n,m(a,e.name,t,r.at("model",o,"shapes",s,n))]))]))}:{}}])):void 0,s=e.content.expressions?Object.fromEntries(Object.entries(e.content.expressions).map(([o,s])=>[o,Object.fromEntries(Object.entries(s).map(([s,n])=>[s,{...n,schema:m(n.schema,e.name,t,r.at("expressions",o,s))}]))])):void 0,a=e.content.actions?Object.fromEntries(Object.entries(e.content.actions).map(([o,s])=>{let n=(s,n)=>Object.fromEntries(Object.entries(s).map(([s,a])=>[s,m(a,e.name,t,r.at("actions",o,n,s))]));return[o,{...s,params:n(s.params,"params"),result:n(s.result,"result")}]})):void 0,i=Object.fromEntries(n.flatMap(o=>{let s,n=e.content[o];return n?[[o,{...s=m(n,e.name,t,r.at(o)),type:"object",properties:s.properties??{},required:s.required??[],additionalProperties:!1}]]:[]}));return{...e.content,...o?{model:o}:{},...s?{expressions:s}:{},...a?{actions:a}:{},...i}};
@@ -1 +1 @@
1
- import{Branded as e}from"@salsita-ai/common/utils";import{AppMetadata as s,BundleSizeBudgets as a,DataSpec as t,Infra as n}from"../sections/index.js";import{AdminSpec as r}from"./admin-spec/index.js";import{ConfiguratorSpec as o}from"./configurator-spec/index.js";export const ApplicationSpec=(i,l)=>{let{merged:m,migrationFiles:p,adminMigrationFiles:d,transformerFiles:c,phxDefinitions:u,evalExpressionParser:w}=i,f=s(m.appMetadata,l);if(!f)return;let S=t({mergedDataSpec:m.dataSpec,mergedChunks:m.chunks},l);if(!S||l.hasErrors)return;let g=l.startSpan("ConfiguratorSpec","ApplicationSpec"),h=o({rawModel:m.model,rawPresets:m.presets,rawExpressions:m.expressions,rawInvariants:m.invariants,rawRelations:m.relations,rawDefaults:m.defaults,dataSchemas:m.dataSchemas,dataSpec:S,appMetadata:f,rawTheme:m.theme,rawUi:m.ui,rawTransformers:m.transformers,rawAssistant:m.assistant,migrationFiles:p,transformerFiles:c,rawComponents:m.components,rawStates:m.configuratorStates,phxDefinitions:u,evalExpressionParser:w,label:"model"},l);if(g(),!h)return;let M=l.startSpan("AdminSpec","ApplicationSpec"),x=r({rawModel:m.adminModel,rawPresets:m.adminPresets,rawExpressions:m.adminExpressions,rawInvariants:m.adminInvariants,rawRelations:m.adminRelations,rawDefaults:m.adminDefaults,migrationFiles:d,rawComponents:m.adminComponents,rawAdminPages:m.adminPages,rawStates:m.adminStates,dataSpec:S,phxDefinitions:u,evalExpressionParser:w,label:"adminModel",requireMigrationFiles:!0},l);if(M(),!x)return;let v=n(m.infra,l);if(!v)return;let y=a(m.budgets);return l.count("entityTypes",h.latestModel.entityTypes.length),l.count("aliases",h.latestModel.aliases.size),l.count("commonTypes",h.latestModel.commonTypes.size),l.count("shapes",h.latestModel.shapes.size),l.count("components",h.components.length),l.count("modelVersions",h.resolvedVersions.versionedModels.length),l.count("presets",h.presets.presets.length),e({configurator:h,admin:x,appMetadata:f,infra:v,budgets:y,dataSpec:S})};
1
+ import{Branded as e}from"@salsita-ai/common/utils";import{AppMetadata as s,BundleSizeBudgets as a,DataSpec as t,Infra as n}from"../sections/index.js";import{AdminSpec as r}from"./admin-spec/index.js";import{ConfiguratorSpec as o}from"./configurator-spec/index.js";export const ApplicationSpec=(i,l)=>{let{merged:m,migrationFiles:p,adminMigrationFiles:d,transformerFiles:c,phxDefinitions:u,evalExpressionParser:w}=i,f=s(m.appMetadata,l);if(!f)return;let S=t({mergedDataSpec:m.dataSpec,mergedChunks:m.chunks},l);if(!S||l.hasErrors)return;let g=l.startSpan("ConfiguratorSpec","ApplicationSpec"),h=o({rawModel:m.model,rawPresets:m.presets,rawExpressions:m.expressions,rawActions:m.actions,rawInvariants:m.invariants,rawRelations:m.relations,rawDefaults:m.defaults,dataSchemas:m.dataSchemas,dataSpec:S,appMetadata:f,rawTheme:m.theme,rawUi:m.ui,rawTransformers:m.transformers,rawAssistant:m.assistant,migrationFiles:p,transformerFiles:c,rawComponents:m.components,rawStates:m.configuratorStates,phxDefinitions:u,evalExpressionParser:w,label:"model"},l);if(g(),!h)return;let M=l.startSpan("AdminSpec","ApplicationSpec"),x=r({rawModel:m.adminModel,rawPresets:m.adminPresets,rawExpressions:m.adminExpressions,rawInvariants:m.adminInvariants,rawRelations:m.adminRelations,rawDefaults:m.adminDefaults,migrationFiles:d,rawComponents:m.adminComponents,rawAdminPages:m.adminPages,rawStates:m.adminStates,dataSpec:S,phxDefinitions:u,evalExpressionParser:w,label:"adminModel",requireMigrationFiles:!0},l);if(M(),!x)return;let A=n(m.infra,l);if(!A)return;let v=a(m.budgets);return l.count("entityTypes",h.latestModel.entityTypes.length),l.count("aliases",h.latestModel.aliases.size),l.count("commonTypes",h.latestModel.commonTypes.size),l.count("shapes",h.latestModel.shapes.size),l.count("components",h.components.length),l.count("modelVersions",h.resolvedVersions.versionedModels.length),l.count("presets",h.presets.presets.length),e({configurator:h,admin:x,appMetadata:f,infra:A,budgets:v,dataSpec:S})};
@@ -1,12 +1,12 @@
1
1
  import { Branded } from '@salsita-ai/common/utils';
2
2
  import { type EvalExpressionParser, type ParseContext, type PhoenixDefinitions } from '../../../../core/index.js';
3
3
  import type { CategorizedFile } from '../../../categorize/index.js';
4
- import type { MergedAssistant, MergedComponents, MergedConfiguratorStates, MergedDataSchemas, MergedDefaults, MergedExpressions, MergedInvariants, MergedModel, MergedPresets, MergedRelations, MergedTheme, MergedTransformers, MergedUi } from '../../../merge/index.js';
4
+ import type { MergedActions, MergedAssistant, MergedComponents, MergedConfiguratorStates, MergedDataSchemas, MergedDefaults, MergedExpressions, MergedInvariants, MergedModel, MergedPresets, MergedRelations, MergedTheme, MergedTransformers, MergedUi } from '../../../merge/index.js';
5
5
  import { Components } from '../../components/index.js';
6
6
  import { Expressions } from '../../expressions/index.js';
7
7
  import { Invariants } from '../../invariants/index.js';
8
8
  import { ConfigurationSchema, DataSchemas, Defaults, ModelVersion, Presets } from '../../model/index.js';
9
- import { type AppMetadata, AssistantServerSettings, AssistantSpec, type DataSpec, Theme, Transformers, Ui } from '../../sections/index.js';
9
+ import { Actions, type AppMetadata, AssistantServerSettings, AssistantSpec, type DataSpec, Theme, Transformers, Ui } from '../../sections/index.js';
10
10
  import { ResolvedVersions } from '../resolved-versions.js';
11
11
  import { ConfiguratorStates } from './states.js';
12
12
  export type ConfiguratorSpec = Branded<'ConfiguratorSpec', {
@@ -17,6 +17,7 @@ export type ConfiguratorSpec = Branded<'ConfiguratorSpec', {
17
17
  readonly configurationSchema: ConfigurationSchema;
18
18
  readonly presets: Presets;
19
19
  readonly expressions: Expressions;
20
+ readonly actions: Actions;
20
21
  readonly invariants: Invariants;
21
22
  readonly dataSchemas: DataSchemas;
22
23
  readonly defaults: Defaults;
@@ -31,6 +32,7 @@ interface ConfiguratorSpecProps {
31
32
  readonly rawModel: MergedModel;
32
33
  readonly rawPresets: MergedPresets;
33
34
  readonly rawExpressions: MergedExpressions;
35
+ readonly rawActions: MergedActions;
34
36
  readonly rawInvariants: MergedInvariants;
35
37
  readonly rawRelations: MergedRelations;
36
38
  readonly rawDefaults: MergedDefaults;
@@ -1 +1 @@
1
- import{Branded as e}from"@salsita-ai/common/utils";import{DiagnosticCode as s,diagnosticMessages as t}from"../../../../errors/index.js";import{Components as r,UserComponents as o}from"../../components/index.js";import{Expressions as i}from"../../expressions/index.js";import{Invariants as n}from"../../invariants/index.js";import{ConfigurationSchema as a,DataSchemas as m,Defaults as p,ModelVersion as l,Presets as d}from"../../model/index.js";import{Relations as f}from"../../relations/index.js";import{AssistantServerSettings as x,AssistantSpec as S,Theme as v,Transformers as c,Ui as j}from"../../sections/index.js";import{ResolvedVersions as u}from"../resolved-versions.js";import{ConfiguratorStates as g}from"./states.js";export const ConfiguratorSpec=(C,I)=>{let{rawModel:M,rawPresets:h,rawExpressions:y,rawInvariants:E,rawRelations:N,rawDefaults:T,dataSchemas:L,dataSpec:G,appMetadata:O,rawTheme:_,rawUi:b,rawTransformers:w,rawAssistant:A,migrationFiles:D,transformerFiles:V,rawComponents:k,phxDefinitions:K,evalExpressionParser:q,label:z}=C;if(0===Object.keys(M).length)return void I.at(z).error(s.MISSING_MODEL,t.MISSING_MODEL(z));M.latest||I.at(z).error(s.MISSING_LATEST,t.MISSING_LATEST(z));let B=new Map;for(let[e,s]of Object.entries(M)){let t=l(e,s,K,I.at(z,e));t&&B.set(t.versionKey,t)}if(I.hasErrors)return;let F=u(B,D,I);if(!F)return;let H=F.versionedModels.find(e=>e.isLatest)?.modelVersion;if(!H)return;let J=F.latestVersion,P=a(H,J),Q=f(N,H,I),R=n({rawInvariants:E,rawExpressions:y,rawRelations:N,rawComponents:k},H,I),U=d(h,H,Q,I),W=o(k,{allNames:H.allNames,resolvedShapes:H.resolvedShapes,parsingContext:I}),X=p(T,H,I.at(z,"defaults")),Y=c(w,V,I),Z=i({rawExpressions:y,inferredExpressions:[...Q.expressions,...R.expressions,...W.expressions]},{latestModel:H,aliases:H.aliases,entityTypes:H.entityTypes,evalExpressionParser:q,parsingContext:I}),$=r({userComponents:W.components,generatedComponents:Q.components,invariantComponents:R.components},{aliases:H.aliases,parsingContext:I}),ee=m({merged:L,dataSpec:G,appMetadata:O,phxDefinitions:K,parsingContext:I}),es=g(C.rawStates,I.at("states"));if(void 0!==X&&void 0!==Z&&void 0!==$&&void 0!==ee)return e({latestModel:H,schemaVersion:J,dataSchemas:ee,resolvedVersions:F,components:$,configurationSchema:P,presets:U,expressions:Z,invariants:R,defaults:X,states:es,theme:v(_),ui:j(b),transformers:Y,assistant:S(A,{model:H,states:es,phxDefinitions:K,expressions:Z,components:$},I),assistantServerSettings:x(A)})};
1
+ import{Branded as e}from"@salsita-ai/common/utils";import{DiagnosticCode as s,diagnosticMessages as t}from"../../../../errors/index.js";import{Components as o,UserComponents as r}from"../../components/index.js";import{Expressions as i}from"../../expressions/index.js";import{Invariants as n}from"../../invariants/index.js";import{ConfigurationSchema as a,DataSchemas as m,Defaults as p,ModelVersion as l,Presets as d}from"../../model/index.js";import{Relations as f}from"../../relations/index.js";import{Actions as x,AssistantServerSettings as S,AssistantSpec as v,Theme as c,Transformers as j,Ui as u}from"../../sections/index.js";import{ResolvedVersions as g}from"../resolved-versions.js";import{ConfiguratorStates as C}from"./states.js";export const ConfiguratorSpec=(I,M)=>{let{rawModel:h,rawPresets:y,rawExpressions:E,rawActions:N,rawInvariants:T,rawRelations:L,rawDefaults:G,dataSchemas:O,dataSpec:_,appMetadata:b,rawTheme:w,rawUi:A,rawTransformers:D,rawAssistant:V,migrationFiles:k,transformerFiles:K,rawComponents:q,phxDefinitions:z,evalExpressionParser:B,label:F}=I;if(0===Object.keys(h).length)return void M.at(F).error(s.MISSING_MODEL,t.MISSING_MODEL(F));h.latest||M.at(F).error(s.MISSING_LATEST,t.MISSING_LATEST(F));let H=new Map;for(let[e,s]of Object.entries(h)){let t=l(e,s,z,M.at(F,e));t&&H.set(t.versionKey,t)}if(M.hasErrors)return;let J=g(H,k,M);if(!J)return;let P=J.versionedModels.find(e=>e.isLatest)?.modelVersion;if(!P)return;let Q=J.latestVersion,R=a(P,Q),U=f(L,P,M),W=n({rawInvariants:T,rawExpressions:E,rawRelations:L,rawComponents:q},P,M),X=d(y,P,U,M),Y=r(q,{allNames:P.allNames,resolvedShapes:P.resolvedShapes,parsingContext:M}),Z=p(G,P,M.at(F,"defaults")),$=j(D,K,M),ee=i({rawExpressions:E,inferredExpressions:[...U.expressions,...W.expressions,...Y.expressions]},{latestModel:P,aliases:P.aliases,entityTypes:P.entityTypes,evalExpressionParser:B,parsingContext:M}),es=o({userComponents:Y.components,generatedComponents:U.components,invariantComponents:W.components},{aliases:P.aliases,parsingContext:M}),et=m({merged:O,dataSpec:_,appMetadata:b,phxDefinitions:z,parsingContext:M}),eo=C(I.rawStates,M.at("states")),er=x(N,{latestModel:P,phxDefinitions:z},M.at("actions"));if(void 0!==Z&&void 0!==ee&&void 0!==er&&void 0!==es&&void 0!==et)return e({latestModel:P,schemaVersion:Q,dataSchemas:et,resolvedVersions:J,components:es,configurationSchema:R,presets:X,expressions:ee,actions:er,invariants:W,defaults:Z,states:eo,theme:c(w),ui:u(A),transformers:$,assistant:v(V,{model:P,states:eo,phxDefinitions:z,expressions:ee,components:es,actions:er},M),assistantServerSettings:S(V)})};
@@ -1 +1,2 @@
1
+ export * from './schema-ref-resolver.js';
1
2
  export * from './validate-schema-refs.js';
@@ -1 +1 @@
1
- export*from"./validate-schema-refs.js";
1
+ export*from"./schema-ref-resolver.js";export*from"./validate-schema-refs.js";