@lunora/hyperdrive 1.0.0-alpha.225 → 1.0.0-alpha.226

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 CHANGED
@@ -95,18 +95,25 @@ Prefer to manage it yourself? Run `wrangler hyperdrive create my-db --connection
95
95
  // src/server/index.ts
96
96
  import type { HyperdriveLike } from "@lunora/hyperdrive";
97
97
  import { createHyperdrive, fromPostgresJs } from "@lunora/hyperdrive";
98
- import postgres from "postgres";
99
98
 
100
99
  import { defineApp } from "../../lunora/_generated/app";
101
100
 
102
101
  const app = defineApp<Env>()
103
102
  .shard((env) => env.SHARD)
104
- .hyperdrive((env) => fromPostgresJs(postgres(createHyperdrive(env.HYPERDRIVE as HyperdriveLike).connectionString, { fetch_types: false, max: 5 })))
103
+ .hyperdrive((env) =>
104
+ fromPostgresJs(async () => {
105
+ const { default: postgres } = await import("postgres");
106
+
107
+ return postgres(createHyperdrive(env.HYPERDRIVE as HyperdriveLike).connectionString, { fetch_types: false, max: 5 });
108
+ }),
109
+ )
105
110
  .build();
106
111
 
107
112
  export const ShardDO = app.ShardDO;
108
113
  ```
109
114
 
115
+ The adapter takes a loader, not a driver: it runs on the first query and the client is reused, so the driver stays out of the Worker's startup graph. A static `import postgres from "postgres"` in the entry is evaluated at startup and defeats this. A failed load rejects that query; the next one retries. For `pg`, the loader may `await client.connect()` before returning.
116
+
110
117
  Hand-composing the worker instead? The same thunk is `createShardDO({ sql: (env) => … })`. Without it, every `ctx.sql` method throws a message pointing back at this wiring.
111
118
 
112
119
  Handlers then just read it. The procedure builders (`action`, `v`) and `api` come from your app-local generated modules:
@@ -157,7 +164,13 @@ Supply the driver once, on the app builder — required, or every tick records `
157
164
  ```ts
158
165
  export default defineApp<Env>()
159
166
  .shard((env) => env.SHARD)
160
- .sourceClient((env, binding) => fromPostgresJs(postgres((env[binding] as { connectionString: string }).connectionString, { fetch_types: false, max: 5 })))
167
+ .sourceClient((env, binding) =>
168
+ fromPostgresJs(async () => {
169
+ const { default: postgres } = await import("postgres");
170
+
171
+ return postgres((env[binding] as { connectionString: string }).connectionString, { fetch_types: false, max: 5 });
172
+ }),
173
+ )
161
174
  .build();
162
175
  ```
163
176
 
@@ -178,13 +191,18 @@ The `@lunora/hyperdrive/global` subpath is the opposite trade-off: Lunora **owns
178
191
  ```ts
179
192
  import { fromPostgresJs } from "@lunora/hyperdrive";
180
193
  import { createPostgresGlobalCtxDb } from "@lunora/hyperdrive/global";
181
- import postgres from "postgres";
182
194
 
