qubu 0.4.3 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{canonical-BXUguqfo.mjs → canonical-DMvR9yBe.mjs} +4 -4
- package/dist/codegen.mjs +2 -2
- package/dist/{complete-WYyVozgK.mjs → complete-DP7pliuY.mjs} +6 -6
- package/dist/diff.d.mts +201 -1
- package/dist/diff.mjs +8 -8
- package/dist/{index-DSX3IGM6.d.mts → index-C-480HmV.d.mts} +10 -10
- package/dist/introspection.mjs +24 -13
- package/dist/{snapshot-tkKoCd-F.mjs → snapshot-BSraiLtH.mjs} +2 -2
- package/dist/snapshot.d.mts +2 -2
- package/dist/snapshot.mjs +585 -5
- package/dist/{sqlite-mPVdrKrk.mjs → sqlite-CsIUtZ2Q.mjs} +4 -4
- package/docs/config.json +1 -0
- package/docs/dialects-and-execution.md +34 -0
- package/docs/index.md +6 -2
- package/docs/migrations/adapters.md +70 -0
- package/docs/migrations/artifacts-and-policy.md +123 -0
- package/docs/migrations/index.md +46 -0
- package/docs/migrations/lotta-adoption.md +50 -0
- package/docs/migrations/operations.md +133 -0
- package/docs/migrations/recovery.md +121 -0
- package/docs/reference/introspection-support.md +1 -1
- package/docs/reference/supported-surface.md +64 -44
- package/docs/schema/catalog-model.md +1 -1
- package/docs/schema/ddl-emission.md +15 -10
- package/docs/schema/introspection.md +4 -3
- package/docs/schema/migration-plans.md +6 -6
- package/docs/schema/snapshots.md +2 -2
- package/package.json +5 -14
- package/dist/ddl.d.mts +0 -118
- package/dist/ddl.mjs +0 -1119
- package/dist/index-C_BQNvbT.d.mts +0 -202
- package/dist/migration.d.mts +0 -214
- package/dist/migration.mjs +0 -1160
- package/dist/mysql-DxFleLw4.mjs +0 -585
package/dist/snapshot.mjs
CHANGED
|
@@ -1,6 +1,586 @@
|
|
|
1
|
+
import { F as isValidSchemaObjectName, r as isColumnReference } from "./column-DmazTL67.mjs";
|
|
2
|
+
import { _ as isUnsafeSchemaSql, m as validateConstraintDialect, w as validateIndexDialect, y as renderSchemaExpression } from "./registry-BGqa05et.mjs";
|
|
1
3
|
import { t as createSchemaDialect } from "./dialect-wUKrnPMB.mjs";
|
|
2
|
-
import { a as toSnapshotJsonValue, c as canonicalizeSchemaSnapshot, d as schemaSnapshotDialectVersion, f as schemaSnapshotFormat, i as
|
|
3
|
-
import { a as sqliteStorageAffinity, c as createSchemaSnapshot, d as
|
|
4
|
-
import { _ as completeSchemaSnapshotVersion, a as canonicalizeCompleteSchemaSnapshot, b as schemaSnapshotVersion2, c as decodeCompleteSchemaSnapshot, d as
|
|
5
|
-
import {
|
|
6
|
-
|
|
4
|
+
import { a as toSnapshotJsonValue, c as canonicalizeSchemaSnapshot, d as schemaSnapshotDialectVersion, f as schemaSnapshotFormat, i as schemaSnapshotFingerprint, l as decodeSchemaSnapshot, m as schemaSnapshotVersion, n as canonicalJson, o as SnapshotValidationError, p as schemaSnapshotNamingPolicyVersion, r as encodeCanonicalSnapshot, s as assertSchemaSnapshot, t as SnapshotCanonicalError, u as neutralSnapshotDialect } from "./canonical-DMvR9yBe.mjs";
|
|
5
|
+
import { a as sqliteStorageAffinity, c as createSchemaSnapshot, d as fingerprintSchemaSnapshot, f as serializeSchema, i as sqliteSnapshotDialect, l as encodeSchemaSnapshot, n as sqliteSchemaDialect, o as tryCreateSqliteSchemaSnapshot, p as tryCreateSchemaSnapshot, r as sqliteSnapshotAdapter, s as SnapshotSerializationError, t as createSqliteSchemaSnapshot, u as encodeSnapshot } from "./sqlite-CsIUtZ2Q.mjs";
|
|
6
|
+
import { _ as completeSchemaSnapshotVersion, a as canonicalizeCompleteSchemaSnapshot, b as schemaSnapshotVersion2, c as decodeCompleteSchemaSnapshot, d as encodeCompleteSchemaSnapshot, f as encodeCompleteSnapshot, g as completeSchemaSnapshotFormat, h as schemaSnapshotV2Fingerprint, i as assertSchemaSnapshotV2, l as decodeCompleteSnapshot, m as fingerprintCompleteSchemaSnapshot, n as assertCompleteSchemaSnapshot, o as canonicalizeSchemaSnapshotV2, p as encodeSchemaSnapshotV2, r as assertCompleteSnapshot, s as completeSchemaSnapshotFingerprint, t as CompleteSnapshotValidationError, u as decodeSchemaSnapshotV2, v as schemaSnapshotCompleteVersion, y as schemaSnapshotV2Version } from "./complete-DP7pliuY.mjs";
|
|
7
|
+
import { postgresDialect } from "./postgres.mjs";
|
|
8
|
+
import { mysqlDialect } from "./mysql.mjs";
|
|
9
|
+
//#region src/snapshot/postgres.ts
|
|
10
|
+
/** PostgreSQL's v1 snapshot extension identity. */
|
|
11
|
+
const postgresSnapshotDialect = Object.freeze({
|
|
12
|
+
name: "postgresql",
|
|
13
|
+
version: 1
|
|
14
|
+
});
|
|
15
|
+
var PostgresSnapshotDialectError = class extends TypeError {
|
|
16
|
+
code = "dialect-mismatch";
|
|
17
|
+
};
|
|
18
|
+
const postgresStorageTypes = Object.freeze({
|
|
19
|
+
integer: "INTEGER",
|
|
20
|
+
numeric: "NUMERIC",
|
|
21
|
+
text: "TEXT",
|
|
22
|
+
boolean: "BOOLEAN",
|
|
23
|
+
date: "DATE",
|
|
24
|
+
timestamp: "TIMESTAMP",
|
|
25
|
+
uuid: "UUID",
|
|
26
|
+
json: "JSONB",
|
|
27
|
+
bigint: "BIGINT",
|
|
28
|
+
binary: "BYTEA"
|
|
29
|
+
});
|
|
30
|
+
/** PostgreSQL's query dialect plus its schema metadata behavior. */
|
|
31
|
+
const postgresSchemaDialect = createSchemaDialect(postgresDialect(), {
|
|
32
|
+
version: 1,
|
|
33
|
+
validate: validatePostgresSchema,
|
|
34
|
+
encodeStorage(storage, context) {
|
|
35
|
+
return encodePostgresStorage(storage, context.dialect);
|
|
36
|
+
},
|
|
37
|
+
encodeExpression(expression, context) {
|
|
38
|
+
return encodePostgresExpression(expression, context.mode, context.dialect);
|
|
39
|
+
},
|
|
40
|
+
encodeDialectExtension(extension, context) {
|
|
41
|
+
return encodePostgresExtension(extension, context.dialect);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
/** Snapshot adapter retaining the historical adapter-shaped entry point. */
|
|
45
|
+
const postgresSnapshotAdapter = Object.freeze({ dialect: postgresSchemaDialect });
|
|
46
|
+
/** Create a canonical PostgreSQL schema snapshot. */
|
|
47
|
+
function createPostgresSchemaSnapshot(schema, options = {}) {
|
|
48
|
+
return createSchemaSnapshot(schema, {
|
|
49
|
+
...options,
|
|
50
|
+
adapter: postgresSnapshotAdapter
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/** Return PostgreSQL capability diagnostics without throwing. */
|
|
54
|
+
function tryCreatePostgresSchemaSnapshot(schema, options = {}) {
|
|
55
|
+
return tryCreateSchemaSnapshot(schema, {
|
|
56
|
+
...options,
|
|
57
|
+
adapter: postgresSnapshotAdapter
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function encodePostgresStorage(storage, dialect) {
|
|
61
|
+
if (storage.kind === "native") {
|
|
62
|
+
if (storage.dialect !== dialect.name) throw new TypeError(`PostgreSQL storage cannot encode a native declaration owned by "${storage.dialect}"`);
|
|
63
|
+
if (storage.type.trim().length === 0) throw new TypeError("PostgreSQL native storage declarations cannot be empty");
|
|
64
|
+
return {
|
|
65
|
+
kind: "native",
|
|
66
|
+
dialect: dialect.name,
|
|
67
|
+
type: storage.type
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const type = postgresStorageTypes[storage.type];
|
|
71
|
+
if (type === void 0) throw new TypeError(`Unsupported portable storage type for PostgreSQL: ${String(storage.type)}`);
|
|
72
|
+
return {
|
|
73
|
+
kind: "native",
|
|
74
|
+
dialect: dialect.name,
|
|
75
|
+
type
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function encodePostgresExpression(expression, mode, dialect) {
|
|
79
|
+
if (isUnsafeSchemaSql(expression) && expression.schemaSqlDialect !== dialect.name) throw new PostgresSnapshotDialectError(`Schema SQL is tagged for "${expression.schemaSqlDialect}" but the PostgreSQL schema dialect is "${dialect.name}"`);
|
|
80
|
+
const rendered = renderSchemaExpression(expression, {
|
|
81
|
+
mode,
|
|
82
|
+
dialect
|
|
83
|
+
});
|
|
84
|
+
if (rendered.parameters.length !== 0) throw new TypeError("PostgreSQL schema expressions must be parameter-free");
|
|
85
|
+
return {
|
|
86
|
+
kind: "expression",
|
|
87
|
+
expressionKind: expression.expressionKind,
|
|
88
|
+
sql: rendered.text,
|
|
89
|
+
...isUnsafeSchemaSql(expression) ? { dialect: dialect.name } : {}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function encodePostgresExtension(extension, dialect) {
|
|
93
|
+
if (extension.dialect !== dialect.name) throw new TypeError(`PostgreSQL schema extensions require dialect "${dialect.name}"`);
|
|
94
|
+
const data = Object.fromEntries(Object.entries(extension).filter(([key]) => key !== "dialect").sort(([left], [right]) => left.localeCompare(right)));
|
|
95
|
+
return {
|
|
96
|
+
dialect: dialect.name,
|
|
97
|
+
version: dialect.schema.version,
|
|
98
|
+
data: sortSnapshotJson$1(toSnapshotJsonValue(data))
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function sortSnapshotJson$1(value) {
|
|
102
|
+
if (Array.isArray(value)) return value.map(sortSnapshotJson$1);
|
|
103
|
+
if (typeof value !== "object" || value === null) return value;
|
|
104
|
+
return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, child]) => [key, sortSnapshotJson$1(child)]));
|
|
105
|
+
}
|
|
106
|
+
function validatePostgresSchema(schema, context) {
|
|
107
|
+
const diagnostics = [];
|
|
108
|
+
const tableEntries = Object.entries(schema.registry).sort(([left], [right]) => compareIds$1(left, right));
|
|
109
|
+
const relationNames = /* @__PURE__ */ new Map();
|
|
110
|
+
if (schema.namespace !== void 0) validatePostgresName(schema.namespace, ["namespace"], "PostgreSQL namespace", diagnostics);
|
|
111
|
+
for (const [id, entry] of tableEntries) {
|
|
112
|
+
const tablePath = ["tables", id];
|
|
113
|
+
validatePostgresName(entry.physicalName, [...tablePath, "physicalName"], "PostgreSQL table", diagnostics);
|
|
114
|
+
addRelationName(relationNames, entry.physicalName, [...tablePath, "physicalName"], "table", diagnostics);
|
|
115
|
+
}
|
|
116
|
+
for (const [id, entry] of tableEntries) validatePostgresTable(id, entry, context, relationNames, diagnostics);
|
|
117
|
+
return Object.freeze(diagnostics);
|
|
118
|
+
}
|
|
119
|
+
function validatePostgresTable(tableId, entry, context, relationNames, diagnostics) {
|
|
120
|
+
const table = entry.table;
|
|
121
|
+
const tablePath = ["tables", tableId];
|
|
122
|
+
const definitions = table.definitions;
|
|
123
|
+
for (const [columnId, definition] of Object.entries(definitions).sort(([left], [right]) => compareIds$1(left, right))) {
|
|
124
|
+
validatePostgresName(table.sqlNames[columnId] ?? columnId, [
|
|
125
|
+
...tablePath,
|
|
126
|
+
"columns",
|
|
127
|
+
columnId,
|
|
128
|
+
"physicalName"
|
|
129
|
+
], "PostgreSQL column", diagnostics);
|
|
130
|
+
if (definition.onUpdate !== void 0) diagnostics.push({
|
|
131
|
+
code: "unsupported-dialect-option",
|
|
132
|
+
message: "MySQL ON UPDATE expressions are not supported by PostgreSQL",
|
|
133
|
+
path: [
|
|
134
|
+
...tablePath,
|
|
135
|
+
"columns",
|
|
136
|
+
columnId,
|
|
137
|
+
"onUpdate"
|
|
138
|
+
]
|
|
139
|
+
});
|
|
140
|
+
const generated = definition.generatedColumn;
|
|
141
|
+
if (generated !== void 0 && generated.kind === "expression" && generated.mode === "virtual") diagnostics.push({
|
|
142
|
+
code: "unsupported-dialect-option",
|
|
143
|
+
message: "PostgreSQL v1 snapshots support stored generated columns only; virtual generated columns are not supported",
|
|
144
|
+
path: [
|
|
145
|
+
...tablePath,
|
|
146
|
+
"columns",
|
|
147
|
+
columnId,
|
|
148
|
+
"generatedColumn",
|
|
149
|
+
"mode"
|
|
150
|
+
]
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
const metadata = table;
|
|
154
|
+
const constraintNames = /* @__PURE__ */ new Map();
|
|
155
|
+
const constraints = Object.entries(metadata.constraints);
|
|
156
|
+
for (const [constraintId, constraint] of constraints.sort(([left], [right]) => compareIds$1(left, right))) {
|
|
157
|
+
const constraintPath = [
|
|
158
|
+
...tablePath,
|
|
159
|
+
"constraints",
|
|
160
|
+
constraintId
|
|
161
|
+
];
|
|
162
|
+
const physicalName = constraint.physicalName ?? constraintId;
|
|
163
|
+
validatePostgresName(physicalName, [...constraintPath, "physicalName"], "PostgreSQL constraint", diagnostics);
|
|
164
|
+
addScopedName$1(constraintNames, physicalName, [...constraintPath, "physicalName"], "constraint", diagnostics);
|
|
165
|
+
appendMetadataDiagnostics$1(diagnostics, validateConstraintDialect(constraint, context.dialect.name, constraintPath));
|
|
166
|
+
if (constraint.kind === "check" && constraint.deferrable === true) diagnostics.push({
|
|
167
|
+
code: "unsupported-dialect-option",
|
|
168
|
+
message: "PostgreSQL CHECK constraints cannot be DEFERRABLE",
|
|
169
|
+
path: [...constraintPath, "deferrable"]
|
|
170
|
+
});
|
|
171
|
+
if (constraint.kind === "foreign-key" && constraint.match === "partial") diagnostics.push({
|
|
172
|
+
code: "unsupported-dialect-option",
|
|
173
|
+
message: "PostgreSQL does not implement MATCH PARTIAL foreign keys",
|
|
174
|
+
path: [...constraintPath, "match"]
|
|
175
|
+
});
|
|
176
|
+
if (constraint.kind === "unique-constraint" && constraint.nulls === "not-distinct") diagnostics.push({
|
|
177
|
+
code: "unsupported-dialect-option",
|
|
178
|
+
message: "PostgreSQL NULLS NOT DISTINCT requires a server-version policy; the v1 adapter does not assume PostgreSQL 15 or newer",
|
|
179
|
+
path: [...constraintPath, "nulls"]
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
const indexes = Object.entries(metadata.indexes);
|
|
183
|
+
for (const [indexId, index] of indexes.sort(([left], [right]) => compareIds$1(left, right))) {
|
|
184
|
+
const indexPath = [
|
|
185
|
+
...tablePath,
|
|
186
|
+
"indexes",
|
|
187
|
+
indexId
|
|
188
|
+
];
|
|
189
|
+
const physicalName = index.physicalName ?? indexId;
|
|
190
|
+
validatePostgresName(physicalName, [...indexPath, "physicalName"], "PostgreSQL index", diagnostics);
|
|
191
|
+
addRelationName(relationNames, physicalName, [...indexPath, "physicalName"], "index", diagnostics);
|
|
192
|
+
appendMetadataDiagnostics$1(diagnostics, validateIndexDialect(index, context.dialect.name, indexPath));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function validatePostgresName(name, path, kind, diagnostics) {
|
|
196
|
+
if (!isValidSchemaObjectName(name) || name.length === 0) diagnostics.push({
|
|
197
|
+
code: "invalid-schema",
|
|
198
|
+
message: `${kind} name "${name}" is not a valid unqualified identifier`,
|
|
199
|
+
path
|
|
200
|
+
});
|
|
201
|
+
if (new TextEncoder().encode(name).length > 63) diagnostics.push({
|
|
202
|
+
code: "unsupported-dialect-option",
|
|
203
|
+
message: `${kind} name exceeds PostgreSQL's 63-byte identifier limit`,
|
|
204
|
+
path
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
function addRelationName(names, name, path, kind, diagnostics) {
|
|
208
|
+
addScopedName$1(names, name, path, kind, diagnostics);
|
|
209
|
+
}
|
|
210
|
+
function addScopedName$1(names, name, path, kind, diagnostics) {
|
|
211
|
+
const previousPath = names.get(name);
|
|
212
|
+
if (previousPath !== void 0) {
|
|
213
|
+
diagnostics.push({
|
|
214
|
+
code: "invalid-schema",
|
|
215
|
+
message: `PostgreSQL ${kind} name "${name}" collides with another relation or metadata object`,
|
|
216
|
+
path,
|
|
217
|
+
relatedPaths: [previousPath]
|
|
218
|
+
});
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
names.set(name, path);
|
|
222
|
+
}
|
|
223
|
+
function appendMetadataDiagnostics$1(diagnostics, issues) {
|
|
224
|
+
for (const issue of issues) {
|
|
225
|
+
if (issue.code === "dialect-mismatch") continue;
|
|
226
|
+
diagnostics.push({
|
|
227
|
+
code: issue.code === "unsupported-dialect-option" ? "unsupported-dialect-option" : "invalid-schema",
|
|
228
|
+
message: issue.message,
|
|
229
|
+
path: issue.path,
|
|
230
|
+
...issue.relatedPaths === void 0 ? {} : { relatedPaths: issue.relatedPaths }
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
function compareIds$1(left, right) {
|
|
235
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
236
|
+
}
|
|
237
|
+
//#endregion
|
|
238
|
+
//#region src/snapshot/mysql.ts
|
|
239
|
+
/** MySQL's v1 snapshot extension identity. */
|
|
240
|
+
const mysqlSnapshotDialect = Object.freeze({
|
|
241
|
+
name: "mysql",
|
|
242
|
+
version: 1
|
|
243
|
+
});
|
|
244
|
+
var MysqlSnapshotDialectError = class extends TypeError {
|
|
245
|
+
code = "dialect-mismatch";
|
|
246
|
+
};
|
|
247
|
+
const mysqlStorageTypes = Object.freeze({
|
|
248
|
+
integer: "INT",
|
|
249
|
+
numeric: "DECIMAL",
|
|
250
|
+
text: "TEXT",
|
|
251
|
+
boolean: "BOOLEAN",
|
|
252
|
+
date: "DATE",
|
|
253
|
+
timestamp: "DATETIME",
|
|
254
|
+
uuid: "CHAR(36)",
|
|
255
|
+
json: "JSON",
|
|
256
|
+
bigint: "BIGINT",
|
|
257
|
+
binary: "VARBINARY"
|
|
258
|
+
});
|
|
259
|
+
/** MySQL's query dialect plus its schema metadata behavior. */
|
|
260
|
+
const mysqlSchemaDialect = createSchemaDialect(mysqlDialect(), {
|
|
261
|
+
version: 1,
|
|
262
|
+
validate: validateMysqlSchema,
|
|
263
|
+
encodeStorage(storage, context) {
|
|
264
|
+
return encodeMysqlStorage(storage, context.dialect);
|
|
265
|
+
},
|
|
266
|
+
encodeExpression(expression, context) {
|
|
267
|
+
return encodeMysqlExpression(expression, context.mode, context.dialect);
|
|
268
|
+
},
|
|
269
|
+
encodeDialectExtension(extension, context) {
|
|
270
|
+
return encodeMysqlExtension(extension, context.dialect);
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
/** Snapshot adapter retaining the historical adapter-shaped entry point. */
|
|
274
|
+
const mysqlSnapshotAdapter = Object.freeze({ dialect: mysqlSchemaDialect });
|
|
275
|
+
/** Create a canonical MySQL schema snapshot. */
|
|
276
|
+
function createMysqlSchemaSnapshot(schema, options = {}) {
|
|
277
|
+
return createSchemaSnapshot(schema, {
|
|
278
|
+
...options,
|
|
279
|
+
adapter: mysqlSnapshotAdapter
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
/** Return MySQL capability diagnostics without throwing. */
|
|
283
|
+
function tryCreateMysqlSchemaSnapshot(schema, options = {}) {
|
|
284
|
+
return tryCreateSchemaSnapshot(schema, {
|
|
285
|
+
...options,
|
|
286
|
+
adapter: mysqlSnapshotAdapter
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
function encodeMysqlStorage(storage, dialect) {
|
|
290
|
+
if (storage.kind === "native") {
|
|
291
|
+
if (storage.dialect !== dialect.name) throw new MysqlSnapshotDialectError(`MySQL storage cannot encode a native declaration owned by "${storage.dialect}"`);
|
|
292
|
+
if (storage.type.trim().length === 0) throw new TypeError("MySQL native storage declarations cannot be empty");
|
|
293
|
+
return {
|
|
294
|
+
kind: "native",
|
|
295
|
+
dialect: dialect.name,
|
|
296
|
+
type: storage.type
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
const type = mysqlStorageTypes[storage.type];
|
|
300
|
+
if (type === void 0) throw new TypeError(`Unsupported portable storage type for MySQL: ${String(storage.type)}`);
|
|
301
|
+
return {
|
|
302
|
+
kind: "native",
|
|
303
|
+
dialect: dialect.name,
|
|
304
|
+
type
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
function encodeMysqlExpression(expression, mode, dialect) {
|
|
308
|
+
if (isUnsafeSchemaSql(expression) && expression.schemaSqlDialect !== dialect.name) throw new MysqlSnapshotDialectError(`Schema SQL is tagged for "${expression.schemaSqlDialect}" but the MySQL schema dialect is "${dialect.name}"`);
|
|
309
|
+
const rendered = renderSchemaExpression(expression, {
|
|
310
|
+
mode,
|
|
311
|
+
dialect
|
|
312
|
+
});
|
|
313
|
+
if (rendered.parameters.length !== 0) throw new TypeError("MySQL schema expressions must be parameter-free");
|
|
314
|
+
return {
|
|
315
|
+
kind: "expression",
|
|
316
|
+
expressionKind: expression.expressionKind,
|
|
317
|
+
sql: rendered.text,
|
|
318
|
+
...isUnsafeSchemaSql(expression) ? { dialect: dialect.name } : {}
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
function encodeMysqlExtension(extension, dialect) {
|
|
322
|
+
if (extension.dialect !== dialect.name) throw new MysqlSnapshotDialectError(`MySQL schema extensions require dialect "${dialect.name}"`);
|
|
323
|
+
const data = Object.fromEntries(Object.entries(extension).filter(([key]) => key !== "dialect").sort(([left], [right]) => left.localeCompare(right)));
|
|
324
|
+
return {
|
|
325
|
+
dialect: dialect.name,
|
|
326
|
+
version: dialect.schema.version,
|
|
327
|
+
data: sortSnapshotJson(toSnapshotJsonValue(data))
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
function sortSnapshotJson(value) {
|
|
331
|
+
if (Array.isArray(value)) return value.map(sortSnapshotJson);
|
|
332
|
+
if (typeof value !== "object" || value === null) return value;
|
|
333
|
+
return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, child]) => [key, sortSnapshotJson(child)]));
|
|
334
|
+
}
|
|
335
|
+
function validateMysqlSchema(schema, context) {
|
|
336
|
+
const diagnostics = [];
|
|
337
|
+
const tableEntries = Object.entries(schema.registry).sort(([left], [right]) => compareIds(left, right));
|
|
338
|
+
const relationNames = /* @__PURE__ */ new Map();
|
|
339
|
+
if (schema.namespace !== void 0) validateMysqlName(schema.namespace, ["namespace"], "MySQL database/schema", diagnostics);
|
|
340
|
+
for (const [id, entry] of tableEntries) {
|
|
341
|
+
const tablePath = ["tables", id];
|
|
342
|
+
validateMysqlName(entry.physicalName, [...tablePath, "physicalName"], "MySQL table", diagnostics);
|
|
343
|
+
addScopedName(relationNames, entry.physicalName, [...tablePath, "physicalName"], "table", diagnostics);
|
|
344
|
+
}
|
|
345
|
+
for (const [id, entry] of tableEntries) validateMysqlTable(id, entry, context, diagnostics);
|
|
346
|
+
return Object.freeze(diagnostics);
|
|
347
|
+
}
|
|
348
|
+
function validateMysqlTable(tableId, entry, context, diagnostics) {
|
|
349
|
+
const table = entry.table;
|
|
350
|
+
const tablePath = ["tables", tableId];
|
|
351
|
+
const definitions = table.definitions;
|
|
352
|
+
const metadata = table;
|
|
353
|
+
for (const [columnId, definition] of Object.entries(definitions).sort(([left], [right]) => compareIds(left, right))) {
|
|
354
|
+
const columnPath = [
|
|
355
|
+
...tablePath,
|
|
356
|
+
"columns",
|
|
357
|
+
columnId
|
|
358
|
+
];
|
|
359
|
+
validateMysqlName(table.sqlNames[columnId] ?? columnId, [...columnPath, "physicalName"], "MySQL column", diagnostics);
|
|
360
|
+
if (definition.onUpdate !== void 0 && definition.generatedColumn) diagnostics.push({
|
|
361
|
+
code: "unsupported-dialect-option",
|
|
362
|
+
message: "MySQL generated columns cannot also declare ON UPDATE",
|
|
363
|
+
path: [...columnPath, "onUpdate"]
|
|
364
|
+
});
|
|
365
|
+
const extension = definition.identity?.dialect;
|
|
366
|
+
const autoIncrement = extension?.dialect === mysqlSnapshotDialect.name ? extension.autoIncrement : void 0;
|
|
367
|
+
if (extension?.dialect === mysqlSnapshotDialect.name && autoIncrement !== void 0 && typeof autoIncrement !== "boolean") diagnostics.push({
|
|
368
|
+
code: "unsupported-dialect-option",
|
|
369
|
+
message: "MySQL identity autoIncrement must be boolean",
|
|
370
|
+
path: [
|
|
371
|
+
...columnPath,
|
|
372
|
+
"identity",
|
|
373
|
+
"dialect",
|
|
374
|
+
"autoIncrement"
|
|
375
|
+
]
|
|
376
|
+
});
|
|
377
|
+
if (autoIncrement === true) validateMysqlAutoIncrement(columnId, definition, metadata, columnPath, diagnostics);
|
|
378
|
+
}
|
|
379
|
+
const constraintNames = /* @__PURE__ */ new Map();
|
|
380
|
+
const constraints = Object.entries(metadata.constraints);
|
|
381
|
+
for (const [constraintId, constraint] of constraints.sort(([left], [right]) => compareIds(left, right))) {
|
|
382
|
+
const constraintPath = [
|
|
383
|
+
...tablePath,
|
|
384
|
+
"constraints",
|
|
385
|
+
constraintId
|
|
386
|
+
];
|
|
387
|
+
const physicalName = constraint.physicalName ?? constraintId;
|
|
388
|
+
validateMysqlName(physicalName, [...constraintPath, "physicalName"], "MySQL constraint", diagnostics);
|
|
389
|
+
addScopedName(constraintNames, physicalName, [...constraintPath, "physicalName"], "constraint", diagnostics);
|
|
390
|
+
appendMetadataDiagnostics(diagnostics, validateConstraintDialect(constraint, context.dialect.name, constraintPath));
|
|
391
|
+
if (constraint.kind === "foreign-key" && (constraint.match === "full" || constraint.match === "partial")) diagnostics.push({
|
|
392
|
+
code: "unsupported-dialect-option",
|
|
393
|
+
message: "MySQL v1 snapshots support MATCH SIMPLE foreign keys only",
|
|
394
|
+
path: [...constraintPath, "match"]
|
|
395
|
+
});
|
|
396
|
+
if (constraint.kind === "foreign-key" && (constraint.onUpdate === "set-default" || constraint.onDelete === "set-default")) diagnostics.push({
|
|
397
|
+
code: "unsupported-dialect-option",
|
|
398
|
+
message: "MySQL does not support SET DEFAULT foreign-key actions",
|
|
399
|
+
path: [...constraintPath, constraint.onUpdate === "set-default" ? "onUpdate" : "onDelete"]
|
|
400
|
+
});
|
|
401
|
+
if (constraint.kind === "unique-constraint" && constraint.nulls === "not-distinct") diagnostics.push({
|
|
402
|
+
code: "unsupported-dialect-option",
|
|
403
|
+
message: "MySQL ordinary UNIQUE constraints use distinct NULL semantics in v1",
|
|
404
|
+
path: [...constraintPath, "nulls"]
|
|
405
|
+
});
|
|
406
|
+
if (constraint.dialect?.dialect === mysqlSnapshotDialect.name && "enforced" in constraint.dialect && constraint.dialect.enforced !== void 0 && typeof constraint.dialect.enforced !== "boolean") diagnostics.push({
|
|
407
|
+
code: "unsupported-dialect-option",
|
|
408
|
+
message: "MySQL constraint enforced must be boolean",
|
|
409
|
+
path: [
|
|
410
|
+
...constraintPath,
|
|
411
|
+
"dialect",
|
|
412
|
+
"enforced"
|
|
413
|
+
]
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
const indexes = Object.entries(metadata.indexes);
|
|
417
|
+
const tableIndexNames = /* @__PURE__ */ new Map();
|
|
418
|
+
for (const [indexId, index] of indexes.sort(([left], [right]) => compareIds(left, right))) {
|
|
419
|
+
const indexPath = [
|
|
420
|
+
...tablePath,
|
|
421
|
+
"indexes",
|
|
422
|
+
indexId
|
|
423
|
+
];
|
|
424
|
+
const physicalName = index.physicalName ?? indexId;
|
|
425
|
+
validateMysqlName(physicalName, [...indexPath, "physicalName"], "MySQL index", diagnostics);
|
|
426
|
+
addScopedName(tableIndexNames, physicalName, [...indexPath, "physicalName"], "index", diagnostics);
|
|
427
|
+
appendMetadataDiagnostics(diagnostics, validateIndexDialect(index, context.dialect.name, indexPath));
|
|
428
|
+
if (index.predicate !== void 0) diagnostics.push({
|
|
429
|
+
code: "unsupported-dialect-option",
|
|
430
|
+
message: "MySQL does not support partial-index predicates",
|
|
431
|
+
path: [...indexPath, "predicate"]
|
|
432
|
+
});
|
|
433
|
+
for (const [termIndex, term] of index.terms.entries()) if (isOrderTerm(term) && term.nulls !== void 0) diagnostics.push({
|
|
434
|
+
code: "unsupported-dialect-option",
|
|
435
|
+
message: "MySQL index terms do not support NULLS FIRST/LAST syntax",
|
|
436
|
+
path: [
|
|
437
|
+
...indexPath,
|
|
438
|
+
"terms",
|
|
439
|
+
termIndex,
|
|
440
|
+
"nulls"
|
|
441
|
+
]
|
|
442
|
+
});
|
|
443
|
+
const extension = index.dialect;
|
|
444
|
+
if (extension?.dialect === mysqlSnapshotDialect.name) {
|
|
445
|
+
const mysqlExtension = extension;
|
|
446
|
+
if (mysqlExtension.algorithm !== void 0 && ![
|
|
447
|
+
"default",
|
|
448
|
+
"inplace",
|
|
449
|
+
"copy"
|
|
450
|
+
].includes(String(mysqlExtension.algorithm))) diagnostics.push({
|
|
451
|
+
code: "unsupported-dialect-option",
|
|
452
|
+
message: "MySQL index algorithm is invalid",
|
|
453
|
+
path: [
|
|
454
|
+
...indexPath,
|
|
455
|
+
"dialect",
|
|
456
|
+
"algorithm"
|
|
457
|
+
]
|
|
458
|
+
});
|
|
459
|
+
if (mysqlExtension.lock !== void 0 && ![
|
|
460
|
+
"default",
|
|
461
|
+
"none",
|
|
462
|
+
"shared",
|
|
463
|
+
"exclusive"
|
|
464
|
+
].includes(String(mysqlExtension.lock))) diagnostics.push({
|
|
465
|
+
code: "unsupported-dialect-option",
|
|
466
|
+
message: "MySQL index lock mode is invalid",
|
|
467
|
+
path: [
|
|
468
|
+
...indexPath,
|
|
469
|
+
"dialect",
|
|
470
|
+
"lock"
|
|
471
|
+
]
|
|
472
|
+
});
|
|
473
|
+
if (mysqlExtension.using !== void 0 && ![
|
|
474
|
+
"btree",
|
|
475
|
+
"hash",
|
|
476
|
+
"rtree"
|
|
477
|
+
].includes(String(mysqlExtension.using))) diagnostics.push({
|
|
478
|
+
code: "unsupported-dialect-option",
|
|
479
|
+
message: "MySQL index access method is invalid",
|
|
480
|
+
path: [
|
|
481
|
+
...indexPath,
|
|
482
|
+
"dialect",
|
|
483
|
+
"using"
|
|
484
|
+
]
|
|
485
|
+
});
|
|
486
|
+
if (mysqlExtension.parser !== void 0 && (typeof mysqlExtension.parser !== "string" || mysqlExtension.parser.trim().length === 0)) diagnostics.push({
|
|
487
|
+
code: "unsupported-dialect-option",
|
|
488
|
+
message: "MySQL index parser must be a non-empty string",
|
|
489
|
+
path: [
|
|
490
|
+
...indexPath,
|
|
491
|
+
"dialect",
|
|
492
|
+
"parser"
|
|
493
|
+
]
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
function validateMysqlAutoIncrement(columnId, definition, metadata, columnPath, diagnostics) {
|
|
499
|
+
if (definition.nullable) diagnostics.push({
|
|
500
|
+
code: "unsupported-dialect-option",
|
|
501
|
+
message: "MySQL AUTO_INCREMENT columns must be non-nullable",
|
|
502
|
+
path: [...columnPath, "nullable"]
|
|
503
|
+
});
|
|
504
|
+
if (definition.generatedColumn !== void 0) diagnostics.push({
|
|
505
|
+
code: "unsupported-dialect-option",
|
|
506
|
+
message: "MySQL AUTO_INCREMENT columns cannot be generated columns",
|
|
507
|
+
path: [...columnPath, "generatedColumn"]
|
|
508
|
+
});
|
|
509
|
+
const declaration = mysqlDeclaration(definition.storage);
|
|
510
|
+
if (declaration === void 0 || !isMysqlIntegerDeclaration(declaration)) diagnostics.push({
|
|
511
|
+
code: "unsupported-dialect-option",
|
|
512
|
+
message: "MySQL AUTO_INCREMENT requires an integer-family physical storage declaration",
|
|
513
|
+
path: [...columnPath, "storage"]
|
|
514
|
+
});
|
|
515
|
+
if (!hasMysqlAutoIncrementKey(columnId, metadata)) diagnostics.push({
|
|
516
|
+
code: "unsupported-dialect-option",
|
|
517
|
+
message: "MySQL AUTO_INCREMENT columns must be the first column of a primary, unique, or ordinary index key",
|
|
518
|
+
path: [
|
|
519
|
+
...columnPath,
|
|
520
|
+
"identity",
|
|
521
|
+
"dialect",
|
|
522
|
+
"autoIncrement"
|
|
523
|
+
]
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
function mysqlDeclaration(storage) {
|
|
527
|
+
if (storage === void 0) return;
|
|
528
|
+
return storage.kind === "native" ? storage.type : mysqlStorageTypes[storage.type];
|
|
529
|
+
}
|
|
530
|
+
function isMysqlIntegerDeclaration(declaration) {
|
|
531
|
+
const base = declaration.trim().toUpperCase().split(/\s+/u)[0] ?? "";
|
|
532
|
+
return /^(?:TINYINT|SMALLINT|MEDIUMINT|INT|INTEGER|BIGINT)(?:\(\d+\))?$/u.test(base);
|
|
533
|
+
}
|
|
534
|
+
function hasMysqlAutoIncrementKey(columnId, metadata) {
|
|
535
|
+
for (const constraint of Object.values(metadata.constraints)) if ((constraint.kind === "primary-key" || constraint.kind === "unique" || constraint.kind === "unique-constraint") && isColumnReference(constraint.columns[0]) && constraint.columns[0].fieldName === columnId) return true;
|
|
536
|
+
for (const index of Object.values(metadata.indexes)) {
|
|
537
|
+
const first = index.terms[0];
|
|
538
|
+
const expression = isOrderTerm(first) ? first.expression : first;
|
|
539
|
+
if (isColumnReference(expression) && expression.fieldName === columnId) return true;
|
|
540
|
+
}
|
|
541
|
+
return false;
|
|
542
|
+
}
|
|
543
|
+
function validateMysqlName(name, path, kind, diagnostics) {
|
|
544
|
+
if (!isValidSchemaObjectName(name) || name.length === 0) diagnostics.push({
|
|
545
|
+
code: "invalid-schema",
|
|
546
|
+
message: `${kind} name "${name}" is not a valid unqualified identifier`,
|
|
547
|
+
path
|
|
548
|
+
});
|
|
549
|
+
if ([...name].length > 64) diagnostics.push({
|
|
550
|
+
code: "unsupported-dialect-option",
|
|
551
|
+
message: `${kind} name exceeds MySQL's 64-character identifier limit`,
|
|
552
|
+
path
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
function addScopedName(names, name, path, kind, diagnostics) {
|
|
556
|
+
const previousPath = names.get(name);
|
|
557
|
+
if (previousPath !== void 0) {
|
|
558
|
+
diagnostics.push({
|
|
559
|
+
code: "invalid-schema",
|
|
560
|
+
message: `MySQL ${kind} name "${name}" collides with another ${kind}`,
|
|
561
|
+
path,
|
|
562
|
+
relatedPaths: [previousPath]
|
|
563
|
+
});
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
names.set(name, path);
|
|
567
|
+
}
|
|
568
|
+
function appendMetadataDiagnostics(diagnostics, issues) {
|
|
569
|
+
for (const issue of issues) {
|
|
570
|
+
if (issue.code === "dialect-mismatch") continue;
|
|
571
|
+
diagnostics.push({
|
|
572
|
+
code: issue.code === "unsupported-dialect-option" ? "unsupported-dialect-option" : "invalid-schema",
|
|
573
|
+
message: issue.message,
|
|
574
|
+
path: issue.path,
|
|
575
|
+
...issue.relatedPaths === void 0 ? {} : { relatedPaths: issue.relatedPaths }
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
function isOrderTerm(value) {
|
|
580
|
+
return typeof value === "object" && value !== null && "orderKind" in value && value.orderKind === "term" && "expression" in value;
|
|
581
|
+
}
|
|
582
|
+
function compareIds(left, right) {
|
|
583
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
584
|
+
}
|
|
585
|
+
//#endregion
|
|
586
|
+
export { CompleteSnapshotValidationError, SnapshotCanonicalError, SnapshotSerializationError, SnapshotValidationError, assertCompleteSchemaSnapshot, assertCompleteSnapshot, assertSchemaSnapshot, assertSchemaSnapshotV2, canonicalJson, canonicalizeCompleteSchemaSnapshot, canonicalizeSchemaSnapshot, canonicalizeSchemaSnapshotV2, completeSchemaSnapshotFingerprint, completeSchemaSnapshotFormat, completeSchemaSnapshotVersion, createMysqlSchemaSnapshot, createPostgresSchemaSnapshot, createSchemaDialect, createSchemaSnapshot, createSqliteSchemaSnapshot, decodeCompleteSchemaSnapshot, decodeCompleteSnapshot, decodeSchemaSnapshot, decodeSchemaSnapshotV2, encodeCanonicalSnapshot, encodeCompleteSchemaSnapshot, encodeCompleteSnapshot, encodeSchemaSnapshot, encodeSchemaSnapshotV2, encodeSnapshot, fingerprintCompleteSchemaSnapshot, fingerprintSchemaSnapshot, mysqlSchemaDialect, mysqlSnapshotAdapter, mysqlSnapshotDialect, neutralSnapshotDialect, postgresSchemaDialect, postgresSnapshotAdapter, postgresSnapshotDialect, schemaSnapshotCompleteVersion, schemaSnapshotDialectVersion, schemaSnapshotFingerprint, schemaSnapshotFormat, schemaSnapshotNamingPolicyVersion, schemaSnapshotV2Fingerprint, schemaSnapshotV2Version, schemaSnapshotVersion, schemaSnapshotVersion2, serializeSchema, sqliteSchemaDialect, sqliteSnapshotAdapter, sqliteSnapshotDialect, sqliteStorageAffinity, toSnapshotJsonValue, tryCreateMysqlSchemaSnapshot, tryCreatePostgresSchemaSnapshot, tryCreateSchemaSnapshot, tryCreateSqliteSchemaSnapshot };
|
|
@@ -2,7 +2,7 @@ import { t as standardDialect } from "./standard-DfcZEVOj.mjs";
|
|
|
2
2
|
import { F as isValidSchemaObjectName, P as generatedSchemaObjectName, r as isColumnReference } from "./column-DmazTL67.mjs";
|
|
3
3
|
import { _ as isUnsafeSchemaSql, m as validateConstraintDialect, n as defaultSchemaNamingPolicy, w as validateIndexDialect, y as renderSchemaExpression } from "./registry-BGqa05et.mjs";
|
|
4
4
|
import { t as createSchemaDialect } from "./dialect-wUKrnPMB.mjs";
|
|
5
|
-
import { a as toSnapshotJsonValue, f as schemaSnapshotFormat, i as
|
|
5
|
+
import { a as toSnapshotJsonValue, f as schemaSnapshotFormat, i as schemaSnapshotFingerprint, o as SnapshotValidationError, r as encodeCanonicalSnapshot, s as assertSchemaSnapshot, u as neutralSnapshotDialect } from "./canonical-DMvR9yBe.mjs";
|
|
6
6
|
import { sqliteDialect } from "./sqlite.mjs";
|
|
7
7
|
//#region src/snapshot/serialize.ts
|
|
8
8
|
const neutralSchemaDialect = createSchemaDialect(Object.freeze({
|
|
@@ -113,8 +113,8 @@ function encodeSchemaSnapshot(snapshot) {
|
|
|
113
113
|
const serializeSchema = createSchemaSnapshot;
|
|
114
114
|
/** Alias for the canonical snapshot encoder. */
|
|
115
115
|
const encodeSnapshot = encodeSchemaSnapshot;
|
|
116
|
-
/** Alias for the canonical snapshot
|
|
117
|
-
const
|
|
116
|
+
/** Alias for the canonical snapshot fingerprint helper. */
|
|
117
|
+
const fingerprintSchemaSnapshot = schemaSnapshotFingerprint;
|
|
118
118
|
function serializeTable(id, table, physicalName, tableIds, tablesById, dialect, options, diagnostics) {
|
|
119
119
|
const definitions = table.definitions;
|
|
120
120
|
const tableMetadata = table;
|
|
@@ -867,4 +867,4 @@ function compareIds(left, right) {
|
|
|
867
867
|
return left < right ? -1 : left > right ? 1 : 0;
|
|
868
868
|
}
|
|
869
869
|
//#endregion
|
|
870
|
-
export { sqliteStorageAffinity as a, createSchemaSnapshot as c,
|
|
870
|
+
export { sqliteStorageAffinity as a, createSchemaSnapshot as c, fingerprintSchemaSnapshot as d, serializeSchema as f, sqliteSnapshotDialect as i, encodeSchemaSnapshot as l, sqliteSchemaDialect as n, tryCreateSqliteSchemaSnapshot as o, tryCreateSchemaSnapshot as p, sqliteSnapshotAdapter as r, SnapshotSerializationError as s, createSqliteSchemaSnapshot as t, encodeSnapshot as u };
|
package/docs/config.json
CHANGED
|
@@ -111,6 +111,40 @@ const adapter: QueryAdapter = {
|
|
|
111
111
|
}
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
+
### Experimental cloud adapters
|
|
115
|
+
|
|
116
|
+
The workspace also publishes experimental adapters for HTTP and serverless
|
|
117
|
+
database clients. They are not production-ready and are intentionally outside
|
|
118
|
+
provider-backed CI until funded infrastructure and an external maintainer are
|
|
119
|
+
available.
|
|
120
|
+
|
|
121
|
+
| Package | Driver boundary | Advertised capabilities |
|
|
122
|
+
| ----------------------------------------- | -------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
|
123
|
+
| `@qubu/adapter-neon` | Neon HTTP PostgreSQL query function | PostgreSQL rendering, object rows, mutation counts, EXPLAIN, and fetch cancellation |
|
|
124
|
+
| `@qubu/adapter-planetscale` | PlanetScale serverless MySQL client | MySQL rendering, rows, mutation metadata, EXPLAIN, and provider transaction callbacks |
|
|
125
|
+
| `@qubu/adapter-aws-rds-data-api/postgres` | AWS RDS Data API for Aurora PostgreSQL | PostgreSQL rendering, typed Data API values/results, EXPLAIN, mutation metadata, and transaction IDs |
|
|
126
|
+
| `@qubu/adapter-aws-rds-data-api/mysql` | AWS RDS Data API for Aurora MySQL | MySQL rendering, typed Data API values/results, EXPLAIN, mutation metadata, and transaction IDs |
|
|
127
|
+
|
|
128
|
+
Neon HTTP and the RDS Data API do not advertise interactive streaming. The
|
|
129
|
+
RDS adapter uses named placeholders (`:p1`, `:p2`, ...), while PlanetScale uses
|
|
130
|
+
the MySQL dialect and the provider's own value formatting. Read each package's
|
|
131
|
+
README for its experimental boundary and required provider setup.
|
|
132
|
+
|
|
133
|
+
### Official SQLite WASM adapter
|
|
134
|
+
|
|
135
|
+
`@qubu/adapter-sqlite-wasm` adapts the official SQLite WASM OO1
|
|
136
|
+
`sqlite3.oo1.DB` API for browser and web-worker use. It prepares one statement
|
|
137
|
+
per request, binds Qubu's ordered parameters, copies object rows, and finalizes
|
|
138
|
+
the statement in a `finally` block. SQLite's change count and generated row ID
|
|
139
|
+
are returned as mutation metadata when the request is a mutation.
|
|
140
|
+
|
|
141
|
+
The package does not create or terminate workers. Initialize the official
|
|
142
|
+
SQLite module inside a dedicated worker, construct the adapter around its
|
|
143
|
+
`sqlite3.oo1.DB`, and close the adapter before terminating that worker. The
|
|
144
|
+
browser build must serve the package's `sqlite3.wasm` asset beside the bundled
|
|
145
|
+
worker module; the combo runner's verified browser scenario demonstrates this
|
|
146
|
+
lifecycle.
|
|
147
|
+
|
|
114
148
|
### Decode schema-aware result values
|
|
115
149
|
|
|
116
150
|
Portable boolean, date, timestamp, and JSON columns retain their logical
|