@lunora/hyperdrive 1.0.0-alpha.11 → 1.0.0-alpha.110

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/LICENSE.md CHANGED
@@ -103,3 +103,9 @@ Unless required by applicable law or agreed to in writing, software distributed
103
103
  under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
104
104
  CONDITIONS OF ANY KIND, either express or implied. See the License for the
105
105
  specific language governing permissions and limitations under the License.
106
+
107
+ <!-- DEPENDENCIES -->
108
+ <!-- /DEPENDENCIES -->
109
+
110
+ <!-- TYPE_DEPENDENCIES -->
111
+ <!-- /TYPE_DEPENDENCIES -->
package/README.md CHANGED
@@ -83,19 +83,33 @@ pnpm add mysql2 # mysql2 → fromMysql2
83
83
 
84
84
  ## Usage
85
85
 
86
- `ctx.sql` is wired by codegen onto `ActionCtx` only — never `QueryCtx`/`MutationCtx`. The procedure builders (`action`, `v`) and `api` come from your app-local generated modules:
86
+ `ctx.sql` is wired by codegen onto `ActionCtx` only — never `QueryCtx`/`MutationCtx`, and it is `readonly`, so it is built once at the app level rather than assigned in a handler. Codegen emits a `sql` config thunk for it (`createHyperdrive` returns connection info; only you know which driver wraps it):
87
87
 
88
88
  ```ts
89
+ // src/server/index.ts
90
+ import type { HyperdriveLike } from "@lunora/hyperdrive";
89
91
  import { createHyperdrive, fromPostgresJs } from "@lunora/hyperdrive";
90
92
  import postgres from "postgres";
91
93
 
94
+ import { defineApp } from "../../lunora/_generated/app";
95
+
96
+ const app = defineApp<Env>()
97
+ .shard((env) => env.SHARD)
98
+ .hyperdrive((env) => fromPostgresJs(postgres(createHyperdrive(env.HYPERDRIVE as HyperdriveLike).connectionString)))
99
+ .build();
100
+
101
+ export const ShardDO = app.ShardDO;
102
+ ```
103
+
104
+ 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.
105
+
106
+ Handlers then just read it. The procedure builders (`action`, `v`) and `api` come from your app-local generated modules:
107
+
108
+ ```ts
92
109
  import { api } from "@/lunora/_generated/api";
93
110
  import { action, v } from "@/lunora/_generated/server";
94
111
 
95
112
  export const syncCustomer = action.input({ orgId: v.string() }).action(async ({ args: { orgId }, ctx }) => {
96
- const { connectionString } = createHyperdrive(ctx.env.HYPERDRIVE);
97
- ctx.sql = fromPostgresJs(postgres(connectionString));
98
-
99
113
  const rows = await ctx.sql.query<{ id: string; name: string }>("select id, name from customers where org = $1", [orgId]);
100
114
 
101
115
  // Want it reactive? Project it into a Lunora table — THIS write is tracked.
@@ -113,6 +127,43 @@ export const syncCustomer = action.input({ orgId: v.string() }).action(async ({
113
127
  | `pg` (node-postgres) | `fromNodePg` | `$1, $2, …` |
114
128
  | `mysql2/promise` | `fromMysql2` | `?` |
115
129
 
130
+ ## Reactive reads over your schema: the `.source()` modifier
131
+
132
+ The projection above is the escape hatch. For the common case — an existing Postgres you are not going to move, and a read path that should feel live — declare the source on the table and Lunora runs the whole loop on the shard's alarm: pull, diff, materialize, poke subscribers. No action, no mutation, no cron to write:
133
+
134
+ ```ts
135
+ // lunora/schema.ts
136
+ export default defineSchema({
137
+ messages: defineTable({ body: v.string(), channelId: v.string() })
138
+ .shardBy("channelId")
139
+ .source({
140
+ binding: "HYPERDRIVE_MESSAGES",
141
+ query: 'select id, body, channel_id as "channelId" from messages where channel_id = $1',
142
+ tenantBy: (shardKey) => [shardKey], // mandatory under .shardBy() — the tenant boundary
143
+ }),
144
+ });
145
+
146
+ export const channelMessages = defineShape({ table: "messages", where: () => ({}) });
147
+ ```
148
+
149
+ Supply the driver once, when the shard DO is constructed. Lunora memoizes it per binding:
150
+
151
+ ```ts
152
+ createShardDO({
153
+ sourceClient: (env, binding) => fromPostgresJs(postgres((env[binding] as { connectionString: string }).connectionString)),
154
+ });
155
+ ```
156
+
157
+ Clients subscribe with the shape they would use over any table — external data stops being external once it is materialized.
158
+
159
+ Three things to know before you build on it:
160
+
161
+ - **It polls; it is not logical replication.** Freshness is the refresh cadence: every alarm tick by default, `refresh: { everyMs }` to throttle, `refresh: "manual"` to drive it yourself. Writes from other systems land on the next pull.
162
+ - **`tenantBy` is the isolation boundary.** `defineSchema` throws at load if a sourced `.shardBy()` table omits it, and the `external_source_unscoped` advisor lint catches it at build time. Without it, one tenant's DO pulls every tenant's rows.
163
+ - **`mode: "full-pull"` (the default) diffs the whole slice each tick**, so upstream deletes are detected for free, at a bench ceiling around 10k rows. Past that, `mode: "incremental"` pulls only rows past a durable watermark via a cursor column. An incremental slice cannot see a delete on its own — an absent row means "unchanged", not "deleted" — so it **requires** a delete-visibility path: either `reconcileEveryMs` (a periodic full-pull sweep) or `softDeleteColumn` (an upstream tombstone column the cursor query returns, so do not filter it out). `defineSchema` throws and the `external_source_incremental_no_delete_path` lint fails the build if an incremental source declares neither.
164
+
165
+ `.source()` cannot be combined with `.global()` — they are contradictory tiers, and `defineSchema` rejects it.
166
+
116
167
  ## Reactive `.global()` over Hyperdrive
117
168
 
118
169
  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`:
@@ -127,7 +178,7 @@ const globalDb = createPostgresGlobalCtxDb({ query: (text, params) => sql.unsafe
127
178
 
128
179
  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.
129
180
 
130
- > This README covers the basics. For the full API and the determinism/realtime rationale, see the **[documentation](https://lunora.sh/docs/addons/hyperdrive)**.
181
+ > This README covers the basics. For the full API and the determinism/realtime rationale, see the **[documentation](https://lunora.sh/docs/packages/hyperdrive)**.
131
182
 
132
183
  ## Non-goals
133
184
 
package/dist/global.d.mts CHANGED
@@ -1,47 +1,141 @@
1
- import { DatabaseWriterLike } from '@lunora/do';
1
+ import { DatabaseWriterLike } from '@lunora/shard-engine';
2
2
  import { SqlExec, SqlDialect, SqlCtxDbOptions } from '@lunora/sql-store';
3
- /** Minimal row-returning client (e.g. `@lunora/hyperdrive`'s `fromPostgresJs`/`fromNodePg` result). */
4
- interface RowClient {
5
- query: <Row = Record<string, unknown>>(text: string, params?: ReadonlyArray<unknown>) => Promise<Row[]>;
6
- }
7
- /** Minimal `mysql2/promise` connection/pool surface`execute` resolves to `[rows | ResultSetHeader, fields]`. */
8
- interface Mysql2Execute {
9
- execute: (text: string, params?: ReadonlyArray<unknown>) => Promise<[unknown, unknown]>;
10
- }
3
+ import { M as Mysql2Like, S as SqlClient } from "./packem_shared/types.d-DE1NYxyA.mjs";
4
+ import { VectorMetric, VectorizeIndexLike } from '@lunora/platform';
5
+ /**
6
+ * Minimal row-returning client (e.g. `@lunora/hyperdrive`'s `fromPostgresJs`/`fromNodePg`
7
+ * result). Aliases {@link SqlClient} the exec-facing name is kept so call sites read
8
+ * intent, but the shape is the single source of truth in `./types` (no drift).
9
+ */
10
+ type RowClient = SqlClient;
11
11
  /**
12
- * Wrap a Postgres row-client (from `@lunora/hyperdrive`'s `fromPostgresJs` /
13
- * `fromNodePg`) as a {@link SqlExec}. The core already renders `$N` placeholders
14
- * for Postgres, so `all`/`run` forward verbatim. Postgres uses `RETURNING` for
15
- * OCC (read via `all`), so `run` reports no affected-row count.
16
- */
12
+ * Minimal `mysql2/promise` connection/pool surface `execute` resolves to
13
+ * `[rows | ResultSetHeader, fields]`. Aliases {@link Mysql2Like} so the /global
14
+ * entry's driver surface can never drift from the main entry's, widened with the
15
+ * (optional, structural) shape {@link assertFoundRows}'s `CLIENT_FOUND_ROWS`
16
+ * probe reads.
17
+ *
18
+ * A real `mysql2/promise` connection/pool exposes the merged client-flags
19
+ * bitmask synchronously, but at two different depths depending on which one it
20
+ * is, and NEITHER is in `mysql2`'s own `.d.ts` — its `Pool extends Connection`
21
+ * signature claims a top-level `config`, but at runtime a `mysql2/promise`
22
+ * `Pool` has none; the real value lives one level down, on the core pool it
23
+ * wraps:
24
+ *
25
+ * - a single `Connection`/`PoolConnection` → `connection.config.clientFlags`
26
+ * - a `Pool` (the `createPool({ flags: ["FOUND_ROWS"] })` example above) →
27
+ * `pool.pool.config.connectionConfig.clientFlags`
28
+ *
29
+ * Because those paths are undocumented driver internals, `global-dialect.test.ts`
30
+ * pins them against a REAL `mysql2` pool (built without connecting) rather than
31
+ * only against hand-written doubles — if a `mysql2` release relocates them, the
32
+ * probe would otherwise degrade silently to the warn branch and the OCC guard
33
+ * would go unchecked.
34
+ *
35
+ * Both fields are optional so a minimal `Mysql2Like` test double (only
36
+ * `execute`) safely resolves to "undeterminable" instead of a `TypeError`.
37
+ */
38
+ type Mysql2Execute = Mysql2Like & {
39
+ config?: {
40
+ clientFlags?: number;
41
+ };
42
+ pool?: {
43
+ config?: {
44
+ connectionConfig?: {
45
+ clientFlags?: number;
46
+ };
47
+ };
48
+ };
49
+ };
50
+ /**
51
+ * Wrap a Postgres row-client (from `@lunora/hyperdrive`'s `fromPostgresJs` /
52
+ * `fromNodePg`) as a {@link SqlExec}. The core already renders `$N` placeholders
53
+ * for Postgres, so `all`/`run` forward verbatim. Postgres uses `RETURNING` for
54
+ * OCC (read via `all`), so `run` reports no affected-row count.
55
+ *
56
+ * `batch` dispatches every statement concurrently (`Promise.all`) over `client`
57
+ * rather than awaiting each `query` call in turn — `RowClient` only exposes a
58
+ * single-statement `query`, so there is no wire-level multi-statement command
59
+ * to reach for. When `client` is backed by a pool (the common production
60
+ * shape), this genuinely spreads the statements across multiple physical
61
+ * connections instead of serializing one full round trip at a time; against a
62
+ * single connection it still removes the sequential *await*, though the
63
+ * underlying driver may itself queue the sends. Either way it stays
64
+ * non-atomic, at-least-once, and unordered between elements, same as the
65
+ * sequential fallback minus the ordering — safe only for statements whose
66
+ * effects don't depend on each other, which is what every current caller
67
+ * batches (distinct-keyed companion rows).
68
+ */
17
69
  declare const buildPgExec: (client: RowClient) => SqlExec;
18
70
  /**
19
- * Wrap a `mysql2/promise` connection/pool as a {@link SqlExec}. The core already
20
- * renders backtick identifiers and `?` placeholders for MySQL, so `all`/`run`
21
- * forward verbatim. MySQL has no `RETURNING`, so `run` surfaces `affectedRows`
22
- * for the store's affected-rows OCC guard.
23
- *
24
- * **The connection MUST be created with the `CLIENT_FOUND_ROWS` flag**
25
- * (mysql2: `createPool({ flags: ["FOUND_ROWS"] })`). Without it, `affectedRows`
26
- * counts *changed* rows, so an idempotent `patch`/`replace` that re-writes the
27
- * same values reports 0 and the OCC guard raises a spurious conflict.
28
- */
71
+ * Wrap a `mysql2/promise` connection/pool as a {@link SqlExec}. The core already
72
+ * renders backtick identifiers and `?` placeholders for MySQL, so `all`/`run`
73
+ * forward verbatim. MySQL has no `RETURNING`, so `run` surfaces `affectedRows`
74
+ * for the store's affected-rows OCC guard.
75
+ *
76
+ * **The connection MUST be created with the `CLIENT_FOUND_ROWS` flag**
77
+ * (mysql2: `createPool({ flags: ["FOUND_ROWS"] })`). Without it, `affectedRows`
78
+ * counts *changed* rows, so an idempotent `patch`/`replace` that re-writes the
79
+ * same values reports 0 and the OCC guard raises a spurious conflict. Verified
80
+ * once here at construction — see {@link assertFoundRows} — never per statement.
81
+ *
82
+ * `batch` dispatches every statement concurrently (`Promise.all`), same
83
+ * rationale as {@link buildPgExec}'s `batch` — `Mysql2Like` only exposes a
84
+ * single-statement `execute`, so this is "spread across a pool's connections"
85
+ * rather than one wire-level multi-statement command; still non-atomic,
86
+ * at-least-once, and unordered between elements, safe only for statements
87
+ * with no cross-effect (what every current caller batches).
88
+ */
29
89
  declare const buildMysqlExec: (connection: Mysql2Execute) => SqlExec;
30
90
  /**
31
- * **Postgres** dialect. Differs from SQLite only in column types
32
- * (`DOUBLE PRECISION`/`BYTEA`/`BIGSERIAL`), the `information_schema` catalog
33
- * probe, and unique-violation detection (SQLSTATE `23505`).
34
- */
91
+ * **Postgres** dialect. Differs from SQLite only in column types
92
+ * (`DOUBLE PRECISION`/`BYTEA`/`BIGSERIAL`), the `information_schema` catalog
93
+ * probe, and unique-violation detection (SQLSTATE `23505`).
94
+ */
35
95
  declare const postgresDialect: SqlDialect;
36
96
  /**
37
- * **MySQL** dialect. Diverges in: **no `RETURNING`** (the store's OCC falls back
38
- * to affected-rows — which requires the connection's `CLIENT_FOUND_ROWS` flag so
39
- * a no-op update still reports a matched row, see `buildMysqlExec`); bounded
40
- * `VARCHAR` keys (TEXT can't be a primary key / unindexed); a TEXT/BLOB index key
41
- * prefix; and `ER_DUP_ENTRY` (errno 1062) unique violations. (Drizzle's MySQL
42
- * dialect supplies the backtick identifiers + `ON DUPLICATE KEY` upserts.)
43
- */
97
+ * **MySQL** dialect. Diverges in: **no `RETURNING`** (the store's OCC falls back
98
+ * to affected-rows — which requires the connection's `CLIENT_FOUND_ROWS` flag so
99
+ * a no-op update still reports a matched row, see `buildMysqlExec`); bounded
100
+ * `VARCHAR` keys (TEXT can't be a primary key / unindexed); a TEXT/BLOB index key
101
+ * prefix; and `ER_DUP_ENTRY` (errno 1062) unique violations. (Drizzle's MySQL
102
+ * dialect supplies the backtick identifiers + `ON DUPLICATE KEY` upserts.)
103
+ */
44
104
  declare const mysqlDialect: SqlDialect;
105
+ /** Options for {@link createPgVectorIndex}. */
106
+ interface PgVectorIndexOptions {
107
+ /** Postgres client — the same `SqlClient` the Hyperdrive global store takes. */
108
+ client: SqlClient;
109
+ /**
110
+ * Vector width. Must match the embedder's output and the `dimensions` on the
111
+ * schema's `.vectorize(...)`; Postgres enforces it per row, so a mismatch
112
+ * surfaces as a write error rather than silently bad neighbours.
113
+ */
114
+ dimensions: number;
115
+ /** Distance metric. Defaults to `"cosine"`, matching the common embedding case. */
116
+ metric?: VectorMetric;
117
+ /**
118
+ * Names the backing table (`__vec_<name>`), derived rather than configurable —
119
+ * a second name for one index is a way to lose data, not a feature.
120
+ *
121
+ * This is NOT required to equal the key in the shard's `vectors` map, and often
122
+ * cannot: that key mirrors the schema's `.vectorize({ index })`, which may carry
123
+ * hyphens (`"docs-body"`), while this must be a bare SQL identifier. Keep it
124
+ * stable — changing it points the index at a different, empty table.
125
+ */
126
+ name: string;
127
+ }
128
+ /**
129
+ * Build a `pgvector`-backed vector index that satisfies {@link VectorizeIndexLike}.
130
+ *
131
+ * Requires the `vector` extension to be installable by the connecting role
132
+ * (`CREATE EXTENSION IF NOT EXISTS vector`). On a managed Postgres where the
133
+ * role cannot create extensions, install it once out-of-band; the statement is
134
+ * then a no-op.
135
+ * @param options See {@link PgVectorIndexOptions}.
136
+ * @returns An index object the shard's `vectors` map accepts as-is.
137
+ */
138
+ declare const createPgVectorIndex: (options: PgVectorIndexOptions) => VectorizeIndexLike;
45
139
  /** Which engine a Hyperdrive-backed `.global()` store targets. */
46
140
  type HyperdriveEngine = "mysql" | "postgres";
47
141
  /** Options for {@link createHyperdriveGlobalCtxDb}: the store options minus `exec`/`dialect`, plus the engine and a built `SqlExec`. */
@@ -52,18 +146,14 @@ interface CreateHyperdriveGlobalCtxDbOptions extends Omit<SqlCtxDbOptions, "dial
52
146
  exec: SqlExec;
53
147
  }
54
148
  /**
55
- * Build a reactive `.global()` writer backed by a Hyperdrive-reachable
56
- * Postgres/MySQL database. Pass a built {@link SqlExec} (via {@link buildPgExec}/
57
- * {@link buildMysqlExec}) and the matching `engine`; everything else mirrors the
58
- * D1 store options.
59
- */
60
- declare const createHyperdriveGlobalCtxDb: ({
61
- engine,
62
- exec,
63
- ...rest
64
- }: CreateHyperdriveGlobalCtxDbOptions) => DatabaseWriterLike;
149
+ * Build a reactive `.global()` writer backed by a Hyperdrive-reachable
150
+ * Postgres/MySQL database. Pass a built {@link SqlExec} (via {@link buildPgExec}/
151
+ * {@link buildMysqlExec}) and the matching `engine`; everything else mirrors the
152
+ * D1 store options.
153
+ */
154
+ declare const createHyperdriveGlobalCtxDb: ({ engine, exec, ...rest }: CreateHyperdriveGlobalCtxDbOptions) => DatabaseWriterLike;
65
155
  /** Convenience: a **Postgres** `.global()` writer from a row-client (postgres.js/pg over Hyperdrive). */
66
156
  declare const createPostgresGlobalCtxDb: (client: RowClient, options: Omit<CreateHyperdriveGlobalCtxDbOptions, "engine" | "exec">) => DatabaseWriterLike;
67
157
  /** Convenience: a **MySQL** `.global()` writer from a `mysql2/promise` connection/pool (created with `flags: ["FOUND_ROWS"]`). */
68
158
  declare const createMysqlGlobalCtxDb: (connection: Mysql2Execute, options: Omit<CreateHyperdriveGlobalCtxDbOptions, "engine" | "exec">) => DatabaseWriterLike;
69
- export { CreateHyperdriveGlobalCtxDbOptions, HyperdriveEngine, type Mysql2Execute, type RowClient, buildMysqlExec, buildPgExec, createHyperdriveGlobalCtxDb, createMysqlGlobalCtxDb, createPostgresGlobalCtxDb, mysqlDialect, postgresDialect };
159
+ export { CreateHyperdriveGlobalCtxDbOptions, HyperdriveEngine, type Mysql2Execute, type PgVectorIndexOptions, type RowClient, buildMysqlExec, buildPgExec, createHyperdriveGlobalCtxDb, createMysqlGlobalCtxDb, createPgVectorIndex, createPostgresGlobalCtxDb, mysqlDialect, postgresDialect };
package/dist/global.d.ts CHANGED
@@ -1,47 +1,141 @@
1
- import { DatabaseWriterLike } from '@lunora/do';
1
+ import { DatabaseWriterLike } from '@lunora/shard-engine';
2
2
  import { SqlExec, SqlDialect, SqlCtxDbOptions } from '@lunora/sql-store';
3
- /** Minimal row-returning client (e.g. `@lunora/hyperdrive`'s `fromPostgresJs`/`fromNodePg` result). */
4
- interface RowClient {
5
- query: <Row = Record<string, unknown>>(text: string, params?: ReadonlyArray<unknown>) => Promise<Row[]>;
6
- }
7
- /** Minimal `mysql2/promise` connection/pool surface`execute` resolves to `[rows | ResultSetHeader, fields]`. */
8
- interface Mysql2Execute {
9
- execute: (text: string, params?: ReadonlyArray<unknown>) => Promise<[unknown, unknown]>;
10
- }
3
+ import { M as Mysql2Like, S as SqlClient } from "./packem_shared/types.d-DE1NYxyA.js";
4
+ import { VectorMetric, VectorizeIndexLike } from '@lunora/platform';
5
+ /**
6
+ * Minimal row-returning client (e.g. `@lunora/hyperdrive`'s `fromPostgresJs`/`fromNodePg`
7
+ * result). Aliases {@link SqlClient} the exec-facing name is kept so call sites read
8
+ * intent, but the shape is the single source of truth in `./types` (no drift).
9
+ */
10
+ type RowClient = SqlClient;
11
11
  /**
12
- * Wrap a Postgres row-client (from `@lunora/hyperdrive`'s `fromPostgresJs` /
13
- * `fromNodePg`) as a {@link SqlExec}. The core already renders `$N` placeholders
14
- * for Postgres, so `all`/`run` forward verbatim. Postgres uses `RETURNING` for
15
- * OCC (read via `all`), so `run` reports no affected-row count.
16
- */
12
+ * Minimal `mysql2/promise` connection/pool surface `execute` resolves to
13
+ * `[rows | ResultSetHeader, fields]`. Aliases {@link Mysql2Like} so the /global
14
+ * entry's driver surface can never drift from the main entry's, widened with the
15
+ * (optional, structural) shape {@link assertFoundRows}'s `CLIENT_FOUND_ROWS`
16
+ * probe reads.
17
+ *
18
+ * A real `mysql2/promise` connection/pool exposes the merged client-flags
19
+ * bitmask synchronously, but at two different depths depending on which one it
20
+ * is, and NEITHER is in `mysql2`'s own `.d.ts` — its `Pool extends Connection`
21
+ * signature claims a top-level `config`, but at runtime a `mysql2/promise`
22
+ * `Pool` has none; the real value lives one level down, on the core pool it
23
+ * wraps:
24
+ *
25
+ * - a single `Connection`/`PoolConnection` → `connection.config.clientFlags`
26
+ * - a `Pool` (the `createPool({ flags: ["FOUND_ROWS"] })` example above) →
27
+ * `pool.pool.config.connectionConfig.clientFlags`
28
+ *
29
+ * Because those paths are undocumented driver internals, `global-dialect.test.ts`
30
+ * pins them against a REAL `mysql2` pool (built without connecting) rather than
31
+ * only against hand-written doubles — if a `mysql2` release relocates them, the
32
+ * probe would otherwise degrade silently to the warn branch and the OCC guard
33
+ * would go unchecked.
34
+ *
35
+ * Both fields are optional so a minimal `Mysql2Like` test double (only
36
+ * `execute`) safely resolves to "undeterminable" instead of a `TypeError`.
37
+ */
38
+ type Mysql2Execute = Mysql2Like & {
39
+ config?: {
40
+ clientFlags?: number;
41
+ };
42
+ pool?: {
43
+ config?: {
44
+ connectionConfig?: {
45
+ clientFlags?: number;
46
+ };
47
+ };
48
+ };
49
+ };
50
+ /**
51
+ * Wrap a Postgres row-client (from `@lunora/hyperdrive`'s `fromPostgresJs` /
52
+ * `fromNodePg`) as a {@link SqlExec}. The core already renders `$N` placeholders
53
+ * for Postgres, so `all`/`run` forward verbatim. Postgres uses `RETURNING` for
54
+ * OCC (read via `all`), so `run` reports no affected-row count.
55
+ *
56
+ * `batch` dispatches every statement concurrently (`Promise.all`) over `client`
57
+ * rather than awaiting each `query` call in turn — `RowClient` only exposes a
58
+ * single-statement `query`, so there is no wire-level multi-statement command
59
+ * to reach for. When `client` is backed by a pool (the common production
60
+ * shape), this genuinely spreads the statements across multiple physical
61
+ * connections instead of serializing one full round trip at a time; against a
62
+ * single connection it still removes the sequential *await*, though the
63
+ * underlying driver may itself queue the sends. Either way it stays
64
+ * non-atomic, at-least-once, and unordered between elements, same as the
65
+ * sequential fallback minus the ordering — safe only for statements whose
66
+ * effects don't depend on each other, which is what every current caller
67
+ * batches (distinct-keyed companion rows).
68
+ */
17
69
  declare const buildPgExec: (client: RowClient) => SqlExec;
18
70
  /**
19
- * Wrap a `mysql2/promise` connection/pool as a {@link SqlExec}. The core already
20
- * renders backtick identifiers and `?` placeholders for MySQL, so `all`/`run`
21
- * forward verbatim. MySQL has no `RETURNING`, so `run` surfaces `affectedRows`
22
- * for the store's affected-rows OCC guard.
23
- *
24
- * **The connection MUST be created with the `CLIENT_FOUND_ROWS` flag**
25
- * (mysql2: `createPool({ flags: ["FOUND_ROWS"] })`). Without it, `affectedRows`
26
- * counts *changed* rows, so an idempotent `patch`/`replace` that re-writes the
27
- * same values reports 0 and the OCC guard raises a spurious conflict.
28
- */
71
+ * Wrap a `mysql2/promise` connection/pool as a {@link SqlExec}. The core already
72
+ * renders backtick identifiers and `?` placeholders for MySQL, so `all`/`run`
73
+ * forward verbatim. MySQL has no `RETURNING`, so `run` surfaces `affectedRows`
74
+ * for the store's affected-rows OCC guard.
75
+ *
76
+ * **The connection MUST be created with the `CLIENT_FOUND_ROWS` flag**
77
+ * (mysql2: `createPool({ flags: ["FOUND_ROWS"] })`). Without it, `affectedRows`
78
+ * counts *changed* rows, so an idempotent `patch`/`replace` that re-writes the
79
+ * same values reports 0 and the OCC guard raises a spurious conflict. Verified
80
+ * once here at construction — see {@link assertFoundRows} — never per statement.
81
+ *
82
+ * `batch` dispatches every statement concurrently (`Promise.all`), same
83
+ * rationale as {@link buildPgExec}'s `batch` — `Mysql2Like` only exposes a
84
+ * single-statement `execute`, so this is "spread across a pool's connections"
85
+ * rather than one wire-level multi-statement command; still non-atomic,
86
+ * at-least-once, and unordered between elements, safe only for statements
87
+ * with no cross-effect (what every current caller batches).
88
+ */
29
89
  declare const buildMysqlExec: (connection: Mysql2Execute) => SqlExec;
30
90
  /**
31
- * **Postgres** dialect. Differs from SQLite only in column types
32
- * (`DOUBLE PRECISION`/`BYTEA`/`BIGSERIAL`), the `information_schema` catalog
33
- * probe, and unique-violation detection (SQLSTATE `23505`).
34
- */
91
+ * **Postgres** dialect. Differs from SQLite only in column types
92
+ * (`DOUBLE PRECISION`/`BYTEA`/`BIGSERIAL`), the `information_schema` catalog
93
+ * probe, and unique-violation detection (SQLSTATE `23505`).
94
+ */
35
95
  declare const postgresDialect: SqlDialect;
36
96
  /**
37
- * **MySQL** dialect. Diverges in: **no `RETURNING`** (the store's OCC falls back
38
- * to affected-rows — which requires the connection's `CLIENT_FOUND_ROWS` flag so
39
- * a no-op update still reports a matched row, see `buildMysqlExec`); bounded
40
- * `VARCHAR` keys (TEXT can't be a primary key / unindexed); a TEXT/BLOB index key
41
- * prefix; and `ER_DUP_ENTRY` (errno 1062) unique violations. (Drizzle's MySQL
42
- * dialect supplies the backtick identifiers + `ON DUPLICATE KEY` upserts.)
43
- */
97
+ * **MySQL** dialect. Diverges in: **no `RETURNING`** (the store's OCC falls back
98
+ * to affected-rows — which requires the connection's `CLIENT_FOUND_ROWS` flag so
99
+ * a no-op update still reports a matched row, see `buildMysqlExec`); bounded
100
+ * `VARCHAR` keys (TEXT can't be a primary key / unindexed); a TEXT/BLOB index key
101
+ * prefix; and `ER_DUP_ENTRY` (errno 1062) unique violations. (Drizzle's MySQL
102
+ * dialect supplies the backtick identifiers + `ON DUPLICATE KEY` upserts.)
103
+ */
44
104
  declare const mysqlDialect: SqlDialect;
105
+ /** Options for {@link createPgVectorIndex}. */
106
+ interface PgVectorIndexOptions {
107
+ /** Postgres client — the same `SqlClient` the Hyperdrive global store takes. */
108
+ client: SqlClient;
109
+ /**
110
+ * Vector width. Must match the embedder's output and the `dimensions` on the
111
+ * schema's `.vectorize(...)`; Postgres enforces it per row, so a mismatch
112
+ * surfaces as a write error rather than silently bad neighbours.
113
+ */
114
+ dimensions: number;
115
+ /** Distance metric. Defaults to `"cosine"`, matching the common embedding case. */
116
+ metric?: VectorMetric;
117
+ /**
118
+ * Names the backing table (`__vec_<name>`), derived rather than configurable —
119
+ * a second name for one index is a way to lose data, not a feature.
120
+ *
121
+ * This is NOT required to equal the key in the shard's `vectors` map, and often
122
+ * cannot: that key mirrors the schema's `.vectorize({ index })`, which may carry
123
+ * hyphens (`"docs-body"`), while this must be a bare SQL identifier. Keep it
124
+ * stable — changing it points the index at a different, empty table.
125
+ */
126
+ name: string;
127
+ }
128
+ /**
129
+ * Build a `pgvector`-backed vector index that satisfies {@link VectorizeIndexLike}.
130
+ *
131
+ * Requires the `vector` extension to be installable by the connecting role
132
+ * (`CREATE EXTENSION IF NOT EXISTS vector`). On a managed Postgres where the
133
+ * role cannot create extensions, install it once out-of-band; the statement is
134
+ * then a no-op.
135
+ * @param options See {@link PgVectorIndexOptions}.
136
+ * @returns An index object the shard's `vectors` map accepts as-is.
137
+ */
138
+ declare const createPgVectorIndex: (options: PgVectorIndexOptions) => VectorizeIndexLike;
45
139
  /** Which engine a Hyperdrive-backed `.global()` store targets. */
46
140
  type HyperdriveEngine = "mysql" | "postgres";
47
141
  /** Options for {@link createHyperdriveGlobalCtxDb}: the store options minus `exec`/`dialect`, plus the engine and a built `SqlExec`. */
@@ -52,18 +146,14 @@ interface CreateHyperdriveGlobalCtxDbOptions extends Omit<SqlCtxDbOptions, "dial
52
146
  exec: SqlExec;
53
147
  }
54
148
  /**
55
- * Build a reactive `.global()` writer backed by a Hyperdrive-reachable
56
- * Postgres/MySQL database. Pass a built {@link SqlExec} (via {@link buildPgExec}/
57
- * {@link buildMysqlExec}) and the matching `engine`; everything else mirrors the
58
- * D1 store options.
59
- */
60
- declare const createHyperdriveGlobalCtxDb: ({
61
- engine,
62
- exec,
63
- ...rest
64
- }: CreateHyperdriveGlobalCtxDbOptions) => DatabaseWriterLike;
149
+ * Build a reactive `.global()` writer backed by a Hyperdrive-reachable
150
+ * Postgres/MySQL database. Pass a built {@link SqlExec} (via {@link buildPgExec}/
151
+ * {@link buildMysqlExec}) and the matching `engine`; everything else mirrors the
152
+ * D1 store options.
153
+ */
154
+ declare const createHyperdriveGlobalCtxDb: ({ engine, exec, ...rest }: CreateHyperdriveGlobalCtxDbOptions) => DatabaseWriterLike;
65
155
  /** Convenience: a **Postgres** `.global()` writer from a row-client (postgres.js/pg over Hyperdrive). */
66
156
  declare const createPostgresGlobalCtxDb: (client: RowClient, options: Omit<CreateHyperdriveGlobalCtxDbOptions, "engine" | "exec">) => DatabaseWriterLike;
67
157
  /** Convenience: a **MySQL** `.global()` writer from a `mysql2/promise` connection/pool (created with `flags: ["FOUND_ROWS"]`). */
68
158
  declare const createMysqlGlobalCtxDb: (connection: Mysql2Execute, options: Omit<CreateHyperdriveGlobalCtxDbOptions, "engine" | "exec">) => DatabaseWriterLike;
69
- export { CreateHyperdriveGlobalCtxDbOptions, HyperdriveEngine, type Mysql2Execute, type RowClient, buildMysqlExec, buildPgExec, createHyperdriveGlobalCtxDb, createMysqlGlobalCtxDb, createPostgresGlobalCtxDb, mysqlDialect, postgresDialect };
159
+ export { CreateHyperdriveGlobalCtxDbOptions, HyperdriveEngine, type Mysql2Execute, type PgVectorIndexOptions, type RowClient, buildMysqlExec, buildPgExec, createHyperdriveGlobalCtxDb, createMysqlGlobalCtxDb, createPgVectorIndex, createPostgresGlobalCtxDb, mysqlDialect, postgresDialect };
package/dist/global.mjs CHANGED
@@ -1,9 +1 @@
1
- import { createSqlCtxDb } from '@lunora/sql-store';
2
- import { postgresDialect, mysqlDialect } from './packem_shared/mysqlDialect-oNhZ58s8.mjs';
3
- import { buildMysqlExec, buildPgExec } from './packem_shared/buildMysqlExec-DBbCjyq3.mjs';
4
-
5
- const createHyperdriveGlobalCtxDb = ({ engine, exec, ...rest }) => createSqlCtxDb({ ...rest, dialect: engine === "postgres" ? postgresDialect : mysqlDialect, exec });
6
- const createPostgresGlobalCtxDb = (client, options) => createHyperdriveGlobalCtxDb({ ...options, engine: "postgres", exec: buildPgExec(client) });
7
- const createMysqlGlobalCtxDb = (connection, options) => createHyperdriveGlobalCtxDb({ ...options, engine: "mysql", exec: buildMysqlExec(connection) });
8
-
9
- export { buildMysqlExec, buildPgExec, createHyperdriveGlobalCtxDb, createMysqlGlobalCtxDb, createPostgresGlobalCtxDb, mysqlDialect, postgresDialect };
1
+ import{createSqlCtxDb as c}from"@lunora/sql-store";import{postgresDialect as l,mysqlDialect as s}from"./packem_shared/mysqlDialect-B0_iJVGV.mjs";import{buildMysqlExec as a,buildPgExec as i}from"./packem_shared/buildMysqlExec-BTmXrDGL.mjs";import{createPgVectorIndex as d}from"./packem_shared/createPgVectorIndex-DtazJ48m.mjs";const r=({engine:e,exec:t,...o})=>c({...o,dialect:e==="postgres"?l:s,exec:t}),p=(e,t)=>r({...t,engine:"postgres",exec:i(e)}),g=(e,t)=>r({...t,engine:"mysql",exec:a(e)});export{a as buildMysqlExec,i as buildPgExec,r as createHyperdriveGlobalCtxDb,g as createMysqlGlobalCtxDb,d as createPgVectorIndex,p as createPostgresGlobalCtxDb,s as mysqlDialect,l as postgresDialect};