183
- const sql = postgres(env.HYPERDRIVE.connectionString, { fetch_types: false, max: 5 });
184
- const globalDb = createPostgresGlobalCtxDb(fromPostgresJs(sql), { schema });
195
+ const globalDb = createPostgresGlobalCtxDb(
196
+ fromPostgresJs(async () => {
197
+ const { default: postgres } = await import("postgres");
198
+
199
+ return postgres(env.HYPERDRIVE.connectionString, { fetch_types: false, max: 5 });
200
+ }),
201
+ { schema },
202
+ );
185
203
  ```
186
204
 
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.
205
+ For MySQL use `createMysqlGlobalCtxDb` with a loader returning a `mysql2/promise` pool created with `disableEval: true` (required on Workers) and `flags: ["FOUND_ROWS"]` (`import()` the driver inside it, as with `fromMysql2`) — 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
206
 
189
207
  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).
190
208
 
package/dist/global.d.mts CHANGED
@@ -30,7 +30,7 @@
30
30
  */
31
31
  import { SchemaLike, ImportConflictMode, CdcChange, DatabaseWriterLike } from '@lunora/shard-engine';
32
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-DKB5Qb1Z.mjs";
33
+ import { M as Mysql2Like, S as SqlClient } from "./packem_shared/types.d-DmkKECp5.mjs";
34
34
  import { VectorMetric, VectorizeIndexLike } from '@lunora/platform';
35
35
  /**
36
36
  * Minimal row-returning client (e.g. `@lunora/hyperdrive`'s `fromPostgresJs`/`fromNodePg`
@@ -97,10 +97,11 @@ type Mysql2Client = Mysql2Like & {
97
97
  */
98
98
  declare const buildPgExec: (client: RowClient) => SqlExec;
99
99
  /**
100
- * Wrap a `mysql2/promise` connection/pool as a {@link SqlExec}. The core already
101
- * renders backtick identifiers and `?` placeholders for MySQL, so `all`/`run`
102
- * forward verbatim. MySQL has no `RETURNING`, so `run` surfaces `affectedRows`
103
- * for the store's affected-rows OCC guard.
100
+ * Wrap a `mysql2/promise` connection/pool, built by `load` on the first statement,
101
+ * as a {@link SqlExec}. The core already renders backtick identifiers and `?`
102
+ * placeholders for MySQL, so `all`/`run` forward verbatim. MySQL has no
103
+ * `RETURNING`, so `run` surfaces `affectedRows` for the store's affected-rows
104
+ * OCC guard.
104
105
  *
105
106
  * Statements go over as text with client-side escaping, so the server must not
106
107
  * run with `NO_BACKSLASH_ESCAPES` (see {@link Mysql2Like}).
@@ -109,12 +110,21 @@ declare const buildPgExec: (client: RowClient) => SqlExec;
109
110
  * (mysql2: `createPool({ flags: ["FOUND_ROWS"] })`). Without it, `affectedRows`
110
111
  * counts *changed* rows, so an idempotent `patch`/`replace` that re-writes the
111
112
  * same values reports 0 and the OCC guard raises a spurious conflict. Verified
112
- * once here at construction — see {@link assertFoundRows} — never per statement.
113
+ * when the loader resolves — see {@link assertFoundRows} — never per statement. A
114
+ * connection without the flag fails that statement and is not kept, so every
115
+ * later statement fails the same way rather than running unguarded.
116
+ *
117
+ * `load` runs on the first statement, not here: `.hyperdriveGlobal`'s `exec(env)`
118
+ * is built with the request's ctx, which can precede the mutation's input-gated
119
+ * span. The `import("mysql2/promise")` inside the loader must start inside that
120
+ * span and finish there, as `buildPgExec(fromPostgresJs(load))` does, so a load
121
+ * begun outside the gate is never awaited inside it.
113
122
  *
114
123
  * Given a **pool** (anything with `getConnection`), `transaction` takes one
115
124
  * connection for the store's row + companion writes, so they commit together.
116
- * A single connection gets no `transaction`: other writers' statements would
117
- * land inside an open one, so its companions commit one statement at a time.
125
+ * A single connection cannot hold one apart from other writers' statements, so
126
+ * `transaction` is present but rejects with `GLOBAL_TRANSACTION_UNSUPPORTED`: a
127
+ * write to a table with companions fails before it writes anything.
118
128
  *
119
129
  * `batch` dispatches every statement concurrently (`Promise.all`), same
120
130
  * rationale as {@link buildPgExec}'s `batch` — `Mysql2Like` only exposes a
@@ -123,7 +133,7 @@ declare const buildPgExec: (client: RowClient) => SqlExec;
123
133
  * at-least-once, and unordered between elements, safe only for statements
124
134
  * with no cross-effect (what every current caller batches).
125
135
  */
126
- declare const buildMysqlExec: (connection: Mysql2Client) => SqlExec;
136
+ declare const buildMysqlExec: (load: () => Mysql2Client | Promise<Mysql2Client>) => SqlExec;
127
137
  /**
128
138
  * **Postgres** dialect. Differs from SQLite only in column types
129
139
  * (`DOUBLE PRECISION`/`BYTEA`/`BIGSERIAL`), the `information_schema` catalog
@@ -225,8 +235,8 @@ interface ImportHyperdriveGlobalRowsOptions {
225
235
  declare const importHyperdriveGlobalRows: ({ cdc, dryRun, engine, exec, onConflict, rows, schema, startLine }: ImportHyperdriveGlobalRowsOptions) => Promise<ImportResult>;
226
236
  /** Convenience: a **Postgres** `.global()` writer from a row-client (postgres.js/pg over Hyperdrive). */
227
237
  declare const createPostgresGlobalCtxDb: (client: RowClient, options: Omit<CreateHyperdriveGlobalCtxDbOptions, "engine" | "exec">) => DatabaseWriterLike;
228
- /** Convenience: a **MySQL** `.global()` writer from a `mysql2/promise` connection/pool (created with `flags: ["FOUND_ROWS"]`). */
229
- declare const createMysqlGlobalCtxDb: (connection: Mysql2Client, options: Omit<CreateHyperdriveGlobalCtxDbOptions, "engine" | "exec">) => DatabaseWriterLike;
238
+ /** Convenience: a **MySQL** `.global()` writer over a `mysql2/promise` connection/pool (created with `disableEval: true` and `flags: ["FOUND_ROWS"]`) that `load` builds on the first statement. */
239
+ declare const createMysqlGlobalCtxDb: (load: () => Mysql2Client | Promise<Mysql2Client>, options: Omit<CreateHyperdriveGlobalCtxDbOptions, "engine" | "exec">) => DatabaseWriterLike;
230
240
  /** The studio's global data browser over a Hyperdrive database — every table in the connection's schema, internal ones hidden. */
231
241
  interface HyperdriveGlobalIntrospector {
232
242
  facetColumn: (options: FacetGlobalColumnOptions) => Promise<GlobalFacetResult>;
package/dist/global.d.ts CHANGED
@@ -30,7 +30,7 @@
30
30
  */
31
31
  import { SchemaLike, ImportConflictMode, CdcChange, DatabaseWriterLike } from '@lunora/shard-engine';
32
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-DKB5Qb1Z.js";
33
+ import { M as Mysql2Like, S as SqlClient } from "./packem_shared/types.d-DmkKECp5.js";
34
34
  import { VectorMetric, VectorizeIndexLike } from '@lunora/platform';
35
35
  /**
36
36
  * Minimal row-returning client (e.g. `@lunora/hyperdrive`'s `fromPostgresJs`/`fromNodePg`
@@ -97,10 +97,11 @@ type Mysql2Client = Mysql2Like & {
97
97
  */
98
98
  declare const buildPgExec: (client: RowClient) => SqlExec;
99
99
  /**
100
- * Wrap a `mysql2/promise` connection/pool as a {@link SqlExec}. The core already
101
- * renders backtick identifiers and `?` placeholders for MySQL, so `all`/`run`
102
- * forward verbatim. MySQL has no `RETURNING`, so `run` surfaces `affectedRows`
103
- * for the store's affected-rows OCC guard.
100
+ * Wrap a `mysql2/promise` connection/pool, built by `load` on the first statement,
101
+ * as a {@link SqlExec}. The core already renders backtick identifiers and `?`
102
+ * placeholders for MySQL, so `all`/`run` forward verbatim. MySQL has no
103
+ * `RETURNING`, so `run` surfaces `affectedRows` for the store's affected-rows
104
+ * OCC guard.
104
105
  *
105
106
  * Statements go over as text with client-side escaping, so the server must not
106
107
  * run with `NO_BACKSLASH_ESCAPES` (see {@link Mysql2Like}).
@@ -109,12 +110,21 @@ declare const buildPgExec: (client: RowClient) => SqlExec;
109
110
  * (mysql2: `createPool({ flags: ["FOUND_ROWS"] })`). Without it, `affectedRows`
110
111
  * counts *changed* rows, so an idempotent `patch`/`replace` that re-writes the
111
112
  * same values reports 0 and the OCC guard raises a spurious conflict. Verified
112
- * once here at construction — see {@link assertFoundRows} — never per statement.
113
+ * when the loader resolves — see {@link assertFoundRows} — never per statement. A
114
+ * connection without the flag fails that statement and is not kept, so every
115
+ * later statement fails the same way rather than running unguarded.
116
+ *
117
+ * `load` runs on the first statement, not here: `.hyperdriveGlobal`'s `exec(env)`
118
+ * is built with the request's ctx, which can precede the mutation's input-gated
119
+ * span. The `import("mysql2/promise")` inside the loader must start inside that
120
+ * span and finish there, as `buildPgExec(fromPostgresJs(load))` does, so a load
121
+ * begun outside the gate is never awaited inside it.
113
122
  *
114
123
  * Given a **pool** (anything with `getConnection`), `transaction` takes one
115
124
  * connection for the store's row + companion writes, so they commit together.
116
- * A single connection gets no `transaction`: other writers' statements would
117
- * land inside an open one, so its companions commit one statement at a time.
125
+ * A single connection cannot hold one apart from other writers' statements, so
126
+ * `transaction` is present but rejects with `GLOBAL_TRANSACTION_UNSUPPORTED`: a
127
+ * write to a table with companions fails before it writes anything.
118
128
  *
119
129
  * `batch` dispatches every statement concurrently (`Promise.all`), same
120
130
  * rationale as {@link buildPgExec}'s `batch` — `Mysql2Like` only exposes a
@@ -123,7 +133,7 @@ declare const buildPgExec: (client: RowClient) => SqlExec;
123
133
  * at-least-once, and unordered between elements, safe only for statements
124
134
  * with no cross-effect (what every current caller batches).
125
135
  */
126
- declare const buildMysqlExec: (connection: Mysql2Client) => SqlExec;
136
+ declare const buildMysqlExec: (load: () => Mysql2Client | Promise<Mysql2Client>) => SqlExec;
127
137
  /**
128
138
  * **Postgres** dialect. Differs from SQLite only in column types
129
139
  * (`DOUBLE PRECISION`/`BYTEA`/`BIGSERIAL`), the `information_schema` catalog
@@ -225,8 +235,8 @@ interface ImportHyperdriveGlobalRowsOptions {
225
235
  declare const importHyperdriveGlobalRows: ({ cdc, dryRun, engine, exec, onConflict, rows, schema, startLine }: ImportHyperdriveGlobalRowsOptions) => Promise<ImportResult>;
226
236
  /** Convenience: a **Postgres** `.global()` writer from a row-client (postgres.js/pg over Hyperdrive). */
227
237
  declare const createPostgresGlobalCtxDb: (client: RowClient, options: Omit<CreateHyperdriveGlobalCtxDbOptions, "engine" | "exec">) => DatabaseWriterLike;
228
- /** Convenience: a **MySQL** `.global()` writer from a `mysql2/promise` connection/pool (created with `flags: ["FOUND_ROWS"]`). */
229
- declare const createMysqlGlobalCtxDb: (connection: Mysql2Client, options: Omit<CreateHyperdriveGlobalCtxDbOptions, "engine" | "exec">) => DatabaseWriterLike;
238
+ /** Convenience: a **MySQL** `.global()` writer over a `mysql2/promise` connection/pool (created with `disableEval: true` and `flags: ["FOUND_ROWS"]`) that `load` builds on the first statement. */
239
+ declare const createMysqlGlobalCtxDb: (load: () => Mysql2Client | Promise<Mysql2Client>, options: Omit<CreateHyperdriveGlobalCtxDbOptions, "engine" | "exec">) => DatabaseWriterLike;
230
240
  /** The studio's global data browser over a Hyperdrive database — every table in the connection's schema, internal ones hidden. */
231
241
  interface HyperdriveGlobalIntrospector {
232
242
  facetColumn: (options: FacetGlobalColumnOptions) => Promise<GlobalFacetResult>;
package/dist/global.mjs CHANGED
@@ -1 +1 @@
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-DrnJfCYF.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};
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-DX34snyg.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-DKB5Qb1Z.mjs";
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-DmkKECp5.mjs";
2
2
  /**
3
3
  * Surface a Cloudflare Hyperdrive binding as a connection ready to feed a
4
4
  * user-supplied SQL driver.
@@ -9,20 +9,25 @@ import { H as HyperdriveLike, a as HyperdriveConnection, M as Mysql2Like, S as S
9
9
  * the binding's connection details out; the user constructs their own driver
10
10
  * from `connectionString` and wraps it with one of the {@link fromPostgresJs} /
11
11
  * {@link fromNodePg} / {@link fromMysql2} adapters to get a {@link SqlClient}.
12
+ * The adapters take a LOADER, not a driver: it runs on the first query, so the
13
+ * driver's `import()` keeps it out of the Worker's startup graph (a static
14
+ * `import postgres from "postgres"` in the entry is evaluated by every isolate
15
+ * before its first event).
12
16
  * @example
13
17
  * ```ts
