@centrali-io/centrali-sdk 5.5.0 → 6.0.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/README.md +164 -14
- package/dist/index.d.ts +1807 -878
- package/dist/index.js +9153 -4076
- package/index.ts +61 -7152
- package/package.json +10 -3
- package/query-types.ts +83 -2
- package/scripts/smoke-types.ts +145 -5
- package/src/client.ts +1507 -0
- package/src/internal/auth.ts +35 -0
- package/src/internal/deprecation.ts +11 -0
- package/src/internal/error.ts +90 -0
- package/src/internal/paths.ts +456 -0
- package/src/internal/queryGuard.ts +21 -0
- package/src/managers/allowedDomains.ts +90 -0
- package/src/managers/anomalyInsights.ts +215 -0
- package/src/managers/auditLog.ts +105 -0
- package/src/managers/collections.ts +197 -0
- package/src/managers/files.ts +182 -0
- package/src/managers/functionRuns.ts +229 -0
- package/src/managers/functions.ts +171 -0
- package/src/managers/orchestrationRuns.ts +122 -0
- package/src/managers/orchestrations.ts +297 -0
- package/src/managers/query.ts +199 -0
- package/src/managers/records.ts +186 -0
- package/src/managers/smartQueries.ts +374 -0
- package/src/managers/structures.ts +205 -0
- package/src/managers/triggers.ts +349 -0
- package/src/managers/validation.ts +303 -0
- package/src/managers/webhookSubscriptions.ts +206 -0
- package/src/realtime/manager.ts +292 -0
- package/src/types/allowedDomains.ts +29 -0
- package/src/types/auth.ts +83 -0
- package/src/types/common.ts +57 -0
- package/src/types/compute.ts +145 -0
- package/src/types/insights.ts +113 -0
- package/src/types/orchestrations.ts +460 -0
- package/src/types/realtime.ts +403 -0
- package/src/types/records.ts +261 -0
- package/src/types/search.ts +44 -0
- package/src/types/smartQueries.ts +303 -0
- package/src/types/structures.ts +203 -0
- package/src/types/triggers.ts +122 -0
- package/src/types/validation.ts +167 -0
- package/src/types/webhooks.ts +114 -0
- package/src/urls.ts +33 -0
- package/dist/query-types.d.ts +0 -187
- package/dist/query-types.js +0 -137
- package/dist/scripts/smoke-types.d.ts +0 -12
- package/dist/scripts/smoke-types.js +0 -102
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@centrali-io/centrali-sdk",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.0",
|
|
4
4
|
"description": "Centrali Node SDK",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"scripts": {
|
|
13
13
|
"sync:query-types": "node scripts/sync-query-types.mjs",
|
|
14
14
|
"prebuild": "npm run sync:query-types",
|
|
15
|
-
"build": "
|
|
15
|
+
"build": "tsup",
|
|
16
|
+
"test": "jest",
|
|
16
17
|
"prepublishOnly": "npm run build"
|
|
17
18
|
},
|
|
18
19
|
"keywords": [],
|
|
@@ -24,10 +25,16 @@
|
|
|
24
25
|
"qs": "^6.14.2"
|
|
25
26
|
},
|
|
26
27
|
"devDependencies": {
|
|
28
|
+
"@centrali/query": "file:../../backend/shared/query",
|
|
27
29
|
"@types/eventsource": "^1.1.15",
|
|
30
|
+
"@types/jest": "^29.5.12",
|
|
28
31
|
"@types/q": "^1.5.8",
|
|
29
32
|
"@types/qs": "^6.14.0",
|
|
30
|
-
"
|
|
33
|
+
"jest": "^29.7.0",
|
|
34
|
+
"ts-jest": "^29.1.2",
|
|
35
|
+
"tsup": "^8.3.5",
|
|
36
|
+
"typescript": "5.9.3",
|
|
37
|
+
"zod": "^3.24.1"
|
|
31
38
|
},
|
|
32
39
|
"publishConfig": {
|
|
33
40
|
"registry": "https://registry.npmjs.org/",
|
package/query-types.ts
CHANGED
|
@@ -34,6 +34,7 @@ export type QueryDefinition = {
|
|
|
34
34
|
page?: PageClause;
|
|
35
35
|
select?: SelectClause;
|
|
36
36
|
include?: IncludeClause[];
|
|
37
|
+
joins?: JoinClause[];
|
|
37
38
|
};
|
|
38
39
|
|
|
39
40
|
export type SavedQueryDefinition = {
|
|
@@ -320,12 +321,81 @@ export type SelectClause = {
|
|
|
320
321
|
};
|
|
321
322
|
|
|
322
323
|
/**
|
|
323
|
-
*
|
|
324
|
+
* Recursive direct-relationship expansion. The executor walks each clause
|
|
325
|
+
* against reference-typed properties on the parent structure and substitutes
|
|
326
|
+
* the related row(s) inline under the relation name. Nested `include`
|
|
327
|
+
* children traverse the next hop on the just-fetched rows. Cycle detection
|
|
328
|
+
* and depth limits live in the executor (CEN-1192).
|
|
329
|
+
*
|
|
330
|
+
* `where` and `select` (CEN-1219) operate on the **target** structure's
|
|
331
|
+
* namespace — fields are resolved against the included relation's own
|
|
332
|
+
* properties (e.g. `data.active` on the related `customer`, not the parent's
|
|
333
|
+
* `data.customer.data.active`). The executor narrows the batched fetch with
|
|
334
|
+
* `where` and trims the attached row to `select.fields` post-fetch. Nested
|
|
335
|
+
* `include` children inherit nothing from this clause's `select` — they are
|
|
336
|
+
* always fetched in full and can carry their own `select`.
|
|
324
337
|
*/
|
|
325
338
|
export type IncludeClause = {
|
|
326
339
|
relation: string;
|
|
340
|
+
where?: WhereExpression;
|
|
341
|
+
select?: SelectClause;
|
|
342
|
+
include?: IncludeClause[];
|
|
327
343
|
};
|
|
328
344
|
|
|
345
|
+
// ---------------------------------------------------------------------------
|
|
346
|
+
// JoinClause (contract §7.5 — added 6.0, CEN-1058)
|
|
347
|
+
//
|
|
348
|
+
// Multi-join saved queries. Each entry compiles to one Knex `.join` /
|
|
349
|
+
// `.leftJoin` / `.rightJoin` / `.fullOuterJoin` against a per-side
|
|
350
|
+
// tenancy-filtered subquery (`SELECT * FROM records WHERE
|
|
351
|
+
// workspaceSlug=? AND recordSlug=? AND ...`). Equi-join predicates
|
|
352
|
+
// remain in the ON clause; tenancy lives in the FROM subquery so
|
|
353
|
+
// outer-join row preservation works correctly across all four join
|
|
354
|
+
// types — see PR 1b commit `dc4fb4fcb` for the SQL-correctness analysis.
|
|
355
|
+
//
|
|
356
|
+
// Identifier slots (`foreignSlug`, `localField`, `foreignField`,
|
|
357
|
+
// `alias`, `joinType`) are literal-only — `${var}` placeholders are
|
|
358
|
+
// rejected by the validator and skipped by every substitution layer.
|
|
359
|
+
// `joins.length` is capped at JOINS_MAX_LENGTH; `text + joins` and
|
|
360
|
+
// `include + joins` are both rejected with `unsupported_combination`.
|
|
361
|
+
// ---------------------------------------------------------------------------
|
|
362
|
+
|
|
363
|
+
export type JoinType = 'inner' | 'left' | 'right' | 'full';
|
|
364
|
+
|
|
365
|
+
export type JoinClause = {
|
|
366
|
+
/** Resource (collection slug) of the joined records table. */
|
|
367
|
+
foreignSlug: string;
|
|
368
|
+
/**
|
|
369
|
+
* Field on the primary or any prior join's alias. Bare names refer to the
|
|
370
|
+
* primary structure; dotted names (`<alias>.<field>`) reference a prior
|
|
371
|
+
* join's logical alias.
|
|
372
|
+
*/
|
|
373
|
+
localField: string;
|
|
374
|
+
/** Field on the joined table. */
|
|
375
|
+
foreignField: string;
|
|
376
|
+
/** SQL join type. Required — no implicit default. */
|
|
377
|
+
joinType: JoinType;
|
|
378
|
+
/**
|
|
379
|
+
* Optional projection on the joined row. Defaults to all readable fields
|
|
380
|
+
* (subject to field-level auth).
|
|
381
|
+
*/
|
|
382
|
+
select?: string[];
|
|
383
|
+
/**
|
|
384
|
+
* Logical alias used to namespace the joined row in the response under
|
|
385
|
+
* `_joined[alias]`, and for `localField` references in subsequent joins.
|
|
386
|
+
* Defaults to `foreignSlug`. Required when joining the same `foreignSlug`
|
|
387
|
+
* more than once in the same query.
|
|
388
|
+
*/
|
|
389
|
+
alias?: string;
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Maximum number of `joins[]` entries allowed in a single QueryDefinition.
|
|
394
|
+
* Cap is conservative until we have telemetry on real-world chain lengths;
|
|
395
|
+
* raise if customers hit it for legitimate reporting use cases.
|
|
396
|
+
*/
|
|
397
|
+
export const JOINS_MAX_LENGTH = 4;
|
|
398
|
+
|
|
329
399
|
// ---------------------------------------------------------------------------
|
|
330
400
|
// QueryVariableDefinition (contract §8) — Phase 4 (saved queries)
|
|
331
401
|
// ---------------------------------------------------------------------------
|
|
@@ -378,7 +448,18 @@ export type QueryErrorCode =
|
|
|
378
448
|
| 'unsupported_legacy_operator'
|
|
379
449
|
| 'unreadable_field'
|
|
380
450
|
| 'invalid_query'
|
|
381
|
-
| 'legacy_write_unsupported'
|
|
451
|
+
| 'legacy_write_unsupported'
|
|
452
|
+
// Phase 4 — saved-query typed parameters (contract §8). Authoring and
|
|
453
|
+
// execute-time validation produce these codes; routes thread them through
|
|
454
|
+
// `queryErrorsToHttp` so the response shape matches the rest of the engine.
|
|
455
|
+
| 'unknown_variable'
|
|
456
|
+
| 'variable_type_mismatch'
|
|
457
|
+
| 'missing_required_variable'
|
|
458
|
+
| 'extra_variable'
|
|
459
|
+
// 6.0 — multi-join (CEN-1058).
|
|
460
|
+
| 'joins_length_exceeded'
|
|
461
|
+
| 'unsupported_combination'
|
|
462
|
+
| 'duplicate_join_alias';
|
|
382
463
|
|
|
383
464
|
export type QueryError = {
|
|
384
465
|
code: QueryErrorCode;
|
package/scripts/smoke-types.ts
CHANGED
|
@@ -20,6 +20,9 @@ import {
|
|
|
20
20
|
type SelectClause,
|
|
21
21
|
type FieldCondition,
|
|
22
22
|
type CanonicalOperator,
|
|
23
|
+
type ExecuteSavedQueryValues,
|
|
24
|
+
type QueryVariableDefinition,
|
|
25
|
+
type SmartQuery,
|
|
23
26
|
CANONICAL_OPERATORS,
|
|
24
27
|
RECORDS_PAGE_DEFAULT_LIMIT,
|
|
25
28
|
RECORDS_PAGE_MAX_LIMIT,
|
|
@@ -103,11 +106,73 @@ async function exerciseSdk() {
|
|
|
103
106
|
});
|
|
104
107
|
console.log(r3.data, r3.meta.mode);
|
|
105
108
|
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
|
|
109
|
+
// ---- Saved-query typed parameters (CEN-1208 / Phase 4 WS4) ----
|
|
110
|
+
//
|
|
111
|
+
// `get()` returns the typed `variables` declarations alongside the
|
|
112
|
+
// query body so callers can construct invocations programmatically.
|
|
113
|
+
const savedQuery: SmartQuery = (await client.savedQueries.get('orders', 'qry-uuid')).data;
|
|
114
|
+
const decls: Record<string, QueryVariableDefinition> | null | undefined = savedQuery.variables;
|
|
115
|
+
console.log(decls);
|
|
116
|
+
|
|
117
|
+
// Untyped execute — accepts ScalarValue | ScalarValue[] | Date | Date[].
|
|
118
|
+
await client.savedQueries.execute('orders', 'qry-uuid', {
|
|
119
|
+
variables: {
|
|
120
|
+
since: new Date('2026-01-01'),
|
|
121
|
+
statuses: ['open', 'paid'],
|
|
122
|
+
limit: 25,
|
|
123
|
+
// array<datetime> binding — matches the shared { array: 'datetime' } variable type.
|
|
124
|
+
window: [new Date('2026-01-01'), new Date('2026-04-01')],
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Typed execute — generic enforces the parameter shape at the call site.
|
|
129
|
+
// Plain TS interfaces (no string index signature) are accepted — the
|
|
130
|
+
// SDK does NOT require `extends ExecuteSavedQueryValues`.
|
|
131
|
+
interface MonthlyRevenueVars {
|
|
132
|
+
month: Date;
|
|
133
|
+
region: string;
|
|
134
|
+
}
|
|
135
|
+
await client.savedQueries.executeTyped<MonthlyRevenueVars>('orders', 'monthly-revenue', {
|
|
136
|
+
month: new Date('2026-04-01'),
|
|
137
|
+
region: 'us-east',
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Canonical authoring — `query: QueryDefinition` + typed `variables`.
|
|
141
|
+
await client.savedQueries.create('orders', {
|
|
142
|
+
name: 'Active Orders',
|
|
143
|
+
query: {
|
|
144
|
+
resource: 'orders',
|
|
145
|
+
where: { 'data.status': { eq: '${statusFilter}' } },
|
|
146
|
+
page: { limit: 100 },
|
|
147
|
+
},
|
|
148
|
+
variables: { statusFilter: { type: 'string', required: true } },
|
|
149
|
+
});
|
|
150
|
+
await client.savedQueries.update('orders', 'qry-uuid', {
|
|
151
|
+
query: {
|
|
152
|
+
resource: 'orders',
|
|
153
|
+
where: { 'data.amount': { gte: '${minTotal}' } },
|
|
154
|
+
page: { limit: 50 },
|
|
155
|
+
},
|
|
156
|
+
variables: { minTotal: { type: 'number', required: true } },
|
|
157
|
+
});
|
|
158
|
+
await client.savedQueries.test('orders', {
|
|
159
|
+
query: {
|
|
160
|
+
resource: 'orders',
|
|
161
|
+
where: { 'data.amount': { gte: '${minTotal}' } },
|
|
162
|
+
page: { limit: 5 },
|
|
163
|
+
},
|
|
164
|
+
variableDeclarations: { minTotal: { type: 'number', required: true } },
|
|
165
|
+
variables: { minTotal: 100 },
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// The lines below MUST fail to compile (uncomment to verify):
|
|
169
|
+
// await client.savedQueries.executeTyped<MonthlyRevenueVars>('orders', 'monthly-revenue', {
|
|
170
|
+
// month: 'not-a-date', // wrong type
|
|
171
|
+
// region: 'us-east',
|
|
172
|
+
// });
|
|
173
|
+
// await client.savedQueries.executeTyped<MonthlyRevenueVars>('orders', 'monthly-revenue', {
|
|
174
|
+
// region: 'us-east', // missing required `month`
|
|
175
|
+
// });
|
|
111
176
|
|
|
112
177
|
// Top-level canonical overload
|
|
113
178
|
const r4: QueryResult<Order> = await client.queryRecords<Order>('orders', {
|
|
@@ -123,6 +188,81 @@ async function exerciseSdk() {
|
|
|
123
188
|
sort: '-createdAt',
|
|
124
189
|
});
|
|
125
190
|
console.log(r5.data);
|
|
191
|
+
|
|
192
|
+
// ---- Function-runs canonical surface (CEN-1216, accessor renamed CEN-1227) ----
|
|
193
|
+
interface FRow { id: string; functionId: string; status: string; startedAt: string }
|
|
194
|
+
|
|
195
|
+
// Canonical accessor: client.functionRuns
|
|
196
|
+
// Caller can omit `resource` — manager forces 'function-runs'
|
|
197
|
+
const r6: QueryResult<FRow> = await client.functionRuns.query<FRow>({
|
|
198
|
+
where: {
|
|
199
|
+
and: [
|
|
200
|
+
{ functionId: { eq: 'fn-123' } },
|
|
201
|
+
{ status: { eq: 'failure' } },
|
|
202
|
+
],
|
|
203
|
+
},
|
|
204
|
+
sort: [{ field: 'startedAt', direction: 'desc' }],
|
|
205
|
+
page: { limit: 50 },
|
|
206
|
+
});
|
|
207
|
+
console.log(r6.data, r6.meta);
|
|
208
|
+
|
|
209
|
+
// Authoring dry-run
|
|
210
|
+
const planResp = await client.functionRuns.test({
|
|
211
|
+
where: { status: { eq: 'completed' } },
|
|
212
|
+
page: { limit: 10 },
|
|
213
|
+
});
|
|
214
|
+
console.log(planResp.plan.executor, planResp.plan.mode);
|
|
215
|
+
|
|
216
|
+
// Deprecated alias still type-checks (emits runtime warning)
|
|
217
|
+
const r6b: QueryResult<FRow> = await client.runs.query<FRow>({
|
|
218
|
+
where: { status: { eq: 'failure' } },
|
|
219
|
+
page: { limit: 1 },
|
|
220
|
+
});
|
|
221
|
+
console.log(r6b.data.length);
|
|
222
|
+
|
|
223
|
+
// ---- Files canonical surface (CEN-1218 / Phase 3) ----
|
|
224
|
+
interface FilesRow { id: string; name?: string; contentType?: string; size?: number; createdAt?: string }
|
|
225
|
+
|
|
226
|
+
// Caller can omit `resource` — manager forces 'files'.
|
|
227
|
+
const r7: QueryResult<FilesRow> = await client.files.query<FilesRow>({
|
|
228
|
+
where: {
|
|
229
|
+
and: [
|
|
230
|
+
{ fileType: { eq: 'image' } },
|
|
231
|
+
{ tags: { hasAny: ['draft', 'review'] } },
|
|
232
|
+
],
|
|
233
|
+
},
|
|
234
|
+
sort: [{ field: 'createdAt', direction: 'desc' }],
|
|
235
|
+
page: { limit: 25 },
|
|
236
|
+
select: { fields: ['name', 'contentType', 'size', 'createdAt'] },
|
|
237
|
+
});
|
|
238
|
+
console.log(r7.data, r7.meta);
|
|
239
|
+
|
|
240
|
+
// Default row shape (no generic) returns the canonical FileMetadata projection.
|
|
241
|
+
const r7b = await client.files.query({
|
|
242
|
+
where: { folderId: { eq: 'folder-uuid' } },
|
|
243
|
+
});
|
|
244
|
+
console.log(r7b.data.map((f) => f.name));
|
|
245
|
+
|
|
246
|
+
// renderId on the canonical row pairs with getFileRenderUrl /
|
|
247
|
+
// getFileDownloadUrl — so callers who discover files via query can
|
|
248
|
+
// build URLs without a separate uploadFile() round-trip.
|
|
249
|
+
const r7c = await client.files.query({
|
|
250
|
+
where: { fileType: { eq: 'image' } },
|
|
251
|
+
select: { fields: ['renderId', 'name', 'contentType'] },
|
|
252
|
+
});
|
|
253
|
+
for (const f of r7c.data) {
|
|
254
|
+
if (f.renderId) {
|
|
255
|
+
const url = client.getFileRenderUrl(f.renderId, { width: 200 });
|
|
256
|
+
console.log(f.name, url);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Authoring dry-run
|
|
261
|
+
const filesPlan = await client.files.test({
|
|
262
|
+
where: { isPublic: { eq: true } },
|
|
263
|
+
page: { limit: 10 },
|
|
264
|
+
});
|
|
265
|
+
console.log(filesPlan.plan.executor, filesPlan.plan.resource);
|
|
126
266
|
}
|
|
127
267
|
|
|
128
268
|
void exerciseSdk;
|