turbine-orm 0.32.2 → 0.34.0

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.
Files changed (43) hide show
  1. package/README.md +2 -2
  2. package/dist/cjs/dialect.js +1 -0
  3. package/dist/cjs/index-advisor.js +0 -0
  4. package/dist/cjs/index.js +2 -1
  5. package/dist/cjs/mssql.js +3 -0
  6. package/dist/cjs/mysql.js +3 -0
  7. package/dist/cjs/optional-peer-import.cjs +28 -0
  8. package/dist/cjs/powdb-introspect.js +222 -0
  9. package/dist/cjs/powdb.js +446 -55
  10. package/dist/cjs/powql.js +566 -111
  11. package/dist/cjs/query/builder.js +136 -53
  12. package/dist/cjs/query/filters.js +4 -4
  13. package/dist/cjs/schema-builder.js +16 -0
  14. package/dist/cjs/schema-metadata.js +81 -10
  15. package/dist/cjs/sqlite.js +2 -0
  16. package/dist/dialect.d.ts +7 -0
  17. package/dist/dialect.js +1 -0
  18. package/dist/index-advisor.d.ts +15 -1
  19. package/dist/index-advisor.js +0 -0
  20. package/dist/index.d.ts +1 -1
  21. package/dist/index.js +1 -1
  22. package/dist/mssql.js +3 -0
  23. package/dist/mysql.js +3 -0
  24. package/dist/optional-peer-import.cjs +28 -0
  25. package/dist/optional-peer-import.d.cts +19 -0
  26. package/dist/powdb-introspect.d.ts +84 -0
  27. package/dist/powdb-introspect.js +219 -0
  28. package/dist/powdb.d.ts +249 -13
  29. package/dist/powdb.js +438 -54
  30. package/dist/powql.d.ts +113 -6
  31. package/dist/powql.js +568 -113
  32. package/dist/query/builder.d.ts +11 -0
  33. package/dist/query/builder.js +136 -53
  34. package/dist/query/filters.d.ts +3 -3
  35. package/dist/query/filters.js +4 -4
  36. package/dist/query/types.d.ts +50 -6
  37. package/dist/schema-builder.d.ts +46 -1
  38. package/dist/schema-builder.js +15 -0
  39. package/dist/schema-metadata.d.ts +13 -7
  40. package/dist/schema-metadata.js +82 -11
  41. package/dist/schema.d.ts +25 -0
  42. package/dist/sqlite.js +2 -0
  43. package/package.json +3 -3
@@ -50,4 +50,23 @@
50
50
  * `false` so a failure in the sibling copy can never bounce back.
51
51
  */
52
52
  declare function importOptionalPeer(specifier: string, allowEsmFallback?: boolean): Promise<unknown>;
53
+ /**
54
+ * Merged namespace so callers can reach {@link peerPackageVersion} off the same
55
+ * default import (`importOptionalPeer.peerPackageVersion(...)`). Lives in this
56
+ * `.cts` file for the same reason the dynamic import does: a `.cts` compiles to
57
+ * CommonJS in BOTH build passes, so `require` / `require.resolve` are natively
58
+ * available and `import.meta` is never emitted (which would break the CJS build
59
+ * and crash CJS consumers, see `resolveEmbeddedVersion` in powdb.ts).
60
+ */
61
+ declare namespace importOptionalPeer {
62
+ /**
63
+ * Resolve an optional peer's declared `package.json` version WITHOUT loading
64
+ * the package itself (so an ESM-only peer never trips `require`). `require` is
65
+ * anchored on THIS module's location (inside the published `dist/`), so bare
66
+ * resolution walks up `node_modules` and finds the peer exactly where
67
+ * `import.meta.url` used to point, but it compiles under `module: CommonJS`
68
+ * too. Returns `null` when the peer / its package.json cannot be resolved.
69
+ */
70
+ function peerPackageVersion(specifier: string): string | null;
71
+ }
53
72
  export = importOptionalPeer;
