@loxtep/sdk 0.7.21 → 0.7.24

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 (76) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +17 -13
  3. package/dist/cli/commands/config-cmd.js +1 -1
  4. package/dist/cli/commands/connectors-cmd.d.ts +23 -0
  5. package/dist/cli/commands/connectors-cmd.d.ts.map +1 -0
  6. package/dist/cli/commands/connectors-cmd.js +32 -0
  7. package/dist/cli/commands/connectors-cmd.js.map +1 -0
  8. package/dist/cli/commands/deploy-cmd.d.ts +5 -1
  9. package/dist/cli/commands/deploy-cmd.d.ts.map +1 -1
  10. package/dist/cli/commands/deploy-cmd.js +25 -2
  11. package/dist/cli/commands/deploy-cmd.js.map +1 -1
  12. package/dist/cli/commands/ingest-cmd.d.ts +7 -2
  13. package/dist/cli/commands/ingest-cmd.d.ts.map +1 -1
  14. package/dist/cli/commands/ingest-cmd.js +120 -31
  15. package/dist/cli/commands/ingest-cmd.js.map +1 -1
  16. package/dist/cli/commands/lint-cmd.d.ts +17 -0
  17. package/dist/cli/commands/lint-cmd.d.ts.map +1 -0
  18. package/dist/cli/commands/lint-cmd.js +61 -0
  19. package/dist/cli/commands/lint-cmd.js.map +1 -0
  20. package/dist/cli/help.d.ts.map +1 -1
  21. package/dist/cli/help.js +11 -6
  22. package/dist/cli/help.js.map +1 -1
  23. package/dist/cli/index.js +24 -3
  24. package/dist/cli/index.js.map +1 -1
  25. package/dist/client/connectors.d.ts.map +1 -1
  26. package/dist/client/connectors.js +7 -1
  27. package/dist/client/connectors.js.map +1 -1
  28. package/dist/errors/parse-http.d.ts.map +1 -1
  29. package/dist/errors/parse-http.js +33 -10
  30. package/dist/errors/parse-http.js.map +1 -1
  31. package/dist/index.d.ts +5 -2
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +3 -1
  34. package/dist/index.js.map +1 -1
  35. package/dist/lib/entity-json-schemas/ajv-loader.cjs +17 -0
  36. package/dist/lib/entity-json-schemas/index.d.ts +5 -0
  37. package/dist/lib/entity-json-schemas/index.d.ts.map +1 -0
  38. package/dist/lib/entity-json-schemas/index.js +3 -0
  39. package/dist/lib/entity-json-schemas/index.js.map +1 -0
  40. package/dist/lib/entity-json-schemas/types.d.ts +21 -0
  41. package/dist/lib/entity-json-schemas/types.d.ts.map +1 -0
  42. package/dist/lib/entity-json-schemas/types.js +32 -0
  43. package/dist/lib/entity-json-schemas/types.js.map +1 -0
  44. package/dist/lib/entity-json-schemas/validate-entity.d.ts +39 -0
  45. package/dist/lib/entity-json-schemas/validate-entity.d.ts.map +1 -0
  46. package/dist/lib/entity-json-schemas/validate-entity.js +143 -0
  47. package/dist/lib/entity-json-schemas/validate-entity.js.map +1 -0
  48. package/dist/lib/sdk-ingest-bundle.d.ts +60 -4
  49. package/dist/lib/sdk-ingest-bundle.d.ts.map +1 -1
  50. package/dist/lib/sdk-ingest-bundle.js +144 -51
  51. package/dist/lib/sdk-ingest-bundle.js.map +1 -1
  52. package/dist/lib/workspace-lint.d.ts +28 -0
  53. package/dist/lib/workspace-lint.d.ts.map +1 -0
  54. package/dist/lib/workspace-lint.js +199 -0
  55. package/dist/lib/workspace-lint.js.map +1 -0
  56. package/docs/getting-started.md +6 -16
  57. package/docs/quick-reference.md +9 -9
  58. package/docs/sdk-first-ingest.md +64 -227
  59. package/package.json +6 -2
  60. package/schemas/entity-json-schemas/SCHEMA_SYNC.md +18 -0
  61. package/schemas/entity-json-schemas/connection.json +310 -0
  62. package/schemas/entity-json-schemas/connector.json +188 -0
  63. package/schemas/entity-json-schemas/container.json +71 -0
  64. package/schemas/entity-json-schemas/contract.json +228 -0
  65. package/schemas/entity-json-schemas/data-product.json +163 -0
  66. package/schemas/entity-json-schemas/domain.json +74 -0
  67. package/schemas/entity-json-schemas/export.json +126 -0
  68. package/schemas/entity-json-schemas/odps-product.json +150 -0
  69. package/schemas/entity-json-schemas/quality-rule.json +115 -0
  70. package/schemas/entity-json-schemas/schema.json +206 -0
  71. package/schemas/entity-json-schemas/transformation.json +166 -0
  72. package/schemas/entity-json-schemas/validation.json +152 -0
  73. package/schemas/entity-json-schemas/workflow-graph.json +116 -0
  74. package/schemas/entity-json-schemas/workflow.json +193 -0
  75. package/docs/examples/generate-ingest-bundle.mjs +0 -133
  76. package/docs/examples/write-events.mjs +0 -54
