@loxtep/sdk 0.9.15 → 0.9.17

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 (100) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/README.md +4 -4
  3. package/dist/authoring/types.d.ts +8 -0
  4. package/dist/authoring/types.d.ts.map +1 -1
  5. package/dist/cli/commands/deploy-cmd.d.ts.map +1 -1
  6. package/dist/cli/commands/deploy-cmd.js +30 -15
  7. package/dist/cli/commands/deploy-cmd.js.map +1 -1
  8. package/dist/cli/commands/init-cmd.d.ts.map +1 -1
  9. package/dist/cli/commands/init-cmd.js +54 -24
  10. package/dist/cli/commands/init-cmd.js.map +1 -1
  11. package/dist/cli/commands/setup-cmd.d.ts +31 -0
  12. package/dist/cli/commands/setup-cmd.d.ts.map +1 -0
  13. package/dist/cli/commands/setup-cmd.js +126 -0
  14. package/dist/cli/commands/setup-cmd.js.map +1 -0
  15. package/dist/cli/commands/test-cmd.d.ts +3 -0
  16. package/dist/cli/commands/test-cmd.d.ts.map +1 -1
  17. package/dist/cli/commands/test-cmd.js +60 -50
  18. package/dist/cli/commands/test-cmd.js.map +1 -1
  19. package/dist/cli/help.d.ts.map +1 -1
  20. package/dist/cli/help.js +7 -3
  21. package/dist/cli/help.js.map +1 -1
  22. package/dist/cli/index.d.ts.map +1 -1
  23. package/dist/cli/index.js +4 -0
  24. package/dist/cli/index.js.map +1 -1
  25. package/dist/cli/load-workflow-module.d.ts +30 -0
  26. package/dist/cli/load-workflow-module.d.ts.map +1 -0
  27. package/dist/cli/load-workflow-module.js +94 -0
  28. package/dist/cli/load-workflow-module.js.map +1 -0
  29. package/dist/cli/resolve-package-root.cjs +49 -0
  30. package/dist/cli/templates-materialize.d.ts +23 -0
  31. package/dist/cli/templates-materialize.d.ts.map +1 -0
  32. package/dist/cli/templates-materialize.js +127 -0
  33. package/dist/cli/templates-materialize.js.map +1 -0
  34. package/dist/client/bundles-types.d.ts +44 -0
  35. package/dist/client/bundles-types.d.ts.map +1 -0
  36. package/dist/client/bundles-types.js +6 -0
  37. package/dist/client/bundles-types.js.map +1 -0
  38. package/dist/client/bundles.d.ts +17 -0
  39. package/dist/client/bundles.d.ts.map +1 -0
  40. package/dist/client/bundles.js +94 -0
  41. package/dist/client/bundles.js.map +1 -0
  42. package/dist/client/data-products.d.ts.map +1 -1
  43. package/dist/client/data-products.js +24 -2
  44. package/dist/client/data-products.js.map +1 -1
  45. package/dist/client/define.d.ts +5 -1
  46. package/dist/client/define.d.ts.map +1 -1
  47. package/dist/client/define.js +3 -1
  48. package/dist/client/define.js.map +1 -1
  49. package/dist/client/index.d.ts +8 -1
  50. package/dist/client/index.d.ts.map +1 -1
  51. package/dist/client/index.js.map +1 -1
  52. package/dist/client/loxtep-client.d.ts +5 -2
  53. package/dist/client/loxtep-client.d.ts.map +1 -1
  54. package/dist/client/loxtep-client.js +33 -6
  55. package/dist/client/loxtep-client.js.map +1 -1
  56. package/dist/client/meaning.d.ts +8 -1
  57. package/dist/client/meaning.d.ts.map +1 -1
  58. package/dist/client/meaning.js +4 -1
  59. package/dist/client/meaning.js.map +1 -1
  60. package/dist/client/proposals-types.d.ts +68 -0
  61. package/dist/client/proposals-types.d.ts.map +1 -0
  62. package/dist/client/proposals-types.js +6 -0
  63. package/dist/client/proposals-types.js.map +1 -0
  64. package/dist/client/proposals.d.ts +24 -0
  65. package/dist/client/proposals.d.ts.map +1 -0
  66. package/dist/client/proposals.js +140 -0
  67. package/dist/client/proposals.js.map +1 -0
  68. package/dist/client/shapes-types.d.ts +102 -0
  69. package/dist/client/shapes-types.d.ts.map +1 -0
  70. package/dist/client/shapes-types.js +14 -0
  71. package/dist/client/shapes-types.js.map +1 -0
  72. package/dist/client/shapes.d.ts +19 -0
  73. package/dist/client/shapes.d.ts.map +1 -0
  74. package/dist/client/shapes.js +110 -0
  75. package/dist/client/shapes.js.map +1 -0
  76. package/dist/client/thesaurus-types.d.ts +112 -5
  77. package/dist/client/thesaurus-types.d.ts.map +1 -1
  78. package/dist/client/thesaurus-types.js +2 -1
  79. package/dist/client/thesaurus-types.js.map +1 -1
  80. package/dist/client/thesaurus.d.ts +17 -3
  81. package/dist/client/thesaurus.d.ts.map +1 -1
  82. package/dist/client/thesaurus.js +120 -14
  83. package/dist/client/thesaurus.js.map +1 -1
  84. package/dist/config/platform-request-url.d.ts.map +1 -1
  85. package/dist/config/platform-request-url.js +5 -1
  86. package/dist/config/platform-request-url.js.map +1 -1
  87. package/dist/rstreams/leo-runtime.d.ts +6 -1
  88. package/dist/rstreams/leo-runtime.d.ts.map +1 -1
  89. package/dist/rstreams/leo-runtime.js +15 -2
  90. package/dist/rstreams/leo-runtime.js.map +1 -1
  91. package/docs/code-first-cli.md +28 -12
  92. package/docs/sdk-mcp-mapping.md +70 -3
  93. package/package.json +6 -4
  94. package/templates/shopify-orders/AGENTS.md +35 -0
  95. package/templates/shopify-orders/README.md +50 -0
  96. package/templates/shopify-orders/events/order-created.json +17 -0
  97. package/templates/shopify-orders/package.json +11 -0
  98. package/templates/shopify-orders/skills/shopify-orders.yaml +29 -0
  99. package/templates/shopify-orders/tsconfig.json +14 -0
  100. package/templates/shopify-orders/workflows/orders-enricher.ts +31 -0
