@cancia/astro 0.10.0 → 0.12.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.
Files changed (44) hide show
  1. package/dist/chunk-4PYVIIOO.js +25 -0
  2. package/dist/{chunk-DIV2FFYX.js → chunk-5R3I5RLT.js} +15 -8
  3. package/dist/chunk-6GUOGDWO.js +172 -0
  4. package/dist/chunk-DEI3FBNY.js +65 -0
  5. package/dist/{chunk-ONKCUTPU.js → chunk-DOFLZ7KG.js} +6 -7
  6. package/dist/{chunk-GJIX7MWC.js → chunk-ECPYHIAW.js} +9 -1
  7. package/dist/{chunk-YGJ3JHXF.js → chunk-QQEUCOXQ.js} +11 -0
  8. package/dist/{chunk-NVXNJ4YC.js → chunk-R7VA3Z5N.js} +53 -11
  9. package/dist/{chunk-5IPHDIC6.js → chunk-RHFTQW4W.js} +10 -1
  10. package/dist/{chunk-2NVZWZPE.js → chunk-S5YLB6P3.js} +310 -7
  11. package/dist/{chunk-LJSSPOSW.js → chunk-VF3EXXEM.js} +139 -16
  12. package/dist/chunk-XINNLVZP.js +183 -0
  13. package/dist/chunk-XZCXWILA.js +145 -0
  14. package/dist/content.js +3 -3
  15. package/dist/{upload-DwCGjXbz.d.ts → email-BRK4WAO2.d.ts} +9 -1
  16. package/dist/endpoints/auth-callback.d.ts +1 -0
  17. package/dist/endpoints/auth-callback.js +9 -0
  18. package/dist/endpoints/auth-logout.d.ts +1 -0
  19. package/dist/endpoints/auth-logout.js +9 -0
  20. package/dist/endpoints/auth-request.d.ts +18 -0
  21. package/dist/endpoints/auth-request.js +9 -0
  22. package/dist/endpoints/auth-session.d.ts +1 -0
  23. package/dist/endpoints/auth-session.js +9 -0
  24. package/dist/endpoints/auth.js +10 -2
  25. package/dist/endpoints/content.js +12 -12
  26. package/dist/endpoints/lists.js +4 -1
  27. package/dist/endpoints/publish.js +8 -6
  28. package/dist/endpoints/schemas.js +6 -3
  29. package/dist/endpoints/upload.js +11 -10
  30. package/dist/{git-backed-DtiH52EI.d.ts → git-backed-CwTFfUS4.d.ts} +2 -2
  31. package/dist/{github-client-Db0pIrqE.d.ts → github-client-Db5EPcSL.d.ts} +1 -1
  32. package/dist/index.d.ts +32 -6
  33. package/dist/index.js +125 -35
  34. package/dist/loader/index.d.ts +34 -4
  35. package/dist/loader/index.js +3 -3
  36. package/dist/runtime.d.ts +69 -3
  37. package/dist/runtime.js +6 -4
  38. package/dist/schema/index.d.ts +14 -1
  39. package/dist/schema/index.js +1 -1
  40. package/dist/storage/index.d.ts +4 -4
  41. package/dist/storage/index.js +5 -5
  42. package/dist/{types-BMlLS-OS.d.ts → types-CKzfz5iu.d.ts} +1 -1
  43. package/package.json +1 -1
  44. package/dist/chunk-MXVOJRAM.js +0 -504
package/dist/index.js CHANGED
@@ -1,40 +1,54 @@
1
1
  import {
2
2
  makeSchemasRoute
3
- } from "./chunk-ONKCUTPU.js";
3
+ } from "./chunk-DOFLZ7KG.js";
4
+ import {
5
+ handleLoginCallback,
6
+ handleLoginRequest,
7
+ handleLogout,
8
+ handleSessionCheck,
9
+ resolveLoginEmailSender
10
+ } from "./chunk-XZCXWILA.js";
4
11
  import {
5
12
  runPublish
6
13
  } from "./chunk-5RCLBWRH.js";