@@ -0,0 +1,199 @@
1
+ /**
2
+ * Offline lint of a local Loxtep project package (entity schemas + relationships).
3
+ */
4
+ import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
5
+ import { join, relative } from 'node:path';
6
+ import { EntityType, validateEntity } from './entity-json-schemas/index.js';
7
+ function readJsonObject(filePath) {
8
+ try {
9
+ const raw = JSON.parse(readFileSync(filePath, 'utf8'));
10
+ if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
11
+ return raw;
12
+ }
13
+ return null;
14
+ }
15
+ catch {
16
+ return null;
17
+ }
18
+ }
19
+ function listJsonFiles(dir) {
20
+ if (!existsSync(dir))
21
+ return [];
22
+ const out = [];
23
+ for (const name of readdirSync(dir)) {
24
+ const full = join(dir, name);
25
+ let st;
26
+ try {
27
+ st = statSync(full);
28
+ }
29
+ catch {
30
+ continue;
31
+ }
32
+ if (st.isDirectory()) {
33
+ out.push(...listJsonFiles(full));
34
+ }
35
+ else if (name.endsWith('.json')) {
36
+ out.push(full);
37
+ }
38
+ }
39
+ return out;
40
+ }
41
+ function classifyEntity(projectDir, filePath) {
42
+ const rel = relative(projectDir, filePath).replace(/\\/g, '/');
43
+ if (rel.startsWith('connectors/') && rel.endsWith('.json')) {
44
+ return EntityType.CONNECTOR;
45
+ }
46
+ if (rel.match(/^workflows\/[^/]+\/workflow\.json$/)) {
47
+ return EntityType.WORKFLOW;
48
+ }
49
+ if (rel.match(/^workflows\/[^/]+\/connections\/[^/]+\.json$/)) {
50
+ return EntityType.CONNECTION;
51
+ }
52
+ if (rel.match(/^workflows\/[^/]+\/data-products\/[^/]+\.json$/)) {
53
+ return EntityType.DATA_PRODUCT;
54
+ }
55
+ if (rel.match(/^workflows\/[^/]+\/transformations\/[^/]+\.json$/)) {
56
+ return EntityType.TRANSFORMATION;
57
+ }
58
+ if (rel.match(/^workflows\/[^/]+\/validations\/[^/]+\.json$/)) {
59
+ return EntityType.VALIDATION;
60
+ }
61
+ if (rel.startsWith('domains/') && rel.endsWith('.json')) {
62
+ return EntityType.DOMAIN;
63
+ }
64
+ return null;
65
+ }
66
+ function discoverEntities(projectDir, workflowId) {
67
+ const entities = [];
68
+ const roots = [join(projectDir, 'connectors'), join(projectDir, 'domains')];
69
+ if (workflowId) {
70
+ roots.push(join(projectDir, 'workflows', workflowId));
71
+ }
72
+ else {
73
+ roots.push(join(projectDir, 'workflows'));
74
+ }
75
+ for (const root of roots) {
76
+ for (const filePath of listJsonFiles(root)) {
77
+ const entityType = classifyEntity(projectDir, filePath);
78
+ if (!entityType)
79
+ continue;
80
+ const data = readJsonObject(filePath);
81
+ const rel = relative(projectDir, filePath).replace(/\\/g, '/');
82
+ if (!data) {
83
+ entities.push({
84
+ path: rel,
85
+ entityType,
86
+ data: {},
87
+ });
88
+ // mark unreadable via empty + special handling below
89
+ continue;
90
+ }
91
+ entities.push({ path: rel, entityType, data });
92
+ }
93
+ }
94
+ return entities;
95
+ }
96
+ /**
97
+ * Lint local entity JSON against shipped schemas and basic relationship checks.
98
+ */
99
+ export function lintLocalPackage(options) {
100
+ const { projectDir, workflow_id } = options;
101
+ const issues = [];
102
+ if (!existsSync(projectDir)) {
103
+ return {
104
+ ok: false,
105
+ files_checked: 0,
106
+ issues: [{ path: projectDir, severity: 'error', message: 'Project directory not found' }],
107
+ };
108
+ }
109
+ const entities = discoverEntities(projectDir, workflow_id);
110
+ const byId = new Map();
111
+ for (const entity of entities) {
112
+ if (Object.keys(entity.data).length === 0) {
113
+ issues.push({
114
+ path: entity.path,
115
+ severity: 'error',
116
+ message: 'Invalid or unreadable JSON object',
117
+ });
118
+ continue;
119
+ }
120
+ const result = validateEntity(entity.entityType, entity.data);
121
+ if (!result.valid && result.errors) {
122
+ for (const err of result.errors) {
123
+ issues.push({
124
+ path: `${entity.path}${err.path}`,
125
+ severity: 'error',
126
+ message: err.message,
127
+ });
128
+ }
129
+ }
130
+ const idKey = entity.data.connector_id ||
131
+ entity.data.connection_id ||
132
+ entity.data.workflow_id ||
133
+ entity.data.data_product_id ||
134
+ entity.data.domain_id;
135
+ if (idKey) {
136
+ byId.set(idKey, entity);
137
+ }
138
+ }
139
+ // Relationship checks
140
+ for (const entity of entities) {
141
+ if (entity.entityType === EntityType.CONNECTION) {
142
+ const connectorId = entity.data.connector_id;
143
+ if (typeof connectorId === 'string' && connectorId.length > 0) {
144
+ // connector may exist only remotely; warn only if local connectors/ is present
145
+ // and does not contain this id — keep as error when local connector file expected.
146
+ const localConnectors = entities.filter(e => e.entityType === EntityType.CONNECTOR);
147
+ if (localConnectors.length > 0 &&
148
+ !localConnectors.some(c => c.data.connector_id === connectorId)) {
149
+ issues.push({
150
+ path: entity.path,
151
+ severity: 'error',
152
+ message: `connector_id "${connectorId}" not found under connectors/`,
153
+ });
154
+ }
155
+ }
156
+ else {
157
+ issues.push({
158
+ path: entity.path,
159
+ severity: 'error',
160
+ message: 'connection is missing connector_id',
161
+ });
162
+ }
163
+ }
164
+ if (entity.entityType === EntityType.DATA_PRODUCT) {
165
+ const upstream = entity.data.upstream_entity_id;
166
+ if (typeof upstream === 'string' && upstream.length > 0 && !byId.has(upstream)) {
167
+ // Upstream may be a connection in the same workflow — check connection ids
168
+ const conn = entities.find(e => e.entityType === EntityType.CONNECTION && e.data.connection_id === upstream);
169
+ if (!conn) {
170
+ issues.push({
171
+ path: entity.path,
172
+ severity: 'error',
173
+ message: `upstream_entity_id "${upstream}" not found in local package`,
174
+ });
175
+ }
176
+ }
177
+ }
178
+ }
179
+ return {
180
+ ok: issues.length === 0,
181
+ issues,
182
+ files_checked: entities.length,
183
+ };
184
+ }
185
+ /**
186
+ * True when the project has at least one entity JSON package file to lint.
187
+ */
188
+ export function hasLocalEntityPackage(projectDir) {
189
+ const workflowsDir = join(projectDir, 'workflows');
190
+ if (!existsSync(workflowsDir))
191
+ return false;
192
+ for (const name of readdirSync(workflowsDir)) {
193
+ const wfJson = join(workflowsDir, name, 'workflow.json');
194
+ if (existsSync(wfJson))
195
+ return true;
196
+ }
197
+ return existsSync(join(projectDir, 'connectors')) && listJsonFiles(join(projectDir, 'connectors')).length > 0;
198
+ }
199
+ //# sourceMappingURL=workspace-lint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-lint.js","sourceRoot":"","sources":["../../src/lib/workspace-lint.ts"],"names":[],"mappings":"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"}
@@ -163,29 +163,19 @@ products, connectors, domains, queues, workflows). Re-run after platform changes
163
163
 
