@krantho/persistence-sqlite 0.0.0-stage → 1.0.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/LICENSE +21 -0
- package/dist/auth-adapter.d.ts +23 -0
- package/dist/auth-adapter.d.ts.map +1 -0
- package/dist/auth-adapter.js +159 -0
- package/dist/auth-adapter.js.map +1 -0
- package/dist/checkpoints.d.ts +20 -0
- package/dist/checkpoints.d.ts.map +1 -0
- package/dist/checkpoints.js +119 -0
- package/dist/checkpoints.js.map +1 -0
- package/dist/connection.d.ts +29 -0
- package/dist/connection.d.ts.map +1 -0
- package/dist/connection.js +77 -0
- package/dist/connection.js.map +1 -0
- package/dist/content-migration.d.ts +18 -0
- package/dist/content-migration.d.ts.map +1 -0
- package/dist/content-migration.js +68 -0
- package/dist/content-migration.js.map +1 -0
- package/dist/content-query.d.ts +16 -0
- package/dist/content-query.d.ts.map +1 -0
- package/dist/content-query.js +155 -0
- package/dist/content-query.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/lock-adapter.d.ts +11 -0
- package/dist/lock-adapter.d.ts.map +1 -0
- package/dist/lock-adapter.js +63 -0
- package/dist/lock-adapter.js.map +1 -0
- package/dist/media-adapter.d.ts +27 -0
- package/dist/media-adapter.d.ts.map +1 -0
- package/dist/media-adapter.js +175 -0
- package/dist/media-adapter.js.map +1 -0
- package/dist/media-folder-adapter.d.ts +13 -0
- package/dist/media-folder-adapter.d.ts.map +1 -0
- package/dist/media-folder-adapter.js +58 -0
- package/dist/media-folder-adapter.js.map +1 -0
- package/dist/migration-runner.d.ts +28 -0
- package/dist/migration-runner.d.ts.map +1 -0
- package/dist/migration-runner.js +109 -0
- package/dist/migration-runner.js.map +1 -0
- package/dist/migrations/index.d.ts +4 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +32 -0
- package/dist/migrations/index.js.map +1 -0
- package/dist/migrations/v001-initial-schema.d.ts +10 -0
- package/dist/migrations/v001-initial-schema.d.ts.map +1 -0
- package/dist/migrations/v001-initial-schema.js +94 -0
- package/dist/migrations/v001-initial-schema.js.map +1 -0
- package/dist/migrations/v002-auth-tables.d.ts +9 -0
- package/dist/migrations/v002-auth-tables.d.ts.map +1 -0
- package/dist/migrations/v002-auth-tables.js +38 -0
- package/dist/migrations/v002-auth-tables.js.map +1 -0
- package/dist/migrations/v003-user-permissions.d.ts +9 -0
- package/dist/migrations/v003-user-permissions.d.ts.map +1 -0
- package/dist/migrations/v003-user-permissions.js +21 -0
- package/dist/migrations/v003-user-permissions.js.map +1 -0
- package/dist/migrations/v004-media-hotspot.d.ts +14 -0
- package/dist/migrations/v004-media-hotspot.d.ts.map +1 -0
- package/dist/migrations/v004-media-hotspot.js +24 -0
- package/dist/migrations/v004-media-hotspot.js.map +1 -0
- package/dist/migrations/v005-content-locks.d.ts +9 -0
- package/dist/migrations/v005-content-locks.d.ts.map +1 -0
- package/dist/migrations/v005-content-locks.js +24 -0
- package/dist/migrations/v005-content-locks.js.map +1 -0
- package/dist/migrations/v006-webhook-deliveries.d.ts +6 -0
- package/dist/migrations/v006-webhook-deliveries.d.ts.map +1 -0
- package/dist/migrations/v006-webhook-deliveries.js +24 -0
- package/dist/migrations/v006-webhook-deliveries.js.map +1 -0
- package/dist/migrations/v007-media-folders.d.ts +10 -0
- package/dist/migrations/v007-media-folders.d.ts.map +1 -0
- package/dist/migrations/v007-media-folders.js +23 -0
- package/dist/migrations/v007-media-folders.js.map +1 -0
- package/dist/migrations/v008-site-settings.d.ts +9 -0
- package/dist/migrations/v008-site-settings.d.ts.map +1 -0
- package/dist/migrations/v008-site-settings.js +20 -0
- package/dist/migrations/v008-site-settings.js.map +1 -0
- package/dist/migrations/v009-seo-config.d.ts +15 -0
- package/dist/migrations/v009-seo-config.d.ts.map +1 -0
- package/dist/migrations/v009-seo-config.js +29 -0
- package/dist/migrations/v009-seo-config.js.map +1 -0
- package/dist/migrations/v010-content-live-version.d.ts +15 -0
- package/dist/migrations/v010-content-live-version.d.ts.map +1 -0
- package/dist/migrations/v010-content-live-version.js +40 -0
- package/dist/migrations/v010-content-live-version.js.map +1 -0
- package/dist/migrations/v011-media.d.ts +12 -0
- package/dist/migrations/v011-media.d.ts.map +1 -0
- package/dist/migrations/v011-media.js +43 -0
- package/dist/migrations/v011-media.js.map +1 -0
- package/dist/migrations/v012-page-cache.d.ts +12 -0
- package/dist/migrations/v012-page-cache.d.ts.map +1 -0
- package/dist/migrations/v012-page-cache.js +31 -0
- package/dist/migrations/v012-page-cache.js.map +1 -0
- package/dist/migrations/v013-content-contracts.d.ts +17 -0
- package/dist/migrations/v013-content-contracts.d.ts.map +1 -0
- package/dist/migrations/v013-content-contracts.js +46 -0
- package/dist/migrations/v013-content-contracts.js.map +1 -0
- package/dist/migrations/v014-content-manifest-commit.d.ts +7 -0
- package/dist/migrations/v014-content-manifest-commit.d.ts.map +1 -0
- package/dist/migrations/v014-content-manifest-commit.js +12 -0
- package/dist/migrations/v014-content-manifest-commit.js.map +1 -0
- package/dist/page-cache-store.d.ts +17 -0
- package/dist/page-cache-store.d.ts.map +1 -0
- package/dist/page-cache-store.js +76 -0
- package/dist/page-cache-store.js.map +1 -0
- package/dist/queries.d.ts +82 -0
- package/dist/queries.d.ts.map +1 -0
- package/dist/queries.js +25 -0
- package/dist/queries.js.map +1 -0
- package/dist/retention-adapter.d.ts +13 -0
- package/dist/retention-adapter.d.ts.map +1 -0
- package/dist/retention-adapter.js +62 -0
- package/dist/retention-adapter.js.map +1 -0
- package/dist/row-mapping.d.ts +14 -0
- package/dist/row-mapping.d.ts.map +1 -0
- package/dist/row-mapping.js +71 -0
- package/dist/row-mapping.js.map +1 -0
- package/dist/seo-config-adapter.d.ts +14 -0
- package/dist/seo-config-adapter.d.ts.map +1 -0
- package/dist/seo-config-adapter.js +57 -0
- package/dist/seo-config-adapter.js.map +1 -0
- package/dist/site-settings-adapter.d.ts +10 -0
- package/dist/site-settings-adapter.d.ts.map +1 -0
- package/dist/site-settings-adapter.js +27 -0
- package/dist/site-settings-adapter.js.map +1 -0
- package/dist/sqlite-adapter.d.ts +69 -0
- package/dist/sqlite-adapter.d.ts.map +1 -0
- package/dist/sqlite-adapter.js +242 -0
- package/dist/sqlite-adapter.js.map +1 -0
- package/dist/webhook-delivery-adapter.d.ts +27 -0
- package/dist/webhook-delivery-adapter.d.ts.map +1 -0
- package/dist/webhook-delivery-adapter.js +93 -0
- package/dist/webhook-delivery-adapter.js.map +1 -0
- package/package.json +37 -3
- package/README.md +0 -3
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V009: SEO configuration — structured entity graph per site, collection,
|
|
3
|
+
* document, or route.
|
|
4
|
+
*
|
|
5
|
+
* Stores a single `config` JSON column holding a `SeoConfigData` object
|
|
6
|
+
* with an ordered entity array and optional field-mapping overrides.
|
|
7
|
+
* Four scopes:
|
|
8
|
+
* - site: global business identity and site-level entities
|
|
9
|
+
* - collection: default archetype per content collection (primary)
|
|
10
|
+
* - route: URL-pattern override (optional, advanced)
|
|
11
|
+
* - document: per-page override
|
|
12
|
+
*/
|
|
13
|
+
export const v009SeoConfig = {
|
|
14
|
+
version: 9,
|
|
15
|
+
name: 'seo_config',
|
|
16
|
+
apply: (db) => {
|
|
17
|
+
db.exec(`
|
|
18
|
+
CREATE TABLE seo_config (
|
|
19
|
+
id TEXT PRIMARY KEY,
|
|
20
|
+
scope TEXT NOT NULL CHECK (scope IN ('site', 'collection', 'document', 'route')),
|
|
21
|
+
config TEXT NOT NULL DEFAULT '{"entities":[]}',
|
|
22
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
23
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
24
|
+
)
|
|
25
|
+
`);
|
|
26
|
+
db.exec('CREATE INDEX idx_seo_config_scope ON seo_config(scope)');
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=v009-seo-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v009-seo-config.js","sourceRoot":"","sources":["../../src/migrations/v009-seo-config.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,aAAa,GAAoB;IAC5C,OAAO,EAAE,CAAC;IACV,IAAI,EAAE,YAAY;IAClB,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE;QACZ,EAAE,CAAC,IAAI,CAAC;;;;;;;;KAQP,CAAC,CAAA;QACF,EAAE,CAAC,IAAI,CAAC,wDAAwD,CAAC,CAAA;IACnE,CAAC;CACF,CAAA"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { SqliteMigration } from '../migration-runner.js';
|
|
2
|
+
/**
|
|
3
|
+
* V010: live-version pointer on content rows.
|
|
4
|
+
*
|
|
5
|
+
* Every version row records `live_version` — the version of its document
|
|
6
|
+
* that is live (published) as of that row, or NULL when nothing is live. The
|
|
7
|
+
* document's live version is the current row's `live_version`, so a draft
|
|
8
|
+
* saved on top of a published page leaves the page live, and restoring
|
|
9
|
+
* `is_current` (checkpoints) restores the live version with it.
|
|
10
|
+
*
|
|
11
|
+
* Backfill: the latest published version at or before each row, unless a
|
|
12
|
+
* deletion lies between the two. Tombstones get NULL.
|
|
13
|
+
*/
|
|
14
|
+
export declare const v010ContentLiveVersion: SqliteMigration;
|
|
15
|
+
//# sourceMappingURL=v010-content-live-version.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v010-content-live-version.d.ts","sourceRoot":"","sources":["../../src/migrations/v010-content-live-version.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAE7D;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,sBAAsB,EAAE,eA0BpC,CAAA"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V010: live-version pointer on content rows.
|
|
3
|
+
*
|
|
4
|
+
* Every version row records `live_version` — the version of its document
|
|
5
|
+
* that is live (published) as of that row, or NULL when nothing is live. The
|
|
6
|
+
* document's live version is the current row's `live_version`, so a draft
|
|
7
|
+
* saved on top of a published page leaves the page live, and restoring
|
|
8
|
+
* `is_current` (checkpoints) restores the live version with it.
|
|
9
|
+
*
|
|
10
|
+
* Backfill: the latest published version at or before each row, unless a
|
|
11
|
+
* deletion lies between the two. Tombstones get NULL.
|
|
12
|
+
*/
|
|
13
|
+
export const v010ContentLiveVersion = {
|
|
14
|
+
version: 10,
|
|
15
|
+
name: 'content_live_version',
|
|
16
|
+
apply: (db) => {
|
|
17
|
+
db.exec('ALTER TABLE content ADD COLUMN live_version INTEGER');
|
|
18
|
+
db.exec(`
|
|
19
|
+
UPDATE content SET live_version = (
|
|
20
|
+
SELECT MAX(p.version) FROM content p
|
|
21
|
+
WHERE p.collection = content.collection
|
|
22
|
+
AND p.slug = content.slug
|
|
23
|
+
AND p.locale = content.locale
|
|
24
|
+
AND p.status = 'published'
|
|
25
|
+
AND p.version <= content.version
|
|
26
|
+
AND NOT EXISTS (
|
|
27
|
+
SELECT 1 FROM content d
|
|
28
|
+
WHERE d.collection = content.collection
|
|
29
|
+
AND d.slug = content.slug
|
|
30
|
+
AND d.locale = content.locale
|
|
31
|
+
AND d.status = 'deleted'
|
|
32
|
+
AND d.version > p.version
|
|
33
|
+
AND d.version <= content.version
|
|
34
|
+
)
|
|
35
|
+
)
|
|
36
|
+
WHERE status <> 'deleted'
|
|
37
|
+
`);
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
//# sourceMappingURL=v010-content-live-version.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v010-content-live-version.js","sourceRoot":"","sources":["../../src/migrations/v010-content-live-version.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAoB;IACrD,OAAO,EAAE,EAAE;IACX,IAAI,EAAE,sBAAsB;IAC5B,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE;QACZ,EAAE,CAAC,IAAI,CAAC,qDAAqD,CAAC,CAAA;QAC9D,EAAE,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;;KAmBP,CAAC,CAAA;IACJ,CAAC;CACF,CAAA"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { SqliteMigration } from '../migration-runner.js';
|
|
2
|
+
/**
|
|
3
|
+
* V011: Versioned media metadata.
|
|
4
|
+
*
|
|
5
|
+
* One row per version of a media item; the current version has
|
|
6
|
+
* `is_current = 1`, and a delete appends a tombstone with `deleted_at` set.
|
|
7
|
+
* `storage_key` is the object key in the configured file store, or NULL for
|
|
8
|
+
* a registered external URL. JSON columns (`tags`, `crop`, `hotspot`) are
|
|
9
|
+
* TEXT.
|
|
10
|
+
*/
|
|
11
|
+
export declare const v011Media: SqliteMigration;
|
|
12
|
+
//# sourceMappingURL=v011-media.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v011-media.d.ts","sourceRoot":"","sources":["../../src/migrations/v011-media.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAE7D;;;;;;;;GAQG;AACH,eAAO,MAAM,SAAS,EAAE,eAgCvB,CAAA"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V011: Versioned media metadata.
|
|
3
|
+
*
|
|
4
|
+
* One row per version of a media item; the current version has
|
|
5
|
+
* `is_current = 1`, and a delete appends a tombstone with `deleted_at` set.
|
|
6
|
+
* `storage_key` is the object key in the configured file store, or NULL for
|
|
7
|
+
* a registered external URL. JSON columns (`tags`, `crop`, `hotspot`) are
|
|
8
|
+
* TEXT.
|
|
9
|
+
*/
|
|
10
|
+
export const v011Media = {
|
|
11
|
+
version: 11,
|
|
12
|
+
name: 'media',
|
|
13
|
+
apply: (db) => {
|
|
14
|
+
db.exec(`
|
|
15
|
+
CREATE TABLE media (
|
|
16
|
+
row_id TEXT PRIMARY KEY,
|
|
17
|
+
id TEXT NOT NULL,
|
|
18
|
+
version INTEGER NOT NULL,
|
|
19
|
+
is_current INTEGER NOT NULL DEFAULT 1,
|
|
20
|
+
filename TEXT NOT NULL,
|
|
21
|
+
storage_key TEXT,
|
|
22
|
+
url TEXT NOT NULL,
|
|
23
|
+
alt_text TEXT NOT NULL DEFAULT '',
|
|
24
|
+
mime_type TEXT NOT NULL,
|
|
25
|
+
size INTEGER NOT NULL,
|
|
26
|
+
width INTEGER,
|
|
27
|
+
height INTEGER,
|
|
28
|
+
folder TEXT DEFAULT NULL,
|
|
29
|
+
tags TEXT DEFAULT '[]',
|
|
30
|
+
crop TEXT DEFAULT NULL,
|
|
31
|
+
hotspot TEXT DEFAULT NULL,
|
|
32
|
+
changed_by TEXT,
|
|
33
|
+
deleted_at TEXT,
|
|
34
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
35
|
+
UNIQUE(id, version)
|
|
36
|
+
)
|
|
37
|
+
`);
|
|
38
|
+
db.exec('CREATE INDEX idx_media_current ON media(id) WHERE is_current = 1');
|
|
39
|
+
db.exec('CREATE INDEX idx_media_folder ON media(folder) WHERE is_current = 1');
|
|
40
|
+
db.exec('CREATE INDEX idx_media_filename ON media(filename) WHERE is_current = 1');
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=v011-media.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v011-media.js","sourceRoot":"","sources":["../../src/migrations/v011-media.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,SAAS,GAAoB;IACxC,OAAO,EAAE,EAAE;IACX,IAAI,EAAE,OAAO;IACb,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE;QACZ,EAAE,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;;;;;;KAuBP,CAAC,CAAA;QACF,EAAE,CAAC,IAAI,CAAC,kEAAkE,CAAC,CAAA;QAC3E,EAAE,CAAC,IAAI,CAAC,qEAAqE,CAAC,CAAA;QAC9E,EAAE,CAAC,IAAI,CAAC,yEAAyE,CAAC,CAAA;IACpF,CAAC;CACF,CAAA"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { SqliteMigration } from '../migration-runner.js';
|
|
2
|
+
/**
|
|
3
|
+
* V012: shared page cache — rendered pages and fetch results, and the latest
|
|
4
|
+
* stale and expire mark per cache tag.
|
|
5
|
+
*
|
|
6
|
+
* `page_cache_entries.value` holds the encoded value (JSON text, binary leaves
|
|
7
|
+
* base64 — `encodePageCacheValue` in `@krantho/persistence`); `tags` is a JSON
|
|
8
|
+
* array replaced on every write. Timestamps are epoch milliseconds.
|
|
9
|
+
* `page_cache_tags.expired_at` is NULL until a tag is first expired.
|
|
10
|
+
*/
|
|
11
|
+
export declare const v012PageCache: SqliteMigration;
|
|
12
|
+
//# sourceMappingURL=v012-page-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v012-page-cache.d.ts","sourceRoot":"","sources":["../../src/migrations/v012-page-cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAE7D;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,EAAE,eAoB3B,CAAA"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V012: shared page cache — rendered pages and fetch results, and the latest
|
|
3
|
+
* stale and expire mark per cache tag.
|
|
4
|
+
*
|
|
5
|
+
* `page_cache_entries.value` holds the encoded value (JSON text, binary leaves
|
|
6
|
+
* base64 — `encodePageCacheValue` in `@krantho/persistence`); `tags` is a JSON
|
|
7
|
+
* array replaced on every write. Timestamps are epoch milliseconds.
|
|
8
|
+
* `page_cache_tags.expired_at` is NULL until a tag is first expired.
|
|
9
|
+
*/
|
|
10
|
+
export const v012PageCache = {
|
|
11
|
+
version: 12,
|
|
12
|
+
name: 'page_cache',
|
|
13
|
+
apply: (db) => {
|
|
14
|
+
db.exec(`
|
|
15
|
+
CREATE TABLE page_cache_entries (
|
|
16
|
+
key TEXT PRIMARY KEY,
|
|
17
|
+
value TEXT NOT NULL,
|
|
18
|
+
tags TEXT NOT NULL DEFAULT '[]',
|
|
19
|
+
last_modified INTEGER NOT NULL
|
|
20
|
+
)
|
|
21
|
+
`);
|
|
22
|
+
db.exec(`
|
|
23
|
+
CREATE TABLE page_cache_tags (
|
|
24
|
+
tag TEXT PRIMARY KEY,
|
|
25
|
+
stale_at INTEGER NOT NULL,
|
|
26
|
+
expired_at INTEGER
|
|
27
|
+
)
|
|
28
|
+
`);
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=v012-page-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v012-page-cache.js","sourceRoot":"","sources":["../../src/migrations/v012-page-cache.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,aAAa,GAAoB;IAC5C,OAAO,EAAE,EAAE;IACX,IAAI,EAAE,YAAY;IAClB,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE;QACZ,EAAE,CAAC,IAAI,CAAC;;;;;;;KAOP,CAAC,CAAA;QACF,EAAE,CAAC,IAAI,CAAC;;;;;;KAMP,CAAC,CAAA;IACJ,CAAC;CACF,CAAA"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { SqliteMigration } from '../migration-runner.js';
|
|
2
|
+
/**
|
|
3
|
+
* V013: content contracts — authored metadata on content rows, metadata groups
|
|
4
|
+
* on runtime schemas, and the locale of a repository import conflict.
|
|
5
|
+
*
|
|
6
|
+
* `content.authored` holds `{ metadata, contract, defaults, repositoryBasis }`
|
|
7
|
+
* (`AuthoredMetadata` in `@krantho/persistence`); rows written before it have
|
|
8
|
+
* no metadata and no contract. A conflict recorded before conflicts had a
|
|
9
|
+
* locale takes the locale of the document's first version.
|
|
10
|
+
*
|
|
11
|
+
* A document's own SEO moves from `seo_config` into its metadata: every version
|
|
12
|
+
* of the document gets the config as `metadata.seo`, as it applied to all of
|
|
13
|
+
* them before, and the `doc:` rows are removed. A config that is not an object
|
|
14
|
+
* (null, or text that does not parse) is left in place, and nothing is lost.
|
|
15
|
+
*/
|
|
16
|
+
export declare const v013ContentContracts: SqliteMigration;
|
|
17
|
+
//# sourceMappingURL=v013-content-contracts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v013-content-contracts.d.ts","sourceRoot":"","sources":["../../src/migrations/v013-content-contracts.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAE7D;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,oBAAoB,EAAE,eAgClC,CAAA"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { documentSeoRow } from '@krantho/persistence';
|
|
2
|
+
/**
|
|
3
|
+
* V013: content contracts — authored metadata on content rows, metadata groups
|
|
4
|
+
* on runtime schemas, and the locale of a repository import conflict.
|
|
5
|
+
*
|
|
6
|
+
* `content.authored` holds `{ metadata, contract, defaults, repositoryBasis }`
|
|
7
|
+
* (`AuthoredMetadata` in `@krantho/persistence`); rows written before it have
|
|
8
|
+
* no metadata and no contract. A conflict recorded before conflicts had a
|
|
9
|
+
* locale takes the locale of the document's first version.
|
|
10
|
+
*
|
|
11
|
+
* A document's own SEO moves from `seo_config` into its metadata: every version
|
|
12
|
+
* of the document gets the config as `metadata.seo`, as it applied to all of
|
|
13
|
+
* them before, and the `doc:` rows are removed. A config that is not an object
|
|
14
|
+
* (null, or text that does not parse) is left in place, and nothing is lost.
|
|
15
|
+
*/
|
|
16
|
+
export const v013ContentContracts = {
|
|
17
|
+
version: 13,
|
|
18
|
+
name: 'content_contracts',
|
|
19
|
+
apply: (db) => {
|
|
20
|
+
db.exec(`ALTER TABLE content ADD COLUMN authored TEXT NOT NULL DEFAULT '{"metadata":{},"contract":null,"defaults":[],"repositoryBasis":null}'`);
|
|
21
|
+
db.exec(`ALTER TABLE collection_schemas ADD COLUMN metadata_groups TEXT NOT NULL DEFAULT '[]'`);
|
|
22
|
+
db.exec(`ALTER TABLE content_migration_conflicts ADD COLUMN locale TEXT NOT NULL DEFAULT ''`);
|
|
23
|
+
db.exec(`
|
|
24
|
+
UPDATE content_migration_conflicts SET locale = COALESCE((
|
|
25
|
+
SELECT c.locale FROM content c
|
|
26
|
+
WHERE c.collection = content_migration_conflicts.collection
|
|
27
|
+
AND c.slug = content_migration_conflicts.slug
|
|
28
|
+
ORDER BY c.created_at, c.version LIMIT 1
|
|
29
|
+
), '')
|
|
30
|
+
`);
|
|
31
|
+
const rows = db
|
|
32
|
+
.prepare(`SELECT id, config FROM seo_config WHERE scope = 'document'`)
|
|
33
|
+
.all();
|
|
34
|
+
const copy = db.prepare(`UPDATE content SET authored = json_set(authored, '$.metadata.seo', json(?))
|
|
35
|
+
WHERE collection = ? AND slug = ?`);
|
|
36
|
+
const remove = db.prepare('DELETE FROM seo_config WHERE id = ?');
|
|
37
|
+
for (const row of rows) {
|
|
38
|
+
const moved = documentSeoRow(row.id, row.config);
|
|
39
|
+
if (!moved)
|
|
40
|
+
continue;
|
|
41
|
+
copy.run(JSON.stringify(moved.config), moved.collection, moved.slug);
|
|
42
|
+
remove.run(row.id);
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=v013-content-contracts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v013-content-contracts.js","sourceRoot":"","sources":["../../src/migrations/v013-content-contracts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAGrD;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAoB;IACnD,OAAO,EAAE,EAAE;IACX,IAAI,EAAE,mBAAmB;IACzB,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE;QACZ,EAAE,CAAC,IAAI,CACL,sIAAsI,CACvI,CAAA;QACD,EAAE,CAAC,IAAI,CAAC,sFAAsF,CAAC,CAAA;QAC/F,EAAE,CAAC,IAAI,CAAC,oFAAoF,CAAC,CAAA;QAC7F,EAAE,CAAC,IAAI,CAAC;;;;;;;KAOP,CAAC,CAAA;QACF,MAAM,IAAI,GAAG,EAAE;aACZ,OAAO,CAAC,4DAA4D,CAAC;aACrE,GAAG,EAAkD,CAAA;QACxD,MAAM,IAAI,GAAG,EAAE,CAAC,OAAO,CACrB;yCACmC,CACpC,CAAA;QACD,MAAM,MAAM,GAAG,EAAE,CAAC,OAAO,CAAC,qCAAqC,CAAC,CAAA;QAChE,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,MAAM,KAAK,GAAG,cAAc,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;YAChD,IAAI,CAAC,KAAK;gBAAE,SAAQ;YACpB,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,CAAC,CAAA;YACpE,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QACpB,CAAC;IACH,CAAC;CACF,CAAA"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { SqliteMigration } from '../migration-runner.js';
|
|
2
|
+
/**
|
|
3
|
+
* V014: the git commit each content import came from. `content_manifest.repo_commit` is NULL
|
|
4
|
+
* for an import whose build did not record one.
|
|
5
|
+
*/
|
|
6
|
+
export declare const v014ContentManifestCommit: SqliteMigration;
|
|
7
|
+
//# sourceMappingURL=v014-content-manifest-commit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v014-content-manifest-commit.d.ts","sourceRoot":"","sources":["../../src/migrations/v014-content-manifest-commit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAE7D;;;GAGG;AACH,eAAO,MAAM,yBAAyB,EAAE,eAMvC,CAAA"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V014: the git commit each content import came from. `content_manifest.repo_commit` is NULL
|
|
3
|
+
* for an import whose build did not record one.
|
|
4
|
+
*/
|
|
5
|
+
export const v014ContentManifestCommit = {
|
|
6
|
+
version: 14,
|
|
7
|
+
name: 'content_manifest_commit',
|
|
8
|
+
apply: (db) => {
|
|
9
|
+
db.exec('ALTER TABLE content_manifest ADD COLUMN repo_commit TEXT');
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=v014-content-manifest-commit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v014-content-manifest-commit.js","sourceRoot":"","sources":["../../src/migrations/v014-content-manifest-commit.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAoB;IACxD,OAAO,EAAE,EAAE;IACX,IAAI,EAAE,yBAAyB;IAC/B,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE;QACZ,EAAE,CAAC,IAAI,CAAC,0DAA0D,CAAC,CAAA;IACrE,CAAC;CACF,CAAA"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { IPageCacheStore, PageCacheEntry, PageCacheMarkOptions, PageCacheTagState, PageCacheValue } from '@krantho/persistence';
|
|
2
|
+
import type { Database } from 'better-sqlite3';
|
|
3
|
+
/**
|
|
4
|
+
* SQLite implementation of `IPageCacheStore`. Timestamps come from this
|
|
5
|
+
* process's clock (`Date.now()`) — a SQLite database has one host.
|
|
6
|
+
*/
|
|
7
|
+
export declare class SqlitePageCacheStore implements IPageCacheStore {
|
|
8
|
+
private readonly db;
|
|
9
|
+
constructor(db: Database);
|
|
10
|
+
get(key: string): Promise<PageCacheEntry | null>;
|
|
11
|
+
set(key: string, value: PageCacheValue, tags: readonly string[]): Promise<void>;
|
|
12
|
+
tagsRevalidatedSince(tags: readonly string[], since: number): Promise<PageCacheTagState>;
|
|
13
|
+
/** One statement: atomic, and each tag's timestamps only ever move forward */
|
|
14
|
+
markTags(tags: readonly string[], options: PageCacheMarkOptions): Promise<void>;
|
|
15
|
+
countTagged(tags: readonly string[]): Promise<number>;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=page-cache-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page-cache-store.d.ts","sourceRoot":"","sources":["../src/page-cache-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,cAAc,EACd,oBAAoB,EACpB,iBAAiB,EACjB,cAAc,EACf,MAAM,sBAAsB,CAAA;AAM7B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAa9C;;;GAGG;AACH,qBAAa,oBAAqB,YAAW,eAAe;IAC9C,OAAO,CAAC,QAAQ,CAAC,EAAE;gBAAF,EAAE,EAAE,QAAQ;IAEnC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAYhD,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAY/E,oBAAoB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAe9F,8EAA8E;IACxE,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;IAkB/E,WAAW,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;CAa5D"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { decodePageCacheValue, encodePageCacheValue, uniquePageCacheTags, } from '@krantho/persistence';
|
|
2
|
+
/**
|
|
3
|
+
* SQLite implementation of `IPageCacheStore`. Timestamps come from this
|
|
4
|
+
* process's clock (`Date.now()`) — a SQLite database has one host.
|
|
5
|
+
*/
|
|
6
|
+
export class SqlitePageCacheStore {
|
|
7
|
+
db;
|
|
8
|
+
constructor(db) {
|
|
9
|
+
this.db = db;
|
|
10
|
+
}
|
|
11
|
+
async get(key) {
|
|
12
|
+
const row = this.db
|
|
13
|
+
.prepare('SELECT value, tags, last_modified FROM page_cache_entries WHERE key = ?')
|
|
14
|
+
.get(key);
|
|
15
|
+
if (!row)
|
|
16
|
+
return null;
|
|
17
|
+
return {
|
|
18
|
+
value: decodePageCacheValue(row.value),
|
|
19
|
+
tags: JSON.parse(row.tags),
|
|
20
|
+
lastModified: row.last_modified,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
async set(key, value, tags) {
|
|
24
|
+
this.db
|
|
25
|
+
.prepare(`INSERT INTO page_cache_entries (key, value, tags, last_modified) VALUES (?, ?, ?, ?)
|
|
26
|
+
ON CONFLICT(key) DO UPDATE SET
|
|
27
|
+
value = excluded.value,
|
|
28
|
+
tags = excluded.tags,
|
|
29
|
+
last_modified = excluded.last_modified`)
|
|
30
|
+
.run(key, encodePageCacheValue(value), JSON.stringify(uniquePageCacheTags(tags)), Date.now());
|
|
31
|
+
}
|
|
32
|
+
async tagsRevalidatedSince(tags, since) {
|
|
33
|
+
if (tags.length === 0)
|
|
34
|
+
return 'fresh';
|
|
35
|
+
const row = this.db
|
|
36
|
+
.prepare(`SELECT
|
|
37
|
+
MAX(CASE WHEN expired_at >= ? THEN 1 ELSE 0 END) AS expired,
|
|
38
|
+
MAX(CASE WHEN stale_at >= ? THEN 1 ELSE 0 END) AS stale
|
|
39
|
+
FROM page_cache_tags
|
|
40
|
+
WHERE tag IN (SELECT value FROM json_each(?))`)
|
|
41
|
+
.get(since, since, JSON.stringify(uniquePageCacheTags(tags)));
|
|
42
|
+
if (row.expired === 1)
|
|
43
|
+
return 'expired';
|
|
44
|
+
return row.stale === 1 ? 'stale' : 'fresh';
|
|
45
|
+
}
|
|
46
|
+
/** One statement: atomic, and each tag's timestamps only ever move forward */
|
|
47
|
+
async markTags(tags, options) {
|
|
48
|
+
if (tags.length === 0)
|
|
49
|
+
return;
|
|
50
|
+
const now = Date.now();
|
|
51
|
+
this.db
|
|
52
|
+
.prepare(`INSERT INTO page_cache_tags (tag, stale_at, expired_at)
|
|
53
|
+
SELECT value, ?, ? FROM json_each(?) WHERE true
|
|
54
|
+
ON CONFLICT(tag) DO UPDATE SET
|
|
55
|
+
stale_at = max(stale_at, excluded.stale_at),
|
|
56
|
+
expired_at = CASE
|
|
57
|
+
WHEN excluded.expired_at IS NULL THEN expired_at
|
|
58
|
+
WHEN expired_at IS NULL THEN excluded.expired_at
|
|
59
|
+
ELSE max(expired_at, excluded.expired_at)
|
|
60
|
+
END`)
|
|
61
|
+
.run(now, options.expire ? now : null, JSON.stringify(uniquePageCacheTags(tags)));
|
|
62
|
+
}
|
|
63
|
+
async countTagged(tags) {
|
|
64
|
+
if (tags.length === 0)
|
|
65
|
+
return 0;
|
|
66
|
+
const row = this.db
|
|
67
|
+
.prepare(`SELECT COUNT(*) AS count FROM page_cache_entries
|
|
68
|
+
WHERE EXISTS (
|
|
69
|
+
SELECT 1 FROM json_each(page_cache_entries.tags)
|
|
70
|
+
WHERE value IN (SELECT value FROM json_each(?))
|
|
71
|
+
)`)
|
|
72
|
+
.get(JSON.stringify(uniquePageCacheTags(tags)));
|
|
73
|
+
return row.count;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=page-cache-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page-cache-store.js","sourceRoot":"","sources":["../src/page-cache-store.ts"],"names":[],"mappings":"AAOA,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,sBAAsB,CAAA;AAc7B;;;GAGG;AACH,MAAM,OAAO,oBAAoB;IACF;IAA7B,YAA6B,EAAY;QAAZ,OAAE,GAAF,EAAE,CAAU;IAAG,CAAC;IAE7C,KAAK,CAAC,GAAG,CAAC,GAAW;QACnB,MAAM,GAAG,GAAG,IAAI,CAAC,EAAE;aAChB,OAAO,CAAC,yEAAyE,CAAC;aAClF,GAAG,CAAC,GAAG,CAAyB,CAAA;QACnC,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAA;QACrB,OAAO;YACL,KAAK,EAAE,oBAAoB,CAAC,GAAG,CAAC,KAAK,CAAC;YACtC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAa;YACtC,YAAY,EAAE,GAAG,CAAC,aAAa;SAChC,CAAA;IACH,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,KAAqB,EAAE,IAAuB;QACnE,IAAI,CAAC,EAAE;aACJ,OAAO,CACN;;;;kDAI0C,CAC3C;aACA,GAAG,CAAC,GAAG,EAAE,oBAAoB,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAA;IACjG,CAAC;IAED,KAAK,CAAC,oBAAoB,CAAC,IAAuB,EAAE,KAAa;QAC/D,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,OAAO,CAAA;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,EAAE;aAChB,OAAO,CACN;;;;uDAI+C,CAChD;aACA,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,SAAS,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC,CAAgB,CAAA;QAC9E,IAAI,GAAG,CAAC,OAAO,KAAK,CAAC;YAAE,OAAO,SAAS,CAAA;QACvC,OAAO,GAAG,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAA;IAC5C,CAAC;IAED,8EAA8E;IAC9E,KAAK,CAAC,QAAQ,CAAC,IAAuB,EAAE,OAA6B;QACnE,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAM;QAC7B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QACtB,IAAI,CAAC,EAAE;aACJ,OAAO,CACN;;;;;;;;eAQO,CACR;aACA,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACrF,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,IAAuB;QACvC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,CAAC,CAAA;QAC/B,MAAM,GAAG,GAAG,IAAI,CAAC,EAAE;aAChB,OAAO,CACN;;;;WAIG,CACJ;aACA,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC,CAAsB,CAAA;QACtE,OAAO,GAAG,CAAC,KAAK,CAAA;IAClB,CAAC;CACF"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type Database from 'better-sqlite3';
|
|
2
|
+
/** Row shape from the append-only content table */
|
|
3
|
+
export interface ContentRow {
|
|
4
|
+
id: string;
|
|
5
|
+
collection: string;
|
|
6
|
+
slug: string;
|
|
7
|
+
locale: string;
|
|
8
|
+
version: number;
|
|
9
|
+
is_current: number;
|
|
10
|
+
status: string;
|
|
11
|
+
fields: string;
|
|
12
|
+
authored: string;
|
|
13
|
+
body: string;
|
|
14
|
+
html: string;
|
|
15
|
+
sections: string;
|
|
16
|
+
source: string;
|
|
17
|
+
file_path: string | null;
|
|
18
|
+
changed_by: string | null;
|
|
19
|
+
deleted_at: string | null;
|
|
20
|
+
created_at: string;
|
|
21
|
+
/** The document's live version as of this row; NULL when nothing is live */
|
|
22
|
+
live_version: number | null;
|
|
23
|
+
}
|
|
24
|
+
/** A content row read without its body, rendered HTML and sections */
|
|
25
|
+
export type SummaryRow = Omit<ContentRow, 'body' | 'html' | 'sections' | 'authored'> & {
|
|
26
|
+
/** `authored.metadata` alone: the contract and provenance stay in the row */
|
|
27
|
+
metadata: string | null;
|
|
28
|
+
};
|
|
29
|
+
/** The columns of a content row that name a document: everything but its body, HTML and sections */
|
|
30
|
+
export declare const SUMMARY_COLUMNS = "id, collection, slug, locale, version, is_current, status, fields, json_extract(authored, '$.metadata') AS metadata, source, file_path, changed_by, deleted_at, created_at, live_version";
|
|
31
|
+
/** A content row read with neither its fields nor its body, rendered HTML and sections */
|
|
32
|
+
export type MetaRow = Omit<SummaryRow, 'fields' | 'metadata'>;
|
|
33
|
+
/** The columns of a content row that make up a document's meta */
|
|
34
|
+
export declare const META_COLUMNS = "id, collection, slug, locale, version, is_current, status, source, file_path, changed_by, deleted_at, created_at, live_version";
|
|
35
|
+
/** Prepared statements for the append-only content table */
|
|
36
|
+
export interface PreparedStatements {
|
|
37
|
+
getBySlug: Database.Statement<[string], ContentRow>;
|
|
38
|
+
getByCollectionSlugLocale: Database.Statement<[string, string, string], ContentRow>;
|
|
39
|
+
getPublished: Database.Statement<[string, string, string], ContentRow>;
|
|
40
|
+
getCurrentState: Database.Statement<[
|
|
41
|
+
string,
|
|
42
|
+
string,
|
|
43
|
+
string
|
|
44
|
+
], {
|
|
45
|
+
live_version: number | null;
|
|
46
|
+
status: string;
|
|
47
|
+
authored: string;
|
|
48
|
+
}>;
|
|
49
|
+
existsBySlug: Database.Statement<[string], {
|
|
50
|
+
count: number;
|
|
51
|
+
}>;
|
|
52
|
+
listAllSlugs: Database.Statement<[], {
|
|
53
|
+
slug: string;
|
|
54
|
+
}>;
|
|
55
|
+
flipCurrent: Database.Statement<[string, string, string]>;
|
|
56
|
+
insertVersion: Database.Statement<[
|
|
57
|
+
string,
|
|
58
|
+
string,
|
|
59
|
+
string,
|
|
60
|
+
string,
|
|
61
|
+
number,
|
|
62
|
+
string,
|
|
63
|
+
string,
|
|
64
|
+
string,
|
|
65
|
+
string,
|
|
66
|
+
string,
|
|
67
|
+
string,
|
|
68
|
+
string | null,
|
|
69
|
+
string | null,
|
|
70
|
+
string | null,
|
|
71
|
+
string,
|
|
72
|
+
number | null,
|
|
73
|
+
string
|
|
74
|
+
]>;
|
|
75
|
+
getMaxVersion: Database.Statement<[string, string, string], {
|
|
76
|
+
max_version: number | null;
|
|
77
|
+
}>;
|
|
78
|
+
listVersionMeta: Database.Statement<[string, string, string], MetaRow>;
|
|
79
|
+
getVersionByNumber: Database.Statement<[string, string, string, number], ContentRow>;
|
|
80
|
+
}
|
|
81
|
+
export declare function prepareStatements(db: Database.Database): PreparedStatements;
|
|
82
|
+
//# sourceMappingURL=queries.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../src/queries.ts"],"names":[],"mappings":"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"}
|
package/dist/queries.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** The columns of a content row that name a document: everything but its body, HTML and sections */
|
|
2
|
+
export const SUMMARY_COLUMNS = "id, collection, slug, locale, version, is_current, status, fields, json_extract(authored, '$.metadata') AS metadata, source, file_path, changed_by, deleted_at, created_at, live_version";
|
|
3
|
+
/** The columns of a content row that make up a document's meta */
|
|
4
|
+
export const META_COLUMNS = 'id, collection, slug, locale, version, is_current, status, source, file_path, changed_by, deleted_at, created_at, live_version';
|
|
5
|
+
export function prepareStatements(db) {
|
|
6
|
+
return {
|
|
7
|
+
getBySlug: db.prepare('SELECT * FROM content WHERE slug = ? AND is_current = 1 AND deleted_at IS NULL'),
|
|
8
|
+
getByCollectionSlugLocale: db.prepare('SELECT * FROM content WHERE collection = ? AND slug = ? AND locale = ? AND is_current = 1 AND deleted_at IS NULL'),
|
|
9
|
+
getPublished: db.prepare(`SELECT live.* FROM content cur
|
|
10
|
+
JOIN content live ON live.collection = cur.collection AND live.slug = cur.slug
|
|
11
|
+
AND live.locale = cur.locale AND live.version = cur.live_version
|
|
12
|
+
WHERE cur.collection = ? AND cur.slug = ? AND cur.locale = ?
|
|
13
|
+
AND cur.is_current = 1 AND cur.deleted_at IS NULL`),
|
|
14
|
+
getCurrentState: db.prepare('SELECT live_version, status, authored FROM content WHERE collection = ? AND slug = ? AND locale = ? AND is_current = 1'),
|
|
15
|
+
existsBySlug: db.prepare('SELECT COUNT(*) as count FROM content WHERE slug = ? AND is_current = 1 AND deleted_at IS NULL'),
|
|
16
|
+
listAllSlugs: db.prepare('SELECT DISTINCT slug FROM content WHERE is_current = 1 AND deleted_at IS NULL'),
|
|
17
|
+
flipCurrent: db.prepare('UPDATE content SET is_current = 0 WHERE collection = ? AND slug = ? AND locale = ? AND is_current = 1'),
|
|
18
|
+
insertVersion: db.prepare(`INSERT INTO content (id, collection, slug, locale, version, status, fields, body, html, sections, source, file_path, changed_by, deleted_at, created_at, live_version, authored)
|
|
19
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),
|
|
20
|
+
getMaxVersion: db.prepare('SELECT MAX(version) as max_version FROM content WHERE collection = ? AND slug = ? AND locale = ?'),
|
|
21
|
+
listVersionMeta: db.prepare(`SELECT ${META_COLUMNS} FROM content WHERE collection = ? AND slug = ? AND locale = ? ORDER BY version DESC`),
|
|
22
|
+
getVersionByNumber: db.prepare('SELECT * FROM content WHERE collection = ? AND slug = ? AND locale = ? AND version = ?'),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=queries.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queries.js","sourceRoot":"","sources":["../src/queries.ts"],"names":[],"mappings":"AA+BA,oGAAoG;AACpG,MAAM,CAAC,MAAM,eAAe,GAC1B,0LAA0L,CAAA;AAK5L,kEAAkE;AAClE,MAAM,CAAC,MAAM,YAAY,GACvB,gIAAgI,CAAA;AAwClI,MAAM,UAAU,iBAAiB,CAAC,EAAqB;IACrD,OAAO;QACL,SAAS,EAAE,EAAE,CAAC,OAAO,CACnB,gFAAgF,CACjF;QACD,yBAAyB,EAAE,EAAE,CAAC,OAAO,CACnC,kHAAkH,CACnH;QACD,YAAY,EAAE,EAAE,CAAC,OAAO,CACtB;;;;2DAIqD,CACtD;QACD,eAAe,EAAE,EAAE,CAAC,OAAO,CAIzB,wHAAwH,CACzH;QACD,YAAY,EAAE,EAAE,CAAC,OAAO,CACtB,gGAAgG,CACjG;QACD,YAAY,EAAE,EAAE,CAAC,OAAO,CACtB,+EAA+E,CAChF;QACD,WAAW,EAAE,EAAE,CAAC,OAAO,CACrB,uGAAuG,CACxG;QACD,aAAa,EAAE,EAAE,CAAC,OAAO,CACvB;kEAC4D,CAC7D;QACD,aAAa,EAAE,EAAE,CAAC,OAAO,CACvB,kGAAkG,CACnG;QACD,eAAe,EAAE,EAAE,CAAC,OAAO,CACzB,UAAU,YAAY,sFAAsF,CAC7G;QACD,kBAAkB,EAAE,EAAE,CAAC,OAAO,CAC5B,wFAAwF,CACzF;KACF,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { IRetentionAdapter, PurgeResult } from '@krantho/persistence';
|
|
2
|
+
/** SQLite implementation of version retention/purge */
|
|
3
|
+
export declare class SqliteRetentionAdapter implements IRetentionAdapter {
|
|
4
|
+
private readonly db;
|
|
5
|
+
constructor(db: import('better-sqlite3').Database);
|
|
6
|
+
purgeVersions(maxVersionsPerItem: number): Promise<PurgeResult[]>;
|
|
7
|
+
countVersions(): Promise<{
|
|
8
|
+
total: number;
|
|
9
|
+
current: number;
|
|
10
|
+
historical: number;
|
|
11
|
+
}>;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=retention-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retention-adapter.d.ts","sourceRoot":"","sources":["../src/retention-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAS1E,uDAAuD;AACvD,qBAAa,sBAAuB,YAAW,iBAAiB;IAC9D,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAmC;gBAE1C,EAAE,EAAE,OAAO,gBAAgB,EAAE,QAAQ;IAI3C,aAAa,CAAC,kBAAkB,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IA4DjE,aAAa,IAAI,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;CAcvF"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/** SQLite implementation of version retention/purge */
|
|
2
|
+
export class SqliteRetentionAdapter {
|
|
3
|
+
db;
|
|
4
|
+
constructor(db) {
|
|
5
|
+
this.db = db;
|
|
6
|
+
}
|
|
7
|
+
async purgeVersions(maxVersionsPerItem) {
|
|
8
|
+
if (maxVersionsPerItem < 1) {
|
|
9
|
+
throw new Error('maxVersionsPerItem must be at least 1');
|
|
10
|
+
}
|
|
11
|
+
// Find items with more versions than the limit
|
|
12
|
+
const groups = this.db
|
|
13
|
+
.prepare(`SELECT collection, slug, locale, COUNT(*) as version_count
|
|
14
|
+
FROM content
|
|
15
|
+
WHERE deleted_at IS NULL
|
|
16
|
+
GROUP BY collection, slug, locale
|
|
17
|
+
HAVING COUNT(*) > ?`)
|
|
18
|
+
.all(maxVersionsPerItem);
|
|
19
|
+
const results = [];
|
|
20
|
+
for (const group of groups) {
|
|
21
|
+
const toPurge = group.version_count - maxVersionsPerItem;
|
|
22
|
+
// Delete oldest non-current versions, never the one that is live. When the
|
|
23
|
+
// live version is among the oldest, fewer rows go and the document stays
|
|
24
|
+
// over the limit until later versions age out.
|
|
25
|
+
const deleted = this.db
|
|
26
|
+
.prepare(`DELETE FROM content WHERE id IN (
|
|
27
|
+
SELECT id FROM content
|
|
28
|
+
WHERE collection = ? AND slug = ? AND locale = ? AND is_current = 0
|
|
29
|
+
AND version IS NOT (
|
|
30
|
+
SELECT live_version FROM content
|
|
31
|
+
WHERE collection = ? AND slug = ? AND locale = ? AND is_current = 1
|
|
32
|
+
)
|
|
33
|
+
ORDER BY version ASC
|
|
34
|
+
LIMIT ?
|
|
35
|
+
)`)
|
|
36
|
+
.run(group.collection, group.slug, group.locale, group.collection, group.slug, group.locale, toPurge);
|
|
37
|
+
if (deleted.changes > 0) {
|
|
38
|
+
results.push({
|
|
39
|
+
collection: group.collection,
|
|
40
|
+
slug: group.slug,
|
|
41
|
+
purged: deleted.changes,
|
|
42
|
+
retained: group.version_count - deleted.changes,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return results;
|
|
47
|
+
}
|
|
48
|
+
async countVersions() {
|
|
49
|
+
const total = this.db
|
|
50
|
+
.prepare('SELECT COUNT(*) as count FROM content WHERE deleted_at IS NULL')
|
|
51
|
+
.get();
|
|
52
|
+
const current = this.db
|
|
53
|
+
.prepare('SELECT COUNT(*) as count FROM content WHERE is_current = 1 AND deleted_at IS NULL')
|
|
54
|
+
.get();
|
|
55
|
+
return {
|
|
56
|
+
total: total.count,
|
|
57
|
+
current: current.count,
|
|
58
|
+
historical: total.count - current.count,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=retention-adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retention-adapter.js","sourceRoot":"","sources":["../src/retention-adapter.ts"],"names":[],"mappings":"AASA,uDAAuD;AACvD,MAAM,OAAO,sBAAsB;IAChB,EAAE,CAAmC;IAEtD,YAAY,EAAqC;QAC/C,IAAI,CAAC,EAAE,GAAG,EAAE,CAAA;IACd,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,kBAA0B;QAC5C,IAAI,kBAAkB,GAAG,CAAC,EAAE,CAAC;YAC3B,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAA;QAC1D,CAAC;QAED,+CAA+C;QAC/C,MAAM,MAAM,GAAG,IAAI,CAAC,EAAE;aACnB,OAAO,CACN;;;;6BAIqB,CACtB;aACA,GAAG,CAAC,kBAAkB,CAAmB,CAAA;QAE5C,MAAM,OAAO,GAAkB,EAAE,CAAA;QAEjC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,MAAM,OAAO,GAAG,KAAK,CAAC,aAAa,GAAG,kBAAkB,CAAA;YAExD,2EAA2E;YAC3E,yEAAyE;YACzE,+CAA+C;YAC/C,MAAM,OAAO,GAAG,IAAI,CAAC,EAAE;iBACpB,OAAO,CACN;;;;;;;;;YASE,CACH;iBACA,GAAG,CACF,KAAK,CAAC,UAAU,EAChB,KAAK,CAAC,IAAI,EACV,KAAK,CAAC,MAAM,EACZ,KAAK,CAAC,UAAU,EAChB,KAAK,CAAC,IAAI,EACV,KAAK,CAAC,MAAM,EACZ,OAAO,CACR,CAAA;YAEH,IAAI,OAAO,CAAC,OAAO,GAAG,CAAC,EAAE,CAAC;gBACxB,OAAO,CAAC,IAAI,CAAC;oBACX,UAAU,EAAE,KAAK,CAAC,UAAU;oBAC5B,IAAI,EAAE,KAAK,CAAC,IAAI;oBAChB,MAAM,EAAE,OAAO,CAAC,OAAO;oBACvB,QAAQ,EAAE,KAAK,CAAC,aAAa,GAAG,OAAO,CAAC,OAAO;iBAChD,CAAC,CAAA;YACJ,CAAC;QACH,CAAC;QAED,OAAO,OAAO,CAAA;IAChB,CAAC;IAED,KAAK,CAAC,aAAa;QACjB,MAAM,KAAK,GAAG,IAAI,CAAC,EAAE;aAClB,OAAO,CAAC,gEAAgE,CAAC;aACzE,GAAG,EAAuB,CAAA;QAC7B,MAAM,OAAO,GAAG,IAAI,CAAC,EAAE;aACpB,OAAO,CAAC,mFAAmF,CAAC;aAC5F,GAAG,EAAuB,CAAA;QAE7B,OAAO;YACL,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,OAAO,EAAE,OAAO,CAAC,KAAK;YACtB,UAAU,EAAE,KAAK,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK;SACxC,CAAA;IACH,CAAC;CACF"}
|