7
14
  import {
8
15
  createRateLimiter,
9
16
  runAuth
10
- } from "./chunk-GJIX7MWC.js";
17
+ } from "./chunk-ECPYHIAW.js";
11
18
  import {
12
19
  makeListsRoutes
13
- } from "./chunk-DIV2FFYX.js";
20
+ } from "./chunk-5R3I5RLT.js";
14
21
  import {
22
+ createSqliteAuthStore,
15
23
  makeLocalUploadHandler,
16
24
  makeR2UploadHandler,
17
25
  makeUploadRoute,
18
26
  setCanciaRuntime
19
- } from "./chunk-LJSSPOSW.js";
20
- import "./chunk-5IPHDIC6.js";
27
+ } from "./chunk-VF3EXXEM.js";
28
+ import "./chunk-RHFTQW4W.js";
29
+ import {
30
+ guardRequest
31
+ } from "./chunk-4PYVIIOO.js";
32
+ import {
33
+ authorizeRequest
34
+ } from "./chunk-6GUOGDWO.js";
21
35
  import {
22
36
  createSQLiteAdapter
23
37
  } from "./chunk-CJDIVWO3.js";
24
38
  import {
25
- closeSqliteAdapterV2,
26
- createGitBackedAdapter,
27
- createSqliteAdapterV2
28
- } from "./chunk-MXVOJRAM.js";
39
+ createGitBackedAdapter
40
+ } from "./chunk-XINNLVZP.js";
29
41
  import "./chunk-U7V53JX7.js";
30
42
  import "./chunk-VL6FO446.js";
31
43
  import {
32
44
  canciaLoader
33
- } from "./chunk-NVXNJ4YC.js";
45
+ } from "./chunk-R7VA3Z5N.js";
34
46
  import {
47
+ closeSqliteAdapterV2,
35
48
  createJsonFileAdapter,
36
- createJsonFileAdapterV2
37
- } from "./chunk-2NVZWZPE.js";
49
+ createJsonFileAdapterV2,
50
+ createSqliteAdapterV2
51
+ } from "./chunk-S5YLB6P3.js";
38
52
  import {
39
53
  RevConflictError
40
54
  } from "./chunk-7IA5B5CF.js";
@@ -43,7 +57,7 @@ import {
43
57
  defineList,
44
58
  describeList,
45
59
  z
46
- } from "./chunk-YGJ3JHXF.js";
60
+ } from "./chunk-QQEUCOXQ.js";
47
61
  import "./chunk-KHCFVVYV.js";
48
62
 
49
63
  // src/integration.ts
@@ -53,16 +67,13 @@ import { readFile } from "fs/promises";
53
67
  import { isAbsolute, join as join2 } from "path";
54
68
 
55
69
  // src/routes/content.ts