14
18
  * import { createHyperdrive, fromPostgresJs } from "@lunora/hyperdrive";
15
- * import postgres from "postgres";
16
19
  *
17
20
  * // Wire the client once, on the app builder — called per shard construction,
18
21
  * // not per request. `ctx.sql` is emitted `readonly`; it is never assigned.
19
22
  * const app = defineApp<Env>()
20
23
  * .shard((env) => env.SHARD)
21
- * .hyperdrive((env) => {
22
- * const { connectionString } = createHyperdrive(env.HYPERDRIVE);
24
+ * .hyperdrive((env) =>
25
+ * fromPostgresJs(async () => {
26
+ * const { default: postgres } = await import("postgres");
23
27
  *
24
- * return fromPostgresJs(postgres(connectionString, { fetch_types: false, max: 5 }));
25
- * })
28
+ * return postgres(createHyperdrive(env.HYPERDRIVE).connectionString, { fetch_types: false, max: 5 });
29
+ * }),
30
+ * )
26
31
  * .build();
27
32
  *
28
33
  * // Then, inside an action (never a query/mutation):
@@ -44,7 +49,9 @@ declare const createHyperdrive: (binding: HyperdriveLike) => {
44
49
  connectionString: string;
45
50
  };
46
51
  /**
47
- * Wrap a `postgres` (postgres.js) client as a {@link SqlClient}.
52
+ * Wrap a `postgres` (postgres.js) client, built by `load` on the first query
53
+ * and reused, as a {@link SqlClient}. A failed load rejects that query and the
54
+ * next one retries.
48
55
  *
49
56
  * Uses the driver's `.unsafe(text, params)` escape hatch so the caller supplies
50
57
  * a plain SQL string with `$1, $2, …` placeholders and a positional params
@@ -57,15 +64,18 @@ declare const createHyperdrive: (binding: HyperdriveLike) => {
57
64
  * wins. Prepared reads are cacheable by Hyperdrive, so a `.global()` store's
58
65
  * config must have caching disabled (see `@lunora/hyperdrive/global`).
59
66
  */
