@cogenta/schema 0.1.2 → 0.2.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/define-collection.d.ts.map +1 -1
- package/dist/define-collection.js +32 -0
- package/dist/define-collection.js.map +1 -1
- package/dist/define-taxonomy.d.ts +9 -0
- package/dist/define-taxonomy.d.ts.map +1 -0
- package/dist/define-taxonomy.js +144 -0
- package/dist/define-taxonomy.js.map +1 -0
- package/dist/fields.d.ts +20 -0
- package/dist/fields.d.ts.map +1 -1
- package/dist/fields.js +14 -0
- package/dist/fields.js.map +1 -1
- package/dist/generate-schema-json.d.ts +28 -4
- package/dist/generate-schema-json.d.ts.map +1 -1
- package/dist/generate-schema-json.js +25 -4
- package/dist/generate-schema-json.js.map +1 -1
- package/dist/generate-types.js +1 -0
- package/dist/generate-types.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/links/check.d.ts +76 -0
- package/dist/links/check.d.ts.map +1 -0
- package/dist/links/check.js +169 -0
- package/dist/links/check.js.map +1 -0
- package/dist/links/extract.d.ts +36 -0
- package/dist/links/extract.d.ts.map +1 -0
- package/dist/links/extract.js +69 -0
- package/dist/links/extract.js.map +1 -0
- package/dist/links/index.d.ts +10 -0
- package/dist/links/index.d.ts.map +1 -0
- package/dist/links/index.js +8 -0
- package/dist/links/index.js.map +1 -0
- package/dist/store/columns.d.ts.map +1 -1
- package/dist/store/columns.js +5 -2
- package/dist/store/columns.js.map +1 -1
- package/dist/store/index.d.ts +11 -2
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/index.js +6 -1
- package/dist/store/index.js.map +1 -1
- package/dist/store/lifecycle-events.d.ts +72 -0
- package/dist/store/lifecycle-events.d.ts.map +1 -0
- package/dist/store/lifecycle-events.js +73 -0
- package/dist/store/lifecycle-events.js.map +1 -0
- package/dist/store/naming.d.ts +11 -2
- package/dist/store/naming.d.ts.map +1 -1
- package/dist/store/naming.js +13 -1
- package/dist/store/naming.js.map +1 -1
- package/dist/store/read-only.d.ts +9 -3
- package/dist/store/read-only.d.ts.map +1 -1
- package/dist/store/read-only.js +13 -3
- package/dist/store/read-only.js.map +1 -1
- package/dist/store/schema-2-migration.d.ts +32 -0
- package/dist/store/schema-2-migration.d.ts.map +1 -0
- package/dist/store/schema-2-migration.js +136 -0
- package/dist/store/schema-2-migration.js.map +1 -0
- package/dist/store/search-indexing.d.ts +34 -0
- package/dist/store/search-indexing.d.ts.map +1 -0
- package/dist/store/search-indexing.js +65 -0
- package/dist/store/search-indexing.js.map +1 -0
- package/dist/store/store.d.ts +36 -7
- package/dist/store/store.d.ts.map +1 -1
- package/dist/store/store.js +348 -58
- package/dist/store/store.js.map +1 -1
- package/dist/store/tables.d.ts +15 -3
- package/dist/store/tables.d.ts.map +1 -1
- package/dist/store/tables.js +98 -6
- package/dist/store/tables.js.map +1 -1
- package/dist/store/taxonomy-path.d.ts +25 -0
- package/dist/store/taxonomy-path.d.ts.map +1 -0
- package/dist/store/taxonomy-path.js +64 -0
- package/dist/store/taxonomy-path.js.map +1 -0
- package/dist/store/taxonomy-store.d.ts +62 -0
- package/dist/store/taxonomy-store.d.ts.map +1 -0
- package/dist/store/taxonomy-store.js +292 -0
- package/dist/store/taxonomy-store.js.map +1 -0
- package/dist/store/types.d.ts +42 -2
- package/dist/store/types.d.ts.map +1 -1
- package/dist/store/values.d.ts.map +1 -1
- package/dist/store/values.js +4 -1
- package/dist/store/values.js.map +1 -1
- package/dist/system-fields.d.ts +2 -1
- package/dist/system-fields.d.ts.map +1 -1
- package/dist/system-fields.js +4 -0
- package/dist/system-fields.js.map +1 -1
- package/dist/types.d.ts +86 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +12 -3
- package/dist/types.js.map +1 -1
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +5 -1
- package/dist/validation.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { buildPath } from '../routing/router.js';
|
|
2
|
+
function pathFor(collection, entry) {
|
|
3
|
+
if (collection.routing === undefined)
|
|
4
|
+
return null;
|
|
5
|
+
const params = {};
|
|
6
|
+
for (const [field, value] of Object.entries(entry.values)) {
|
|
7
|
+
if (typeof value === 'string')
|
|
8
|
+
params[field] = value;
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
return buildPath(collection, params, entry.locale);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
// A route whose parameters are not all present yet. `null` is the honest
|
|
15
|
+
// answer — better than refusing the publish that just succeeded.
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export function withLifecycleEvents(store, options) {
|
|
20
|
+
async function fire(event, entry) {
|
|
21
|
+
try {
|
|
22
|
+
await options.emit({
|
|
23
|
+
event,
|
|
24
|
+
collection: options.collection.name,
|
|
25
|
+
id: entry.id,
|
|
26
|
+
locale: entry.locale,
|
|
27
|
+
status: entry.status,
|
|
28
|
+
publishedAt: entry.publishedAt,
|
|
29
|
+
updatedAt: entry.updatedAt,
|
|
30
|
+
version: entry.version,
|
|
31
|
+
path: pathFor(options.collection, entry),
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
options.onError?.(error);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
...store,
|
|
40
|
+
// `create({ status: 'published' })` is a real publication — it is how an
|
|
41
|
+
// import, an agent, or `POST /api/content/{collection}` with a published
|
|
42
|
+
// status puts a page online without ever calling `publish()`. Missing it
|
|
43
|
+
// would make the event depend on which door the content came through.
|
|
44
|
+
create: async (input) => {
|
|
45
|
+
const created = await store.create(input);
|
|
46
|
+
if (created.status === 'published')
|
|
47
|
+
await fire('content.publish', created);
|
|
48
|
+
return created;
|
|
49
|
+
},
|
|
50
|
+
publish: async (id, input) => {
|
|
51
|
+
const published = await store.publish(id, input);
|
|
52
|
+
await fire('content.publish', published);
|
|
53
|
+
return published;
|
|
54
|
+
},
|
|
55
|
+
unpublish: async (id, input) => {
|
|
56
|
+
const unpublished = await store.unpublish(id, input);
|
|
57
|
+
await fire('content.unpublish', unpublished);
|
|
58
|
+
return unpublished;
|
|
59
|
+
},
|
|
60
|
+
delete: async (id) => {
|
|
61
|
+
// Read before the delete: afterwards there is nothing left to describe,
|
|
62
|
+
// and a receiver told only "some id disappeared" cannot find the page to
|
|
63
|
+
// drop. The published face is asked for first — deleting an entry that
|
|
64
|
+
// was never public is not news to a public consumer.
|
|
65
|
+
const before = await store.read(id, { state: 'published' }).catch(() => null);
|
|
66
|
+
const removed = await store.delete(id);
|
|
67
|
+
if (removed && before !== null)
|
|
68
|
+
await fire('content.delete', before);
|
|
69
|
+
return removed;
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=lifecycle-events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lifecycle-events.js","sourceRoot":"","sources":["../../src/store/lifecycle-events.ts"],"names":[],"mappings":"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"}
|
package/dist/store/naming.d.ts
CHANGED
|
@@ -10,8 +10,17 @@ export declare function entriesTable(collection: string): string;
|
|
|
10
10
|
*/
|
|
11
11
|
export declare function blocksTable(collection: string): string;
|
|
12
12
|
export declare function versionsTable(collection: string): string;
|
|
13
|
-
/** The join table of a `many: true` relation. */
|
|
13
|
+
/** The join table of a `many: true` relation, or of a `taxonomy` field. */
|
|
14
14
|
export declare function relationTable(collection: string, field: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* The terms of a taxonomy (`schema@2.0`, ADR-0022).
|
|
17
|
+
*
|
|
18
|
+
* A prefix of its own rather than `entriesTable`: a taxonomy and a collection
|
|
19
|
+
* may legitimately share a name — a site can have a `category` collection and
|
|
20
|
+
* a `category` taxonomy — and two different things must never land on one
|
|
21
|
+
* table.
|
|
22
|
+
*/
|
|
23
|
+
export declare function taxonomyTable(taxonomy: string): string;
|
|
15
24
|
export declare function indexName(table: string, suffix: string): string;
|
|
16
25
|
export declare function columnFor(field: string): string;
|
|
17
26
|
/**
|
|
@@ -19,7 +28,7 @@ export declare function columnFor(field: string): string;
|
|
|
19
28
|
* rather than silently shadowed — `updated_at` written by an editor would break
|
|
20
29
|
* every ordering and every cache decision in the product.
|
|
21
30
|
*/
|
|
22
|
-
export declare const SYSTEM_COLUMNS: readonly ['id', 'created_at', 'updated_at', 'created_by', 'updated_by', 'status', 'locale', 'translation_of', 'version', 'provenance', 'provenance_detail'];
|
|
31
|
+
export declare const SYSTEM_COLUMNS: readonly ['id', 'created_at', 'updated_at', 'created_by', 'updated_by', 'status', 'deleted_at', 'locale', 'translation_of', 'version', 'provenance', 'provenance_detail'];
|
|
23
32
|
export type SystemColumn = (typeof SYSTEM_COLUMNS)[number];
|
|
24
33
|
export declare function isSystemColumn(column: string): column is SystemColumn;
|
|
25
34
|
//# sourceMappingURL=naming.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"naming.d.ts","sourceRoot":"","sources":["../../src/store/naming.ts"],"names":[],"mappings":"AAiCA,wFAAwF;AACxF,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAExD;AAED,
|
|
1
|
+
{"version":3,"file":"naming.d.ts","sourceRoot":"","sources":["../../src/store/naming.ts"],"names":[],"mappings":"AAiCA,wFAAwF;AACxF,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAExD;AAED,2EAA2E;AAC3E,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAEvE;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAE/D;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED;;;;GAIG;AACH,eAAO,MAAM,cAAc,YACzB,IAAI,EACJ,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,QAAQ,EACR,YAAY,EACZ,QAAQ,EACR,gBAAgB,EAChB,SAAS,EACT,YAAY,EACZ,mBAAmB,CACX,CAAA;AAEV,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAA;AAE1D,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,IAAI,YAAY,CAErE"}
|
package/dist/store/naming.js
CHANGED
|
@@ -46,10 +46,21 @@ export function blocksTable(collection) {
|
|
|
46
46
|
export function versionsTable(collection) {
|
|
47
47
|
return checked(`${entriesTable(collection)}_versions`);
|
|
48
48
|
}
|
|
49
|
-
/** The join table of a `many: true` relation. */
|
|
49
|
+
/** The join table of a `many: true` relation, or of a `taxonomy` field. */
|
|
50
50
|
export function relationTable(collection, field) {
|
|
51
51
|
return checked(`${entriesTable(collection)}_${toSnakeCase(field)}`);
|
|
52
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* The terms of a taxonomy (`schema@2.0`, ADR-0022).
|
|
55
|
+
*
|
|
56
|
+
* A prefix of its own rather than `entriesTable`: a taxonomy and a collection
|
|
57
|
+
* may legitimately share a name — a site can have a `category` collection and
|
|
58
|
+
* a `category` taxonomy — and two different things must never land on one
|
|
59
|
+
* table.
|
|
60
|
+
*/
|
|
61
|
+
export function taxonomyTable(taxonomy) {
|
|
62
|
+
return checked(`${PREFIX}tax_${toSnakeCase(taxonomy)}`);
|
|
63
|
+
}
|
|
53
64
|
export function indexName(table, suffix) {
|
|
54
65
|
return checked(`${table}_${suffix}`);
|
|
55
66
|
}
|
|
@@ -68,6 +79,7 @@ export const SYSTEM_COLUMNS = [
|
|
|
68
79
|
'created_by',
|
|
69
80
|
'updated_by',
|
|
70
81
|
'status',
|
|
82
|
+
'deleted_at',
|
|
71
83
|
'locale',
|
|
72
84
|
'translation_of',
|
|
73
85
|
'version',
|
package/dist/store/naming.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"naming.js","sourceRoot":"","sources":["../../src/store/naming.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAE5C;;;;;;;GAOG;AAEH,MAAM,MAAM,GAAG,UAAU,CAAA;AAEzB;;;;;GAKG;AACH,MAAM,qBAAqB,GAAG,EAAE,CAAA;AAEhC,SAAS,OAAO,CAAC,IAAY;IAC3B,IAAI,IAAI,CAAC,MAAM,GAAG,qBAAqB,EAAE,CAAC;QACxC,MAAM,IAAI,YAAY,CAAC;YACrB,IAAI,EAAE,gBAAgB;YACtB,OAAO,EAAE,iCAAiC,IAAI,QAAQ,IAAI,CAAC,MAAM,mBAAmB;YACpF,IAAI,EAAE,wBAAwB,qBAAqB,yCAAyC;YAC5F,OAAO,EAAE,EAAE,IAAI,EAAE;SAClB,CAAC,CAAA;IACJ,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED,wFAAwF;AACxF,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,OAAO,IAAI,CAAC,OAAO,CAAC,oBAAoB,EAAE,OAAO,CAAC,CAAC,WAAW,EAAE,CAAA;AAClE,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,UAAkB;IAC7C,OAAO,OAAO,CAAC,GAAG,MAAM,GAAG,WAAW,CAAC,UAAU,CAAC,EAAE,CAAC,CAAA;AACvD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,UAAkB;IAC5C,OAAO,OAAO,CAAC,GAAG,YAAY,CAAC,UAAU,CAAC,SAAS,CAAC,CAAA;AACtD,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,UAAkB;IAC9C,OAAO,OAAO,CAAC,GAAG,YAAY,CAAC,UAAU,CAAC,WAAW,CAAC,CAAA;AACxD,CAAC;AAED,
|
|
1
|
+
{"version":3,"file":"naming.js","sourceRoot":"","sources":["../../src/store/naming.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAE5C;;;;;;;GAOG;AAEH,MAAM,MAAM,GAAG,UAAU,CAAA;AAEzB;;;;;GAKG;AACH,MAAM,qBAAqB,GAAG,EAAE,CAAA;AAEhC,SAAS,OAAO,CAAC,IAAY;IAC3B,IAAI,IAAI,CAAC,MAAM,GAAG,qBAAqB,EAAE,CAAC;QACxC,MAAM,IAAI,YAAY,CAAC;YACrB,IAAI,EAAE,gBAAgB;YACtB,OAAO,EAAE,iCAAiC,IAAI,QAAQ,IAAI,CAAC,MAAM,mBAAmB;YACpF,IAAI,EAAE,wBAAwB,qBAAqB,yCAAyC;YAC5F,OAAO,EAAE,EAAE,IAAI,EAAE;SAClB,CAAC,CAAA;IACJ,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED,wFAAwF;AACxF,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,OAAO,IAAI,CAAC,OAAO,CAAC,oBAAoB,EAAE,OAAO,CAAC,CAAC,WAAW,EAAE,CAAA;AAClE,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,UAAkB;IAC7C,OAAO,OAAO,CAAC,GAAG,MAAM,GAAG,WAAW,CAAC,UAAU,CAAC,EAAE,CAAC,CAAA;AACvD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,UAAkB;IAC5C,OAAO,OAAO,CAAC,GAAG,YAAY,CAAC,UAAU,CAAC,SAAS,CAAC,CAAA;AACtD,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,UAAkB;IAC9C,OAAO,OAAO,CAAC,GAAG,YAAY,CAAC,UAAU,CAAC,WAAW,CAAC,CAAA;AACxD,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,aAAa,CAAC,UAAkB,EAAE,KAAa;IAC7D,OAAO,OAAO,CAAC,GAAG,YAAY,CAAC,UAAU,CAAC,IAAI,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;AACrE,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,OAAO,OAAO,CAAC,GAAG,MAAM,OAAO,WAAW,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;AACzD,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,KAAa,EAAE,MAAc;IACrD,OAAO,OAAO,CAAC,GAAG,KAAK,IAAI,MAAM,EAAE,CAAC,CAAA;AACtC,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,KAAa;IACrC,OAAO,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAA;AACpC,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,IAAI;IACJ,YAAY;IACZ,YAAY;IACZ,YAAY;IACZ,YAAY;IACZ,QAAQ;IACR,YAAY;IACZ,QAAQ;IACR,gBAAgB;IAChB,SAAS;IACT,YAAY;IACZ,mBAAmB;CACX,CAAA;AAIV,MAAM,UAAU,cAAc,CAAC,MAAc;IAC3C,OAAQ,cAAoC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;AAC/D,CAAC"}
|
|
@@ -3,9 +3,15 @@ import type { ContentValues } from './types.js';
|
|
|
3
3
|
/**
|
|
4
4
|
* "Commencer par une démo en lecture seule" (L9 tâche 12, playground). Wraps
|
|
5
5
|
* any `ContentStore` so every mutating method refuses instead of writing —
|
|
6
|
-
* `create`/`update`/`delete`/`
|
|
7
|
-
* read (`read`/`list`/`history
|
|
8
|
-
* `resolveLocale`) passes straight
|
|
6
|
+
* `create`/`duplicate`/`update`/`delete`/`untrash`/`purge`/`purgeExpired`/
|
|
7
|
+
* `publish`/`unpublish`/`restore` — while every read (`read`/`list`/`history`/
|
|
8
|
+
* `readVersion`/`diff`/`translations`/`resolveLocale`) passes straight
|
|
9
|
+
* through, unchanged.
|
|
10
|
+
*
|
|
11
|
+
* The three trash methods are refused for the reason `delete` always was, and
|
|
12
|
+
* `purge` most of all: a read-only demo that let a visitor empty the trash
|
|
13
|
+
* would destroy content irrecoverably, which is worse than the write it was
|
|
14
|
+
* meant to prevent (ADR-0022).
|
|
9
15
|
*
|
|
10
16
|
* A single wrap at the store level protects every consumer built on top of
|
|
11
17
|
* it — REST's `ContentService` and GraphQL's gateway both construct their
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"read-only.d.ts","sourceRoot":"","sources":["../../src/store/read-only.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAC9C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE/C
|
|
1
|
+
{"version":3,"file":"read-only.d.ts","sourceRoot":"","sources":["../../src/store/read-only.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAC9C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE/C;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,SAAS,aAAa,GAAG,aAAa,EAC7E,KAAK,EAAE,YAAY,CAAC,OAAO,CAAC,GAC3B,YAAY,CAAC,OAAO,CAAC,CA0BvB"}
|
package/dist/store/read-only.js
CHANGED
|
@@ -2,9 +2,15 @@ import { CogentaError } from '@cogenta/core';
|
|
|
2
2
|
/**
|
|
3
3
|
* "Commencer par une démo en lecture seule" (L9 tâche 12, playground). Wraps
|
|
4
4
|
* any `ContentStore` so every mutating method refuses instead of writing —
|
|
5
|
-
* `create`/`update`/`delete`/`
|
|
6
|
-
* read (`read`/`list`/`history
|
|
7
|
-
* `resolveLocale`) passes straight
|
|
5
|
+
* `create`/`duplicate`/`update`/`delete`/`untrash`/`purge`/`purgeExpired`/
|
|
6
|
+
* `publish`/`unpublish`/`restore` — while every read (`read`/`list`/`history`/
|
|
7
|
+
* `readVersion`/`diff`/`translations`/`resolveLocale`) passes straight
|
|
8
|
+
* through, unchanged.
|
|
9
|
+
*
|
|
10
|
+
* The three trash methods are refused for the reason `delete` always was, and
|
|
11
|
+
* `purge` most of all: a read-only demo that let a visitor empty the trash
|
|
12
|
+
* would destroy content irrecoverably, which is worse than the write it was
|
|
13
|
+
* meant to prevent (ADR-0022).
|
|
8
14
|
*
|
|
9
15
|
* A single wrap at the store level protects every consumer built on top of
|
|
10
16
|
* it — REST's `ContentService` and GraphQL's gateway both construct their
|
|
@@ -26,8 +32,12 @@ export function withReadOnlyStore(store) {
|
|
|
26
32
|
return {
|
|
27
33
|
...store,
|
|
28
34
|
create: () => refuse(),
|
|
35
|
+
duplicate: () => refuse(),
|
|
29
36
|
update: () => refuse(),
|
|
30
37
|
delete: () => refuse(),
|
|
38
|
+
untrash: () => refuse(),
|
|
39
|
+
purge: () => refuse(),
|
|
40
|
+
purgeExpired: () => refuse(),
|
|
31
41
|
publish: () => refuse(),
|
|
32
42
|
unpublish: () => refuse(),
|
|
33
43
|
restore: () => refuse(),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"read-only.js","sourceRoot":"","sources":["../../src/store/read-only.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAI5C
|
|
1
|
+
{"version":3,"file":"read-only.js","sourceRoot":"","sources":["../../src/store/read-only.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAI5C;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,iBAAiB,CAC/B,KAA4B;IAE5B,0EAA0E;IAC1E,wEAAwE;IACxE,wEAAwE;IACxE,8BAA8B;IAC9B,KAAK,UAAU,MAAM;QACnB,MAAM,IAAI,YAAY,CAAC;YACrB,IAAI,EAAE,mBAAmB;YACzB,OAAO,EAAE,iEAAiE;YAC1E,IAAI,EAAE,uHAAuH;SAC9H,CAAC,CAAA;IACJ,CAAC;IAED,OAAO;QACL,GAAG,KAAK;QACR,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE;QACtB,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE;QACzB,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE;QACtB,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE;QACtB,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE;QACvB,KAAK,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE;QACrB,YAAY,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE;QAC5B,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE;QACvB,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE;QACzB,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE;KACxB,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type Migration } from '@cogenta/core';
|
|
2
|
+
import type { CollectionDefinition, TaxonomyDefinition } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* The `schema@1.0 → 2.0` migration (ADR-0022).
|
|
5
|
+
*
|
|
6
|
+
* It does three things, and each is reversible:
|
|
7
|
+
*
|
|
8
|
+
* 1. adds `deleted_at` to every collection's entry table — the trash;
|
|
9
|
+
* 2. creates one terms table per declared taxonomy;
|
|
10
|
+
* 3. creates the join table of every `f.taxonomy({ many: true })` field.
|
|
11
|
+
*
|
|
12
|
+
* **The `down` is a real data loss and says so.** Dropping `deleted_at`
|
|
13
|
+
* discards the trash: every entry sitting in it becomes live again with no
|
|
14
|
+
* record that it was ever thrown away, and dropping the terms tables discards
|
|
15
|
+
* the whole classification. The migration is therefore marked `destructive`,
|
|
16
|
+
* which makes the migrator demand an explicit confirmation *and* a verified
|
|
17
|
+
* backup before it will run in either direction. Marking only the rollback is
|
|
18
|
+
* not something `Migration` can express, and erring towards asking is the
|
|
19
|
+
* right way round for a flag whose whole job is to make someone stop.
|
|
20
|
+
*
|
|
21
|
+
* The project has no site in production today, so this moves no real data —
|
|
22
|
+
* it is written and tested as if it did, because the day it does is not the
|
|
23
|
+
* day to start.
|
|
24
|
+
*/
|
|
25
|
+
export interface Schema2MigrationOptions {
|
|
26
|
+
readonly collections: readonly CollectionDefinition[];
|
|
27
|
+
readonly taxonomies?: readonly TaxonomyDefinition[];
|
|
28
|
+
/** Overridable so a site can slot this into its own numbering. */
|
|
29
|
+
readonly id?: string;
|
|
30
|
+
}
|
|
31
|
+
export declare function schema2Migration(options: Schema2MigrationOptions): Migration;
|
|
32
|
+
//# sourceMappingURL=schema-2-migration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-2-migration.d.ts","sourceRoot":"","sources":["../../src/store/schema-2-migration.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,SAAS,EAIf,MAAM,eAAe,CAAA;AACtB,OAAO,KAAK,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAM3E;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,WAAW,EAAE,SAAS,oBAAoB,EAAE,CAAA;IACrD,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,kBAAkB,EAAE,CAAA;IACnD,kEAAkE;IAClE,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CACrB;AA0GD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,SAAS,CA8D5E"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { identifier, sql, } from '@cogenta/core';
|
|
2
|
+
import { integerColumn, jsonColumn, textColumn, timestampColumn, uuidColumn } from './columns.js';
|
|
3
|
+
import { entriesTable, indexName, relationTable, taxonomyTable } from './naming.js';
|
|
4
|
+
import { relationsOf } from './tables.js';
|
|
5
|
+
import { TAXONOMY_PATH_LENGTH } from './taxonomy-path.js';
|
|
6
|
+
const DEFAULT_ID = '0002_schema_2_trash_and_taxonomies';
|
|
7
|
+
/**
|
|
8
|
+
* `add column` and `drop column` exist on all three dialects — but SQLite only
|
|
9
|
+
* learnt `drop column` in 3.35 (2021) and refuses it on a column an index
|
|
10
|
+
* touches, which is why the index is dropped first on the way down.
|
|
11
|
+
*/
|
|
12
|
+
function addDeletedAt(collection, dialect) {
|
|
13
|
+
const table = identifier(entriesTable(collection.name), dialect);
|
|
14
|
+
return sql `alter table ${table}
|
|
15
|
+
add column ${identifier('deleted_at', dialect)} ${timestampColumn(dialect)}`;
|
|
16
|
+
}
|
|
17
|
+
function dropDeletedAt(collection, dialect) {
|
|
18
|
+
const table = identifier(entriesTable(collection.name), dialect);
|
|
19
|
+
return sql `alter table ${table} drop column ${identifier('deleted_at', dialect)}`;
|
|
20
|
+
}
|
|
21
|
+
function trashIndex(collection, dialect) {
|
|
22
|
+
const name = entriesTable(collection.name);
|
|
23
|
+
return sql `create index ${identifier(indexName(name, 'trash'), dialect)}
|
|
24
|
+
on ${identifier(name, dialect)} (${identifier('deleted_at', dialect)})`;
|
|
25
|
+
}
|
|
26
|
+
function dropTrashIndex(collection, dialect) {
|
|
27
|
+
// MySQL has no `drop index if exists` before 8.0.x either, so the caller
|
|
28
|
+
// swallows the failure: the only realistic cause is that it is already gone.
|
|
29
|
+
return sql `drop index ${identifier(indexName(entriesTable(collection.name), 'trash'), dialect)}
|
|
30
|
+
on ${identifier(entriesTable(collection.name), dialect)}`;
|
|
31
|
+
}
|
|
32
|
+
function dropIndexStandalone(collection, dialect) {
|
|
33
|
+
return sql `drop index ${identifier(indexName(entriesTable(collection.name), 'trash'), dialect)}`;
|
|
34
|
+
}
|
|
35
|
+
function termsTable(taxonomy, dialect) {
|
|
36
|
+
const table = identifier(taxonomyTable(taxonomy.name), dialect);
|
|
37
|
+
return sql `create table if not exists ${table} (
|
|
38
|
+
${identifier('id', dialect)} ${uuidColumn(dialect)} not null primary key,
|
|
39
|
+
${identifier('parent_id', dialect)} ${uuidColumn(dialect)},
|
|
40
|
+
${identifier('slug', dialect)} ${textColumn(dialect, 255)} not null,
|
|
41
|
+
${identifier('labels', dialect)} ${jsonColumn()} not null,
|
|
42
|
+
${identifier('position', dialect)} ${integerColumn()} not null,
|
|
43
|
+
${identifier('path', dialect)} ${textColumn(dialect, TAXONOMY_PATH_LENGTH)} not null,
|
|
44
|
+
${identifier('created_at', dialect)} ${timestampColumn(dialect)} not null,
|
|
45
|
+
${identifier('updated_at', dialect)} ${timestampColumn(dialect)} not null,
|
|
46
|
+
constraint ${identifier(indexName(taxonomyTable(taxonomy.name), 'parent_fk'), dialect)}
|
|
47
|
+
foreign key (${identifier('parent_id', dialect)})
|
|
48
|
+
references ${table} (${identifier('id', dialect)}) on delete cascade
|
|
49
|
+
)`;
|
|
50
|
+
}
|
|
51
|
+
function termsIndexes(taxonomy, dialect) {
|
|
52
|
+
const name = taxonomyTable(taxonomy.name);
|
|
53
|
+
const table = identifier(name, dialect);
|
|
54
|
+
return [
|
|
55
|
+
sql `create unique index ${identifier(indexName(name, 'slug_unique'), dialect)}
|
|
56
|
+
on ${table} (${identifier('slug', dialect)})`,
|
|
57
|
+
sql `create index ${identifier(indexName(name, 'path'), dialect)}
|
|
58
|
+
on ${table} (${identifier('path', dialect)})`,
|
|
59
|
+
sql `create index ${identifier(indexName(name, 'parent'), dialect)}
|
|
60
|
+
on ${table} (${identifier('parent_id', dialect)}, ${identifier('position', dialect)})`,
|
|
61
|
+
];
|
|
62
|
+
}
|
|
63
|
+
function taxonomyJoinTable(collection, field, taxonomy, dialect) {
|
|
64
|
+
const name = relationTable(collection.name, field);
|
|
65
|
+
const table = identifier(name, dialect);
|
|
66
|
+
return sql `create table if not exists ${table} (
|
|
67
|
+
${identifier('entry_id', dialect)} ${uuidColumn(dialect)} not null,
|
|
68
|
+
${identifier('target_id', dialect)} ${uuidColumn(dialect)} not null,
|
|
69
|
+
${identifier('position', dialect)} ${integerColumn()} not null,
|
|
70
|
+
primary key (${identifier('entry_id', dialect)}, ${identifier('target_id', dialect)}),
|
|
71
|
+
constraint ${identifier(indexName(name, 'entry_fk'), dialect)}
|
|
72
|
+
foreign key (${identifier('entry_id', dialect)})
|
|
73
|
+
references ${identifier(entriesTable(collection.name), dialect)} (${identifier('id', dialect)})
|
|
74
|
+
on delete cascade,
|
|
75
|
+
constraint ${identifier(indexName(name, 'target_fk'), dialect)}
|
|
76
|
+
foreign key (${identifier('target_id', dialect)})
|
|
77
|
+
references ${identifier(taxonomyTable(taxonomy), dialect)} (${identifier('id', dialect)})
|
|
78
|
+
on delete cascade
|
|
79
|
+
)`;
|
|
80
|
+
}
|
|
81
|
+
/** The `many: true` taxonomy fields of a collection, with their taxonomy. */
|
|
82
|
+
function taxonomyJoins(collection) {
|
|
83
|
+
return relationsOf(collection)
|
|
84
|
+
.filter((relation) => relation.kind === 'taxonomy' && relation.many)
|
|
85
|
+
.map((relation) => ({ field: relation.field, taxonomy: relation.to }));
|
|
86
|
+
}
|
|
87
|
+
export function schema2Migration(options) {
|
|
88
|
+
const { collections } = options;
|
|
89
|
+
const taxonomies = options.taxonomies ?? [];
|
|
90
|
+
return {
|
|
91
|
+
id: options.id ?? DEFAULT_ID,
|
|
92
|
+
name: 'schema@2.0 — trash and taxonomies',
|
|
93
|
+
destructive: true,
|
|
94
|
+
impact: 'Adds a nullable deleted_at column to every collection and creates the taxonomy tables. ' +
|
|
95
|
+
'Moves no existing row. The rollback drops deleted_at and the taxonomy tables, which ' +
|
|
96
|
+
'permanently discards everything in the trash and every classification: entries sitting ' +
|
|
97
|
+
'in the trash silently become live again, with no record that they were ever deleted.',
|
|
98
|
+
estimatedDurationMs: 500 * Math.max(1, collections.length + taxonomies.length),
|
|
99
|
+
up: async (tx) => {
|
|
100
|
+
const dialect = tx.dialect;
|
|
101
|
+
// Terms first: a join table below carries a real foreign key into them.
|
|
102
|
+
for (const taxonomy of taxonomies) {
|
|
103
|
+
await tx.query(termsTable(taxonomy, dialect));
|
|
104
|
+
for (const statement of termsIndexes(taxonomy, dialect)) {
|
|
105
|
+
await tx.query(statement).catch(() => undefined);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
for (const collection of collections) {
|
|
109
|
+
await tx.query(addDeletedAt(collection, dialect));
|
|
110
|
+
// `create index` has no portable `if not exists`; a failure here means
|
|
111
|
+
// it is already there, which is the state we wanted anyway.
|
|
112
|
+
await tx.query(trashIndex(collection, dialect)).catch(() => undefined);
|
|
113
|
+
for (const join of taxonomyJoins(collection)) {
|
|
114
|
+
await tx.query(taxonomyJoinTable(collection, join.field, join.taxonomy, dialect));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
down: async (tx) => {
|
|
119
|
+
const dialect = tx.dialect;
|
|
120
|
+
for (const collection of [...collections].reverse()) {
|
|
121
|
+
for (const join of taxonomyJoins(collection)) {
|
|
122
|
+
await tx.query(sql `drop table if exists ${identifier(relationTable(collection.name, join.field), dialect)}`);
|
|
123
|
+
}
|
|
124
|
+
// SQLite refuses to drop a column an index still covers, and the two
|
|
125
|
+
// dialects spell "drop index" differently — MySQL needs the table.
|
|
126
|
+
const drop = dialect === 'mysql' ? dropTrashIndex : dropIndexStandalone;
|
|
127
|
+
await tx.query(drop(collection, dialect)).catch(() => undefined);
|
|
128
|
+
await tx.query(dropDeletedAt(collection, dialect));
|
|
129
|
+
}
|
|
130
|
+
for (const taxonomy of [...taxonomies].reverse()) {
|
|
131
|
+
await tx.query(sql `drop table if exists ${identifier(taxonomyTable(taxonomy.name), dialect)}`);
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=schema-2-migration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-2-migration.js","sourceRoot":"","sources":["../../src/store/schema-2-migration.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { SearchDriver } from '../search/types.js';
|
|
2
|
+
import type { CollectionDefinition } from '../types.js';
|
|
3
|
+
import type { ContentStore } from './store.js';
|
|
4
|
+
import type { ContentValues } from './types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Keeps the full-text index in step with the content, by wrapping the store.
|
|
7
|
+
*
|
|
8
|
+
* The search engine (`src/search/`) was complete and tested from L1 and had
|
|
9
|
+
* no writer: nothing anywhere in the repository ever called `index()`, so
|
|
10
|
+
* every search returned nothing however the query was written (L10 task 3).
|
|
11
|
+
*
|
|
12
|
+
* Wrapping the *store* rather than hooking each transport is the same choice
|
|
13
|
+
* `withReadOnlyStore` already makes, and for the same reason: REST's
|
|
14
|
+
* `ContentService` and GraphQL's `ContentGateway` are both handed the very
|
|
15
|
+
* same store instances by `serve.ts`, so one wrap covers both transports and
|
|
16
|
+
* neither can bypass it. A hook on a router would have left the other
|
|
17
|
+
* transport writing content the index never hears about.
|
|
18
|
+
*/
|
|
19
|
+
export interface SearchIndexingOptions {
|
|
20
|
+
readonly collection: CollectionDefinition;
|
|
21
|
+
readonly index: SearchDriver;
|
|
22
|
+
/**
|
|
23
|
+
* Called when indexing fails. Defaults to swallowing it.
|
|
24
|
+
*
|
|
25
|
+
* A failed index write must never fail the content write that triggered it:
|
|
26
|
+
* the index is derived data that can be rebuilt from the content at any
|
|
27
|
+
* time, and losing an editor's save because a search table was locked would
|
|
28
|
+
* be the wrong trade. Callers with a logger pass one so the failure is
|
|
29
|
+
* still visible rather than silent — `serve.ts` does.
|
|
30
|
+
*/
|
|
31
|
+
readonly onError?: (error: unknown) => void;
|
|
32
|
+
}
|
|
33
|
+
export declare function withSearchIndexing<TValues extends ContentValues = ContentValues>(store: ContentStore<TValues>, options: SearchIndexingOptions): ContentStore<TValues>;
|
|
34
|
+
//# sourceMappingURL=search-indexing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-indexing.d.ts","sourceRoot":"","sources":["../../src/store/search-indexing.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACtD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAC9C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE/C;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,UAAU,EAAE,oBAAoB,CAAA;IACzC,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAA;IAC5B;;;;;;;;OAQG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;CAC5C;AA0CD,wBAAgB,kBAAkB,CAAC,OAAO,SAAS,aAAa,GAAG,aAAa,EAC9E,KAAK,EAAE,YAAY,CAAC,OAAO,CAAC,EAC5B,OAAO,EAAE,qBAAqB,GAC7B,YAAY,CAAC,OAAO,CAAC,CA2BvB"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { searchDocumentFor } from '../search/extract.js';
|
|
2
|
+
/**
|
|
3
|
+
* Re-derives the indexed document for one entry.
|
|
4
|
+
*
|
|
5
|
+
* The published face is asked for **first**, and it is the one indexed
|
|
6
|
+
* whenever it exists. That ordering is the whole safety property: with drafts
|
|
7
|
+
* enabled, `update()` returns the working face of an entry whose `status` is
|
|
8
|
+
* still `published`, and indexing *that* would file unreviewed text under a
|
|
9
|
+
* status a public search is allowed to reach. Reading back through
|
|
10
|
+
* `state: 'published'` makes it structurally impossible — the store only
|
|
11
|
+
* returns a row there when its status really is `published`.
|
|
12
|
+
*
|
|
13
|
+
* When there is no published face, the working one is indexed under its real
|
|
14
|
+
* status (`draft`, `scheduled`, `archived`), which is what lets the admin
|
|
15
|
+
* find a draft while `SearchQuery`'s `published` default keeps it away from
|
|
16
|
+
* anyone who did not explicitly ask for it and hold the permission to.
|
|
17
|
+
*/
|
|
18
|
+
async function reindex(store, options, id) {
|
|
19
|
+
const { collection, index } = options;
|
|
20
|
+
try {
|
|
21
|
+
const published = await store.read(id, { state: 'published' });
|
|
22
|
+
if (published !== null) {
|
|
23
|
+
await index.index(searchDocumentFor(collection, published));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const working = await store.read(id, { state: 'working' });
|
|
27
|
+
if (working === null) {
|
|
28
|
+
await index.remove({ id, collection: collection.name });
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
await index.index(searchDocumentFor(collection, working));
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
options.onError?.(error);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export function withSearchIndexing(store, options) {
|
|
38
|
+
async function after(entry) {
|
|
39
|
+
await reindex(store, options, entry.id);
|
|
40
|
+
return entry;
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
...store,
|
|
44
|
+
create: async (input) => after(await store.create(input)),
|
|
45
|
+
update: async (id, input) => after(await store.update(id, input)),
|
|
46
|
+
publish: async (id, input) => after(await store.publish(id, input)),
|
|
47
|
+
unpublish: async (id, input) => after(await store.unpublish(id, input)),
|
|
48
|
+
restore: async (id, version, input) => after(await store.restore(id, version, input)),
|
|
49
|
+
delete: async (id) => {
|
|
50
|
+
const removed = await store.delete(id);
|
|
51
|
+
// Unconditionally, not only on `removed`: an entry the store says was
|
|
52
|
+
// already gone may still have a stale row in the index — that is exactly
|
|
53
|
+
// the state a crash between the two writes leaves behind, and the
|
|
54
|
+
// cheapest place to repair it is the next delete.
|
|
55
|
+
try {
|
|
56
|
+
await options.index.remove({ id, collection: options.collection.name });
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
options.onError?.(error);
|
|
60
|
+
}
|
|
61
|
+
return removed;
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=search-indexing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-indexing.js","sourceRoot":"","sources":["../../src/store/search-indexing.ts"],"names":[],"mappings":"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"}
|
package/dist/store/store.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type DatabaseHandle } from '@cogenta/core';
|
|
2
|
-
import type
|
|
2
|
+
import { type CollectionDefinition } from '../types.js';
|
|
3
3
|
import { type ContentDiff } from './diff.js';
|
|
4
|
-
import type { ContentEntry, ContentValues, CreateInput,
|
|
4
|
+
import type { ContentEntry, ContentValues, CreateInput, DuplicateInput, ListOptions, LocaleResolution, Page, PurgeReport, ReadOptions, ResolveLocaleOptions, TrashOptions, UpdateInput, VersionSummary } from './types.js';
|
|
5
5
|
/**
|
|
6
6
|
* The persistence layer of a collection.
|
|
7
7
|
*
|
|
@@ -21,6 +21,18 @@ import type { ContentEntry, ContentValues, CreateInput, EntryState, ListOptions,
|
|
|
21
21
|
export interface ContentStoreOptions {
|
|
22
22
|
readonly db: DatabaseHandle;
|
|
23
23
|
readonly collection: CollectionDefinition;
|
|
24
|
+
/**
|
|
25
|
+
* The other collections of the site, so `delete()` can enforce `restrict`.
|
|
26
|
+
*
|
|
27
|
+
* Trashing is not a `DELETE`, so the foreign key cannot refuse it any more
|
|
28
|
+
* (ADR-0022) — the check has to be made in application code, and this is
|
|
29
|
+
* what makes it possible: a store only knows its own collection otherwise.
|
|
30
|
+
*
|
|
31
|
+
* Left out, only self-references are checked. That degrades honestly rather
|
|
32
|
+
* than silently: nothing is destroyed, since `purge()` still meets the real
|
|
33
|
+
* foreign key. Every real runtime (`serve.ts`) passes the whole set.
|
|
34
|
+
*/
|
|
35
|
+
readonly siblings?: readonly CollectionDefinition[];
|
|
24
36
|
/** The locale an entry gets when the caller does not say. */
|
|
25
37
|
readonly defaultLocale?: string;
|
|
26
38
|
/** Injectable so tests can pin time; nothing else should pass it. */
|
|
@@ -29,11 +41,28 @@ export interface ContentStoreOptions {
|
|
|
29
41
|
}
|
|
30
42
|
export interface ContentStore<TValues extends ContentValues = ContentValues> {
|
|
31
43
|
create(input: CreateInput<TValues>): Promise<ContentEntry<TValues>>;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
44
|
+
/** Copies an entry into a new, independent draft. See the implementation for what is deliberately not copied. */
|
|
45
|
+
duplicate(id: string, input?: DuplicateInput<TValues>): Promise<ContentEntry<TValues>>;
|
|
46
|
+
read(id: string, options?: ReadOptions): Promise<ContentEntry<TValues> | null>;
|
|
35
47
|
update(id: string, input: UpdateInput<TValues>): Promise<ContentEntry<TValues>>;
|
|
48
|
+
/**
|
|
49
|
+
* Moves an entry to the trash (`schema@2.0`, ADR-0022).
|
|
50
|
+
*
|
|
51
|
+
* **This used to be a hard `DELETE`.** It now writes `deletedAt` and leaves
|
|
52
|
+
* every row — versions, blocks, join rows, the `translation_of` of its
|
|
53
|
+
* translations — exactly where it was, which is the only way `untrash()` can
|
|
54
|
+
* give back precisely what was taken.
|
|
55
|
+
*
|
|
56
|
+
* A collection declared `trash: false` keeps the old behaviour: `delete()`
|
|
57
|
+
* is `purge()`.
|
|
58
|
+
*/
|
|
36
59
|
delete(id: string): Promise<boolean>;
|
|
60
|
+
/** Takes an entry back out of the trash, with the status it went in with. */
|
|
61
|
+
untrash(id: string): Promise<ContentEntry<TValues>>;
|
|
62
|
+
/** The real `DELETE`, and the only one. What `delete()` did before 2.0. */
|
|
63
|
+
purge(id: string): Promise<boolean>;
|
|
64
|
+
/** Purges what has sat in the trash longer than `trash.retainDays`. */
|
|
65
|
+
purgeExpired(): Promise<PurgeReport>;
|
|
37
66
|
list(options?: ListOptions): Promise<Page<ContentEntry<TValues>>>;
|
|
38
67
|
publish(id: string, input?: {
|
|
39
68
|
readonly publishedBy?: string | null;
|
|
@@ -41,11 +70,11 @@ export interface ContentStore<TValues extends ContentValues = ContentValues> {
|
|
|
41
70
|
unpublish(id: string, input?: {
|
|
42
71
|
readonly status?: 'draft' | 'archived';
|
|
43
72
|
}): Promise<ContentEntry<TValues>>;
|
|
44
|
-
history(id: string): Promise<readonly VersionSummary[]>;
|
|
73
|
+
history(id: string, options?: TrashOptions): Promise<readonly VersionSummary[]>;
|
|
45
74
|
readVersion(id: string, version: number): Promise<ContentEntry<TValues> | null>;
|
|
46
75
|
restore(id: string, version: number, input?: UpdateInput<TValues>): Promise<ContentEntry<TValues>>;
|
|
47
76
|
diff(id: string, from: number, to: number): Promise<ContentDiff>;
|
|
48
|
-
translations(id: string): Promise<readonly ContentEntry<TValues>[]>;
|
|
77
|
+
translations(id: string, options?: TrashOptions): Promise<readonly ContentEntry<TValues>[]>;
|
|
49
78
|
resolveLocale(id: string, locale: string, options: ResolveLocaleOptions): Promise<LocaleResolution<TValues>>;
|
|
50
79
|
}
|
|
51
80
|
export declare function createContentStore<TValues extends ContentValues = ContentValues>(options: ContentStoreOptions): ContentStore<TValues>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/store/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,cAAc,EAMpB,MAAM,eAAe,CAAA;AAEtB,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/store/store.ts"],"names":[],"mappings":"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"}
|