@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.
Files changed (49) hide show
  1. package/README.md +164 -14
  2. package/dist/index.d.ts +1807 -878
  3. package/dist/index.js +9153 -4076
  4. package/index.ts +61 -7152
  5. package/package.json +10 -3
  6. package/query-types.ts +83 -2
  7. package/scripts/smoke-types.ts +145 -5
  8. package/src/client.ts +1507 -0
  9. package/src/internal/auth.ts +35 -0
  10. package/src/internal/deprecation.ts +11 -0
  11. package/src/internal/error.ts +90 -0
  12. package/src/internal/paths.ts +456 -0
  13. package/src/internal/queryGuard.ts +21 -0
  14. package/src/managers/allowedDomains.ts +90 -0
  15. package/src/managers/anomalyInsights.ts +215 -0
  16. package/src/managers/auditLog.ts +105 -0
  17. package/src/managers/collections.ts +197 -0
  18. package/src/managers/files.ts +182 -0
  19. package/src/managers/functionRuns.ts +229 -0
  20. package/src/managers/functions.ts +171 -0
  21. package/src/managers/orchestrationRuns.ts +122 -0
  22. package/src/managers/orchestrations.ts +297 -0
  23. package/src/managers/query.ts +199 -0
  24. package/src/managers/records.ts +186 -0
  25. package/src/managers/smartQueries.ts +374 -0
  26. package/src/managers/structures.ts +205 -0
  27. package/src/managers/triggers.ts +349 -0
  28. package/src/managers/validation.ts +303 -0
  29. package/src/managers/webhookSubscriptions.ts +206 -0
  30. package/src/realtime/manager.ts +292 -0
  31. package/src/types/allowedDomains.ts +29 -0
  32. package/src/types/auth.ts +83 -0
  33. package/src/types/common.ts +57 -0
  34. package/src/types/compute.ts +145 -0
  35. package/src/types/insights.ts +113 -0
  36. package/src/types/orchestrations.ts +460 -0
  37. package/src/types/realtime.ts +403 -0
  38. package/src/types/records.ts +261 -0
  39. package/src/types/search.ts +44 -0
  40. package/src/types/smartQueries.ts +303 -0
  41. package/src/types/structures.ts +203 -0
  42. package/src/types/triggers.ts +122 -0
  43. package/src/types/validation.ts +167 -0
  44. package/src/types/webhooks.ts +114 -0
  45. package/src/urls.ts +33 -0
  46. package/dist/query-types.d.ts +0 -187
  47. package/dist/query-types.js +0 -137
  48. package/dist/scripts/smoke-types.d.ts +0 -12
  49. package/dist/scripts/smoke-types.js +0 -102
