@easy-cms/drizzle 0.6.0 → 0.8.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.
- package/dist/index.d.ts +34 -1
- package/dist/index.js +432 -254
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -19,9 +19,23 @@ interface SqlRunner {
|
|
|
19
19
|
/** Runs the statements in one transaction: all or nothing. */
|
|
20
20
|
transaction(statements: readonly Statement[]): Promise<void>;
|
|
21
21
|
}
|
|
22
|
+
/** How to read a value out of a JSON element for comparing. */
|
|
23
|
+
type JsonValueType = 'text' | 'number' | 'integer' | 'boolean';
|
|
24
|
+
/** The JSON element being tested. Paths are field names, already checked against the config. */
|
|
25
|
+
interface JsonElement {
|
|
26
|
+
/** A value inside the element, e.g. `['heading', 'en']`; `[]` for the element itself. */
|
|
27
|
+
get(path: readonly string[], type: JsonValueType): SQL;
|
|
28
|
+
/** A JSON array inside the element, to test with `someElement`. */
|
|
29
|
+
list(path: readonly string[]): SQL;
|
|
30
|
+
}
|
|
22
31
|
/** What differs between SQL dialects. */
|
|
23
32
|
interface Dialect {
|
|
24
33
|
readonly name: 'sqlite' | 'postgres';
|
|
34
|
+
/**
|
|
35
|
+
* One writer at a time (SQLite): writes from this process wait for each other instead of
|
|
36
|
+
* failing with "database is locked".
|
|
37
|
+
*/
|
|
38
|
+
readonly singleWriter?: boolean;
|
|
25
39
|
table(name: string, columns: Record<string, ColumnBuilder>, indexes: (t: Record<string, AnyColumn>) => IndexBuilder[]): AnyTable;
|
|
26
40
|
index(name: string): {
|
|
27
41
|
on(column: AnyColumn): IndexBuilder;
|
|
@@ -39,6 +53,13 @@ interface Dialect {
|
|
|
39
53
|
json(name: string): ColumnBuilder;
|
|
40
54
|
/** Case-insensitive substring match; `pattern` is already escaped and wrapped in `%`. */
|
|
41
55
|
like(column: AnyColumn, pattern: string): SQL;
|
|
56
|
+
/**
|
|
57
|
+
* True when some element of a JSON array (a column, or `JsonElement.list`) matches `condition`
|
|
58
|
+
* (any element when it returns undefined). Anything but an array has no elements.
|
|
59
|
+
*/
|
|
60
|
+
someElement(array: AnyColumn | SQL, condition: (el: JsonElement) => SQL | undefined): SQL;
|
|
61
|
+
/** The value at `path` in the first element of a JSON array that has one, for sorting. */
|
|
62
|
+
firstElementValue(array: AnyColumn | SQL, path: readonly string[], type: JsonValueType): SQL;
|
|
42
63
|
/** Placeholder for the n-th parameter (1-based) in raw SQL. */
|
|
43
64
|
param(n: number): string;
|
|
44
65
|
/** CREATE TABLE IF NOT EXISTS for the migrations table. */
|
|
@@ -111,6 +132,12 @@ interface SchemaModel {
|
|
|
111
132
|
readonly tables: Readonly<Record<string, AnyTable>>;
|
|
112
133
|
/** Changes whenever the generated schema changes. Used to detect missing migrations. */
|
|
113
134
|
readonly hash: string;
|
|
135
|
+
/** Locales of localized fields, recorded with snapshots so a new default locale keeps data. */
|
|
136
|
+
readonly localization: SchemaLocalization | null;
|
|
137
|
+
}
|
|
138
|
+
interface SchemaLocalization {
|
|
139
|
+
readonly defaultLocale: string;
|
|
140
|
+
readonly locales: readonly string[];
|
|
114
141
|
}
|
|
115
142
|
declare const snake: (name: string) => string;
|
|
116
143
|
/** Builds Drizzle tables for every collection and global in the config. */
|
|
@@ -139,6 +166,12 @@ declare class Migrator {
|
|
|
139
166
|
private snapshotOf;
|
|
140
167
|
/** SQL statements that turn `prev` into `cur`. Renames become drop + add unless interactive. */
|
|
141
168
|
private diff;
|
|
169
|
+
/**
|
|
170
|
+
* A new default locale changes which locale the plain column holds (`title` is the default
|
|
171
|
+
* locale's, `title__en` another's). Moves the values so every locale keeps its own.
|
|
172
|
+
* Snapshots from before 0.7 do not record the locales, so nothing can be moved for them.
|
|
173
|
+
*/
|
|
174
|
+
private localeMoves;
|
|
142
175
|
ensureTable(): Promise<void>;
|
|
143
176
|
private record;
|
|
144
177
|
private applied;
|
|
@@ -161,4 +194,4 @@ declare class Migrator {
|
|
|
161
194
|
private files;
|
|
162
195
|
}
|
|
163
196
|
|
|
164
|
-
export { type AnyColumn, type AnyTable, type ColumnBuilder, type Connection, DEFAULT_MIGRATION_DIR, DEFAULT_TABLE_PREFIX, type Dialect, type DrizzleAdapterOptions, type DrizzleDb, type IndexBuilder, Migrator, type SchemaModel, type Snapshot, type SqlRunner, type Statement, buildSchema, connectDatabase, snake, unionSnapshot };
|
|
197
|
+
export { type AnyColumn, type AnyTable, type ColumnBuilder, type Connection, DEFAULT_MIGRATION_DIR, DEFAULT_TABLE_PREFIX, type Dialect, type DrizzleAdapterOptions, type DrizzleDb, type IndexBuilder, type JsonElement, type JsonValueType, Migrator, type SchemaModel, type Snapshot, type SqlRunner, type Statement, buildSchema, connectDatabase, snake, unionSnapshot };
|
package/dist/index.js
CHANGED
|
@@ -168,6 +168,248 @@ async function deleteDocument(db, model, id) {
|
|
|
168
168
|
import { mkdir, readdir, readFile, writeFile } from "fs/promises";
|
|
169
169
|
import { join } from "path";
|
|
170
170
|
import { SchemaError } from "@easy-cms/core";
|
|
171
|
+
|
|
172
|
+
// src/schema.ts
|
|
173
|
+
import { createHash } from "crypto";
|
|
174
|
+
import { ConfigError } from "@easy-cms/core";
|
|
175
|
+
function tableModels(schema) {
|
|
176
|
+
const all = [];
|
|
177
|
+
const walk = (model) => {
|
|
178
|
+
all.push(model);
|
|
179
|
+
for (const child of model.children) walk(child.table);
|
|
180
|
+
};
|
|
181
|
+
for (const collection of schema.collections.values()) walk(collection.root);
|
|
182
|
+
return all;
|
|
183
|
+
}
|
|
184
|
+
function migrationsTableName(prefix) {
|
|
185
|
+
return `${prefix}migrations`;
|
|
186
|
+
}
|
|
187
|
+
var snake = (name) => name.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").toLowerCase();
|
|
188
|
+
function columnKind(field) {
|
|
189
|
+
switch (field.type) {
|
|
190
|
+
case "number":
|
|
191
|
+
return "number";
|
|
192
|
+
case "boolean":
|
|
193
|
+
return "boolean";
|
|
194
|
+
case "json":
|
|
195
|
+
case "richText":
|
|
196
|
+
// Blocks are stored whole as JSON: rows of different kinds don't fit one child table.
|
|
197
|
+
case "blocks":
|
|
198
|
+
return "json";
|
|
199
|
+
case "upload":
|
|
200
|
+
case "relationship":
|
|
201
|
+
return "integer";
|
|
202
|
+
default:
|
|
203
|
+
return "text";
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function buildSchema(config, prefix, dialect) {
|
|
207
|
+
const builder = {
|
|
208
|
+
dialect,
|
|
209
|
+
prefix,
|
|
210
|
+
tableNames: /* @__PURE__ */ new Set([migrationsTableName(prefix)]),
|
|
211
|
+
// The hash covers the logical schema only, so it stays stable across releases for the same config.
|
|
212
|
+
description: [],
|
|
213
|
+
localization: config.localization
|
|
214
|
+
};
|
|
215
|
+
const collections = /* @__PURE__ */ new Map();
|
|
216
|
+
const tables = {};
|
|
217
|
+
for (const collection of config.collections) {
|
|
218
|
+
const root = buildTable(
|
|
219
|
+
builder,
|
|
220
|
+
`${prefix}${snake(collection.slug)}`,
|
|
221
|
+
"root",
|
|
222
|
+
collection.fields,
|
|
223
|
+
{
|
|
224
|
+
drafts: collection.drafts === true
|
|
225
|
+
}
|
|
226
|
+
);
|
|
227
|
+
collections.set(collection.slug, { config: collection, root });
|
|
228
|
+
collectTables(root, tables);
|
|
229
|
+
}
|
|
230
|
+
const globalsName = claimName(builder, `${prefix}globals`);
|
|
231
|
+
const globals = dialect.table(
|
|
232
|
+
globalsName,
|
|
233
|
+
{
|
|
234
|
+
slug: dialect.text("slug").primaryKey(),
|
|
235
|
+
data: dialect.json("data").notNull(),
|
|
236
|
+
status: dialect.text("status"),
|
|
237
|
+
updated_at: dialect.text("updated_at").notNull()
|
|
238
|
+
},
|
|
239
|
+
() => []
|
|
240
|
+
);
|
|
241
|
+
tables[globalsName] = globals;
|
|
242
|
+
builder.description.push(["globals", globalsName]);
|
|
243
|
+
const hash = createHash("sha256").update(JSON.stringify(builder.description)).digest("hex").slice(0, 16);
|
|
244
|
+
const localization = config.localization ? { defaultLocale: config.localization.defaultLocale, locales: config.localization.locales } : null;
|
|
245
|
+
return { collections, globals, tables, hash, localization };
|
|
246
|
+
}
|
|
247
|
+
function collectTables(model, into) {
|
|
248
|
+
into[model.name] = model.table;
|
|
249
|
+
for (const child of model.children) collectTables(child.table, into);
|
|
250
|
+
}
|
|
251
|
+
function claimName(builder, name) {
|
|
252
|
+
if (builder.tableNames.has(name)) {
|
|
253
|
+
throw new ConfigError([
|
|
254
|
+
{
|
|
255
|
+
path: name,
|
|
256
|
+
message: `two tables would be named "${name}"`,
|
|
257
|
+
hint: "rename one of the collections or fields"
|
|
258
|
+
}
|
|
259
|
+
]);
|
|
260
|
+
}
|
|
261
|
+
builder.tableNames.add(name);
|
|
262
|
+
return name;
|
|
263
|
+
}
|
|
264
|
+
function makeColumn(dialect, name, kind) {
|
|
265
|
+
switch (kind) {
|
|
266
|
+
case "number":
|
|
267
|
+
return dialect.number(name);
|
|
268
|
+
case "integer":
|
|
269
|
+
return dialect.integer(name);
|
|
270
|
+
case "boolean":
|
|
271
|
+
return dialect.boolean(name);
|
|
272
|
+
case "json":
|
|
273
|
+
return dialect.json(name);
|
|
274
|
+
case "text":
|
|
275
|
+
return dialect.text(name);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
function buildTable(builder, name, kind, fields, options) {
|
|
279
|
+
const { dialect } = builder;
|
|
280
|
+
claimName(builder, name);
|
|
281
|
+
const columns = {};
|
|
282
|
+
const described = [];
|
|
283
|
+
const columnModels = [];
|
|
284
|
+
const children = [];
|
|
285
|
+
const indexes = [];
|
|
286
|
+
const add = (column, builderColumn, desc2) => {
|
|
287
|
+
if (Object.hasOwn(columns, column)) {
|
|
288
|
+
throw new ConfigError([
|
|
289
|
+
{
|
|
290
|
+
path: `${name}.${column}`,
|
|
291
|
+
message: `two fields would use the column "${column}"`,
|
|
292
|
+
hint: "rename one of them"
|
|
293
|
+
}
|
|
294
|
+
]);
|
|
295
|
+
}
|
|
296
|
+
columns[column] = builderColumn;
|
|
297
|
+
described.push([column, desc2]);
|
|
298
|
+
};
|
|
299
|
+
if (kind === "root") {
|
|
300
|
+
add("id", dialect.serial("id"), "id:int");
|
|
301
|
+
add("created_at", dialect.text("created_at").notNull(), "text!");
|
|
302
|
+
add("updated_at", dialect.text("updated_at").notNull(), "text!");
|
|
303
|
+
if (options.drafts) add("status", dialect.text("status").notNull().default("draft"), "status");
|
|
304
|
+
indexes.push({ column: "created_at", unique: false });
|
|
305
|
+
} else {
|
|
306
|
+
const parentKind = options.parentIdKind ?? "integer";
|
|
307
|
+
add(
|
|
308
|
+
"id",
|
|
309
|
+
kind === "array" ? dialect.text("id").primaryKey() : dialect.serial("id"),
|
|
310
|
+
`id:${kind}`
|
|
311
|
+
);
|
|
312
|
+
add(
|
|
313
|
+
"_parent_id",
|
|
314
|
+
parentKind === "text" ? dialect.text("_parent_id").notNull() : dialect.integer("_parent_id").notNull(),
|
|
315
|
+
parentKind
|
|
316
|
+
);
|
|
317
|
+
add("_order", dialect.integer("_order").notNull(), "int!");
|
|
318
|
+
indexes.push({ column: "_parent_id", unique: false });
|
|
319
|
+
if (options.localized) {
|
|
320
|
+
add(
|
|
321
|
+
"_locale",
|
|
322
|
+
dialect.text("_locale").notNull().default(options.localized),
|
|
323
|
+
`locale:${options.localized}`
|
|
324
|
+
);
|
|
325
|
+
indexes.push({ column: "_locale", unique: false });
|
|
326
|
+
}
|
|
327
|
+
if (kind === "values" && options.valueField) {
|
|
328
|
+
const valueKind = columnKind(options.valueField);
|
|
329
|
+
add("value", makeColumn(dialect, "value", valueKind), valueKind);
|
|
330
|
+
indexes.push({ column: "value", unique: false });
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
const walk = (list, path, topLevel) => {
|
|
334
|
+
for (const field of list) {
|
|
335
|
+
const fieldPath = [...path, field.name];
|
|
336
|
+
const base = fieldPath.map(snake).join("_");
|
|
337
|
+
if (field.type === "group") {
|
|
338
|
+
walk(field.fields, fieldPath, false);
|
|
339
|
+
} else if (field.type === "array") {
|
|
340
|
+
const localization = field.localized ? builder.localization : null;
|
|
341
|
+
const child = buildTable(builder, `${name}__${base}`, "array", field.fields, {
|
|
342
|
+
parentIdKind: kind === "array" ? "text" : "integer",
|
|
343
|
+
...localization ? { localized: localization.defaultLocale } : {}
|
|
344
|
+
});
|
|
345
|
+
children.push({
|
|
346
|
+
kind: "array",
|
|
347
|
+
path: fieldPath,
|
|
348
|
+
field,
|
|
349
|
+
table: child,
|
|
350
|
+
...localeInfo(localization)
|
|
351
|
+
});
|
|
352
|
+
described.push([base, "array", child.name]);
|
|
353
|
+
} else if ((field.type === "select" || field.type === "relationship") && field.hasMany) {
|
|
354
|
+
const localization = field.localized ? builder.localization : null;
|
|
355
|
+
const child = buildTable(builder, `${name}__${base}`, "values", [], {
|
|
356
|
+
parentIdKind: kind === "array" ? "text" : "integer",
|
|
357
|
+
valueField: field,
|
|
358
|
+
...localization ? { localized: localization.defaultLocale } : {}
|
|
359
|
+
});
|
|
360
|
+
children.push({
|
|
361
|
+
kind: "values",
|
|
362
|
+
path: fieldPath,
|
|
363
|
+
field,
|
|
364
|
+
table: child,
|
|
365
|
+
...localeInfo(localization)
|
|
366
|
+
});
|
|
367
|
+
described.push([base, "values", child.name]);
|
|
368
|
+
} else {
|
|
369
|
+
const kindOfColumn = columnKind(field);
|
|
370
|
+
const localization = field.localized ? builder.localization : null;
|
|
371
|
+
const targets = localization ? localization.locales.map((locale) => ({
|
|
372
|
+
column: locale === localization.defaultLocale ? base : `${base}__${snake(locale)}`,
|
|
373
|
+
locale
|
|
374
|
+
})) : [{ column: base, locale: void 0 }];
|
|
375
|
+
for (const { column, locale } of targets) {
|
|
376
|
+
add(column, makeColumn(dialect, column, kindOfColumn), kindOfColumn);
|
|
377
|
+
columnModels.push(
|
|
378
|
+
locale === void 0 ? { path: fieldPath, field, column } : {
|
|
379
|
+
path: fieldPath,
|
|
380
|
+
field,
|
|
381
|
+
column,
|
|
382
|
+
locale,
|
|
383
|
+
defaultLocale: locale === localization?.defaultLocale
|
|
384
|
+
}
|
|
385
|
+
);
|
|
386
|
+
if (topLevel && kind === "root" && (field.unique || field.type === "slug")) {
|
|
387
|
+
indexes.push({ column, unique: true });
|
|
388
|
+
} else if (field.index || field.unique || field.type === "relationship" || field.type === "upload") {
|
|
389
|
+
indexes.push({ column, unique: false });
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
walk(fields, [], true);
|
|
396
|
+
const table = dialect.table(
|
|
397
|
+
name,
|
|
398
|
+
columns,
|
|
399
|
+
(t) => indexes.map(({ column, unique }) => {
|
|
400
|
+
const col3 = t[column];
|
|
401
|
+
return unique ? dialect.uniqueIndex(`${name}_${column}_unique`).on(col3) : dialect.index(`${name}_${column}_idx`).on(col3);
|
|
402
|
+
})
|
|
403
|
+
);
|
|
404
|
+
builder.description.push([name, kind, described, indexes]);
|
|
405
|
+
const model = { name, kind, table, fields, columns: columnModels, children };
|
|
406
|
+
return options.valueField ? { ...model, valueField: options.valueField } : model;
|
|
407
|
+
}
|
|
408
|
+
function localeInfo(localization) {
|
|
409
|
+
return localization ? { locales: localization.locales, defaultLocale: localization.defaultLocale } : {};
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// src/migrations.ts
|
|
171
413
|
var BREAKPOINT = "--> statement-breakpoint";
|
|
172
414
|
var TABLE_PARTS = [
|
|
173
415
|
"columns",
|
|
@@ -196,6 +438,17 @@ function unionSnapshot(prev, cur) {
|
|
|
196
438
|
}
|
|
197
439
|
return merged;
|
|
198
440
|
}
|
|
441
|
+
var META = "easyCms";
|
|
442
|
+
var metaOf = (snapshot) => snapshot[META];
|
|
443
|
+
function withoutMeta(snapshot) {
|
|
444
|
+
const { [META]: _, ...rest } = snapshot;
|
|
445
|
+
return rest;
|
|
446
|
+
}
|
|
447
|
+
function snapshotTable(snapshot, name) {
|
|
448
|
+
return Object.values(snapshot.tables ?? {}).find(
|
|
449
|
+
(t) => t.name === name
|
|
450
|
+
);
|
|
451
|
+
}
|
|
199
452
|
var DESTRUCTIVE = /\b(DROP TABLE|DROP COLUMN)\b|__new_/i;
|
|
200
453
|
var DEV_PUSH = "dev";
|
|
201
454
|
var Migrator = class {
|
|
@@ -209,15 +462,58 @@ var Migrator = class {
|
|
|
209
462
|
q(name) {
|
|
210
463
|
return this.options.dialect.name === "postgres" ? `"${name}"` : `\`${name}\``;
|
|
211
464
|
}
|
|
212
|
-
snapshotOf(schema) {
|
|
213
|
-
|
|
465
|
+
async snapshotOf(schema) {
|
|
466
|
+
const snapshot = await this.options.dialect.snapshot(schema ? schema.tables : {});
|
|
467
|
+
const meta = { localization: schema?.localization ?? null };
|
|
468
|
+
return { ...snapshot, [META]: meta };
|
|
214
469
|
}
|
|
215
470
|
/** SQL statements that turn `prev` into `cur`. Renames become drop + add unless interactive. */
|
|
216
471
|
async diff(prev, cur, interactive) {
|
|
217
472
|
const { dialect } = this.options;
|
|
218
|
-
|
|
219
|
-
const
|
|
220
|
-
|
|
473
|
+
const moves = this.localeMoves(prev, metaOf(prev), metaOf(cur));
|
|
474
|
+
const [from, to] = [withoutMeta(prev), withoutMeta(cur)];
|
|
475
|
+
if (interactive && moves.length === 0) return dialect.migration(from, to);
|
|
476
|
+
const union = unionSnapshot(from, to);
|
|
477
|
+
return [
|
|
478
|
+
...await dialect.migration(from, union),
|
|
479
|
+
...moves,
|
|
480
|
+
...await dialect.migration(union, to)
|
|
481
|
+
];
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* A new default locale changes which locale the plain column holds (`title` is the default
|
|
485
|
+
* locale's, `title__en` another's). Moves the values so every locale keeps its own.
|
|
486
|
+
* Snapshots from before 0.7 do not record the locales, so nothing can be moved for them.
|
|
487
|
+
*/
|
|
488
|
+
localeMoves(prev, before, after) {
|
|
489
|
+
const was = before?.localization;
|
|
490
|
+
const now = after?.localization;
|
|
491
|
+
if (!was || !now || was.defaultLocale === now.defaultLocale) return [];
|
|
492
|
+
const q = (name) => this.q(name);
|
|
493
|
+
const statements = [];
|
|
494
|
+
for (const model of tableModels(this.options.schema)) {
|
|
495
|
+
const old = snapshotTable(prev, model.name);
|
|
496
|
+
if (!old) continue;
|
|
497
|
+
const has = (column) => Object.hasOwn(old.columns ?? {}, column);
|
|
498
|
+
const keep = [];
|
|
499
|
+
const clear = [];
|
|
500
|
+
const fill = [];
|
|
501
|
+
for (const column of model.columns) {
|
|
502
|
+
if (!column.defaultLocale || !has(column.column)) continue;
|
|
503
|
+
const base = column.column;
|
|
504
|
+
const moved = model.columns.find(
|
|
505
|
+
(c) => c.path.join(".") === column.path.join(".") && c.locale === was.defaultLocale
|
|
506
|
+
);
|
|
507
|
+
if (moved && !has(moved.column)) keep.push(`${q(moved.column)} = ${q(base)}`);
|
|
508
|
+
clear.push(`${q(base)} = NULL`);
|
|
509
|
+
const incoming = `${base}__${snake(now.defaultLocale)}`;
|
|
510
|
+
if (has(incoming)) fill.push(`${q(base)} = ${q(incoming)}`);
|
|
511
|
+
}
|
|
512
|
+
for (const set of [keep, clear, fill]) {
|
|
513
|
+
if (set.length > 0) statements.push(`UPDATE ${q(model.name)} SET ${set.join(", ")}`);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
return statements;
|
|
221
517
|
}
|
|
222
518
|
async ensureTable() {
|
|
223
519
|
await this.runner.query(this.options.dialect.migrationsTableSQL(this.options.table));
|
|
@@ -417,7 +713,53 @@ var SYSTEM_COLUMNS = {
|
|
|
417
713
|
var col2 = (model, name) => model.table[name];
|
|
418
714
|
var ID_FIELD = { name: "id", type: "relationship", to: "" };
|
|
419
715
|
var LIKE_TYPES = /* @__PURE__ */ new Set(["text", "textarea", "email", "slug", "select"]);
|
|
420
|
-
|
|
716
|
+
var JSON_TYPES = {
|
|
717
|
+
number: "number",
|
|
718
|
+
boolean: "boolean",
|
|
719
|
+
relationship: "integer",
|
|
720
|
+
upload: "integer"
|
|
721
|
+
};
|
|
722
|
+
function resolveJsonValue(fields, rest, shown, localization, blocks) {
|
|
723
|
+
if (rest.length === 0) return { lists: [], path: null, type: "text", field: void 0 };
|
|
724
|
+
if (blocks && rest.length === 1 && rest[0] === "blockType")
|
|
725
|
+
return { lists: [], path: ["blockType"], type: "text", field: void 0 };
|
|
726
|
+
let list = fields;
|
|
727
|
+
const path = [];
|
|
728
|
+
for (let i = 0; i < rest.length; i++) {
|
|
729
|
+
const segment = rest[i];
|
|
730
|
+
const field = list.find((f) => f.name === segment);
|
|
731
|
+
const at = [shown, ...rest.slice(0, i + 1)].join(".");
|
|
732
|
+
if (!field) throw new QueryError(`Unknown field "${at}"`);
|
|
733
|
+
path.push(segment);
|
|
734
|
+
if (field.type === "group") {
|
|
735
|
+
list = field.fields;
|
|
736
|
+
continue;
|
|
737
|
+
}
|
|
738
|
+
if (field.type === "json" || field.type === "richText")
|
|
739
|
+
throw new QueryError(`Cannot query "${at}": ${field.type} fields are not queryable`);
|
|
740
|
+
let next = i + 1;
|
|
741
|
+
if (field.localized && localization) {
|
|
742
|
+
const named = rest[next] !== void 0 && localization.locales.includes(rest[next]);
|
|
743
|
+
path.push(named ? rest[next] : localization.defaultLocale);
|
|
744
|
+
if (named) next++;
|
|
745
|
+
}
|
|
746
|
+
const inner = rest.slice(next);
|
|
747
|
+
const shownHere = [shown, ...rest.slice(0, next)].join(".");
|
|
748
|
+
if ("hasMany" in field && field.hasMany === true) {
|
|
749
|
+
if (inner.length > 0) throw new QueryError(`Cannot query inside "${shownHere}"`);
|
|
750
|
+
return { lists: [path], path: [], type: JSON_TYPES[field.type] ?? "text", field };
|
|
751
|
+
}
|
|
752
|
+
if (field.type === "array" || field.type === "blocks") {
|
|
753
|
+
const rows = field.type === "array" ? field.fields : field.blocks.flatMap((block) => block.fields);
|
|
754
|
+
const value = resolveJsonValue(rows, inner, shownHere, localization, field.type === "blocks");
|
|
755
|
+
return { ...value, lists: [path, ...value.lists] };
|
|
756
|
+
}
|
|
757
|
+
if (inner.length > 0) throw new QueryError(`Cannot query inside "${at}"`);
|
|
758
|
+
return { lists: [], path, type: JSON_TYPES[field.type] ?? "text", field };
|
|
759
|
+
}
|
|
760
|
+
throw new QueryError(`"${[shown, ...rest].join(".")}" is a group; query one of its fields`);
|
|
761
|
+
}
|
|
762
|
+
function resolvePath(model, segments, drafts, localization = null) {
|
|
421
763
|
const [first] = segments;
|
|
422
764
|
if (first === void 0) throw new QueryError("empty field path");
|
|
423
765
|
if (segments.length === 1) {
|
|
@@ -454,18 +796,29 @@ function resolvePath(model, segments, drafts) {
|
|
|
454
796
|
};
|
|
455
797
|
}
|
|
456
798
|
if (child) return { kind: "child", child, rest: segments.slice(i + 1) };
|
|
457
|
-
|
|
799
|
+
let locale = field.localized ? segments[i + 1] : void 0;
|
|
800
|
+
if (field.type === "blocks" && locale !== void 0 && !localization?.locales.includes(locale))
|
|
801
|
+
locale = void 0;
|
|
458
802
|
const last = field.localized && locale !== void 0 ? i + 1 : i;
|
|
803
|
+
const same = (c) => c.path.join(".") === path.join(".");
|
|
804
|
+
const columnOf = () => field.localized ? model.columns.find(
|
|
805
|
+
(c) => same(c) && (locale === void 0 ? c.defaultLocale : c.locale === locale)
|
|
806
|
+
) : model.columns.find(same);
|
|
807
|
+
if (field.type === "blocks") {
|
|
808
|
+
const column2 = columnOf();
|
|
809
|
+
if (!column2) throw new QueryError(`Unknown locale "${locale}" for "${shown}"`);
|
|
810
|
+
const rest = segments.slice(last + 1);
|
|
811
|
+
const rows = field.blocks.flatMap((block) => block.fields);
|
|
812
|
+
const value = resolveJsonValue(rows, rest, shown, localization, true);
|
|
813
|
+
return { kind: "blocks", column: col2(model, column2.column), value };
|
|
814
|
+
}
|
|
459
815
|
if (last < segments.length - 1) {
|
|
460
816
|
throw new QueryError(`Cannot query inside "${shown}" (${field.type} field)`);
|
|
461
817
|
}
|
|
462
|
-
if (field.type === "json" || field.type === "richText"
|
|
818
|
+
if (field.type === "json" || field.type === "richText") {
|
|
463
819
|
throw new QueryError(`Cannot query "${shown}": ${field.type} fields are not queryable`);
|
|
464
820
|
}
|
|
465
|
-
const
|
|
466
|
-
const column = field.localized ? model.columns.find(
|
|
467
|
-
(c) => same(c) && (locale === void 0 ? c.defaultLocale : c.locale === locale)
|
|
468
|
-
) : model.columns.find(same);
|
|
821
|
+
const column = columnOf();
|
|
469
822
|
if (field.localized && !column)
|
|
470
823
|
throw new QueryError(`Unknown locale "${locale}" for "${shown}"`);
|
|
471
824
|
if (!column) throw new QueryError(`Unknown field "${shown}"`);
|
|
@@ -521,14 +874,16 @@ function operatorSQL(dialect, column, field, op, raw, path) {
|
|
|
521
874
|
}
|
|
522
875
|
}
|
|
523
876
|
var WhereBuilder = class {
|
|
524
|
-
constructor(db, dialect, drafts) {
|
|
877
|
+
constructor(db, dialect, drafts, localization = null) {
|
|
525
878
|
this.db = db;
|
|
526
879
|
this.dialect = dialect;
|
|
527
880
|
this.drafts = drafts;
|
|
881
|
+
this.localization = localization;
|
|
528
882
|
}
|
|
529
883
|
db;
|
|
530
884
|
dialect;
|
|
531
885
|
drafts;
|
|
886
|
+
localization;
|
|
532
887
|
build(model, where) {
|
|
533
888
|
if (where === void 0) return void 0;
|
|
534
889
|
if (typeof where !== "object" || where === null || Array.isArray(where)) {
|
|
@@ -553,15 +908,38 @@ var WhereBuilder = class {
|
|
|
553
908
|
}
|
|
554
909
|
const entries = Object.entries(operators).filter(([, v]) => v !== void 0);
|
|
555
910
|
if (entries.length === 0) throw new QueryError(`"${path}" has no operator`);
|
|
556
|
-
const target = resolvePath(
|
|
911
|
+
const target = resolvePath(
|
|
912
|
+
model,
|
|
913
|
+
path.split("."),
|
|
914
|
+
this.drafts && model.kind === "root",
|
|
915
|
+
this.localization
|
|
916
|
+
);
|
|
557
917
|
const parts = entries.map(([op, value]) => {
|
|
558
918
|
if (!OPERATORS.has(op)) throw new QueryError(`Unknown operator "${op}" on "${path}"`);
|
|
559
919
|
if (target.kind === "column")
|
|
560
920
|
return operatorSQL(this.dialect, target.column, target.field, op, value, path);
|
|
921
|
+
if (target.kind === "blocks") return this.blocks(target, op, value, path);
|
|
561
922
|
return this.child(model, target.child, target.rest, op, value, path, target.locale);
|
|
562
923
|
});
|
|
563
924
|
return parts.length === 1 ? parts[0] : and(...parts);
|
|
564
925
|
}
|
|
926
|
+
/**
|
|
927
|
+
* Conditions inside blocks test the JSON rows: some block (and, for nested lists, some row in
|
|
928
|
+
* it) must match.
|
|
929
|
+
*/
|
|
930
|
+
blocks(target, op, value, path) {
|
|
931
|
+
const { lists, path: inner, type, field } = target.value;
|
|
932
|
+
if (inner === null && op !== "exists")
|
|
933
|
+
throw new QueryError(`"${path}" is a list; query one of its fields`);
|
|
934
|
+
const match = (el, level) => {
|
|
935
|
+
const list = lists[level];
|
|
936
|
+
if (list) return this.dialect.someElement(el.list(list), (row) => match(row, level + 1));
|
|
937
|
+
if (inner === null) return void 0;
|
|
938
|
+
return operatorSQL(this.dialect, el.get(inner, type), field, op, value, path);
|
|
939
|
+
};
|
|
940
|
+
const some = this.dialect.someElement(target.column, (el) => match(el, 0));
|
|
941
|
+
return inner === null && value === false ? not(some) : some;
|
|
942
|
+
}
|
|
565
943
|
/** Conditions on array rows or hasMany values become (NOT) EXISTS subqueries. */
|
|
566
944
|
child(parent, child, rest, op, value, path, locale) {
|
|
567
945
|
const table = child.table;
|
|
@@ -590,249 +968,27 @@ var WhereBuilder = class {
|
|
|
590
968
|
}
|
|
591
969
|
return subquery(this.build(table, { [rest.join(".")]: { [op]: value } }));
|
|
592
970
|
}
|
|
971
|
+
/** A column, or the value in the first block that has it. */
|
|
972
|
+
sortValue(target, path) {
|
|
973
|
+
if (target.kind === "column") return target.column;
|
|
974
|
+
if (target.kind === "blocks" && target.value.lists.length === 0 && target.value.path) {
|
|
975
|
+
return this.dialect.firstElementValue(target.column, target.value.path, target.value.type);
|
|
976
|
+
}
|
|
977
|
+
throw new QueryError(`Cannot sort by "${path}"`);
|
|
978
|
+
}
|
|
593
979
|
orderBy(model, sort) {
|
|
594
980
|
const order = sort.map((entry) => {
|
|
595
981
|
const descending = entry.startsWith("-");
|
|
596
982
|
const path = descending ? entry.slice(1) : entry;
|
|
597
|
-
const target = resolvePath(model, path.split("."), this.drafts);
|
|
598
|
-
|
|
599
|
-
return descending ? desc(
|
|
983
|
+
const target = resolvePath(model, path.split("."), this.drafts, this.localization);
|
|
984
|
+
const value = this.sortValue(target, path);
|
|
985
|
+
return descending ? desc(value) : asc2(value);
|
|
600
986
|
});
|
|
601
987
|
order.push(desc(col2(model, "id")));
|
|
602
988
|
return order;
|
|
603
989
|
}
|
|
604
990
|
};
|
|
605
991
|
|
|
606
|
-
// src/schema.ts
|
|
607
|
-
import { createHash } from "crypto";
|
|
608
|
-
import { ConfigError } from "@easy-cms/core";
|
|
609
|
-
function migrationsTableName(prefix) {
|
|
610
|
-
return `${prefix}migrations`;
|
|
611
|
-
}
|
|
612
|
-
var snake = (name) => name.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").toLowerCase();
|
|
613
|
-
function columnKind(field) {
|
|
614
|
-
switch (field.type) {
|
|
615
|
-
case "number":
|
|
616
|
-
return "number";
|
|
617
|
-
case "boolean":
|
|
618
|
-
return "boolean";
|
|
619
|
-
case "json":
|
|
620
|
-
case "richText":
|
|
621
|
-
// Blocks are stored whole as JSON: rows of different kinds don't fit one child table.
|
|
622
|
-
case "blocks":
|
|
623
|
-
return "json";
|
|
624
|
-
case "upload":
|
|
625
|
-
case "relationship":
|
|
626
|
-
return "integer";
|
|
627
|
-
default:
|
|
628
|
-
return "text";
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
function buildSchema(config, prefix, dialect) {
|
|
632
|
-
const builder = {
|
|
633
|
-
dialect,
|
|
634
|
-
prefix,
|
|
635
|
-
tableNames: /* @__PURE__ */ new Set([migrationsTableName(prefix)]),
|
|
636
|
-
// The hash covers the logical schema only, so it stays stable across releases for the same config.
|
|
637
|
-
description: [],
|
|
638
|
-
localization: config.localization
|
|
639
|
-
};
|
|
640
|
-
const collections = /* @__PURE__ */ new Map();
|
|
641
|
-
const tables = {};
|
|
642
|
-
for (const collection of config.collections) {
|
|
643
|
-
const root = buildTable(
|
|
644
|
-
builder,
|
|
645
|
-
`${prefix}${snake(collection.slug)}`,
|
|
646
|
-
"root",
|
|
647
|
-
collection.fields,
|
|
648
|
-
{
|
|
649
|
-
drafts: collection.drafts === true
|
|
650
|
-
}
|
|
651
|
-
);
|
|
652
|
-
collections.set(collection.slug, { config: collection, root });
|
|
653
|
-
collectTables(root, tables);
|
|
654
|
-
}
|
|
655
|
-
const globalsName = claimName(builder, `${prefix}globals`);
|
|
656
|
-
const globals = dialect.table(
|
|
657
|
-
globalsName,
|
|
658
|
-
{
|
|
659
|
-
slug: dialect.text("slug").primaryKey(),
|
|
660
|
-
data: dialect.json("data").notNull(),
|
|
661
|
-
status: dialect.text("status"),
|
|
662
|
-
updated_at: dialect.text("updated_at").notNull()
|
|
663
|
-
},
|
|
664
|
-
() => []
|
|
665
|
-
);
|
|
666
|
-
tables[globalsName] = globals;
|
|
667
|
-
builder.description.push(["globals", globalsName]);
|
|
668
|
-
const hash = createHash("sha256").update(JSON.stringify(builder.description)).digest("hex").slice(0, 16);
|
|
669
|
-
return { collections, globals, tables, hash };
|
|
670
|
-
}
|
|
671
|
-
function collectTables(model, into) {
|
|
672
|
-
into[model.name] = model.table;
|
|
673
|
-
for (const child of model.children) collectTables(child.table, into);
|
|
674
|
-
}
|
|
675
|
-
function claimName(builder, name) {
|
|
676
|
-
if (builder.tableNames.has(name)) {
|
|
677
|
-
throw new ConfigError([
|
|
678
|
-
{
|
|
679
|
-
path: name,
|
|
680
|
-
message: `two tables would be named "${name}"`,
|
|
681
|
-
hint: "rename one of the collections or fields"
|
|
682
|
-
}
|
|
683
|
-
]);
|
|
684
|
-
}
|
|
685
|
-
builder.tableNames.add(name);
|
|
686
|
-
return name;
|
|
687
|
-
}
|
|
688
|
-
function makeColumn(dialect, name, kind) {
|
|
689
|
-
switch (kind) {
|
|
690
|
-
case "number":
|
|
691
|
-
return dialect.number(name);
|
|
692
|
-
case "integer":
|
|
693
|
-
return dialect.integer(name);
|
|
694
|
-
case "boolean":
|
|
695
|
-
return dialect.boolean(name);
|
|
696
|
-
case "json":
|
|
697
|
-
return dialect.json(name);
|
|
698
|
-
case "text":
|
|
699
|
-
return dialect.text(name);
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
function buildTable(builder, name, kind, fields, options) {
|
|
703
|
-
const { dialect } = builder;
|
|
704
|
-
claimName(builder, name);
|
|
705
|
-
const columns = {};
|
|
706
|
-
const described = [];
|
|
707
|
-
const columnModels = [];
|
|
708
|
-
const children = [];
|
|
709
|
-
const indexes = [];
|
|
710
|
-
const add = (column, builderColumn, desc2) => {
|
|
711
|
-
if (Object.hasOwn(columns, column)) {
|
|
712
|
-
throw new ConfigError([
|
|
713
|
-
{
|
|
714
|
-
path: `${name}.${column}`,
|
|
715
|
-
message: `two fields would use the column "${column}"`,
|
|
716
|
-
hint: "rename one of them"
|
|
717
|
-
}
|
|
718
|
-
]);
|
|
719
|
-
}
|
|
720
|
-
columns[column] = builderColumn;
|
|
721
|
-
described.push([column, desc2]);
|
|
722
|
-
};
|
|
723
|
-
if (kind === "root") {
|
|
724
|
-
add("id", dialect.serial("id"), "id:int");
|
|
725
|
-
add("created_at", dialect.text("created_at").notNull(), "text!");
|
|
726
|
-
add("updated_at", dialect.text("updated_at").notNull(), "text!");
|
|
727
|
-
if (options.drafts) add("status", dialect.text("status").notNull().default("draft"), "status");
|
|
728
|
-
indexes.push({ column: "created_at", unique: false });
|
|
729
|
-
} else {
|
|
730
|
-
const parentKind = options.parentIdKind ?? "integer";
|
|
731
|
-
add(
|
|
732
|
-
"id",
|
|
733
|
-
kind === "array" ? dialect.text("id").primaryKey() : dialect.serial("id"),
|
|
734
|
-
`id:${kind}`
|
|
735
|
-
);
|
|
736
|
-
add(
|
|
737
|
-
"_parent_id",
|
|
738
|
-
parentKind === "text" ? dialect.text("_parent_id").notNull() : dialect.integer("_parent_id").notNull(),
|
|
739
|
-
parentKind
|
|
740
|
-
);
|
|
741
|
-
add("_order", dialect.integer("_order").notNull(), "int!");
|
|
742
|
-
indexes.push({ column: "_parent_id", unique: false });
|
|
743
|
-
if (options.localized) {
|
|
744
|
-
add(
|
|
745
|
-
"_locale",
|
|
746
|
-
dialect.text("_locale").notNull().default(options.localized),
|
|
747
|
-
`locale:${options.localized}`
|
|
748
|
-
);
|
|
749
|
-
indexes.push({ column: "_locale", unique: false });
|
|
750
|
-
}
|
|
751
|
-
if (kind === "values" && options.valueField) {
|
|
752
|
-
const valueKind = columnKind(options.valueField);
|
|
753
|
-
add("value", makeColumn(dialect, "value", valueKind), valueKind);
|
|
754
|
-
indexes.push({ column: "value", unique: false });
|
|
755
|
-
}
|
|
756
|
-
}
|
|
757
|
-
const walk = (list, path, topLevel) => {
|
|
758
|
-
for (const field of list) {
|
|
759
|
-
const fieldPath = [...path, field.name];
|
|
760
|
-
const base = fieldPath.map(snake).join("_");
|
|
761
|
-
if (field.type === "group") {
|
|
762
|
-
walk(field.fields, fieldPath, false);
|
|
763
|
-
} else if (field.type === "array") {
|
|
764
|
-
const localization = field.localized ? builder.localization : null;
|
|
765
|
-
const child = buildTable(builder, `${name}__${base}`, "array", field.fields, {
|
|
766
|
-
parentIdKind: kind === "array" ? "text" : "integer",
|
|
767
|
-
...localization ? { localized: localization.defaultLocale } : {}
|
|
768
|
-
});
|
|
769
|
-
children.push({
|
|
770
|
-
kind: "array",
|
|
771
|
-
path: fieldPath,
|
|
772
|
-
field,
|
|
773
|
-
table: child,
|
|
774
|
-
...localeInfo(localization)
|
|
775
|
-
});
|
|
776
|
-
described.push([base, "array", child.name]);
|
|
777
|
-
} else if ((field.type === "select" || field.type === "relationship") && field.hasMany) {
|
|
778
|
-
const localization = field.localized ? builder.localization : null;
|
|
779
|
-
const child = buildTable(builder, `${name}__${base}`, "values", [], {
|
|
780
|
-
parentIdKind: kind === "array" ? "text" : "integer",
|
|
781
|
-
valueField: field,
|
|
782
|
-
...localization ? { localized: localization.defaultLocale } : {}
|
|
783
|
-
});
|
|
784
|
-
children.push({
|
|
785
|
-
kind: "values",
|
|
786
|
-
path: fieldPath,
|
|
787
|
-
field,
|
|
788
|
-
table: child,
|
|
789
|
-
...localeInfo(localization)
|
|
790
|
-
});
|
|
791
|
-
described.push([base, "values", child.name]);
|
|
792
|
-
} else {
|
|
793
|
-
const kindOfColumn = columnKind(field);
|
|
794
|
-
const localization = field.localized ? builder.localization : null;
|
|
795
|
-
const targets = localization ? localization.locales.map((locale) => ({
|
|
796
|
-
column: locale === localization.defaultLocale ? base : `${base}__${snake(locale)}`,
|
|
797
|
-
locale
|
|
798
|
-
})) : [{ column: base, locale: void 0 }];
|
|
799
|
-
for (const { column, locale } of targets) {
|
|
800
|
-
add(column, makeColumn(dialect, column, kindOfColumn), kindOfColumn);
|
|
801
|
-
columnModels.push(
|
|
802
|
-
locale === void 0 ? { path: fieldPath, field, column } : {
|
|
803
|
-
path: fieldPath,
|
|
804
|
-
field,
|
|
805
|
-
column,
|
|
806
|
-
locale,
|
|
807
|
-
defaultLocale: locale === localization?.defaultLocale
|
|
808
|
-
}
|
|
809
|
-
);
|
|
810
|
-
if (topLevel && kind === "root" && (field.unique || field.type === "slug")) {
|
|
811
|
-
indexes.push({ column, unique: true });
|
|
812
|
-
} else if (field.index || field.unique || field.type === "relationship" || field.type === "upload") {
|
|
813
|
-
indexes.push({ column, unique: false });
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
};
|
|
819
|
-
walk(fields, [], true);
|
|
820
|
-
const table = dialect.table(
|
|
821
|
-
name,
|
|
822
|
-
columns,
|
|
823
|
-
(t) => indexes.map(({ column, unique }) => {
|
|
824
|
-
const col3 = t[column];
|
|
825
|
-
return unique ? dialect.uniqueIndex(`${name}_${column}_unique`).on(col3) : dialect.index(`${name}_${column}_idx`).on(col3);
|
|
826
|
-
})
|
|
827
|
-
);
|
|
828
|
-
builder.description.push([name, kind, described, indexes]);
|
|
829
|
-
const model = { name, kind, table, fields, columns: columnModels, children };
|
|
830
|
-
return options.valueField ? { ...model, valueField: options.valueField } : model;
|
|
831
|
-
}
|
|
832
|
-
function localeInfo(localization) {
|
|
833
|
-
return localization ? { locales: localization.locales, defaultLocale: localization.defaultLocale } : {};
|
|
834
|
-
}
|
|
835
|
-
|
|
836
992
|
// src/database.ts
|
|
837
993
|
var DEFAULT_TABLE_PREFIX = "ecms_";
|
|
838
994
|
var DEFAULT_MIGRATION_DIR = "easy-cms/migrations";
|
|
@@ -860,14 +1016,19 @@ async function connectDatabase(dialect, connection, args, options = {}) {
|
|
|
860
1016
|
}
|
|
861
1017
|
var DrizzleDatabase = class {
|
|
862
1018
|
constructor(dialect, connection, schema, migrator) {
|
|
1019
|
+
this.dialect = dialect;
|
|
863
1020
|
this.connection = connection;
|
|
864
1021
|
this.schema = schema;
|
|
865
1022
|
this.migrator = migrator;
|
|
866
1023
|
this.db = connection.db;
|
|
867
1024
|
for (const [slug, model] of schema.collections) {
|
|
868
|
-
this.builders.set(
|
|
1025
|
+
this.builders.set(
|
|
1026
|
+
slug,
|
|
1027
|
+
new WhereBuilder(this.db, dialect, model.config.drafts === true, schema.localization)
|
|
1028
|
+
);
|
|
869
1029
|
}
|
|
870
1030
|
}
|
|
1031
|
+
dialect;
|
|
871
1032
|
connection;
|
|
872
1033
|
schema;
|
|
873
1034
|
migrator;
|
|
@@ -922,23 +1083,38 @@ var DrizzleDatabase = class {
|
|
|
922
1083
|
const [row] = await this.db.select({ total: count() }).from(model.root.table).where(this.where(args.collection, args.where));
|
|
923
1084
|
return Number(row?.total ?? 0);
|
|
924
1085
|
}
|
|
1086
|
+
writing = Promise.resolve();
|
|
1087
|
+
/** Runs a write; with a single-writer database, after the writes before it are done. */
|
|
1088
|
+
write(run) {
|
|
1089
|
+
if (!this.dialect.singleWriter) return run();
|
|
1090
|
+
const result = this.writing.then(run);
|
|
1091
|
+
this.writing = result.catch(() => {
|
|
1092
|
+
});
|
|
1093
|
+
return result;
|
|
1094
|
+
}
|
|
925
1095
|
async create(args) {
|
|
926
1096
|
const model = this.model(args.collection);
|
|
927
|
-
const id = await this.
|
|
928
|
-
(
|
|
1097
|
+
const id = await this.write(
|
|
1098
|
+
() => this.db.transaction(
|
|
1099
|
+
(tx) => insertDocument(tx, model.root, args.data, model.config.drafts === true)
|
|
1100
|
+
)
|
|
929
1101
|
);
|
|
930
1102
|
return await this.findById({ collection: args.collection, id });
|
|
931
1103
|
}
|
|
932
1104
|
async update(args) {
|
|
933
1105
|
const model = this.model(args.collection);
|
|
934
|
-
await this.
|
|
935
|
-
(
|
|
1106
|
+
await this.write(
|
|
1107
|
+
() => this.db.transaction(
|
|
1108
|
+
(tx) => replaceDocument(tx, model.root, args.id, args.data, model.config.drafts === true)
|
|
1109
|
+
)
|
|
936
1110
|
);
|
|
937
1111
|
return await this.findById({ collection: args.collection, id: args.id });
|
|
938
1112
|
}
|
|
939
1113
|
async delete(args) {
|
|
940
1114
|
const model = this.model(args.collection);
|
|
941
|
-
await this.
|
|
1115
|
+
await this.write(
|
|
1116
|
+
() => this.db.transaction((tx) => deleteDocument(tx, model.root, args.id))
|
|
1117
|
+
);
|
|
942
1118
|
}
|
|
943
1119
|
async findGlobal(args) {
|
|
944
1120
|
const table = this.schema.globals;
|
|
@@ -959,7 +1135,9 @@ var DrizzleDatabase = class {
|
|
|
959
1135
|
status: status ?? null,
|
|
960
1136
|
updated_at: updatedAt
|
|
961
1137
|
};
|
|
962
|
-
await this.
|
|
1138
|
+
await this.write(
|
|
1139
|
+
() => this.db.insert(table).values(values).onConflictDoUpdate({ target: table.slug, set: values })
|
|
1140
|
+
);
|
|
963
1141
|
return await this.findGlobal({ slug: args.slug });
|
|
964
1142
|
}
|
|
965
1143
|
createMigration(args) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@easy-cms/drizzle",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Shared Drizzle ORM layer for Easy CMS database adapters",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -23,10 +23,10 @@
|
|
|
23
23
|
"drizzle-orm": "0.45.3"
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|
|
26
|
-
"@easy-cms/core": "^0.
|
|
26
|
+
"@easy-cms/core": "^0.8.0"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"@easy-cms/core": "^0.
|
|
29
|
+
"@easy-cms/core": "^0.8.0"
|
|
30
30
|
},
|
|
31
31
|
"publishConfig": {
|
|
32
32
|
"access": "public"
|