164
164
  ---
165
165
 
166
- ## Step 6: Your first data product (SDK ingest)
166
+ ## Step 6: Create a data product and write from your app
167
167
 
168
- After `attach`, the usual greenfield goal is: **provision a source data product
169
- on your instance, then call `get_writer` from your app.**
170
-
171
- `get_writer` needs **deployment metadata** (queues and bots created at deploy
172
- time). Creating a catalog row alone is not enough.
173
-
174
- **Recommended:** follow **[SDK-first ingest](./sdk-first-ingest.md)** end to end.
175
- Short version:
168
+ After `attach`, create a data product on that instance, then call `get_writer`
169
+ from your application:
176
170
 
177
171
  ```bash
178
- pnpm exec loxtep domains list
179
-
180
- # One command: SDK connector + workflow bundle + deploy (no MCP)
181
172
  pnpm exec loxtep ingest provision --name app-events
182
-
173
+ pnpm exec loxtep lint
174
+ pnpm exec loxtep deploy
183
175
  pnpm exec loxtep data-products list
184
- node node_modules/@loxtep/sdk/docs/examples/write-events.mjs
185
176
  ```
186
177
 
187
- Do **not** expect `pnpm exec loxtep data-products create` alone to enable
188
- streaming — it registers catalog metadata without runtime bindings.
178
+ See **[Write to a data product](./sdk-first-ingest.md)** for the full flow and code sample.
189
179
 
