@easy-cms/drizzle 0.4.0 → 0.6.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 +7 -0
- package/dist/index.js +120 -26
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -75,6 +75,10 @@ interface ColumnModel {
|
|
|
75
75
|
readonly path: readonly string[];
|
|
76
76
|
readonly field: Field;
|
|
77
77
|
readonly column: string;
|
|
78
|
+
/** For localized fields: the locale whose value this column holds. */
|
|
79
|
+
readonly locale?: string;
|
|
80
|
+
/** The default locale's column, used when a query names no locale. */
|
|
81
|
+
readonly defaultLocale?: boolean;
|
|
78
82
|
}
|
|
79
83
|
/** A child table holding array rows or hasMany values. */
|
|
80
84
|
interface ChildModel {
|
|
@@ -82,6 +86,9 @@ interface ChildModel {
|
|
|
82
86
|
readonly path: readonly string[];
|
|
83
87
|
readonly field: Field;
|
|
84
88
|
readonly table: TableModel;
|
|
89
|
+
/** Localized: rows carry `_locale`, and the data holds `{ [locale]: rows }`. */
|
|
90
|
+
readonly locales?: readonly string[];
|
|
91
|
+
readonly defaultLocale?: string;
|
|
85
92
|
}
|
|
86
93
|
interface TableModel {
|
|
87
94
|
readonly name: string;
|
package/dist/index.js
CHANGED
|
@@ -22,15 +22,21 @@ function setAt(data, path, value) {
|
|
|
22
22
|
}
|
|
23
23
|
current[path[path.length - 1]] = value;
|
|
24
24
|
}
|
|
25
|
+
var columnPath = (column) => column.locale === void 0 ? column.path : [...column.path, column.locale];
|
|
25
26
|
function rowToData(model, row) {
|
|
26
27
|
const data = {};
|
|
27
|
-
for (const column of model.columns) setAt(data, column
|
|
28
|
-
for (const child of model.children)
|
|
28
|
+
for (const column of model.columns) setAt(data, columnPath(column), row[column.column] ?? null);
|
|
29
|
+
for (const child of model.children)
|
|
30
|
+
setAt(
|
|
31
|
+
data,
|
|
32
|
+
child.path,
|
|
33
|
+
child.locales ? Object.fromEntries(child.locales.map((locale) => [locale, []])) : []
|
|
34
|
+
);
|
|
29
35
|
return data;
|
|
30
36
|
}
|
|
31
37
|
function dataToRow(model, data) {
|
|
32
38
|
const row = {};
|
|
33
|
-
for (const column of model.columns) row[column.column] = getAt(data, column
|
|
39
|
+
for (const column of model.columns) row[column.column] = getAt(data, columnPath(column)) ?? null;
|
|
34
40
|
return row;
|
|
35
41
|
}
|
|
36
42
|
var CHUNK = 500;
|
|
@@ -54,8 +60,12 @@ async function attachChildren(db, model, rows, docs) {
|
|
|
54
60
|
childRows.forEach((childRow, i) => {
|
|
55
61
|
const index = indexById.get(childRow._parent_id);
|
|
56
62
|
if (index === void 0) return;
|
|
57
|
-
const
|
|
58
|
-
|
|
63
|
+
const locale = child.locales ? String(childRow._locale) : void 0;
|
|
64
|
+
const path = locale === void 0 ? child.path : [...child.path, locale];
|
|
65
|
+
const list = getAt(docs[index], path);
|
|
66
|
+
if (!Array.isArray(list)) return;
|
|
67
|
+
const id = locale === void 0 ? childRow.id : String(childRow.id).replace(new RegExp(`:${locale}$`), "");
|
|
68
|
+
list.push(child.kind === "array" ? { id, ...childDocs?.[i] } : childRow.value);
|
|
59
69
|
});
|
|
60
70
|
}
|
|
61
71
|
}
|
|
@@ -76,20 +86,41 @@ function toDocument(row, data, drafts) {
|
|
|
76
86
|
async function insertChildren(db, model, parentId, data) {
|
|
77
87
|
for (const child of model.children) {
|
|
78
88
|
const value = getAt(data, child.path);
|
|
89
|
+
if (child.locales) {
|
|
90
|
+
if (!value || typeof value !== "object") continue;
|
|
91
|
+
for (const locale of child.locales) {
|
|
92
|
+
const items = value[locale];
|
|
93
|
+
if (Array.isArray(items) && items.length > 0)
|
|
94
|
+
await insertChild(db, child, parentId, items, locale);
|
|
95
|
+
}
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
79
98
|
if (!Array.isArray(value) || value.length === 0) continue;
|
|
80
99
|
await insertChild(db, child, parentId, value);
|
|
81
100
|
}
|
|
82
101
|
}
|
|
83
|
-
async function insertChild(db, child, parentId, items) {
|
|
102
|
+
async function insertChild(db, child, parentId, items, locale) {
|
|
103
|
+
const localeColumn = locale === void 0 ? {} : { _locale: locale };
|
|
84
104
|
if (child.kind === "values") {
|
|
85
|
-
const rows = items.map((value, i) => ({
|
|
105
|
+
const rows = items.map((value, i) => ({
|
|
106
|
+
_parent_id: parentId,
|
|
107
|
+
_order: i,
|
|
108
|
+
value,
|
|
109
|
+
...localeColumn
|
|
110
|
+
}));
|
|
86
111
|
for (const part of chunks(rows)) await db.insert(child.table.table).values(part);
|
|
87
112
|
return;
|
|
88
113
|
}
|
|
89
114
|
for (const [i, item] of items.entries()) {
|
|
90
115
|
const rowData = item;
|
|
91
|
-
const id = rowData.id
|
|
92
|
-
await db.insert(child.table.table).values({
|
|
116
|
+
const id = locale === void 0 ? rowData.id : `${rowData.id}:${locale}`;
|
|
117
|
+
await db.insert(child.table.table).values({
|
|
118
|
+
...dataToRow(child.table, rowData),
|
|
119
|
+
id,
|
|
120
|
+
_parent_id: parentId,
|
|
121
|
+
_order: i,
|
|
122
|
+
...localeColumn
|
|
123
|
+
});
|
|
93
124
|
await insertChildren(db, child.table, id, rowData);
|
|
94
125
|
}
|
|
95
126
|
}
|
|
@@ -412,14 +443,31 @@ function resolvePath(model, segments, drafts) {
|
|
|
412
443
|
continue;
|
|
413
444
|
}
|
|
414
445
|
const child = model.children.find((c) => c.path.join(".") === path.join("."));
|
|
446
|
+
if (child?.locales) {
|
|
447
|
+
const next = segments[i + 1];
|
|
448
|
+
const named = next !== void 0 && child.locales.includes(next);
|
|
449
|
+
return {
|
|
450
|
+
kind: "child",
|
|
451
|
+
child,
|
|
452
|
+
rest: segments.slice(i + (named ? 2 : 1)),
|
|
453
|
+
locale: named ? next : child.defaultLocale
|
|
454
|
+
};
|
|
455
|
+
}
|
|
415
456
|
if (child) return { kind: "child", child, rest: segments.slice(i + 1) };
|
|
416
|
-
|
|
457
|
+
const locale = field.localized ? segments[i + 1] : void 0;
|
|
458
|
+
const last = field.localized && locale !== void 0 ? i + 1 : i;
|
|
459
|
+
if (last < segments.length - 1) {
|
|
417
460
|
throw new QueryError(`Cannot query inside "${shown}" (${field.type} field)`);
|
|
418
461
|
}
|
|
419
|
-
if (field.type === "json" || field.type === "richText") {
|
|
462
|
+
if (field.type === "json" || field.type === "richText" || field.type === "blocks") {
|
|
420
463
|
throw new QueryError(`Cannot query "${shown}": ${field.type} fields are not queryable`);
|
|
421
464
|
}
|
|
422
|
-
const
|
|
465
|
+
const same = (c) => c.path.join(".") === path.join(".");
|
|
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);
|
|
469
|
+
if (field.localized && !column)
|
|
470
|
+
throw new QueryError(`Unknown locale "${locale}" for "${shown}"`);
|
|
423
471
|
if (!column) throw new QueryError(`Unknown field "${shown}"`);
|
|
424
472
|
return { kind: "column", column: col2(model, column.column), field };
|
|
425
473
|
}
|
|
@@ -510,14 +558,15 @@ var WhereBuilder = class {
|
|
|
510
558
|
if (!OPERATORS.has(op)) throw new QueryError(`Unknown operator "${op}" on "${path}"`);
|
|
511
559
|
if (target.kind === "column")
|
|
512
560
|
return operatorSQL(this.dialect, target.column, target.field, op, value, path);
|
|
513
|
-
return this.child(model, target.child, target.rest, op, value, path);
|
|
561
|
+
return this.child(model, target.child, target.rest, op, value, path, target.locale);
|
|
514
562
|
});
|
|
515
563
|
return parts.length === 1 ? parts[0] : and(...parts);
|
|
516
564
|
}
|
|
517
565
|
/** Conditions on array rows or hasMany values become (NOT) EXISTS subqueries. */
|
|
518
|
-
child(parent, child, rest, op, value, path) {
|
|
566
|
+
child(parent, child, rest, op, value, path, locale) {
|
|
519
567
|
const table = child.table;
|
|
520
|
-
const
|
|
568
|
+
const parentLink = eq2(col2(table, "_parent_id"), col2(parent, "id"));
|
|
569
|
+
const link = locale === void 0 ? parentLink : and(parentLink, eq2(col2(table, "_locale"), locale));
|
|
521
570
|
const subquery = (condition) => exists(
|
|
522
571
|
this.db.select({ one: sql`1` }).from(table.table).where(condition ? and(link, condition) : link)
|
|
523
572
|
);
|
|
@@ -569,6 +618,8 @@ function columnKind(field) {
|
|
|
569
618
|
return "boolean";
|
|
570
619
|
case "json":
|
|
571
620
|
case "richText":
|
|
621
|
+
// Blocks are stored whole as JSON: rows of different kinds don't fit one child table.
|
|
622
|
+
case "blocks":
|
|
572
623
|
return "json";
|
|
573
624
|
case "upload":
|
|
574
625
|
case "relationship":
|
|
@@ -583,7 +634,8 @@ function buildSchema(config, prefix, dialect) {
|
|
|
583
634
|
prefix,
|
|
584
635
|
tableNames: /* @__PURE__ */ new Set([migrationsTableName(prefix)]),
|
|
585
636
|
// The hash covers the logical schema only, so it stays stable across releases for the same config.
|
|
586
|
-
description: []
|
|
637
|
+
description: [],
|
|
638
|
+
localization: config.localization
|
|
587
639
|
};
|
|
588
640
|
const collections = /* @__PURE__ */ new Map();
|
|
589
641
|
const tables = {};
|
|
@@ -688,6 +740,14 @@ function buildTable(builder, name, kind, fields, options) {
|
|
|
688
740
|
);
|
|
689
741
|
add("_order", dialect.integer("_order").notNull(), "int!");
|
|
690
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
|
+
}
|
|
691
751
|
if (kind === "values" && options.valueField) {
|
|
692
752
|
const valueKind = columnKind(options.valueField);
|
|
693
753
|
add("value", makeColumn(dialect, "value", valueKind), valueKind);
|
|
@@ -701,26 +761,57 @@ function buildTable(builder, name, kind, fields, options) {
|
|
|
701
761
|
if (field.type === "group") {
|
|
702
762
|
walk(field.fields, fieldPath, false);
|
|
703
763
|
} else if (field.type === "array") {
|
|
764
|
+
const localization = field.localized ? builder.localization : null;
|
|
704
765
|
const child = buildTable(builder, `${name}__${base}`, "array", field.fields, {
|
|
705
|
-
parentIdKind: kind === "array" ? "text" : "integer"
|
|
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)
|
|
706
775
|
});
|
|
707
|
-
children.push({ kind: "array", path: fieldPath, field, table: child });
|
|
708
776
|
described.push([base, "array", child.name]);
|
|
709
777
|
} else if ((field.type === "select" || field.type === "relationship") && field.hasMany) {
|
|
778
|
+
const localization = field.localized ? builder.localization : null;
|
|
710
779
|
const child = buildTable(builder, `${name}__${base}`, "values", [], {
|
|
711
780
|
parentIdKind: kind === "array" ? "text" : "integer",
|
|
712
|
-
valueField: field
|
|
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)
|
|
713
790
|
});
|
|
714
|
-
children.push({ kind: "values", path: fieldPath, field, table: child });
|
|
715
791
|
described.push([base, "values", child.name]);
|
|
716
792
|
} else {
|
|
717
793
|
const kindOfColumn = columnKind(field);
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
}
|
|
723
|
-
|
|
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
|
+
}
|
|
724
815
|
}
|
|
725
816
|
}
|
|
726
817
|
}
|
|
@@ -738,6 +829,9 @@ function buildTable(builder, name, kind, fields, options) {
|
|
|
738
829
|
const model = { name, kind, table, fields, columns: columnModels, children };
|
|
739
830
|
return options.valueField ? { ...model, valueField: options.valueField } : model;
|
|
740
831
|
}
|
|
832
|
+
function localeInfo(localization) {
|
|
833
|
+
return localization ? { locales: localization.locales, defaultLocale: localization.defaultLocale } : {};
|
|
834
|
+
}
|
|
741
835
|
|
|
742
836
|
// src/database.ts
|
|
743
837
|
var DEFAULT_TABLE_PREFIX = "ecms_";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@easy-cms/drizzle",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.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.6.0"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"@easy-cms/core": "^0.
|
|
29
|
+
"@easy-cms/core": "^0.6.0"
|
|
30
30
|
},
|
|
31
31
|
"publishConfig": {
|
|
32
32
|
"access": "public"
|