@lunora/hyperdrive 1.0.0-alpha.13 → 1.0.0-alpha.130
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 +6 -0
- package/README.md +58 -6
- package/dist/global.d.mts +136 -46
- package/dist/global.d.ts +136 -46
- package/dist/global.mjs +1 -9
- package/dist/index.d.mts +140 -152
- package/dist/index.d.ts +140 -152
- package/dist/index.mjs +1 -1
- package/dist/packem_shared/buildMysqlExec-BTmXrDGL.mjs +1 -0
- package/dist/packem_shared/createHyperdrive-DEXMfGd0.mjs +1 -0
- package/dist/packem_shared/createPgVectorIndex-CSNnG065.mjs +1 -0
- package/dist/packem_shared/mysqlDialect-B0_iJVGV.mjs +1 -0
- package/dist/packem_shared/projectSourceRow-DN8ywPRl.mjs +1 -0
- package/dist/packem_shared/types.d-DpWhWTrT.d.mts +150 -0
- package/dist/packem_shared/types.d-DpWhWTrT.d.ts +150 -0
- package/package.json +5 -3
- package/dist/packem_shared/buildMysqlExec-DBbCjyq3.mjs +0 -23
- package/dist/packem_shared/createHyperdrive-DD8GoDZo.mjs +0 -36
- package/dist/packem_shared/mysqlDialect-oNhZ58s8.mjs +0 -102
|
@@ -0,0 +1,150 @@
|
|
|
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
|
+
* 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
|
+
* Method syntax, not an arrow property — see {@link PostgresJsLike}.
|
|
93
|
+
*/
|
|
94
|
+
interface NodePgLike {
|
|
95
|
+
query(this: void, text: string, params?: ReadonlyArray<unknown>): Promise<{
|
|
96
|
+
rows: unknown[];
|
|
97
|
+
}>;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Structural projection of a `postgres` (postgres.js) tagged-template client.
|
|
101
|
+
* The adapter uses the `.unsafe(text, params)` escape hatch so callers keep
|
|
102
|
+
* full control of the parameter list.
|
|
103
|
+
*
|
|
104
|
+
* **`unsafe` is declared with METHOD syntax, and that is load-bearing.** Under
|
|
105
|
+
* `strictFunctionTypes` an arrow PROPERTY is checked contravariantly in its
|
|
106
|
+
* parameters, so a driver whose `unsafe` takes a MUTABLE array — which real
|
|
107
|
+
* postgres.js does (`parameters?: ParameterOrJSON<never>[]`) — is not assignable
|
|
108
|
+
* to one declared `params?: ReadonlyArray<unknown>`. As an arrow property this
|
|
109
|
+
* projection could therefore never accept the package it projects: every
|
|
110
|
+
* consumer got
|
|
111
|
+
* `TS2345: Argument of type 'Sql<{}>' is not assignable to parameter of type
|
|
112
|
+
* 'PostgresJsLike'`, and this repo's own test reached for
|
|
113
|
+
* `as unknown as PostgresJsLike` to get past it. Method syntax is checked
|
|
114
|
+
* bivariantly, which is what a structural projection of someone else's type
|
|
115
|
+
* needs — `readonly unknown[]` and postgres.js's mutable array relate in one
|
|
116
|
+
* direction, which is all bivariance asks for.
|
|
117
|
+
*/
|
|
118
|
+
interface PostgresJsLike {
|
|
119
|
+
unsafe(this: void, text: string, params?: ReadonlyArray<unknown>): Promise<unknown>;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Structural projection of a `mysql2/promise` connection/pool. `mysql2`'s
|
|
123
|
+
* `execute` resolves to a `[rows, fields]` tuple; the adapter takes the first
|
|
124
|
+
* element as the rows.
|
|
125
|
+
*
|
|
126
|
+
* Method syntax, not an arrow property — see {@link PostgresJsLike} — but here
|
|
127
|
+
* that is necessary and NOT sufficient, which is why `params` is `unknown`.
|
|
128
|
+
*
|
|
129
|
+
* mysql2 types its second argument as a single `ExecuteValues`: a union of
|
|
130
|
+
* scalars, `ExecuteValues[]` and `{ [key: string]: ExecuteValues }`. Declared
|
|
131
|
+
* `params?: ReadonlyArray<unknown>` this projection could never accept the
|
|
132
|
+
* driver it projects — bivariance needs the two parameter types to relate in
|
|
133
|
+
* ONE direction and they relate in neither (`readonly unknown[] | undefined` is
|
|
134
|
+
* not an `ExecuteValues`, and a `string` is not an array).
|
|
135
|
+
*
|
|
136
|
+
* `unknown` is the narrowest supertype of `ExecuteValues` that does not restate
|
|
137
|
+
* mysql2's union here. Restating it would be the vacuous shim all over again —
|
|
138
|
+
* a local copy that drifts from the package it mirrors and makes the gate pass
|
|
139
|
+
* while a consumer fails.
|
|
140
|
+
*
|
|
141
|
+
* The looseness is confined to the INPUT position: this types the driver a
|
|
142
|
+
* caller hands to `fromMysql2`, not what anyone passes to
|
|
143
|
+
* {@link SqlClient.query}, which still declares `ReadonlyArray<unknown>`. Every
|
|
144
|
+
* call site in this package forwards an array, and the driver validates the
|
|
145
|
+
* values at runtime regardless.
|
|
146
|
+
*/
|
|
147
|
+
interface Mysql2Like {
|
|
148
|
+
execute(this: void, text: string, params?: unknown): Promise<[unknown, unknown]>;
|
|
149
|
+
}
|
|
150
|
+
export { HyperdriveLike as H, Mysql2Like as M, NodePgLike as N, PostgresJsLike as P, SqlClient as S, HyperdriveConnection as a };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/hyperdrive",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.130",
|
|
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",
|
|
@@ -50,8 +50,10 @@
|
|
|
50
50
|
"access": "public"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@lunora/
|
|
54
|
-
"@lunora/
|
|
53
|
+
"@lunora/errors": "1.0.0-alpha.41",
|
|
54
|
+
"@lunora/platform": "1.0.0-alpha.34",
|
|
55
|
+
"@lunora/shard-engine": "1.0.0-alpha.80",
|
|
56
|
+
"@lunora/sql-store": "1.0.0-alpha.132",
|
|
55
57
|
"drizzle-orm": "^0.45.2"
|
|
56
58
|
},
|
|
57
59
|
"peerDependencies": {
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
const buildPgExec = (client) => {
|
|
2
|
-
return {
|
|
3
|
-
all: (sql, params) => client.query(sql, params),
|
|
4
|
-
run: async (sql, params) => {
|
|
5
|
-
await client.query(sql, params);
|
|
6
|
-
return { rowsAffected: 0 };
|
|
7
|
-
}
|
|
8
|
-
};
|
|
9
|
-
};
|
|
10
|
-
const buildMysqlExec = (connection) => {
|
|
11
|
-
return {
|
|
12
|
-
all: async (sql, params) => {
|
|
13
|
-
const [rows] = await connection.execute(sql, params);
|
|
14
|
-
return rows;
|
|
15
|
-
},
|
|
16
|
-
run: async (sql, params) => {
|
|
17
|
-
const [result] = await connection.execute(sql, params);
|
|
18
|
-
return { rowsAffected: result.affectedRows ?? 0 };
|
|
19
|
-
}
|
|
20
|
-
};
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export { buildMysqlExec, buildPgExec };
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
const createHyperdrive = (binding) => {
|
|
2
|
-
const config = {
|
|
3
|
-
database: binding.database,
|
|
4
|
-
host: binding.host,
|
|
5
|
-
password: binding.password,
|
|
6
|
-
port: binding.port,
|
|
7
|
-
user: binding.user
|
|
8
|
-
};
|
|
9
|
-
return { config, connectionString: binding.connectionString };
|
|
10
|
-
};
|
|
11
|
-
const fromPostgresJs = (client) => {
|
|
12
|
-
return {
|
|
13
|
-
query: async (text, params = []) => {
|
|
14
|
-
const rows = await client.unsafe(text, params);
|
|
15
|
-
return rows;
|
|
16
|
-
}
|
|
17
|
-
};
|
|
18
|
-
};
|
|
19
|
-
const fromNodePg = (client) => {
|
|
20
|
-
return {
|
|
21
|
-
query: async (text, params = []) => {
|
|
22
|
-
const result = await client.query(text, params);
|
|
23
|
-
return result.rows;
|
|
24
|
-
}
|
|
25
|
-
};
|
|
26
|
-
};
|
|
27
|
-
const fromMysql2 = (connection) => {
|
|
28
|
-
return {
|
|
29
|
-
query: async (text, params = []) => {
|
|
30
|
-
const [rows] = await connection.execute(text, params);
|
|
31
|
-
return Array.isArray(rows) ? rows : [];
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
export { createHyperdrive, fromMysql2, fromNodePg, fromPostgresJs };
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import { sqliteEncode, sqliteDecode } from '@lunora/sql-store';
|
|
2
|
-
import { sql } from 'drizzle-orm';
|
|
3
|
-
|
|
4
|
-
const PG_UNIQUE_VIOLATION_RE = /duplicate key value violates unique constraint/iu;
|
|
5
|
-
const mysqlColumnType = (kind) => {
|
|
6
|
-
switch (kind) {
|
|
7
|
-
case "array":
|
|
8
|
-
case "object":
|
|
9
|
-
case "record": {
|
|
10
|
-
return "JSON";
|
|
11
|
-
}
|
|
12
|
-
case "bigint": {
|
|
13
|
-
return "VARCHAR(64)";
|
|
14
|
-
}
|
|
15
|
-
case "boolean": {
|
|
16
|
-
return "TINYINT";
|
|
17
|
-
}
|
|
18
|
-
case "bytes": {
|
|
19
|
-
return "LONGBLOB";
|
|
20
|
-
}
|
|
21
|
-
case "date":
|
|
22
|
-
case "number":
|
|
23
|
-
case "timestamp": {
|
|
24
|
-
return "DOUBLE";
|
|
25
|
-
}
|
|
26
|
-
default: {
|
|
27
|
-
return "LONGTEXT";
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
const MYSQL_PREFIX_INDEX_RE = /TEXT|BLOB/u;
|
|
32
|
-
const postgresDialect = {
|
|
33
|
-
companionTypes: {
|
|
34
|
-
autoincrementPrimaryKey: "BIGSERIAL PRIMARY KEY",
|
|
35
|
-
integer: "INTEGER",
|
|
36
|
-
key: "TEXT",
|
|
37
|
-
real: "DOUBLE PRECISION",
|
|
38
|
-
text: "TEXT"
|
|
39
|
-
},
|
|
40
|
-
columnType: (kind) => {
|
|
41
|
-
switch (kind) {
|
|
42
|
-
case "boolean": {
|
|
43
|
-
return "INTEGER";
|
|
44
|
-
}
|
|
45
|
-
case "bytes": {
|
|
46
|
-
return "BYTEA";
|
|
47
|
-
}
|
|
48
|
-
case "date":
|
|
49
|
-
case "number":
|
|
50
|
-
case "timestamp": {
|
|
51
|
-
return "DOUBLE PRECISION";
|
|
52
|
-
}
|
|
53
|
-
default: {
|
|
54
|
-
return "TEXT";
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
},
|
|
58
|
-
decode: (value, kind) => sqliteDecode(value, kind),
|
|
59
|
-
encode: (value) => sqliteEncode(value),
|
|
60
|
-
frameworkColumns: () => [
|
|
61
|
-
{ name: "id", type: "TEXT PRIMARY KEY" },
|
|
62
|
-
{ name: "_creationTime", type: "DOUBLE PRECISION NOT NULL" }
|
|
63
|
-
],
|
|
64
|
-
isUniqueViolation: (error) => {
|
|
65
|
-
const { code } = error;
|
|
66
|
-
return code === "23505" || error instanceof Error && PG_UNIQUE_VIOLATION_RE.test(error.message);
|
|
67
|
-
},
|
|
68
|
-
name: "postgres",
|
|
69
|
-
supportsReturning: true,
|
|
70
|
-
tableExists: (table) => sql`SELECT table_name FROM information_schema.tables WHERE table_name = ${table}`
|
|
71
|
-
};
|
|
72
|
-
const mysqlDialect = {
|
|
73
|
-
affectedRows: (result) => result.rowsAffected,
|
|
74
|
-
companionTypes: {
|
|
75
|
-
autoincrementPrimaryKey: "BIGINT AUTO_INCREMENT PRIMARY KEY",
|
|
76
|
-
integer: "INTEGER",
|
|
77
|
-
// VARCHAR so it can be a PRIMARY KEY and be fully indexed (TEXT cannot,
|
|
78
|
-
// without a prefix length). 768 = InnoDB utf8mb4 single-column index limit.
|
|
79
|
-
key: "VARCHAR(768)",
|
|
80
|
-
real: "DOUBLE",
|
|
81
|
-
// Unbounded post-image storage (CDC `doc`); never an index key, so no bound.
|
|
82
|
-
text: "LONGTEXT"
|
|
83
|
-
},
|
|
84
|
-
columnType: (kind) => mysqlColumnType(kind),
|
|
85
|
-
decode: (value, kind) => sqliteDecode(value, kind),
|
|
86
|
-
encode: (value) => sqliteEncode(value),
|
|
87
|
-
frameworkColumns: () => [
|
|
88
|
-
{ name: "id", type: "VARCHAR(768) PRIMARY KEY" },
|
|
89
|
-
{ name: "_creationTime", type: "DOUBLE NOT NULL" }
|
|
90
|
-
],
|
|
91
|
-
// InnoDB can't index a TEXT/LONGTEXT/BLOB column without a key prefix; bound it to 768 chars.
|
|
92
|
-
indexKeyPrefix: (kind) => MYSQL_PREFIX_INDEX_RE.test(mysqlColumnType(kind)) ? 768 : void 0,
|
|
93
|
-
isUniqueViolation: (error) => {
|
|
94
|
-
const candidate = error;
|
|
95
|
-
return candidate.errno === 1062 || candidate.code === "ER_DUP_ENTRY";
|
|
96
|
-
},
|
|
97
|
-
name: "mysql",
|
|
98
|
-
supportsReturning: false,
|
|
99
|
-
tableExists: (table) => sql`SELECT table_name FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = ${table}`
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
export { mysqlDialect, postgresDialect };
|