60
- declare const fromPostgresJs: (client: PostgresJsLike) => SqlClient;
67
+ declare const fromPostgresJs: (load: () => Promise<PostgresJsLike> | PostgresJsLike) => SqlClient;
61
68
  /**
62
- * Wrap a `pg` (node-postgres) `Client` or `Pool` as a {@link SqlClient}.
69
+ * Wrap a `pg` (node-postgres) `Client` or `Pool`, built by `load` on the first
70
+ * query, as a {@link SqlClient}. `load` may `await client.connect()` before
71
+ * returning.
63
72
  *
64
73
  * node-postgres returns a result object whose `rows` field holds the row array.
65
74
  */
66
- declare const fromNodePg: (client: NodePgLike) => SqlClient;
75
+ declare const fromNodePg: (load: () => Promise<NodePgLike> | NodePgLike) => SqlClient;
67
76
  /**
68
- * Wrap a `mysql2/promise` connection or pool as a {@link SqlClient}.
77
+ * Wrap a `mysql2/promise` connection or pool, built by `load` on the first
78
+ * query, as a {@link SqlClient}.
69
79
  *
70
80
  * Use `?` placeholders (MySQL positional syntax). `mysql2`'s `query` resolves
71
81
  * to a `[rows, fields]` tuple; the adapter returns the first element. For a
@@ -73,7 +83,7 @@ declare const fromNodePg: (client: NodePgLike) => SqlClient;
73
83
  * row array, so the adapter normalises that to `[]` — matching the empty-array
74
84
  * contract the postgres.js / node-postgres adapters already honour.
75
85
  */
