@alvera-ai/platform-sdk 0.17.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agent/AGENTS.md +82 -144
- package/.agent/account_management.md +2 -2
- package/.agent/action_logs.md +4 -4
- package/.agent/ai_agents.md +28 -21
- package/.agent/ai_sandbox.md +49 -39
- package/.agent/connected_apps.md +3 -3
- package/.agent/cookbook/_fixtures/README.md +1 -1
- package/.agent/cookbook/_fixtures/{foundation → organic-marketing}/_lead_submissions_foundation_generic_table.liquid +1 -1
- package/.agent/cookbook/_fixtures/organic-marketing/_lead_submissions_foundation_legal_entity.liquid +80 -0
- package/.agent/cookbook/_fixtures/{foundation → organic-marketing}/_lead_submissions_foundation_mdm.liquid +2 -1
- package/.agent/cookbook/_fixtures/payments-compliance/_compliance_screenings_generic_table.liquid +57 -0
- package/.agent/cookbook/_fixtures/payments-compliance/_compliance_screenings_legal_entity.liquid +30 -0
- package/.agent/cookbook/_fixtures/payments-compliance/_compliance_screenings_mdm.liquid +44 -0
- package/.agent/cookbook/_fixtures/payments-compliance/_payment_accounts_generic_table.liquid +57 -0
- package/.agent/cookbook/_fixtures/payments-compliance/_payment_accounts_legal_entity.liquid +36 -0
- package/.agent/cookbook/_fixtures/payments-compliance/_payment_accounts_mdm.liquid +41 -0
- package/.agent/cookbook/_fixtures/primary-care-feedback/_cahps_appointments_generic_table.liquid +70 -0
- package/.agent/cookbook/_fixtures/primary-care-feedback/_cahps_appointments_legal_entity.liquid +52 -0
- package/.agent/cookbook/_fixtures/primary-care-feedback/_cahps_appointments_mdm.liquid +42 -0
- package/.agent/cookbook/_fixtures/subscription-saas/_customers_subscription_generic_table.liquid +38 -0
- package/.agent/cookbook/_fixtures/subscription-saas/_customers_subscription_legal_entity.liquid +48 -0
- package/.agent/cookbook/_fixtures/subscription-saas/_customers_subscription_mdm.liquid +49 -0
- package/.agent/cookbook/organic-marketing.md +2801 -0
- package/.agent/cookbook/payments-compliance.md +2180 -0
- package/.agent/cookbook/primary-care.md +2175 -0
- package/.agent/cookbook/subscription-saas.md +2403 -0
- package/.agent/data_activation_clients.md +65 -52
- package/.agent/datalakes.md +338 -171
- package/.agent/errors.md +3 -3
- package/.agent/generic_tables.md +151 -62
- package/.agent/interoperability_contracts.md +57 -22
- package/.agent/mdm.md +136 -153
- package/.agent/messages.md +36 -34
- package/.agent/mock-services.md +1 -1
- package/.agent/mutations.md +2 -2
- package/.agent/templates.md +14 -13
- package/.agent/tools.md +63 -21
- package/.agent/type_naming.md +13 -13
- package/.agent/workflows.md +99 -53
- package/README.md +2 -2
- package/dist/bin/platform-sdk.mjs +33 -47
- package/dist/bin/platform-sdk.mjs.map +1 -1
- package/dist/index.d.mts +565 -379
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +494 -59
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -3
- package/.agent/cookbook/_fixtures/foundation/_lead_submissions_foundation_legal_entity.liquid +0 -88
- package/.agent/cookbook/_fixtures/healthcare/_cahps_appointments_healthcare_appointment.liquid +0 -47
- package/.agent/cookbook/_fixtures/healthcare/_cahps_appointments_healthcare_mdm.liquid +0 -24
- package/.agent/cookbook/_fixtures/healthcare/_cahps_appointments_healthcare_patient.liquid +0 -38
- package/.agent/cookbook/_fixtures/payments/_compliance_screenings_payments_compliance_screening.liquid +0 -59
- package/.agent/cookbook/_fixtures/payments/_compliance_screenings_payments_mdm.liquid +0 -36
- package/.agent/cookbook/_fixtures/payments/_payment_accounts_payments_mdm.liquid +0 -30
- package/.agent/cookbook/_fixtures/payments/_payment_accounts_payments_payment_account.liquid +0 -55
- package/.agent/cookbook/_fixtures/subscription/_customers_subscription_mdm.liquid +0 -20
- package/.agent/cookbook/_setup/foundation.md +0 -359
- package/.agent/cookbook/_setup/healthcare.md +0 -361
- package/.agent/cookbook/_setup/payments.md +0 -365
- package/.agent/cookbook/_setup/subscription.md +0 -364
- package/.agent/cookbook/action-status-updaters.md +0 -278
- package/.agent/cookbook/ai-agent-invoke.md +0 -279
- package/.agent/cookbook/appointment-review-sms-workflow.md +0 -801
- package/.agent/cookbook/birthday-greeting-sms-trigger.md +0 -696
- package/.agent/cookbook/bulk-ingest.md +0 -302
- package/.agent/cookbook/contact-us-triage-with-llm.md +0 -663
- package/.agent/cookbook/dunning-sms-for-delinquent.md +0 -659
- package/.agent/cookbook/generic-tables.md +0 -244
- package/.agent/cookbook/invite-team.md +0 -200
- package/.agent/cookbook/kyc-notification-on-account-activation.md +0 -661
- package/.agent/cookbook/marketing-campaign-send.md +0 -1044
- package/.agent/cookbook/paginated-restapi-poller.md +0 -383
- package/.agent/cookbook/rest-fetch.md +0 -273
- package/.agent/cookbook/sanctions-screening-with-agent-review.md +0 -773
- package/.agent/cookbook/score-leads-with-llm-categorization.md +0 -665
- package/.agent/cookbook/system-templates.md +0 -165
- package/.agent/cookbook/talk-to-data.md +0 -178
- package/.agent/cookbook/triage-prospects-by-priority.md +0 -571
- package/.agent/cookbook/welcome-sms-for-customers.md +0 -647
- /package/.agent/cookbook/_fixtures/{healthcare → primary-care-feedback}/memorandum-of-association-01.png +0 -0
- /package/.agent/cookbook/_fixtures/{healthcare → primary-care-feedback}/sample_two_page.pdf +0 -0
- /package/.agent/cookbook/_fixtures/{subscription → subscription-saas}/_customers_subscription_customer.liquid +0 -0
- /package/.agent/cookbook/_fixtures/{subscription → subscription-saas}/stripe_customers_batch1.csv +0 -0
|
@@ -1,302 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: "Capability: load a whole file of records in one upload"
|
|
3
|
-
summary: A capability walk for bulk file ingestion. Stand up an ingestion chain (data source → manual-upload tool → interop contract → DAC), mint a presigned upload URL (`api.datalakes.createUploadLink`), PUT a CSV to it, enqueue the file (`api.dataActivationClients.ingestFile`), discover the batch the worker allocates by diffing the logs, and verify every row landed (`api.datasets.search`). For backfills and exports — not row-at-a-time.
|
|
4
|
-
industry: subscription
|
|
5
|
-
slug: bulk-ingest
|
|
6
|
-
vitest_source:
|
|
7
|
-
- integration-tests/tests/subscription/run-dac-bulk.test.ts
|
|
8
|
-
- integration-tests/tests/subscription/create-dac.test.ts
|
|
9
|
-
- integration-tests/tests/subscription/interoperability-contracts.test.ts
|
|
10
|
-
- integration-tests/tests/subscription/tools.test.ts
|
|
11
|
-
- integration-tests/tests/subscription/data-sources.test.ts
|
|
12
|
-
- integration-tests/tests/subscription/bootstrap.test.ts
|
|
13
|
-
status: green
|
|
14
|
-
---
|
|
15
|
-
|
|
16
|
-
# Capability
|
|
17
|
-
|
|
18
|
-
**What you get:** a whole file of records — a CSV export, a backfill — turned
|
|
19
|
-
into dataset rows in one upload, instead of one `.ingest` call per row.
|
|
20
|
-
|
|
21
|
-
The bulk path is three calls plus a discovery step:
|
|
22
|
-
|
|
23
|
-
1. `datalakes.createUploadLink(...)` → a presigned PUT URL + a storage `key`.
|
|
24
|
-
2. Raw `fetch` PUT of the file bytes to that URL (**plain HTTP, not the SDK**).
|
|
25
|
-
3. `dataActivationClients.ingestFile(..., { key })` → enqueues a background job.
|
|
26
|
-
4. The worker reads the file, allocates a fresh `batch_id`, and fans out
|
|
27
|
-
per-row jobs. The `batch_id` is **not returned** — you discover it by diffing
|
|
28
|
-
the DAC's logs before vs. after.
|
|
29
|
-
|
|
30
|
-
It runs through the same data-activation chain as inline `.ingest`; only the
|
|
31
|
-
entry point differs. This is **global** to every datalake. See
|
|
32
|
-
`data_activation_clients.md` §6.2 for the reference.
|
|
33
|
-
|
|
34
|
-
# Walkthrough
|
|
35
|
-
|
|
36
|
-
The `_setup/subscription.md` bootstrap left `api`, `tenantSlug`,
|
|
37
|
-
`datalakeSlug`, and `ctx.datalakeId` populated. Steps 001–004 build the same
|
|
38
|
-
ingestion chain any AR cookbook uses; 005–008 are the bulk-specific part.
|
|
39
|
-
|
|
40
|
-
## 001 — create the data source
|
|
41
|
-
|
|
42
|
-
The chain's first link registers where the rows originate. Its `uri` flows into
|
|
43
|
-
each ingested row's `source_uri`.
|
|
44
|
-
|
|
45
|
-
```typescript
|
|
46
|
-
const { data: ds } = await api.dataSources.create(tenantSlug, datalakeSlug, {
|
|
47
|
-
name: `Bulk Stripe Source ${runSuffix}`,
|
|
48
|
-
uri: 'stripe.example.com',
|
|
49
|
-
description: 'Stripe billing export — origin of the bulk customer CSV.',
|
|
50
|
-
status: 'active',
|
|
51
|
-
is_default: false,
|
|
52
|
-
})
|
|
53
|
-
dataSourceId = ds.id!
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
## 002 — create the manual-upload tool
|
|
57
|
-
|
|
58
|
-
A `manual_upload` tool backs both the inline and the file-upload ingest paths —
|
|
59
|
-
the rows arrive in the upload, not by the tool fetching them.
|
|
60
|
-
|
|
61
|
-
```typescript
|
|
62
|
-
const { data: tool } = await api.tools.create(tenantSlug, datalakeSlug, {
|
|
63
|
-
name: `Bulk Manual Upload Tool ${runSuffix}`,
|
|
64
|
-
description: 'Manual-upload data-exchange tool backing the bulk-ingest DAC.',
|
|
65
|
-
intent: 'data_exchange',
|
|
66
|
-
status: 'active',
|
|
67
|
-
datalake_id: ctx.datalakeId,
|
|
68
|
-
data_source_id: dataSourceId,
|
|
69
|
-
body: { tool_body_type: 'manual_upload' },
|
|
70
|
-
})
|
|
71
|
-
ctx.manualUploadToolId = tool.id!
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
## 003 — create the customer interoperability contract
|
|
75
|
-
|
|
76
|
-
The contract maps each inbound CSV row into a `Customer` upsert. Load the
|
|
77
|
-
production Stripe customer + MDM Liquid templates from the vendored fixtures
|
|
78
|
-
rather than inlining them.
|
|
79
|
-
|
|
80
|
-
```typescript
|
|
81
|
-
const { readFileSync } = await import('node:fs')
|
|
82
|
-
const { join } = await import('node:path')
|
|
83
|
-
const customerTemplate = readFileSync(
|
|
84
|
-
join(process.env.COOKBOOK_FIXTURES_DIR!, 'subscription/_customers_subscription_customer.liquid'),
|
|
85
|
-
'utf8',
|
|
86
|
-
)
|
|
87
|
-
const mdmTemplate = readFileSync(
|
|
88
|
-
join(process.env.COOKBOOK_FIXTURES_DIR!, 'subscription/_customers_subscription_mdm.liquid'),
|
|
89
|
-
'utf8',
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
const { data: contract } = await api.interoperabilityContracts.create(tenantSlug, datalakeSlug, {
|
|
93
|
-
name: `Bulk Stripe Customer Contract ${runSuffix}`,
|
|
94
|
-
description: 'Stripe customers → Subscription Customer (custom Liquid + MDM input).',
|
|
95
|
-
resource_type: 'customer',
|
|
96
|
-
template_config: { type: 'custom', body: customerTemplate },
|
|
97
|
-
mdm_input_config: { type: 'custom', body: mdmTemplate },
|
|
98
|
-
generic_table_id: null,
|
|
99
|
-
})
|
|
100
|
-
interopContractId = contract.id!
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
## 004 — create the manual-upload DAC
|
|
104
|
-
|
|
105
|
-
The Data Activation Client binds tool + data source + contract into one
|
|
106
|
-
ingestion endpoint. Its server-derived `slug` is the handle the file is ingested
|
|
107
|
-
against.
|
|
108
|
-
|
|
109
|
-
```typescript
|
|
110
|
-
const { data: dac } = await api.dataActivationClients.create(tenantSlug, datalakeSlug, {
|
|
111
|
-
name: `Bulk Stripe Customer DAC ${runSuffix}`,
|
|
112
|
-
description: 'Manual-upload DAC — ingests the bulk Stripe customer CSV.',
|
|
113
|
-
tool_id: ctx.manualUploadToolId,
|
|
114
|
-
data_source_id: dataSourceId,
|
|
115
|
-
tool_call: { tool_call_type: 'manual_upload' },
|
|
116
|
-
interoperability_contract_ids: [interopContractId],
|
|
117
|
-
})
|
|
118
|
-
dacId = dac.id!
|
|
119
|
-
ctx.dacSlug = dac.slug!
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
## 005 — mint a presigned upload URL and PUT the CSV
|
|
123
|
-
|
|
124
|
-
`createUploadLink` returns a presigned PUT `url` and the storage `key` the
|
|
125
|
-
platform will reference. Upload the file body to the URL with a **raw `fetch`** —
|
|
126
|
-
this PUT goes straight to object storage, **not** through the SDK. The CSV is the
|
|
127
|
-
vendored 4-row Stripe customer export.
|
|
128
|
-
|
|
129
|
-
```typescript
|
|
130
|
-
const { readFileSync } = await import('node:fs')
|
|
131
|
-
const { join } = await import('node:path')
|
|
132
|
-
const csvBody = readFileSync(
|
|
133
|
-
join(process.env.COOKBOOK_FIXTURES_DIR!, 'subscription/stripe_customers_batch1.csv'),
|
|
134
|
-
'utf8',
|
|
135
|
-
)
|
|
136
|
-
|
|
137
|
-
const { data: link } = await api.datalakes.createUploadLink(tenantSlug, datalakeSlug, {
|
|
138
|
-
content_type: 'text/csv',
|
|
139
|
-
filename: 'stripe_customers_batch1.csv',
|
|
140
|
-
})
|
|
141
|
-
ctx.uploadKey = link.key!
|
|
142
|
-
|
|
143
|
-
const put = await fetch(link.url!, {
|
|
144
|
-
method: 'PUT',
|
|
145
|
-
headers: { 'Content-Type': 'text/csv' },
|
|
146
|
-
body: csvBody,
|
|
147
|
-
})
|
|
148
|
-
if (put.status !== 200) {
|
|
149
|
-
throw new Error(`presigned PUT failed: ${put.status}`)
|
|
150
|
-
}
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
## 006 — snapshot the logs, then enqueue the file
|
|
154
|
-
|
|
155
|
-
`ingestFile` is fully async — it enqueues a background job and returns a
|
|
156
|
-
`job_id`, but **no `batch_id`** (the worker allocates that later). Snapshot the
|
|
157
|
-
existing log batch ids first, so step 007 can spot the new one the worker
|
|
158
|
-
creates.
|
|
159
|
-
|
|
160
|
-
```typescript
|
|
161
|
-
const { data: pre } = await api.dataActivationClients.logs.list(tenantSlug, datalakeSlug, ctx.dacSlug)
|
|
162
|
-
ctx.preBatchIds = (pre.data ?? [])
|
|
163
|
-
.map((r) => (r as { batch_id?: string }).batch_id)
|
|
164
|
-
.filter((b): b is string => typeof b === 'string')
|
|
165
|
-
|
|
166
|
-
const { data: job } = await api.dataActivationClients.ingestFile(tenantSlug, datalakeSlug, ctx.dacSlug, {
|
|
167
|
-
key: ctx.uploadKey,
|
|
168
|
-
})
|
|
169
|
-
if (!job.job_id) {
|
|
170
|
-
throw new Error('ingestFile did not return a job_id')
|
|
171
|
-
}
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
## 007 — discover the batch the worker allocated
|
|
175
|
-
|
|
176
|
-
Poll the logs until a **new** batch id (one not in the pre-snapshot) shows a
|
|
177
|
-
fully-merged customer ingest: `rows_ingested >= 4` (the four CSV rows) and a
|
|
178
|
-
non-empty `output_files` array (the merged data landed in storage).
|
|
179
|
-
|
|
180
|
-
```typescript
|
|
181
|
-
const pre = new Set<string>(ctx.preBatchIds)
|
|
182
|
-
const deadline = Date.now() + 60_000
|
|
183
|
-
let batchId: string | null = null
|
|
184
|
-
while (Date.now() < deadline && !batchId) {
|
|
185
|
-
const { data } = await api.dataActivationClients.logs.list(tenantSlug, datalakeSlug, ctx.dacSlug)
|
|
186
|
-
for (const row of (data.data ?? []) as Array<Record<string, unknown>>) {
|
|
187
|
-
const b = row.batch_id
|
|
188
|
-
if (typeof b !== 'string' || pre.has(b)) continue
|
|
189
|
-
if (row.dataset_table !== 'customers') continue
|
|
190
|
-
if (typeof row.rows_ingested !== 'number' || row.rows_ingested < 4) continue
|
|
191
|
-
if (!Array.isArray(row.output_files) || row.output_files.length === 0) continue
|
|
192
|
-
batchId = b
|
|
193
|
-
break
|
|
194
|
-
}
|
|
195
|
-
if (!batchId) await new Promise((r) => setTimeout(r, 1_000))
|
|
196
|
-
}
|
|
197
|
-
if (!batchId) {
|
|
198
|
-
throw new Error('bulk ingest did not produce a fully-merged batch within 60s')
|
|
199
|
-
}
|
|
200
|
-
ctx.batchId = batchId
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
## 008 — verify every row landed
|
|
204
|
-
|
|
205
|
-
Search the `customer` dataset scoped to that batch. `createUserSearch` compiles
|
|
206
|
-
the query (`ra` is the customer dataset alias); `search` returns the rows once
|
|
207
|
-
they materialize — poll until all four appear.
|
|
208
|
-
|
|
209
|
-
```typescript
|
|
210
|
-
const { data: userSearch } = await api.datasets.createUserSearch(tenantSlug, datalakeSlug, 'customer', {
|
|
211
|
-
search_query: `ra.batch_id = '${ctx.batchId}'`,
|
|
212
|
-
})
|
|
213
|
-
if (userSearch.status !== 'completed') {
|
|
214
|
-
throw new Error(`customer user-search did not compile: ${userSearch.status}`)
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
const deadline = Date.now() + 90_000
|
|
218
|
-
let rows: Array<Record<string, unknown>> = []
|
|
219
|
-
while (Date.now() < deadline && rows.length < 4) {
|
|
220
|
-
const { data: page } = await api.datasets.search(tenantSlug, datalakeSlug, 'customer', {
|
|
221
|
-
userSearchId: userSearch.id!,
|
|
222
|
-
dataAccessMode: 'unregulated',
|
|
223
|
-
})
|
|
224
|
-
rows = (page.data ?? []) as Array<Record<string, unknown>>
|
|
225
|
-
if (rows.length < 4) await new Promise((r) => setTimeout(r, 1_000))
|
|
226
|
-
}
|
|
227
|
-
if (rows.length < 4) {
|
|
228
|
-
throw new Error(`expected 4 ingested customers, found ${rows.length} within 90s`)
|
|
229
|
-
}
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
## 009 — write the integration test
|
|
233
|
-
|
|
234
|
-
End the build with a test you keep: run the bulk path in miniature —
|
|
235
|
-
mint a link, PUT a tiny inline CSV, enqueue it — and assert each call's
|
|
236
|
-
own acknowledgement. The probe rows are `test-`-prefixed so they are
|
|
237
|
-
unmistakably synthetic wherever they surface, and the test asserts the
|
|
238
|
-
enqueue ack (`job_id`), never synchronous merge completion — the worker
|
|
239
|
-
owns that. This block runs live under `make validate-cookbook`.
|
|
240
|
-
|
|
241
|
-
```typescript
|
|
242
|
-
// Re-GET — the client must still be there and carry its ingest slug.
|
|
243
|
-
const { data: dacRow } = await api.dataActivationClients.get(tenantSlug, datalakeSlug, dacId)
|
|
244
|
-
if (dacRow.slug !== ctx.dacSlug) {
|
|
245
|
-
throw new Error(`DAC slug drifted on read-back: ${dacRow.slug}`)
|
|
246
|
-
}
|
|
247
|
-
// Behavioural probe — the three-step bulk path, each step asserted on
|
|
248
|
-
// its own response.
|
|
249
|
-
const probeCsv =
|
|
250
|
-
'customer_number,customer_type,status,name,email,currency\n' +
|
|
251
|
-
`test-PROBE-1-${runSuffix},individual,contracted,test-Grace Hopper,test-grace-${runSuffix}@example.com,USD\n`
|
|
252
|
-
const { data: probeLink } = await api.datalakes.createUploadLink(tenantSlug, datalakeSlug, {
|
|
253
|
-
content_type: 'text/csv',
|
|
254
|
-
filename: `test-bulk-probe-${runSuffix}.csv`,
|
|
255
|
-
})
|
|
256
|
-
if (!probeLink.url || !probeLink.key) {
|
|
257
|
-
throw new Error('createUploadLink returned no url/key')
|
|
258
|
-
}
|
|
259
|
-
const probePut = await fetch(probeLink.url, {
|
|
260
|
-
method: 'PUT',
|
|
261
|
-
headers: { 'Content-Type': 'text/csv' },
|
|
262
|
-
body: probeCsv,
|
|
263
|
-
})
|
|
264
|
-
if (probePut.status !== 200) {
|
|
265
|
-
throw new Error(`presigned PUT failed: ${probePut.status}`)
|
|
266
|
-
}
|
|
267
|
-
const { data: probeJob } = await api.dataActivationClients.ingestFile(tenantSlug, datalakeSlug, ctx.dacSlug, {
|
|
268
|
-
key: probeLink.key,
|
|
269
|
-
})
|
|
270
|
-
// job_id is a NUMBER on the wire (an Oban job id), status "scheduled".
|
|
271
|
-
if (probeJob.job_id === undefined || probeJob.job_id === null) {
|
|
272
|
-
throw new Error('ingestFile probe returned no job_id')
|
|
273
|
-
}
|
|
274
|
-
```
|
|
275
|
-
|
|
276
|
-
If the probe fails in production, escalate with the failing call's
|
|
277
|
-
response — don't retry the enqueue in a loop or reach into the worker's
|
|
278
|
-
storage to "help it along".
|
|
279
|
-
|
|
280
|
-
# Gotchas
|
|
281
|
-
|
|
282
|
-
- **The file PUT is raw HTTP, not the SDK.** `createUploadLink` and `ingestFile`
|
|
283
|
-
are SDK calls; the upload itself is a plain `fetch` PUT to the presigned URL.
|
|
284
|
-
Don't try to route the bytes through an `api.*` method.
|
|
285
|
-
- **`ingestFile` does NOT return the `batch_id`.** It returns only a `job_id`.
|
|
286
|
-
The worker allocates the `batch_id` when it reads the file — discover it by
|
|
287
|
-
diffing the DAC logs before vs. after (the snapshot in 006).
|
|
288
|
-
- **Wait for merge, not just for rows.** A log row with `rows_ingested > 0` but
|
|
289
|
-
`output_files: []` is mid-merge — gate on `output_files.length > 0` before
|
|
290
|
-
trusting the batch.
|
|
291
|
-
- **Search is a two-call, poll-until-ready pattern.** `createUserSearch` compiles
|
|
292
|
-
the query and returns `status: 'completed'`; the rows still take a moment to
|
|
293
|
-
materialize, so poll `search` until they appear.
|
|
294
|
-
|
|
295
|
-
# See also
|
|
296
|
-
|
|
297
|
-
- `data_activation_clients.md` §6.2 — `.ingestFile` reference + the bulk flow
|
|
298
|
-
- `datalakes.md` — `createUploadLink` (presigned upload) reference
|
|
299
|
-
- `_setup/subscription.md` — the bootstrap this walk starts from
|
|
300
|
-
- `_fixtures/subscription/` — the vendored Stripe customer CSV + Liquid
|
|
301
|
-
- `integration-tests/tests/subscription/run-dac-bulk.test.ts` — the green
|
|
302
|
-
test these calls are lifted from
|