190
180
  ---
191
181
 
@@ -54,8 +54,8 @@ bus config from deployment metadata.
54
54
  ```typescript
55
55
  const writer = await client.get_writer('orders'); // deployed data product name
56
56
 
57
- writer.write({ id: 'evt-1', payload: { key: 'value' } });
58
- writer.write({ id: 'evt-2', payload: { key: 'value' } });
57
+ writer.write({ order_id: 'ord-1', status: 'placed' });
58
+ writer.write({ order_id: 'ord-2', status: 'shipped' });
59
59
 
60
60
  await writer.close(); // flushes all buffered events
61
61
  ```
@@ -65,8 +65,8 @@ await writer.close(); // flushes all buffered events
65
65
  ```python
66
66
  writer = await client.get_writer("my-data-product")
67
67
 
68
- writer.write({"id": "evt-1", "payload": {"key": "value"}})
69
- writer.write({"id": "evt-2", "payload": {"key": "value"}})
68
+ writer.write({"order_id": "ord-1", "status": "placed"})
69
+ writer.write({"order_id": "ord-2", "status": "shipped"})
70
70
 
71
71
  await writer.close() # flushes all buffered events
72
72
  ```
@@ -189,15 +189,15 @@ pnpm exec loxtep attach --instance <instance-id>
189
189
  pnpm exec loxtep generate
190
190
  ```
191
191
 
192
- **SDK-first ingest** (after attach see [SDK-first ingest](./sdk-first-ingest.md)):
192
+ **Create a data product and write from your app** (after attach):
193
193
 
194
194
  ```bash
