@cancia/astro 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -166,10 +166,20 @@ function setBakedConfig(config) {
166
166
  function setCanciaRuntime(runtime) {
167
167
  _runtime = runtime;
168
168
  }
169
+ function __resetRuntimeForTests() {
170
+ _runtime = null;
171
+ _bakedConfig = null;
172
+ }
169
173
  function buildRuntimeFromBaked(baked) {
170
174
  const projectRoot = baked.projectRoot || process.cwd();
171
175
  const token = process.env.CANCIA_TOKEN?.trim() || "";
172
- const secret = baked.public ? void 0 : token || void 0;
176
+ const isPublic = baked.public === true;
177
+ if (!isPublic && !token) {
178
+ throw new Error(
179
+ "[cancia] CANCIA_TOKEN is not set but this site is not public \u2014 refusing to start to avoid serving an open, unauthenticated mutation API. Set CANCIA_TOKEN in the deployed process, or set public:true if anonymous editing is intended."
180
+ );
181
+ }
182
+ const secret = isPublic ? void 0 : token;
173
183
  const redeployHook = baked.redeployHook || void 0;
174
184
  const deployHook = redeployHook || process.env.CANCIA_DEPLOY_HOOK || void 0;
175
185
  const deployHookToken = process.env.CANCIA_DEPLOY_HOOK_TOKEN || void 0;
@@ -261,5 +271,6 @@ export {
261
271
  makeR2UploadHandler,
262
272
  setBakedConfig,
263
273
  setCanciaRuntime,
274
+ __resetRuntimeForTests,
264
275
  getCanciaRuntime
265
276
  };
File without changes
@@ -0,0 +1,60 @@
1
+ /** Flat content map as returned by /api/cancia/content and kv.getAll: "key.lang" → value. */
2
+ type ContentMap = Record<string, string>;
3
+ /** A translator: `(key, fallback) => cms[`${key}.${lang}`] ?? fallback`. */
4
+ type Translate = (key: string, fallback: string) => string;
5
+ interface ContentReaderOptions {
6
+ /** Site id (the `?site=` query param and the storage `site` column). */
7
+ site: string;
8
+ /**
9
+ * Build-time content API base. Default: `process.env.CANCIA_CONTENT_URL`.
10
+ * When set (non-empty), `getCMS()` FETCHES from
11
+ * `${url}/api/cancia/content?site=<site>` instead of reading the DB directly.
12
+ * This is how a static build on a runtime-only volume gets live content.
13
+ */
14
+ url?: string;
15
+ /**
16
+ * Bearer token for the fetch. Default: `process.env.CANCIA_TOKEN`. Required by
17
+ * a non-public site (036 fail-closed) — without it the fetch 401s and the
18
+ * reader falls back to the direct DB read.
19
+ */
20
+ token?: string;
21
+ /**
22
+ * Direct-DB fallback path. Default:
23
+ * `process.env.CANCIA_DB_PATH ?? "<cwd>/cancia.db"`.
24
+ * Ignored when a `readDb` override is supplied.
25
+ */
26
+ dbPath?: string;
27
+ /** Default language for `makeT`/`get`. Default: `"en"`. */
28
+ lang?: string;
29
+ /** Injectable fetch (tests). Default: global `fetch`. */
30
+ fetch?: typeof fetch;
31
+ /**
32
+ * Optional DB-read override. Lets a non-sqlite project (e.g. json-file
33
+ * storage) supply its own fallback WITHOUT this reader importing every
34
+ * adapter. Default: read via `createSqliteAdapterV2({ dbPath })` (the
35
+ * recommended backend).
36
+ */
37
+ readDb?: () => Promise<ContentMap>;
38
+ }
39
+ interface ContentReader {
40
+ /**
41
+ * Read all content for the site as a flat `"key.lang" → value` map.
42
+ * Chain: fetch (when `url` set) → direct DB read → `{}`. NEVER throws.
43
+ */
44
+ getCMS(): Promise<ContentMap>;
45
+ /** Build a translator over a content map for the reader's `lang`. */
46
+ makeT(cms: ContentMap): Translate;
47
+ /** Sugar: `const { t, cms } = await reader.get()` in a page's frontmatter. */
48
+ get(): Promise<{
49
+ t: Translate;
50
+ cms: ContentMap;
51
+ }>;
52
+ }
53
+ /**
54
+ * Create a content reader. Reads are transport-agnostic (network OR local file)
55
+ * and never throw — a static build reads content the same way whether it's a
56
+ * local DB, the site's own API, or a future hosted Cancia DB.
57
+ */
58
+ declare function createContentReader(opts: ContentReaderOptions): ContentReader;
59
+
60
+ export { type ContentMap, type ContentReader, type ContentReaderOptions, type Translate, createContentReader };
@@ -0,0 +1,52 @@
1
+ import "./chunk-FOSOWSXV.js";
2
+ import "./chunk-CJDIVWO3.js";
3
+ import {
4
+ createSqliteAdapterV2
5
+ } from "./chunk-GNWD7EL2.js";
6
+ import "./chunk-U7V53JX7.js";
7
+ import "./chunk-L2VKQJPY.js";
8
+ import "./chunk-7IA5B5CF.js";
9
+
10
+ // src/content.ts
11
+ function createContentReader(opts) {
12
+ const site = opts.site;
13
+ const lang = opts.lang ?? "en";
14
+ const url = opts.url ?? process.env.CANCIA_CONTENT_URL;
15
+ const token = opts.token ?? process.env.CANCIA_TOKEN;
16
+ const fetchImpl = opts.fetch ?? globalThis.fetch;
17
+ function readDb() {
18
+ if (opts.readDb) return opts.readDb();
19
+ const dbPath = opts.dbPath ?? process.env.CANCIA_DB_PATH ?? `${process.cwd()}/cancia.db`;
20
+ return createSqliteAdapterV2({ dbPath }).kv.getAll(site);
21
+ }
22
+ async function fetchFromApi(base) {
23
+ const endpoint = `${base.replace(/\/+$/, "")}/api/cancia/content?site=${encodeURIComponent(site)}`;
24
+ const res = await fetchImpl(endpoint, {
25
+ headers: token ? { Authorization: `Bearer ${token}` } : {}
26
+ });
27
+ if (!res.ok) throw new Error(`Content fetch failed: ${res.status}`);
28
+ return await res.json();
29
+ }
30
+ async function getCMS() {
31
+ try {
32
+ return url ? await fetchFromApi(url) : await readDb();
33
+ } catch {
34
+ try {
35
+ return await readDb();
36
+ } catch {
37
+ return {};
38
+ }
39
+ }
40
+ }
41
+ function makeT(cms) {
42
+ return (key, fallback) => cms[`${key}.${lang}`] ?? fallback;
43
+ }
44
+ async function get() {
45
+ const cms = await getCMS();
46
+ return { t: makeT(cms), cms };
47
+ }
48
+ return { getCMS, makeT, get };
49
+ }
50
+ export {
51
+ createContentReader
52
+ };
@@ -1,13 +1,13 @@
1
- import {
2
- makeListsRoutes
3
- } from "../chunk-22DJVJBR.js";
4
- import "../chunk-NG5GJME5.js";
5
- import "../chunk-7IA5B5CF.js";
6
1
  import {
7
2
  extractRoute,
8
3
  invalidateOnSave
9
4
  } from "../chunk-VGRG5DN7.js";
5
+ import {
6
+ makeListsRoutes
7
+ } from "../chunk-22DJVJBR.js";
8
+ import "../chunk-NG5GJME5.js";
10
9
  import "../chunk-X6ZFFGIA.js";
10
+ import "../chunk-7IA5B5CF.js";
11
11
 
12
12
  // src/endpoints/lists.ts
13
13
  import { getCanciaRuntime } from "virtual:cancia/runtime";
package/dist/index.js CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  makeR2UploadHandler,
14
14
  makeUploadRoute,
15
15
  setCanciaRuntime
16
- } from "./chunk-YZH7QWZV.js";
16
+ } from "./chunk-AGNPUTKS.js";
17
17
  import "./chunk-5IPHDIC6.js";