76
- declare const fromMysql2: (connection: Mysql2Like) => SqlClient;
86
+ declare const fromMysql2: (load: () => Promise<Mysql2Like> | Mysql2Like) => SqlClient;
77
87
  /** How an external row maps to a Lunora document. */
78
88
  interface ProjectOptions {
79
89
  /**
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-DKB5Qb1Z.js";
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-DmkKECp5.js";
2
2
  /**
3
3
  * Surface a Cloudflare Hyperdrive binding as a connection ready to feed a
4
4
  * user-supplied SQL driver.
@@ -9,20 +9,25 @@ import { H as HyperdriveLike, a as HyperdriveConnection, M as Mysql2Like, S as S
9
9
  * the binding's connection details out; the user constructs their own driver
10
10
  * from `connectionString` and wraps it with one of the {@link fromPostgresJs} /
11
11
  * {@link fromNodePg} / {@link fromMysql2} adapters to get a {@link SqlClient}.
12
+ * The adapters take a LOADER, not a driver: it runs on the first query, so the
13
+ * driver's `import()` keeps it out of the Worker's startup graph (a static
14
+ * `import postgres from "postgres"` in the entry is evaluated by every isolate
15
+ * before its first event).
12
16
  * @example
13
17
  * ```ts
14
18
  * import { createHyperdrive, fromPostgresJs } from "@lunora/hyperdrive";
15
- * import postgres from "postgres";
16
19
  *
17
20
  * // Wire the client once, on the app builder — called per shard construction,
18
21
  * // not per request. `ctx.sql` is emitted `readonly`; it is never assigned.
19
22
  * const app = defineApp<Env>()
20
23
  * .shard((env) => env.SHARD)
21
- * .hyperdrive((env) => {
22
- * const { connectionString } = createHyperdrive(env.HYPERDRIVE);
24
+ * .hyperdrive((env) =>
25
+ * fromPostgresJs(async () => {
26
+ * const { default: postgres } = await import("postgres");
23
27
  *
24
- * return fromPostgresJs(postgres(connectionString, { fetch_types: false, max: 5 }));
25
- * })
28
+ * return postgres(createHyperdrive(env.HYPERDRIVE).connectionString, { fetch_types: false, max: 5 });
29
+ * }),
30
+ * )
26
31
  * .build();
27
32
  *
28
33
  * // Then, inside an action (never a query/mutation):
@@ -44,7 +49,9 @@ declare const createHyperdrive: (binding: HyperdriveLike) => {
44
49
  connectionString: string;
45
50
  };
46
51
  /**
47
- * Wrap a `postgres` (postgres.js) client as a {@link SqlClient}.
52
+ * Wrap a `postgres` (postgres.js) client, built by `load` on the first query
53
+ * and reused, as a {@link SqlClient}. A failed load rejects that query and the
54
+ * next one retries.
48
55
  *
49
56
  * Uses the driver's `.unsafe(text, params)` escape hatch so the caller supplies
50
57
  * a plain SQL string with `$1, $2, …` placeholders and a positional params
@@ -57,15 +64,18 @@ declare const createHyperdrive: (binding: HyperdriveLike) => {
57
64
  * wins. Prepared reads are cacheable by Hyperdrive, so a `.global()` store's
58
65
  * config must have caching disabled (see `@lunora/hyperdrive/global`).
59
66
  */
60
- declare const fromPostgresJs: (client: PostgresJsLike) => SqlClient;
67
+ declare const fromPostgresJs: (load: () => Promise<PostgresJsLike> | PostgresJsLike) => SqlClient;
61
68
  /**
62
- * Wrap a `pg` (node-postgres) `Client` or `Pool` as a {@link SqlClient}.
69
+ * Wrap a `pg` (node-postgres) `Client` or `Pool`, built by `load` on the first
70
+ * query, as a {@link SqlClient}. `load` may `await client.connect()` before
71
+ * returning.
63
72
  *
64
73
  * node-postgres returns a result object whose `rows` field holds the row array.
65
74
  */
66
- declare const fromNodePg: (client: NodePgLike) => SqlClient;
75
+ declare const fromNodePg: (load: () => Promise<NodePgLike> | NodePgLike) => SqlClient;
67
76
  /**
68
- * Wrap a `mysql2/promise` connection or pool as a {@link SqlClient}.
77
+ * Wrap a `mysql2/promise` connection or pool, built by `load` on the first
78
+ * query, as a {@link SqlClient}.
69
79
  *
70
80
  * Use `?` placeholders (MySQL positional syntax). `mysql2`'s `query` resolves
71
81
  * to a `[rows, fields]` tuple; the adapter returns the first element. For a
@@ -73,7 +83,7 @@ declare const fromNodePg: (client: NodePgLike) => SqlClient;
73
83
  * row array, so the adapter normalises that to `[]` — matching the empty-array
74
84
  * contract the postgres.js / node-postgres adapters already honour.
75
85
  */
76
- declare const fromMysql2: (connection: Mysql2Like) => SqlClient;
86
+ declare const fromMysql2: (load: () => Promise<Mysql2Like> | Mysql2Like) => SqlClient;
77
87
  /** How an external row maps to a Lunora document. */
78
88
  interface ProjectOptions {
79
89
  /**
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-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};
1
+ import{createHyperdrive as e,fromMysql2 as f,fromNodePg as m,fromPostgresJs as p}from"./packem_shared/createHyperdrive-Bw1Au7bE.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 l}from"@lunora/errors";import{t as i,l as d}from"./mysql-params-BYlr9fFZ.mjs";const u=2,c=new WeakSet,w=e=>{const a=e,r=a.config?.clientFlags;if(typeof r=="number")return r;const t=a.pool?.config?.connectionConfig?.clientFlags;return typeof t=="number"?t:void 0},y=e=>{const a=w(e);if(a===void 0){if(c.has(e))return;c.add(e),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&u)===0)throw new l("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.')},p=async(e,a,r)=>{const t=await e.getConnection();let o;try{await t.beginTransaction(),o=await r(a(t)),await t.commit()}catch(n){try{await t.rollback(),t.release()}catch{t.destroy()}throw n}return t.release(),o},f=e=>{throw new l("GLOBAL_TRANSACTION_UNSUPPORTED",`@lunora/hyperdrive: a .global() write needs ${e} to commit its row and companions together; the supplied driver cannot open a transaction. Pass ${e}.`)},h=e=>({all:(a,r)=>e.query(a,r),batch:async a=>{await Promise.all(a.map(r=>e.query(r.sql,r.params)))},run:async(a,r)=>(await e.query(a,r),{rowsAffected:0}),...e.transaction===void 0?{}:{transaction:async a=>await e.transaction(async r=>await a(h(r)))}}),m=e=>{const a=d(async()=>{const t=await e();return y(t),t});return{transaction:async t=>{const o=await a(),{getConnection:n}=o;return typeof n=="function"?await p(o,s=>m(()=>s),t):f("a mysql2 pool (`createPool`)")},all:async(t,o)=>{const n=await a(),[s]=await n.query(t,i(o));return Array.isArray(s)?s:[]},batch:async t=>{const o=await a();await Promise.all(t.map(n=>o.query(n.sql,i(n.params))))},run:async(t,o)=>{const n=await a(),[s]=await n.query(t,i(o));return{rowsAffected:s.affectedRows??0}}}};export{m as buildMysqlExec,h as buildPgExec};
@@ -0,0 +1 @@
1
+ import{LunoraError as c}from"@lunora/errors";import{t as i,l as n}from"./mysql-params-BYlr9fFZ.mjs";const l=r=>({config:{database:r.database,host:r.host,password:r.password,port:r.port,user:r.user},connectionString:r.connectionString}),u=r=>{const t=n(r);return{dbSystem:"postgresql",query:async(s,o=[])=>await(await t()).unsafe(s,o,{prepare:!0}),transaction:async s=>{const{begin:o}=await t();if(o===void 0)throw new c("GLOBAL_TRANSACTION_UNSUPPORTED","@lunora/hyperdrive: the postgres.js client has no `begin()`, so a .global() write cannot commit its row and companions together. Pass a postgres.js client that has `begin()`.");return await o(async e=>await s(u(()=>e)))}}},m=r=>{const t=n(r);return{dbSystem:"postgresql",query:async(a,s=[])=>(await(await t()).query(a,s)).rows}},d=r=>{const t=n(r);return{dbSystem:"mysql",query:async(a,s=[])=>{const o=await t(),[e]=await o.query(a,i(s));return Array.isArray(e)?e:[]}}};export{l as createHyperdrive,d as fromMysql2,m as fromNodePg,u as fromPostgresJs};
@@ -0,0 +1 @@
1
+ const o=t=>{let r;return()=>{if(r!==void 0)return r;const n=(async()=>await t())().catch(e=>{throw r===n&&(r=void 0),e});return r=n,n}},i=t=>({toSqlString:()=>`'${t.replaceAll("\\","\\\\").replaceAll("'","''")}'`,toString:()=>t}),s=t=>t.map((r,n)=>{if(r===void 0)throw new TypeError(`@lunora/hyperdrive: MySQL bind parameter ${String(n+1)} is undefined`);return typeof r=="string"?i(r):r===null||typeof r!="object"||r instanceof Date||ArrayBuffer.isView(r)?r:r instanceof ArrayBuffer?new Uint8Array(r):i(JSON.stringify(r))});export{o as l,s as t};
@@ -98,8 +98,9 @@ interface SqlClient {
98
98
  * transaction: committed when `work` resolves, rolled back when it throws.
99
99
  * {@link import("./create-hyperdrive").fromPostgresJs | fromPostgresJs} provides it over postgres.js's `begin`;
100
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.
101
+ * write and its aggregate/rank/CDC companions commit together. `fromPostgresJs`
102
+ * always exposes it and rejects with `GLOBAL_TRANSACTION_UNSUPPORTED` when the loaded client has no `begin`.
103
+ * Absent (`fromNodePg`), the companions commit one statement at a time.
103
104
  */
104
105
  transaction?: <T>(work: (tx: SqlClient) => Promise<T>) => Promise<T>;
105
106
  }
@@ -98,8 +98,9 @@ interface SqlClient {
98
98
  * transaction: committed when `work` resolves, rolled back when it throws.
99
99
  * {@link import("./create-hyperdrive").fromPostgresJs | fromPostgresJs} provides it over postgres.js's `begin`;
100
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.
101
+ * write and its aggregate/rank/CDC companions commit together. `fromPostgresJs`
102
+ * always exposes it and rejects with `GLOBAL_TRANSACTION_UNSUPPORTED` when the loaded client has no `begin`.
103
+ * Absent (`fromNodePg`), the companions commit one statement at a time.
103
104
  */
104
105
  transaction?: <T>(work: (tx: SqlClient) => Promise<T>) => Promise<T>;
105
106
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/hyperdrive",
3
- "version": "1.0.0-alpha.225",
3
+ "version": "1.0.0-alpha.226",
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.78",
55
- "@lunora/shard-engine": "1.0.0-alpha.182",
56
- "@lunora/sql-store": "1.0.0-alpha.230",
55
+ "@lunora/shard-engine": "1.0.0-alpha.183",
56
+ "@lunora/sql-store": "1.0.0-alpha.231",
57
57
  "drizzle-orm": "1.0.0-rc.5"
58
58
  },
59
59
  "peerDependencies": {
@@ -1 +0,0 @@
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};
@@ -1 +0,0 @@
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};
@@ -1 +0,0 @@
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};