195
- pnpm exec loxtep domains list
196
- node node_modules/@loxtep/sdk/docs/examples/generate-ingest-bundle.mjs
197
- pnpm exec loxtep workflows deploy --project-id <id> --instance-id <id>
198
- node node_modules/@loxtep/sdk/docs/examples/write-events.mjs
195
+ pnpm exec loxtep ingest provision --name app-events
196
+ pnpm exec loxtep lint && pnpm exec loxtep deploy
199
197
  ```
200
198
 
199
+ See [Write to a data product](./sdk-first-ingest.md).
200
+
201
201
  Code-first workflow modules:
202
202
 
203
203
  ```bash
@@ -1,263 +1,94 @@
1
- # SDK-first ingest your first data product and `get_writer`
1
+ # Write to a data product from your app
2
2
 
3
- You logged in, ran `init`, and `attach`. This guide takes you from there to
4
- **writing events from application code** with `client.get_writer(name)`.
3
+ You installed the SDK, initialized a workspace, and attached an instance. Next:
4
+ **create a data product**, open a writer, and send events from your code.
5
5
 
6
- That is the primary greenfield path when you are **not** wiring a SaaS connector
7
- first — you want your app (or ETL job) to push events into a **source** data
8
- product on your attached instance.
9
-
10
- > **Full CLI workspace setup:** [Getting Started](./getting-started.md) steps 1–4.
11
- > **Code-first workflow modules:** [Code-first CLI](./code-first-cli.md).
6
+ > Already set up login, init, and attach? [Getting started](./getting-started.md).
12
7
 
13
8
  ---
14
9
 
15
- ## What you are building
16
-
17
- ```text
18
- Your app ──get_writer──▶ rstreams queue ──▶ source data product (catalog)
19
- ▲ ▲
20
- │ │
21
- @loxtep/sdk provisioned at deploy time
22
- fromWorkspace()
23
- ```
10
+ ## Phase 1 Workspace ready
24
11
 
25
- `get_writer` resolves **queue**, **bot**, and **stream bus** config from
26
- **deployment metadata** (`deployment_bindings` on the data product). A catalog
27
- row alone is not enough — the ingestion workflow must be **saved and deployed**
28
- on your attached instance first.
29
-
30
- ---
31
-
32
- ## Prerequisites
33
-
34
- Run from your scaffolded workspace (directory with `.loxtep/project.json`):
12
+ Run commands from your project directory (the folder with `.loxtep/project.json`).
35
13
 
36
14
  ```bash
37
- pnpm exec loxtep config list # project_id + instance_id should be set
38
- pnpm exec loxtep domains list # you need a domain_id for the data product
15
+ pnpm exec loxtep login
16
+ pnpm exec loxtep init
17
+ pnpm exec loxtep attach --instance <instance-id>
18
+ pnpm exec loxtep config list
39
19
  ```
40
20
 
41
- Every org has at least one domain. Copy a `domain_id` from the list.
42
-
43
- ---
44
-
45
- ## Overview (four phases)
46
-
47
- | Phase | What | How |
48
- | ----- | ---- | --- |
49
- | 1 | Workspace ready | `login` → `init` → `attach` (done) |
50
- | 2 | Provision runtime | SDK connector + workflow bundle + deploy |
51
- | 3 | Verify | `data-products list` / `data-products get` |
52
- | 4 | Write events | `LoxtepClient.fromWorkspace()` + `get_writer` |
53
-
54
- Phases 2–4 below.
21
+ You should see `project_id` and `instance_id` from `attach`.
55
22
 
56
23
  ---
57
24
 
58
- ## Phase 2 — Provision runtime (required before `get_writer`)
59
-
60
- Runtime provisioning creates the workflow graph (SDK connection node → source
61
- data product node), saves it to your project workspace, and **deploys** it to your
62
- instance so queues and bots exist.
63
-
64
- ### Option A — CLI one-shot (recommended, no MCP)
25
+ ## Phase 2 — Create your data product (local package)
65
26
 
