@centrali-io/centrali-sdk 5.5.1 → 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
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
// =====================================================
|
|
2
|
+
// Records Manager (canonical query surface — CEN-1194)
|
|
3
|
+
// =====================================================
|
|
4
|
+
|
|
5
|
+
import type { Method } from 'axios';
|
|
6
|
+
import type { ApiResponse } from '../types/common';
|
|
7
|
+
import type { QueryRecordOptions } from '../types/records';
|
|
8
|
+
import type {
|
|
9
|
+
QueryDefinition,
|
|
10
|
+
QueryResult,
|
|
11
|
+
WhereExpression,
|
|
12
|
+
SortClause,
|
|
13
|
+
PageClause,
|
|
14
|
+
SelectClause,
|
|
15
|
+
} from '../../query-types';
|
|
16
|
+
import { getRecordApiPath } from '../internal/paths';
|
|
17
|
+
import { validateQueryDefinition } from '@centrali/query';
|
|
18
|
+
import { queryErrorsToCentraliError } from './query';
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* RecordsManager exposes the canonical query surface for records.
|
|
23
|
+
*
|
|
24
|
+
* All three methods compile to a single canonical `QueryDefinition` server-side
|
|
25
|
+
* and return the canonical `{ data, meta }` envelope (`QueryResult<T>`).
|
|
26
|
+
*
|
|
27
|
+
* - {@link RecordsManager.query | query} — full POST `/records/query`. Use for
|
|
28
|
+
* nested boolean trees, `select`, `text`, `include`.
|
|
29
|
+
* - {@link RecordsManager.list | list} — GET adapter for simple URL-param
|
|
30
|
+
* queries. Bookmarkable, cacheable, but cannot express nested `or`/`not`.
|
|
31
|
+
* - {@link RecordsManager.search | search} — sugar over `query({ text: ... })`.
|
|
32
|
+
*
|
|
33
|
+
* Access via `client.records`.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* // Canonical query — boolean tree, select, paging
|
|
38
|
+
* const open = await client.records.query<Order>('orders', {
|
|
39
|
+
* resource: 'orders',
|
|
40
|
+
* where: {
|
|
41
|
+
* and: [
|
|
42
|
+
* { 'data.status': { eq: 'open' } },
|
|
43
|
+
* { or: [
|
|
44
|
+
* { 'data.amount': { gte: 100 } },
|
|
45
|
+
* { 'data.priority': { eq: 'high' } }
|
|
46
|
+
* ]}
|
|
47
|
+
* ]
|
|
48
|
+
* },
|
|
49
|
+
* sort: [{ field: 'createdAt', direction: 'desc' }],
|
|
50
|
+
* page: { limit: 50 },
|
|
51
|
+
* select: { fields: ['id', 'data.status', 'data.amount'] }
|
|
52
|
+
* });
|
|
53
|
+
* console.log(open.data, open.meta.hasMore);
|
|
54
|
+
*
|
|
55
|
+
* // GET adapter — simple cases
|
|
56
|
+
* const recent = await client.records.list<Order>('orders', {
|
|
57
|
+
* 'data.status': 'paid',
|
|
58
|
+
* sort: '-createdAt',
|
|
59
|
+
* pageSize: 25,
|
|
60
|
+
* });
|
|
61
|
+
*
|
|
62
|
+
* // Text search — sugar over { text }
|
|
63
|
+
* const matches = await client.records.search<Order>('orders', 'urgent shipping');
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export class RecordsManager {
|
|
67
|
+
private requestFn: <T>(method: Method, path: string, data?: any, queryParams?: Record<string, any>) => Promise<ApiResponse<T>>;
|
|
68
|
+
private workspaceId: string;
|
|
69
|
+
|
|
70
|
+
constructor(
|
|
71
|
+
workspaceId: string,
|
|
72
|
+
requestFn: <T>(method: Method, path: string, data?: any, queryParams?: Record<string, any>) => Promise<ApiResponse<T>>
|
|
73
|
+
) {
|
|
74
|
+
this.workspaceId = workspaceId;
|
|
75
|
+
this.requestFn = requestFn;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Run a canonical query against `POST /records/query`.
|
|
80
|
+
*
|
|
81
|
+
* The body **is** a `QueryDefinition`. Returns the canonical `{ data, meta }`
|
|
82
|
+
* envelope. If `definition.resource` is omitted, `resource` is filled in
|
|
83
|
+
* from the first argument so the wire shape always matches the contract.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* const open = await client.records.query<Order>('orders', {
|
|
87
|
+
* resource: 'orders',
|
|
88
|
+
* where: { 'data.status': { eq: 'open' } },
|
|
89
|
+
* page: { limit: 100 }
|
|
90
|
+
* });
|
|
91
|
+
*/
|
|
92
|
+
public async query<T = any>(
|
|
93
|
+
resource: string,
|
|
94
|
+
definition: Omit<QueryDefinition, 'resource'> & { resource?: string }
|
|
95
|
+
): Promise<QueryResult<T>> {
|
|
96
|
+
const path = `data/workspace/${this.workspaceId}/api/v1/records/query`;
|
|
97
|
+
const body: QueryDefinition = { ...definition, resource: definition.resource ?? resource };
|
|
98
|
+
// Local pre-validation (CEN-1202). The SDK bundles the same
|
|
99
|
+
// validator the data service runs server-side, so a malformed
|
|
100
|
+
// body is rejected before the HTTP roundtrip with the same
|
|
101
|
+
// `CentraliError` shape the server would have returned.
|
|
102
|
+
const validation = validateQueryDefinition(body);
|
|
103
|
+
if (!validation.ok) {
|
|
104
|
+
throw queryErrorsToCentraliError(validation.errors);
|
|
105
|
+
}
|
|
106
|
+
const resp = await this.requestFn<T[]>('POST', path, body);
|
|
107
|
+
// POST /records/query returns canonical `{ data, meta }`. The SDK's
|
|
108
|
+
// `request()` normalizer leaves objects with a `data` key untouched, so
|
|
109
|
+
// the runtime shape already matches `QueryResult<T>` — we only need to
|
|
110
|
+
// re-type the response. `meta` is server-guaranteed for this route.
|
|
111
|
+
return resp as unknown as QueryResult<T>;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Authoring-time dry-run of a canonical query against `POST /records/query/test`.
|
|
116
|
+
*
|
|
117
|
+
* Same input shape as {@link RecordsManager.query | query}. Returns
|
|
118
|
+
* `422 unreadable_field` on fields the caller can't see (instead of the
|
|
119
|
+
* runtime privacy-preserving empty-result behavior). Use from query
|
|
120
|
+
* builders to surface precise errors to the author.
|
|
121
|
+
*/
|
|
122
|
+
public async test<T = any>(
|
|
123
|
+
resource: string,
|
|
124
|
+
definition: Omit<QueryDefinition, 'resource'> & { resource?: string }
|
|
125
|
+
): Promise<QueryResult<T>> {
|
|
126
|
+
const path = `data/workspace/${this.workspaceId}/api/v1/records/query/test`;
|
|
127
|
+
const body: QueryDefinition = { ...definition, resource: definition.resource ?? resource };
|
|
128
|
+
const validation = validateQueryDefinition(body);
|
|
129
|
+
if (!validation.ok) {
|
|
130
|
+
throw queryErrorsToCentraliError(validation.errors);
|
|
131
|
+
}
|
|
132
|
+
const resp = await this.requestFn<T[]>('POST', path, body);
|
|
133
|
+
return resp as unknown as QueryResult<T>;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* GET adapter for simple, URL-encodable queries (`?status=paid&sort=-createdAt`).
|
|
138
|
+
*
|
|
139
|
+
* Server-side this routes through the same canonical engine as
|
|
140
|
+
* {@link RecordsManager.query | query} (per CEN-1181 WS3) — the URL
|
|
141
|
+
* params compile into a `QueryDefinition` before execution. Use this when
|
|
142
|
+
* a flat AND of field conditions is enough; reach for `query()` when you
|
|
143
|
+
* need nested booleans, `select`, `text`, or `include`.
|
|
144
|
+
*
|
|
145
|
+
* Returns the records-list `ApiResponse<T[]>` envelope unchanged so
|
|
146
|
+
* callers that read `meta.page` / `meta.pageSize` keep working during
|
|
147
|
+
* the deprecation window.
|
|
148
|
+
*/
|
|
149
|
+
public list<T = any>(
|
|
150
|
+
resource: string,
|
|
151
|
+
urlOpts?: QueryRecordOptions
|
|
152
|
+
): Promise<ApiResponse<T[]>> {
|
|
153
|
+
const path = getRecordApiPath(this.workspaceId, resource);
|
|
154
|
+
return this.requestFn<T[]>('GET', path, null, urlOpts);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Full-text search sugar — equivalent to `query(resource, { text: ... })`.
|
|
159
|
+
*
|
|
160
|
+
* Routes through `RecordsSearchExecutor` (Meilisearch) when only `text` is
|
|
161
|
+
* provided, or through the hybrid Meili-first path when `where` is also
|
|
162
|
+
* set. Result envelope is identical across pure-filter, pure-text, and
|
|
163
|
+
* hybrid (`{ data, meta }`).
|
|
164
|
+
*/
|
|
165
|
+
public async search<T = any>(
|
|
166
|
+
resource: string,
|
|
167
|
+
text: string,
|
|
168
|
+
opts?: {
|
|
169
|
+
fields?: string[];
|
|
170
|
+
typoTolerance?: boolean;
|
|
171
|
+
where?: WhereExpression;
|
|
172
|
+
sort?: SortClause[];
|
|
173
|
+
page?: PageClause;
|
|
174
|
+
select?: SelectClause;
|
|
175
|
+
}
|
|
176
|
+
): Promise<QueryResult<T>> {
|
|
177
|
+
return this.query<T>(resource, {
|
|
178
|
+
resource,
|
|
179
|
+
text: { query: text, fields: opts?.fields, typoTolerance: opts?.typoTolerance },
|
|
180
|
+
where: opts?.where,
|
|
181
|
+
sort: opts?.sort,
|
|
182
|
+
page: opts?.page,
|
|
183
|
+
select: opts?.select,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
// =====================================================
|
|
2
|
+
// Smart Queries Manager
|
|
3
|
+
// =====================================================
|
|
4
|
+
|
|
5
|
+
import type { Method } from 'axios';
|
|
6
|
+
import type { ApiResponse } from '../types/common';
|
|
7
|
+
import type {
|
|
8
|
+
SmartQuery,
|
|
9
|
+
ListSmartQueryOptions,
|
|
10
|
+
ExecuteSmartQueryOptions,
|
|
11
|
+
ExecuteSavedQueryValues,
|
|
12
|
+
CreateSmartQueryInput,
|
|
13
|
+
UpdateSmartQueryInput,
|
|
14
|
+
TestSmartQueryInput,
|
|
15
|
+
} from '../types/smartQueries';
|
|
16
|
+
import {
|
|
17
|
+
getSmartQueriesApiPath,
|
|
18
|
+
getSmartQueriesStructureApiPath,
|
|
19
|
+
getSmartQueryByNameApiPath,
|
|
20
|
+
getSavedQueryCanonicalCollectionPath,
|
|
21
|
+
getSavedQueryCanonicalByIdPath,
|
|
22
|
+
getSavedQueryCanonicalExecutePath,
|
|
23
|
+
getSavedQueryCanonicalTestPath,
|
|
24
|
+
getSmartQueryTestApiPath,
|
|
25
|
+
} from '../internal/paths';
|
|
26
|
+
|
|
27
|
+
// =====================================================
|
|
28
|
+
// Smart Queries Manager
|
|
29
|
+
// =====================================================
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* SmartQueriesManager — reusable, parameterized saved queries. Access via
|
|
33
|
+
* `client.savedQueries` (canonical) or `client.smartQueries` (deprecated alias).
|
|
34
|
+
*
|
|
35
|
+
* Phase 4 of the query foundation (CEN-1198) shipped the canonical
|
|
36
|
+
* `/saved-queries/*` HTTP routes; this manager now routes through them. The
|
|
37
|
+
* data service dual-mounts the deprecated `/smart-queries/*` alias for the
|
|
38
|
+
* deprecation window. New code that doesn't need saved queries should still
|
|
39
|
+
* prefer `client.records.query()` for ad-hoc queries.
|
|
40
|
+
*
|
|
41
|
+
* Usage:
|
|
42
|
+
* ```ts
|
|
43
|
+
* // List saved queries for a structure
|
|
44
|
+
* const queries = await client.savedQueries.list('employee');
|
|
45
|
+
*
|
|
46
|
+
* // Execute a saved query by ID
|
|
47
|
+
* const results = await client.savedQueries.execute('employee', 'query-uuid');
|
|
48
|
+
*
|
|
49
|
+
* // Get a saved query by name
|
|
50
|
+
* const query = await client.savedQueries.getByName('employee', 'Active Employees');
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export class SmartQueriesManager {
|
|
54
|
+
private requestFn: <T>(method: Method, path: string, data?: any, queryParams?: Record<string, any>) => Promise<ApiResponse<T>>;
|
|
55
|
+
private workspaceId: string;
|
|
56
|
+
|
|
57
|
+
constructor(
|
|
58
|
+
workspaceId: string,
|
|
59
|
+
requestFn: <T>(method: Method, path: string, data?: any, queryParams?: Record<string, any>) => Promise<ApiResponse<T>>
|
|
60
|
+
) {
|
|
61
|
+
this.workspaceId = workspaceId;
|
|
62
|
+
this.requestFn = requestFn;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* List all smart queries in the workspace.
|
|
67
|
+
*
|
|
68
|
+
* @param options - Optional list parameters (pagination, search, etc.)
|
|
69
|
+
* @returns List of smart queries with pagination metadata
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```ts
|
|
73
|
+
* // List all smart queries
|
|
74
|
+
* const queries = await client.smartQueries.listAll();
|
|
75
|
+
*
|
|
76
|
+
* // With pagination
|
|
77
|
+
* const queries = await client.smartQueries.listAll({ page: 1, limit: 20 });
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
public listAll(options?: ListSmartQueryOptions): Promise<ApiResponse<SmartQuery[]>> {
|
|
81
|
+
const path = getSmartQueriesApiPath(this.workspaceId);
|
|
82
|
+
return this.requestFn<SmartQuery[]>('GET', path, null, options);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* List smart queries for a specific structure.
|
|
87
|
+
*
|
|
88
|
+
* @param structureSlug - The structure's record slug (e.g., "employee")
|
|
89
|
+
* @param options - Optional list parameters (pagination, search, etc.)
|
|
90
|
+
* @returns List of smart queries for the structure
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```ts
|
|
94
|
+
* // List queries for employee structure
|
|
95
|
+
* const queries = await client.smartQueries.list('employee');
|
|
96
|
+
*
|
|
97
|
+
* // With pagination and search
|
|
98
|
+
* const queries = await client.smartQueries.list('employee', {
|
|
99
|
+
* page: 1,
|
|
100
|
+
* limit: 10,
|
|
101
|
+
* search: 'active'
|
|
102
|
+
* });
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
public list(structureSlug: string, options?: ListSmartQueryOptions): Promise<ApiResponse<SmartQuery[]>> {
|
|
106
|
+
const path = getSmartQueriesStructureApiPath(this.workspaceId, structureSlug);
|
|
107
|
+
return this.requestFn<SmartQuery[]>('GET', path, null, options);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Get a smart query by ID.
|
|
112
|
+
*
|
|
113
|
+
* @param structureSlug - The structure's record slug
|
|
114
|
+
* @param queryId - The smart query UUID
|
|
115
|
+
* @returns The smart query details
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```ts
|
|
119
|
+
* const query = await client.smartQueries.get('employee', 'query-uuid');
|
|
120
|
+
* console.log('Query name:', query.data.name);
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
public get(structureSlug: string, queryId: string): Promise<ApiResponse<SmartQuery>> {
|
|
124
|
+
const path = getSmartQueriesStructureApiPath(this.workspaceId, structureSlug, queryId);
|
|
125
|
+
return this.requestFn<SmartQuery>('GET', path);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Get a smart query by name.
|
|
130
|
+
*
|
|
131
|
+
* @param structureSlug - The structure's record slug
|
|
132
|
+
* @param name - The smart query name
|
|
133
|
+
* @returns The smart query details
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* ```ts
|
|
137
|
+
* const query = await client.smartQueries.getByName('employee', 'Active Employees');
|
|
138
|
+
* console.log('Query ID:', query.data.id);
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
public getByName(structureSlug: string, name: string): Promise<ApiResponse<SmartQuery>> {
|
|
142
|
+
const path = getSmartQueryByNameApiPath(this.workspaceId, structureSlug, name);
|
|
143
|
+
return this.requestFn<SmartQuery>('GET', path);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Execute a saved query and return results.
|
|
148
|
+
*
|
|
149
|
+
* Pagination (`limit` / `offset`) is defined inside the persisted
|
|
150
|
+
* `queryDefinition`, not at execution time.
|
|
151
|
+
*
|
|
152
|
+
* Variables in the query body use canonical `${var}` placeholders. Phase 4
|
|
153
|
+
* saved queries declare types for each placeholder via `variables` (see
|
|
154
|
+
* `get()`); the server validates each value against the declared type by
|
|
155
|
+
* JS `typeof` (no coercion — `"123"` is rejected for `number`). Legacy
|
|
156
|
+
* untyped rows accept the same `variables` map but stringify each value
|
|
157
|
+
* before substitution.
|
|
158
|
+
*
|
|
159
|
+
* @param structureSlug - The collection's record slug
|
|
160
|
+
* @param queryId - The saved query UUID
|
|
161
|
+
* @param options - Optional execution options including variables
|
|
162
|
+
* @returns Query results
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```ts
|
|
166
|
+
* // Simple execution without variables
|
|
167
|
+
* const results = await client.savedQueries.execute('employee', 'query-uuid');
|
|
168
|
+
* console.log('Found:', results.data.length, 'records');
|
|
169
|
+
*
|
|
170
|
+
* // Execution with variables
|
|
171
|
+
* // Query definition: { where: { status: { eq: "${statusFilter}" } } }
|
|
172
|
+
* const filtered = await client.savedQueries.execute('orders', 'query-id', {
|
|
173
|
+
* variables: { statusFilter: 'active' }
|
|
174
|
+
* });
|
|
175
|
+
*
|
|
176
|
+
* // Accessing joined data (nested under _joined)
|
|
177
|
+
* // Query with join: { join: { foreignSlug: "products", ... } }
|
|
178
|
+
* const items = await client.savedQueries.execute('order-items', 'items-with-products');
|
|
179
|
+
* items.data.forEach(item => {
|
|
180
|
+
* console.log('Item:', item.name);
|
|
181
|
+
* console.log('Product:', item._joined?.products?.title);
|
|
182
|
+
* });
|
|
183
|
+
* ```
|
|
184
|
+
*/
|
|
185
|
+
public execute<T = any>(
|
|
186
|
+
_structureSlug: string,
|
|
187
|
+
queryId: string,
|
|
188
|
+
options?: ExecuteSmartQueryOptions
|
|
189
|
+
): Promise<ApiResponse<T[]>> {
|
|
190
|
+
// CEN-1245 — route through the canonical `/saved-queries/:id/execute`
|
|
191
|
+
// endpoint, which returns the contract §9 envelope (`{ data, meta }`).
|
|
192
|
+
// The slug segment is unused by the canonical path; the data service
|
|
193
|
+
// resolves the resource from the saved-query row. The previous legacy
|
|
194
|
+
// slug path returned `{ result: { rows, rowCount } }` and the SDK had
|
|
195
|
+
// to unwrap through `request()`'s `{ result } -> { data: result }`
|
|
196
|
+
// handler — we now stay on the canonical envelope end-to-end.
|
|
197
|
+
const path = getSavedQueryCanonicalExecutePath(this.workspaceId, queryId);
|
|
198
|
+
// Use POST to support variables, with empty body if no options
|
|
199
|
+
const body = options?.variables ? { variables: options.variables } : undefined;
|
|
200
|
+
return this.requestFn<T[]>('POST', path, body);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Type-safe variant of {@link execute}. The caller passes a generic
|
|
205
|
+
* `TVars` type that describes the saved query's parameter shape, and the
|
|
206
|
+
* compiler enforces that every required key is provided with the right
|
|
207
|
+
* scalar type.
|
|
208
|
+
*
|
|
209
|
+
* Use this when the saved query declares typed `variables` (Phase 4) and
|
|
210
|
+
* the caller has — or can derive — a TypeScript type for the parameter
|
|
211
|
+
* map. For untyped callers, prefer {@link execute}.
|
|
212
|
+
*
|
|
213
|
+
* @example
|
|
214
|
+
* ```ts
|
|
215
|
+
* type MonthlyRevenueVars = { month: Date; region: string };
|
|
216
|
+
*
|
|
217
|
+
* const rows = await client.savedQueries.executeTyped<MonthlyRevenueVars>(
|
|
218
|
+
* 'orders',
|
|
219
|
+
* 'monthly-revenue',
|
|
220
|
+
* { month: new Date('2026-04-01'), region: 'us-east' },
|
|
221
|
+
* );
|
|
222
|
+
*
|
|
223
|
+
* // Compile error — missing required key:
|
|
224
|
+
* // await client.savedQueries.executeTyped<MonthlyRevenueVars>('orders', 'monthly-revenue', { region: 'us-east' });
|
|
225
|
+
* ```
|
|
226
|
+
*/
|
|
227
|
+
public executeTyped<TVars extends object, TRow = any>(
|
|
228
|
+
structureSlug: string,
|
|
229
|
+
queryId: string,
|
|
230
|
+
values: TVars,
|
|
231
|
+
): Promise<ApiResponse<TRow[]>> {
|
|
232
|
+
// Cast to the runtime-values type at the boundary: the network call
|
|
233
|
+
// serializes whatever shape the caller passes (axios JSON-encodes
|
|
234
|
+
// Date as ISO-8601), and the server enforces the typed contract via
|
|
235
|
+
// declarations on the saved-query row. The generic exists purely for
|
|
236
|
+
// call-site type safety, so we deliberately don't constrain `TVars`
|
|
237
|
+
// to `ExecuteSavedQueryValues` — that constraint requires a string
|
|
238
|
+
// index signature that ordinary TS interfaces don't have.
|
|
239
|
+
return this.execute<TRow>(structureSlug, queryId, {
|
|
240
|
+
variables: values as unknown as ExecuteSavedQueryValues,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Create a new saved query.
|
|
246
|
+
*
|
|
247
|
+
* Routing: when `input.query` (canonical `QueryDefinition`) is provided
|
|
248
|
+
* the SDK calls the canonical `POST /saved-queries` endpoint, which
|
|
249
|
+
* accepts typed `variables` declarations and `${var}` placeholders. When
|
|
250
|
+
* only legacy `input.queryDefinition` is provided the SDK falls back to
|
|
251
|
+
* the slug-based legacy endpoint.
|
|
252
|
+
*
|
|
253
|
+
* @param structureSlug - The collection's record slug. Required for the
|
|
254
|
+
* legacy path; on the canonical path the resource is read from
|
|
255
|
+
* `query.resource` and this slug is currently ignored on the wire.
|
|
256
|
+
* @param input - Canonical (`query`) or legacy (`queryDefinition`) body.
|
|
257
|
+
* @returns The created saved query.
|
|
258
|
+
*
|
|
259
|
+
* @example
|
|
260
|
+
* ```ts
|
|
261
|
+
* // Canonical (Phase 4) — typed parameters + canonical operators
|
|
262
|
+
* const query = await client.savedQueries.create('orders', {
|
|
263
|
+
* name: 'Active Orders',
|
|
264
|
+
* description: 'All orders with active status',
|
|
265
|
+
* query: {
|
|
266
|
+
* resource: 'orders',
|
|
267
|
+
* where: { 'data.status': { eq: '${statusFilter}' } },
|
|
268
|
+
* sort: [{ field: 'createdAt', direction: 'desc' }],
|
|
269
|
+
* page: { limit: 100 },
|
|
270
|
+
* },
|
|
271
|
+
* variables: {
|
|
272
|
+
* statusFilter: { type: 'string', required: true },
|
|
273
|
+
* },
|
|
274
|
+
* });
|
|
275
|
+
* ```
|
|
276
|
+
*/
|
|
277
|
+
public create(structureSlug: string, input: CreateSmartQueryInput): Promise<ApiResponse<SmartQuery>> {
|
|
278
|
+
if (input.query) {
|
|
279
|
+
const path = getSavedQueryCanonicalCollectionPath(this.workspaceId);
|
|
280
|
+
const body = {
|
|
281
|
+
name: input.name,
|
|
282
|
+
description: input.description,
|
|
283
|
+
query: input.query,
|
|
284
|
+
variables: input.variables,
|
|
285
|
+
};
|
|
286
|
+
return this.requestFn<SmartQuery>('POST', path, body);
|
|
287
|
+
}
|
|
288
|
+
const path = getSmartQueriesStructureApiPath(this.workspaceId, structureSlug);
|
|
289
|
+
return this.requestFn<SmartQuery>('POST', path, input);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Update an existing saved query.
|
|
294
|
+
*
|
|
295
|
+
* Routing: when `input.query` is provided the SDK calls canonical
|
|
296
|
+
* `PUT /saved-queries/:id`; otherwise it falls back to the legacy
|
|
297
|
+
* slug-based PUT.
|
|
298
|
+
*
|
|
299
|
+
* @example
|
|
300
|
+
* ```ts
|
|
301
|
+
* const updated = await client.savedQueries.update('orders', 'query-uuid', {
|
|
302
|
+
* name: 'Active Orders v2',
|
|
303
|
+
* query: {
|
|
304
|
+
* resource: 'orders',
|
|
305
|
+
* where: { 'data.status': { in: ['active', 'processing'] } },
|
|
306
|
+
* page: { limit: 200 },
|
|
307
|
+
* },
|
|
308
|
+
* });
|
|
309
|
+
* ```
|
|
310
|
+
*/
|
|
311
|
+
public update(structureSlug: string, queryId: string, input: UpdateSmartQueryInput): Promise<ApiResponse<SmartQuery>> {
|
|
312
|
+
if (input.query || input.variables !== undefined) {
|
|
313
|
+
const path = getSavedQueryCanonicalByIdPath(this.workspaceId, queryId);
|
|
314
|
+
const body: Record<string, unknown> = {};
|
|
315
|
+
if (input.name !== undefined) body.name = input.name;
|
|
316
|
+
if (input.description !== undefined) body.description = input.description;
|
|
317
|
+
if (input.query !== undefined) body.query = input.query;
|
|
318
|
+
if (input.variables !== undefined) body.variables = input.variables;
|
|
319
|
+
return this.requestFn<SmartQuery>('PUT', path, body);
|
|
320
|
+
}
|
|
321
|
+
const path = getSmartQueriesStructureApiPath(this.workspaceId, structureSlug, queryId);
|
|
322
|
+
return this.requestFn<SmartQuery>('PUT', path, input);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Delete a smart query.
|
|
327
|
+
*
|
|
328
|
+
* @param structureSlug - The structure's record slug
|
|
329
|
+
* @param queryId - The smart query UUID
|
|
330
|
+
*
|
|
331
|
+
* @example
|
|
332
|
+
* ```ts
|
|
333
|
+
* await client.smartQueries.delete('orders', 'query-uuid');
|
|
334
|
+
* ```
|
|
335
|
+
*/
|
|
336
|
+
public delete(structureSlug: string, queryId: string): Promise<ApiResponse<void>> {
|
|
337
|
+
const path = getSmartQueriesStructureApiPath(this.workspaceId, structureSlug, queryId);
|
|
338
|
+
return this.requestFn<void>('DELETE', path);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Test-execute a query definition without saving it. Useful for
|
|
343
|
+
* validating syntax, previewing results, and dry-running a draft's
|
|
344
|
+
* typed `variableDeclarations` against runtime `variables`.
|
|
345
|
+
*
|
|
346
|
+
* Routing: when `input.query` (canonical) is provided the SDK calls
|
|
347
|
+
* `POST /saved-queries/test`; otherwise it falls back to the legacy
|
|
348
|
+
* slug-based test endpoint.
|
|
349
|
+
*
|
|
350
|
+
* @example
|
|
351
|
+
* ```ts
|
|
352
|
+
* // Canonical (Phase 4) — typed variable declarations dry-run
|
|
353
|
+
* const result = await client.savedQueries.test('orders', {
|
|
354
|
+
* query: {
|
|
355
|
+
* resource: 'orders',
|
|
356
|
+
* where: { 'data.amount': { gte: '${minTotal}' } },
|
|
357
|
+
* page: { limit: 5 },
|
|
358
|
+
* },
|
|
359
|
+
* variableDeclarations: {
|
|
360
|
+
* minTotal: { type: 'number', required: true },
|
|
361
|
+
* },
|
|
362
|
+
* variables: { minTotal: 100 },
|
|
363
|
+
* });
|
|
364
|
+
* ```
|
|
365
|
+
*/
|
|
366
|
+
public test<T = any>(structureSlug: string, input: TestSmartQueryInput): Promise<ApiResponse<T[]>> {
|
|
367
|
+
if (input.query) {
|
|
368
|
+
const path = getSavedQueryCanonicalTestPath(this.workspaceId);
|
|
369
|
+
return this.requestFn<T[]>('POST', path, input);
|
|
370
|
+
}
|
|
371
|
+
const path = getSmartQueryTestApiPath(this.workspaceId, structureSlug);
|
|
372
|
+
return this.requestFn<T[]>('POST', path, input);
|
|
373
|
+
}
|
|
374
|
+
}
|