@@ -14,8 +14,8 @@ deprecation aliases.
14
14
  | `loxtep_connect` | `client.connect` | `.connectors.*`, `.templates.*` |
15
15
  | `loxtep_workspace` | `client.workspace` | `.projects.*`, `.instances.*` (`list`/`get`/`create`; **`get_stream_config` is REST/CLI only** — not an MCP op). `.versions` (REST pending); planned MCP `get_project_workspace_status` → `ProjectWorkspaceStatus` ([docs](./project-workspace-status.md)) |
16
16
  | `loxtep_build` | `client.build` | `.workflows.*`, `.triggers.*`, `.data_products.*`, `.targets.*`, deploy writes, `.get_writer({ bot_id, queue })` escape hatch |
17
- | `loxtep_define` | `client.define` | `.schemas.*`, `.quality.*`, `.standards.*`, `.data_contracts.*`, `.domains.*` |
18
- | `loxtep_meaning` | `client.meaning` | `.thesaurus.*`, `.ontology.*`, `.packs.*`, `.semantic.*` (search/artifact/completeness) |
17
+ | `loxtep_define` | `client.define` | `.schemas.*` (data-product), `.shapes.*` (domain canonical shapes), `.quality.*`, `.standards.*`, `.data_contracts.*`, `.domains.*` |
18
+ | `loxtep_meaning` | `client.meaning` | `.thesaurus.*`, `.ontology.*`, `.packs.*`, `.semantic.*`, `.proposals.*`, `.bundles.import` |
19
19
  | `loxtep_review` | `client.review` | `.approvals.*`, `.improvements.*`, `.cdlc.*` (get/transition/propagate/lineage/deps + `list_review_queue`); `.mining.*` (`run_mining_pass`, `list_candidates`, `act_on_candidate`). CLI: `loxtep cdlc …`, `loxtep candidates list|act` |
20
20
  | `loxtep_query` | `client.query` | `.catalog.*`, `.discovery.*`, `.query()`, `.list_tables()`, `.search()` |