66
- From your workspace root (after `login`, `init`, `attach`):
27
+ Pick a name your app will use (example: `app-events`):
67
28
 
68
29
  ```bash
30
+ pnpm exec loxtep connectors list --type sdk # optional: see existing SDK connectors
69
31
  pnpm exec loxtep ingest provision --name app-events
32
+ pnpm exec loxtep lint
70
33
  ```
71
34
 
72
- This command:
73
-
74
- 1. Creates an org-level **SDK connector** (`connector_type: "sdk"`).
75
- 2. Builds and saves the workflow bundle (`workflow.json` + connection + source data product).
76
- 3. **Deploys** to your attached instance (unless you pass `--no-deploy` or `--dry-run`).
77
-
78
- Validate first without writing:
79
-
80
- ```bash
81
- pnpm exec loxtep ingest provision --name app-events --dry-run
82
- ```
83
-
84
- ### Option B — Helper script + bundle save
85
-
86
- Generate bundle JSON only:
87
-
88
- ```bash
89
- node node_modules/@loxtep/sdk/docs/examples/generate-ingest-bundle.mjs
90
- ```
91
-
92
- Then persist and deploy via CLI:
35
+ `ingest provision` reuses an existing SDK connector when one exists (or creates one),
36
+ then writes a **local** workflow package under `connectors/` and `workflows/<id>/`.
37
+ It does **not** deploy by default. Validate with `loxtep lint`, then publish:
93
38
 
94
39
  ```bash
95
- pnpm exec loxtep bundle save --dry-run --file .loxtep/sdk-ingest-bundle.json
96
- pnpm exec loxtep bundle save --file .loxtep/sdk-ingest-bundle.json
97
- pnpm exec loxtep workflows deploy \
98
- --project-id <project-id-from-config-list> \
99
- --instance-id <instance-id-from-config-list>
40
+ pnpm exec loxtep deploy --dry-run # lint only
41
+ pnpm exec loxtep deploy # lint + compile/deploy modules
42
+ # or: pnpm exec loxtep ingest provision --name app-events --deploy
100
43
  ```
101
44
 
102
- Or set `LOXTEP_AUTO_SAVE=1` on the helper script to save + deploy in one step.
103
-
104
- Poll until deployment completes (Web UI **Observe**, or `loxtep observe status`).
105
-
106
- ### Option C — Loxtep MCP (Cursor / agent)
107
-
108
- Use **`loxtep_build`** → `save_workflow_bundle` if you prefer MCP authoring.
109
- Same bundle shape as below; deploy with `loxtep workflows deploy` or MCP
110
- `deploy_project`.
111
-
112
- ### Option D — Web UI (Studio)
113
-
114
- Create an **ingestion** workflow with an **SDK** connection node and a **source**
115
- data product node, then deploy to your attached instance from the Studio deploy
116
- flow.
45
+ That local package is what you iterate on; deploy pushes it to your instance.
117
46
 
118
47
  ---
119
48
 
