@clivly/sdk 0.9.6 → 0.10.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 +34 -13
- package/dist/chat-session.d.cts +1 -1
- package/dist/chat-session.d.mts +1 -1
- package/dist/drizzle.cjs +56 -22
- package/dist/drizzle.d.cts +8 -2
- package/dist/drizzle.d.mts +9 -3
- package/dist/drizzle.mjs +56 -22
- package/dist/index.cjs +345 -15
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +345 -15
- package/dist/{types-CiFEalbP.d.cts → types-B7HKG2Ph.d.cts} +41 -1
- package/dist/{types-CiFEalbP.d.mts → types-B7HKG2Ph.d.mts} +41 -1
- package/dist/vite.d.cts +1 -1
- package/dist/vite.d.mts +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -336,6 +336,13 @@ that query — read-only, capped at 50 rows, through the sync-trigger route you
|
|
|
336
336
|
already expose. Without it the dashboard says preview is unavailable and names
|
|
337
337
|
this setting rather than failing.
|
|
338
338
|
|
|
339
|
+
**From `0.9.7` the same setting is what runs derived objects, not just previews
|
|
340
|
+
them.** An object authored in Clivly's dashboard starts syncing on your app's
|
|
341
|
+
next run, with no per-object entry in `clivly.config.ts` — `introspector` is the
|
|
342
|
+
one line that makes it possible, and `npx clivly init` now scaffolds it. Without
|
|
343
|
+
it, dashboard-authored objects are fetched and then skipped; nothing errors, and
|
|
344
|
+
`clivly status` reports it. See [Derived objects](#derived-objects).
|
|
345
|
+
|
|
339
346
|
## Common errors and troubleshooting
|
|
340
347
|
|
|
341
348
|
| Symptom | Cause | Fix |
|
|
@@ -453,7 +460,7 @@ is honoured. Other `4xx` responses return immediately.
|
|
|
453
460
|
| `retry.maxAttempts` / `retry.baseDelayMs` | `number` | `3` / `500` | |
|
|
454
461
|
| `syncTrigger.path` / `.url` / `.secret` | `string` | — | See above |
|
|
455
462
|
| `verifyToken` | `string` | — | For `createAuthVerifyHandler`. **Nothing calls that endpoint today** |
|
|
456
|
-
| `introspector` | `NamespaceIntrospector` | — | `drizzleIntrospector(db)`. Used
|
|
463
|
+
| `introspector` | `NamespaceIntrospector` | — | `drizzleIntrospector(db)`. Used by `doctor()`, derived-object previews, and dashboard-authored derived-object sync |
|
|
457
464
|
| `namespaceReady` | `boolean` | `false` | Self-reported; no live check |
|
|
458
465
|
| `env` | `Record<string, string \| undefined>` | `process.env` | For trigger-URL discovery in non-Node runtimes |
|
|
459
466
|
| `framework` / `orm` / `sdkVersion` | `string` | — | Reported for dashboard display |
|
|
@@ -519,27 +526,41 @@ rejected key and a dead network all mean the question went unanswered.
|
|
|
519
526
|
|
|
520
527
|
A custom object can be derived from a base table plus explicit joins, instead of
|
|
521
528
|
mapping exactly one table — an `Enrollment` built from `enrollments` joined to
|
|
522
|
-
`students` and `courses`.
|
|
523
|
-
|
|
524
|
-
|
|
529
|
+
`students` and `courses`.
|
|
530
|
+
|
|
531
|
+
There are two ways to author one, and from `0.9.7` the first needs no code:
|
|
532
|
+
|
|
533
|
+
- **In Clivly's dashboard.** It starts syncing on your app's next run. The only
|
|
534
|
+
requirement is `introspector: drizzleIntrospector(db)` in your config, which
|
|
535
|
+
`npx clivly init` scaffolds — Clivly never connects to your database, so your
|
|
536
|
+
app needs a handle to run the query with. Each sync fetches your org's active
|
|
537
|
+
objects, **revalidates every one against the schema your app itself
|
|
538
|
+
discovered** before compiling any SQL, runs them, and pushes the rows. That
|
|
539
|
+
revalidation is what confines a dashboard-authored object to the tables you
|
|
540
|
+
passed to `discoverFromDrizzle`, and nothing wider.
|
|
541
|
+
- **In `clivly.config.ts`.** Declare the projection on an entity alongside the
|
|
542
|
+
object it materializes into; `validateEntitiesConfig` checks it in CI. Useful
|
|
543
|
+
when you want the definition reviewed in your repo.
|
|
525
544
|
|
|
526
|
-
Two things have to line up:
|
|
545
|
+
Two things have to line up either way:
|
|
527
546
|
|
|
528
547
|
- **Discovery must be rich enough.** `discoverFromDrizzle` sends primary keys,
|
|
529
548
|
unique constraints, nullability, column types and foreign-key edges.
|
|
530
549
|
Validation is fail-closed, so if your config hand-writes `schema` as
|
|
531
|
-
`{ name, columns }` pairs, every table is rejected with *"
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
validate against the last reported snapshot.
|
|
550
|
+
`{ name, columns }` pairs, every table is rejected with *"was discovered
|
|
551
|
+
without column types or unique keys"*. Pass `discoverFromDrizzle(schema)` and
|
|
552
|
+
redeploy so a fresh heartbeat carries the richer payload — and re-run it after
|
|
553
|
+
schema changes, since projections validate against the last reported snapshot.
|
|
536
554
|
- **The server must have the capability enabled.** Authoring sits behind
|
|
537
555
|
`CLIVLY_PROJECTION_MAPPINGS=true` while the surface settles. Reading and
|
|
538
556
|
syncing an existing derived object is never gated.
|
|
539
557
|
|
|
540
|
-
> Requires `
|
|
541
|
-
>
|
|
542
|
-
>
|
|
558
|
+
> **Requires `0.9.7` or later.** Every earlier version — `0.9.0` included, despite
|
|
559
|
+
> what this file used to claim — dropped the unique-key half of the discovery
|
|
560
|
+
> payload on the heartbeat, so validation failed closed and *no* host app could
|
|
561
|
+
> author a derived object at any version. `0.9.7` fixes that and is also the
|
|
562
|
+
> first release where a dashboard-authored object syncs without a per-object
|
|
563
|
+
> config entry.
|
|
543
564
|
|
|
544
565
|
Full guide: [`docs/guides/derived-objects.md`](../../docs/guides/derived-objects.md).
|
|
545
566
|
|
package/dist/chat-session.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as CreateChatSessionHandlerOptions } from "./types-
|
|
1
|
+
import { c as CreateChatSessionHandlerOptions } from "./types-B7HKG2Ph.cjs";
|
|
2
2
|
//#region src/chat-session.d.ts
|
|
3
3
|
declare function createChatSessionHandler({ allowedOrigins, apiKey, apiUrl, fetchImpl, resolveUser }: CreateChatSessionHandlerOptions): (request: Request) => Promise<Response>;
|
|
4
4
|
//#endregion
|
package/dist/chat-session.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as CreateChatSessionHandlerOptions } from "./types-
|
|
1
|
+
import { c as CreateChatSessionHandlerOptions } from "./types-B7HKG2Ph.mjs";
|
|
2
2
|
//#region src/chat-session.d.ts
|
|
3
3
|
declare function createChatSessionHandler({ allowedOrigins, apiKey, apiUrl, fetchImpl, resolveUser }: CreateChatSessionHandlerOptions): (request: Request) => Promise<Response>;
|
|
4
4
|
//#endregion
|
package/dist/drizzle.cjs
CHANGED
|
@@ -1,22 +1,8 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_drizzle_meta = require("./drizzle-meta-CdCs30q3.cjs");
|
|
3
|
+
let _clivly_core = require("@clivly/core");
|
|
3
4
|
let _clivly_core_drizzle = require("@clivly/core/drizzle");
|
|
4
5
|
let drizzle_orm = require("drizzle-orm");
|
|
5
|
-
let _clivly_core = require("@clivly/core");
|
|
6
|
-
//#region src/projection-preview.ts
|
|
7
|
-
/** `db.execute` returns either an array or `{ rows }`, depending on driver. */
|
|
8
|
-
function rowsOf(result) {
|
|
9
|
-
if (Array.isArray(result)) return result;
|
|
10
|
-
const rows = result.rows;
|
|
11
|
-
if (Array.isArray(rows)) return rows;
|
|
12
|
-
throw new Error("previewProjection: db.execute() returned neither an array of rows nor { rows }.");
|
|
13
|
-
}
|
|
14
|
-
async function previewProjection(db, projection, opts) {
|
|
15
|
-
const client = db;
|
|
16
|
-
const { sql } = await import("drizzle-orm");
|
|
17
|
-
return rowsOf(await client.execute(sql.raw((0, _clivly_core.compileProjectionPreview)(projection, opts.limit))));
|
|
18
|
-
}
|
|
19
|
-
//#endregion
|
|
20
6
|
//#region src/drizzle-projection.ts
|
|
21
7
|
/** `<ref>.<column>` — the token `fields` carries and cloud-ingest reads. */
|
|
22
8
|
function refToken(ref) {
|
|
@@ -53,10 +39,12 @@ function fromDrizzleProjection(db, projection, opts) {
|
|
|
53
39
|
const client = db;
|
|
54
40
|
const known = /* @__PURE__ */ new Set([_clivly_core.BASE_REF, ...projection.joins.map((j) => j.key)]);
|
|
55
41
|
if (projection.identity.kind === "base_pk" && projection.identity.column === void 0 && opts.basePkColumn === void 0) throw new Error(`fromDrizzleProjection: projection "${projection.entityKey}" has a base_pk identity with no column — pass { basePkColumn }.`);
|
|
56
|
-
const cursorRef = parseRef(opts.cursorField);
|
|
57
|
-
assertKnownRef(cursorRef, known, "cursorField");
|
|
58
42
|
for (const [field, binding] of Object.entries(projection.fieldBindings)) assertKnownRef(binding.from, known, `field "${field}"`);
|
|
59
43
|
const identityExpr = (0, _clivly_core.compileProjectionIdentity)(projection, opts.basePkColumn ?? "");
|
|
44
|
+
const fromClause = [(0, _clivly_core.compileProjectionFrom)(projection), ...(0, _clivly_core.compileProjectionJoins)(projection)].join(" ");
|
|
45
|
+
if (opts.cursorField === void 0) return fromDrizzleProjectionByIdentity(client, projection, identityExpr, fromClause, opts);
|
|
46
|
+
const cursorRef = parseRef(opts.cursorField);
|
|
47
|
+
assertKnownRef(cursorRef, known, "cursorField");
|
|
60
48
|
const cursorToken = refToken(cursorRef);
|
|
61
49
|
const cursorExpr = `CAST(${refSql(cursorRef)} AS timestamptz)`;
|
|
62
50
|
const cursorRead = `to_char(${cursorExpr} AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')`;
|
|
@@ -68,11 +56,7 @@ function fromDrizzleProjection(db, projection, opts) {
|
|
|
68
56
|
emitted.add(token);
|
|
69
57
|
selectItems.push(`${refSql(binding.from)} AS ${(0, _clivly_core.quoteIdent)(token)}`);
|
|
70
58
|
}
|
|
71
|
-
const head = [
|
|
72
|
-
`select ${selectItems.join(", ")}`,
|
|
73
|
-
(0, _clivly_core.compileProjectionFrom)(projection),
|
|
74
|
-
...(0, _clivly_core.compileProjectionJoins)(projection)
|
|
75
|
-
].join(" ");
|
|
59
|
+
const head = [`select ${selectItems.join(", ")}`, fromClause].join(" ");
|
|
76
60
|
return {
|
|
77
61
|
entity: opts.entity,
|
|
78
62
|
cursorField: cursorToken,
|
|
@@ -85,6 +69,49 @@ function fromDrizzleProjection(db, projection, opts) {
|
|
|
85
69
|
}
|
|
86
70
|
};
|
|
87
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* The identity-only paging path `fromDrizzleProjection` takes when
|
|
74
|
+
* `cursorField` is omitted. Deliberately not a generalisation of the
|
|
75
|
+
* timestamp path above — it selects no timestamp cursor, pages on the
|
|
76
|
+
* identity expression alone, and always reports `cursorField`/`idField` as
|
|
77
|
+
* `"id"`, the alias every row carries either way.
|
|
78
|
+
*/
|
|
79
|
+
function fromDrizzleProjectionByIdentity(client, projection, identityExpr, fromClause, opts) {
|
|
80
|
+
const selectItems = [`${identityExpr} AS ${(0, _clivly_core.quoteIdent)("id")}`];
|
|
81
|
+
const emitted = /* @__PURE__ */ new Set();
|
|
82
|
+
for (const binding of Object.values(projection.fieldBindings)) {
|
|
83
|
+
const token = refToken(binding.from);
|
|
84
|
+
if (emitted.has(token)) continue;
|
|
85
|
+
emitted.add(token);
|
|
86
|
+
selectItems.push(`${refSql(binding.from)} AS ${(0, _clivly_core.quoteIdent)(token)}`);
|
|
87
|
+
}
|
|
88
|
+
const head = [`select ${selectItems.join(", ")}`, fromClause].join(" ");
|
|
89
|
+
return {
|
|
90
|
+
entity: opts.entity,
|
|
91
|
+
cursorField: "id",
|
|
92
|
+
idField: "id",
|
|
93
|
+
...opts.objectType === void 0 ? {} : { objectType: opts.objectType },
|
|
94
|
+
fetchPage: async ({ since, limit }) => {
|
|
95
|
+
const where = since ? drizzle_orm.sql` where ${drizzle_orm.sql.raw(identityExpr)} > ${since.id}` : drizzle_orm.sql``;
|
|
96
|
+
const query = drizzle_orm.sql`${drizzle_orm.sql.raw(head)}${where} order by ${drizzle_orm.sql.raw(identityExpr)} asc limit ${limit}`;
|
|
97
|
+
return normalizeRows(await client.execute(query));
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
//#endregion
|
|
102
|
+
//#region src/projection-preview.ts
|
|
103
|
+
/** `db.execute` returns either an array or `{ rows }`, depending on driver. */
|
|
104
|
+
function rowsOf(result) {
|
|
105
|
+
if (Array.isArray(result)) return result;
|
|
106
|
+
const rows = result.rows;
|
|
107
|
+
if (Array.isArray(rows)) return rows;
|
|
108
|
+
throw new Error("previewProjection: db.execute() returned neither an array of rows nor { rows }.");
|
|
109
|
+
}
|
|
110
|
+
async function previewProjection(db, projection, opts) {
|
|
111
|
+
const client = db;
|
|
112
|
+
const { sql } = await import("drizzle-orm");
|
|
113
|
+
return rowsOf(await client.execute(sql.raw((0, _clivly_core.compileProjectionPreview)(projection, opts.limit))));
|
|
114
|
+
}
|
|
88
115
|
//#endregion
|
|
89
116
|
//#region src/drizzle.ts
|
|
90
117
|
function inferIdField(table) {
|
|
@@ -151,6 +178,13 @@ function drizzleIntrospector(db) {
|
|
|
151
178
|
},
|
|
152
179
|
runProjection(projection, opts) {
|
|
153
180
|
return previewProjection(db, projection, opts);
|
|
181
|
+
},
|
|
182
|
+
projectionSource(projection, opts) {
|
|
183
|
+
return fromDrizzleProjection(db, projection, {
|
|
184
|
+
entity: opts.objectType,
|
|
185
|
+
objectType: opts.objectType,
|
|
186
|
+
...opts.basePkColumn ? { basePkColumn: opts.basePkColumn } : {}
|
|
187
|
+
});
|
|
154
188
|
}
|
|
155
189
|
};
|
|
156
190
|
}
|
package/dist/drizzle.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { _ as SyncSource, y as NamespaceIntrospector } from "./types-B7HKG2Ph.cjs";
|
|
2
2
|
import { ProjectionMapping, ProjectionRef } from "@clivly/core";
|
|
3
3
|
import { Table } from "drizzle-orm";
|
|
4
4
|
import { buildUniqueConstraints, discoverFromDrizzle } from "@clivly/core/drizzle";
|
|
@@ -37,8 +37,14 @@ interface FromDrizzleProjectionOptions {
|
|
|
37
37
|
* The column to keyset-paginate on, as a ref token (`"base.updated_at"`) or
|
|
38
38
|
* a `ProjectionRef`. A projection spans several tables, so there is no
|
|
39
39
|
* single table to infer a cursor from.
|
|
40
|
+
*
|
|
41
|
+
* Omit it to page by identity alone — the mode a dashboard-authored
|
|
42
|
+
* projection uses, since it has no timestamp column and no UI to nominate
|
|
43
|
+
* one. `checkIdentitySafety` (`@clivly/core`) already proves the identity
|
|
44
|
+
* uniquely identifies a row, which makes it a sound keyset cursor with no
|
|
45
|
+
* timestamp involved.
|
|
40
46
|
*/
|
|
41
|
-
cursorField
|
|
47
|
+
cursorField?: string | ProjectionRef;
|
|
42
48
|
/** The source name rows are pushed under. */
|
|
43
49
|
entity: string;
|
|
44
50
|
/**
|
package/dist/drizzle.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { _ as SyncSource, y as NamespaceIntrospector } from "./types-B7HKG2Ph.mjs";
|
|
2
|
+
import { ProjectionMapping, ProjectionRef } from "@clivly/core";
|
|
2
3
|
import { buildUniqueConstraints, discoverFromDrizzle } from "@clivly/core/drizzle";
|
|
3
4
|
import { Table } from "drizzle-orm";
|
|
4
|
-
import { ProjectionMapping, ProjectionRef } from "@clivly/core";
|
|
5
5
|
//#region src/drizzle-projection.d.ts
|
|
6
6
|
/**
|
|
7
7
|
* Build a `SyncSource` that actually executes a projection's joins.
|
|
@@ -37,8 +37,14 @@ interface FromDrizzleProjectionOptions {
|
|
|
37
37
|
* The column to keyset-paginate on, as a ref token (`"base.updated_at"`) or
|
|
38
38
|
* a `ProjectionRef`. A projection spans several tables, so there is no
|
|
39
39
|
* single table to infer a cursor from.
|
|
40
|
+
*
|
|
41
|
+
* Omit it to page by identity alone — the mode a dashboard-authored
|
|
42
|
+
* projection uses, since it has no timestamp column and no UI to nominate
|
|
43
|
+
* one. `checkIdentitySafety` (`@clivly/core`) already proves the identity
|
|
44
|
+
* uniquely identifies a row, which makes it a sound keyset cursor with no
|
|
45
|
+
* timestamp involved.
|
|
40
46
|
*/
|
|
41
|
-
cursorField
|
|
47
|
+
cursorField?: string | ProjectionRef;
|
|
42
48
|
/** The source name rows are pushed under. */
|
|
43
49
|
entity: string;
|
|
44
50
|
/**
|
package/dist/drizzle.mjs
CHANGED
|
@@ -1,21 +1,7 @@
|
|
|
1
1
|
import { t as DRIZZLE_META } from "./drizzle-meta-DNqzkREf.mjs";
|
|
2
|
+
import { BASE_REF, compileProjectionFrom, compileProjectionIdentity, compileProjectionJoins, compileProjectionPreview, quoteIdent } from "@clivly/core";
|
|
2
3
|
import { buildColumnsMeta, buildUniqueConstraints, buildUniqueConstraints as buildUniqueConstraints$1, discoverFromDrizzle } from "@clivly/core/drizzle";
|
|
3
4
|
import { asc, getTableColumns, getTableName, sql } from "drizzle-orm";
|
|
4
|
-
import { BASE_REF, compileProjectionFrom, compileProjectionIdentity, compileProjectionJoins, compileProjectionPreview, quoteIdent } from "@clivly/core";
|
|
5
|
-
//#region src/projection-preview.ts
|
|
6
|
-
/** `db.execute` returns either an array or `{ rows }`, depending on driver. */
|
|
7
|
-
function rowsOf(result) {
|
|
8
|
-
if (Array.isArray(result)) return result;
|
|
9
|
-
const rows = result.rows;
|
|
10
|
-
if (Array.isArray(rows)) return rows;
|
|
11
|
-
throw new Error("previewProjection: db.execute() returned neither an array of rows nor { rows }.");
|
|
12
|
-
}
|
|
13
|
-
async function previewProjection(db, projection, opts) {
|
|
14
|
-
const client = db;
|
|
15
|
-
const { sql } = await import("drizzle-orm");
|
|
16
|
-
return rowsOf(await client.execute(sql.raw(compileProjectionPreview(projection, opts.limit))));
|
|
17
|
-
}
|
|
18
|
-
//#endregion
|
|
19
5
|
//#region src/drizzle-projection.ts
|
|
20
6
|
/** `<ref>.<column>` — the token `fields` carries and cloud-ingest reads. */
|
|
21
7
|
function refToken(ref) {
|
|
@@ -52,10 +38,12 @@ function fromDrizzleProjection(db, projection, opts) {
|
|
|
52
38
|
const client = db;
|
|
53
39
|
const known = /* @__PURE__ */ new Set([BASE_REF, ...projection.joins.map((j) => j.key)]);
|
|
54
40
|
if (projection.identity.kind === "base_pk" && projection.identity.column === void 0 && opts.basePkColumn === void 0) throw new Error(`fromDrizzleProjection: projection "${projection.entityKey}" has a base_pk identity with no column — pass { basePkColumn }.`);
|
|
55
|
-
const cursorRef = parseRef(opts.cursorField);
|
|
56
|
-
assertKnownRef(cursorRef, known, "cursorField");
|
|
57
41
|
for (const [field, binding] of Object.entries(projection.fieldBindings)) assertKnownRef(binding.from, known, `field "${field}"`);
|
|
58
42
|
const identityExpr = compileProjectionIdentity(projection, opts.basePkColumn ?? "");
|
|
43
|
+
const fromClause = [compileProjectionFrom(projection), ...compileProjectionJoins(projection)].join(" ");
|
|
44
|
+
if (opts.cursorField === void 0) return fromDrizzleProjectionByIdentity(client, projection, identityExpr, fromClause, opts);
|
|
45
|
+
const cursorRef = parseRef(opts.cursorField);
|
|
46
|
+
assertKnownRef(cursorRef, known, "cursorField");
|
|
59
47
|
const cursorToken = refToken(cursorRef);
|
|
60
48
|
const cursorExpr = `CAST(${refSql(cursorRef)} AS timestamptz)`;
|
|
61
49
|
const cursorRead = `to_char(${cursorExpr} AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')`;
|
|
@@ -67,11 +55,7 @@ function fromDrizzleProjection(db, projection, opts) {
|
|
|
67
55
|
emitted.add(token);
|
|
68
56
|
selectItems.push(`${refSql(binding.from)} AS ${quoteIdent(token)}`);
|
|
69
57
|
}
|
|
70
|
-
const head = [
|
|
71
|
-
`select ${selectItems.join(", ")}`,
|
|
72
|
-
compileProjectionFrom(projection),
|
|
73
|
-
...compileProjectionJoins(projection)
|
|
74
|
-
].join(" ");
|
|
58
|
+
const head = [`select ${selectItems.join(", ")}`, fromClause].join(" ");
|
|
75
59
|
return {
|
|
76
60
|
entity: opts.entity,
|
|
77
61
|
cursorField: cursorToken,
|
|
@@ -84,6 +68,49 @@ function fromDrizzleProjection(db, projection, opts) {
|
|
|
84
68
|
}
|
|
85
69
|
};
|
|
86
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* The identity-only paging path `fromDrizzleProjection` takes when
|
|
73
|
+
* `cursorField` is omitted. Deliberately not a generalisation of the
|
|
74
|
+
* timestamp path above — it selects no timestamp cursor, pages on the
|
|
75
|
+
* identity expression alone, and always reports `cursorField`/`idField` as
|
|
76
|
+
* `"id"`, the alias every row carries either way.
|
|
77
|
+
*/
|
|
78
|
+
function fromDrizzleProjectionByIdentity(client, projection, identityExpr, fromClause, opts) {
|
|
79
|
+
const selectItems = [`${identityExpr} AS ${quoteIdent("id")}`];
|
|
80
|
+
const emitted = /* @__PURE__ */ new Set();
|
|
81
|
+
for (const binding of Object.values(projection.fieldBindings)) {
|
|
82
|
+
const token = refToken(binding.from);
|
|
83
|
+
if (emitted.has(token)) continue;
|
|
84
|
+
emitted.add(token);
|
|
85
|
+
selectItems.push(`${refSql(binding.from)} AS ${quoteIdent(token)}`);
|
|
86
|
+
}
|
|
87
|
+
const head = [`select ${selectItems.join(", ")}`, fromClause].join(" ");
|
|
88
|
+
return {
|
|
89
|
+
entity: opts.entity,
|
|
90
|
+
cursorField: "id",
|
|
91
|
+
idField: "id",
|
|
92
|
+
...opts.objectType === void 0 ? {} : { objectType: opts.objectType },
|
|
93
|
+
fetchPage: async ({ since, limit }) => {
|
|
94
|
+
const where = since ? sql` where ${sql.raw(identityExpr)} > ${since.id}` : sql``;
|
|
95
|
+
const query = sql`${sql.raw(head)}${where} order by ${sql.raw(identityExpr)} asc limit ${limit}`;
|
|
96
|
+
return normalizeRows(await client.execute(query));
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
//#endregion
|
|
101
|
+
//#region src/projection-preview.ts
|
|
102
|
+
/** `db.execute` returns either an array or `{ rows }`, depending on driver. */
|
|
103
|
+
function rowsOf(result) {
|
|
104
|
+
if (Array.isArray(result)) return result;
|
|
105
|
+
const rows = result.rows;
|
|
106
|
+
if (Array.isArray(rows)) return rows;
|
|
107
|
+
throw new Error("previewProjection: db.execute() returned neither an array of rows nor { rows }.");
|
|
108
|
+
}
|
|
109
|
+
async function previewProjection(db, projection, opts) {
|
|
110
|
+
const client = db;
|
|
111
|
+
const { sql } = await import("drizzle-orm");
|
|
112
|
+
return rowsOf(await client.execute(sql.raw(compileProjectionPreview(projection, opts.limit))));
|
|
113
|
+
}
|
|
87
114
|
//#endregion
|
|
88
115
|
//#region src/drizzle.ts
|
|
89
116
|
function inferIdField(table) {
|
|
@@ -150,6 +177,13 @@ function drizzleIntrospector(db) {
|
|
|
150
177
|
},
|
|
151
178
|
runProjection(projection, opts) {
|
|
152
179
|
return previewProjection(db, projection, opts);
|
|
180
|
+
},
|
|
181
|
+
projectionSource(projection, opts) {
|
|
182
|
+
return fromDrizzleProjection(db, projection, {
|
|
183
|
+
entity: opts.objectType,
|
|
184
|
+
objectType: opts.objectType,
|
|
185
|
+
...opts.basePkColumn ? { basePkColumn: opts.basePkColumn } : {}
|
|
186
|
+
});
|
|
153
187
|
}
|
|
154
188
|
};
|
|
155
189
|
}
|