@cancia/astro 0.2.0 → 0.3.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/index.js CHANGED
@@ -1,3 +1,6 @@
1
+ import {
2
+ firePublish
3
+ } from "./chunk-PIDFNJME.js";
1
4
  import {
2
5
  makeListsRoutes
3
6
  } from "./chunk-22DJVJBR.js";
@@ -6,8 +9,11 @@ import {
6
9
  } from "./chunk-IIGDU5SV.js";
7
10
  import "./chunk-NG5GJME5.js";
8
11
  import {
12
+ makeLocalUploadHandler,
13
+ makeR2UploadHandler,
14
+ makeUploadRoute,
9
15
  setCanciaRuntime
10
- } from "./chunk-DGCGIEFD.js";
16
+ } from "./chunk-AIPRCBJM.js";
11
17
  import {
12
18
  defineField,
13
19
  defineList,
@@ -16,27 +22,29 @@ import {
16
22
  } from "./chunk-MCHQV6Y7.js";
17
23
  import {
18
24
  canciaLoader
19
- } from "./chunk-337LJIKX.js";
25
+ } from "./chunk-UR5WC3RA.js";
20
26
  import {
21
- createGitBackedAdapter,
22
27
  createSQLiteAdapter
23
- } from "./chunk-SXKZ2WUL.js";
28
+ } from "./chunk-AE4SIY24.js";
29
+ import {
30
+ closeSqliteAdapterV2,
31
+ createGitBackedAdapter,
32
+ createSqliteAdapterV2
33
+ } from "./chunk-52URFK5Y.js";
24
34
  import {
25
35
  createJsonFileAdapter,
26
36
  createJsonFileAdapterV2
27
- } from "./chunk-ST44VULL.js";
37
+ } from "./chunk-L2VKQJPY.js";
28
38
  import {
29
39
  RevConflictError
30
40
  } from "./chunk-7IA5B5CF.js";
31
41
  import "./chunk-BOIQNZAO.js";
32
- import {
33
- detectImageType,
34
- isValidSite
35
- } from "./chunk-5IPHDIC6.js";
42
+ import "./chunk-5IPHDIC6.js";
36
43
 
37
44
  // src/integration.ts
38
45
  import { loadEnv } from "vite";
39
- import { fileURLToPath } from "url";
46
+ import { fileURLToPath, pathToFileURL } from "url";
47
+ import { isAbsolute, join as join2 } from "path";
40
48
 
41
49
  // src/routes/content.ts
42
50
  function makeContentRoute(storage, secret) {
@@ -84,59 +92,8 @@ function makeContentRoute(storage, secret) {
84
92
  };
85
93
  }
86
94
 
87
- // src/routes/upload.ts
88
- import { writeFile, mkdir } from "fs/promises";
89
- import { join } from "path";
90
- import { randomUUID } from "crypto";
91
- function makeLocalUploadHandler(opts) {
92
- const uploadDir = opts.uploadDir ?? join(process.cwd(), "public/uploads");
93
- const publicUrlBase = opts.publicUrlBase ?? "/uploads";
94
- const maxBytes = (opts.maxMB ?? 10) * 1024 * 1024;
95
- return async (file, site, detected) => {
96
- const dest = join(uploadDir, site);
97
- await mkdir(dest, { recursive: true });
98
- const name = `${randomUUID()}.${detected.ext}`;
99
- await writeFile(join(dest, name), Buffer.from(await file.arrayBuffer()));
100
- return `${publicUrlBase}/${site}/${name}`;
101
- };
102
- }
103
- function makeUploadRoute(uploadHandler, secret, maxMB = 10) {
104
- return async ({ request }) => {
105
- if (secret) {
106
- const token = request.headers.get("Authorization")?.replace("Bearer ", "").trim();
107
- if (token !== secret) {
108
- return new Response(JSON.stringify({ error: "Unauthorized" }), { status: 401 });
109
- }
110
- }
111
- const form = await request.formData().catch(() => null);
112
- const file = form?.get("file");
113
- const site = form?.get("site");
114
- if (!(file instanceof File)) {
115
- return new Response(JSON.stringify({ error: "Missing file field" }), { status: 400 });
116
- }
117
- if (typeof site !== "string" || !isValidSite(site)) {
118
- return new Response(
119
- JSON.stringify({ error: "Invalid site (allowed: a-z, 0-9, and . _ -; must start alphanumeric)" }),
120
- { status: 400 }
121
- );
122
- }
123
- if (file.size > maxMB * 1024 * 1024) {
124
- return new Response(JSON.stringify({ error: `File too large (max ${maxMB}MB)` }), { status: 413 });
125
- }
126
- const buf = new Uint8Array(await file.arrayBuffer());
127
- const detected = detectImageType(buf);
128
- if (!detected) {
129
- return new Response(JSON.stringify({ error: "File type not allowed" }), { status: 415 });
130
- }
131
- const url = await uploadHandler(file, site, detected);
132
- return new Response(JSON.stringify({ url }), {
133
- headers: { "Content-Type": "application/json" }
134
- });
135
- };
136
- }
137
-
138
95
  // src/routes/publish.ts