120
- ### Workflow bundle shape
121
-
122
- Minimal ingestion bundle (SDK connection → source data product). Replace UUIDs
123
- and names; `connector_id` must be the SDK connector from step 2A or 2B.
124
-
125
- ```json
126
- {
127
- "workflow.json": {
128
- "workflow_id": "<workflow-uuid>",
129
- "organization_id": "<org-uuid>",
130
- "project_id": "<project-uuid>",
131
- "name": "SDK App Events Ingest",
132
- "workflow_type": "ingestion",
133
- "domain_id": "<domain-uuid>",
134
- "status": "active",
135
- "configuration": {},
136
- "metadata": {},
137
- "created_at": "2026-07-23T00:00:00.000Z",
138
- "updated_at": "2026-07-23T00:00:00.000Z"
139
- },
140
- "connections/<connection-uuid>.json": {
141
- "connection_id": "<connection-uuid>",
142
- "organization_id": "<org-uuid>",
143
- "project_id": "<project-uuid>",
144
- "workflow_id": "<workflow-uuid>",
145
- "connector_id": "<sdk-connector-uuid>",
146
- "key": "sdk-input",
147
- "name": "SDK Input",
148
- "type": "sdk",
149
- "status": "active",
150
- "configuration": {
151
- "sdk_type": "nodejs",
152
- "event_type": "app-events"
153
- },
154
- "created_at": "2026-07-23T00:00:00.000Z",
155
- "updated_at": "2026-07-23T00:00:00.000Z"
156
- },
157
- "data-products/<data-product-uuid>.json": {
158
- "data_product_id": "<data-product-uuid>",
159
- "organization_id": "<org-uuid>",
160
- "project_id": "<project-uuid>",
161
- "workflow_id": "<workflow-uuid>",
162
- "upstream_entity_id": "<connection-uuid>",
163
- "upstream_entity_type": "connections",
164
- "domain_id": "<domain-uuid>",
165
- "name": "app-events",
166
- "kind": "source",
167
- "status": "draft",
168
- "owner": {},
169
- "governance": {
170
- "classification": "internal",
171
- "pii_fields": [],
172
- "compliance_requirements": [],
173
- "tags": []
174
- },
175
- "metadata": {},
176
- "created_at": "2026-07-23T00:00:00.000Z",
177
- "updated_at": "2026-07-23T00:00:00.000Z"
178
- }
179
- }
180
- ```
181
-
182
- The **`name`** on the data product node (`app-events` above) is what you pass
183
- to `get_writer('app-events')`.
184
-
185
- ---
186
-
187
- ### Do not stop at `data-products create`
188
-
189
- ```bash
190
- # Creates a catalog row only — get_writer will fail with "not deployed"
191
- pnpm exec loxtep data-products create --name app-events --domain-id <uuid> --kind source
192
- ```
193
-
194
- Use **`data-products create`** only when you already have (or will immediately
195
- add) a deployed workflow that sets `deployment_bindings`. For greenfield SDK
196
- ingest, prefer the bundle + deploy path above.
197
-
198
- ---
49
+ ## Phase 3 — Confirm it exists
199
50
 
200
- ## Phase 3 — Verify deployment
51
+ After deploy:
201
52
 
202
53
  ```bash
203
54
  pnpm exec loxtep data-products list
204
- pnpm exec loxtep data-products get <data-product-id>
205
55
  ```
206
56
 
207
- A deployable source data product includes **`deployment_bindings`** with
208
- `queue_name`, `bot_id`, and `instance_id`. If those are missing, finish Phase 2
209
- before writing.
210
-
211
- Re-run code generation after platform changes:
212
-
213
- ```bash
214
- pnpm exec loxtep generate
215
- ```
57
+ You should see `app-events` (or whatever name you chose).
216
58
 
217
59
  ---
218
60
 
219
- ## Phase 4 — Write events from your app
61
+ ## Phase 4 — Write from your application
220
62
 
221
- Copy or run the example:
222
-
223
- ```bash
224
- node node_modules/@loxtep/sdk/docs/examples/write-events.mjs
225
- ```
226
-
227
- Or embed in your service:
63
+ The SDK reads your workspace (`.loxtep/project.json` + credentials). No manual
64
+ URL or token wiring.
228
65
 
229
66
  ```typescript
230
67
  import { LoxtepClient } from '@loxtep/sdk';
231
68
 
232
69
  const client = await LoxtepClient.fromWorkspace();
233
70
 
234
- // Name from Phase 2 bundle / data-products list (case-sensitive)
235
71
  const writer = await client.get_writer('app-events');
236
72
 
237
73
  writer.write({
238
- event_id: 'evt_001',
239
- occurred_at: new Date().toISOString(),
240
- payload: { user_id: 'u_1', action: 'signup' },
74
+ user_id: 'u_1',
75
+ action: 'signup',
241
76
  });
242
77
 
243
- await writer.close(); // flush buffered events
78
+ await writer.close();
244
79
  ```
245
80
 
246
- Optional writer tuning:
81
+ Pass your **business fields** to `write()`. The SDK wraps them for transport —
82
+ you do not build an envelope or use a `payload` property yourself.
247
83
 
