@lunora/hyperdrive 1.0.0-alpha.211 → 1.0.0-alpha.213
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 +8 -5
- package/dist/global.d.mts +88 -10
- package/dist/global.d.ts +88 -10
- package/dist/global.mjs +1 -1
- package/dist/index.d.mts +14 -3
- package/dist/index.d.ts +14 -3
- package/dist/index.mjs +1 -1
- package/dist/packem_shared/buildMysqlExec-PqvV5h4p.mjs +1 -0
- package/dist/packem_shared/createHyperdrive-DWZZ_3e2.mjs +1 -0
- package/dist/packem_shared/createPgVectorIndex-B2Wbzqns.mjs +1 -0
- package/dist/packem_shared/mysql-params-D4Ijlvo5.mjs +1 -0
- package/dist/packem_shared/{types.d-B2JdXSms.d.ts → types.d-Zm06MRhL.d.mts} +18 -7
- package/dist/packem_shared/{types.d-B2JdXSms.d.mts → types.d-Zm06MRhL.d.ts} +18 -7
- package/package.json +3 -3
- package/dist/packem_shared/buildMysqlExec-CjkrA5JG.mjs +0 -1
- package/dist/packem_shared/createHyperdrive-BeXs5Dr6.mjs +0 -1
- package/dist/packem_shared/createPgVectorIndex-CSNnG065.mjs +0 -1
package/README.md
CHANGED
|
@@ -101,7 +101,7 @@ import { defineApp } from "../../lunora/_generated/app";
|
|
|
101
101
|
|
|
102
102
|
const app = defineApp<Env>()
|
|
103
103
|
.shard((env) => env.SHARD)
|
|
104
|
-
.hyperdrive((env) => fromPostgresJs(postgres(createHyperdrive(env.HYPERDRIVE as HyperdriveLike).connectionString)))
|
|
104
|
+
.hyperdrive((env) => fromPostgresJs(postgres(createHyperdrive(env.HYPERDRIVE as HyperdriveLike).connectionString, { fetch_types: false, max: 5 })))
|
|
105
105
|
.build();
|
|
106
106
|
|
|
107
107
|
export const ShardDO = app.ShardDO;
|
|
@@ -157,7 +157,7 @@ Supply the driver once, on the app builder — required, or every tick records `
|
|
|
157
157
|
```ts
|
|
158
158
|
export default defineApp<Env>()
|
|
159
159
|
.shard((env) => env.SHARD)
|
|
160
|
-
.sourceClient((env, binding) => fromPostgresJs(postgres((env[binding] as { connectionString: string }).connectionString)))
|
|
160
|
+
.sourceClient((env, binding) => fromPostgresJs(postgres((env[binding] as { connectionString: string }).connectionString, { fetch_types: false, max: 5 })))
|
|
161
161
|
.build();
|
|
162
162
|
```
|
|
163
163
|
|
|
@@ -176,14 +176,17 @@ Three things to know before you build on it:
|
|
|
176
176
|
The `@lunora/hyperdrive/global` subpath is the opposite trade-off: Lunora **owns** the schema and a `.global()` table gets a real column-per-field layout on your Postgres/MySQL, with every write routed through the shared store core. Live queries stay reactive — identical to D1 — because the writer drives the same broadcast hook. Build the writer inside the Durable Object that hosts the `.global()` store and inject it as `globalDb`:
|
|
177
177
|
|
|
178
178
|
```ts
|
|
179
|
+
import { fromPostgresJs } from "@lunora/hyperdrive";
|
|
179
180
|
import { createPostgresGlobalCtxDb } from "@lunora/hyperdrive/global";
|
|
180
181
|
import postgres from "postgres";
|
|
181
182
|
|
|
182
|
-
|
|
183
|
-
const globalDb = createPostgresGlobalCtxDb(
|
|
183
|
+
const sql = postgres(env.HYPERDRIVE.connectionString, { fetch_types: false, max: 5 });
|
|
184
|
+
const globalDb = createPostgresGlobalCtxDb(fromPostgresJs(sql), { schema });
|
|
184
185
|
```
|
|
185
186
|
|
|
186
|
-
For MySQL use `createMysqlGlobalCtxDb` with a `mysql2/promise` pool created with `flags: ["FOUND_ROWS"]` — without `CLIENT_FOUND_ROWS` the affected-rows OCC guard sees changed (not matched) rows and raises spurious conflicts. Lower-level building blocks (`buildPgExec`, `buildMysqlExec`, `postgresDialect`, `mysqlDialect`, `createHyperdriveGlobalCtxDb`) are exported for custom wiring.
|
|
187
|
+
For MySQL use `createMysqlGlobalCtxDb` with a `mysql2/promise` pool created with `disableEval: true` (required on Workers) and `flags: ["FOUND_ROWS"]` — without `CLIENT_FOUND_ROWS` the affected-rows OCC guard sees changed (not matched) rows and raises spurious conflicts. Lower-level building blocks (`buildPgExec`, `buildMysqlExec`, `postgresDialect`, `mysqlDialect`, `createHyperdriveGlobalCtxDb`) are exported for custom wiring.
|
|
188
|
+
|
|
189
|
+
The Hyperdrive config behind `.global()` must have **query caching disabled** (`wrangler hyperdrive create … --caching-disabled`, or `wrangler hyperdrive update <id> --caching-disabled`). Hyperdrive caches reads for up to 75 s by default and writes don't invalidate them, so the store's OCC re-reads, change-log polls and live-query re-runs would see stale rows. `lunora deploy` sets this on the configs it creates when the schema has `.global({ backend: "hyperdrive" })` tables, and warns about an already-bound config that still caches (when Cloudflare API credentials are set).
|
|
187
190
|
|
|
188
191
|
> This README covers the basics. For the full API and the determinism/realtime rationale, see the **[documentation](https://lunora.sh/docs/packages/hyperdrive)**.
|
|
189
192
|
|
package/dist/global.d.mts
CHANGED
|
@@ -21,10 +21,16 @@
|
|
|
21
21
|
* **MySQL connections must enable `CLIENT_FOUND_ROWS`** (mysql2:
|
|
22
22
|
* `createPool({ flags: ["FOUND_ROWS"] })`) so the affected-rows OCC guard sees
|
|
23
23
|
* matched (not changed) rows — see {@link buildMysqlExec}.
|
|
24
|
+
*
|
|
25
|
+
* **The Hyperdrive config must have query caching disabled**
|
|
26
|
+
* (`wrangler hyperdrive create … --caching-disabled`). Hyperdrive caches reads by
|
|
27
|
+
* default and a write does not invalidate them, so OCC re-reads, change-log polls
|
|
28
|
+
* and live-query re-runs would read rows up to `max_age + stale_while_revalidate`
|
|
29
|
+
* old.
|
|
24
30
|
*/
|
|
25
|
-
import { DatabaseWriterLike } from '@lunora/shard-engine';
|
|
26
|
-
import { SqlExec, SqlDialect, SqlCtxDbOptions } from '@lunora/sql-store';
|
|
27
|
-
import { M as Mysql2Like, S as SqlClient } from "./packem_shared/types.d-
|
|
31
|
+
import { SchemaLike, ImportConflictMode, CdcChange, DatabaseWriterLike } from '@lunora/shard-engine';
|
|
32
|
+
import { SqlExec, SqlDialect, SqlCtxDbOptions, FacetGlobalColumnOptions, GlobalFacetResult, GlobalTableInfo, ReadGlobalTablePageOptions, GlobalTablePage, ExportRow, ImportResult } from '@lunora/sql-store';
|
|
33
|
+
import { M as Mysql2Like, S as SqlClient } from "./packem_shared/types.d-Zm06MRhL.mjs";
|
|
28
34
|
import { VectorMetric, VectorizeIndexLike } from '@lunora/platform';
|
|
29
35
|
/**
|
|
30
36
|
* Minimal row-returning client (e.g. `@lunora/hyperdrive`'s `fromPostgresJs`/`fromNodePg`
|
|
@@ -33,7 +39,7 @@ import { VectorMetric, VectorizeIndexLike } from '@lunora/platform';
|
|
|
33
39
|
*/
|
|
34
40
|
type RowClient = SqlClient;
|
|
35
41
|
/**
|
|
36
|
-
* Minimal `mysql2/promise` connection/pool surface — `
|
|
42
|
+
* Minimal `mysql2/promise` connection/pool surface — `query` resolves to
|
|
37
43
|
* `[rows | ResultSetHeader, fields]`. Aliases {@link Mysql2Like} so the /global
|
|
38
44
|
* entry's driver surface can never drift from the main entry's, widened with the
|
|
39
45
|
* (optional, structural) shape {@link assertFoundRows}'s `CLIENT_FOUND_ROWS`
|
|
@@ -60,9 +66,9 @@ type RowClient = SqlClient;
|
|
|
60
66
|
* typings, so spelling them out made a real `createPool(...)` fail to type-check
|
|
61
67
|
* (TS2345, weak-type mismatch on `config`). {@link readMysqlClientFlags} reads
|
|
62
68
|
* them through the internal `FlagProbeShape` instead, and a minimal test double
|
|
63
|
-
* (only `
|
|
69
|
+
* (only `query`) resolves to "undeterminable" rather than a `TypeError`.
|
|
64
70
|
*/
|
|
65
|
-
type
|
|
71
|
+
type Mysql2Client = Mysql2Like & {
|
|
66
72
|
config?: unknown;
|
|
67
73
|
pool?: unknown;
|
|
68
74
|
};
|
|
@@ -92,6 +98,9 @@ declare const buildPgExec: (client: RowClient) => SqlExec;
|
|
|
92
98
|
* forward verbatim. MySQL has no `RETURNING`, so `run` surfaces `affectedRows`
|
|
93
99
|
* for the store's affected-rows OCC guard.
|
|
94
100
|
*
|
|
101
|
+
* Statements go over as text with client-side escaping, so the server must not
|
|
102
|
+
* run with `NO_BACKSLASH_ESCAPES` (see {@link Mysql2Like}).
|
|
103
|
+
*
|
|
95
104
|
* **The connection MUST be created with the `CLIENT_FOUND_ROWS` flag**
|
|
96
105
|
* (mysql2: `createPool({ flags: ["FOUND_ROWS"] })`). Without it, `affectedRows`
|
|
97
106
|
* counts *changed* rows, so an idempotent `patch`/`replace` that re-writes the
|
|
@@ -100,12 +109,12 @@ declare const buildPgExec: (client: RowClient) => SqlExec;
|
|
|
100
109
|
*
|
|
101
110
|
* `batch` dispatches every statement concurrently (`Promise.all`), same
|
|
102
111
|
* rationale as {@link buildPgExec}'s `batch` — `Mysql2Like` only exposes a
|
|
103
|
-
* single-statement `
|
|
112
|
+
* single-statement `query`, so this is "spread across a pool's connections"
|
|
104
113
|
* rather than one wire-level multi-statement command; still non-atomic,
|
|
105
114
|
* at-least-once, and unordered between elements, safe only for statements
|
|
106
115
|
* with no cross-effect (what every current caller batches).
|
|
107
116
|
*/
|
|
108
|
-
declare const buildMysqlExec: (connection:
|
|
117
|
+
declare const buildMysqlExec: (connection: Mysql2Client) => SqlExec;
|
|
109
118
|
/**
|
|
110
119
|
* **Postgres** dialect. Differs from SQLite only in column types
|
|
111
120
|
* (`DOUBLE PRECISION`/`BYTEA`/`BIGSERIAL`), the `information_schema` catalog
|
|
@@ -171,8 +180,77 @@ interface CreateHyperdriveGlobalCtxDbOptions extends Omit<SqlCtxDbOptions, "dial
|
|
|
171
180
|
* D1 store options.
|
|
172
181
|
*/
|
|
173
182
|
declare const createHyperdriveGlobalCtxDb: ({ engine, exec, ...rest }: CreateHyperdriveGlobalCtxDbOptions) => DatabaseWriterLike;
|
|
183
|
+
/** Options for {@link exportHyperdriveGlobalRows}. */
|
|
184
|
+
interface ExportHyperdriveGlobalRowsOptions {
|
|
185
|
+
engine: HyperdriveEngine;
|
|
186
|
+
exec: SqlExec;
|
|
187
|
+
schema: SchemaLike;
|
|
188
|
+
/** Table allowlist; empty or absent exports every `.global()` table. */
|
|
189
|
+
tables?: ReadonlyArray<string>;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* The admin export's global plane on Hyperdrive: yield every requested
|
|
193
|
+
* `.global()` table's rows, keyset-paged. Provisions the schema's tables first,
|
|
194
|
+
* so a table that was never written exports as empty instead of failing.
|
|
195
|
+
*/
|
|
196
|
+
declare const exportHyperdriveGlobalRows: ({ engine, exec, schema, tables }: ExportHyperdriveGlobalRowsOptions) => AsyncGenerator<ExportRow, void, undefined>;
|
|
197
|
+
/** Options for {@link importHyperdriveGlobalRows}. */
|
|
198
|
+
interface ImportHyperdriveGlobalRowsOptions {
|
|
199
|
+
/** Append each insert to the global CDC log, like any other write — the app's own `cdc` setting. */
|
|
200
|
+
cdc: boolean;
|
|
201
|
+
/** Validate and report shape errors only; nothing is probed or written. */
|
|
202
|
+
dryRun?: boolean;
|
|
203
|
+
engine: HyperdriveEngine;
|
|
204
|
+
exec: SqlExec;
|
|
205
|
+
/** `"replace"` overwrites a row whose `_id` already exists; `"skip"` (the default) counts it as a conflict. */
|
|
206
|
+
onConflict?: ImportConflictMode;
|
|
207
|
+
rows: ReadonlyArray<ExportRow>;
|
|
208
|
+
schema: SchemaLike;
|
|
209
|
+
startLine?: number;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* The admin import's global plane on Hyperdrive: insert snapshot rows through
|
|
213
|
+
* the same store core `.global()` writes use. A row whose `_id` already exists
|
|
214
|
+
* is a conflict left unchanged, unless `onConflict` is `"replace"`.
|
|
215
|
+
*/
|
|
216
|
+
declare const importHyperdriveGlobalRows: ({ cdc, dryRun, engine, exec, onConflict, rows, schema, startLine }: ImportHyperdriveGlobalRowsOptions) => Promise<ImportResult>;
|
|
174
217
|
/** Convenience: a **Postgres** `.global()` writer from a row-client (postgres.js/pg over Hyperdrive). */
|
|
175
218
|
declare const createPostgresGlobalCtxDb: (client: RowClient, options: Omit<CreateHyperdriveGlobalCtxDbOptions, "engine" | "exec">) => DatabaseWriterLike;
|
|
176
219
|
/** Convenience: a **MySQL** `.global()` writer from a `mysql2/promise` connection/pool (created with `flags: ["FOUND_ROWS"]`). */
|
|
177
|
-
declare const createMysqlGlobalCtxDb: (connection:
|
|
178
|
-
|
|
220
|
+
declare const createMysqlGlobalCtxDb: (connection: Mysql2Client, options: Omit<CreateHyperdriveGlobalCtxDbOptions, "engine" | "exec">) => DatabaseWriterLike;
|
|
221
|
+
/** The studio's global data browser over a Hyperdrive database — every table in the connection's schema, internal ones hidden. */
|
|
222
|
+
interface HyperdriveGlobalIntrospector {
|
|
223
|
+
facetColumn: (options: FacetGlobalColumnOptions) => Promise<GlobalFacetResult>;
|
|
224
|
+
listTables: () => Promise<GlobalTableInfo[]>;
|
|
225
|
+
readTablePage: (options: ReadGlobalTablePageOptions) => Promise<GlobalTablePage>;
|
|
226
|
+
}
|
|
227
|
+
/** Build the studio's global-browser introspector for a Hyperdrive `.global()` store. */
|
|
228
|
+
declare const createHyperdriveGlobalIntrospector: (engine: HyperdriveEngine, exec: SqlExec, schema: SchemaLike) => HyperdriveGlobalIntrospector;
|
|
229
|
+
/**
|
|
230
|
+
* The admin CDC sync's global plane on Hyperdrive: a page of the global
|
|
231
|
+
* `__cdc_log` past `sinceSeq`. The log only exists once a CDC-enabled writer has
|
|
232
|
+
* run, so its absence is "nothing has changed yet" — an empty page that leaves
|
|
233
|
+
* the cursor where it was — not an error.
|
|
234
|
+
*/
|
|
235
|
+
declare const readHyperdriveGlobalCdc: (engine: HyperdriveEngine, exec: SqlExec, request: {
|
|
236
|
+
limit?: number;
|
|
237
|
+
now?: number;
|
|
238
|
+
sinceSeq: number;
|
|
239
|
+
}) => Promise<{
|
|
240
|
+
changes: CdcChange[];
|
|
241
|
+
cursor: number;
|
|
242
|
+
}>;
|
|
243
|
+
/**
|
|
244
|
+
* The admin point-in-time apply's global plane on Hyperdrive: replay CDC changes
|
|
245
|
+
* in order through the store core (insert, falling back to replace). The writer
|
|
246
|
+
* carries the app's `cdc` setting so the replay appends to the global changelog
|
|
247
|
+
* like any other write. Returns the number replayed.
|
|
248
|
+
*/
|
|
249
|
+
declare const applyHyperdriveGlobalCdc: (options: {
|
|
250
|
+
cdc: boolean;
|
|
251
|
+
changes: ReadonlyArray<CdcChange>;
|
|
252
|
+
engine: HyperdriveEngine;
|
|
253
|
+
exec: SqlExec;
|
|
254
|
+
schema: SchemaLike;
|
|
255
|
+
}) => Promise<number>;
|
|
256
|
+
export { CreateHyperdriveGlobalCtxDbOptions, ExportHyperdriveGlobalRowsOptions, HyperdriveEngine, HyperdriveGlobalIntrospector, ImportHyperdriveGlobalRowsOptions, type Mysql2Client, type PgVectorIndexOptions, type RowClient, applyHyperdriveGlobalCdc, buildMysqlExec, buildPgExec, createHyperdriveGlobalCtxDb, createHyperdriveGlobalIntrospector, createMysqlGlobalCtxDb, createPgVectorIndex, createPostgresGlobalCtxDb, exportHyperdriveGlobalRows, importHyperdriveGlobalRows, mysqlDialect, postgresDialect, readHyperdriveGlobalCdc };
|
package/dist/global.d.ts
CHANGED
|
@@ -21,10 +21,16 @@
|
|
|
21
21
|
* **MySQL connections must enable `CLIENT_FOUND_ROWS`** (mysql2:
|
|
22
22
|
* `createPool({ flags: ["FOUND_ROWS"] })`) so the affected-rows OCC guard sees
|
|
23
23
|
* matched (not changed) rows — see {@link buildMysqlExec}.
|
|
24
|
+
*
|
|
25
|
+
* **The Hyperdrive config must have query caching disabled**
|
|
26
|
+
* (`wrangler hyperdrive create … --caching-disabled`). Hyperdrive caches reads by
|
|
27
|
+
* default and a write does not invalidate them, so OCC re-reads, change-log polls
|
|
28
|
+
* and live-query re-runs would read rows up to `max_age + stale_while_revalidate`
|
|
29
|
+
* old.
|
|
24
30
|
*/
|
|
25
|
-
import { DatabaseWriterLike } from '@lunora/shard-engine';
|
|
26
|
-
import { SqlExec, SqlDialect, SqlCtxDbOptions } from '@lunora/sql-store';
|
|
27
|
-
import { M as Mysql2Like, S as SqlClient } from "./packem_shared/types.d-
|
|
31
|
+
import { SchemaLike, ImportConflictMode, CdcChange, DatabaseWriterLike } from '@lunora/shard-engine';
|
|
32
|
+
import { SqlExec, SqlDialect, SqlCtxDbOptions, FacetGlobalColumnOptions, GlobalFacetResult, GlobalTableInfo, ReadGlobalTablePageOptions, GlobalTablePage, ExportRow, ImportResult } from '@lunora/sql-store';
|
|
33
|
+
import { M as Mysql2Like, S as SqlClient } from "./packem_shared/types.d-Zm06MRhL.js";
|
|
28
34
|
import { VectorMetric, VectorizeIndexLike } from '@lunora/platform';
|
|
29
35
|
/**
|
|
30
36
|
* Minimal row-returning client (e.g. `@lunora/hyperdrive`'s `fromPostgresJs`/`fromNodePg`
|
|
@@ -33,7 +39,7 @@ import { VectorMetric, VectorizeIndexLike } from '@lunora/platform';
|
|
|
33
39
|
*/
|
|
34
40
|
type RowClient = SqlClient;
|
|
35
41
|
/**
|
|
36
|
-
* Minimal `mysql2/promise` connection/pool surface — `
|
|
42
|
+
* Minimal `mysql2/promise` connection/pool surface — `query` resolves to
|
|
37
43
|
* `[rows | ResultSetHeader, fields]`. Aliases {@link Mysql2Like} so the /global
|
|
38
44
|
* entry's driver surface can never drift from the main entry's, widened with the
|
|
39
45
|
* (optional, structural) shape {@link assertFoundRows}'s `CLIENT_FOUND_ROWS`
|
|
@@ -60,9 +66,9 @@ type RowClient = SqlClient;
|
|
|
60
66
|
* typings, so spelling them out made a real `createPool(...)` fail to type-check
|
|
61
67
|
* (TS2345, weak-type mismatch on `config`). {@link readMysqlClientFlags} reads
|
|
62
68
|
* them through the internal `FlagProbeShape` instead, and a minimal test double
|
|
63
|
-
* (only `
|
|
69
|
+
* (only `query`) resolves to "undeterminable" rather than a `TypeError`.
|
|
64
70
|
*/
|
|
65
|
-
type
|
|
71
|
+
type Mysql2Client = Mysql2Like & {
|
|
66
72
|
config?: unknown;
|
|
67
73
|
pool?: unknown;
|
|
68
74
|
};
|
|
@@ -92,6 +98,9 @@ declare const buildPgExec: (client: RowClient) => SqlExec;
|
|
|
92
98
|
* forward verbatim. MySQL has no `RETURNING`, so `run` surfaces `affectedRows`
|
|
93
99
|
* for the store's affected-rows OCC guard.
|
|
94
100
|
*
|
|
101
|
+
* Statements go over as text with client-side escaping, so the server must not
|
|
102
|
+
* run with `NO_BACKSLASH_ESCAPES` (see {@link Mysql2Like}).
|
|
103
|
+
*
|
|
95
104
|
* **The connection MUST be created with the `CLIENT_FOUND_ROWS` flag**
|
|
96
105
|
* (mysql2: `createPool({ flags: ["FOUND_ROWS"] })`). Without it, `affectedRows`
|
|
97
106
|
* counts *changed* rows, so an idempotent `patch`/`replace` that re-writes the
|
|
@@ -100,12 +109,12 @@ declare const buildPgExec: (client: RowClient) => SqlExec;
|
|
|
100
109
|
*
|
|
101
110
|
* `batch` dispatches every statement concurrently (`Promise.all`), same
|
|
102
111
|
* rationale as {@link buildPgExec}'s `batch` — `Mysql2Like` only exposes a
|
|
103
|
-
* single-statement `
|
|
112
|
+
* single-statement `query`, so this is "spread across a pool's connections"
|
|
104
113
|
* rather than one wire-level multi-statement command; still non-atomic,
|
|
105
114
|
* at-least-once, and unordered between elements, safe only for statements
|
|
106
115
|
* with no cross-effect (what every current caller batches).
|
|
107
116
|
*/
|
|
108
|
-
declare const buildMysqlExec: (connection:
|
|
117
|
+
declare const buildMysqlExec: (connection: Mysql2Client) => SqlExec;
|
|
109
118
|
/**
|
|
110
119
|
* **Postgres** dialect. Differs from SQLite only in column types
|
|
111
120
|
* (`DOUBLE PRECISION`/`BYTEA`/`BIGSERIAL`), the `information_schema` catalog
|
|
@@ -171,8 +180,77 @@ interface CreateHyperdriveGlobalCtxDbOptions extends Omit<SqlCtxDbOptions, "dial
|
|
|
171
180
|
* D1 store options.
|
|
172
181
|
*/
|
|
173
182
|
declare const createHyperdriveGlobalCtxDb: ({ engine, exec, ...rest }: CreateHyperdriveGlobalCtxDbOptions) => DatabaseWriterLike;
|
|
183
|
+
/** Options for {@link exportHyperdriveGlobalRows}. */
|
|
184
|
+
interface ExportHyperdriveGlobalRowsOptions {
|
|
185
|
+
engine: HyperdriveEngine;
|
|
186
|
+
exec: SqlExec;
|
|
187
|
+
schema: SchemaLike;
|
|
188
|
+
/** Table allowlist; empty or absent exports every `.global()` table. */
|
|
189
|
+
tables?: ReadonlyArray<string>;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* The admin export's global plane on Hyperdrive: yield every requested
|
|
193
|
+
* `.global()` table's rows, keyset-paged. Provisions the schema's tables first,
|
|
194
|
+
* so a table that was never written exports as empty instead of failing.
|
|
195
|
+
*/
|
|
196
|
+
declare const exportHyperdriveGlobalRows: ({ engine, exec, schema, tables }: ExportHyperdriveGlobalRowsOptions) => AsyncGenerator<ExportRow, void, undefined>;
|
|
197
|
+
/** Options for {@link importHyperdriveGlobalRows}. */
|
|
198
|
+
interface ImportHyperdriveGlobalRowsOptions {
|
|
199
|
+
/** Append each insert to the global CDC log, like any other write — the app's own `cdc` setting. */
|
|
200
|
+
cdc: boolean;
|
|
201
|
+
/** Validate and report shape errors only; nothing is probed or written. */
|
|
202
|
+
dryRun?: boolean;
|
|
203
|
+
engine: HyperdriveEngine;
|
|
204
|
+
exec: SqlExec;
|
|
205
|
+
/** `"replace"` overwrites a row whose `_id` already exists; `"skip"` (the default) counts it as a conflict. */
|
|
206
|
+
onConflict?: ImportConflictMode;
|
|
207
|
+
rows: ReadonlyArray<ExportRow>;
|
|
208
|
+
schema: SchemaLike;
|
|
209
|
+
startLine?: number;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* The admin import's global plane on Hyperdrive: insert snapshot rows through
|
|
213
|
+
* the same store core `.global()` writes use. A row whose `_id` already exists
|
|
214
|
+
* is a conflict left unchanged, unless `onConflict` is `"replace"`.
|
|
215
|
+
*/
|
|
216
|
+
declare const importHyperdriveGlobalRows: ({ cdc, dryRun, engine, exec, onConflict, rows, schema, startLine }: ImportHyperdriveGlobalRowsOptions) => Promise<ImportResult>;
|
|
174
217
|
/** Convenience: a **Postgres** `.global()` writer from a row-client (postgres.js/pg over Hyperdrive). */
|
|
175
218
|
declare const createPostgresGlobalCtxDb: (client: RowClient, options: Omit<CreateHyperdriveGlobalCtxDbOptions, "engine" | "exec">) => DatabaseWriterLike;
|
|
176
219
|
/** Convenience: a **MySQL** `.global()` writer from a `mysql2/promise` connection/pool (created with `flags: ["FOUND_ROWS"]`). */
|
|
177
|
-
declare const createMysqlGlobalCtxDb: (connection:
|
|
178
|
-
|
|
220
|
+
declare const createMysqlGlobalCtxDb: (connection: Mysql2Client, options: Omit<CreateHyperdriveGlobalCtxDbOptions, "engine" | "exec">) => DatabaseWriterLike;
|
|
221
|
+
/** The studio's global data browser over a Hyperdrive database — every table in the connection's schema, internal ones hidden. */
|
|
222
|
+
interface HyperdriveGlobalIntrospector {
|
|
223
|
+
facetColumn: (options: FacetGlobalColumnOptions) => Promise<GlobalFacetResult>;
|
|
224
|
+
listTables: () => Promise<GlobalTableInfo[]>;
|
|
225
|
+
readTablePage: (options: ReadGlobalTablePageOptions) => Promise<GlobalTablePage>;
|
|
226
|
+
}
|
|
227
|
+
/** Build the studio's global-browser introspector for a Hyperdrive `.global()` store. */
|
|
228
|
+
declare const createHyperdriveGlobalIntrospector: (engine: HyperdriveEngine, exec: SqlExec, schema: SchemaLike) => HyperdriveGlobalIntrospector;
|
|
229
|
+
/**
|
|
230
|
+
* The admin CDC sync's global plane on Hyperdrive: a page of the global
|
|
231
|
+
* `__cdc_log` past `sinceSeq`. The log only exists once a CDC-enabled writer has
|
|
232
|
+
* run, so its absence is "nothing has changed yet" — an empty page that leaves
|
|
233
|
+
* the cursor where it was — not an error.
|
|
234
|
+
*/
|
|
235
|
+
declare const readHyperdriveGlobalCdc: (engine: HyperdriveEngine, exec: SqlExec, request: {
|
|
236
|
+
limit?: number;
|
|
237
|
+
now?: number;
|
|
238
|
+
sinceSeq: number;
|
|
239
|
+
}) => Promise<{
|
|
240
|
+
changes: CdcChange[];
|
|
241
|
+
cursor: number;
|
|
242
|
+
}>;
|
|
243
|
+
/**
|
|
244
|
+
* The admin point-in-time apply's global plane on Hyperdrive: replay CDC changes
|
|
245
|
+
* in order through the store core (insert, falling back to replace). The writer
|
|
246
|
+
* carries the app's `cdc` setting so the replay appends to the global changelog
|
|
247
|
+
* like any other write. Returns the number replayed.
|
|
248
|
+
*/
|
|
249
|
+
declare const applyHyperdriveGlobalCdc: (options: {
|
|
250
|
+
cdc: boolean;
|
|
251
|
+
changes: ReadonlyArray<CdcChange>;
|
|
252
|
+
engine: HyperdriveEngine;
|
|
253
|
+
exec: SqlExec;
|
|
254
|
+
schema: SchemaLike;
|
|
255
|
+
}) => Promise<number>;
|
|
256
|
+
export { CreateHyperdriveGlobalCtxDbOptions, ExportHyperdriveGlobalRowsOptions, HyperdriveEngine, HyperdriveGlobalIntrospector, ImportHyperdriveGlobalRowsOptions, type Mysql2Client, type PgVectorIndexOptions, type RowClient, applyHyperdriveGlobalCdc, buildMysqlExec, buildPgExec, createHyperdriveGlobalCtxDb, createHyperdriveGlobalIntrospector, createMysqlGlobalCtxDb, createPgVectorIndex, createPostgresGlobalCtxDb, exportHyperdriveGlobalRows, importHyperdriveGlobalRows, mysqlDialect, postgresDialect, readHyperdriveGlobalCdc };
|
package/dist/global.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createSqlCtxDb as
|
|
1
|
+
import{applyCdcChanges as d,CDC_LOG_TABLE as b}from"@lunora/shard-engine";import{createSqlCtxDb as p,readSqlGlobalTablePage as g,listSqlGlobalTables as m,facetSqlGlobalColumn as y,exportSqlGlobalRows as C,importSqlGlobalRows as G,readSqlCdcChanges as x}from"@lunora/sql-store";import{postgresDialect as q,mysqlDialect as u}from"./packem_shared/mysqlDialect-BS1Sz60B.mjs";import{buildMysqlExec as f,buildPgExec as v}from"./packem_shared/buildMysqlExec-PqvV5h4p.mjs";import{createPgVectorIndex as A}from"./packem_shared/createPgVectorIndex-B2Wbzqns.mjs";const o=e=>e==="postgres"?q:u,c=({engine:e,exec:l,...r})=>p({...r,dialect:o(e),exec:l}),H=({engine:e,exec:l,schema:r,tables:t})=>C(l,r,o(e),t===void 0?{}:{tables:t}),P=({cdc:e,dryRun:l,engine:r,exec:t,onConflict:a,rows:s,schema:n,startLine:i})=>G(c({cdc:e,engine:r,exec:t,schema:n}),n,o(r),{dryRun:l===!0,exec:t,onConflict:a??"skip",rows:s,...i===void 0?{}:{startLine:i}}),R=(e,l)=>c({...l,engine:"postgres",exec:v(e)}),T=(e,l)=>c({...l,engine:"mysql",exec:f(e)}),E=(e,l,r)=>{const t=o(e);return{facetColumn:a=>y(l,r,t,a),listTables:()=>m(l,r,t),readTablePage:a=>g(l,r,t,a)}},I=async(e,l,r)=>{const t=o(e),{params:a,sql:s}=t.render(t.tableExists(b));return(await l.all(s,a)).length===0?{changes:[],cursor:r.sinceSeq}:x(l,r,t)},M=async e=>(await d(c({cdc:e.cdc,engine:e.engine,exec:e.exec,schema:e.schema}),e.changes),e.changes.length);export{M as applyHyperdriveGlobalCdc,f as buildMysqlExec,v as buildPgExec,c as createHyperdriveGlobalCtxDb,E as createHyperdriveGlobalIntrospector,T as createMysqlGlobalCtxDb,A as createPgVectorIndex,R as createPostgresGlobalCtxDb,H as exportHyperdriveGlobalRows,P as importHyperdriveGlobalRows,u as mysqlDialect,q as postgresDialect,I as readHyperdriveGlobalCdc};
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { H as HyperdriveLike, a as HyperdriveConnection, M as Mysql2Like, S as SqlClient, N as NodePgLike, P as PostgresJsLike } from "./packem_shared/types.d-
|
|
1
|
+
import { H as HyperdriveLike, a as HyperdriveConnection, M as Mysql2Like, S as SqlClient, N as NodePgLike, P as PostgresJsLike } from "./packem_shared/types.d-Zm06MRhL.mjs";
|
|
2
2
|
/**
|
|
3
3
|
* Surface a Cloudflare Hyperdrive binding as a connection ready to feed a
|
|
4
4
|
* user-supplied SQL driver.
|
|
@@ -18,7 +18,11 @@ import { H as HyperdriveLike, a as HyperdriveConnection, M as Mysql2Like, S as S
|
|
|
18
18
|
* // not per request. `ctx.sql` is emitted `readonly`; it is never assigned.
|
|
19
19
|
* const app = defineApp<Env>()
|
|
20
20
|
* .shard((env) => env.SHARD)
|
|
21
|
-
* .hyperdrive((env) =>
|
|
21
|
+
* .hyperdrive((env) => {
|
|
22
|
+
* const { connectionString } = createHyperdrive(env.HYPERDRIVE);
|
|
23
|
+
*
|
|
24
|
+
* return fromPostgresJs(postgres(connectionString, { fetch_types: false, max: 5 }));
|
|
25
|
+
* })
|
|
22
26
|
* .build();
|
|
23
27
|
*
|
|
24
28
|
* // Then, inside an action (never a query/mutation):
|
|
@@ -45,6 +49,13 @@ declare const createHyperdrive: (binding: HyperdriveLike) => {
|
|
|
45
49
|
* Uses the driver's `.unsafe(text, params)` escape hatch so the caller supplies
|
|
46
50
|
* a plain SQL string with `$1, $2, …` placeholders and a positional params
|
|
47
51
|
* array. postgres.js's `.unsafe` resolves to a row array.
|
|
52
|
+
*
|
|
53
|
+
* `prepare: true` because `.unsafe` defaults to `prepare: false`, which
|
|
54
|
+
* Cloudflare warns against: under Hyperdrive's transaction pooling unnamed
|
|
55
|
+
* statements can hang or fail intermittently, and each one costs a Describe
|
|
56
|
+
* round trip before it executes. A client built with `prepare: false` still
|
|
57
|
+
* wins. Prepared reads are cacheable by Hyperdrive, so a `.global()` store's
|
|
58
|
+
* config must have caching disabled (see `@lunora/hyperdrive/global`).
|
|
48
59
|
*/
|
|
49
60
|
declare const fromPostgresJs: (client: PostgresJsLike) => SqlClient;
|
|
50
61
|
/**
|
|
@@ -56,7 +67,7 @@ declare const fromNodePg: (client: NodePgLike) => SqlClient;
|
|
|
56
67
|
/**
|
|
57
68
|
* Wrap a `mysql2/promise` connection or pool as a {@link SqlClient}.
|
|
58
69
|
*
|
|
59
|
-
* Use `?` placeholders (MySQL positional syntax). `mysql2`'s `
|
|
70
|
+
* Use `?` placeholders (MySQL positional syntax). `mysql2`'s `query` resolves
|
|
60
71
|
* to a `[rows, fields]` tuple; the adapter returns the first element. For a
|
|
61
72
|
* non-`SELECT` (DML), `mysql2` yields a `ResultSetHeader` object rather than a
|
|
62
73
|
* row array, so the adapter normalises that to `[]` — matching the empty-array
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { H as HyperdriveLike, a as HyperdriveConnection, M as Mysql2Like, S as SqlClient, N as NodePgLike, P as PostgresJsLike } from "./packem_shared/types.d-
|
|
1
|
+
import { H as HyperdriveLike, a as HyperdriveConnection, M as Mysql2Like, S as SqlClient, N as NodePgLike, P as PostgresJsLike } from "./packem_shared/types.d-Zm06MRhL.js";
|
|
2
2
|
/**
|
|
3
3
|
* Surface a Cloudflare Hyperdrive binding as a connection ready to feed a
|
|
4
4
|
* user-supplied SQL driver.
|
|
@@ -18,7 +18,11 @@ import { H as HyperdriveLike, a as HyperdriveConnection, M as Mysql2Like, S as S
|
|
|
18
18
|
* // not per request. `ctx.sql` is emitted `readonly`; it is never assigned.
|
|
19
19
|
* const app = defineApp<Env>()
|
|
20
20
|
* .shard((env) => env.SHARD)
|
|
21
|
-
* .hyperdrive((env) =>
|
|
21
|
+
* .hyperdrive((env) => {
|
|
22
|
+
* const { connectionString } = createHyperdrive(env.HYPERDRIVE);
|
|
23
|
+
*
|
|
24
|
+
* return fromPostgresJs(postgres(connectionString, { fetch_types: false, max: 5 }));
|
|
25
|
+
* })
|
|
22
26
|
* .build();
|
|
23
27
|
*
|
|
24
28
|
* // Then, inside an action (never a query/mutation):
|
|
@@ -45,6 +49,13 @@ declare const createHyperdrive: (binding: HyperdriveLike) => {
|
|
|
45
49
|
* Uses the driver's `.unsafe(text, params)` escape hatch so the caller supplies
|
|
46
50
|
* a plain SQL string with `$1, $2, …` placeholders and a positional params
|
|
47
51
|
* array. postgres.js's `.unsafe` resolves to a row array.
|
|
52
|
+
*
|
|
53
|
+
* `prepare: true` because `.unsafe` defaults to `prepare: false`, which
|
|
54
|
+
* Cloudflare warns against: under Hyperdrive's transaction pooling unnamed
|
|
55
|
+
* statements can hang or fail intermittently, and each one costs a Describe
|
|
56
|
+
* round trip before it executes. A client built with `prepare: false` still
|
|
57
|
+
* wins. Prepared reads are cacheable by Hyperdrive, so a `.global()` store's
|
|
58
|
+
* config must have caching disabled (see `@lunora/hyperdrive/global`).
|
|
48
59
|
*/
|
|
49
60
|
declare const fromPostgresJs: (client: PostgresJsLike) => SqlClient;
|
|
50
61
|
/**
|
|
@@ -56,7 +67,7 @@ declare const fromNodePg: (client: NodePgLike) => SqlClient;
|
|
|
56
67
|
/**
|
|
57
68
|
* Wrap a `mysql2/promise` connection or pool as a {@link SqlClient}.
|
|
58
69
|
*
|
|
59
|
-
* Use `?` placeholders (MySQL positional syntax). `mysql2`'s `
|
|
70
|
+
* Use `?` placeholders (MySQL positional syntax). `mysql2`'s `query` resolves
|
|
60
71
|
* to a `[rows, fields]` tuple; the adapter returns the first element. For a
|
|
61
72
|
* non-`SELECT` (DML), `mysql2` yields a `ResultSetHeader` object rather than a
|
|
62
73
|
* row array, so the adapter normalises that to `[]` — matching the empty-array
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createHyperdrive as e,fromMysql2 as f,fromNodePg as m,fromPostgresJs as p}from"./packem_shared/createHyperdrive-
|
|
1
|
+
import{createHyperdrive as e,fromMysql2 as f,fromNodePg as m,fromPostgresJs as p}from"./packem_shared/createHyperdrive-DWZZ_3e2.mjs";import{projectSourceRow as t,pullSourceRows as c}from"./packem_shared/projectSourceRow-DN8ywPRl.mjs";export{e as createHyperdrive,f as fromMysql2,m as fromNodePg,p as fromPostgresJs,t as projectSourceRow,c as pullSourceRows};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{LunoraError as n}from"@lunora/errors";import{t as o}from"./mysql-params-D4Ijlvo5.mjs";const i=2,s=new WeakSet,c=t=>{const e=t,r=e.config?.clientFlags;if(typeof r=="number")return r;const a=e.pool?.config?.connectionConfig?.clientFlags;return typeof a=="number"?a:void 0},l=t=>{const e=c(t);if(e===void 0){if(s.has(t))return;s.add(t),console.warn('@lunora/hyperdrive: could not determine whether this MySQL connection negotiated CLIENT_FOUND_ROWS (the driver exposes no flag introspection). If it was NOT created with the `CLIENT_FOUND_ROWS` flag (mysql2: `createPool({ flags: ["FOUND_ROWS"] })`), an idempotent patch/replace that rewrites identical values may report 0 affectedRows and raise a spurious OCC ConflictError.');return}if((e&i)===0)throw new n("INTERNAL",'@lunora/hyperdrive: this MySQL connection was not created with the `CLIENT_FOUND_ROWS` flag (mysql2: `createPool({ flags: ["FOUND_ROWS"] })`). Without it, `affectedRows` counts changed (not matched) rows, so an idempotent patch/replace that rewrites identical values reports 0 and the OCC guard raises a spurious conflict.')},f=t=>({all:(e,r)=>t.query(e,r),batch:async e=>{await Promise.all(e.map(r=>t.query(r.sql,r.params)))},run:async(e,r)=>(await t.query(e,r),{rowsAffected:0})}),p=t=>(l(t),{all:async(e,r)=>{const[a]=await t.query(e,o(r));return Array.isArray(a)?a:[]},batch:async e=>{await Promise.all(e.map(r=>t.query(r.sql,o(r.params))))},run:async(e,r)=>{const[a]=await t.query(e,o(r));return{rowsAffected:a.affectedRows??0}}});export{p as buildMysqlExec,f as buildPgExec};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{t as o}from"./mysql-params-D4Ijlvo5.mjs";const n=r=>({config:{database:r.database,host:r.host,password:r.password,port:r.port,user:r.user},connectionString:r.connectionString}),c=r=>({dbSystem:"postgresql",query:async(s,t=[])=>await r.unsafe(s,t,{prepare:!0})}),u=r=>({dbSystem:"postgresql",query:async(s,t=[])=>(await r.query(s,t)).rows}),y=r=>({dbSystem:"mysql",query:async(s,t=[])=>{const[e]=await r.query(s,o(t));return Array.isArray(e)?e:[]}});export{n as createHyperdrive,y as fromMysql2,u as fromNodePg,c as fromPostgresJs};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const F=t=>/^[A-Z_]\w*$/i.test(t);const X=new Set(["42P06","42P07","23505","42710"]),J=t=>typeof t=="object"&&t!==null&&"code"in t&&typeof t.code=="string"&&X.has(t.code),P=5,j="__vec_",v="__ann_",b={cosine:{operator:"<=>",operatorClass:"vector_cosine_ops",score:t=>1-t},"dot-product":{operator:"<#>",operatorClass:"vector_ip_ops",score:t=>t},euclidean:{operator:"<->",operatorClass:"vector_l2_ops",score:t=>t}},U=63,A=U-j.length-v.length-Math.max(...Object.values(b).map(t=>t.operatorClass.length)),C="SELECT jsonb_array_elements_text($1::text::jsonb)",R=t=>`[${[...t].join(",")}]`,D=t=>{const c=Object.getPrototypeOf(t);return c===null||c===Object.prototype},L=t=>t.constructor?.name??"non-plain object",B=(t,c)=>{const o=(e,r)=>{throw new TypeError(`@lunora/hyperdrive: pgvector index "${c}" supports equality metadata filters only, but "${r}" ${e}. Pre-compute it into an equality-shaped metadata field, or keep this index on Vectorize.`)},E=(e,r)=>{(e===void 0||typeof e=="function"||typeof e=="symbol")&&o(`has a value at "${r}" that JSON cannot represent, which would widen the filter to match every row`,r),typeof e=="number"&&!Number.isFinite(e)&&o(`has a non-finite number at "${r}", which JSON turns into null`,r),typeof e=="bigint"&&o(`has a bigint at "${r}", which JSON cannot serialise`,r)},m=(e,r)=>{if(E(e,r),Array.isArray(e)){for(const[p,l]of e.entries())m(l,`${r}[${String(p)}]`);return}if(!(typeof e!="object"||e===null)){D(e)||o(`is a ${L(e)}, whose fields JSON does not preserve`,r);for(const[p,l]of Object.entries(e))p.startsWith("$")&&o("uses a comparison operator",r),m(l,`${r}.${p}`)}};D(t)||o(`is a ${L(t)}, whose fields JSON does not preserve`,"filter");for(const[e,r]of Object.entries(t))e.startsWith("$")&&o("uses a comparison operator",e),e.includes(".")&&o("uses Vectorize's dot-addressed nested syntax, which JSONB containment cannot express",e),m(r,e)},H=t=>{if(!F(t))throw new TypeError(`@lunora/hyperdrive: pgvector \`name\` must be a bare SQL identifier (letters, digits, underscore; not starting with a digit) — got "${t}"`);if(t.length>A)throw new TypeError(`@lunora/hyperdrive: pgvector \`name\` must be at most ${String(A)} characters — got "${t}" (${String(t.length)}). Postgres truncates identifiers at 63 bytes and this index derives longer names from it.`);return t},V=t=>{const{client:c,dimensions:o,metric:E="cosine",name:m}=t,e=`${j}${H(m)}`;if(!Object.hasOwn(b,E))throw new TypeError(`@lunora/hyperdrive: pgvector index "${m}" got an unknown metric "${E}" — expected cosine, euclidean, or dot-product`);const{operator:r,operatorClass:p,score:l}=b[E];if(!Number.isInteger(o)||o<=0)throw new TypeError(`@lunora/hyperdrive: pgvector index "${m}" needs a positive integer \`dimensions\` — got ${String(o)}`);if(o>2e3)throw new TypeError(`@lunora/hyperdrive: pgvector index "${m}" declares ${String(o)} dimensions, over the HNSW limit of ${String(2e3)}. Reduce the embedding width (Vectorize itself tops out at 1536), or keep this index on Vectorize.`);let O=0;const N=()=>(O+=1,`${e}:${String(O)}`);let T;const $=async()=>(T??=(async()=>{for(const n of["CREATE EXTENSION IF NOT EXISTS vector",`CREATE TABLE IF NOT EXISTS "${e}" (id text PRIMARY KEY, namespace text, embedding vector(${String(o)}) NOT NULL, metadata jsonb)`,`CREATE INDEX IF NOT EXISTS "${e}${v}${p}" ON "${e}" USING hnsw (embedding ${p})`,`CREATE INDEX IF NOT EXISTS "${e}__ns" ON "${e}" (namespace)`,`CREATE INDEX IF NOT EXISTS "${e}__meta" ON "${e}" USING gin (metadata)`])try{await c.query(n)}catch(a){if(!J(a))throw a}})().catch(n=>{throw T=void 0,n}),T),w=async(n,a)=>{if(n.length===0)return{mutationId:N()};await $();const s=[...new Map(n.map(i=>[i.id,i])).values()],g=[],u=i=>`$${String(g.push(i))}`,h=s.map(i=>`(${u(i.id)}, ${u(i.namespace??null)}, ${u(R(i.values))}::vector, ${u(i.metadata===void 0?null:JSON.stringify(i.metadata))}::text::jsonb)`);return await c.query(`INSERT INTO "${e}" (id, namespace, embedding, metadata) VALUES ${h.join(", ")} ${a}`,g),{mutationId:N()}};return{async deleteByIds(n){return n.length===0?{count:0,mutationId:N()}:(await $(),{count:(await c.query(`DELETE FROM "${e}" WHERE id IN (${C}) RETURNING id`,[JSON.stringify(n)])).length,mutationId:N()})},async describe(){await $();const[n]=await c.query(`SELECT count(*) AS count FROM "${e}"`);return{dimensions:o,vectorsCount:Number(n?.count??0)}},async getByIds(n){return n.length===0?[]:(await $(),(await c.query(`SELECT id, namespace, metadata, embedding::text AS embedding FROM "${e}" WHERE id IN (${C})`,[JSON.stringify(n)])).map(s=>({id:s.id,...s.metadata===null?{}:{metadata:s.metadata},...s.namespace===null?{}:{namespace:s.namespace},values:JSON.parse(s.embedding)})))},async insert(n){return w(n,"ON CONFLICT (id) DO NOTHING")},async query(n,a){const s=a?.topK??P;if(!Number.isInteger(s)||s<=0)throw new TypeError(`@lunora/hyperdrive: pgvector index "${m}" needs a positive integer \`topK\` — got ${String(s)}`);a?.filter!==void 0&&B(a.filter,m),await $();const g=a?.returnValues===!0,u=a?.returnMetadata??"none",h=u!=="none"&&u!==!1,i=[],f=d=>`$${String(i.push(d))}`,y=`embedding ${r} ${f(R(n))}::vector`,S=[];a?.namespace!==void 0&&S.push(`namespace = ${f(a.namespace)}`),a?.filter!==void 0&&Object.keys(a.filter).length>0&&S.push(`metadata @> ${f(JSON.stringify(a.filter))}::text::jsonb`);const I=["id","namespace",`${y} AS distance`];h&&I.push("metadata"),g&&I.push("embedding::text AS embedding");const M=S.length>0?`(SELECT * FROM "${e}" WHERE ${S.join(" AND ")})`:`"${e}"`,x=S.length>0?`WITH filtered AS MATERIALIZED ${M} SELECT ${I.join(", ")} FROM filtered ORDER BY ${y} LIMIT ${f(s)}`:`SELECT ${I.join(", ")} FROM "${e}" ORDER BY ${y} LIMIT ${f(s)}`,_=(await c.query(x,i)).map(d=>({id:d.id,...h&&d.metadata!==null&&d.metadata!==void 0?{metadata:d.metadata}:{},...d.namespace===null?{}:{namespace:d.namespace},score:l(Number(d.distance)),...g&&d.embedding!==void 0?{values:JSON.parse(d.embedding)}:{}}));return{count:_.length,matches:_}},async upsert(n){return w(n,"ON CONFLICT (id) DO UPDATE SET embedding = EXCLUDED.embedding, namespace = EXCLUDED.namespace, metadata = EXCLUDED.metadata")}}};export{V as createPgVectorIndex};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const n=t=>({toSqlString:()=>`'${t.replaceAll("\\","\\\\").replaceAll("'","''")}'`,toString:()=>t}),e=t=>t.map((r,i)=>{if(r===void 0)throw new TypeError(`@lunora/hyperdrive: MySQL bind parameter ${String(i+1)} is undefined`);return typeof r=="string"?n(r):r===null||typeof r!="object"||r instanceof Date||ArrayBuffer.isView(r)?r:r instanceof ArrayBuffer?new Uint8Array(r):n(JSON.stringify(r))});export{e as t};
|
|
@@ -125,24 +125,35 @@ interface NodePgLike {
|
|
|
125
125
|
* direction, which is all bivariance asks for.
|
|
126
126
|
*/
|
|
127
127
|
interface PostgresJsLike {
|
|
128
|
-
unsafe(this: void, text: string, params?: ReadonlyArray<unknown
|
|
128
|
+
unsafe(this: void, text: string, params?: ReadonlyArray<unknown>, options?: {
|
|
129
|
+
prepare?: boolean;
|
|
130
|
+
}): Promise<unknown>;
|
|
129
131
|
}
|
|
130
132
|
/**
|
|
131
133
|
* Structural projection of a `mysql2/promise` connection/pool. `mysql2`'s
|
|
132
|
-
* `
|
|
134
|
+
* `query` resolves to a `[rows, fields]` tuple; the adapter takes the first
|
|
133
135
|
* element as the rows.
|
|
134
136
|
*
|
|
137
|
+
* `query`, not `execute`: `execute` is a protocol-level prepared statement
|
|
138
|
+
* (`COM_STMT_PREPARE` + the binary protocol), which Hyperdrive does not support
|
|
139
|
+
* for MySQL. `query` interpolates the `?` placeholders client-side and sends
|
|
140
|
+
* plain text. Strings are quoted with `''` for a quote and `\\` for a
|
|
141
|
+
* backslash, so a server whose `sql_mode` includes `NO_BACKSLASH_ESCAPES` is
|
|
142
|
+
* unsupported: there a backslash is stored twice. No value can end its literal
|
|
143
|
+
* under either mode. mysql2 skips a `?` inside a quoted string or comment, but
|
|
144
|
+
* reads `??` as an identifier placeholder.
|
|
145
|
+
*
|
|
135
146
|
* Method syntax, not an arrow property — see {@link PostgresJsLike} — but here
|
|
136
147
|
* that is necessary and NOT sufficient, which is why `params` is `unknown`.
|
|
137
148
|
*
|
|
138
|
-
* mysql2 types its second argument as a single `
|
|
139
|
-
* scalars, `
|
|
149
|
+
* mysql2 types its second argument as a single `QueryValues`: a union of
|
|
150
|
+
* scalars, `QueryValues[]` and `{ [key: string]: QueryValues }`. Declared
|
|
140
151
|
* `params?: ReadonlyArray<unknown>` this projection could never accept the
|
|
141
152
|
* driver it projects — bivariance needs the two parameter types to relate in
|
|
142
153
|
* ONE direction and they relate in neither (`readonly unknown[] | undefined` is
|
|
143
|
-
* not
|
|
154
|
+
* not a `QueryValues`, and a `string` is not an array).
|
|
144
155
|
*
|
|
145
|
-
* `unknown` is the narrowest supertype of `
|
|
156
|
+
* `unknown` is the narrowest supertype of `QueryValues` that does not restate
|
|
146
157
|
* mysql2's union here. Restating it would be the vacuous shim all over again —
|
|
147
158
|
* a local copy that drifts from the package it mirrors and makes the gate pass
|
|
148
159
|
* while a consumer fails.
|
|
@@ -154,6 +165,6 @@ interface PostgresJsLike {
|
|
|
154
165
|
* values at runtime regardless.
|
|
155
166
|
*/
|
|
156
167
|
interface Mysql2Like {
|
|
157
|
-
|
|
168
|
+
query(this: void, text: string, params?: unknown): Promise<[unknown, unknown]>;
|
|
158
169
|
}
|
|
159
170
|
export { HyperdriveLike as H, Mysql2Like as M, NodePgLike as N, PostgresJsLike as P, SqlClient as S, HyperdriveConnection as a };
|
|
@@ -125,24 +125,35 @@ interface NodePgLike {
|
|
|
125
125
|
* direction, which is all bivariance asks for.
|
|
126
126
|
*/
|
|
127
127
|
interface PostgresJsLike {
|
|
128
|
-
unsafe(this: void, text: string, params?: ReadonlyArray<unknown
|
|
128
|
+
unsafe(this: void, text: string, params?: ReadonlyArray<unknown>, options?: {
|
|
129
|
+
prepare?: boolean;
|
|
130
|
+
}): Promise<unknown>;
|
|
129
131
|
}
|
|
130
132
|
/**
|
|
131
133
|
* Structural projection of a `mysql2/promise` connection/pool. `mysql2`'s
|
|
132
|
-
* `
|
|
134
|
+
* `query` resolves to a `[rows, fields]` tuple; the adapter takes the first
|
|
133
135
|
* element as the rows.
|
|
134
136
|
*
|
|
137
|
+
* `query`, not `execute`: `execute` is a protocol-level prepared statement
|
|
138
|
+
* (`COM_STMT_PREPARE` + the binary protocol), which Hyperdrive does not support
|
|
139
|
+
* for MySQL. `query` interpolates the `?` placeholders client-side and sends
|
|
140
|
+
* plain text. Strings are quoted with `''` for a quote and `\\` for a
|
|
141
|
+
* backslash, so a server whose `sql_mode` includes `NO_BACKSLASH_ESCAPES` is
|
|
142
|
+
* unsupported: there a backslash is stored twice. No value can end its literal
|
|
143
|
+
* under either mode. mysql2 skips a `?` inside a quoted string or comment, but
|
|
144
|
+
* reads `??` as an identifier placeholder.
|
|
145
|
+
*
|
|
135
146
|
* Method syntax, not an arrow property — see {@link PostgresJsLike} — but here
|
|
136
147
|
* that is necessary and NOT sufficient, which is why `params` is `unknown`.
|
|
137
148
|
*
|
|
138
|
-
* mysql2 types its second argument as a single `
|
|
139
|
-
* scalars, `
|
|
149
|
+
* mysql2 types its second argument as a single `QueryValues`: a union of
|
|
150
|
+
* scalars, `QueryValues[]` and `{ [key: string]: QueryValues }`. Declared
|
|
140
151
|
* `params?: ReadonlyArray<unknown>` this projection could never accept the
|
|
141
152
|
* driver it projects — bivariance needs the two parameter types to relate in
|
|
142
153
|
* ONE direction and they relate in neither (`readonly unknown[] | undefined` is
|
|
143
|
-
* not
|
|
154
|
+
* not a `QueryValues`, and a `string` is not an array).
|
|
144
155
|
*
|
|
145
|
-
* `unknown` is the narrowest supertype of `
|
|
156
|
+
* `unknown` is the narrowest supertype of `QueryValues` that does not restate
|
|
146
157
|
* mysql2's union here. Restating it would be the vacuous shim all over again —
|
|
147
158
|
* a local copy that drifts from the package it mirrors and makes the gate pass
|
|
148
159
|
* while a consumer fails.
|
|
@@ -154,6 +165,6 @@ interface PostgresJsLike {
|
|
|
154
165
|
* values at runtime regardless.
|
|
155
166
|
*/
|
|
156
167
|
interface Mysql2Like {
|
|
157
|
-
|
|
168
|
+
query(this: void, text: string, params?: unknown): Promise<[unknown, unknown]>;
|
|
158
169
|
}
|
|
159
170
|
export { HyperdriveLike as H, Mysql2Like as M, NodePgLike as N, PostgresJsLike as P, SqlClient as S, HyperdriveConnection as a };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/hyperdrive",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.213",
|
|
4
4
|
"description": "Bring-your-own Postgres/MySQL for Lunora via Cloudflare Hyperdrive: a driver-agnostic, action-only ctx.sql",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare",
|
|
@@ -52,8 +52,8 @@
|
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"@lunora/errors": ">=1.0.0-alpha.51 <2.0.0-0",
|
|
54
54
|
"@lunora/platform": "1.0.0-alpha.75",
|
|
55
|
-
"@lunora/shard-engine": "1.0.0-alpha.
|
|
56
|
-
"@lunora/sql-store": "1.0.0-alpha.
|
|
55
|
+
"@lunora/shard-engine": "1.0.0-alpha.167",
|
|
56
|
+
"@lunora/sql-store": "1.0.0-alpha.216",
|
|
57
57
|
"drizzle-orm": "1.0.0-rc.5"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{LunoraError as s}from"@lunora/errors";const n=2,o=new WeakSet,i=t=>{const e=t,r=e.config?.clientFlags;if(typeof r=="number")return r;const a=e.pool?.config?.connectionConfig?.clientFlags;return typeof a=="number"?a:void 0},c=t=>{const e=i(t);if(e===void 0){if(o.has(t))return;o.add(t),console.warn('@lunora/hyperdrive: could not determine whether this MySQL connection negotiated CLIENT_FOUND_ROWS (the driver exposes no flag introspection). If it was NOT created with the `CLIENT_FOUND_ROWS` flag (mysql2: `createPool({ flags: ["FOUND_ROWS"] })`), an idempotent patch/replace that rewrites identical values may report 0 affectedRows and raise a spurious OCC ConflictError.');return}if((e&n)===0)throw new s("INTERNAL",'@lunora/hyperdrive: this MySQL connection was not created with the `CLIENT_FOUND_ROWS` flag (mysql2: `createPool({ flags: ["FOUND_ROWS"] })`). Without it, `affectedRows` counts changed (not matched) rows, so an idempotent patch/replace that rewrites identical values reports 0 and the OCC guard raises a spurious conflict.')},u=t=>({all:(e,r)=>t.query(e,r),batch:async e=>{await Promise.all(e.map(r=>t.query(r.sql,r.params)))},run:async(e,r)=>(await t.query(e,r),{rowsAffected:0})}),d=t=>(c(t),{all:async(e,r)=>{const[a]=await t.execute(e,r);return Array.isArray(a)?a:[]},batch:async e=>{await Promise.all(e.map(r=>t.execute(r.sql,r.params)))},run:async(e,r)=>{const[a]=await t.execute(e,r);return{rowsAffected:a.affectedRows??0}}});export{d as buildMysqlExec,u as buildPgExec};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
const o=r=>({config:{database:r.database,host:r.host,password:r.password,port:r.port,user:r.user},connectionString:r.connectionString}),a=r=>({dbSystem:"postgresql",query:async(s,t=[])=>await r.unsafe(s,t)}),c=r=>({dbSystem:"postgresql",query:async(s,t=[])=>(await r.query(s,t)).rows}),n=r=>({dbSystem:"mysql",query:async(s,t=[])=>{const[e]=await r.execute(s,t);return Array.isArray(e)?e:[]}});export{o as createHyperdrive,n as fromMysql2,c as fromNodePg,a as fromPostgresJs};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
const x=t=>/^[A-Z_]\w*$/i.test(t);const F=new Set(["42P06","42P07","23505","42710"]),X=t=>typeof t=="object"&&t!==null&&"code"in t&&typeof t.code=="string"&&F.has(t.code),P=5,v="__vec_",L="__ann_",y={cosine:{operator:"<=>",operatorClass:"vector_cosine_ops",score:t=>1-t},"dot-product":{operator:"<#>",operatorClass:"vector_ip_ops",score:t=>t},euclidean:{operator:"<->",operatorClass:"vector_l2_ops",score:t=>t}},J=63,A=J-v.length-L.length-Math.max(...Object.values(y).map(t=>t.operatorClass.length)),C=t=>`[${[...t].join(",")}]`,R=t=>{const c=Object.getPrototypeOf(t);return c===null||c===Object.prototype},D=t=>t.constructor?.name??"non-plain object",U=(t,c)=>{const o=(e,r)=>{throw new TypeError(`@lunora/hyperdrive: pgvector index "${c}" supports equality metadata filters only, but "${r}" ${e}. Pre-compute it into an equality-shaped metadata field, or keep this index on Vectorize.`)},l=(e,r)=>{(e===void 0||typeof e=="function"||typeof e=="symbol")&&o(`has a value at "${r}" that JSON cannot represent, which would widen the filter to match every row`,r),typeof e=="number"&&!Number.isFinite(e)&&o(`has a non-finite number at "${r}", which JSON turns into null`,r),typeof e=="bigint"&&o(`has a bigint at "${r}", which JSON cannot serialise`,r)},m=(e,r)=>{if(l(e,r),Array.isArray(e)){for(const[p,E]of e.entries())m(E,`${r}[${String(p)}]`);return}if(!(typeof e!="object"||e===null)){R(e)||o(`is a ${D(e)}, whose fields JSON does not preserve`,r);for(const[p,E]of Object.entries(e))p.startsWith("$")&&o("uses a comparison operator",r),m(E,`${r}.${p}`)}};R(t)||o(`is a ${D(t)}, whose fields JSON does not preserve`,"filter");for(const[e,r]of Object.entries(t))e.startsWith("$")&&o("uses a comparison operator",e),e.includes(".")&&o("uses Vectorize's dot-addressed nested syntax, which JSONB containment cannot express",e),m(r,e)},B=t=>{if(!x(t))throw new TypeError(`@lunora/hyperdrive: pgvector \`name\` must be a bare SQL identifier (letters, digits, underscore; not starting with a digit) — got "${t}"`);if(t.length>A)throw new TypeError(`@lunora/hyperdrive: pgvector \`name\` must be at most ${String(A)} characters — got "${t}" (${String(t.length)}). Postgres truncates identifiers at 63 bytes and this index derives longer names from it.`);return t},W=t=>{const{client:c,dimensions:o,metric:l="cosine",name:m}=t,e=`${v}${B(m)}`;if(!Object.hasOwn(y,l))throw new TypeError(`@lunora/hyperdrive: pgvector index "${m}" got an unknown metric "${l}" — expected cosine, euclidean, or dot-product`);const{operator:r,operatorClass:p,score:E}=y[l];if(!Number.isInteger(o)||o<=0)throw new TypeError(`@lunora/hyperdrive: pgvector index "${m}" needs a positive integer \`dimensions\` — got ${String(o)}`);if(o>2e3)throw new TypeError(`@lunora/hyperdrive: pgvector index "${m}" declares ${String(o)} dimensions, over the HNSW limit of ${String(2e3)}. Reduce the embedding width (Vectorize itself tops out at 1536), or keep this index on Vectorize.`);let O=0;const N=()=>(O+=1,`${e}:${String(O)}`);let T;const $=async()=>(T??=(async()=>{for(const n of["CREATE EXTENSION IF NOT EXISTS vector",`CREATE TABLE IF NOT EXISTS "${e}" (id text PRIMARY KEY, namespace text, embedding vector(${String(o)}) NOT NULL, metadata jsonb)`,`CREATE INDEX IF NOT EXISTS "${e}${L}${p}" ON "${e}" USING hnsw (embedding ${p})`,`CREATE INDEX IF NOT EXISTS "${e}__ns" ON "${e}" (namespace)`,`CREATE INDEX IF NOT EXISTS "${e}__meta" ON "${e}" USING gin (metadata)`])try{await c.query(n)}catch(a){if(!X(a))throw a}})().catch(n=>{throw T=void 0,n}),T),w=async(n,a)=>{if(n.length===0)return{mutationId:N()};await $();const s=[...new Map(n.map(i=>[i.id,i])).values()],g=[],u=i=>`$${String(g.push(i))}`,h=s.map(i=>`(${u(i.id)}, ${u(i.namespace??null)}, ${u(C(i.values))}::vector, ${u(i.metadata===void 0?null:JSON.stringify(i.metadata))}::jsonb)`);return await c.query(`INSERT INTO "${e}" (id, namespace, embedding, metadata) VALUES ${h.join(", ")} ${a}`,g),{mutationId:N()}};return{async deleteByIds(n){return n.length===0?{count:0,mutationId:N()}:(await $(),{count:(await c.query(`DELETE FROM "${e}" WHERE id = ANY($1::text[]) RETURNING id`,[[...n]])).length,mutationId:N()})},async describe(){await $();const[n]=await c.query(`SELECT count(*) AS count FROM "${e}"`);return{dimensions:o,vectorsCount:Number(n?.count??0)}},async getByIds(n){return n.length===0?[]:(await $(),(await c.query(`SELECT id, namespace, metadata, embedding::text AS embedding FROM "${e}" WHERE id = ANY($1::text[])`,[[...n]])).map(s=>({id:s.id,...s.metadata===null?{}:{metadata:s.metadata},...s.namespace===null?{}:{namespace:s.namespace},values:JSON.parse(s.embedding)})))},async insert(n){return w(n,"ON CONFLICT (id) DO NOTHING")},async query(n,a){const s=a?.topK??P;if(!Number.isInteger(s)||s<=0)throw new TypeError(`@lunora/hyperdrive: pgvector index "${m}" needs a positive integer \`topK\` — got ${String(s)}`);a?.filter!==void 0&&U(a.filter,m),await $();const g=a?.returnValues===!0,u=a?.returnMetadata??"none",h=u!=="none"&&u!==!1,i=[],f=d=>`$${String(i.push(d))}`,b=`embedding ${r} ${f(C(n))}::vector`,S=[];a?.namespace!==void 0&&S.push(`namespace = ${f(a.namespace)}`),a?.filter!==void 0&&Object.keys(a.filter).length>0&&S.push(`metadata @> ${f(JSON.stringify(a.filter))}::jsonb`);const I=["id","namespace",`${b} AS distance`];h&&I.push("metadata"),g&&I.push("embedding::text AS embedding");const M=S.length>0?`(SELECT * FROM "${e}" WHERE ${S.join(" AND ")})`:`"${e}"`,j=S.length>0?`WITH filtered AS MATERIALIZED ${M} SELECT ${I.join(", ")} FROM filtered ORDER BY ${b} LIMIT ${f(s)}`:`SELECT ${I.join(", ")} FROM "${e}" ORDER BY ${b} LIMIT ${f(s)}`,_=(await c.query(j,i)).map(d=>({id:d.id,...h&&d.metadata!==null&&d.metadata!==void 0?{metadata:d.metadata}:{},...d.namespace===null?{}:{namespace:d.namespace},score:E(Number(d.distance)),...g&&d.embedding!==void 0?{values:JSON.parse(d.embedding)}:{}}));return{count:_.length,matches:_}},async upsert(n){return w(n,"ON CONFLICT (id) DO UPDATE SET embedding = EXCLUDED.embedding, namespace = EXCLUDED.namespace, metadata = EXCLUDED.metadata")}}};export{W as createPgVectorIndex};
|