56
- function makeContentRoute(storage, secret) {
57
- function checkAuth(req) {
58
- if (!secret) return null;
59
- const token = req.headers.get("Authorization")?.replace("Bearer ", "").trim();
60
- if (token !== secret) return new Response(JSON.stringify({ error: "Unauthorized" }), { status: 401 });
61
- return null;
70
+ function makeContentRoute(storage, secret, auth) {
71
+ async function checkAuth(req) {
72
+ return (await guardRequest(req, { secret, auth })).deny;
62
73
  }
63
74
  return {
64
75
  GET: async ({ request }) => {
65
- const deny = checkAuth(request);
76
+ const deny = await checkAuth(request);
66
77
  if (deny) return deny;
67
78
  const site = new URL(request.url).searchParams.get("site");
68
79
  if (!site) return new Response(JSON.stringify({ error: "Missing ?site=" }), { status: 400 });
@@ -72,7 +83,7 @@ function makeContentRoute(storage, secret) {
72
83
  });
73
84
  },
74
85
  POST: async ({ request }) => {
75
- const deny = checkAuth(request);
86
+ const deny = await checkAuth(request);
76
87
  if (deny) return deny;
77
88
  const body = await request.json().catch(() => null);
78
89
  if (!body?.site || !body?.key || !body?.lang || body?.value === void 0) {
@@ -84,7 +95,7 @@ function makeContentRoute(storage, secret) {
84
95
  });
85
96
  },
86
97
  DELETE: async ({ request }) => {
87
- const deny = checkAuth(request);
98
+ const deny = await checkAuth(request);
88
99
  if (deny) return deny;
89
100
  const body = await request.json().catch(() => null);
90
101
  if (!body?.site || !body?.key || !body?.lang) {
@@ -99,14 +110,10 @@ function makeContentRoute(storage, secret) {
99
110
  }
100
111
 
101
112
  // src/routes/publish.ts
102
- function makePublishRoute(deployHook, secret, hookOptions, dispatch, mode) {
113
+ function makePublishRoute(deployHook, secret, hookOptions, dispatch, mode, auth) {
103
114
  return async ({ request }) => {
104
- if (secret) {
105
- const token = request.headers.get("Authorization")?.replace("Bearer ", "").trim();
106
- if (token !== secret) {
107
- return new Response(JSON.stringify({ error: "Unauthorized" }), { status: 401 });
108
- }
109
- }
115
+ const deny = (await guardRequest(request, { secret, auth })).deny;
116
+ if (deny) return deny;
110
117
  const hook = deployHook ?? process.env.CANCIA_DEPLOY_HOOK;
111
118
  return runPublish({
112
119
  mode,
@@ -357,6 +364,34 @@ ${code}`;
357
364
  ${code.slice(at)}`;
358
365
  }
359
366
 
367
+ // src/routes/auth-magic.ts
368
+ function makeMagicAuthRoutes(ctx) {
369
+ const limiter = createRateLimiter();
370
+ const shared = { store: ctx.store, site: ctx.site, sendEmail: ctx.sendEmail, limiter };
371
+ return {
372
+ async request(req) {
373
+ return handleLoginRequest(req, shared, new URL(req.url).origin);
374
+ },
375
+ async callback(req) {
376
+ return handleLoginCallback(req, shared);
377
+ },
378
+ async logout(req) {
379
+ return handleLogout(req, shared);
380
+ },
381
+ async session(req) {
382
+ return handleSessionCheck(
383
+ req,
384
+ shared,
385
+ () => authorizeRequest({
386
+ request: req,
387
+ secret: ctx.secret,
388
+ auth: { store: ctx.store, site: ctx.site }
389
+ })
390
+ );
391
+ }
392
+ };
393
+ }
394
+
360
395
  // src/integration.ts
361
396
  function resolvePublish(opts, hasDeployHookEnv) {
362
397
  const publish = opts.publish;
@@ -386,6 +421,15 @@ function buildDefaultV2Store(opts, projectRoot) {
386
421
  }
387
422
  return createJsonFileAdapterV2({ projectRoot });
388
423
  }
424
+ function buildAuthContext(opts, projectRoot) {
425
+ const editors = opts.editors?.map((e) => e.trim().toLowerCase()).filter(Boolean) ?? [];
426
+ if (editors.length === 0) return void 0;
427
+ const dbPath = opts.db?.kind === "sqlite" && opts.db.path ? isAbsolute(opts.db.path) ? opts.db.path : join2(projectRoot, opts.db.path) : join2(projectRoot, "cancia.db");
428
+ const store = createSqliteAuthStore({ dbPath });
429
+ const site = opts.site ?? "default";
430
+ void store.seedEditors(site, editors);
431
+ return { store, site };
432
+ }
389
433
  function canciaIntegration(opts = {}) {
390
434
  let storage;
391
435
  let resolvedToken = "";
@@ -459,8 +503,12 @@ function canciaIntegration(opts = {}) {
459
503
  prefix: r2Opts.prefix
460
504
  };
461
505
  }
506
+ const bakedEditors = opts.editors?.map((e) => e.trim().toLowerCase()).filter(Boolean) ?? [];
462
507
  bakedConfig = {
463
508
  projectRoot: resolvedRootPath,
509
+ site,
510
+ editors: bakedEditors,
511
+ authDbPath: opts.db?.kind === "sqlite" && opts.db.path ? opts.db.path : "cancia.db",
464
512
  schemasPath: opts.schemasPath,
465
513
  defaultLocale: resolvedLocales[0],
466
514
  locales: resolvedLocales,
@@ -485,7 +533,12 @@ function canciaIntegration(opts = {}) {
485
533
  languages,
486
534
  page: "unknown",
487
535
  public: opts.public ?? false,
488
- canPublish
536
+ canPublish,
537
+ // Whether to offer the email login box. The ADDRESSES are never
538
+ // sent to the browser — publishing a client's staff list on every
539
+ // page would be a disclosure, and the server checks the allow-list
540
+ // anyway. The toolbar only needs to know the flow exists.
541
+ magicLink: bakedEditors.length > 0
489
542
  })};`
490
543
  );
491
544
  injectScript("page", `import "@cancia/toolbar";`);
@@ -497,6 +550,10 @@ function canciaIntegration(opts = {}) {
497
550
  injectRoute({ pattern: "/api/cancia/publish", entrypoint: endpointPath("publish"), prerender: false });
498
551
  injectRoute({ pattern: "/api/cancia/auth", entrypoint: endpointPath("auth"), prerender: false });
499
552
  injectRoute({ pattern: "/api/cancia/health", entrypoint: endpointPath("health"), prerender: false });
553
+ injectRoute({ pattern: "/api/cancia/auth/request", entrypoint: endpointPath("auth-request"), prerender: false });
554
+ injectRoute({ pattern: "/api/cancia/auth/callback", entrypoint: endpointPath("auth-callback"), prerender: false });
555
+ injectRoute({ pattern: "/api/cancia/auth/logout", entrypoint: endpointPath("auth-logout"), prerender: false });
556
+ injectRoute({ pattern: "/api/cancia/auth/session", entrypoint: endpointPath("auth-session"), prerender: false });
500
557
  injectRoute({ pattern: "/api/cancia/schemas", entrypoint: endpointPath("schemas"), prerender: false });
501
558
  injectRoute({ pattern: "/api/cancia/lists/[listName]", entrypoint: endpointPath("lists"), prerender: false });
502
559
  injectRoute({ pattern: "/api/cancia/lists/[listName]/[id]", entrypoint: endpointPath("lists"), prerender: false });
@@ -616,6 +673,9 @@ function canciaIntegration(opts = {}) {
616
673
  defaultLocale: resolvedLocales[0],
617
674
  locales: resolvedLocales,
618
675
  secret: routeSecret,
676
+ site: opts.site ?? "default",
677
+ auth: buildAuthContext(opts, resolvedRootPath),
678
+ sendLoginEmail: resolveLoginEmailSender(opts.sendLoginEmail),
619
679
  uploadHandler,
620
680
  deployHook,
621
681
  deployHookToken,
@@ -627,8 +687,9 @@ function canciaIntegration(opts = {}) {
627
687
  redeployHook: resolvedRedeployHook,
628
688
  maxUploadMB: opts.maxUploadMB ?? 10
629
689
  });
630
- const contentRoutes = makeContentRoute(storage, routeSecret);
631
- const uploadRoute = makeUploadRoute(uploadHandler, routeSecret, opts.maxUploadMB);
690
+ const devAuthCtx = buildAuthContext(opts, resolvedRootPath);
691
+ const contentRoutes = makeContentRoute(storage, routeSecret, devAuthCtx);
692
+ const uploadRoute = makeUploadRoute(uploadHandler, routeSecret, opts.maxUploadMB, devAuthCtx);
632
693
  const publishRoute = makePublishRoute(
633
694
  deployHook,
634
695
  routeSecret,
@@ -638,20 +699,33 @@ function canciaIntegration(opts = {}) {
638
699
  headers: opts.deployHookHeaders
639
700
  },
640
701
  { repo: resolvedPublishRepo, token: publishGithubToken },
641
- resolvedPublishMode
702
+ resolvedPublishMode,
703
+ devAuthCtx
642
704
  );
643
705
  const authRoute = makeAuthRoute(secret);
706
+ const magicRoutes = devAuthCtx ? makeMagicAuthRoutes({
707
+ store: devAuthCtx.store,
708
+ site: devAuthCtx.site,
709
+ sendEmail: resolveLoginEmailSender(opts.sendLoginEmail),
710
+ secret: routeSecret
711
+ }) : void 0;
712
+ const magicNotConfigured = () => new Response(
713
+ JSON.stringify({ error: "Magic-link login is not configured for this site." }),
714
+ { status: 404, headers: { "Content-Type": "application/json" } }
715
+ );
644
716
  const listsRoutes = makeListsRoutes({
645
717
  storageV2,
646
718
  projectRoot: resolvedRootPath,
647
719
  schemasPath: opts.schemasPath,
648
720
  secret: routeSecret,
721
+ auth: devAuthCtx,
649
722
  defaultLocale: resolvedLocales[0]
650
723
  });
651
724
  const schemasRoute = makeSchemasRoute({
652
725
  projectRoot: resolvedRootPath,
653
726
  schemasPath: opts.schemasPath,
654
- secret: routeSecret
727
+ secret: routeSecret,
728
+ auth: devAuthCtx
655
729
  });
656
730
  server.middlewares.use(async (req, res, next) => {
657
731
  const url = req.url ?? "";
@@ -716,8 +790,21 @@ function canciaIntegration(opts = {}) {
716
790
  response = await publishRoute(ctx);
717
791
  } else if (url === "/api/cancia/health") {
718
792
  response = new Response(JSON.stringify({ ok: true }), { status: 200 });
793
+ } else if (url === "/api/cancia/auth/request" && method === "POST") {
794
+ response = magicRoutes ? await magicRoutes.request(webReq) : magicNotConfigured();
795
+ } else if (url === "/api/cancia/auth/callback" && method === "POST") {
796
+ response = magicRoutes ? await magicRoutes.callback(webReq) : magicNotConfigured();
797
+ } else if (url === "/api/cancia/auth/logout" && method === "POST") {
798
+ response = magicRoutes ? await magicRoutes.logout(webReq) : magicNotConfigured();
799
+ } else if (url === "/api/cancia/auth/session" && method === "GET") {
800
+ response = magicRoutes ? await magicRoutes.session(webReq) : magicNotConfigured();
719
801
  } else if (url === "/api/cancia/auth" && method === "POST") {
720
- response = await authRoute(webReq);
802
+ response = magicRoutes ? new Response(
803
+ JSON.stringify({
804
+ error: "This site uses email login. Open the site and request a link instead."
805
+ }),
806
+ { status: 403, headers: { "Content-Type": "application/json" } }
807
+ ) : await authRoute(webReq);
721
808
  }
722
809
  if (!response) return next();
723
810
  res.statusCode = response.status;
@@ -782,6 +869,9 @@ function canciaIntegration(opts = {}) {
782
869
  defaultLocale: resolvedLocales[0],
783
870
  locales: resolvedLocales,
784
871
  secret: routeSecret,
872
+ site: opts.site ?? "default",
873
+ auth: buildAuthContext(opts, resolvedRootPath),
874
+ sendLoginEmail: resolveLoginEmailSender(opts.sendLoginEmail),
785
875
  uploadHandler,
786
876
  deployHook,
787
877
  deployHookToken,
@@ -1,5 +1,5 @@
1
1
  import { Loader } from 'astro/loaders';
2
- import { a as CanciaStorageV2 } from '../types-BMlLS-OS.js';
2
+ import { C as CanciaStorageV2 } from '../types-CKzfz5iu.js';
3
3
  import { ListSchema } from '../schema/index.js';
4
4
  import 'zod';
5
5
  import '../portable-text-D74aIuHn.js';
@@ -13,9 +13,39 @@ interface CanciaLoaderOptions {
13
13
  */
14
14
  site: string;
15
15
  /**
16
- * Custom storage adapter. Defaults to the JSON-file v2 adapter rooted at
17
- * `process.cwd()` — which is the project root during Astro's content sync.
18
- * Override to point at a different location (testing, monorepos, etc.).
16
+ * Where this site's content lives.
17
+ *
18
+ * USUALLY UNNECESSARY. A `cancia.db` at the project root where the
19
+ * integration's `db: { kind: "sqlite" }` puts it — is detected automatically,
20
+ * so a fresh project needs nothing here.
21
+ *
22
+ * Set it when the site uses a CUSTOM `db.path` (autodetection deliberately
23
+ * checks only the default location and never searches), or to force the
24
+ * JSON-file backend on a project that also has a stray .db.
25
+ *
26
+ * When set, MIRROR THE INTEGRATION'S `db` OPTION.
27
+ *
28
+ * cancia({ db: { kind: "sqlite" } }) // astro.config.mjs
29
+ * canciaLoader({ …, db: { kind: "sqlite" } }) // content.config.ts
30
+ *
31
+ * It has to be repeated because `content.config.ts` is evaluated by Astro
32
+ * BEFORE the integration's hooks run, so the loader cannot read the baked
33
+ * config the way the SSR runtime does.
34
+ *
35
+ * Omitting it on a sqlite-backed site is the bug this option exists to make
36
+ * visible: the loader used to hardcode the JSON-file adapter, so it read an
37
+ * empty directory and `getCollection()` returned nothing — silently, because
38
+ * "no entries" is a legitimate state. A page using the common
39
+ * `entries.length > 0 ? entries : seeded` fallback then rendered its
40
+ * hardcoded seed array and looked entirely correct.
41
+ */
42
+ db?: {
43
+ kind: "sqlite" | "json-file";
44
+ path?: string;
45
+ };
46
+ /**
47
+ * Custom storage adapter. Wins over `db` — for tests, monorepos, or a
48
+ * backend Cancia does not know about.
19
49
  */
20
50
  storage?: CanciaStorageV2;
21
51
  /**
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  canciaLoader
3
- } from "../chunk-NVXNJ4YC.js";
4
- import "../chunk-2NVZWZPE.js";
3
+ } from "../chunk-R7VA3Z5N.js";
4
+ import "../chunk-S5YLB6P3.js";
5
5
  import "../chunk-7IA5B5CF.js";
6
- import "../chunk-YGJ3JHXF.js";
6
+ import "../chunk-QQEUCOXQ.js";
7
7
  import "../chunk-KHCFVVYV.js";
8
8
  export {
9
9
  canciaLoader
package/dist/runtime.d.ts CHANGED
@@ -1,6 +1,41 @@
1
- import { C as CanciaStorage, a as CanciaStorageV2 } from './types-BMlLS-OS.js';
2
- import { U as UploadHandler } from './upload-DwCGjXbz.js';
3
- import { G as GitHubCommitter } from './github-client-Db0pIrqE.js';
1
+ import { d as CanciaStorage, C as CanciaStorageV2 } from './types-CKzfz5iu.js';
2
+ import { S as SendLoginEmail, U as UploadHandler } from './email-BRK4WAO2.js';
3
+ import { b as GitHubCommitter } from './github-client-Db5EPcSL.js';
4
+
5
+ /** A live browser session. `id` is the SHA-256 hash of the cookie value. */
6
+ interface SessionRecord {
7
+ /** Hash of the session secret — never the secret itself. */
8
+ id: string;
9
+ email: string;
10
+ createdAt: string;
11
+ expiresAt: string;
12
+ }
13
+ /** A pending, single-use login link. `hash` is the SHA-256 of the emailed token. */
14
+ interface LoginTokenRecord {
15
+ hash: string;
16
+ email: string;
17
+ expiresAt: string;
18
+ }
19
+ interface CanciaAuthStore {
20
+ /** Replace the allow-list with exactly these addresses. Called on boot. */
21
+ seedEditors(site: string, emails: string[]): Promise<void>;
22
+ /** Is this address allowed to sign in? */
23
+ isEditor(site: string, email: string): Promise<boolean>;
24
+ createLoginToken(site: string, rec: LoginTokenRecord): Promise<void>;
25
+ /**
26
+ * Atomically redeem a login token: return it and delete it in one step, so
27
+ * two concurrent redemptions of the same link cannot both succeed.
28
+ * Returns null when the hash is unknown (already used, or never existed).
29
+ */
30
+ consumeLoginToken(site: string, hash: string): Promise<LoginTokenRecord | null>;
31
+ createSession(site: string, rec: SessionRecord): Promise<void>;
32
+ getSession(site: string, id: string): Promise<SessionRecord | null>;
33
+ /** Push a session's expiry out — the sliding half of the window. */
34
+ touchSession(site: string, id: string, expiresAt: string): Promise<void>;
35
+ deleteSession(site: string, id: string): Promise<void>;
36
+ /** Drop expired tokens and sessions. Best-effort housekeeping. */
37
+ purgeExpired(site: string, now: string): Promise<void>;
38
+ }
4
39
 
5
40
  /**
6
41
  * The resolved publish mode (034). One of:
@@ -35,6 +70,23 @@ interface CanciaRuntime {
35
70
  /** All configured locales, in order with defaultLocale first. */
36
71
  locales: string[];
37
72
  secret: string | undefined;
73
+ /** Site identifier — scopes auth rows, as it scopes content rows. */
74
+ site: string;
75
+ /**
76
+ * Magic-link auth (042). Undefined when the site has no `editors`, which is
77
+ * the pre-042 world where the bearer token is the only credential. Every
78
+ * caller reads `undefined` as "bearer only", so no separate flag is needed.
79
+ */
80
+ auth: {
81
+ store: CanciaAuthStore;
82
+ site: string;
83
+ } | undefined;
84
+ /**
85
+ * Custom login-email sender. Not baked (it is a function): supplied by the
86
+ * dev hook, and undefined in a fresh SSR process, where the sender is
87
+ * resolved from env instead.
88
+ */
89
+ sendLoginEmail?: SendLoginEmail;
38
90
  uploadHandler: UploadHandler;
39
91
  deployHook: string | undefined;
40
92
  /**
@@ -97,6 +149,19 @@ interface BakedR2Config {
97
149
  }
98
150
  interface BakedConfig {
99
151
  projectRoot: string;
152
+ /** Site identifier. Non-secret. */
153
+ site?: string;
154
+ /**
155
+ * Addresses allowed to sign in (042). Non-secret — an allow-list is not a
156
+ * credential, and the client needs to know whether to show the login box.
157
+ */
158
+ editors?: string[];
159
+ /**
160
+ * Where the auth tables live. Same file as the content DB; kept as its own
161
+ * field because a json-file content site still needs somewhere to put
162
+ * sessions, and that somewhere is a sqlite file.
163
+ */
164
+ authDbPath?: string;
100
165
  schemasPath: string | undefined;
101
166
  defaultLocale: string;
102
167
  locales: string[];
@@ -154,6 +219,7 @@ declare function setCanciaRuntime(runtime: CanciaRuntime): void;
154
219
  * vitest's vi.resetModules() (its singletons would otherwise leak across cases).
155
220
  */
156
221
  declare function __resetRuntimeForTests(): void;
222
+
157
223
  declare function getCanciaRuntime(): CanciaRuntime;
158
224
 
159
225
  export { type BakedConfig, type BakedR2Config, type BakedStorageDescriptor, type CanciaRuntime, type PublishMode, __resetRuntimeForTests, getCanciaRuntime, setBakedConfig, setCanciaRuntime };
package/dist/runtime.js CHANGED
@@ -3,11 +3,13 @@ import {
3
3
  getCanciaRuntime,
4
4
  setBakedConfig,
5
5
  setCanciaRuntime
6
- } from "./chunk-LJSSPOSW.js";
7
- import "./chunk-5IPHDIC6.js";
8
- import "./chunk-MXVOJRAM.js";
6
+ } from "./chunk-VF3EXXEM.js";
7
+ import "./chunk-RHFTQW4W.js";
8
+ import "./chunk-4PYVIIOO.js";
9
+ import "./chunk-6GUOGDWO.js";
10
+ import "./chunk-XINNLVZP.js";
9
11
  import "./chunk-U7V53JX7.js";
10
- import "./chunk-2NVZWZPE.js";
12
+ import "./chunk-S5YLB6P3.js";
11
13
  import "./chunk-7IA5B5CF.js";
12
14
  export {
13
15
  __resetRuntimeForTests,
@@ -16,7 +16,7 @@ export { P as PT_DECORATORS, a as PT_LIST_ITEMS, b as PT_STYLES, c as PortableTe
16
16
  * z.boolean() → "checkbox"
17
17
  * z.enum([...]) → "select"
18
18
  */
19
- type FieldWidget = "text" | "textarea" | "url" | "email" | "datetime" | "number" | "checkbox" | "select" | "image" | "slug" | "array" | "object" | "reference" | "richtext" | "link";
19
+ type FieldWidget = "text" | "textarea" | "url" | "email" | "datetime" | "number" | "checkbox" | "select" | "image" | "file" | "slug" | "array" | "object" | "reference" | "richtext" | "link";
20
20
  /**
21
21
  * Fields common to every widget's metadata. All optional.
22
22
  */
@@ -49,6 +49,8 @@ type FieldMeta = (FieldMetaBase & {
49
49
  }) | (FieldMetaBase & {
50
50
  widget: "image";
51
51
  alt?: boolean;
52
+ }) | (FieldMetaBase & {
53
+ widget: "file";
52
54
  }) | (FieldMetaBase & {
53
55
  widget: "slug";
54
56
  source?: string;
@@ -204,6 +206,17 @@ declare const defineField: {
204
206
  image: (o?: FieldMetaBase & {
205
207
  alt?: boolean;
206
208
  }) => z.ZodString;
209
+ /**
210
+ * An uploaded document — a PDF today.
211
+ *
212
+ * Stores a URL exactly as `image` does, because the upload path is the same:
213
+ * the file goes to the configured store (R2, or public/uploads) and what
214
+ * comes back is a plain URL. A separate widget rather than reusing `image`
215
+ * so the editor renders a filename and a download affordance instead of a
216
+ * thumbnail, and so an `image` field cannot silently accept a PDF and
217
+ * render a broken <img>.
218
+ */
219
+ file: (o?: FieldMetaBase) => z.ZodString;
207
220
  select: (o: FieldMetaBase & {
208
221
  options: string[];
209
222
  }) => z.ZodEnum<{
@@ -8,7 +8,7 @@ import {
8
8
  serializeLinkValue,
9
9
  slugify,
10
10
  z
11
- } from "../chunk-YGJ3JHXF.js";
11
+ } from "../chunk-QQEUCOXQ.js";
12
12
  import {
13
13
  PT_DECORATORS,
14
14
  PT_LIST_ITEMS,
@@ -1,7 +1,7 @@
1
- import { R as Rev } from '../types-BMlLS-OS.js';
2
- export { b as CanciaKVStore, c as CanciaListStore, d as CanciaPageStore, C as CanciaStorage, a as CanciaStorageV2, L as ListEntry, P as PageMeta, e as PageRecord, f as PageSEO, g as RevConflictError } from '../types-BMlLS-OS.js';
3
- export { G as GitBackedContentPaths, a as GitBackedControls, b as GitBackedOptions, c as GitBackedStorage, S as SqliteV2Options, d as closeSqliteAdapterV2, e as createGitBackedAdapter, f as createJsonFileAdapter, g as createJsonFileAdapterV2, h as createSQLiteAdapter, i as createSqliteAdapterV2 } from '../git-backed-DtiH52EI.js';
4
- export { C as CommitFile, a as GitHubClient, b as GitHubClientOptions, G as GitHubCommitter, c as createGitHubClient } from '../github-client-Db0pIrqE.js';
1
+ import { R as Rev } from '../types-CKzfz5iu.js';
2
+ export { a as CanciaKVStore, b as CanciaListStore, c as CanciaPageStore, d as CanciaStorage, C as CanciaStorageV2, L as ListEntry, P as PageMeta, e as PageRecord, f as PageSEO, g as RevConflictError } from '../types-CKzfz5iu.js';
3
+ export { G as GitBackedContentPaths, a as GitBackedControls, b as GitBackedOptions, c as GitBackedStorage, S as SqliteV2Options, d as closeSqliteAdapterV2, e as createGitBackedAdapter, f as createJsonFileAdapter, g as createJsonFileAdapterV2, h as createSQLiteAdapter, i as createSqliteAdapterV2 } from '../git-backed-CwTFfUS4.js';
4
+ export { C as CommitFile, G as GitHubClient, a as GitHubClientOptions, b as GitHubCommitter, c as createGitHubClient } from '../github-client-Db5EPcSL.js';
5
5
 
6
6
  /**
7
7
  * Canonical JSON: keys sorted alphabetically at every level. Two values that
@@ -3,19 +3,19 @@ import {
3
3
  createSQLiteAdapter
4
4
  } from "../chunk-CJDIVWO3.js";
5
5
  import {
6
- closeSqliteAdapterV2,
7
- createGitBackedAdapter,
8
- createSqliteAdapterV2
9
- } from "../chunk-MXVOJRAM.js";
6
+ createGitBackedAdapter
7
+ } from "../chunk-XINNLVZP.js";
10
8
  import {
11
9
  createGitHubClient
12
10
  } from "../chunk-U7V53JX7.js";
13
11
  import {
14
12
  canonicalize,
13
+ closeSqliteAdapterV2,
15
14
  createJsonFileAdapter,
16
15
  createJsonFileAdapterV2,
16
+ createSqliteAdapterV2,
17
17
  hashRev
18
- } from "../chunk-2NVZWZPE.js";
18
+ } from "../chunk-S5YLB6P3.js";
19
19
  import {
20
20
  RevConflictError
21
21
  } from "../chunk-7IA5B5CF.js";
@@ -158,4 +158,4 @@ interface CanciaStorageV2 {
158
158
  lists: CanciaListStore;
159
159
  }
160
160
 
161
- export { type CanciaStorage as C, type ListEntry as L, type PageMeta as P, type Rev as R, type CanciaStorageV2 as a, type CanciaKVStore as b, type CanciaListStore as c, type CanciaPageStore as d, type PageRecord as e, type PageSEO as f, RevConflictError as g };
161
+ export { type CanciaStorageV2 as C, type ListEntry as L, type PageMeta as P, type Rev as R, type CanciaKVStore as a, type CanciaListStore as b, type CanciaPageStore as c, type CanciaStorage as d, type PageRecord as e, type PageSEO as f, RevConflictError as g };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cancia/astro",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "description": "Astro integration for Cancia CMS — inline editing with zero separate server",
5
5
  "license": "MIT",
6
6
  "repository": {