@lunora/hyperdrive 1.0.0-alpha.22 → 1.0.0-alpha.221

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/dist/index.d.ts CHANGED
@@ -1,187 +1,92 @@
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-DKB5Qb1Z.js";
1
2
  /**
2
- * Public types for `@lunora/hyperdrive`.
3
- *
4
- * Hyperdrive points at a database **Lunora does not own**. Everything here is
5
- * deliberately structural (no hard dependency on `@cloudflare/workers-types` or
6
- * any SQL driver) so unit tests can pass plain-object doubles, exactly like the
7
- * `D1DatabaseLike` projection in `@lunora/d1`.
8
- *
9
- * The hard constraint, restated wherever this surface is used: Hyperdrive
10
- * queries are **non-deterministic** (forbidden in `query`/`mutation`, allowed
11
- * only in `action`s — see the `hyperdrive_outside_action` advisor lint), and
12
- * external writes are **invisible to Lunora live queries** — a subscription will
13
- * NOT re-run when an external Postgres/MySQL row changes.
14
- */
15
- /**
16
- * Structural projection of the Cloudflare `Hyperdrive` binding (`env.HYPERDRIVE`).
17
- *
18
- * Mirrors the fields of the real `Hyperdrive` from `@cloudflare/workers-types`
19
- * but stays structural so a unit test can pass a plain object. At runtime only
20
- * `connectionString` is needed to construct a driver; the discrete connection
21
- * parts are surfaced for drivers that prefer a config object over a DSN.
22
- */
23
- interface HyperdriveLike {
24
- /** A connection string Hyperdrive routes through its pooled, cached edge connection. */
25
- connectionString: string;
26
- /** Database name component of the connection. */
27
- database: string;
28
- /** Host Hyperdrive presents to the driver (the local proxy, not your origin DB). */
29
- host: string;
30
- /** Password component of the connection. */
31
- password: string;
32
- /** Port Hyperdrive presents to the driver. */
33
- port: number;
34
- /** User component of the connection. */
35
- user: string;
36
- }
37
- /**
38
- * The connection config surfaced by {@link import("./create-hyperdrive").createHyperdrive | createHyperdrive}: the raw
39
- * `connectionString` plus the discrete parts, ready to hand to a driver.
40
- */
41
- interface HyperdriveConnection {
42
- /** Database name. */
43
- database: string;
44
- /** Host (Hyperdrive's local proxy). */
45
- host: string;
46
- /** Password. */
47
- password: string;
48
- /** Port. */
49
- port: number;
50
- /** User. */
51
- user: string;
52
- }
53
- /**
54
- * The driver-agnostic SQL surface bound to `ctx.sql` on **`ActionCtx` only**.
55
- *
56
- * This is the exact type the generated ctx imports as
57
- * `import("@lunora/hyperdrive").SqlClient`. Keep the name and shape stable — the
58
- * codegen ctx wiring (Phase 1) depends on it.
59
- *
60
- * It is intentionally minimal — a single parameterised `query` — so it maps onto
61
- * `postgres` (postgres.js), `pg` (node-postgres) and `mysql2` alike via the
62
- * {@link import("./create-hyperdrive").fromPostgresJs | fromPostgresJs} /
63
- * {@link import("./create-hyperdrive").fromNodePg | fromNodePg} /
64
- * {@link import("./create-hyperdrive").fromMysql2 | fromMysql2} adapters. Use
65
- * positional placeholders that match your driver (`$1, $2` for Postgres, `?` for
66
- * MySQL); the package does not rewrite SQL.
67
- *
68
- * Reminder (also on the emitted JSDoc): non-deterministic, action-only,
69
- * non-reactive — writes here are not tracked by Lunora live queries.
70
- */
71
- interface SqlClient {
72
- /**
73
- * Run a parameterised SQL statement and return the result rows.
74
- *
75
- * SECURITY: `text` is executed verbatim — the package never rewrites or
76
- * escapes it. NEVER interpolate untrusted/user input into `text`; put every
77
- * value in `params` and reference it with a positional placeholder (`$1`/`?`).
78
- * Building `text` by string-concatenating request data is a SQL-injection
79
- * sink against your own Postgres/MySQL. Identifiers (table/column names) can't
80
- * be parameterised — allowlist them against a fixed set, don't interpolate.
81
- * @param text SQL text with driver-native positional placeholders.
82
- * @param params Bound parameter values, positionally matched to `text`.
83
- * @returns The rows the statement produced (empty for non-`SELECT`s that
84
- * return no rows).
85
- */
86
- query: <Row = Record<string, unknown>>(text: string, params?: ReadonlyArray<unknown>) => Promise<Row[]>;
87
- }
88
- /**
89
- * Structural projection of a `pg` (node-postgres) `Client`/`Pool`. Only the
90
- * `query` method `fromNodePg` calls is required, kept structural for testing.
91
- */
92
- interface NodePgLike {
93
- query: (text: string, params?: ReadonlyArray<unknown>) => Promise<{
94
- rows: unknown[];
95
- }>;
96
- }
97
- /**
98
- * Structural projection of a `postgres` (postgres.js) tagged-template client.
99
- * The adapter uses the `.unsafe(text, params)` escape hatch so callers keep
100
- * full control of the parameter list.
101
- */
102
- interface PostgresJsLike {
103
- unsafe: (text: string, params?: ReadonlyArray<unknown>) => Promise<unknown>;
104
- }
105
- /**
106
- * Structural projection of a `mysql2/promise` connection/pool. `mysql2`'s
107
- * `execute` resolves to a `[rows, fields]` tuple; the adapter takes the first
108
- * element as the rows.
109
- */
110
- interface Mysql2Like {
111
- execute: (text: string, params?: ReadonlyArray<unknown>) => Promise<[unknown, unknown]>;
112
- }
113
- /**
114
- * Surface a Cloudflare Hyperdrive binding as a connection ready to feed a
115
- * user-supplied SQL driver.
116
- *
117
- * `@lunora/hyperdrive` deliberately **bundles no driver** — `postgres`, `pg` and
118
- * `mysql2` are heavy and the choice is the user's (they are `optional`
119
- * `peerDependencies`, never `dependencies`). This factory's only job is to lift
120
- * the binding's connection details out; the user constructs their own driver
121
- * from `connectionString` and wraps it with one of the {@link fromPostgresJs} /
122
- * {@link fromNodePg} / {@link fromMysql2} adapters to get a {@link SqlClient}.
123
- * @example
124
- * ```ts
125
- * import { createHyperdrive, fromPostgresJs } from "@lunora/hyperdrive";
126
- * import postgres from "postgres";
127
- *
128
- * // inside an action (never a query/mutation):
129
- * const { connectionString } = createHyperdrive(env.HYPERDRIVE);
130
- * ctx.sql = fromPostgresJs(postgres(connectionString));
131
- * const rows = await ctx.sql.query("select id from users where org = $1", [orgId]);
132
- * ```
133
- * @remarks
134
- * Hyperdrive talks to an **external** database Lunora has no visibility into.
135
- * Queries through `ctx.sql` are non-deterministic (action-only — enforced by the
136
- * `hyperdrive_outside_action` advisor lint) and external writes are NOT tracked
137
- * by Lunora live queries: subscriptions will not re-run when external rows
138
- * change. Use Hyperdrive to *integrate* an existing DB from an action; if you
139
- * want that data to be reactive, write a projection of it into a `defineSchema`
140
- * DO/D1 table.
141
- * @param binding The `env.HYPERDRIVE` binding (or a structural double).
142
- * @returns The raw `connectionString` plus the discrete connection parts.
143
- */
3
+ * Surface a Cloudflare Hyperdrive binding as a connection ready to feed a
4
+ * user-supplied SQL driver.
5
+ *
6
+ * `@lunora/hyperdrive` deliberately **bundles no driver** — `postgres`, `pg` and
7
+ * `mysql2` are heavy and the choice is the user's (they are `optional`
8
+ * `peerDependencies`, never `dependencies`). This factory's only job is to lift
9
+ * the binding's connection details out; the user constructs their own driver
10
+ * from `connectionString` and wraps it with one of the {@link fromPostgresJs} /
11
+ * {@link fromNodePg} / {@link fromMysql2} adapters to get a {@link SqlClient}.
12
+ * @example
13
+ * ```ts
14
+ * import { createHyperdrive, fromPostgresJs } from "@lunora/hyperdrive";
15
+ * import postgres from "postgres";
16
+ *
17
+ * // Wire the client once, on the app builder — called per shard construction,
18
+ * // not per request. `ctx.sql` is emitted `readonly`; it is never assigned.
19
+ * const app = defineApp<Env>()
20
+ * .shard((env) => env.SHARD)
21
+ * .hyperdrive((env) => {
22
+ * const { connectionString } = createHyperdrive(env.HYPERDRIVE);
23
+ *
24
+ * return fromPostgresJs(postgres(connectionString, { fetch_types: false, max: 5 }));
25
+ * })
26
+ * .build();
27
+ *
28
+ * // Then, inside an action (never a query/mutation):
29
+ * const rows = await ctx.sql.query("select id from users where org = $1", [orgId]);
30
+ * ```
31
+ * @remarks
32
+ * Hyperdrive talks to an **external** database Lunora has no visibility into.
33
+ * Queries through `ctx.sql` are non-deterministic (action-only — enforced by the
34
+ * `hyperdrive_outside_action` advisor lint) and external writes are NOT tracked
35
+ * by Lunora live queries: subscriptions will not re-run when external rows
36
+ * change. Use Hyperdrive to *integrate* an existing DB from an action; if you
37
+ * want that data to be reactive, write a projection of it into a `defineSchema`
38
+ * DO/D1 table.
39
+ * @param binding The `env.HYPERDRIVE` binding (or a structural double).
40
+ * @returns The raw `connectionString` plus the discrete connection parts.
41
+ */
144
42
  declare const createHyperdrive: (binding: HyperdriveLike) => {
145
43
  config: HyperdriveConnection;
146
44
  connectionString: string;
147
45
  };
