@dashai/sdk 0.7.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 (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +377 -0
  3. package/dist/auth/client.cjs +185 -0
  4. package/dist/auth/client.cjs.map +1 -0
  5. package/dist/auth/client.d.cts +137 -0
  6. package/dist/auth/client.d.ts +137 -0
  7. package/dist/auth/client.js +175 -0
  8. package/dist/auth/client.js.map +1 -0
  9. package/dist/auth/middleware.cjs +352 -0
  10. package/dist/auth/middleware.cjs.map +1 -0
  11. package/dist/auth/middleware.d.cts +90 -0
  12. package/dist/auth/middleware.d.ts +90 -0
  13. package/dist/auth/middleware.js +343 -0
  14. package/dist/auth/middleware.js.map +1 -0
  15. package/dist/auth/server.cjs +445 -0
  16. package/dist/auth/server.cjs.map +1 -0
  17. package/dist/auth/server.d.cts +170 -0
  18. package/dist/auth/server.d.ts +170 -0
  19. package/dist/auth/server.js +432 -0
  20. package/dist/auth/server.js.map +1 -0
  21. package/dist/auth/types.cjs +31 -0
  22. package/dist/auth/types.cjs.map +1 -0
  23. package/dist/auth/types.d.cts +163 -0
  24. package/dist/auth/types.d.ts +163 -0
  25. package/dist/auth/types.js +29 -0
  26. package/dist/auth/types.js.map +1 -0
  27. package/dist/deps/index.cjs +117 -0
  28. package/dist/deps/index.cjs.map +1 -0
  29. package/dist/deps/index.d.cts +93 -0
  30. package/dist/deps/index.d.ts +93 -0
  31. package/dist/deps/index.js +112 -0
  32. package/dist/deps/index.js.map +1 -0
  33. package/dist/errors-BV75u7b9.d.cts +207 -0
  34. package/dist/errors-BV75u7b9.d.ts +207 -0
  35. package/dist/index.cjs +721 -0
  36. package/dist/index.cjs.map +1 -0
  37. package/dist/index.d.cts +171 -0
  38. package/dist/index.d.ts +171 -0
  39. package/dist/index.js +703 -0
  40. package/dist/index.js.map +1 -0
  41. package/dist/query/index.cjs +621 -0
  42. package/dist/query/index.cjs.map +1 -0
  43. package/dist/query/index.d.cts +800 -0
  44. package/dist/query/index.d.ts +800 -0
  45. package/dist/query/index.js +617 -0
  46. package/dist/query/index.js.map +1 -0
  47. package/dist/react/index.cjs +199 -0
  48. package/dist/react/index.cjs.map +1 -0
  49. package/dist/react/index.d.cts +117 -0
  50. package/dist/react/index.d.ts +117 -0
  51. package/dist/react/index.js +195 -0
  52. package/dist/react/index.js.map +1 -0
  53. package/dist/types-BLNQ1S1C.d.cts +396 -0
  54. package/dist/types-BLNQ1S1C.d.ts +396 -0
  55. package/package.json +109 -0
@@ -0,0 +1,396 @@
1
+ /**
2
+ * Public types for the @dashai/sdk data plane.
3
+ *
4
+ * These types are the contract between:
5
+ * - the runtime HTTP client in this package
6
+ * - the per-module generated typed wrappers (see `dashwise module generate`)
7
+ * - hand-authored / custom modules that import the SDK directly
8
+ *
9
+ * Anything in this file is part of the package's SemVer-stable surface.
10
+ * Breaking changes require a major version bump.
11
+ */
12
+ /**
13
+ * The two columns every DashWise row carries regardless of manifest
14
+ * configuration: an auto-incrementing primary key + a fractional `order`
15
+ * string for stable user-defined sort orders.
16
+ *
17
+ * Per-module generated row types extend this shape with their
18
+ * manifest-declared fields.
19
+ */
20
+ interface BaseRow {
21
+ id: number;
22
+ order: string;
23
+ }
24
+ /**
25
+ * Shape passed to `db.<table>.create()`. The generated typed layer
26
+ * intersects this with the per-table required-vs-optional field map.
27
+ */
28
+ type BaseInsert = Record<string, unknown>;
29
+ /** Shape passed to `db.<table>.update()`. Always a `Partial` of insert. */
30
+ type BaseUpdate = Record<string, unknown>;
31
+ /**
32
+ * Reference to a file stored in DashWise's blob store. Returned in row
33
+ * payloads for `file` / `multi_file` field types. Mutation goes through
34
+ * a separate upload endpoint (out of scope for v0.1).
35
+ */
36
+ interface FileRef {
37
+ id: number;
38
+ name: string;
39
+ mime: string;
40
+ size: number;
41
+ }
42
+ /**
43
+ * Filter operators for a `Where<Row>` clause. Each key maps 1:1 to a
44
+ * backend filter type (see `RowQueryService`'s filter registry in
45
+ * `dashwise-backend`). Multiple ops on the same field are AND'd
46
+ * (`{ count: { higher_than: 5, lower_than: 100 } }` →
47
+ * `count > 5 AND count < 100`). Unknown keys are rejected by the backend
48
+ * with `FILTER_OP_UNKNOWN`.
49
+ *
50
+ * Per-field-type narrowing (e.g. rejecting `contains` on a number field)
51
+ * is M5+ polish — for now every operator is typed as optional on every
52
+ * field, and the backend returns a typed 400 if a runtime mismatch happens.
53
+ */
54
+ interface FilterOps {
55
+ equal?: unknown;
56
+ not_equal?: unknown;
57
+ contains?: string;
58
+ not_contains?: string;
59
+ empty?: true;
60
+ not_empty?: true;
61
+ higher_than?: number;
62
+ lower_than?: number;
63
+ date_equal?: string;
64
+ date_before?: string;
65
+ date_after?: string;
66
+ boolean?: boolean;
67
+ }
68
+ /**
69
+ * Per-field filter value: either a bare primitive (shorthand for
70
+ * `{ equal: ... }`) or a `FilterOps` operator object.
71
+ */
72
+ type FilterValue<V> = V | FilterOps;
73
+ /**
74
+ * Recursive filter clause for `db.<table>.list()`. Top-level keys are
75
+ * AND'd together. Use `AND` / `OR` arrays for explicit logical grouping;
76
+ * nest as deeply as needed (server caps at depth 10).
77
+ *
78
+ * @example
79
+ * { done: false, title: { contains: 'urgent' } }
80
+ * // → WHERE done = false AND title ILIKE '%urgent%'
81
+ *
82
+ * @example
83
+ * { OR: [{ done: true }, { archived: true }] }
84
+ * // → WHERE done = true OR archived = true
85
+ */
86
+ type Where<Row> = {
87
+ [K in keyof Row]?: FilterValue<Row[K]>;
88
+ } & {
89
+ AND?: Where<Row>[];
90
+ OR?: Where<Row>[];
91
+ };
92
+ type SortDir = 'asc' | 'desc';
93
+ interface SortClause<Row> {
94
+ field: keyof Row & string;
95
+ dir: SortDir;
96
+ }
97
+ /**
98
+ * Options accepted by `db.<table>.list()`. All fields are optional;
99
+ * server defaults: `limit=50`, no filter, no sort.
100
+ */
101
+ interface ListOpts<Row> {
102
+ filter?: Where<Row>;
103
+ sort?: SortClause<Row>[];
104
+ limit?: number;
105
+ offset?: number;
106
+ }
107
+ /**
108
+ * Paginated response from `db.<table>.list()`. Today the generated typed
109
+ * layer unwraps this to a bare array for ergonomics; consumers who need
110
+ * pagination metadata should use the raw client or wait for M5+ when
111
+ * `.list()` returns the full `Page<Row>`.
112
+ */
113
+ interface Page<Row> {
114
+ rows: Row[];
115
+ total: number;
116
+ limit: number;
117
+ offset: number;
118
+ }
119
+ /**
120
+ * The chainable surface returned by `client.db<Row>('table-slug')`. This
121
+ * is the generic / untyped face of the data plane — the generated layer
122
+ * binds the table slug + row type and re-exports a typed wrapper.
123
+ */
124
+ interface TableClient<Row extends BaseRow, Insert extends BaseInsert = BaseInsert, Update extends BaseUpdate = BaseUpdate> {
125
+ /** List rows matching `opts.filter`; paginated. */
126
+ list(opts?: ListOpts<Row>): Promise<Page<Row>>;
127
+ /** Fetch a single row by primary key. Rejects with `ROW_NOT_FOUND` if absent. */
128
+ get(id: number): Promise<Row>;
129
+ /** Insert a row; returns the persisted row including server-assigned id + order. */
130
+ create(body: Insert): Promise<Row>;
131
+ /** Partially update a row by id; returns the updated row. */
132
+ update(id: number, patch: Update): Promise<Row>;
133
+ /** Soft-delete a row by id; resolves with `void` on success. */
134
+ delete(id: number): Promise<void>;
135
+ }
136
+ /**
137
+ * The chainable surface returned by `client.deps('provider').db<Row>('table-slug')`.
138
+ *
139
+ * Same `list` + `get` shape as the full {@link TableClient}, but
140
+ * deliberately omits write operations: cross-module writes flow
141
+ * through the provider module's published actions (Phase 7+), NOT
142
+ * through direct INSERT/UPDATE/DELETE. Surfaced as a distinct
143
+ * interface so the type system enforces the no-cross-module-writes
144
+ * invariant — `(client.deps('crm').db('contacts') as any).create(...)`
145
+ * is the only way to bypass it, which is exactly the cost we want.
146
+ *
147
+ * See `dashwise-devops-docs/plans/modules-sdk-query-v1.md` §4.3 for
148
+ * the trust-model rationale.
149
+ */
150
+ interface ReadOnlyTableClient<Row extends BaseRow> {
151
+ /** List rows from the provider's table, projected to the consumer's contract. */
152
+ list(opts?: ListOpts<Row>): Promise<Page<Row>>;
153
+ /** Fetch a single row by primary key, projected to the consumer's contract. */
154
+ get(id: number): Promise<Row>;
155
+ }
156
+ /**
157
+ * Borrowed-table accessor returned by {@link Client.deps}.
158
+ *
159
+ * Mirrors `client.db()` shape but the inner `db()` returns a
160
+ * read-only {@link ReadOnlyTableClient}. The `providerSlug` argument
161
+ * passed to `client.deps('provider')` must match a `dependencies[]`
162
+ * entry in the caller's `module.json`; mismatches surface as
163
+ * `DependencyContractError` on the first call.
164
+ */
165
+ interface Deps {
166
+ /**
167
+ * Return a read-only table client for `tableSlug` exposed by the
168
+ * provider this {@link Deps} was bound to. The table must be listed
169
+ * in the caller's `dependencies[].reads[]` block AND the provider's
170
+ * `exposes[]` block; backend re-validates on every call.
171
+ */
172
+ db<Row extends BaseRow>(tableSlug: string): ReadOnlyTableClient<Row>;
173
+ }
174
+ /**
175
+ * Token resolver. Called once per request. Return `null` for unauthenticated
176
+ * (the backend will reject most calls); return a JWT or CLI token otherwise.
177
+ *
178
+ * Sync or async — the client awaits the result.
179
+ */
180
+ type TokenResolver = () => string | null | Promise<string | null>;
181
+ /**
182
+ * Optional fetch implementation override. Defaults to `globalThis.fetch`.
183
+ * Useful for testing (inject a mock) or for runtimes that need a custom
184
+ * agent (Node.js + corporate proxy). MUST match the standard `fetch`
185
+ * signature.
186
+ */
187
+ type FetchImpl = typeof globalThis.fetch;
188
+ /**
189
+ * Config passed to `createClient()`.
190
+ */
191
+ interface ClientConfig {
192
+ /**
193
+ * Base URL of the DashWise API. No trailing slash; the client will
194
+ * append `/api/...` paths. Example: `https://api.dashwise.io`.
195
+ */
196
+ baseUrl: string;
197
+ /**
198
+ * Token resolver. Called once per request, before the HTTP call.
199
+ * Resolved tokens are attached as `Authorization: Bearer <token>`.
200
+ */
201
+ getToken?: TokenResolver;
202
+ /**
203
+ * Installation ID this client is scoped to. Required for module
204
+ * runtime calls. Typical values:
205
+ * - a numeric `module_installations.id` for production modules
206
+ * - `'sandbox'` for AI builder draft loops (no installation row yet)
207
+ * - `'local'` for `dashwise module dev` against a dev token
208
+ *
209
+ * Accepts any string-shaped id (or number) so consumers can pass
210
+ * `process.env.DASHWISE_INSTALLATION_ID` directly without coercion.
211
+ * The value is interpolated into the URL path as-is.
212
+ */
213
+ installationId: number | string;
214
+ /**
215
+ * Optional `fetch` override. Defaults to `globalThis.fetch`. Set this
216
+ * for tests or runtimes that need a custom HTTP agent.
217
+ */
218
+ fetch?: FetchImpl;
219
+ /**
220
+ * Optional request timeout in milliseconds. Each call wires up an
221
+ * `AbortController` that fires at this timeout. Defaults to 30_000.
222
+ */
223
+ timeoutMs?: number;
224
+ /**
225
+ * Optional custom headers added to every request. Useful for tracing
226
+ * (`X-Request-Id`, `traceparent`) when integrating with observability
227
+ * pipelines.
228
+ */
229
+ headers?: Record<string, string>;
230
+ /**
231
+ * Phase 6 slice 6.3 (2026-05-19): per-call slow-query notification.
232
+ * When set, the transport invokes this callback after EVERY request
233
+ * whose round-trip wall time exceeds `slowQueryThresholdMs`. The
234
+ * callback fires on both success AND error paths so observability
235
+ * stays consistent.
236
+ *
237
+ * Fired synchronously after the response is parsed but BEFORE the
238
+ * caller's `await` resolves/rejects. Don't do heavy work here —
239
+ * push to an in-memory queue or telemetry SDK and return quickly.
240
+ * Errors thrown from the callback are caught + swallowed (a buggy
241
+ * observability hook must not crash the query path).
242
+ *
243
+ * Measures **round-trip** time (just before `fetch()` to just after
244
+ * the response body is parsed). This includes network + server-side
245
+ * processing. The backend's `module_query_log.duration_ms` captures
246
+ * server-side processing only — useful for "is this slow because
247
+ * of the server or the network?" diagnosis when you cross-reference.
248
+ *
249
+ * @example
250
+ * const client = createClient({
251
+ * baseUrl, installationId,
252
+ * slowQueryThresholdMs: 500,
253
+ * onSlowQuery: (info) => {
254
+ * sentry.captureMessage('Slow query', { extra: info });
255
+ * },
256
+ * });
257
+ */
258
+ onSlowQuery?: (info: SlowQueryInfo) => void;
259
+ /**
260
+ * Threshold in milliseconds at which `onSlowQuery` fires. Defaults
261
+ * to 500ms (matches the backend's partial `module_query_log` slow
262
+ * index for symmetry — same threshold, same row both server-side +
263
+ * client-side observability look at). Ignored when `onSlowQuery`
264
+ * is not set.
265
+ */
266
+ slowQueryThresholdMs?: number;
267
+ }
268
+ /**
269
+ * Phase 6 slice 6.3 (2026-05-19): payload passed to the
270
+ * {@link ClientConfig.onSlowQuery} callback.
271
+ *
272
+ * Carries enough context for an observability sink to log a useful
273
+ * message + correlate with the backend's `module_query_log` row via
274
+ * `requestId`. `path` is the SDK-shaped path (relative to the
275
+ * installation-scoped base URL — e.g. `/query` or `/db/tasks/list`);
276
+ * combine with the URL the client is configured against if you want
277
+ * the full URL.
278
+ *
279
+ * `success` distinguishes "completed normally but slow" from "failed
280
+ * AND was slow" — sentry / DataDog typically want to separate these.
281
+ */
282
+ interface SlowQueryInfo {
283
+ method: string;
284
+ path: string;
285
+ /** Round-trip wall time from `fetch()` call to response body parsed. */
286
+ durationMs: number;
287
+ /** HTTP response status code. 0 for transport failures (NetworkError / TimeoutError). */
288
+ status: number;
289
+ /** Whether the request succeeded (2xx) or failed (non-2xx or transport error). */
290
+ success: boolean;
291
+ /**
292
+ * Backend trace anchor (echo of the `x-request-id` response header).
293
+ * Same value the matching {@link DashwiseError}'s `requestId` carries
294
+ * on error paths. Null when the endpoint doesn't echo the header
295
+ * yet, or when the failure was client-side (network / timeout).
296
+ */
297
+ requestId: string | null;
298
+ }
299
+ /**
300
+ * The public client returned by `createClient()`. Holds the configured
301
+ * transport + exposes a `db<Row>(slug)` factory for table clients plus
302
+ * a low-level `request` escape hatch for endpoints outside the
303
+ * `db.<table>` CRUD shape (cross-module deps, custom RPC, etc.).
304
+ *
305
+ * Thread-safe — the same client can be reused across concurrent requests.
306
+ */
307
+ interface Client {
308
+ /** Returns a table client bound to the given table slug + row type. */
309
+ db<Row extends BaseRow, Insert extends BaseInsert = BaseInsert, Update extends BaseUpdate = BaseUpdate>(tableSlug: string): TableClient<Row, Insert, Update>;
310
+ /**
311
+ * Returns a borrowed-table accessor scoped to `providerSlug`. The
312
+ * `providerSlug` must be the **module slug** (kebab-case, matches
313
+ * the manifest's `MODULE_SLUG_REGEX`) of a module listed in the
314
+ * caller's `module.json#dependencies[]`. Calls hit
315
+ * `POST /api/modules/:installation/deps/:providerSlug/db/:tableSlug/list`
316
+ * and friends; backend enforces field projection + operation gating
317
+ * against the declared contract.
318
+ *
319
+ * Cross-module access is **read-only** in v1 (the inner `db()` returns
320
+ * a {@link ReadOnlyTableClient}); writes flow through provider-published
321
+ * actions in a future phase.
322
+ *
323
+ * @example
324
+ * const contacts = await client
325
+ * .deps('crm')
326
+ * .db<ContactRow>('contacts')
327
+ * .list({ filter: { company: { equal: 'acme' } } });
328
+ */
329
+ deps(providerSlug: string): Deps;
330
+ /**
331
+ * Low-level HTTP request escape hatch. Useful for endpoints that
332
+ * don't match the `db.<table>` CRUD shape — most notably any future
333
+ * custom RPC endpoints a module exposes.
334
+ *
335
+ * The path is appended to the configured installation-scoped base
336
+ * URL (`<baseUrl>/api/modules/<installationId>`). Token attachment,
337
+ * timeout, error mapping, and 204 handling all behave identically to
338
+ * the typed CRUD methods.
339
+ *
340
+ * For cross-module reads, prefer `client.deps(providerSlug).db(...)`
341
+ * — it carries typed errors + the no-write enforcement.
342
+ */
343
+ request<T = unknown>(method: string, path: string, body?: unknown): Promise<T>;
344
+ /**
345
+ * Phase 6 streaming (2026-05-19): low-level streaming entry that
346
+ * returns the unparsed `Response`. Used by `qb.from(...).stream()`
347
+ * to consume the backend's SSE endpoint chunk-by-chunk. Most
348
+ * consumers should use `.stream()` on the builder rather than
349
+ * calling this directly — the builder handles SSE parsing + maps
350
+ * errors to typed `DashwiseError`. `streamRaw` is exposed so
351
+ * advanced users (custom transports, non-SDK streaming endpoints,
352
+ * tests) can build on top.
353
+ *
354
+ * Differs from `request()`:
355
+ * - Returns the raw `Response` regardless of HTTP status (caller
356
+ * handles non-2xx + parses the error envelope from the body).
357
+ * - Optional `signal` parameter for caller-side cancellation.
358
+ * Combined with the transport's own timeout — either trigger
359
+ * cancels the fetch.
360
+ * - Timeout applies ONLY to the headers-arrival phase. The body
361
+ * stream can take arbitrarily long; the stream reader's
362
+ * backpressure handles the long tail.
363
+ */
364
+ streamRaw(method: string, path: string, body?: unknown, signal?: AbortSignal): Promise<Response>;
365
+ /**
366
+ * Phase 7 slice 7.3a (2026-05-20): cross-module action dispatch
367
+ * helper. Internal — invoked by the codegen-emitted
368
+ * `qb.deps.<dep>.actions.<name>(input)` factories. Underscore-
369
+ * prefixed to signal "not for direct use; run `dashwise module
370
+ * generate` to get the typed surface."
371
+ *
372
+ * The factory pattern:
373
+ * ```ts
374
+ * // Generated:
375
+ * export const actions = {
376
+ * archiveContact: (input: {...}): Promise<{...}> =>
377
+ * client._callAction('crm', 'archiveContact', input),
378
+ * };
379
+ * ```
380
+ *
381
+ * Sends `POST :installationId/deps/:providerSlug/actions/:actionName`,
382
+ * unwraps the envelope on success (returns `result`), throws
383
+ * `DepActionError` on dispatch-layer failure or on action-body
384
+ * failure (envelope `ok: false`). Drift between codegen + runtime
385
+ * (provider added/removed an action) surfaces as
386
+ * `ACTION_NOT_EXPOSED` / `ACTION_NOT_DECLARED`.
387
+ *
388
+ * Spec: dashwise-devops-docs/plans/modules-sdk-query-v1-p7-cross-module-writes.md §5.1.
389
+ */
390
+ _callAction<TOutput = unknown>(providerSlug: string, actionName: string, input: unknown, opts?: {
391
+ timeoutMs?: number;
392
+ idempotencyKey?: string;
393
+ }): Promise<TOutput>;
394
+ }
395
+
396
+ export type { BaseInsert as B, ClientConfig as C, Deps as D, FetchImpl as F, ListOpts as L, Page as P, ReadOnlyTableClient as R, SlowQueryInfo as S, TokenResolver as T, Where as W, Client as a, BaseRow as b, BaseUpdate as c, FileRef as d, FilterOps as e, FilterValue as f, SortClause as g, SortDir as h, TableClient as i };