139
- function makePublishRoute(deployHook, secret) {
96
+ function makePublishRoute(deployHook, secret, hookOptions) {
140
97
  return async ({ request }) => {
141
98
  if (secret) {
142
99
  const token = request.headers.get("Authorization")?.replace("Bearer ", "").trim();
@@ -145,26 +102,25 @@ function makePublishRoute(deployHook, secret) {
145
102
  }
146
103
  }
147
104
  const hook = deployHook ?? process.env.CANCIA_DEPLOY_HOOK;
148
- if (!hook) {
105
+ const result = await firePublish(hook, hookOptions);
106
+ if (result.ok) {
107
+ return new Response(JSON.stringify({ ok: true }), {
108
+ headers: { "Content-Type": "application/json" }
109
+ });
110
+ }
111
+ if (result.kind === "no-hook") {
149
112
  return new Response(
150
113
  JSON.stringify({ error: "No deploy hook configured. Set CANCIA_DEPLOY_HOOK." }),
151
114
  { status: 503 }
152
115
  );
153
116
  }
154
- try {
155
- const res = await fetch(hook, { method: "POST" });
156
- if (!res.ok) {
157
- return new Response(
158
- JSON.stringify({ error: `Deploy hook responded with ${res.status}` }),
159
- { status: 502 }
160
- );
161
- }
162
- return new Response(JSON.stringify({ ok: true }), {
163
- headers: { "Content-Type": "application/json" }
164
- });
165
- } catch {
166
- return new Response(JSON.stringify({ error: "Failed to reach deploy hook" }), { status: 502 });
117
+ if (result.kind === "bad-response") {
118
+ return new Response(
119
+ JSON.stringify({ error: `Deploy hook responded with ${result.status}` }),
120
+ { status: 502 }
121
+ );
167
122
  }
123
+ return new Response(JSON.stringify({ error: "Failed to reach deploy hook" }), { status: 502 });
168
124
  };
169
125
  }
170
126
 
@@ -227,109 +183,15 @@ function makeAuthRoute(secret) {
227
183
  };
228
184
  }
229
185
 
230
- // src/routes/upload-r2.ts
231
- import { createHmac, createHash } from "crypto";
232
- import { randomUUID as randomUUID2 } from "crypto";
233
- function sha256hex(data) {
234
- return createHash("sha256").update(data).digest("hex");
235
- }
236
- function hmacSha256(key, data) {
237
- return createHmac("sha256", key).update(data).digest();
238
- }
239
- function getSigningKey(secretKey, date, region, service) {
240
- const kDate = hmacSha256(Buffer.from(`AWS4${secretKey}`, "utf8"), date);
241
- const kRegion = hmacSha256(kDate, region);
242
- const kService = hmacSha256(kRegion, service);
243
- const kSigning = hmacSha256(kService, "aws4_request");
244
- return kSigning;
245
- }
246
- async function signedPutRequest(opts) {
247
- const { endpoint, bucket, key, body, contentType, accessKeyId, secretAccessKey } = opts;
248
- const region = "auto";
249
- const service = "s3";
250
- const now = /* @__PURE__ */ new Date();
251
- const isoDate = now.toISOString().replace(/[:-]|\.\d{3}/g, "").slice(0, 15) + "Z";
252
- const shortDate = isoDate.slice(0, 8);
253
- const url = `${endpoint}/${bucket}/${key}`;
254
- const host = new URL(endpoint).host;
255
- const payloadHash = sha256hex(body);
256
- const headers = {
257
- "content-type": contentType,
258
- "host": host,
259
- "x-amz-content-sha256": payloadHash,
260
- "x-amz-date": isoDate
261
- };
262
- const signedHeaders = Object.keys(headers).sort().join(";");
263
- const canonicalHeaders = Object.keys(headers).sort().map((k) => `${k}:${headers[k]}
264
- `).join("");
265
- const canonicalRequest = [
266
- "PUT",
267
- `/${bucket}/${key}`,
268
- "",
269
- canonicalHeaders,
270
- signedHeaders,
271
- payloadHash
272
- ].join("\n");
273
- const credentialScope = `${shortDate}/${region}/${service}/aws4_request`;
274
- const stringToSign = [
275
- "AWS4-HMAC-SHA256",
276
- isoDate,
277
- credentialScope,
278
- sha256hex(canonicalRequest)
279
- ].join("\n");
280
- const signingKey = getSigningKey(secretAccessKey, shortDate, region, service);
281
- const signature = createHmac("sha256", signingKey).update(stringToSign).digest("hex");
282
- const authHeader = `AWS4-HMAC-SHA256 Credential=${accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
283
- let res;
284
- try {
285
- res = await fetch(url, {
286
- method: "PUT",
287
- headers: { ...headers, Authorization: authHeader },
288
- body: new Uint8Array(body)
289
- });
290
- } catch (err) {
291
- throw new Error(`R2 upload: network error reaching ${url} \u2014 ${err}`);
292
- }
293
- if (!res.ok) {
294
- const text = await res.text().catch(() => res.statusText);
295
- throw new Error(`R2 upload failed (${res.status}): ${text}`);
296
- }
297
- }
298
- function makeR2UploadHandler(opts) {
299
- const {
300
- accountId,
301
- bucket,
302
- accessKeyId,
303
- secretAccessKey,
304
- publicUrl,
305
- prefix = "uploads"
306
- } = opts;
307
- const endpoint = `https://${accountId}.r2.cloudflarestorage.com`;
308
- return async (file, _site, detected) => {
309
- const key = `${prefix}/${randomUUID2()}.${detected.ext}`;
310
- const body = Buffer.from(await file.arrayBuffer());
311
- await signedPutRequest({
312
- endpoint,
313
- bucket,
314
- key,
315
- body,
316
- contentType: file.type,
317
- accessKeyId,
318
- secretAccessKey
319
- });
320
- return `${publicUrl.replace(/\/$/, "")}/${key}`;
321
- };
322
- }
323
-
324
186
  // src/token.ts
325
187
  import { randomBytes } from "crypto";
326
188
  import { readFileSync, writeFileSync, existsSync } from "fs";
327
- import { join as join2 } from "path";
189
+ import { join } from "path";
328
190
  function generateToken() {
329
191
  return randomBytes(32).toString("hex");
330
192
  }
331
193
  function ensureToken(rootPath) {
332
- const envPath = join2(rootPath, ".env");
194
+ const envPath = join(rootPath, ".env");
333
195
  let contents = "";
334
196
  if (existsSync(envPath)) {
335
197
  contents = readFileSync(envPath, "utf-8");
@@ -348,6 +210,13 @@ CANCIA_TOKEN=${token}
348
210
  }
349
211
 
350
212
  // src/integration.ts
213
+ function buildDefaultV2Store(opts, projectRoot) {
214
+ if (opts.db?.kind === "sqlite") {
215
+ const path = opts.db.path ? isAbsolute(opts.db.path) ? opts.db.path : join2(projectRoot, opts.db.path) : join2(projectRoot, "cancia.db");
216
+ return createSqliteAdapterV2({ dbPath: path });
217
+ }
218
+ return createJsonFileAdapterV2({ projectRoot });
219
+ }
351
220
  function canciaIntegration(opts = {}) {
352
221
  let storage;
353
222
  let resolvedToken = "";
@@ -356,6 +225,7 @@ function canciaIntegration(opts = {}) {
356
225
  let resolvedUploadHandler;
357
226
  let resolvedDeployHook;
358
227
  let resolvedLocales = ["en"];
228
+ let bakedConfig = null;
359
229
  return {
360
230
  name: "@cancia/astro",
361
231
  hooks: {
@@ -385,6 +255,46 @@ function canciaIntegration(opts = {}) {
385
255
  \x1B[32mcancia\x1B[0m No CANCIA_TOKEN found \u2014 generated one for you.`);
386
256
  }
387
257
  const hasDeployHook = !!(opts.deployHook ?? env.CANCIA_DEPLOY_HOOK ?? process.env.CANCIA_DEPLOY_HOOK);
258
+ let storageDescriptor;
259
+ if (opts.git) {
260
+ storageDescriptor = {
261
+ kind: "git-backed",
262
+ repo: opts.git.repo,
263
+ branch: opts.git.branch,
264
+ committer: opts.git.committer,
265
+ debounceMs: opts.git.debounceMs,
266
+ commitMessage: opts.git.commitMessage
267
+ };
268
+ } else if (opts.storageV2 === null) {
269
+ storageDescriptor = void 0;
270
+ } else if (opts.db?.kind === "sqlite") {
271
+ storageDescriptor = { kind: "sqlite-v2", dbPath: opts.db.path };
272
+ } else {
273
+ storageDescriptor = { kind: "json-file", dbPath: opts.dbPath };
274
+ }
275
+ let r2Baked;
276
+ if (opts.r2) {
277
+ const r2Opts = typeof opts.r2 === "function" ? opts.r2() : opts.r2;
278
+ r2Baked = {
279
+ accountId: r2Opts.accountId,
280
+ bucket: r2Opts.bucket,
281
+ publicUrl: r2Opts.publicUrl,
282
+ prefix: r2Opts.prefix
283
+ };
284
+ }
285
+ bakedConfig = {
286
+ projectRoot: resolvedRootPath,
287
+ schemasPath: opts.schemasPath,
288
+ defaultLocale: resolvedLocales[0],
289
+ locales: resolvedLocales,
290
+ maxUploadMB: opts.maxUploadMB ?? 10,
291
+ public: opts.public ?? false,
292
+ hasDeployHook,
293
+ deployHookMethod: opts.deployHookMethod,
294
+ deployHookHeaders: opts.deployHookHeaders,
295
+ storage: storageDescriptor,
296
+ r2: r2Baked
297
+ };
388
298
  injectScript(
389
299
  "head-inline",
390
300
  `window.__CANCIA__=${JSON.stringify({
@@ -410,6 +320,8 @@ function canciaIntegration(opts = {}) {
410
320
  injectRoute({ pattern: "/api/cancia/schemas", entrypoint: endpointPath("schemas"), prerender: false });
411
321
  injectRoute({ pattern: "/api/cancia/lists/[listName]", entrypoint: endpointPath("lists"), prerender: false });
412
322
  injectRoute({ pattern: "/api/cancia/lists/[listName]/[id]", entrypoint: endpointPath("lists"), prerender: false });
323
+ const runtimeModulePath = fileURLToPath(new URL("./runtime.js", import.meta.url));
324
+ const RESOLVED_VIRTUAL_ID = "\0virtual:cancia/runtime";
413
325
  updateConfig({
414
326
  vite: {
415
327
  plugins: [
@@ -417,8 +329,18 @@ function canciaIntegration(opts = {}) {
417
329
  name: "vite-plugin-cancia-runtime",
418
330
  resolveId(id) {
419
331
  if (id === "virtual:cancia/runtime") {
420
- return fileURLToPath(new URL("./runtime.js", import.meta.url));
332
+ return RESOLVED_VIRTUAL_ID;
421
333
  }
334
+ },
335
+ load(id) {
336
+ if (id !== RESOLVED_VIRTUAL_ID) return;
337
+ const importUrl = pathToFileURL(runtimeModulePath).href;
338
+ const bakedLiteral = JSON.stringify(bakedConfig ?? null);
339
+ return [
340
+ `import { setBakedConfig, getCanciaRuntime } from ${JSON.stringify(importUrl)};`,
341
+ `setBakedConfig(${bakedLiteral});`,
342
+ `export { getCanciaRuntime };`
343
+ ].join("\n");
422
344
  }
423
345
  }
424
346
  ]
@@ -429,6 +351,7 @@ function canciaIntegration(opts = {}) {
429
351
  storage = opts.storage ?? createJsonFileAdapter(opts.dbPath);
430
352
  const secret = resolvedToken;
431
353
  const deployHook = opts.deployHook ?? process.env.CANCIA_DEPLOY_HOOK;
354
+ const deployHookToken = process.env.CANCIA_DEPLOY_HOOK_TOKEN || opts.deployHookToken;
432
355
  let uploadHandler;
433
356
  if (opts.r2) {
434
357
  const serverEnv = loadEnv(process.env.NODE_ENV ?? "development", resolvedRootPath, "");
@@ -448,7 +371,7 @@ function canciaIntegration(opts = {}) {
448
371
  const routeSecret = opts.public ? void 0 : secret || void 0;
449
372
  resolvedUploadHandler = uploadHandler;
450
373
  resolvedDeployHook = deployHook;
451
- const storageV2 = opts.storageV2 === null ? void 0 : opts.storageV2 ?? createJsonFileAdapterV2({ projectRoot: resolvedRootPath });
374
+ const storageV2 = opts.storageV2 === null ? void 0 : opts.storageV2 ?? buildDefaultV2Store(opts, resolvedRootPath);
452
375
  setCanciaRuntime({
453
376
  storage,
454
377
  storageV2,
@@ -459,11 +382,18 @@ function canciaIntegration(opts = {}) {
459
382
  secret: routeSecret,
460
383
  uploadHandler,
461
384
  deployHook,
385
+ deployHookToken,
386
+ deployHookMethod: opts.deployHookMethod,
387
+ deployHookHeaders: opts.deployHookHeaders,
462
388
  maxUploadMB: opts.maxUploadMB ?? 10
463
389
  });
464
390
  const contentRoutes = makeContentRoute(storage, routeSecret);
465
391
  const uploadRoute = makeUploadRoute(uploadHandler, routeSecret, opts.maxUploadMB);
466
- const publishRoute = makePublishRoute(deployHook, routeSecret);
392
+ const publishRoute = makePublishRoute(deployHook, routeSecret, {
393
+ token: deployHookToken,
394
+ method: opts.deployHookMethod,
395
+ headers: opts.deployHookHeaders
396
+ });
467
397
  const authRoute = makeAuthRoute(secret);
468
398
  const listsRoutes = makeListsRoutes({
469
399
  storageV2,
@@ -595,7 +525,8 @@ function canciaIntegration(opts = {}) {
595
525
  uploadHandler = opts.uploadHandler ?? makeLocalUploadHandler({ maxMB: opts.maxUploadMB });
596
526
  }
597
527
  const deployHook = opts.deployHook ?? process.env.CANCIA_DEPLOY_HOOK;
598
- const storageV2 = opts.storageV2 === null ? void 0 : opts.storageV2 ?? createJsonFileAdapterV2({ projectRoot: resolvedRootPath });
528
+ const deployHookToken = process.env.CANCIA_DEPLOY_HOOK_TOKEN || opts.deployHookToken;
529
+ const storageV2 = opts.storageV2 === null ? void 0 : opts.storageV2 ?? buildDefaultV2Store(opts, resolvedRootPath);
599
530
  setCanciaRuntime({
600
531
  storage,
601
532
  storageV2,
@@ -606,6 +537,9 @@ function canciaIntegration(opts = {}) {
606
537
  secret: routeSecret,
607
538
  uploadHandler,
608
539
  deployHook,
540
+ deployHookToken,
541
+ deployHookMethod: opts.deployHookMethod,
542
+ deployHookHeaders: opts.deployHookHeaders,
609
543
  maxUploadMB: opts.maxUploadMB ?? 10
610
544
  });
611
545
  }
@@ -646,10 +580,12 @@ export {
646
580
  RevConflictError,
647
581
  canciaIntegration,
648
582
  canciaLoader,
583
+ closeSqliteAdapterV2,
649
584
  createGitBackedAdapter,
650
585
  createJsonFileAdapter,
651
586
  createJsonFileAdapterV2,
652
587
  createSQLiteAdapter,
588
+ createSqliteAdapterV2,
653
589
  canciaIntegration as default,
654
590
  defineField,
655
591
  defineList,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  canciaLoader
3
- } from "../chunk-337LJIKX.js";
4
- import "../chunk-ST44VULL.js";
3
+ } from "../chunk-UR5WC3RA.js";
4
+ import "../chunk-L2VKQJPY.js";
5
5
  import "../chunk-7IA5B5CF.js";
6
6
  export {
7
7
  canciaLoader
package/dist/runtime.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { C as CanciaStorage, a as CanciaStorageV2 } from './types-BMlLS-OS.js';
2
2
  import { U as UploadHandler } from './upload-DwCGjXbz.js';
3
+ import { G as GitHubCommitter } from './github-client-BAZ1pW24.js';
3
4
 
4
5
  interface CanciaRuntime {
5
6
  /** v1 KV-only storage. Kept for the existing /content endpoint. */
@@ -26,9 +27,70 @@ interface CanciaRuntime {
26
27
  secret: string | undefined;
27
28
  uploadHandler: UploadHandler;
28
29
  deployHook: string | undefined;
30
+ /**
31
+ * Auth token for the deploy hook (e.g. Coolify's "auth required" hook).
32
+ * SECRET — read from CANCIA_DEPLOY_HOOK_TOKEN at runtime, never baked. When
33
+ * set the publish POST sends `Authorization: Bearer <token>`.
34
+ */
35
+ deployHookToken: string | undefined;
36
+ /** Optional HTTP method for the hook (default POST). Non-secret, baked. */
37
+ deployHookMethod: string | undefined;
38
+ /** Optional extra headers merged into the hook request. Non-secret, baked. */
39
+ deployHookHeaders: Record<string, string> | undefined;
40
+ maxUploadMB: number;
41
+ }
42
+ /** Describes which storage adapter to build lazily in the server process. */
43
+ type BakedStorageDescriptor = {
44
+ kind: "json-file";
45
+ dbPath?: string;
46
+ } | {
47
+ kind: "sqlite-v2";
48
+ dbPath?: string;
49
+ } | {
50
+ kind: "git-backed";
51
+ repo: string;
52
+ branch?: string;
53
+ committer?: GitHubCommitter;
54
+ debounceMs?: number;
55
+ commitMessage?: string;
56
+ };
57
+ /** R2 config baked minus its secrets (accessKeyId/secretAccessKey come from env). */
58
+ interface BakedR2Config {
59
+ accountId?: string;
60
+ bucket?: string;
61
+ publicUrl?: string;
62
+ prefix?: string;
63
+ }
64
+ interface BakedConfig {
65
+ projectRoot: string;
66
+ schemasPath: string | undefined;
67
+ defaultLocale: string;
68
+ locales: string[];
29
69
  maxUploadMB: number;
70
+ /** When true, endpoints skip auth (public demos). Baked, never a secret. */
71
+ public: boolean;
72
+ /** Whether a deploy hook exists — the value itself is read from env. */
73
+ hasDeployHook: boolean;
74
+ /** Optional deploy-hook HTTP method (default POST). Non-secret. */
75
+ deployHookMethod: string | undefined;
76
+ /** Optional deploy-hook extra headers. Non-secret (the token stays in env). */
77
+ deployHookHeaders: Record<string, string> | undefined;
78
+ /** How to build the v2 storage adapter at runtime. Absent = no v2 storage. */
79
+ storage: BakedStorageDescriptor | undefined;
80
+ /** When set, build an R2 upload handler; secrets come from env at runtime. */
81
+ r2: BakedR2Config | undefined;
30
82
  }
83
+ /**
84
+ * Called by the generated virtual:cancia/runtime module at import time with the
85
+ * build-baked, non-secret config. Enables lazy self-init in a fresh process.
86
+ */
87
+ declare function setBakedConfig(config: BakedConfig): void;
88
+ /**
89
+ * Eagerly set the runtime. Used in dev (astro:server:setup) and in-process
90
+ * builds where the resolved runtime already exists. Production falls through
91
+ * to lazy init instead.
92
+ */
31
93
  declare function setCanciaRuntime(runtime: CanciaRuntime): void;
32
94
  declare function getCanciaRuntime(): CanciaRuntime;
33
95
 
34
- export { type CanciaRuntime, getCanciaRuntime, setCanciaRuntime };
96
+ export { type BakedConfig, type BakedR2Config, type BakedStorageDescriptor, type CanciaRuntime, getCanciaRuntime, setBakedConfig, setCanciaRuntime };
package/dist/runtime.js CHANGED
@@ -1,8 +1,14 @@
1
1
  import {
2
2
  getCanciaRuntime,
3
+ setBakedConfig,
3
4
  setCanciaRuntime
4
- } from "./chunk-DGCGIEFD.js";
5
+ } from "./chunk-AIPRCBJM.js";
6
+ import "./chunk-52URFK5Y.js";
7
+ import "./chunk-L2VKQJPY.js";
8
+ import "./chunk-7IA5B5CF.js";
9
+ import "./chunk-5IPHDIC6.js";
5
10
  export {
6
11
  getCanciaRuntime,
12
+ setBakedConfig,
7
13
  setCanciaRuntime
8
14
  };
@@ -1,129 +1,13 @@
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
- /** Minimal fetch signature — matches the global `fetch` we depend on. */
21
- type FetchLike = (input: string, init?: {
22
- method?: string;
23
- headers?: Record<string, string>;
24
- body?: string;
25
- }) => Promise<{
26
- ok: boolean;
27
- status: number;
28
- json(): Promise<unknown>;
29
- text(): Promise<string>;
30
- }>;
31
- interface GitHubCommitter {
32
- name: string;
33
- email: string;
34
- }
35
- interface GitHubClientOptions {
36
- /** "owner/name" */
37
- repo: string;
38
- /** Branch to commit onto, e.g. "main". */
39
- branch: string;
40
- /** Fine-grained PAT with contents:write on the one repo. */
41
- token: string;
42
- /** Optional committer identity. GitHub uses the token's user if omitted. */
43
- committer?: GitHubCommitter;
44
- /** Injectable fetch (defaults to global fetch) — tests mock this. */
45
- fetch?: FetchLike;
46
- /** API base — defaults to https://api.github.com. Overridable for tests. */
47
- apiBase?: string;
48
- }
49
- interface CommitFile {
50
- /** Repo-relative path, forward slashes, no leading slash. */
51
- path: string;
52
- /** Raw file content (UTF-8 text). Encoded to base64 before PUT. */
53
- content: string;
54
- }
55
- interface GitHubClient {
56
- /** Current blob sha for `path`, or null if the file doesn't exist yet. */
57
- getFileSha(path: string): Promise<string | null>;
58
- /**
59
- * Commit each file to the branch. Updates pass the current sha; creates omit
60
- * it. Resolves once every PUT succeeds; rejects (without partial silence) if
61
- * any PUT fails so the caller can keep the batch dirty and retry.
62
- */
63
- commitFiles(files: CommitFile[], message: string): Promise<void>;
64
- }
65
- declare function createGitHubClient(opts: GitHubClientOptions): GitHubClient;
66
-
67
- interface GitBackedContentPaths {
68
- /** KV file path. Default <projectRoot>/cancia-content.json. */
69
- kvPath?: string;
70
- /** Pages file path. Default <projectRoot>/.cancia/pages.json. */
71
- pagesPath?: string;
72
- /** Lists directory. Default <projectRoot>/.cancia/lists. */
73
- listsDir?: string;
74
- }
75
- interface GitBackedOptions {
76
- /** The wrapped local adapter — the on-disk source of truth. */
77
- local: CanciaStorageV2;
78
- /** "owner/name" of the GitHub repo whose builds carry the content. */
79
- repo: string;
80
- /** Branch to commit onto. Default "main". */
81
- branch?: string;
82
- /**
83
- * GitHub PAT. Reads CANCIA_GITHUB_TOKEN if omitted. When absent entirely the
84
- * adapter runs in local-only mode (disk writes only; no commits) + warns once.
85
- */
86
- token?: string;
87
- /** Optional committer identity for commits. */
88
- committer?: GitHubCommitter;
89
- /**
90
- * Project root the local adapter writes under — needed to turn absolute
91
- * on-disk paths into repo-relative commit paths. Default process.cwd().
92
- */
93
- projectRoot?: string;
94
- /** Override where the local adapter's content lives (must match `local`). */
95
- contentPaths?: GitBackedContentPaths;
96
- /** Quiet window (ms) before a flush fires. Default 3000. */
97
- debounceMs?: number;
98
- /** Commit message for content updates. */
99
- commitMessage?: string;
100
- /** Injected GitHub client (tests pass a mock). Overrides token/fetch. */
101
- client?: GitHubClient;
102
- /** Injected fetch, forwarded to the default GitHub client. */
103
- fetch?: FetchLike;
104
- /** API base override, forwarded to the default GitHub client (tests). */
105
- apiBase?: string;
106
- /** Warn sink (tests capture). Default console.warn. */
107
- warn?: (msg: string) => void;
108
- /** Error sink for push failures (tests capture). Default console.error. */
109
- onError?: (msg: string, err: unknown) => void;
110
- }
111
- /** The extra control surface the git adapter adds on top of CanciaStorageV2. */
112
- interface GitBackedControls {
113
- /**
114
- * Force any pending dirty files to commit now, bypassing the debounce.
115
- * Resolves once the flush completes (or rejects if the push failed — the
116
- * files stay dirty for the next flush). For tests + graceful shutdown.
117
- */
118
- flush(): Promise<void>;
119
- /** True if git commits are active (token present). */
120
- readonly gitEnabled: boolean;
121
- /** Snapshot of currently-dirty repo-relative paths (for tests/inspection). */
122
- pendingPaths(): string[];
123
- }
124
- type GitBackedStorage = CanciaStorageV2 & {
125
- git: GitBackedControls;
126
- };
127
- declare function createGitBackedAdapter(opts: GitBackedOptions): GitBackedStorage;
128
-
129
- export { CanciaStorage, CanciaStorageV2, type CommitFile, type GitBackedContentPaths, type GitBackedControls, type GitBackedOptions, type GitBackedStorage, type GitHubClient, type GitHubClientOptions, type GitHubCommitter, createGitBackedAdapter, createGitHubClient, createJsonFileAdapter, createJsonFileAdapterV2, createSQLiteAdapter };
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-DFAB0tzf.js';
4
+ export { C as CommitFile, a as GitHubClient, b as GitHubClientOptions, G as GitHubCommitter, c as createGitHubClient } from '../github-client-BAZ1pW24.js';
5
+
6
+ /**
7
+ * Canonical JSON: keys sorted alphabetically at every level. Two values that
8
+ * are logically equal hash to the same _rev regardless of insertion order.
9
+ */
10
+ declare function canonicalize(value: unknown): string;
11
+ declare function hashRev(value: unknown): Rev;
12
+
13
+ export { Rev, canonicalize, hashRev };