248
- ```typescript
249
- await client.get_writer('app-events', {
250
- batch_size: 500,
251
- max_retries: 5,
252
- });
253
- ```
84
+ Use the same name you passed to `ingest provision`.
254
85
 
255
- Read back (after data has landed):
86
+ ### Read back (optional)
256
87
 
257
88
  ```typescript
258
- const reader = await client.get_reader('app-events', {
259
- bot_id: 'my-app-reader',
260
- });
89
+ const client = await LoxtepClient.fromWorkspace();
90
+ const reader = await client.get_reader('app-events');
91
+
261
92
  for await (const event of reader) {
262
93
  console.log(event);
263
94
  break;
@@ -266,39 +97,45 @@ for await (const event of reader) {
266
97
 
267
98
  ---
268
99
 
269
- ## End-to-end checklist
100
+ ## Quick checklist
270
101
 
271
102
  ```bash
272
103
  pnpm exec loxtep login
273
104
  pnpm exec loxtep init
274
105
  pnpm exec loxtep attach --instance <instance-id>
275
- pnpm exec loxtep domains list
276
-
277
106
  pnpm exec loxtep ingest provision --name app-events
278
- # or: generate-ingest-bundle.mjs + loxtep bundle save
279
-
107
+ pnpm exec loxtep lint
108
+ pnpm exec loxtep deploy
280
109
  pnpm exec loxtep data-products list
281
- node node_modules/@loxtep/sdk/docs/examples/write-events.mjs
282
110
  ```
283
111
 
112
+ Then `get_writer('app-events')` in your app.
113
+
284
114
  ---
285
115
 
286
- ## Troubleshooting
116
+ ## If something goes wrong
287
117
 
288
- | Symptom | Cause | Fix |
289
- | ------- | ----- | --- |
290
- | `StreamingError: is not deployed` | No `deployment_bindings` | Complete Phase 2 (bundle + deploy) |
291
- | `NotFoundError: Data product '…' not found` | Wrong name or wrong instance | `data-products list`; check `attach` / `instance_id` |
292
- | `AmbiguityError: Multiple data products match` | Same name on multiple instances | Use UUID or set `instance_id` in project.json |
293
- | Empty `domains list` | New org / permissions | Create a domain in the Web UI (Governance) |
118
+ | What you see | What to do |
119
+ | ------------ | ---------- |
120
+ | Data product not found | Run `data-products list`. Check the name matches `get_writer('…')` exactly. |
121
+ | Not ready to accept writes | Run `ingest provision`, then `deploy` (or `ingest provision --deploy`). Make sure you ran `attach` first. |
122
+ | Multiple products with the same name | Pass the data product id to `get_writer`, or ensure `attach` points at the right instance. |
123
+ | No domain available | Create a domain in the Loxtep UI, or pass `--domain-id` to `ingest provision`. |
294
124
 
295
125
  ---
296
126
 
297
- ## Next steps
127
+ ## Other ways to create a data product
128
+
129
+ **Loxtep MCP (AI assistant)** — If you use the Loxtep MCP in Cursor, Claude Code,
130
+ or another client, describe what you want in plain language, for example:
131
+
132
+ > Create a data product named `app-events` on my current Loxtep project and
133
+ > deploy it to the instance I already attached. I want to write events from my
134
+ > app with the SDK.
135
+
136
+ The assistant provisions the same end state as `ingest provision`. After that,
137
+ Phase 4 above is unchanged: `get_writer('app-events')` and `write()`.
298
138
 
299
- | Resource | Description |
300
- | -------- | ----------- |
301
- | [Getting Started](./getting-started.md) | Login, init, attach, generate |
302
- | [Quick Reference](./quick-reference.md) | CLI + SDK cheat sheet |
303
- | [Code-first CLI](./code-first-cli.md) | TypeScript workflow modules + `loxtep deploy` |
304
- | [Event Replay Cookbook](./event-replay-cookbook.md) | Replay and reprocess |
139
+ **Loxtep Studio (web app)** — *Beta.* You can also design and publish a data
140
+ product in the Loxtep UI. Once it exists on your instance, the SDK code in
141
+ Phase 4 is the same.