@cancia/astro 0.0.1 → 0.1.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/chunk-337LJIKX.js +76 -0
- package/dist/chunk-AE4SIY24.js +63 -0
- package/dist/chunk-BOIQNZAO.js +64 -0
- package/dist/{chunk-YPVZDWTW.js → chunk-IIGDU5SV.js} +1 -1
- package/dist/chunk-MCHQV6Y7.js +155 -0
- package/dist/{chunk-YQDZQSES.js → chunk-ST44VULL.js} +4 -78
- package/dist/endpoints/schemas.js +3 -2
- package/dist/index.d.ts +4 -18
- package/dist/index.js +12 -64
- package/dist/loader/index.js +2 -1
- package/dist/portable-text-BikSqS9T.d.ts +117 -0
- package/dist/richtext/CanciaRichText.astro +44 -0
- package/dist/richtext/Link.astro +25 -0
- package/dist/richtext/index.d.ts +23 -0
- package/dist/richtext/index.js +142 -0
- package/dist/schema/index.d.ts +173 -6
- package/dist/schema/index.js +19 -1
- package/dist/storage/index.d.ts +20 -0
- package/dist/storage/index.js +16 -0
- package/package.json +16 -4
- package/dist/chunk-QAM5VKAF.js +0 -73
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// src/schema/portable-text.ts
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
var PT_STYLES = ["normal", "h2", "h3", "blockquote"];
|
|
4
|
+
var PT_LIST_ITEMS = ["bullet", "number"];
|
|
5
|
+
var PT_DECORATORS = ["strong", "em"];
|
|
6
|
+
function isSafeHref(href) {
|
|
7
|
+
if (typeof href !== "string") return false;
|
|
8
|
+
const trimmed = href.trim();
|
|
9
|
+
if (trimmed === "") return false;
|
|
10
|
+
if (/^(https?:|mailto:|tel:)/i.test(trimmed)) return true;
|
|
11
|
+
const schemeMatch = /^([a-z][a-z0-9+.-]*):/i.exec(trimmed);
|
|
12
|
+
if (schemeMatch) {
|
|
13
|
+
const beforeColon = trimmed.slice(0, schemeMatch[1].length);
|
|
14
|
+
const firstSep = trimmed.search(/[/?#]/);
|
|
15
|
+
const colonIndex = beforeColon.length;
|
|
16
|
+
if (firstSep === -1 || colonIndex < firstSep) return false;
|
|
17
|
+
}
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
var ptLinkMarkDefSchema = z.object({
|
|
21
|
+
_type: z.literal("link"),
|
|
22
|
+
_key: z.string(),
|
|
23
|
+
href: z.string().refine(isSafeHref, "unsafe or unsupported URL scheme")
|
|
24
|
+
});
|
|
25
|
+
var ptSpanSchema = z.object({
|
|
26
|
+
_type: z.literal("span"),
|
|
27
|
+
_key: z.string(),
|
|
28
|
+
text: z.string(),
|
|
29
|
+
marks: z.array(z.string())
|
|
30
|
+
});
|
|
31
|
+
var ptBlockSchema = z.object({
|
|
32
|
+
_type: z.literal("block"),
|
|
33
|
+
_key: z.string(),
|
|
34
|
+
style: z.enum(PT_STYLES),
|
|
35
|
+
listItem: z.enum(PT_LIST_ITEMS).optional(),
|
|
36
|
+
level: z.number().int().positive().optional(),
|
|
37
|
+
markDefs: z.array(ptLinkMarkDefSchema).default([]),
|
|
38
|
+
children: z.array(ptSpanSchema)
|
|
39
|
+
}).superRefine((block, ctx) => {
|
|
40
|
+
const defKeys = new Set(block.markDefs.map((d) => d._key));
|
|
41
|
+
const decorators = new Set(PT_DECORATORS);
|
|
42
|
+
for (const span of block.children) {
|
|
43
|
+
for (const mark of span.marks) {
|
|
44
|
+
if (decorators.has(mark)) continue;
|
|
45
|
+
if (!defKeys.has(mark)) {
|
|
46
|
+
ctx.addIssue({
|
|
47
|
+
code: "custom",
|
|
48
|
+
message: `Mark "${mark}" is not an allowed decorator and does not reference a link markDef`,
|
|
49
|
+
path: ["children"]
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
var portableTextSubsetSchema = z.array(ptBlockSchema);
|
|
56
|
+
|
|
57
|
+
export {
|
|
58
|
+
PT_STYLES,
|
|
59
|
+
PT_LIST_ITEMS,
|
|
60
|
+
PT_DECORATORS,
|
|
61
|
+
isSafeHref,
|
|
62
|
+
ptBlockSchema,
|
|
63
|
+
portableTextSubsetSchema
|
|
64
|
+
};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import {
|
|
2
|
+
portableTextSubsetSchema
|
|
3
|
+
} from "./chunk-BOIQNZAO.js";
|
|
4
|
+
|
|
5
|
+
// src/schema/index.ts
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
function defineList(opts) {
|
|
8
|
+
return {
|
|
9
|
+
label: opts.label,
|
|
10
|
+
labelSingular: opts.labelSingular ?? opts.label.replace(/s$/, ""),
|
|
11
|
+
titleField: opts.titleField,
|
|
12
|
+
bodyField: opts.bodyField,
|
|
13
|
+
slugField: opts.slugField,
|
|
14
|
+
fields: opts.fields,
|
|
15
|
+
validator: z.object(opts.fields)
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
var defineField = {
|
|
19
|
+
text: (o) => z.string().meta({ widget: "text", ...o }),
|
|
20
|
+
textarea: (o) => z.string().meta({ widget: "textarea", ...o }),
|
|
21
|
+
url: (o) => z.string().url().meta({ widget: "url", ...o }),
|
|
22
|
+
email: (o) => z.string().email().meta({ widget: "email", ...o }),
|
|
23
|
+
datetime: (o) => z.string().datetime().meta({ widget: "datetime", ...o }),
|
|
24
|
+
checkbox: (o) => z.boolean().meta({ widget: "checkbox", ...o }),
|
|
25
|
+
number: (o) => {
|
|
26
|
+
let n = z.number();
|
|
27
|
+
if (o?.min != null) n = n.min(o.min);
|
|
28
|
+
if (o?.max != null) n = n.max(o.max);
|
|
29
|
+
return n.meta({ widget: "number", ...o });
|
|
30
|
+
},
|
|
31
|
+
slug: (o) => z.string().regex(/^[a-z0-9-]+$/).meta({ widget: "slug", ...o }),
|
|
32
|
+
image: (o) => z.string().url().meta({ widget: "image", ...o }),
|
|
33
|
+
select: (o) => z.enum(o.options).meta({ widget: "select", ...o }),
|
|
34
|
+
/**
|
|
35
|
+
* A repeatable list of a single member type. `member` is any Zod type,
|
|
36
|
+
* typically another `defineField.*` (so it keeps its own `.meta({ widget })`
|
|
37
|
+
* and recursion carries labels/widgets into each row).
|
|
38
|
+
*
|
|
39
|
+
* bullets: f.array(f.text({ label: "Point" }), { label: "Key points" }),
|
|
40
|
+
* faqs: f.array(f.object({ q: f.text(), a: f.textarea() })),
|
|
41
|
+
*/
|
|
42
|
+
array: (member, o) => z.array(member).meta({ widget: "array", ...o }),
|
|
43
|
+
/**
|
|
44
|
+
* A nested group of named sub-fields, rendered as a collapsible fieldset.
|
|
45
|
+
*
|
|
46
|
+
* socials: f.object({ linkedin: f.url(), twitter: f.url() }, { label: "Socials" }),
|
|
47
|
+
*/
|
|
48
|
+
object: (shape, o) => z.object(shape).meta({ widget: "object", ...o }),
|
|
49
|
+
/**
|
|
50
|
+
* A pointer to an entry in another list. Stored as a plain string — the
|
|
51
|
+
* target entry's id. Per the v0.1.0 design (D5) there is NO integrity
|
|
52
|
+
* backend: dangling ids are tolerated and surfaced by the editor, never
|
|
53
|
+
* prevented. `list` is the target list's name (a key in the schemas module).
|
|
54
|
+
*
|
|
55
|
+
* author: f.reference({ list: "people", label: "Author" }),
|
|
56
|
+
* related: f.array(f.reference({ list: "posts" }), { label: "Related" }),
|
|
57
|
+
*/
|
|
58
|
+
reference: (o) => z.string().meta({ widget: "reference", ...o }),
|
|
59
|
+
/**
|
|
60
|
+
* A constrained rich-text body. Stored as a Portable-Text SUBSET array (D4):
|
|
61
|
+
* block styles normal/h2/h3/blockquote, bullet/number lists, strong/em marks,
|
|
62
|
+
* and link annotations — nothing else. The toolbar renders a structured block
|
|
63
|
+
* editor (approach B) over it, and `<CanciaRichText>` renders it to HTML.
|
|
64
|
+
*
|
|
65
|
+
* body: f.richtext({ label: "Body" }),
|
|
66
|
+
*
|
|
67
|
+
* The JSON-Schema type of this field is "array"; the explicit
|
|
68
|
+
* `widget: "richtext"` meta is what distinguishes it from a plain array in
|
|
69
|
+
* describeProperty.
|
|
70
|
+
*/
|
|
71
|
+
richtext: (o) => portableTextSubsetSchema.meta({ widget: "richtext", ...o })
|
|
72
|
+
};
|
|
73
|
+
function slugify(input) {
|
|
74
|
+
return input.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
|
|
75
|
+
}
|
|
76
|
+
function humanise(name) {
|
|
77
|
+
return name.replace(/([A-Z])/g, " $1").replace(/[_-]/g, " ").replace(/^\w/, (c) => c.toUpperCase()).trim();
|
|
78
|
+
}
|
|
79
|
+
function inferWidget(prop, label) {
|
|
80
|
+
if (prop.widget) return prop.widget;
|
|
81
|
+
if (prop.type === "array") return "array";
|
|
82
|
+
if (prop.type === "object") return "object";
|
|
83
|
+
if (prop.enum) return "select";
|
|
84
|
+
if (prop.type === "boolean") return "checkbox";
|
|
85
|
+
if (prop.type === "number" || prop.type === "integer") return "number";
|
|
86
|
+
if (prop.type === "string") {
|
|
87
|
+
if (prop.format === "uri" || prop.format === "url") return "url";
|
|
88
|
+
if (prop.format === "email") return "email";
|
|
89
|
+
if (prop.format === "date-time" || prop.format === "date") return "datetime";
|
|
90
|
+
if (label.toLowerCase() === "body" || label.toLowerCase() === "content") return "textarea";
|
|
91
|
+
if (prop.maxLength && prop.maxLength > 200) return "textarea";
|
|
92
|
+
return "text";
|
|
93
|
+
}
|
|
94
|
+
return "text";
|
|
95
|
+
}
|
|
96
|
+
function describeProperty(name, prop, required) {
|
|
97
|
+
const label = prop.label ?? humanise(name);
|
|
98
|
+
const desc = {
|
|
99
|
+
name,
|
|
100
|
+
label,
|
|
101
|
+
widget: inferWidget(prop, label),
|
|
102
|
+
required
|
|
103
|
+
};
|
|
104
|
+
if (prop.description) desc.description = prop.description;
|
|
105
|
+
if (prop.placeholder) desc.placeholder = prop.placeholder;
|
|
106
|
+
if (prop.minLength !== void 0) desc.minLength = prop.minLength;
|
|
107
|
+
if (prop.maxLength !== void 0) desc.maxLength = prop.maxLength;
|
|
108
|
+
if (prop.minimum !== void 0) desc.min = prop.minimum;
|
|
109
|
+
if (prop.maximum !== void 0) desc.max = prop.maximum;
|
|
110
|
+
if (prop.enum) desc.options = prop.enum;
|
|
111
|
+
else if (prop.options) desc.options = prop.options;
|
|
112
|
+
if (prop.pattern !== void 0) desc.pattern = prop.pattern;
|
|
113
|
+
if (prop.source !== void 0) desc.source = prop.source;
|
|
114
|
+
if (desc.widget === "reference" && prop.list !== void 0) desc.referenceList = prop.list;
|
|
115
|
+
if (desc.widget === "richtext") {
|
|
116
|
+
return desc;
|
|
117
|
+
}
|
|
118
|
+
if (desc.widget === "array" && prop.items) {
|
|
119
|
+
desc.of = describeProperty("", prop.items, true);
|
|
120
|
+
}
|
|
121
|
+
if (desc.widget === "object" && prop.properties) {
|
|
122
|
+
const subRequired = new Set(prop.required ?? []);
|
|
123
|
+
desc.fields = Object.keys(prop.properties).map(
|
|
124
|
+
(subName) => describeProperty(subName, prop.properties[subName], subRequired.has(subName))
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
return desc;
|
|
128
|
+
}
|
|
129
|
+
function describeList(name, schema) {
|
|
130
|
+
const json = z.toJSONSchema(schema.validator, {
|
|
131
|
+
metadata: z.globalRegistry
|
|
132
|
+
});
|
|
133
|
+
const properties = json.properties ?? {};
|
|
134
|
+
const requiredSet = new Set(json.required ?? []);
|
|
135
|
+
const fields = Object.keys(schema.fields).map(
|
|
136
|
+
(fieldName) => describeProperty(fieldName, properties[fieldName] ?? {}, requiredSet.has(fieldName))
|
|
137
|
+
);
|
|
138
|
+
return {
|
|
139
|
+
name,
|
|
140
|
+
label: schema.label,
|
|
141
|
+
labelSingular: schema.labelSingular,
|
|
142
|
+
titleField: schema.titleField,
|
|
143
|
+
bodyField: schema.bodyField,
|
|
144
|
+
slugField: schema.slugField,
|
|
145
|
+
fields
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export {
|
|
150
|
+
z,
|
|
151
|
+
defineList,
|
|
152
|
+
defineField,
|
|
153
|
+
slugify,
|
|
154
|
+
describeList
|
|
155
|
+
};
|
|
@@ -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,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
makeSchemasRoute
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-IIGDU5SV.js";
|
|
4
4
|
import "../chunk-NG5GJME5.js";
|
|
5
|
-
import "../chunk-
|
|
5
|
+
import "../chunk-MCHQV6Y7.js";
|
|
6
|
+
import "../chunk-BOIQNZAO.js";
|
|
6
7
|
|
|
7
8
|
// src/endpoints/schemas.ts
|
|
8
9
|
import { getCanciaRuntime } from "virtual:cancia/runtime";
|
package/dist/index.d.ts
CHANGED
|
@@ -4,9 +4,11 @@ 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';
|
|
11
|
+
import './portable-text-BikSqS9T.js';
|
|
10
12
|
|
|
11
13
|
interface R2UploadHandlerOptions {
|
|
12
14
|
/** Cloudflare account ID (from R2 dashboard) */
|
|
@@ -126,20 +128,4 @@ declare function fetchCMSData(opts: {
|
|
|
126
128
|
*/
|
|
127
129
|
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
130
|
|
|
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 };
|
|
131
|
+
export { type CMSData, type CanciaIntegrationOptions, CanciaStorage, CanciaStorageV2, type R2UploadHandlerOptions, UploadHandler, canciaIntegration, canciaIntegration as default, fetchCMSData, makeR2UploadHandler, makeUseTranslations };
|
package/dist/index.js
CHANGED
|
@@ -3,24 +3,31 @@ import {
|
|
|
3
3
|
} from "./chunk-22DJVJBR.js";
|
|
4
4
|
import {
|
|
5
5
|
makeSchemasRoute
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-IIGDU5SV.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-MCHQV6Y7.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";
|
|
30
|
+
import "./chunk-BOIQNZAO.js";
|
|
24
31
|
import {
|
|
25
32
|
detectImageType,
|
|
26
33
|
isValidSite
|
|
@@ -634,66 +641,6 @@ function makeUseTranslations(ui, defaultLang) {
|
|
|
634
641
|
};
|
|
635
642
|
};
|
|
636
643
|
}
|
|
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
644
|
export {
|
|
698
645
|
RevConflictError,
|
|
699
646
|
canciaIntegration,
|
|
@@ -702,6 +649,7 @@ export {
|
|
|
702
649
|
createJsonFileAdapterV2,
|
|
703
650
|
createSQLiteAdapter,
|
|
704
651
|
canciaIntegration as default,
|
|
652
|
+
defineField,
|
|
705
653
|
defineList,
|
|
706
654
|
describeList,
|
|
707
655
|
fetchCMSData,
|