@cancia/astro 0.0.1 → 0.0.2
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/chunk-337LJIKX.js +76 -0
- package/dist/chunk-AE4SIY24.js +63 -0
- package/dist/{chunk-YPVZDWTW.js → chunk-KL2YMBFV.js} +1 -1
- package/dist/{chunk-QAM5VKAF.js → chunk-RKHJNQ6R.js} +21 -0
- package/dist/{chunk-YQDZQSES.js → chunk-ST44VULL.js} +4 -78
- package/dist/endpoints/schemas.js +2 -2
- package/dist/index.d.ts +3 -18
- package/dist/index.js +11 -64
- package/dist/loader/index.js +2 -1
- package/dist/schema/index.d.ts +75 -6
- package/dist/schema/index.js +3 -1
- package/dist/storage/index.d.ts +20 -0
- package/dist/storage/index.js +16 -0
- package/package.json +9 -3
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createJsonFileAdapterV2
|
|
3
|
+
} from "./chunk-ST44VULL.js";
|
|
4
|
+
|
|
5
|
+
// src/loader/index.ts
|
|
6
|
+
import { join } from "path";
|
|
7
|
+
function makeId(locale, entryId) {
|
|
8
|
+
return `${locale}/${entryId}`;
|
|
9
|
+
}
|
|
10
|
+
async function syncOnce(ctx, lists, list, site) {
|
|
11
|
+
ctx.store.clear();
|
|
12
|
+
const entries = await lists.list(site, list);
|
|
13
|
+
for (const entry of entries) {
|
|
14
|
+
const id = makeId(entry.locale, entry.id);
|
|
15
|
+
const data = {
|
|
16
|
+
...entry.data,
|
|
17
|
+
locale: entry.locale,
|
|
18
|
+
createdAt: entry.createdAt,
|
|
19
|
+
updatedAt: entry.updatedAt
|
|
20
|
+
};
|
|
21
|
+
ctx.store.set({
|
|
22
|
+
id,
|
|
23
|
+
data,
|
|
24
|
+
digest: entry._rev
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
ctx.logger.info(
|
|
28
|
+
`cancia: loaded ${entries.length} entr${entries.length === 1 ? "y" : "ies"} from list "${list}"`
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
function canciaLoader(opts) {
|
|
32
|
+
let watcherAttached = false;
|
|
33
|
+
let latestCtx = null;
|
|
34
|
+
let pendingTimer = null;
|
|
35
|
+
let inflight = Promise.resolve();
|
|
36
|
+
return {
|
|
37
|
+
name: `@cancia/astro/loader[${opts.list}]`,
|
|
38
|
+
async load(ctx) {
|
|
39
|
+
latestCtx = ctx;
|
|
40
|
+
const storage = opts.storage ?? createJsonFileAdapterV2({
|
|
41
|
+
projectRoot: opts.projectRoot ?? process.cwd()
|
|
42
|
+
});
|
|
43
|
+
const { lists } = storage;
|
|
44
|
+
await syncOnce(ctx, lists, opts.list, opts.site);
|
|
45
|
+
if (ctx.watcher && !watcherAttached) {
|
|
46
|
+
watcherAttached = true;
|
|
47
|
+
const root = opts.projectRoot ?? process.cwd();
|
|
48
|
+
const watchDir = join(root, ".cancia", "lists", opts.list, opts.site);
|
|
49
|
+
ctx.watcher.add(watchDir);
|
|
50
|
+
const scheduleSync = () => {
|
|
51
|
+
if (pendingTimer) clearTimeout(pendingTimer);
|
|
52
|
+
pendingTimer = setTimeout(() => {
|
|
53
|
+
pendingTimer = null;
|
|
54
|
+
inflight = inflight.catch(() => {
|
|
55
|
+
}).then(
|
|
56
|
+
() => syncOnce(latestCtx, lists, opts.list, opts.site).catch((err) => {
|
|
57
|
+
latestCtx.logger.error(`cancia: resync failed \u2014 ${err.message}`);
|
|
58
|
+
})
|
|
59
|
+
);
|
|
60
|
+
}, 50);
|
|
61
|
+
};
|
|
62
|
+
const onEvent = (path) => {
|
|
63
|
+
if (!path.startsWith(watchDir)) return;
|
|
64
|
+
scheduleSync();
|
|
65
|
+
};
|
|
66
|
+
ctx.watcher.on("change", onEvent);
|
|
67
|
+
ctx.watcher.on("add", onEvent);
|
|
68
|
+
ctx.watcher.on("unlink", onEvent);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export {
|
|
75
|
+
canciaLoader
|
|
76
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// src/storage/sqlite.ts
|
|
2
|
+
import { createRequire } from "module";
|
|
3
|
+
var require2 = createRequire(import.meta.url);
|
|
4
|
+
var _db = null;
|
|
5
|
+
function createDB(dbPath) {
|
|
6
|
+
const Database = require2("better-sqlite3");
|
|
7
|
+
const sqlite = new Database(dbPath);
|
|
8
|
+
sqlite.pragma("journal_mode = WAL");
|
|
9
|
+
sqlite.exec(`
|
|
10
|
+
CREATE TABLE IF NOT EXISTS cancia_content (
|
|
11
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
12
|
+
site TEXT NOT NULL,
|
|
13
|
+
key TEXT NOT NULL,
|
|
14
|
+
lang TEXT NOT NULL,
|
|
15
|
+
value TEXT NOT NULL,
|
|
16
|
+
updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
17
|
+
UNIQUE(site, key, lang)
|
|
18
|
+
)
|
|
19
|
+
`);
|
|
20
|
+
return {
|
|
21
|
+
get: sqlite.prepare(
|
|
22
|
+
"SELECT value FROM cancia_content WHERE site=? AND key=? AND lang=?"
|
|
23
|
+
),
|
|
24
|
+
set: sqlite.prepare(
|
|
25
|
+
`INSERT INTO cancia_content (site, key, lang, value, updated_at)
|
|
26
|
+
VALUES (?, ?, ?, ?, unixepoch())
|
|
27
|
+
ON CONFLICT(site, key, lang) DO UPDATE SET value=excluded.value, updated_at=unixepoch()`
|
|
28
|
+
),
|
|
29
|
+
getAll: sqlite.prepare(
|
|
30
|
+
"SELECT key, lang, value FROM cancia_content WHERE site=?"
|
|
31
|
+
),
|
|
32
|
+
delete: sqlite.prepare(
|
|
33
|
+
"DELETE FROM cancia_content WHERE site=? AND key=? AND lang=?"
|
|
34
|
+
)
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function getDB(dbPath) {
|
|
38
|
+
if (!_db) _db = createDB(dbPath);
|
|
39
|
+
return _db;
|
|
40
|
+
}
|
|
41
|
+
function createSQLiteAdapter(dbPath) {
|
|
42
|
+
const path = dbPath ?? process.cwd() + "/cancia.db";
|
|
43
|
+
return {
|
|
44
|
+
async get(site, key, lang) {
|
|
45
|
+
const row = getDB(path).get.get(site, key, lang);
|
|
46
|
+
return row?.value ?? null;
|
|
47
|
+
},
|
|
48
|
+
async set(site, key, lang, value) {
|
|
49
|
+
getDB(path).set.run(site, key, lang, value);
|
|
50
|
+
},
|
|
51
|
+
async getAll(site) {
|
|
52
|
+
const rows = getDB(path).getAll.all(site);
|
|
53
|
+
return Object.fromEntries(rows.map((r) => [`${r.key}.${r.lang}`, r.value]));
|
|
54
|
+
},
|
|
55
|
+
async delete(site, key, lang) {
|
|
56
|
+
getDB(path).delete.run(site, key, lang);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export {
|
|
62
|
+
createSQLiteAdapter
|
|
63
|
+
};
|
|
@@ -11,6 +11,23 @@ function defineList(opts) {
|
|
|
11
11
|
validator: z.object(opts.fields)
|
|
12
12
|
};
|
|
13
13
|
}
|
|
14
|
+
var defineField = {
|
|
15
|
+
text: (o) => z.string().meta({ widget: "text", ...o }),
|
|
16
|
+
textarea: (o) => z.string().meta({ widget: "textarea", ...o }),
|
|
17
|
+
url: (o) => z.string().url().meta({ widget: "url", ...o }),
|
|
18
|
+
email: (o) => z.string().email().meta({ widget: "email", ...o }),
|
|
19
|
+
datetime: (o) => z.string().datetime().meta({ widget: "datetime", ...o }),
|
|
20
|
+
checkbox: (o) => z.boolean().meta({ widget: "checkbox", ...o }),
|
|
21
|
+
number: (o) => {
|
|
22
|
+
let n = z.number();
|
|
23
|
+
if (o?.min != null) n = n.min(o.min);
|
|
24
|
+
if (o?.max != null) n = n.max(o.max);
|
|
25
|
+
return n.meta({ widget: "number", ...o });
|
|
26
|
+
},
|
|
27
|
+
slug: (o) => z.string().regex(/^[a-z0-9-]+$/).meta({ widget: "slug", ...o }),
|
|
28
|
+
image: (o) => z.string().url().meta({ widget: "image", ...o }),
|
|
29
|
+
select: (o) => z.enum(o.options).meta({ widget: "select", ...o })
|
|
30
|
+
};
|
|
14
31
|
function humanise(name) {
|
|
15
32
|
return name.replace(/([A-Z])/g, " $1").replace(/[_-]/g, " ").replace(/^\w/, (c) => c.toUpperCase()).trim();
|
|
16
33
|
}
|
|
@@ -44,6 +61,9 @@ function describeProperty(name, prop, required) {
|
|
|
44
61
|
if (prop.minimum !== void 0) desc.min = prop.minimum;
|
|
45
62
|
if (prop.maximum !== void 0) desc.max = prop.maximum;
|
|
46
63
|
if (prop.enum) desc.options = prop.enum;
|
|
64
|
+
else if (prop.options) desc.options = prop.options;
|
|
65
|
+
if (prop.pattern !== void 0) desc.pattern = prop.pattern;
|
|
66
|
+
if (prop.source !== void 0) desc.source = prop.source;
|
|
47
67
|
return desc;
|
|
48
68
|
}
|
|
49
69
|
function describeList(name, schema) {
|
|
@@ -69,5 +89,6 @@ function describeList(name, schema) {
|
|
|
69
89
|
export {
|
|
70
90
|
z,
|
|
71
91
|
defineList,
|
|
92
|
+
defineField,
|
|
72
93
|
describeList
|
|
73
94
|
};
|
|
@@ -2,14 +2,6 @@ import {
|
|
|
2
2
|
RevConflictError
|
|
3
3
|
} from "./chunk-7IA5B5CF.js";
|
|
4
4
|
|
|
5
|
-
// src/loader/index.ts
|
|
6
|
-
import { join as join2 } from "path";
|
|
7
|
-
|
|
8
|
-
// src/storage/json-file-v2.ts
|
|
9
|
-
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, rmSync } from "fs";
|
|
10
|
-
import { dirname as dirname2, join } from "path";
|
|
11
|
-
import { createHash, randomUUID } from "crypto";
|
|
12
|
-
|
|
13
5
|
// src/storage/json-file.ts
|
|
14
6
|
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
15
7
|
import { dirname } from "path";
|
|
@@ -57,6 +49,9 @@ function createJsonFileAdapter(filePath) {
|
|
|
57
49
|
}
|
|
58
50
|
|
|
59
51
|
// src/storage/json-file-v2.ts
|
|
52
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, rmSync } from "fs";
|
|
53
|
+
import { dirname as dirname2, join } from "path";
|
|
54
|
+
import { createHash, randomUUID } from "crypto";
|
|
60
55
|
function canonicalize(value) {
|
|
61
56
|
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
62
57
|
if (Array.isArray(value)) {
|
|
@@ -318,76 +313,7 @@ function createJsonFileAdapterV2(opts = {}) {
|
|
|
318
313
|
};
|
|
319
314
|
}
|
|
320
315
|
|
|
321
|
-
// src/loader/index.ts
|
|
322
|
-
function makeId(locale, entryId) {
|
|
323
|
-
return `${locale}/${entryId}`;
|
|
324
|
-
}
|
|
325
|
-
async function syncOnce(ctx, lists, list, site) {
|
|
326
|
-
ctx.store.clear();
|
|
327
|
-
const entries = await lists.list(site, list);
|
|
328
|
-
for (const entry of entries) {
|
|
329
|
-
const id = makeId(entry.locale, entry.id);
|
|
330
|
-
const data = {
|
|
331
|
-
...entry.data,
|
|
332
|
-
locale: entry.locale,
|
|
333
|
-
createdAt: entry.createdAt,
|
|
334
|
-
updatedAt: entry.updatedAt
|
|
335
|
-
};
|
|
336
|
-
ctx.store.set({
|
|
337
|
-
id,
|
|
338
|
-
data,
|
|
339
|
-
digest: entry._rev
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
ctx.logger.info(
|
|
343
|
-
`cancia: loaded ${entries.length} entr${entries.length === 1 ? "y" : "ies"} from list "${list}"`
|
|
344
|
-
);
|
|
345
|
-
}
|
|
346
|
-
function canciaLoader(opts) {
|
|
347
|
-
let watcherAttached = false;
|
|
348
|
-
let latestCtx = null;
|
|
349
|
-
let pendingTimer = null;
|
|
350
|
-
let inflight = Promise.resolve();
|
|
351
|
-
return {
|
|
352
|
-
name: `@cancia/astro/loader[${opts.list}]`,
|
|
353
|
-
async load(ctx) {
|
|
354
|
-
latestCtx = ctx;
|
|
355
|
-
const storage = opts.storage ?? createJsonFileAdapterV2({
|
|
356
|
-
projectRoot: opts.projectRoot ?? process.cwd()
|
|
357
|
-
});
|
|
358
|
-
const { lists } = storage;
|
|
359
|
-
await syncOnce(ctx, lists, opts.list, opts.site);
|
|
360
|
-
if (ctx.watcher && !watcherAttached) {
|
|
361
|
-
watcherAttached = true;
|
|
362
|
-
const root = opts.projectRoot ?? process.cwd();
|
|
363
|
-
const watchDir = join2(root, ".cancia", "lists", opts.list, opts.site);
|
|
364
|
-
ctx.watcher.add(watchDir);
|
|
365
|
-
const scheduleSync = () => {
|
|
366
|
-
if (pendingTimer) clearTimeout(pendingTimer);
|
|
367
|
-
pendingTimer = setTimeout(() => {
|
|
368
|
-
pendingTimer = null;
|
|
369
|
-
inflight = inflight.catch(() => {
|
|
370
|
-
}).then(
|
|
371
|
-
() => syncOnce(latestCtx, lists, opts.list, opts.site).catch((err) => {
|
|
372
|
-
latestCtx.logger.error(`cancia: resync failed \u2014 ${err.message}`);
|
|
373
|
-
})
|
|
374
|
-
);
|
|
375
|
-
}, 50);
|
|
376
|
-
};
|
|
377
|
-
const onEvent = (path) => {
|
|
378
|
-
if (!path.startsWith(watchDir)) return;
|
|
379
|
-
scheduleSync();
|
|
380
|
-
};
|
|
381
|
-
ctx.watcher.on("change", onEvent);
|
|
382
|
-
ctx.watcher.on("add", onEvent);
|
|
383
|
-
ctx.watcher.on("unlink", onEvent);
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
};
|
|
387
|
-
}
|
|
388
|
-
|
|
389
316
|
export {
|
|
390
317
|
createJsonFileAdapter,
|
|
391
|
-
createJsonFileAdapterV2
|
|
392
|
-
canciaLoader
|
|
318
|
+
createJsonFileAdapterV2
|
|
393
319
|
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
makeSchemasRoute
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-KL2YMBFV.js";
|
|
4
4
|
import "../chunk-NG5GJME5.js";
|
|
5
|
-
import "../chunk-
|
|
5
|
+
import "../chunk-RKHJNQ6R.js";
|
|
6
6
|
|
|
7
7
|
// src/endpoints/schemas.ts
|
|
8
8
|
import { getCanciaRuntime } from "virtual:cancia/runtime";
|
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,8 @@ export { b as CanciaKVStore, c as CanciaListStore, d as CanciaPageStore, L as Li
|
|
|
4
4
|
import { U as UploadHandler } from './upload-DwCGjXbz.js';
|
|
5
5
|
export { m as makeLocalUploadHandler } from './upload-DwCGjXbz.js';
|
|
6
6
|
export { CanciaLoaderOptions, canciaLoader } from './loader/index.js';
|
|
7
|
-
export { FieldDescription, FieldMeta, FieldWidget, ListDescription, ListSchema, SchemasModule, defineList, describeList } from './schema/index.js';
|
|
7
|
+
export { FieldDescription, FieldMeta, FieldMetaBase, FieldWidget, ListDescription, ListSchema, SchemasModule, defineField, defineList, describeList } from './schema/index.js';
|
|
8
|
+
export { createJsonFileAdapter, createJsonFileAdapterV2, createSQLiteAdapter } from './storage/index.js';
|
|
8
9
|
export { z } from 'zod';
|
|
9
10
|
import 'astro/loaders';
|
|
10
11
|
|
|
@@ -126,20 +127,4 @@ declare function fetchCMSData(opts: {
|
|
|
126
127
|
*/
|
|
127
128
|
declare function makeUseTranslations<TUI extends Record<string, Record<string, string>>, TLang extends keyof TUI>(ui: TUI, defaultLang: TLang): (lang: TLang, cmsData?: CMSData) => (key: keyof TUI[TLang]) => string;
|
|
128
129
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
interface JsonFileV2Options {
|
|
132
|
-
/** Project root. Defaults to process.cwd(). */
|
|
133
|
-
projectRoot?: string;
|
|
134
|
-
/** Override the KV file path. Defaults to <root>/cancia-content.json. */
|
|
135
|
-
kvPath?: string;
|
|
136
|
-
/** Override the pages file path. Defaults to <root>/.cancia/pages.json. */
|
|
137
|
-
pagesPath?: string;
|
|
138
|
-
/** Override the lists directory. Defaults to <root>/.cancia/lists. */
|
|
139
|
-
listsDir?: string;
|
|
140
|
-
}
|
|
141
|
-
declare function createJsonFileAdapterV2(opts?: JsonFileV2Options): CanciaStorageV2;
|
|
142
|
-
|
|
143
|
-
declare function createSQLiteAdapter(dbPath?: string): CanciaStorage;
|
|
144
|
-
|
|
145
|
-
export { type CMSData, type CanciaIntegrationOptions, CanciaStorage, CanciaStorageV2, type R2UploadHandlerOptions, UploadHandler, canciaIntegration, createJsonFileAdapter, createJsonFileAdapterV2, createSQLiteAdapter, canciaIntegration as default, fetchCMSData, makeR2UploadHandler, makeUseTranslations };
|
|
130
|
+
export { type CMSData, type CanciaIntegrationOptions, CanciaStorage, CanciaStorageV2, type R2UploadHandlerOptions, UploadHandler, canciaIntegration, canciaIntegration as default, fetchCMSData, makeR2UploadHandler, makeUseTranslations };
|
package/dist/index.js
CHANGED
|
@@ -3,21 +3,27 @@ import {
|
|
|
3
3
|
} from "./chunk-22DJVJBR.js";
|
|
4
4
|
import {
|
|
5
5
|
makeSchemasRoute
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-KL2YMBFV.js";
|
|
7
7
|
import "./chunk-NG5GJME5.js";
|
|
8
8
|
import {
|
|
9
9
|
setCanciaRuntime
|
|
10
10
|
} from "./chunk-DGCGIEFD.js";
|
|
11
11
|
import {
|
|
12
|
+
defineField,
|
|
12
13
|
defineList,
|
|
13
14
|
describeList,
|
|
14
15
|
z
|
|
15
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-RKHJNQ6R.js";
|
|
17
|
+
import {
|
|
18
|
+
canciaLoader
|
|
19
|
+
} from "./chunk-337LJIKX.js";
|
|
20
|
+
import {
|
|
21
|
+
createSQLiteAdapter
|
|
22
|
+
} from "./chunk-AE4SIY24.js";
|
|
16
23
|
import {
|
|
17
|
-
canciaLoader,
|
|
18
24
|
createJsonFileAdapter,
|
|
19
25
|
createJsonFileAdapterV2
|
|
20
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-ST44VULL.js";
|
|
21
27
|
import {
|
|
22
28
|
RevConflictError
|
|
23
29
|
} from "./chunk-7IA5B5CF.js";
|
|
@@ -634,66 +640,6 @@ function makeUseTranslations(ui, defaultLang) {
|
|
|
634
640
|
};
|
|
635
641
|
};
|
|
636
642
|
}
|
|
637
|
-
|
|
638
|
-
// src/storage/sqlite.ts
|
|
639
|
-
import { createRequire } from "module";
|
|
640
|
-
var require2 = createRequire(import.meta.url);
|
|
641
|
-
var _db = null;
|
|
642
|
-
function createDB(dbPath) {
|
|
643
|
-
const Database = require2("better-sqlite3");
|
|
644
|
-
const sqlite = new Database(dbPath);
|
|
645
|
-
sqlite.pragma("journal_mode = WAL");
|
|
646
|
-
sqlite.exec(`
|
|
647
|
-
CREATE TABLE IF NOT EXISTS cancia_content (
|
|
648
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
649
|
-
site TEXT NOT NULL,
|
|
650
|
-
key TEXT NOT NULL,
|
|
651
|
-
lang TEXT NOT NULL,
|
|
652
|
-
value TEXT NOT NULL,
|
|
653
|
-
updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
654
|
-
UNIQUE(site, key, lang)
|
|
655
|
-
)
|
|
656
|
-
`);
|
|
657
|
-
return {
|
|
658
|
-
get: sqlite.prepare(
|
|
659
|
-
"SELECT value FROM cancia_content WHERE site=? AND key=? AND lang=?"
|
|
660
|
-
),
|
|
661
|
-
set: sqlite.prepare(
|
|
662
|
-
`INSERT INTO cancia_content (site, key, lang, value, updated_at)
|
|
663
|
-
VALUES (?, ?, ?, ?, unixepoch())
|
|
664
|
-
ON CONFLICT(site, key, lang) DO UPDATE SET value=excluded.value, updated_at=unixepoch()`
|
|
665
|
-
),
|
|
666
|
-
getAll: sqlite.prepare(
|
|
667
|
-
"SELECT key, lang, value FROM cancia_content WHERE site=?"
|
|
668
|
-
),
|
|
669
|
-
delete: sqlite.prepare(
|
|
670
|
-
"DELETE FROM cancia_content WHERE site=? AND key=? AND lang=?"
|
|
671
|
-
)
|
|
672
|
-
};
|
|
673
|
-
}
|
|
674
|
-
function getDB(dbPath) {
|
|
675
|
-
if (!_db) _db = createDB(dbPath);
|
|
676
|
-
return _db;
|
|
677
|
-
}
|
|
678
|
-
function createSQLiteAdapter(dbPath) {
|
|
679
|
-
const path = dbPath ?? process.cwd() + "/cancia.db";
|
|
680
|
-
return {
|
|
681
|
-
async get(site, key, lang) {
|
|
682
|
-
const row = getDB(path).get.get(site, key, lang);
|
|
683
|
-
return row?.value ?? null;
|
|
684
|
-
},
|
|
685
|
-
async set(site, key, lang, value) {
|
|
686
|
-
getDB(path).set.run(site, key, lang, value);
|
|
687
|
-
},
|
|
688
|
-
async getAll(site) {
|
|
689
|
-
const rows = getDB(path).getAll.all(site);
|
|
690
|
-
return Object.fromEntries(rows.map((r) => [`${r.key}.${r.lang}`, r.value]));
|
|
691
|
-
},
|
|
692
|
-
async delete(site, key, lang) {
|
|
693
|
-
getDB(path).delete.run(site, key, lang);
|
|
694
|
-
}
|
|
695
|
-
};
|
|
696
|
-
}
|
|
697
643
|
export {
|
|
698
644
|
RevConflictError,
|
|
699
645
|
canciaIntegration,
|
|
@@ -702,6 +648,7 @@ export {
|
|
|
702
648
|
createJsonFileAdapterV2,
|
|
703
649
|
createSQLiteAdapter,
|
|
704
650
|
canciaIntegration as default,
|
|
651
|
+
defineField,
|
|
705
652
|
defineList,
|
|
706
653
|
describeList,
|
|
707
654
|
fetchCMSData,
|
package/dist/loader/index.js
CHANGED
package/dist/schema/index.d.ts
CHANGED
|
@@ -15,20 +15,43 @@ export { z } from 'zod';
|
|
|
15
15
|
* z.boolean() → "checkbox"
|
|
16
16
|
* z.enum([...]) → "select"
|
|
17
17
|
*/
|
|
18
|
-
type FieldWidget = "text" | "textarea" | "url" | "email" | "datetime" | "number" | "checkbox" | "select" | "image";
|
|
18
|
+
type FieldWidget = "text" | "textarea" | "url" | "email" | "datetime" | "number" | "checkbox" | "select" | "image" | "slug";
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
20
|
+
* Fields common to every widget's metadata. All optional.
|
|
21
21
|
*/
|
|
22
|
-
interface
|
|
22
|
+
interface FieldMetaBase {
|
|
23
23
|
/** Display label for the field. Defaults to humanised field name. */
|
|
24
24
|
label?: string;
|
|
25
25
|
/** Help text shown under the input. */
|
|
26
26
|
description?: string;
|
|
27
|
-
/** Override widget choice. */
|
|
28
|
-
widget?: FieldWidget;
|
|
29
27
|
/** Placeholder text in the input. */
|
|
30
28
|
placeholder?: string;
|
|
31
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Metadata attached to a Zod field via `.meta({...})`.
|
|
32
|
+
*
|
|
33
|
+
* This is a widget-keyed discriminated union: each widget variant only
|
|
34
|
+
* permits the options that are legal for it. It exists at the type level
|
|
35
|
+
* only — Zod's `.meta()` accepts any object (its `GlobalMeta` carries an
|
|
36
|
+
* index signature), so this union is what `defineField` uses to type its
|
|
37
|
+
* per-widget option parameters. Passing an illegal option to a
|
|
38
|
+
* `defineField.*` helper is therefore a compile error, while bare
|
|
39
|
+
* `z.string().meta({ widget: "textarea" })` calls stay valid.
|
|
40
|
+
*
|
|
41
|
+
* Later plans extend this union with array / reference / richtext variants.
|
|
42
|
+
*/
|
|
43
|
+
type FieldMeta = (FieldMetaBase & {
|
|
44
|
+
widget?: "text" | "textarea" | "url" | "email" | "number" | "checkbox" | "datetime";
|
|
45
|
+
}) | (FieldMetaBase & {
|
|
46
|
+
widget: "select";
|
|
47
|
+
options?: string[];
|
|
48
|
+
}) | (FieldMetaBase & {
|
|
49
|
+
widget: "image";
|
|
50
|
+
alt?: boolean;
|
|
51
|
+
}) | (FieldMetaBase & {
|
|
52
|
+
widget: "slug";
|
|
53
|
+
source?: string;
|
|
54
|
+
});
|
|
32
55
|
interface ListSchemaOptions<TFields extends Record<string, z.ZodTypeAny>> {
|
|
33
56
|
/** Display label for the list (e.g. "Blog Posts"). */
|
|
34
57
|
label: string;
|
|
@@ -79,7 +102,53 @@ interface FieldDescription {
|
|
|
79
102
|
/** For number widgets: min/max. */
|
|
80
103
|
min?: number;
|
|
81
104
|
max?: number;
|
|
105
|
+
/** For string widgets: a regex pattern (source, no delimiters) the value must match. */
|
|
106
|
+
pattern?: string;
|
|
107
|
+
/** For slug widgets: the field name to auto-generate the slug from. */
|
|
108
|
+
source?: string;
|
|
82
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* Typed field constructors. Each returns the underlying Zod type with a
|
|
112
|
+
* correctly-typed `.meta()` already attached, so schemas read declaratively:
|
|
113
|
+
*
|
|
114
|
+
* import { defineField as f } from "@cancia/astro/schema";
|
|
115
|
+
*
|
|
116
|
+
* fields: {
|
|
117
|
+
* title: f.text({ label: "Title" }),
|
|
118
|
+
* slug: f.slug({ source: "title", label: "URL slug" }),
|
|
119
|
+
* status: f.select({ options: ["draft", "published"], label: "Status" }),
|
|
120
|
+
* }
|
|
121
|
+
*
|
|
122
|
+
* The option parameter of each helper is derived from the matching FieldMeta
|
|
123
|
+
* variant, so illegal options (e.g. passing `source` to `text`) are compile
|
|
124
|
+
* errors. `defineField` is purely additive — bare `z.string().meta({...})`
|
|
125
|
+
* declarations keep working unchanged.
|
|
126
|
+
*/
|
|
127
|
+
/** Options accepted by the plain string/number/etc. widgets (no extras). */
|
|
128
|
+
type PlainFieldOptions = FieldMetaBase;
|
|
129
|
+
declare const defineField: {
|
|
130
|
+
text: (o?: PlainFieldOptions) => z.ZodString;
|
|
131
|
+
textarea: (o?: PlainFieldOptions) => z.ZodString;
|
|
132
|
+
url: (o?: PlainFieldOptions) => z.ZodString;
|
|
133
|
+
email: (o?: PlainFieldOptions) => z.ZodString;
|
|
134
|
+
datetime: (o?: PlainFieldOptions) => z.ZodString;
|
|
135
|
+
checkbox: (o?: PlainFieldOptions) => z.ZodBoolean;
|
|
136
|
+
number: (o?: PlainFieldOptions & {
|
|
137
|
+
min?: number;
|
|
138
|
+
max?: number;
|
|
139
|
+
}) => z.ZodNumber;
|
|
140
|
+
slug: (o: FieldMetaBase & {
|
|
141
|
+
source?: string;
|
|
142
|
+
}) => z.ZodString;
|
|
143
|
+
image: (o?: FieldMetaBase & {
|
|
144
|
+
alt?: boolean;
|
|
145
|
+
}) => z.ZodString;
|
|
146
|
+
select: (o: FieldMetaBase & {
|
|
147
|
+
options: string[];
|
|
148
|
+
}) => z.ZodEnum<{
|
|
149
|
+
[x: string]: string;
|
|
150
|
+
}>;
|
|
151
|
+
};
|
|
83
152
|
interface ListDescription {
|
|
84
153
|
name: string;
|
|
85
154
|
label: string;
|
|
@@ -93,4 +162,4 @@ declare function describeList(name: string, schema: ListSchema): ListDescription
|
|
|
93
162
|
/** Map of list name → schema. What users export from src/cms/schemas.ts. */
|
|
94
163
|
type SchemasModule = Record<string, ListSchema>;
|
|
95
164
|
|
|
96
|
-
export { type FieldDescription, type FieldMeta, type FieldWidget, type ListDescription, type ListSchema, type ListSchemaOptions, type SchemasModule, defineList, describeList };
|
|
165
|
+
export { type FieldDescription, type FieldMeta, type FieldMetaBase, type FieldWidget, type ListDescription, type ListSchema, type ListSchemaOptions, type SchemasModule, defineField, defineList, describeList };
|
package/dist/schema/index.js
CHANGED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { C as CanciaStorage, a as CanciaStorageV2 } from '../types-BMlLS-OS.js';
|
|
2
|
+
export { b as CanciaKVStore, c as CanciaListStore, d as CanciaPageStore, L as ListEntry, P as PageMeta, e as PageRecord, f as PageSEO, R as Rev, g as RevConflictError } from '../types-BMlLS-OS.js';
|
|
3
|
+
|
|
4
|
+
declare function createJsonFileAdapter(filePath?: string): CanciaStorage;
|
|
5
|
+
|
|
6
|
+
interface JsonFileV2Options {
|
|
7
|
+
/** Project root. Defaults to process.cwd(). */
|
|
8
|
+
projectRoot?: string;
|
|
9
|
+
/** Override the KV file path. Defaults to <root>/cancia-content.json. */
|
|
10
|
+
kvPath?: string;
|
|
11
|
+
/** Override the pages file path. Defaults to <root>/.cancia/pages.json. */
|
|
12
|
+
pagesPath?: string;
|
|
13
|
+
/** Override the lists directory. Defaults to <root>/.cancia/lists. */
|
|
14
|
+
listsDir?: string;
|
|
15
|
+
}
|
|
16
|
+
declare function createJsonFileAdapterV2(opts?: JsonFileV2Options): CanciaStorageV2;
|
|
17
|
+
|
|
18
|
+
declare function createSQLiteAdapter(dbPath?: string): CanciaStorage;
|
|
19
|
+
|
|
20
|
+
export { CanciaStorage, CanciaStorageV2, createJsonFileAdapter, createJsonFileAdapterV2, createSQLiteAdapter };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createSQLiteAdapter
|
|
3
|
+
} from "../chunk-AE4SIY24.js";
|
|
4
|
+
import {
|
|
5
|
+
createJsonFileAdapter,
|
|
6
|
+
createJsonFileAdapterV2
|
|
7
|
+
} from "../chunk-ST44VULL.js";
|
|
8
|
+
import {
|
|
9
|
+
RevConflictError
|
|
10
|
+
} from "../chunk-7IA5B5CF.js";
|
|
11
|
+
export {
|
|
12
|
+
RevConflictError,
|
|
13
|
+
createJsonFileAdapter,
|
|
14
|
+
createJsonFileAdapterV2,
|
|
15
|
+
createSQLiteAdapter
|
|
16
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cancia/astro",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "Astro integration for Cancia CMS — inline editing with zero separate server",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -23,6 +23,10 @@
|
|
|
23
23
|
"./loader": {
|
|
24
24
|
"types": "./dist/loader/index.d.ts",
|
|
25
25
|
"import": "./dist/loader/index.js"
|
|
26
|
+
},
|
|
27
|
+
"./storage": {
|
|
28
|
+
"types": "./dist/storage/index.d.ts",
|
|
29
|
+
"import": "./dist/storage/index.js"
|
|
26
30
|
}
|
|
27
31
|
},
|
|
28
32
|
"files": [
|
|
@@ -46,13 +50,15 @@
|
|
|
46
50
|
"better-sqlite3": "^11.0.0",
|
|
47
51
|
"tsup": "^8.0.0",
|
|
48
52
|
"typescript": "^5.4.0",
|
|
49
|
-
"vite": "^8.0.0"
|
|
53
|
+
"vite": "^8.0.0",
|
|
54
|
+
"vitest": "^4.1.9"
|
|
50
55
|
},
|
|
51
56
|
"dependencies": {
|
|
52
57
|
"zod": "^4.4.3"
|
|
53
58
|
},
|
|
54
59
|
"scripts": {
|
|
55
60
|
"build": "tsup",
|
|
56
|
-
"typecheck": "tsc --noEmit"
|
|
61
|
+
"typecheck": "tsc --noEmit",
|
|
62
|
+
"test": "vitest run"
|
|
57
63
|
}
|
|
58
64
|
}
|