148
46
  /**
149
- * Wrap a `postgres` (postgres.js) client as a {@link SqlClient}.
150
- *
151
- * Uses the driver's `.unsafe(text, params)` escape hatch so the caller supplies
152
- * a plain SQL string with `$1, $2, …` placeholders and a positional params
153
- * array. postgres.js's `.unsafe` resolves to a row array.
154
- */
47
+ * Wrap a `postgres` (postgres.js) client as a {@link SqlClient}.
48
+ *
49
+ * Uses the driver's `.unsafe(text, params)` escape hatch so the caller supplies
50
+ * a plain SQL string with `$1, $2, …` placeholders and a positional params
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`).
59
+ */
155
60
  declare const fromPostgresJs: (client: PostgresJsLike) => SqlClient;
156
61
  /**
157
- * Wrap a `pg` (node-postgres) `Client` or `Pool` as a {@link SqlClient}.
158
- *
159
- * node-postgres returns a result object whose `rows` field holds the row array.
160
- */
62
+ * Wrap a `pg` (node-postgres) `Client` or `Pool` as a {@link SqlClient}.
63
+ *
64
+ * node-postgres returns a result object whose `rows` field holds the row array.
65
+ */
161
66
  declare const fromNodePg: (client: NodePgLike) => SqlClient;
162
67
  /**
163
- * Wrap a `mysql2/promise` connection or pool as a {@link SqlClient}.
164
- *
165
- * Use `?` placeholders (MySQL positional syntax). `mysql2`'s `execute` resolves
166
- * to a `[rows, fields]` tuple; the adapter returns the first element. For a
167
- * non-`SELECT` (DML), `mysql2` yields a `ResultSetHeader` object rather than a
168
- * row array, so the adapter normalises that to `[]` — matching the empty-array
169
- * contract the postgres.js / node-postgres adapters already honour.
170
- */
68
+ * Wrap a `mysql2/promise` connection or pool as a {@link SqlClient}.
69
+ *
70
+ * Use `?` placeholders (MySQL positional syntax). `mysql2`'s `query` resolves
71
+ * to a `[rows, fields]` tuple; the adapter returns the first element. For a
72
+ * non-`SELECT` (DML), `mysql2` yields a `ResultSetHeader` object rather than a
73
+ * row array, so the adapter normalises that to `[]` — matching the empty-array
74
+ * contract the postgres.js / node-postgres adapters already honour.
75
+ */
171
76
  declare const fromMysql2: (connection: Mysql2Like) => SqlClient;
172
77
  /** How an external row maps to a Lunora document. */
173
78
  interface ProjectOptions {
174
79
  /**
175
- * Column whose value becomes the Lunora `_id` (stringified). Defaults to `"id"`.
176
- * With no `map`, this column is dropped from the document body (it lives on as
177
- * `_id`); with a `map`, the mapper owns the body and `_id` is added from here.
178
- */
80
+ * Column whose value becomes the Lunora `_id` (stringified). Defaults to `"id"`.
81
+ * With no `map`, this column is dropped from the document body (it lives on as
82
+ * `_id`); with a `map`, the mapper owns the body and `_id` is added from here.
83
+ */
179
84
  idColumn?: string;
180
85
  /**
181
- * Transform an external row into the stored document body. Omit for the default:
182
- * every selected column except `idColumn` is copied verbatim. The returned object
183
- * must not include `_id` — it is set from `idColumn`.
184
- */
86
+ * Transform an external row into the stored document body. Omit for the default:
87
+ * every selected column except `idColumn` is copied verbatim. The returned object
88
+ * must not include `_id` — it is set from `idColumn`.
89
+ */
185
90
  map?: (row: Record<string, unknown>) => Record<string, unknown>;
186
91
  }
187
92
  /** Options for {@link pullSourceRows}: the parameterised tenant query plus the row projection. */
@@ -192,20 +97,68 @@ interface PullSourceOptions extends ProjectOptions {
192
97
  query: string;
193
98
  }
194
99
  /**
195
- * Project one external row to a Lunora document: lift `idColumn` to a stringified
196
- * `_id`, then either apply `map` or copy every other column verbatim. Throws when
197
- * the id column is missing/nullish so a misconfigured query fails loudly rather than
198
- * materializing rows under an `"undefined"` id.
199
- *
200
- * Delegates to `@lunora/do`'s `liftSourceId` — the single id-lift the declarative
201
- * `.source()` poll loop also uses — so the manual bridge and the codegen path can
202
- * never diverge in their missing-id handling.
203
- */
100
+ * Project one external row to a Lunora document: lift `idColumn` to a stringified
101
+ * `_id`, then either apply `map` or copy every other column verbatim. Throws when
102
+ * the id column is missing/nullish so a misconfigured query fails loudly rather than
103
+ * materializing rows under an `"undefined"` id.
104
+ *
105
+ * Delegates to `@lunora/shard-engine`'s `liftSourceId` — the single id-lift the declarative
106
+ * `.source()` poll loop also uses — so the manual bridge and the codegen path can
107
+ * never diverge in their missing-id handling.
108
+ */
204
109
  declare const projectSourceRow: (row: Record<string, unknown>, options?: ProjectOptions) => Record<string, unknown>;
205
110
  /**
206
- * Run a parameterised tenant query against Hyperdrive and project every row to a
207
- * Lunora document ready to hand to `materializeExternalRows`. Call this inside an
208
- * **action** (where `ctx.sql` lives); pass the result to a mutation for the write.
209
- */
111
+ * Run a parameterised tenant query against Hyperdrive and project every row to a
112
+ * Lunora document ready to hand to `materializeExternalRows`. Call this inside an
113
+ * **action** (where `ctx.sql` lives); pass the result to a mutation for the write.
114
+ */
210
115
  declare const pullSourceRows: (sql: SqlClient, options: PullSourceOptions) => Promise<Record<string, unknown>[]>;
211
- export { type HyperdriveConnection, type HyperdriveLike, type Mysql2Like, type NodePgLike, type PostgresJsLike, type ProjectOptions, type PullSourceOptions, type SqlClient, createHyperdrive, fromMysql2, fromNodePg, fromPostgresJs, projectSourceRow, pullSourceRows };
116
+ export { type HyperdriveConnection, type HyperdriveLike, type Mysql2Like, type NodePgLike, type PostgresJsLike, type ProjectOptions, type PullSourceOptions, type SqlClient,
117
+ /**
118
+ * `@lunora/hyperdrive` — bring-your-own Postgres/MySQL for Lunora via Cloudflare
119
+ * Hyperdrive.
120
+ *
121
+ * Surfaces a Hyperdrive binding's connection string and a driver-agnostic
122
+ * {@link import("./types").SqlClient | SqlClient} bound to `ctx.sql` on
123
+ * **`ActionCtx` only**. External SQL is non-deterministic (action-only) and
124
+ * non-reactive (live queries do not track external writes) — integrate an
125
+ * existing DB, do not replace the Lunora data layer. See the README and
126
+ * {@link import("./create-hyperdrive").createHyperdrive | createHyperdrive} JSDoc.
127
+ */
128
+ createHyperdrive,
129
+ /**
130
+ * `@lunora/hyperdrive` — bring-your-own Postgres/MySQL for Lunora via Cloudflare
131
+ * Hyperdrive.
132
+ *
133
+ * Surfaces a Hyperdrive binding's connection string and a driver-agnostic
134
+ * {@link import("./types").SqlClient | SqlClient} bound to `ctx.sql` on
135
+ * **`ActionCtx` only**. External SQL is non-deterministic (action-only) and
136
+ * non-reactive (live queries do not track external writes) — integrate an
137
+ * existing DB, do not replace the Lunora data layer. See the README and
138
+ * {@link import("./create-hyperdrive").createHyperdrive | createHyperdrive} JSDoc.
139
+ */
140
+ fromMysql2,
141
+ /**
142
+ * `@lunora/hyperdrive` — bring-your-own Postgres/MySQL for Lunora via Cloudflare
143
+ * Hyperdrive.
144
+ *
145
+ * Surfaces a Hyperdrive binding's connection string and a driver-agnostic
146
+ * {@link import("./types").SqlClient | SqlClient} bound to `ctx.sql` on
147
+ * **`ActionCtx` only**. External SQL is non-deterministic (action-only) and
148
+ * non-reactive (live queries do not track external writes) — integrate an
149
+ * existing DB, do not replace the Lunora data layer. See the README and
150
+ * {@link import("./create-hyperdrive").createHyperdrive | createHyperdrive} JSDoc.
151
+ */
152
+ fromNodePg,
153
+ /**
154
+ * `@lunora/hyperdrive` — bring-your-own Postgres/MySQL for Lunora via Cloudflare
155
+ * Hyperdrive.
156
+ *
157
+ * Surfaces a Hyperdrive binding's connection string and a driver-agnostic
158
+ * {@link import("./types").SqlClient | SqlClient} bound to `ctx.sql` on
159
+ * **`ActionCtx` only**. External SQL is non-deterministic (action-only) and
160
+ * non-reactive (live queries do not track external writes) — integrate an
161
+ * existing DB, do not replace the Lunora data layer. See the README and
162
+ * {@link import("./create-hyperdrive").createHyperdrive | createHyperdrive} JSDoc.
163
+ */
164
+ fromPostgresJs, projectSourceRow, pullSourceRows };
package/dist/index.mjs CHANGED
@@ -1,2 +1 @@
1
- export { createHyperdrive, fromMysql2, fromNodePg, fromPostgresJs } from './packem_shared/createHyperdrive-DD8GoDZo.mjs';
2
- export { projectSourceRow, pullSourceRows } from './packem_shared/projectSourceRow-DtKuMRIm.mjs';
1
+ import{createHyperdrive as e,fromMysql2 as f,fromNodePg as m,fromPostgresJs as p}from"./packem_shared/createHyperdrive-HbpYCCgt.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 c}from"@lunora/errors";import{t as n}from"./mysql-params-D4Ijlvo5.mjs";const l=2,i=new WeakSet,u=t=>{const a=t,e=a.config?.clientFlags;if(typeof e=="number")return e;const r=a.pool?.config?.connectionConfig?.clientFlags;return typeof r=="number"?r:void 0},d=t=>{const a=u(t);if(a===void 0){if(i.has(t))return;i.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((a&l)===0)throw new c("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.')},w=async(t,a,e)=>{const r=await t.getConnection();let o;try{await r.beginTransaction(),o=await e(a(r)),await r.commit()}catch(s){try{await r.rollback(),r.release()}catch{r.destroy()}throw s}return r.release(),o},y=t=>({all:(a,e)=>t.query(a,e),batch:async a=>{await Promise.all(a.map(e=>t.query(e.sql,e.params)))},run:async(a,e)=>(await t.query(a,e),{rowsAffected:0}),...t.transaction===void 0?{}:{transaction:async a=>await t.transaction(async e=>await a(y(e)))}}),f=t=>{d(t);const a=t,{getConnection:e}=a;return{...typeof e=="function"?{transaction:async r=>await w(a,f,r)}:{},all:async(r,o)=>{const[s]=await t.query(r,n(o));return Array.isArray(s)?s:[]},batch:async r=>{await Promise.all(r.map(o=>t.query(o.sql,n(o.params))))},run:async(r,o)=>{const[s]=await t.query(r,n(o));return{rowsAffected:s.affectedRows??0}}}};export{f as buildMysqlExec,y as buildPgExec};
@@ -0,0 +1 @@
1
+ import{t as e}from"./mysql-params-D4Ijlvo5.mjs";const y=r=>({config:{database:r.database,host:r.host,password:r.password,port:r.port,user:r.user},connectionString:r.connectionString}),a=r=>{const{begin:s}=r;return{dbSystem:"postgresql",query:async(t,o=[])=>await r.unsafe(t,o,{prepare:!0}),...s===void 0?{}:{transaction:async t=>await s(async o=>await t(a(o)))}}},u=r=>({dbSystem:"postgresql",query:async(s,t=[])=>(await r.query(s,t)).rows}),p=r=>({dbSystem:"mysql",query:async(s,t=[])=>{const[o]=await r.query(s,e(t));return Array.isArray(o)?o:[]}});export{y as createHyperdrive,p as fromMysql2,u as fromNodePg,a 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};
@@ -0,0 +1 @@
1
+ import{sql as t}from"drizzle-orm";import{MySqlDialect as T}from"drizzle-orm/mysql-core";import{PgDialect as u}from"drizzle-orm/pg-core";const R=new u,f=new T,m=e=>n=>{const{params:r,sql:i}=e.sqlToQuery(n);return{params:r,sql:i}},o="__id__",a="__vector__",c=e=>t`${t.identifier(e)}.${t.identifier(a)}`,d=e=>t`to_tsvector('simple', ${e})`,l=e=>t`to_tsquery('simple', ${e.map((n,r)=>r===e.length-1?`${n}:*`:n).join(" & ")})`,I=/duplicate key value violates unique constraint/iu,_="utf8mb4_0900_bin",s=e=>`${e} COLLATE ${_}`,E=(e,n)=>{if(n?.unique===!0)switch(e){case"bigint":case"boolean":case"date":case"number":case"timestamp":break;case"bytes":return"VARBINARY(768)";default:return s("VARCHAR(768)")}switch(e){case"bigint":return s("VARCHAR(64)");case"boolean":return"TINYINT";case"bytes":return"LONGBLOB";case"date":case"number":case"timestamp":return"DOUBLE";default:return s("LONGTEXT")}},N=/TEXT|BLOB/u,L={companionTypes:{autoincrementPrimaryKey:"BIGSERIAL PRIMARY KEY",integer:"INTEGER",key:"TEXT",real:"DOUBLE PRECISION",text:"TEXT"},columnType:e=>{switch(e){case"boolean":return"INTEGER";case"bytes":return"BYTEA";case"date":case"number":case"timestamp":return"DOUBLE PRECISION";default:return"TEXT"}},frameworkColumns:()=>[{name:"id",type:"TEXT PRIMARY KEY"},{name:"_creationTime",type:"DOUBLE PRECISION NOT NULL"}],isUniqueViolation:e=>{const{code:n}=e;return n==="23505"||e instanceof Error&&I.test(e.message)},maxTableColumns:1600,name:"postgres",render:m(R),searchBackfillHint:"call backfillSqlSearchIndexes from @lunora/sql-store with the store's exec from a host-side admin path",supportsFts5:!1,nativeTextSearch:{createCompanion:(e,n)=>t`CREATE TABLE IF NOT EXISTS ${t.identifier(e)} (${t.identifier(o)} ${t.raw(n)} PRIMARY KEY, ${t.identifier(a)} tsvector)`,createIndexes:e=>[t`CREATE INDEX IF NOT EXISTS ${t.identifier(`${e}__gin`)} ON ${t.identifier(e)} USING GIN (${t.identifier(a)})`],indexDocument:(e,n,r,i)=>t`INSERT INTO ${t.identifier(e)} (${t.identifier(o)}, ${t.identifier(a)}) SELECT ${n}, ${d(r)} WHERE ${i} ON CONFLICT (${t.identifier(o)}) DO UPDATE SET ${t.identifier(a)} = EXCLUDED.${t.identifier(a)}`,matches:(e,n)=>t`${c(e)} @@ ${l(n)}`,rank:(e,n)=>t`ts_rank_cd(${c(e)}, ${l(n)})`},supportsReturning:!0,textPatternOperatorClass:"text_pattern_ops",tableExists:e=>t`SELECT table_name FROM information_schema.tables WHERE table_schema = ANY (current_schemas(false)) AND table_name = ${e}`},C={affectedRows:e=>e.rowsAffected,companionTypes:{autoincrementPrimaryKey:"BIGINT AUTO_INCREMENT PRIMARY KEY",integer:"INTEGER",key:s("VARCHAR(768)"),real:"DOUBLE",text:s("LONGTEXT")},columnType:E,frameworkColumns:()=>[{name:"id",type:`${s("VARCHAR(768)")} PRIMARY KEY`},{name:"_creationTime",type:"DOUBLE NOT NULL"}],indexKeyPrefix:e=>N.test(E(e))?191:void 0,isUniqueViolation:e=>{const n=e;return n.errno===1062||n.code==="ER_DUP_ENTRY"},maxTableColumns:4096,name:"mysql",render:m(f),searchBackfillHint:"call backfillSqlSearchIndexes from @lunora/sql-store with the store's exec from a host-side admin path",supportsFts5:!1,supportsReturning:!1,tableExists:e=>t`SELECT table_name FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = ${e}`};export{C as mysqlDialect,L as postgresDialect};
@@ -0,0 +1 @@
1
+ import{liftSourceId as a}from"@lunora/shard-engine";const n=(o,r={})=>a(o,r),w=async(o,r)=>{const{idColumn:t,map:c,params:s,query:e}=r;return(await o.query(e,s)).map(u=>n(u,{idColumn:t,map:c}))};export{n as projectSourceRow,w as pullSourceRows};
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Public types for `@lunora/hyperdrive`.
3
+ *
4
+ * Hyperdrive points at a database **Lunora does not own**. Everything here is
5
+ * deliberately structural (no hard dependency on `@cloudflare/workers-types` or
6
+ * any SQL driver) so unit tests can pass plain-object doubles, exactly like the
7
+ * `D1DatabaseLike` projection in `@lunora/d1`.
8
+ *
9
+ * The hard constraint, restated wherever this surface is used: Hyperdrive
10
+ * queries are **non-deterministic** (forbidden in `query`/`mutation`, allowed
11
+ * only in `action`s — see the `hyperdrive_outside_action` advisor lint), and
12
+ * external writes are **invisible to Lunora live queries** — a subscription will
13
+ * NOT re-run when an external Postgres/MySQL row changes.
14
+ */
15
+ /**
16
+ * Structural projection of the Cloudflare `Hyperdrive` binding (`env.HYPERDRIVE`).
17
+ *
18
+ * Mirrors the fields of the real `Hyperdrive` from `@cloudflare/workers-types`
19
+ * but stays structural so a unit test can pass a plain object. At runtime only
20
+ * `connectionString` is needed to construct a driver; the discrete connection
21
+ * parts are surfaced for drivers that prefer a config object over a DSN.
22
+ */
23
+ interface HyperdriveLike {
24
+ /** A connection string Hyperdrive routes through its pooled, cached edge connection. */
25
+ connectionString: string;
26
+ /** Database name component of the connection. */
27
+ database: string;
28
+ /** Host Hyperdrive presents to the driver (the local proxy, not your origin DB). */
29
+ host: string;
30
+ /** Password component of the connection. */
31
+ password: string;
32
+ /** Port Hyperdrive presents to the driver. */
33
+ port: number;
34
+ /** User component of the connection. */
35
+ user: string;
36
+ }
37
+ /**
38
+ * The connection config surfaced by {@link import("./create-hyperdrive").createHyperdrive | createHyperdrive}: the raw
39
+ * `connectionString` plus the discrete parts, ready to hand to a driver.
40
+ */
41
+ interface HyperdriveConnection {
42
+ /** Database name. */
43
+ database: string;
44
+ /** Host (Hyperdrive's local proxy). */
45
+ host: string;
46
+ /** Password. */
47
+ password: string;
48
+ /** Port. */
49
+ port: number;
50
+ /** User. */
51
+ user: string;
52
+ }
53
+ /**
54
+ * The driver-agnostic SQL surface bound to `ctx.sql` on **`ActionCtx` only**.
55
+ *
56
+ * This is the exact type the generated ctx imports as
57
+ * `import("@lunora/hyperdrive").SqlClient`. Keep the name and shape stable — the
58
+ * codegen ctx wiring (Phase 1) depends on it.
59
+ *
60
+ * It is intentionally minimal — a single parameterised `query` — so it maps onto
61
+ * `postgres` (postgres.js), `pg` (node-postgres) and `mysql2` alike via the
62
+ * {@link import("./create-hyperdrive").fromPostgresJs | fromPostgresJs} /
63
+ * {@link import("./create-hyperdrive").fromNodePg | fromNodePg} /
64
+ * {@link import("./create-hyperdrive").fromMysql2 | fromMysql2} adapters. Use
65
+ * positional placeholders that match your driver (`$1, $2` for Postgres, `?` for
66
+ * MySQL); the package does not rewrite SQL.
67
+ *
68
+ * Reminder (also on the emitted JSDoc): non-deterministic, action-only,
69
+ * non-reactive — writes here are not tracked by Lunora live queries.
70
+ */
71
+ interface SqlClient {
72
+ /**
73
+ * The database system behind this client, as OTel names it in
74
+ * `db.system.name`. Stamped by the driver adapters ({@link import("./create-hyperdrive").fromPostgresJs | fromPostgresJs}
75
+ * / {@link import("./create-hyperdrive").fromNodePg | fromNodePg} → `"postgresql"`,
76
+ * {@link import("./create-hyperdrive").fromMysql2 | fromMysql2} → `"mysql"`) so `ctx.sql`'s automatic
77
+ * instrumentation reports the real system. Optional: a hand-built client may
78
+ * omit it and is then reported as `"other_sql"`.
79
+ */
80
+ readonly dbSystem?: "mysql" | "postgresql";
81
+ /**
82
+ * Run a parameterised SQL statement and return the result rows.
83
+ *
84
+ * SECURITY: `text` is executed verbatim — the package never rewrites or
85
+ * escapes it. NEVER interpolate untrusted/user input into `text`; put every
86
+ * value in `params` and reference it with a positional placeholder (`$1`/`?`).
87
+ * Building `text` by string-concatenating request data is a SQL-injection
88
+ * sink against your own Postgres/MySQL. Identifiers (table/column names) can't
89
+ * be parameterised — allowlist them against a fixed set, don't interpolate.
90
+ * @param text SQL text with driver-native positional placeholders.
91
+ * @param params Bound parameter values, positionally matched to `text`.
92
+ * @returns The rows the statement produced (empty for non-`SELECT`s that
93
+ * return no rows).
94
+ */
95
+ query: <Row = Record<string, unknown>>(text: string, params?: ReadonlyArray<unknown>) => Promise<Row[]>;
96
+ /**
97
+ * Run `work` against a client pinned to one connection inside one
98
+ * transaction: committed when `work` resolves, rolled back when it throws.
99
+ * {@link import("./create-hyperdrive").fromPostgresJs | fromPostgresJs} provides it over postgres.js's `begin`;
100
+ * `@lunora/hyperdrive/global`'s `buildPgExec` uses it so a `.global()` row
101
+ * write and its aggregate/rank/CDC companions commit together. Absent, they
102
+ * commit one statement at a time.
103
+ */
104
+ transaction?: <T>(work: (tx: SqlClient) => Promise<T>) => Promise<T>;
105
+ }
106
+ /**
107
+ * Structural projection of a `pg` (node-postgres) `Client`/`Pool`. Only the
108
+ * `query` method `fromNodePg` calls is required, kept structural for testing.
109
+ *
110
+ * Method syntax, not an arrow property — see {@link PostgresJsLike}.
111
+ */
112
+ interface NodePgLike {
113
+ query(this: void, text: string, params?: ReadonlyArray<unknown>): Promise<{
114
+ rows: unknown[];
115
+ }>;
116
+ }
117
+ /**
118
+ * Structural projection of a `postgres` (postgres.js) tagged-template client.
119
+ * The adapter uses the `.unsafe(text, params)` escape hatch so callers keep
120
+ * full control of the parameter list.
121
+ *
122
+ * **`unsafe` is declared with METHOD syntax, and that is load-bearing.** Under
123
+ * `strictFunctionTypes` an arrow PROPERTY is checked contravariantly in its
124
+ * parameters, so a driver whose `unsafe` takes a MUTABLE array — which real
125
+ * postgres.js does (`parameters?: ParameterOrJSON<never>[]`) — is not assignable
126
+ * to one declared `params?: ReadonlyArray<unknown>`. As an arrow property this
127
+ * projection could therefore never accept the package it projects: every
128
+ * consumer got
129
+ * `TS2345: Argument of type 'Sql<{}>' is not assignable to parameter of type
130
+ * 'PostgresJsLike'`, and this repo's own test reached for
131
+ * `as unknown as PostgresJsLike` to get past it. Method syntax is checked
132
+ * bivariantly, which is what a structural projection of someone else's type
133
+ * needs — `readonly unknown[]` and postgres.js's mutable array relate in one
134
+ * direction, which is all bivariance asks for.
135
+ */
136
+ interface PostgresJsLike {
137
+ /** postgres.js's `sql.begin(fn)`: reserves one connection and runs `fn` inside `BEGIN` … `COMMIT`. */
138
+ begin?(this: void, work: (tx: PostgresJsLike) => Promise<unknown>): Promise<unknown>;
139
+ unsafe(this: void, text: string, params?: ReadonlyArray<unknown>, options?: {
140
+ prepare?: boolean;
141
+ }): Promise<unknown>;
142
+ }
143
+ /**
144
+ * Structural projection of a `mysql2/promise` connection/pool. `mysql2`'s
145
+ * `query` resolves to a `[rows, fields]` tuple; the adapter takes the first
146
+ * element as the rows.
147
+ *
148
+ * `query`, not `execute`: `execute` is a protocol-level prepared statement
149
+ * (`COM_STMT_PREPARE` + the binary protocol), which Hyperdrive does not support
150
+ * for MySQL. `query` interpolates the `?` placeholders client-side and sends
151
+ * plain text. Strings are quoted with `''` for a quote and `\\` for a
152
+ * backslash, so a server whose `sql_mode` includes `NO_BACKSLASH_ESCAPES` is
153
+ * unsupported: there a backslash is stored twice. No value can end its literal
154
+ * under either mode. mysql2 skips a `?` inside a quoted string or comment, but
155
+ * reads `??` as an identifier placeholder.
156
+ *
157
+ * Method syntax, not an arrow property — see {@link PostgresJsLike} — but here
158
+ * that is necessary and NOT sufficient, which is why `params` is `unknown`.
159
+ *
160
+ * mysql2 types its second argument as a single `QueryValues`: a union of
161
+ * scalars, `QueryValues[]` and `{ [key: string]: QueryValues }`. Declared
162
+ * `params?: ReadonlyArray<unknown>` this projection could never accept the
163
+ * driver it projects — bivariance needs the two parameter types to relate in
164
+ * ONE direction and they relate in neither (`readonly unknown[] | undefined` is
165
+ * not a `QueryValues`, and a `string` is not an array).
166
+ *
167
+ * `unknown` is the narrowest supertype of `QueryValues` that does not restate
168
+ * mysql2's union here. Restating it would be the vacuous shim all over again —
169
+ * a local copy that drifts from the package it mirrors and makes the gate pass
170
+ * while a consumer fails.
171
+ *
172
+ * The looseness is confined to the INPUT position: this types the driver a
173
+ * caller hands to `fromMysql2`, not what anyone passes to
174
+ * {@link SqlClient.query}, which still declares `ReadonlyArray<unknown>`. Every
175
+ * call site in this package forwards an array, and the driver validates the
176
+ * values at runtime regardless.
177
+ */
178
+ interface Mysql2Like {
179
+ query(this: void, text: string, params?: unknown): Promise<[unknown, unknown]>;
180
+ }
181
+ export { HyperdriveLike as H, Mysql2Like as M, NodePgLike as N, PostgresJsLike as P, SqlClient as S, HyperdriveConnection as a };