21
21
  | `loxtep_observe` | `client.observe` | `.status()`, `.stream_config()`, queue `.open_reader` / `.open_writer`, `.list_deployments()`, `.get_deployment()`, trust signals |
@@ -58,9 +58,31 @@ Bus physical names (`LeoCron`, `LeoS3`, …): Node `loxtep instances stream-conf
58
58
  or `client.workspace.instances.get_stream_config(id)` (Python same method). Not
59
59
  MCP `list_instances` or `get_sdk_config`.
60
60
 
61
+ ## Meaning: thesaurus / vocabulary (Phase 2)
62
+
63
+ MCP `loxtep_meaning` thesaurus ops map to `client.meaning.thesaurus`:
64
+
65
+ | MCP operation | SDK | REST |
66
+ | --- | --- | --- |
67
+ | `list_terms` | `client.meaning.thesaurus.list_terms()` | `GET /graph/organizations/{org}/thesaurus` |
68
+ | `get_term` | `.get_term(term_id)` | `GET …/thesaurus/{term_id}` |
69
+ | `create_term` | `.create_term({ canonical_key, aliases, … })` | `POST …/thesaurus` |
70
+ | `update_term` | `.update_term(term_id, { … })` | `PUT …/thesaurus/{term_id}` |
71
+ | `delete_term` | `.delete_term(term_id)` | `DELETE …/thesaurus/{term_id}` |
72
+ | `sync_vocabulary` | `.sync_vocabulary({ domain, terms, mode, dry_run? })` | `POST …/thesaurus/sync` |
73
+ | `create_enterprise_override` | `.create_enterprise_override({ canonical_key, enterprise_definition, divergence_reason, … })` | `POST …/thesaurus` (`is_override: true`) |
74
+ | `resolve_canonical_key` | `.resolve_canonical_key(key_or_alias)` | client-side over `list_terms` |
75
+ | append synonym (SDK) | `.append_synonym(canonical_key, alias_path, …)` | `POST …/thesaurus/synonyms` |
76
+
77
+ MCP-only (no SDK yet): `list_enterprise_overrides`, `resolve_semantic_gap`,
78
+ namespace-mapping CRUD (`register_namespace_mapping`, `list_namespace_mappings`,
79
+ `get_namespace_mapping`).
80
+
61
81
  ## Meaning: ontology concepts (LOX-1241)
62
82
 
63
- MCP `loxtep_meaning` ontology ops map to `client.meaning.ontology`:
83
+ MCP `loxtep_meaning` ontology ops map to `client.meaning.ontology`.
84
+ `node_type` is a **lowercase** enum: `entity` \| `microservice` \| `taxonomy` \|
85
+ `pattern` \| `custom` (not `Entity`).
64
86
 
65
87
  | MCP operation | SDK |
66
88
  | --- | --- |
@@ -74,6 +96,10 @@ MCP `loxtep_meaning` ontology ops map to `client.meaning.ontology`:
74
96
 
75
97
  REST: `/graph/organizations/{org}/ontology/concepts` and `…/relationships`.
76
98
 
99
+ Ontology concepts are **graph types and relationships** between them. Defining
100
+ **meaning** for agents/stewards is primarily **terms** (`thesaurus`) + **shapes**
101
+ (`client.define.shapes`).
102
+
77
103
  ## Meaning: vocabulary packs (LOX-1242)
78
104
 
79
105
  These ops live under MCP **`loxtep_meaning`** / semantic-layer (not `loxtep_define`).
@@ -117,6 +143,47 @@ Notes:
117
143
  - SDK calls the semantic-layer MS REST routes directly. MCP-only enrichments (empty-search / completeness `metadata.activation_state` from pack activation) are not duplicated — use `client.meaning.packs.get_activation_status()` when needed.
118
144
  - Pack lifecycle remains under `client.meaning.packs` (see vocabulary packs section above).
119
145
 
146
+ ## Meaning: semantic proposals (Phase 2)
147
+
148
+ Agent review of the same inbox Meaning shows. Batch helpers fan out to the
149
+ single-id PUT (no dedicated batch REST).
150
+
151
+ | Concern | SDK | REST |
152
+ | --- | --- | --- |
153
+ | list proposals | `client.meaning.proposals.list({ disposition?, proposal_type?, … })` | `GET /semantic-layer/semantic-proposals` |
154
+ | accept | `.accept(semantic_proposal_id, { resolution_note? })` | `PUT …/semantic-proposals/{id}` `disposition: accepted` |
155
+ | reject | `.reject(semantic_proposal_id, { resolution_note? })` | `PUT …` `disposition: rejected` |
156
+ | batch accept / reject | `.accept_batch` / `.reject_batch` | N× PUT |
157
+
158
+ ## Meaning: semantic bundles (Phase 2)
159
+
160
+ | MCP operation | SDK | REST |
161
+ | --- | --- | --- |
162
+ | `import_semantic_bundle` | `client.meaning.bundles.import({ bundle, dry_run? })` | `POST /semantic-layer/bundles/import` |
163
+
164
+ Phase 0 error contract: result always includes `skipped_count` and `errors`.
165
+ When skips/errors exist (or HTTP 207/422 with a parseable body), the SDK sets
166
+ `partial: true` and still returns the result instead of dropping error detail.
167
+ MCP-only sibling: `export_semantic_bundle` (no SDK yet).
168
+
169
+ ## Define: domain shapes (Phase 2)
170
+
171
+ Domain / org **canonical shapes** (`domain_schemas`) — distinct from
172
+ `client.define.schemas` (data-product schema versions on `/dataproducts`).
173
+
174
+ | MCP operation | SDK | REST |
175
+ | --- | --- | --- |
176
+ | `create_schema` | `client.define.shapes.create({ name, format, … })` | `POST /semantic-layer/schemas` |
177
+ | `list_schemas` | `.list({ domain_id?, format?, search? })` | `GET /semantic-layer/schemas` |
178
+ | `get_schema` (by `schema_id`) | `.get(schema_id)` | `GET /semantic-layer/schemas/{schema_id}` |
179
+ | `apply_schema` | `.apply({ schema_id, data_product_id, schema_version_id? })` | `POST …/schemas/{schema_id}/applications` |
180
+ | `patch_schema` (align) | `.align({ schema_id, aligned_to_concept_uri })` | `PUT …/schemas/{schema_id}` |
181
+
182
+ MCP-only (no SDK yet): `update_schema`, `delete_schema`, `list_schema_versions`,
183
+ `unapply_schema`, `list_schema_applications`, `add_schema_version`,
184
+ `get_schema_impact`, `install_schema_pack`. Data-product PII tagging stays on
185
+ `client.define.schemas.tag_pii_fields`.
186
+
120
187
  ## Review: CDLC + context mining (LOX-1244…1247)
121
188
 
122
189
  MCP `loxtep_review` CDLC / mining ops map to `client.review.cdlc` and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loxtep/sdk",
3
- "version": "0.9.15",
3
+ "version": "0.9.17",
4
4
  "description": "Loxtep SDK for Node.js — the Enterprise Context Layer: data products, workflows, projects, queues, and machine-usable context for AI",
5
5
  "author": "Loxtep <engineering@loxtep.io>",
6
6
  "homepage": "https://loxtep.io",
@@ -34,17 +34,19 @@
34
34
  "dist",
35
35
  "docs",
36
36
  "schemas",
37
+ "templates",
37
38
  "CHANGELOG.md"
38
39
  ],
39
40
  "scripts": {
40
- "build": "tsc && mkdir -p dist/lib/entity-json-schemas && cp src/lib/entity-json-schemas/ajv-loader.cjs dist/lib/entity-json-schemas/ajv-loader.cjs",
41
+ "build": "tsc && mkdir -p dist/lib/entity-json-schemas && cp src/lib/entity-json-schemas/ajv-loader.cjs dist/lib/entity-json-schemas/ajv-loader.cjs && cp src/cli/resolve-package-root.cjs dist/cli/resolve-package-root.cjs",
41
42
  "prepublishOnly": "npm run build",
42
43
  "test": "jest --config jest.config.cjs",
43
44
  "lint": "eslint src --ext .ts",
44
45
  "sync:entity-schemas": "node scripts/sync-entity-schemas.mjs",
45
46
  "generate:api-types": "tsx scripts/generate-api-types.ts",
46
47
  "docs": "typedoc",
47
- "test:e2e:cli": "bash scripts/cli-e2e.sh"
48
+ "test:e2e:cli": "bash scripts/cli-e2e.sh",
49
+ "test:e2e:try-it": "bash scripts/try-it-onboarding-e2e.sh"
48
50
  },
49
51
  "publishConfig": {
50
52
  "access": "public",
@@ -89,6 +91,7 @@
89
91
  "ajv-formats": "^3.0.1",
90
92
  "js-yaml": "^4.3.0",
91
93
  "leo-sdk": "7.1.21",
94
+ "tsx": "^4.22.0",
92
95
  "zod": "^4.4.3"
93
96
  },
94
97
  "devDependencies": {
@@ -97,7 +100,6 @@
97
100
  "fast-check": "^4.8.0",
98
101
  "jest": "^30.4.2",
99
102
  "ts-jest": "^29.4.9",
100
- "tsx": "^4.22.0",
101
103
  "typedoc": "^0.28.19",
102
104
  "typescript": "^6.0.3",
103
105
  "zod-to-ts": "^2.0.0"
@@ -0,0 +1,35 @@
1
+ # AGENTS.md — shopify-orders
2
+
3
+ This project was scaffolded from the `shopify-orders` template (Try it sample).
4
+
5
+ ## MCP tools
6
+
7
+ ```json
8
+ { "mcpServers": { "loxtep": { "url": "https://mcp.loxtep.io/ai/mcp/stream" } } }
9
+ ```
10
+
11
+ Dev MCP host: `https://mcpdev.loxtep.io/ai/mcp/stream`.
12
+
13
+ ## Lifecycle
14
+
15
+ ```bash
16
+ loxtep login
17
+ loxtep attach --instance <non-prod-instance-id>
18
+ loxtep setup
19
+ loxtep generate
20
+ loxtep test orders-enricher --event ./events/order-created.json
21
+ loxtep deploy
22
+ ```
23
+
24
+ `loxtep test` runs the handler locally with **live** instance I/O. Approve
25
+ `dataProducts.write` when prompted.
26
+
27
+ ## SDK methods
28
+
29
+ - `LoxtepClient.fromWorkspace()` — configure from `.loxtep/project.json`
30
+ - `defineDataWorkflow({ name, triggers, handler, requireApproval })`
31
+ - `workspace.dataProducts.*` — typed constants after `loxtep generate`
32
+
33
+ ## Skill scope
34
+
35
+ Default skill: `.loxtep/skills/shopify-orders.yaml`
@@ -0,0 +1,50 @@
1
+ # Shopify orders sample (`shopify-orders`)
2
+
3
+ Minimal code-first Loxtep workspace used by [Try it](https://loxtep.io/try-it).
4
+
5
+ ## Prerequisites
6
+
7
+ | Requirement | Notes |
8
+ | --- | --- |
9
+ | **Node.js 22+** | `node --version` |
10
+ | **Loxtep account** | [app.loxtep.io](https://app.loxtep.io) |
11
+ | **Auth** | `loxtep login` (writes `./.loxtep/credentials.json`) |
12
+ | **Instance** | Non-production preferred — `loxtep instances list` then `loxtep attach` |
13
+ | **Domain** | Org must have at least one domain (create in UI if none) |
14
+
15
+ `loxtep test` executes your handler **locally with live instance I/O** (stream writes).
16
+ It is not an offline simulator. Sample writes require terminal approval
17
+ (`requireApproval: ['dataProducts.write']`).
18
+
19
+ ## Lifecycle (empty directory → deploy)
20
+
21
+ ```bash
22
+ mkdir shopify-orders-demo && cd shopify-orders-demo
23
+ npm init -y
24
+ npm install @loxtep/sdk@^0.9.16
25
+ npx loxtep login
26
+ npx loxtep init --template shopify-orders
27
+ npx loxtep attach --instance <non-prod-instance-id>
28
+ npx loxtep setup # provisions orders_raw + orders_enriched (idempotent)
29
+ npx loxtep generate
30
+ npx loxtep test orders-enricher --event ./events/order-created.json
31
+ # Approve dataProducts.write when prompted (y)
32
+ npx loxtep deploy
33
+ ```
34
+
35
+ Re-running `setup` is safe: existing data products are reused.
36
+
37
+ ## What this template scaffolds
38
+
39
+ | Path | Purpose |
40
+ | --- | --- |
41
+ | `workflows/orders-enricher.ts` | Sample module (webhook → enrich → write) |
42
+ | `events/order-created.json` | Fixture for `loxtep test` |
43
+ | `AGENTS.md` / `.loxtep/skills/` | Agent + skill scope |
44
+ | `package.json` / `tsconfig.json` | Node 22 + typed workflows |
45
+
46
+ ## Approval / failure behavior
47
+
48
+ - Reject or timeout on a guarded write → operation skipped, **nonzero exit**
49
+ - Handler or toolbox errors → recorded in the action trace, **nonzero exit**
50
+ - Success → exit 0 and a trace with `handler.start` → `dataProducts.write` → `handler.complete`
@@ -0,0 +1,17 @@
1
+ {
2
+ "id": "gid://shopify/Order/5678901234",
3
+ "name": "#1001",
4
+ "email": "customer@example.com",
5
+ "created_at": "2026-09-22T12:00:00Z",
6
+ "currency": "USD",
7
+ "total_price": "129.99",
8
+ "financial_status": "paid",
9
+ "line_items": [
10
+ {
11
+ "id": 1,
12
+ "title": "Sample Widget",
13
+ "quantity": 1,
14
+ "price": "129.99"
15
+ }
16
+ ]
17
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "loxtep-shopify-orders-sample",
3
+ "private": true,
4
+ "type": "module",
5
+ "engines": {
6
+ "node": ">=22"
7
+ },
8
+ "dependencies": {
9
+ "@loxtep/sdk": "^0.9.16"
10
+ }
11
+ }
@@ -0,0 +1,29 @@
1
+ # Default skill for the shopify-orders template.
2
+ # Scoped to the sample resources provisioned by `loxtep setup`.
3
+ name: shopify-orders
4
+ description: Shopify orders enricher sample — orders_raw ingest and orders_enriched write target
5
+ scope:
6
+ # workflows left empty until after first `loxtep deploy` of orders-enricher
7
+ # (generate validates skill refs against the live workspace).
8
+ data_products:
9
+ - orders_raw
10
+ - orders_enriched
11
+ connectors: []
12
+ workflows: []
13
+ domains: []
14
+ queues: []
15
+ permissions:
16
+ data_products:
17
+ - read
18
+ - write
19
+ connectors:
20
+ - read
21
+ workflows:
22
+ - read
23
+ - write
24
+ - create
25
+ domains:
26
+ - read
27
+ queues:
28
+ - read
29
+ - write
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "resolveJsonModule": true,
10
+ "noEmit": true,
11
+ "types": ["node"]
12
+ },
13
+ "include": ["workflows/**/*.ts", ".loxtep/generated/**/*.ts"]
14
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Sample: enrich an inbound Shopify order and write it to `orders_enriched`.
3
+ *
4
+ * Prerequisites (see README / `loxtep setup`):
5
+ * - attached non-production instance
6
+ * - `orders_enriched` data product provisioned and deployed
7
+ * - `loxtep generate` so `.loxtep/generated` exists
8
+ *
9
+ * `loxtep test` runs this handler locally against the attached instance
10
+ * (live stream I/O — not an offline simulation). `dataProducts.write` is
11
+ * gated by `requireApproval` so sample writes cannot silently hit the bus.
12
+ */
13
+ import { defineDataWorkflow, on } from '@loxtep/sdk';
14
+ import { workspace } from '../.loxtep/generated/index.js';
15
+
16
+ export default defineDataWorkflow({
17
+ name: 'orders-enricher',
18
+ // Webhook trigger avoids depending on a friendly observe queue name.
19
+ // After `loxtep setup`, you can switch to on.queueEvent(workspace.queues.*)
20
+ // once a matching queue appears in `loxtep generate` output.
21
+ triggers: [on.webhook('/shopify/orders')],
22
+ requireApproval: ['dataProducts.write'],
23
+ async handler(ctx, event) {
24
+ const order = (event ?? {}) as Record<string, unknown>;
25
+ await ctx.toolbox.dataProducts.write(workspace.dataProducts.orders_enriched, {
26
+ ...order,
27
+ enriched_at: new Date().toISOString(),
28
+ source: 'orders-enricher',
29
+ });
30
+ },
31
+ });