18
18
  import {
19
19
  defineField,
package/dist/runtime.d.ts CHANGED
@@ -146,6 +146,14 @@ declare function setBakedConfig(config: BakedConfig): void;
146
146
  * to lazy init instead.
147
147
  */
148
148
  declare function setCanciaRuntime(runtime: CanciaRuntime): void;
149
+ /**
150
+ * TEST-ONLY: clear both module singletons so the next getCanciaRuntime() rebuilds
151
+ * from scratch. Production never calls this — the virtual module supplies baked
152
+ * config exactly once and endpoints share the built runtime for the process
153
+ * lifetime. Needed only because a mocked virtual:cancia/runtime module survives
154
+ * vitest's vi.resetModules() (its singletons would otherwise leak across cases).
155
+ */
156
+ declare function __resetRuntimeForTests(): void;
149
157
  declare function getCanciaRuntime(): CanciaRuntime;
150
158
 
151
- export { type BakedConfig, type BakedR2Config, type BakedStorageDescriptor, type CanciaRuntime, type PublishMode, getCanciaRuntime, setBakedConfig, setCanciaRuntime };
159
+ export { type BakedConfig, type BakedR2Config, type BakedStorageDescriptor, type CanciaRuntime, type PublishMode, __resetRuntimeForTests, getCanciaRuntime, setBakedConfig, setCanciaRuntime };
package/dist/runtime.js CHANGED
@@ -1,14 +1,16 @@
1
1
  import {
2
+ __resetRuntimeForTests,
2
3
  getCanciaRuntime,
3
4
  setBakedConfig,
4
5
  setCanciaRuntime
5
- } from "./chunk-YZH7QWZV.js";
6
+ } from "./chunk-AGNPUTKS.js";
6
7
  import "./chunk-5IPHDIC6.js";
7
8
  import "./chunk-GNWD7EL2.js";
8
9
  import "./chunk-U7V53JX7.js";
9
10
  import "./chunk-L2VKQJPY.js";
10
11
  import "./chunk-7IA5B5CF.js";
11
12
  export {
13
+ __resetRuntimeForTests,
12
14
  getCanciaRuntime,
13
15
  setBakedConfig,
14
16
  setCanciaRuntime
@@ -1,3 +1,4 @@
1
+ import "../chunk-FOSOWSXV.js";
1
2
  import {
2
3
  createSQLiteAdapter
3
4
  } from "../chunk-CJDIVWO3.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cancia/astro",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Astro integration for Cancia CMS — inline editing with zero separate server",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -32,6 +32,10 @@
32
32
  "types": "./dist/cache.d.ts",
33
33
  "import": "./dist/cache.js"
34
34
  },
35
+ "./content": {
36
+ "types": "./dist/content.d.ts",
37
+ "import": "./dist/content.js"
38
+ },
35
39
  "./richtext": {
36
40
  "types": "./dist/richtext/index.d.ts",
37
41
  "import": "./dist/richtext/index.js"