@@ -0,0 +1,122 @@
1
+ // =====================================================
2
+ // Orchestration Runs Manager
3
+ // =====================================================
4
+
5
+ import type { Method } from 'axios';
6
+ import type { ApiResponse } from '../types/common';
7
+ import type { OrchestrationRun } from '../types/orchestrations';
8
+ import type { QueryDefinition, QueryResult } from '../../query-types';
9
+ import { getOrchestrationRunsCanonicalApiPath } from '../internal/paths';
10
+
11
+ // =====================================================
12
+ // Orchestration Runs Manager
13
+ // =====================================================
14
+
15
+ /**
16
+ * OrchestrationRunsManager — canonical query surface for orchestration runs
17
+ * (CEN-1217 / Phase 3 of the query foundation).
18
+ *
19
+ * Available methods:
20
+ * - {@link OrchestrationRunsManager.query | query} — canonical
21
+ * `POST /orchestration-runs/query`. Use for nested boolean trees, `select`
22
+ * projection, paging.
23
+ * - {@link OrchestrationRunsManager.test | test} — authoring dry-run against
24
+ * `/orchestration-runs/query/test`. Validates and plans without executing.
25
+ *
26
+ * Per-orchestration trigger / list / get-by-id helpers stay on
27
+ * `client.orchestrations.{trigger, listRuns, getRun, getRunSteps}` — those
28
+ * back the legacy nested `/orchestrations/{id}/runs` routes (which now emit a
29
+ * Deprecation header pointing here for the list shape).
30
+ *
31
+ * Access via `client.orchestrationRuns`.
32
+ */
33
+ export class OrchestrationRunsManager {
34
+ private requestFn: <T>(method: Method, path: string, data?: any, queryParams?: Record<string, any>) => Promise<ApiResponse<T>>;
35
+ private workspaceId: string;
36
+
37
+ constructor(
38
+ workspaceId: string,
39
+ requestFn: <T>(method: Method, path: string, data?: any, queryParams?: Record<string, any>) => Promise<ApiResponse<T>>
40
+ ) {
41
+ this.workspaceId = workspaceId;
42
+ this.requestFn = requestFn;
43
+ }
44
+
45
+ /**
46
+ * Run a canonical query against `POST /workspace/<ws>/api/v1/orchestration-runs/query`.
47
+ *
48
+ * The body **is** a `QueryDefinition`. `resource` is forced to
49
+ * `"orchestration-runs"` — the orchestration-service executor rejects
50
+ * anything else. Returns the canonical `{ data, meta }` envelope.
51
+ *
52
+ * Queryable fields are the fixed-schema columns: `id`, `orchestrationId`,
53
+ * `orchestrationVersion`, `status`, `currentStepId`, `correlationId`,
54
+ * `triggerType`, `hasErrors`, `delayStepCount`, `loopIterationCount`,
55
+ * `failureReason`, `startedAt`, `completedAt`, `ttlExpiresAt`. The JSONB
56
+ * columns (`input`, `context`, `stepOutputs`, `triggerMetadata`,
57
+ * `activeLoop`) are intentionally not queryable —
58
+ * fetch a specific run via
59
+ * `client.orchestrations.getRun(orchestrationId, runId, true)` if you need
60
+ * its payload and step history.
61
+ *
62
+ * The `where` clause that pins `orchestrationId.eq` or `id.eq` (in a pure
63
+ * AND-of-scalar-eq shape) uses `orchestrations:retrieve` server-side;
64
+ * everything else uses `orchestrations:list`. Retrieve-only roles can
65
+ * still query a specific orchestration's run history this way.
66
+ *
67
+ * @example
68
+ * ```ts
69
+ * // Recent failed runs for a specific orchestration
70
+ * const failures = await client.orchestrationRuns.query({
71
+ * resource: 'orchestration-runs',
72
+ * where: {
73
+ * and: [
74
+ * { orchestrationId: { eq: 'orch-123' } },
75
+ * { status: { eq: 'failed' } },
76
+ * ],
77
+ * },
78
+ * sort: [{ field: 'startedAt', direction: 'desc' }],
79
+ * page: { limit: 50 },
80
+ * });
81
+ *
82
+ * // Workspace-wide run activity in a date window with field projection
83
+ * const activity = await client.orchestrationRuns.query({
84
+ * resource: 'orchestration-runs',
85
+ * where: {
86
+ * and: [
87
+ * { startedAt: { gte: '2026-04-01' } },
88
+ * { startedAt: { lt: '2026-05-01' } },
89
+ * ],
90
+ * },
91
+ * sort: [{ field: 'startedAt', direction: 'desc' }],
92
+ * page: { limit: 100 },
93
+ * select: { fields: ['id', 'orchestrationId', 'status', 'startedAt', 'completedAt', 'hasErrors'] },
94
+ * });
95
+ * ```
96
+ */
97
+ public async query<T = OrchestrationRun>(
98
+ definition: Omit<QueryDefinition, 'resource'> & { resource?: string }
99
+ ): Promise<QueryResult<T>> {
100
+ const path = getOrchestrationRunsCanonicalApiPath(this.workspaceId, 'query');
101
+ const body: QueryDefinition = { ...definition, resource: 'orchestration-runs' };
102
+ const resp = await this.requestFn<T[]>('POST', path, body);
103
+ return resp as unknown as QueryResult<T>;
104
+ }
105
+
106
+ /**
107
+ * Authoring-time dry-run against `POST /orchestration-runs/query/test`.
108
+ *
109
+ * Same input shape as {@link OrchestrationRunsManager.query | query}.
110
+ * Returns a plan summary on success or structured `QueryError`s on
111
+ * failure, without executing the query. Use from query builders to
112
+ * surface precise errors before saving / running.
113
+ */
114
+ public async test(
115
+ definition: Omit<QueryDefinition, 'resource'> & { resource?: string }
116
+ ): Promise<{ ok: true; plan: { executor: string; resource: string; mode: string; hasUnreadableField: boolean } }> {
117
+ const path = getOrchestrationRunsCanonicalApiPath(this.workspaceId, 'query/test');
118
+ const body: QueryDefinition = { ...definition, resource: 'orchestration-runs' };
119
+ const resp = await this.requestFn<{ ok: true; plan: { executor: string; resource: string; mode: string; hasUnreadableField: boolean } }>('POST', path, body);
120
+ return resp.data;
121
+ }
122
+ }
@@ -0,0 +1,297 @@
1
+ // =====================================================
2
+ // Orchestrations Manager
3
+ // =====================================================
4
+
5
+ import type { Method } from 'axios';
6
+ import type { ApiResponse, PaginatedResponse } from '../types/common';
7
+ import type {
8
+ Orchestration,
9
+ CreateOrchestrationInput,
10
+ UpdateOrchestrationInput,
11
+ OrchestrationRun,
12
+ OrchestrationRunStep,
13
+ TriggerOrchestrationRunOptions,
14
+ ListOrchestrationsOptions,
15
+ ListOrchestrationRunsOptions,
16
+ } from '../types/orchestrations';
17
+ import {
18
+ getOrchestrationsApiPath,
19
+ getOrchestrationRunsApiPath,
20
+ getOrchestrationRunStepsApiPath,
21
+ } from '../internal/paths';
22
+
23
+ export class OrchestrationsManager {
24
+ private requestFn: <T>(method: Method, path: string, data?: any, queryParams?: Record<string, any>) => Promise<ApiResponse<T>>;
25
+ private workspaceId: string;
26
+
27
+ constructor(
28
+ workspaceId: string,
29
+ requestFn: <T>(method: Method, path: string, data?: any, queryParams?: Record<string, any>) => Promise<ApiResponse<T>>
30
+ ) {
31
+ this.workspaceId = workspaceId;
32
+ this.requestFn = requestFn;
33
+ }
34
+
35
+ /**
36
+ * List all orchestrations in the workspace.
37
+ *
38
+ * @param options - Optional pagination and filtering options
39
+ * @returns Paginated list of orchestrations
40
+ *
41
+ * @example
42
+ * ```ts
43
+ * // List all orchestrations
44
+ * const result = await client.orchestrations.list();
45
+ * console.log('Total:', result.data.meta.total);
46
+ *
47
+ * // With pagination and status filter
48
+ * const result = await client.orchestrations.list({
49
+ * limit: 10,
50
+ * offset: 0,
51
+ * status: 'active'
52
+ * });
53
+ * ```
54
+ */
55
+ public list(options?: ListOrchestrationsOptions): Promise<ApiResponse<PaginatedResponse<Orchestration>>> {
56
+ const path = getOrchestrationsApiPath(this.workspaceId);
57
+ return this.requestFn<PaginatedResponse<Orchestration>>('GET', path, null, options);
58
+ }
59
+
60
+ /**
61
+ * Get an orchestration by ID.
62
+ *
63
+ * @param orchestrationId - The orchestration ID
64
+ * @returns The orchestration details including all steps
65
+ *
66
+ * @example
67
+ * ```ts
68
+ * const orch = await client.orchestrations.get('orch-id');
69
+ * console.log('Name:', orch.data.name);
70
+ * console.log('Steps:', orch.data.steps.length);
71
+ * ```
72
+ */
73
+ public get(orchestrationId: string): Promise<ApiResponse<Orchestration>> {
74
+ const path = getOrchestrationsApiPath(this.workspaceId, orchestrationId);
75
+ return this.requestFn<Orchestration>('GET', path);
76
+ }
77
+
78
+ /**
79
+ * Create a new orchestration.
80
+ *
81
+ * @param input - The orchestration definition
82
+ * @returns The created orchestration
83
+ *
84
+ * @example
85
+ * ```ts
86
+ * const orch = await client.orchestrations.create({
87
+ * slug: 'order-processing',
88
+ * name: 'Order Processing Workflow',
89
+ * trigger: { type: 'on-demand' },
90
+ * steps: [
91
+ * {
92
+ * id: 'validate',
93
+ * type: 'compute',
94
+ * functionId: 'func_validate',
95
+ * onSuccess: { nextStepId: 'process' }
96
+ * },
97
+ * {
98
+ * id: 'process',
99
+ * type: 'compute',
100
+ * functionId: 'func_process'
101
+ * }
102
+ * ]
103
+ * });
104
+ * ```
105
+ */
106
+ public create(input: CreateOrchestrationInput): Promise<ApiResponse<Orchestration>> {
107
+ const path = getOrchestrationsApiPath(this.workspaceId);
108
+ return this.requestFn<Orchestration>('POST', path, input);
109
+ }
110
+
111
+ /**
112
+ * Update an existing orchestration.
113
+ *
114
+ * Updates increment the orchestration version. Running instances use the
115
+ * version at the time they started.
116
+ *
117
+ * @param orchestrationId - The orchestration ID
118
+ * @param input - Fields to update
119
+ * @returns The updated orchestration
120
+ *
121
+ * @example
122
+ * ```ts
123
+ * // Activate an orchestration
124
+ * await client.orchestrations.update('orch-id', { status: 'active' });
125
+ *
126
+ * // Update steps
127
+ * await client.orchestrations.update('orch-id', {
128
+ * steps: [...]
129
+ * });
130
+ * ```
131
+ */
132
+ public update(orchestrationId: string, input: UpdateOrchestrationInput): Promise<ApiResponse<Orchestration>> {
133
+ const path = getOrchestrationsApiPath(this.workspaceId, orchestrationId);
134
+ return this.requestFn<Orchestration>('PUT', path, input);
135
+ }
136
+
137
+ /**
138
+ * Delete an orchestration.
139
+ *
140
+ * This also deletes all runs associated with the orchestration.
141
+ *
142
+ * @param orchestrationId - The orchestration ID
143
+ *
144
+ * @example
145
+ * ```ts
146
+ * await client.orchestrations.delete('orch-id');
147
+ * ```
148
+ */
149
+ public delete(orchestrationId: string): Promise<ApiResponse<void>> {
150
+ const path = getOrchestrationsApiPath(this.workspaceId, orchestrationId);
151
+ return this.requestFn<void>('DELETE', path);
152
+ }
153
+
154
+ /**
155
+ * Trigger an orchestration run.
156
+ *
157
+ * This creates a new run instance and starts executing the workflow.
158
+ * Can be used on on-demand, draft, or active orchestrations.
159
+ * Paused orchestrations cannot be triggered.
160
+ *
161
+ * @param orchestrationId - The orchestration ID
162
+ * @param options - Optional input data and correlation ID
163
+ * @returns The created run
164
+ *
165
+ * @example
166
+ * ```ts
167
+ * // Simple trigger
168
+ * const run = await client.orchestrations.trigger('orch-id');
169
+ *
170
+ * // With input data
171
+ * const run = await client.orchestrations.trigger('orch-id', {
172
+ * input: {
173
+ * orderId: '12345',
174
+ * customerId: 'cust_abc'
175
+ * }
176
+ * });
177
+ *
178
+ * // With correlation ID for tracing
179
+ * const run = await client.orchestrations.trigger('orch-id', {
180
+ * input: { orderId: '12345' },
181
+ * correlationId: 'req-123'
182
+ * });
183
+ * ```
184
+ */
185
+ public trigger(orchestrationId: string, options?: TriggerOrchestrationRunOptions): Promise<ApiResponse<OrchestrationRun>> {
186
+ const path = getOrchestrationRunsApiPath(this.workspaceId, orchestrationId);
187
+ const body = {
188
+ input: options?.input ?? {},
189
+ correlationId: options?.correlationId
190
+ };
191
+ return this.requestFn<OrchestrationRun>('POST', path, body);
192
+ }
193
+
194
+ /**
195
+ * List runs for an orchestration.
196
+ *
197
+ * @param orchestrationId - The orchestration ID
198
+ * @param options - Optional pagination and filtering options
199
+ * @returns Paginated list of runs
200
+ *
201
+ * @example
202
+ * ```ts
203
+ * // List all runs
204
+ * const runs = await client.orchestrations.listRuns('orch-id');
205
+ *
206
+ * // Filter by status
207
+ * const failedRuns = await client.orchestrations.listRuns('orch-id', {
208
+ * status: 'failed'
209
+ * });
210
+ * ```
211
+ */
212
+ public listRuns(orchestrationId: string, options?: ListOrchestrationRunsOptions): Promise<ApiResponse<PaginatedResponse<OrchestrationRun>>> {
213
+ const path = getOrchestrationRunsApiPath(this.workspaceId, orchestrationId);
214
+ return this.requestFn<PaginatedResponse<OrchestrationRun>>('GET', path, null, options);
215
+ }
216
+
217
+ /**
218
+ * Get a specific run by ID.
219
+ *
220
+ * @param orchestrationId - The orchestration ID
221
+ * @param runId - The run ID
222
+ * @param includeSteps - Whether to include step execution history
223
+ * @returns The run details
224
+ *
225
+ * @example
226
+ * ```ts
227
+ * // Get basic run info
228
+ * const run = await client.orchestrations.getRun('orch-id', 'run-id');
229
+ * console.log('Status:', run.data.status);
230
+ *
231
+ * // Include step history
232
+ * const run = await client.orchestrations.getRun('orch-id', 'run-id', true);
233
+ * console.log('Steps:', run.data.steps);
234
+ * ```
235
+ */
236
+ public getRun(orchestrationId: string, runId: string, includeSteps?: boolean): Promise<ApiResponse<OrchestrationRun & { steps?: OrchestrationRunStep[] }>> {
237
+ const path = getOrchestrationRunsApiPath(this.workspaceId, orchestrationId, runId);
238
+ const params = includeSteps ? { include: 'steps' } : undefined;
239
+ return this.requestFn<OrchestrationRun & { steps?: OrchestrationRunStep[] }>('GET', path, null, params);
240
+ }
241
+
242
+ /**
243
+ * Get step execution history for a run.
244
+ *
245
+ * @param orchestrationId - The orchestration ID
246
+ * @param runId - The run ID
247
+ * @returns List of step executions
248
+ *
249
+ * @example
250
+ * ```ts
251
+ * const steps = await client.orchestrations.getRunSteps('orch-id', 'run-id');
252
+ * for (const step of steps.data.data) {
253
+ * console.log(`${step.stepId}: ${step.status}`);
254
+ * }
255
+ * ```
256
+ */
257
+ public getRunSteps(orchestrationId: string, runId: string): Promise<ApiResponse<{ data: OrchestrationRunStep[]; meta: { total: number } }>> {
258
+ const path = getOrchestrationRunStepsApiPath(this.workspaceId, orchestrationId, runId);
259
+ return this.requestFn<{ data: OrchestrationRunStep[]; meta: { total: number } }>('GET', path);
260
+ }
261
+
262
+ /**
263
+ * Activate an orchestration.
264
+ *
265
+ * Convenience method to set status to 'active'.
266
+ * Active orchestrations can be triggered by scheduled events, record events, and webhooks.
267
+ *
268
+ * @param orchestrationId - The orchestration ID
269
+ * @returns The updated orchestration
270
+ *
271
+ * @example
272
+ * ```ts
273
+ * await client.orchestrations.activate('orch-id');
274
+ * ```
275
+ */
276
+ public activate(orchestrationId: string): Promise<ApiResponse<Orchestration>> {
277
+ return this.update(orchestrationId, { status: 'active' });
278
+ }
279
+
280
+ /**
281
+ * Pause an orchestration.
282
+ *
283
+ * Convenience method to set status to 'paused'.
284
+ * Paused orchestrations cannot be triggered by any mechanism.
285
+ *
286
+ * @param orchestrationId - The orchestration ID
287
+ * @returns The updated orchestration
288
+ *
289
+ * @example
290
+ * ```ts
291
+ * await client.orchestrations.pause('orch-id');
292
+ * ```
293
+ */
294
+ public pause(orchestrationId: string): Promise<ApiResponse<Orchestration>> {
295
+ return this.update(orchestrationId, { status: 'paused' });
296
+ }
297
+ }
@@ -0,0 +1,199 @@
1
+ // =====================================================
2
+ // Query Manager (canonical query primitives — CEN-1202)
3
+ // =====================================================
4
+
5
+ import {
6
+ validateQueryDefinition,
7
+ translateLegacyQuery,
8
+ parseUrlQuery,
9
+ queryErrorsToHttp,
10
+ } from '@centrali/query';
11
+ import type {
12
+ QueryDefinition,
13
+ QueryError,
14
+ ValidationResult,
15
+ } from '../../query-types';
16
+ import { CentraliError } from '../internal/error';
17
+
18
+ // ---------------------------------------------------------------------------
19
+ // Vendored option/result types
20
+ //
21
+ // These mirror the option/result shapes exported from `@centrali/query`
22
+ // (validator.ts, legacyTranslator.ts, urlParser.ts, errorAdapter.ts). They
23
+ // are vendored locally so the published `dist/index.d.ts` does not pull in
24
+ // `@centrali/query` (which would force consumers to install the workspace
25
+ // package — failing the SDK's "one install" promise and breaking
26
+ // `tsc --skipLibCheck=false`).
27
+ //
28
+ // Drift is caught by the parity tests in `tests/queryParity.test.ts` —
29
+ // the runtime functions imported above expect the upstream shapes, and
30
+ // any incompatibility surfaces as a TypeScript error at SDK build time.
31
+ // ---------------------------------------------------------------------------
32
+
33
+ /** Mirror of `@centrali/query`'s `ValidateOptions`. */
34
+ export type QueryValidateOptions = {
35
+ rejectReservedClauses?: boolean;
36
+ reservedClauses?: readonly (keyof QueryDefinition)[];
37
+ };
38
+
39
+ /** Mirror of `@centrali/query`'s `LegacyTranslateWarning`. */
40
+ export type LegacyTranslateWarning = {
41
+ kind:
42
+ | 'regex_translated'
43
+ | 'operator_renamed'
44
+ | 'variable_syntax_canonicalized'
45
+ | 'operator_dropped';
46
+ path: string;
47
+ from: string;
48
+ to?: string;
49
+ };
50
+
51
+ /** Mirror of `@centrali/query`'s `LegacyTranslateResult`. */
52
+ export type LegacyTranslateResult = {
53
+ query: QueryDefinition;
54
+ warnings: LegacyTranslateWarning[];
55
+ };
56
+
57
+ /** Mirror of `@centrali/query`'s `LegacyTranslateOptions`. */
58
+ export type LegacyTranslateOptions = {
59
+ resource?: string;
60
+ dataProperties?: ReadonlySet<string>;
61
+ };
62
+
63
+ /** Mirror of `@centrali/query`'s `ParsedUrlQuery`. */
64
+ export type ParsedUrlQuery = {
65
+ query: QueryDefinition;
66
+ searchTerm?: string;
67
+ };
68
+
69
+ /** Mirror of `@centrali/query`'s `UrlParserOptions`. */
70
+ export type UrlParserOptions = {
71
+ resource: string;
72
+ filterableFields?: readonly string[];
73
+ defaultLimit?: number;
74
+ maxLimit?: number;
75
+ };
76
+
77
+ /** Mirror of `@centrali/query`'s `QueryHttpErrorCode`. */
78
+ export type QueryHttpErrorCode =
79
+ | 'UNSUPPORTED_CLAUSE'
80
+ | 'UNSUPPORTED_OPERATOR'
81
+ | 'UNSUPPORTED_LEGACY_OPERATOR'
82
+ | 'UNREADABLE_FIELD'
83
+ | 'INVALID_QUERY'
84
+ | 'LEGACY_WRITE_UNSUPPORTED'
85
+ | 'UNKNOWN_VARIABLE'
86
+ | 'VARIABLE_TYPE_MISMATCH'
87
+ | 'MISSING_REQUIRED_VARIABLE'
88
+ | 'EXTRA_VARIABLE'
89
+ | 'JOINS_LENGTH_EXCEEDED'
90
+ | 'UNSUPPORTED_COMBINATION'
91
+ | 'DUPLICATE_JOIN_ALIAS';
92
+
93
+ /** Mirror of `@centrali/query`'s `QueryHttpError`. */
94
+ export type QueryHttpError = {
95
+ status: number;
96
+ code: QueryHttpErrorCode;
97
+ message: string;
98
+ errors: QueryError[];
99
+ };
100
+
101
+ /**
102
+ * QueryManager exposes the canonical query primitives bundled into the SDK.
103
+ *
104
+ * The same source that runs server-side (`@centrali/query`) is bundled into
105
+ * the SDK artifact, so callers can validate a `QueryDefinition` locally and
106
+ * skip a roundtrip when the body is malformed. Output is byte-for-byte
107
+ * identical to the server's validator.
108
+ *
109
+ * Access via `client.query`.
110
+ *
111
+ * @example
112
+ * ```ts
113
+ * const result = client.query.validate({
114
+ * resource: 'orders',
115
+ * where: { 'data.status': { eq: 'open' } },
116
+ * page: { limit: 50 },
117
+ * });
118
+ * if (!result.ok) {
119
+ * for (const err of result.errors) console.log(err.code, err.path, err.message);
120
+ * }
121
+ *
122
+ * // Migrate a legacy saved-query body to canonical
123
+ * const t = client.query.translateLegacy({ collection: 'orders', limit: 25 });
124
+ * if (t.ok) console.log(t.value.query, t.value.warnings);
125
+ * ```
126
+ */
127
+ export class QueryManager {
128
+ /**
129
+ * Validate a canonical `QueryDefinition` locally. Returns the same
130
+ * `ValidationResult` shape the server returns — `ok: true` plus the
131
+ * narrowed value, or `ok: false` plus the error list.
132
+ */
133
+ public validate(
134
+ definition: unknown,
135
+ options?: QueryValidateOptions
136
+ ): ValidationResult<QueryDefinition> {
137
+ return validateQueryDefinition(definition, options);
138
+ }
139
+
140
+ /**
141
+ * Translate a legacy query body (`{ collection, $eq, ... }`) into a
142
+ * canonical `QueryDefinition`. Useful for migration tooling and for
143
+ * smart-query authors carrying older bodies forward.
144
+ *
145
+ * Returns translation warnings alongside the canonical query so callers
146
+ * can surface "this used to be a regex / dropped operator" notes.
147
+ */
148
+ public translateLegacy(
149
+ legacyBody: unknown,
150
+ options?: LegacyTranslateOptions
151
+ ): ValidationResult<LegacyTranslateResult> {
152
+ return translateLegacyQuery(legacyBody, options) as ValidationResult<LegacyTranslateResult>;
153
+ }
154
+
155
+ /**
156
+ * Parse a URL-style flat query (`?status=paid&sort=-createdAt&limit=10`)
157
+ * into a canonical `QueryDefinition` slice. Mirrors the server-side
158
+ * GET-adapter so SDK consumers can pre-build canonical bodies from
159
+ * search-page URL state without a roundtrip.
160
+ */
161
+ public parseUrl(
162
+ params: Record<string, unknown>,
163
+ options: UrlParserOptions
164
+ ): ValidationResult<ParsedUrlQuery> {
165
+ return parseUrlQuery(params, options) as ValidationResult<ParsedUrlQuery>;
166
+ }
167
+
168
+ /**
169
+ * Aggregate one or more `QueryError`s into the same `{ status, code,
170
+ * message, errors }` shape the server emits. Use to convert a
171
+ * validation failure into a thrown `CentraliError` matching the wire
172
+ * format.
173
+ */
174
+ public errorsToHttp(errors: QueryError[]): QueryHttpError {
175
+ return queryErrorsToHttp(errors) as QueryHttpError;
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Convert a list of `QueryError`s to a `CentraliError` matching what the
181
+ * server would have sent on a `422`. Used by `RecordsManager.query()` for
182
+ * local pre-rejection.
183
+ *
184
+ * @internal
185
+ */
186
+ export function queryErrorsToCentraliError(errors: QueryError[]): CentraliError {
187
+ const http = queryErrorsToHttp(errors);
188
+ const fieldErrors = errors
189
+ .filter((e) => typeof e.path === 'string' && e.path.length > 0)
190
+ .map((e) => ({ field: e.path as string, message: e.message }));
191
+ return new CentraliError(
192
+ http.message,
193
+ http.status,
194
+ http.status === 422 ? 'Unprocessable Entity' : 'Bad Request',
195
+ { code: http.code, message: http.message, errors: http.errors },
196
+ http.code,
197
+ fieldErrors.length > 0 ? fieldErrors : undefined
198
+ );
199
+ }