@@ -0,0 +1,84 @@
1
+ /**
2
+ * turbine-orm/powdb — `describe`-based introspection.
3
+ *
4
+ * PowDB exposes its catalog through two ordinary rows-returning statements
5
+ * (keywords since engine 0.10):
6
+ * - `schema` → one row per type: `{ name, columns }` (columns = a count).
7
+ * - `describe <T>` / `schema <T>` → one row per column:
8
+ * `{ column, type, nullable, index }` where `type` is a PowQL type name
9
+ * (`str`/`int`/`float`/`bool`/`json`/`datetime`/`uuid`/`bytes`), `nullable`
10
+ * is `"true"`/`"false"`, and `index` is `"unique"` / `"index"` / `""`.
11
+ *
12
+ * {@link introspectPowdbDatabase} turns those into the same {@link SchemaMetadata}
13
+ * shape the SQL introspectors produce, so a code-first PowDB database can be
14
+ * introspected for bootstrap/verification. It is transport-agnostic: the caller
15
+ * supplies an `exec(powql)` that returns row objects **keyed by column name**.
16
+ * - Embedded / owned pool: `exec = async (q) => ({ rows: await db.raw([q]) })`
17
+ * using a live `turbinePowDB` client's `raw` tagged template.
18
+ * - Networked: the raw `@zvndev/powdb-client` returns POSITIONAL rows
19
+ * (`{ columns: string[], rows: string[][] }`), so zip them into records.
20
+ * A bare `(await client.query(q)).rows` would hand this function `string[][]`
21
+ * whose `.name` cell is `undefined` and every table would silently drop out:
22
+ * ```ts
23
+ * const exec = async (q) => {
24
+ * const r = await client.query(q);
25
+ * return { rows: r.rows.map((row) => Object.fromEntries(r.columns.map((c, i) => [c, row[i]]))) };
26
+ * };
27
+ * ```
28
+ * (A mis-shaped exec is now caught: if `schema` returns rows but none carry
29
+ * a `name`, {@link introspectPowdbDatabase} throws instead of returning an
30
+ * empty schema.)
31
+ *
32
+ * IMPORTANT LIMITATIONS (all documented, none silent):
33
+ * - Relations are ALWAYS `{}`: PowDB has no declared foreign keys, so
34
+ * `describe` cannot report them. The recommended flow for relation-aware
35
+ * metadata is code-first `defineSchema` + `schemaDefToMetadata`; use
36
+ * introspection to bootstrap or verify column shape.
37
+ * - Primary key is a HEURISTIC (`describe` has no PK concept): PowDB marks a
38
+ * PK column as `required unique`, so the first non-nullable `unique` column
39
+ * is chosen (a column named `id` wins ties). A table with no such column
40
+ * yields `primaryKey: []` and a warning; single-row ops on it fail loudly.
41
+ * - `isGenerated` is always `false`: `describe` does not expose PowDB's `auto`
42
+ * modifier, so an introspected int PK is treated as client-supplied unless
43
+ * the caller hand-edits the metadata.
44
+ * - Doc-field expression indexes are INVISIBLE to `describe`, so they never
45
+ * round-trip; only plain `unique`/`index` columns appear in `indexes`.
46
+ * - `datetime` / `uuid` / `bytes` columns map to read-oriented TS types
47
+ * (`Date` / `string` / `Uint8Array`). Turbine never emits those PowQL types
48
+ * on write, so writing to such a column may not round-trip.
49
+ *
50
+ * v1 is a PROGRAMMATIC API (exported from `turbine-orm/powdb`); the CLI's
51
+ * `turbine generate` still defaults to Postgres. Routing a `powdb://` URL
52
+ * through the CLI would additionally need: a `powdbDialect.introspector`
53
+ * wired to a networked `exec`, and `cli/config.ts` teaching the generate
54
+ * funnel to construct a PowDB client instead of a `pg` client for `powdb://`.
55
+ */
56
+ import { type PowdbCapabilities } from './powdb.js';
57
+ import type { SchemaMetadata } from './schema.js';
58
+ /** A minimal rows-returning executor over a PowDB connection (embedded or networked). */
59
+ export type PowdbExec = (powql: string) => Promise<{
60
+ rows: Record<string, unknown>[];
61
+ }>;
62
+ /** Options controlling which tables {@link introspectPowdbDatabase} reads. */
63
+ export interface PowdbIntrospectOptions {
64
+ /** Only introspect these table names (snake_case, as PowDB reports them). */
65
+ include?: string[];
66
+ /** Skip these table names. */
67
+ exclude?: string[];
68
+ /**
69
+ * Bound connection capabilities. When supplied AND `introspection` (engine
70
+ * >= 0.10) is false, this throws a version-hinting {@link UnsupportedFeatureError}
71
+ * (E017) up front instead of letting a pre-0.10 engine reject the `schema` /
72
+ * `describe` keywords with an opaque parse error. Omit it (the bare-exec path)
73
+ * to run ungated; the pool paths that know the version pass it through.
74
+ */
75
+ capabilities?: PowdbCapabilities;
76
+ }
77
+ /**
78
+ * Read a live PowDB database into {@link SchemaMetadata} via `schema` +
79
+ * `describe <T>` statements run through the supplied {@link PowdbExec}.
80
+ *
81
+ * @param exec Rows-returning executor (embedded `db.raw` or networked `client.query`).
82
+ * @param options `include`/`exclude` table filters.
83
+ */
84
+ export declare function introspectPowdbDatabase(exec: PowdbExec, options?: PowdbIntrospectOptions): Promise<SchemaMetadata>;
@@ -0,0 +1,219 @@
1
+ /**
2
+ * turbine-orm/powdb — `describe`-based introspection.
3
+ *
4
+ * PowDB exposes its catalog through two ordinary rows-returning statements
5
+ * (keywords since engine 0.10):
6
+ * - `schema` → one row per type: `{ name, columns }` (columns = a count).
7
+ * - `describe <T>` / `schema <T>` → one row per column:
8
+ * `{ column, type, nullable, index }` where `type` is a PowQL type name
9
+ * (`str`/`int`/`float`/`bool`/`json`/`datetime`/`uuid`/`bytes`), `nullable`
10
+ * is `"true"`/`"false"`, and `index` is `"unique"` / `"index"` / `""`.
11
+ *
12
+ * {@link introspectPowdbDatabase} turns those into the same {@link SchemaMetadata}
13
+ * shape the SQL introspectors produce, so a code-first PowDB database can be
14
+ * introspected for bootstrap/verification. It is transport-agnostic: the caller
15
+ * supplies an `exec(powql)` that returns row objects **keyed by column name**.
16
+ * - Embedded / owned pool: `exec = async (q) => ({ rows: await db.raw([q]) })`
17
+ * using a live `turbinePowDB` client's `raw` tagged template.
18
+ * - Networked: the raw `@zvndev/powdb-client` returns POSITIONAL rows
19
+ * (`{ columns: string[], rows: string[][] }`), so zip them into records.
20
+ * A bare `(await client.query(q)).rows` would hand this function `string[][]`
21
+ * whose `.name` cell is `undefined` and every table would silently drop out:
22
+ * ```ts
23
+ * const exec = async (q) => {
24
+ * const r = await client.query(q);
25
+ * return { rows: r.rows.map((row) => Object.fromEntries(r.columns.map((c, i) => [c, row[i]]))) };
26
+ * };
27
+ * ```
28
+ * (A mis-shaped exec is now caught: if `schema` returns rows but none carry
29
+ * a `name`, {@link introspectPowdbDatabase} throws instead of returning an
30
+ * empty schema.)
31
+ *
32
+ * IMPORTANT LIMITATIONS (all documented, none silent):
33
+ * - Relations are ALWAYS `{}`: PowDB has no declared foreign keys, so
34
+ * `describe` cannot report them. The recommended flow for relation-aware
35
+ * metadata is code-first `defineSchema` + `schemaDefToMetadata`; use
36
+ * introspection to bootstrap or verify column shape.
37
+ * - Primary key is a HEURISTIC (`describe` has no PK concept): PowDB marks a
38
+ * PK column as `required unique`, so the first non-nullable `unique` column
39
+ * is chosen (a column named `id` wins ties). A table with no such column
40
+ * yields `primaryKey: []` and a warning; single-row ops on it fail loudly.
41
+ * - `isGenerated` is always `false`: `describe` does not expose PowDB's `auto`
42
+ * modifier, so an introspected int PK is treated as client-supplied unless
43
+ * the caller hand-edits the metadata.
44
+ * - Doc-field expression indexes are INVISIBLE to `describe`, so they never
45
+ * round-trip; only plain `unique`/`index` columns appear in `indexes`.
46
+ * - `datetime` / `uuid` / `bytes` columns map to read-oriented TS types
47
+ * (`Date` / `string` / `Uint8Array`). Turbine never emits those PowQL types
48
+ * on write, so writing to such a column may not round-trip.
49
+ *
50
+ * v1 is a PROGRAMMATIC API (exported from `turbine-orm/powdb`); the CLI's
51
+ * `turbine generate` still defaults to Postgres. Routing a `powdb://` URL
52
+ * through the CLI would additionally need: a `powdbDialect.introspector`
53
+ * wired to a networked `exec`, and `cli/config.ts` teaching the generate
54
+ * funnel to construct a PowDB client instead of a `pg` client for `powdb://`.
55
+ */
56
+ import { ValidationError } from './errors.js';
57
+ import { quotePowqlIdent, requireCapability } from './powdb.js';
58
+ import { snakeToCamel } from './schema.js';
59
+ /** Coerce a wire cell to string (legacy wire cells are strings; native cells may be typed). */
60
+ function asString(v) {
61
+ return v === null || v === undefined ? '' : String(v);
62
+ }
63
+ /** Coerce a `describe` `nullable` cell (`"true"`/`"false"` or a native boolean) to a JS boolean. */
64
+ function asBool(v) {
65
+ return v === true || asString(v).toLowerCase() === 'true';
66
+ }
67
+ /**
68
+ * Map a PowQL type name to the {@link ColumnMetadata} TS/dialect types. The
69
+ * `tsType` drives read coercion (`coerceValue`) and write typing
70
+ * (`powqlColumnType`); `dialectType`/`pgType` carry the PowQL type name so
71
+ * `isFloatColumn` / `isJsonColumn` classify correctly.
72
+ */
73
+ function mapPowqlType(powqlType) {
74
+ switch (powqlType) {
75
+ case 'int':
76
+ return { tsType: 'number', dialectType: 'int' };
77
+ case 'float':
78
+ return { tsType: 'number', dialectType: 'float' };
79
+ case 'bool':
80
+ return { tsType: 'boolean', dialectType: 'bool' };
81
+ case 'json':
82
+ return { tsType: 'unknown', dialectType: 'json' };
83
+ case 'datetime':
84
+ return { tsType: 'Date', dialectType: 'datetime' };
85
+ case 'uuid':
86
+ return { tsType: 'string', dialectType: 'uuid' };
87
+ case 'bytes':
88
+ return { tsType: 'Uint8Array', dialectType: 'bytes' };
89
+ default:
90
+ // `str` and any unknown future scalar fall back to string.
91
+ return { tsType: 'string', dialectType: 'str' };
92
+ }
93
+ }
94
+ /**
95
+ * Read a live PowDB database into {@link SchemaMetadata} via `schema` +
96
+ * `describe <T>` statements run through the supplied {@link PowdbExec}.
97
+ *
98
+ * @param exec Rows-returning executor (embedded `db.raw` or networked `client.query`).
99
+ * @param options `include`/`exclude` table filters.
100
+ */
101
+ export async function introspectPowdbDatabase(exec, options = {}) {
102
+ // Gate on the engine's introspection capability (>= 0.10) when the caller
103
+ // knows the version, so a pre-0.10 engine gets a typed E017 hint instead of
104
+ // an opaque `unexpected token schema` parse error.
105
+ if (options.capabilities) {
106
+ requireCapability(options.capabilities, 'introspection', 'PowDB `describe` introspection');
107
+ }
108
+ // ----- Types (one row per table, columns `name`, `columns`) -----
109
+ const schemaRows = (await exec('schema')).rows;
110
+ let tableNames = schemaRows.map((r) => asString(r.name)).filter((n) => n.length > 0);
111
+ // A mis-shaped `exec` (e.g. the raw client's positional `string[][]` rows
112
+ // passed straight through) yields rows whose `name` cell is `undefined`, so
113
+ // every table filters out and the schema comes back silently empty. Refuse
114
+ // that instead of losing data: real rows must carry a `name`.
115
+ if (schemaRows.length > 0 && tableNames.length === 0) {
116
+ throw new ValidationError(`[turbine] PowDB introspection: the \`schema\` statement returned ${schemaRows.length} row(s) but none carried a ` +
117
+ '`name` cell. The `exec` you supplied likely returns POSITIONAL rows (string[][]) rather than records keyed by ' +
118
+ 'column name; zip `columns` with each row (see introspectPowdbDatabase docs).');
119
+ }
120
+ if (options.include?.length) {
121
+ const inc = new Set(options.include);
122
+ tableNames = tableNames.filter((t) => inc.has(t));
123
+ }
124
+ if (options.exclude?.length) {
125
+ const exc = new Set(options.exclude);
126
+ tableNames = tableNames.filter((t) => !exc.has(t));
127
+ }
128
+ const tables = {};
129
+ for (const tableName of tableNames) {
130
+ // `describe` needs the table name in bare-identifier position → quote it so
131
+ // a reserved-word / non-bare table name (`order`) does not become a parse
132
+ // error.
133
+ const describeRows = (await exec(`describe ${quotePowqlIdent(tableName)}`)).rows.map((r) => ({
134
+ column: asString(r.column),
135
+ type: asString(r.type),
136
+ nullable: asBool(r.nullable),
137
+ index: asString(r.index),
138
+ }));
139
+ const columns = [];
140
+ const columnMap = {};
141
+ const reverseColumnMap = {};
142
+ const dateColumns = new Set();
143
+ const dialectTypes = {};
144
+ const pgTypes = {};
145
+ const allColumns = [];
146
+ const uniqueColumns = [];
147
+ const indexes = [];
148
+ // PK heuristic candidates: non-nullable `unique` columns.
149
+ const pkCandidates = [];
150
+ for (const row of describeRows) {
151
+ const name = row.column;
152
+ const field = snakeToCamel(name);
153
+ const { tsType, dialectType } = mapPowqlType(row.type);
154
+ const nullable = row.nullable;
155
+ const finalTs = nullable ? `${tsType} | null` : tsType;
156
+ const col = {
157
+ name,
158
+ field,
159
+ dialectType,
160
+ pgType: dialectType,
161
+ tsType: finalTs,
162
+ nullable,
163
+ // `describe` reports neither defaults nor the `auto` modifier.
164
+ hasDefault: false,
165
+ isGenerated: false,
166
+ isArray: false,
167
+ arrayType: undefined,
168
+ pgArrayType: 'text[]',
169
+ };
170
+ columns.push(col);
171
+ columnMap[field] = name;
172
+ reverseColumnMap[name] = field;
173
+ allColumns.push(name);
174
+ dialectTypes[name] = dialectType;
175
+ pgTypes[name] = dialectType;
176
+ if (dialectType === 'datetime')
177
+ dateColumns.add(name);
178
+ if (row.index === 'unique') {
179
+ uniqueColumns.push([name]);
180
+ if (!nullable)
181
+ pkCandidates.push(name);
182
+ }
183
+ if (row.index === 'unique' || row.index === 'index') {
184
+ indexes.push({
185
+ name: `${tableName}_${name}_idx`,
186
+ columns: [name],
187
+ unique: row.index === 'unique',
188
+ definition: `${row.index === 'unique' ? 'unique ' : ''}index on ${tableName}(${name})`,
189
+ });
190
+ }
191
+ }
192
+ // Primary key: first non-nullable unique column, preferring one named `id`.
193
+ let primaryKey = [];
194
+ if (pkCandidates.length > 0) {
195
+ primaryKey = [pkCandidates.includes('id') ? 'id' : pkCandidates[0]];
196
+ }
197
+ else {
198
+ console.warn(`[turbine] PowDB introspection: table "${tableName}" has no non-nullable unique column; ` +
199
+ 'primaryKey is [] (single-row operations will fail). Supply a primary key via code-first ' +
200
+ '`defineSchema` metadata if this table needs findUnique/update/delete by id.');
201
+ }
202
+ tables[tableName] = {
203
+ name: tableName,
204
+ columns,
205
+ columnMap,
206
+ reverseColumnMap,
207
+ dateColumns,
208
+ dialectTypes,
209
+ pgTypes,
210
+ allColumns,
211
+ primaryKey,
212
+ uniqueColumns,
213
+ // PowDB has no declared foreign keys → no relations from introspection.
214
+ relations: {},
215
+ indexes,
216
+ };
217
+ }
218
+ return { tables, enums: {} };
219
+ }