@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.
@@ -0,0 +1,258 @@
1
+ import {
2
+ createGitBackedAdapter,
3
+ createSqliteAdapterV2
4
+ } from "./chunk-52URFK5Y.js";
5
+ import {
6
+ createJsonFileAdapter,
7
+ createJsonFileAdapterV2
8
+ } from "./chunk-L2VKQJPY.js";
9
+ import {
10
+ detectImageType,
11
+ isValidSite
12
+ } from "./chunk-5IPHDIC6.js";
13
+
14
+ // src/routes/upload.ts
15
+ import { writeFile, mkdir } from "fs/promises";
16
+ import { join } from "path";
17
+ import { randomUUID } from "crypto";
18
+ function makeLocalUploadHandler(opts) {
19
+ const uploadDir = opts.uploadDir ?? join(process.cwd(), "public/uploads");
20
+ const publicUrlBase = opts.publicUrlBase ?? "/uploads";
21
+ const maxBytes = (opts.maxMB ?? 10) * 1024 * 1024;
22
+ return async (file, site, detected) => {
23
+ const dest = join(uploadDir, site);
24
+ await mkdir(dest, { recursive: true });
25
+ const name = `${randomUUID()}.${detected.ext}`;
26
+ await writeFile(join(dest, name), Buffer.from(await file.arrayBuffer()));
27
+ return `${publicUrlBase}/${site}/${name}`;
28
+ };
29
+ }
30
+ function makeUploadRoute(uploadHandler, secret, maxMB = 10) {
31
+ return async ({ request }) => {
32
+ if (secret) {
33
+ const token = request.headers.get("Authorization")?.replace("Bearer ", "").trim();
34
+ if (token !== secret) {
35
+ return new Response(JSON.stringify({ error: "Unauthorized" }), { status: 401 });
36
+ }
37
+ }
38
+ const form = await request.formData().catch(() => null);
39
+ const file = form?.get("file");
40
+ const site = form?.get("site");
41
+ if (!(file instanceof File)) {
42
+ return new Response(JSON.stringify({ error: "Missing file field" }), { status: 400 });
43
+ }
44
+ if (typeof site !== "string" || !isValidSite(site)) {
45
+ return new Response(
46
+ JSON.stringify({ error: "Invalid site (allowed: a-z, 0-9, and . _ -; must start alphanumeric)" }),
47
+ { status: 400 }
48
+ );
49
+ }
50
+ if (file.size > maxMB * 1024 * 1024) {
51
+ return new Response(JSON.stringify({ error: `File too large (max ${maxMB}MB)` }), { status: 413 });
52
+ }
53
+ const buf = new Uint8Array(await file.arrayBuffer());
54
+ const detected = detectImageType(buf);
55
+ if (!detected) {
56
+ return new Response(JSON.stringify({ error: "File type not allowed" }), { status: 415 });
57
+ }
58
+ const url = await uploadHandler(file, site, detected);
59
+ return new Response(JSON.stringify({ url }), {
60
+ headers: { "Content-Type": "application/json" }
61
+ });
62
+ };
63
+ }
64
+
65
+ // src/routes/upload-r2.ts
66
+ import { createHmac, createHash } from "crypto";
67
+ import { randomUUID as randomUUID2 } from "crypto";
68
+ function sha256hex(data) {
69
+ return createHash("sha256").update(data).digest("hex");
70
+ }
71
+ function hmacSha256(key, data) {
72
+ return createHmac("sha256", key).update(data).digest();
73
+ }
74
+ function getSigningKey(secretKey, date, region, service) {
75
+ const kDate = hmacSha256(Buffer.from(`AWS4${secretKey}`, "utf8"), date);
76
+ const kRegion = hmacSha256(kDate, region);
77
+ const kService = hmacSha256(kRegion, service);
78
+ const kSigning = hmacSha256(kService, "aws4_request");
79
+ return kSigning;
80
+ }
81
+ async function signedPutRequest(opts) {
82
+ const { endpoint, bucket, key, body, contentType, accessKeyId, secretAccessKey } = opts;
83
+ const region = "auto";
84
+ const service = "s3";
85
+ const now = /* @__PURE__ */ new Date();
86
+ const isoDate = now.toISOString().replace(/[:-]|\.\d{3}/g, "").slice(0, 15) + "Z";
87
+ const shortDate = isoDate.slice(0, 8);
88
+ const url = `${endpoint}/${bucket}/${key}`;
89
+ const host = new URL(endpoint).host;
90
+ const payloadHash = sha256hex(body);
91
+ const headers = {
92
+ "content-type": contentType,
93
+ "host": host,
94
+ "x-amz-content-sha256": payloadHash,
95
+ "x-amz-date": isoDate
96
+ };
97
+ const signedHeaders = Object.keys(headers).sort().join(";");
98
+ const canonicalHeaders = Object.keys(headers).sort().map((k) => `${k}:${headers[k]}
99
+ `).join("");
100
+ const canonicalRequest = [
101
+ "PUT",
102
+ `/${bucket}/${key}`,
103
+ "",
104
+ canonicalHeaders,
105
+ signedHeaders,
106
+ payloadHash
107
+ ].join("\n");
108
+ const credentialScope = `${shortDate}/${region}/${service}/aws4_request`;
109
+ const stringToSign = [
110
+ "AWS4-HMAC-SHA256",
111
+ isoDate,
112
+ credentialScope,
113
+ sha256hex(canonicalRequest)
114
+ ].join("\n");
115
+ const signingKey = getSigningKey(secretAccessKey, shortDate, region, service);
116
+ const signature = createHmac("sha256", signingKey).update(stringToSign).digest("hex");
117
+ const authHeader = `AWS4-HMAC-SHA256 Credential=${accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
118
+ let res;
119
+ try {
120
+ res = await fetch(url, {
121
+ method: "PUT",
122
+ headers: { ...headers, Authorization: authHeader },
123
+ body: new Uint8Array(body)
124
+ });
125
+ } catch (err) {
126
+ throw new Error(`R2 upload: network error reaching ${url} \u2014 ${err}`);
127
+ }
128
+ if (!res.ok) {
129
+ const text = await res.text().catch(() => res.statusText);
130
+ throw new Error(`R2 upload failed (${res.status}): ${text}`);
131
+ }
132
+ }
133
+ function makeR2UploadHandler(opts) {
134
+ const {
135
+ accountId,
136
+ bucket,
137
+ accessKeyId,
138
+ secretAccessKey,
139
+ publicUrl,
140
+ prefix = "uploads"
141
+ } = opts;
142
+ const endpoint = `https://${accountId}.r2.cloudflarestorage.com`;
143
+ return async (file, _site, detected) => {
144
+ const key = `${prefix}/${randomUUID2()}.${detected.ext}`;
145
+ const body = Buffer.from(await file.arrayBuffer());
146
+ await signedPutRequest({
147
+ endpoint,
148
+ bucket,
149
+ key,
150
+ body,
151
+ contentType: file.type,
152
+ accessKeyId,
153
+ secretAccessKey
154
+ });
155
+ return `${publicUrl.replace(/\/$/, "")}/${key}`;
156
+ };
157
+ }
158
+
159
+ // src/runtime.ts
160
+ import { isAbsolute, join as join2 } from "path";
161
+ var _runtime = null;
162
+ var _bakedConfig = null;
163
+ function setBakedConfig(config) {
164
+ _bakedConfig = config;
165
+ }
166
+ function setCanciaRuntime(runtime) {
167
+ _runtime = runtime;
168
+ }
169
+ function buildRuntimeFromBaked(baked) {
170
+ const projectRoot = baked.projectRoot || process.cwd();
171
+ const token = process.env.CANCIA_TOKEN?.trim() || "";
172
+ const secret = baked.public ? void 0 : token || void 0;
173
+ const deployHook = process.env.CANCIA_DEPLOY_HOOK || void 0;
174
+ const deployHookToken = process.env.CANCIA_DEPLOY_HOOK_TOKEN || void 0;
175
+ const storageV2 = buildStorageV2(baked, projectRoot);
176
+ const storage = storageV2 ? storageV2.kv : lazyJsonFileAdapter(projectRoot);
177
+ const uploadHandler = buildUploadHandler(baked, projectRoot);
178
+ return {
179
+ storage,
180
+ storageV2,
181
+ projectRoot,
182
+ schemasPath: baked.schemasPath,
183
+ defaultLocale: baked.defaultLocale,
184
+ locales: baked.locales,
185
+ secret,
186
+ uploadHandler,
187
+ deployHook,
188
+ deployHookToken,
189
+ deployHookMethod: baked.deployHookMethod,
190
+ deployHookHeaders: baked.deployHookHeaders,
191
+ maxUploadMB: baked.maxUploadMB
192
+ };
193
+ }
194
+ function lazyJsonFileAdapter(projectRoot) {
195
+ return createJsonFileAdapter(projectRoot + "/cancia-content.json");
196
+ }
197
+ function resolveDbPath(dbPath, projectRoot) {
198
+ return isAbsolute(dbPath) ? dbPath : join2(projectRoot, dbPath);
199
+ }
200
+ function buildStorageV2(baked, projectRoot) {
201
+ const desc = baked.storage;
202
+ if (!desc) return void 0;
203
+ if (desc.kind === "git-backed") {
204
+ const local = createJsonFileAdapterV2({ projectRoot });
205
+ return createGitBackedAdapter({
206
+ local,
207
+ repo: desc.repo,
208
+ branch: desc.branch,
209
+ token: process.env.CANCIA_GITHUB_TOKEN,
210
+ committer: desc.committer,
211
+ projectRoot,
212
+ debounceMs: desc.debounceMs,
213
+ commitMessage: desc.commitMessage
214
+ });
215
+ }
216
+ if (desc.kind === "sqlite-v2") {
217
+ const dbPath = desc.dbPath ? resolveDbPath(desc.dbPath, projectRoot) : `${projectRoot}/cancia.db`;
218
+ return createSqliteAdapterV2({ dbPath });
219
+ }
220
+ return createJsonFileAdapterV2({ projectRoot });
221
+ }
222
+ function buildUploadHandler(baked, projectRoot) {
223
+ if (baked.r2) {
224
+ const env = process.env;
225
+ const resolved = {
226
+ accountId: baked.r2.accountId ?? env.R2_ACCOUNT_ID ?? "",
227
+ bucket: baked.r2.bucket ?? env.R2_BUCKET_NAME ?? env.R2_BUCKET ?? "",
228
+ accessKeyId: env.R2_ACCESS_KEY_ID ?? "",
229
+ secretAccessKey: env.R2_SECRET_ACCESS_KEY ?? "",
230
+ publicUrl: baked.r2.publicUrl ?? env.R2_PUBLIC_URL ?? "",
231
+ prefix: baked.r2.prefix
232
+ };
233
+ return makeR2UploadHandler(resolved);
234
+ }
235
+ return makeLocalUploadHandler({
236
+ uploadDir: projectRoot + "/public/uploads",
237
+ maxMB: baked.maxUploadMB
238
+ });
239
+ }
240
+ function getCanciaRuntime() {
241
+ if (_runtime) return _runtime;
242
+ if (_bakedConfig) {
243
+ _runtime = buildRuntimeFromBaked(_bakedConfig);
244
+ return _runtime;
245
+ }
246
+ throw new Error(
247
+ "[cancia] Runtime not initialised and no baked config available. This is a bug \u2014 the virtual:cancia/runtime module should have called setBakedConfig() at import time."
248
+ );
249
+ }
250
+
251
+ export {
252
+ makeLocalUploadHandler,
253
+ makeUploadRoute,
254
+ makeR2UploadHandler,
255
+ setBakedConfig,
256
+ setCanciaRuntime,
257
+ getCanciaRuntime
258
+ };
@@ -48,10 +48,8 @@ function createJsonFileAdapter(filePath) {
48
48
  };
49
49
  }
50
50
 
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";
51
+ // src/storage/rev.ts
52
+ import { createHash } from "crypto";
55
53
  function canonicalize(value) {
56
54
  if (value === null || typeof value !== "object") return JSON.stringify(value);
57
55
  if (Array.isArray(value)) {
@@ -64,6 +62,11 @@ function canonicalize(value) {
64
62
  function hashRev(value) {
65
63
  return createHash("sha256").update(canonicalize(value)).digest("hex").slice(0, 16);
66
64
  }
65
+
66
+ // src/storage/json-file-v2.ts
67
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, rmSync } from "fs";
68
+ import { dirname as dirname2, join } from "path";
69
+ import { randomUUID } from "crypto";
67
70
  function readJsonFile(filePath, fallback) {
68
71
  if (!existsSync2(filePath)) return fallback;
69
72
  try {
@@ -315,5 +318,7 @@ function createJsonFileAdapterV2(opts = {}) {
315
318
 
316
319
  export {
317
320
  createJsonFileAdapter,
321
+ canonicalize,
322
+ hashRev,
318
323
  createJsonFileAdapterV2
319
324
  };
@@ -0,0 +1,19 @@
1
+ // src/publish-hook.ts
2
+ async function firePublish(hook, opts = {}) {
3
+ if (!hook) return { ok: false, kind: "no-hook" };
4
+ const headers = { ...opts.headers ?? {} };
5
+ if (opts.token) headers.Authorization = `Bearer ${opts.token}`;
6
+ const init = { method: opts.method ?? "POST" };
7
+ if (Object.keys(headers).length > 0) init.headers = headers;
8
+ try {
9
+ const res = await fetch(hook, init);
10
+ if (!res.ok) return { ok: false, kind: "bad-response", status: res.status };
11
+ return { ok: true };
12
+ } catch {
13
+ return { ok: false, kind: "unreachable" };
14
+ }
15
+ }
16
+
17
+ export {
18
+ firePublish
19
+ };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createJsonFileAdapterV2
3
- } from "./chunk-ST44VULL.js";
3
+ } from "./chunk-L2VKQJPY.js";
4
4
 
5
5
  // src/loader/index.ts
6
6
  import { join } from "path";
@@ -1,33 +1,39 @@
1
+ import {
2
+ firePublish
3
+ } from "../chunk-PIDFNJME.js";
4
+
1
5
  // src/endpoints/publish.ts
2
6
  import { getCanciaRuntime } from "virtual:cancia/runtime";
3
7
  async function POST({ request }) {
4
- const { deployHook, secret } = getCanciaRuntime();
8
+ const { deployHook, deployHookToken, deployHookMethod, deployHookHeaders, secret } = getCanciaRuntime();
5
9
  if (secret) {
6
10
  const token = request.headers.get("Authorization")?.replace("Bearer ", "").trim();
7
11
  if (token !== secret)
8
12
  return new Response(JSON.stringify({ error: "Unauthorized" }), { status: 401 });
9
13
  }
10
14
  const hook = deployHook ?? process.env.CANCIA_DEPLOY_HOOK;
11
- if (!hook)
15
+ const result = await firePublish(hook, {
16
+ token: deployHookToken,
17
+ method: deployHookMethod,
18
+ headers: deployHookHeaders
19
+ });
20
+ if (result.ok)
21
+ return new Response(JSON.stringify({ ok: true }), {
22
+ headers: { "Content-Type": "application/json" }
23
+ });
24
+ if (result.kind === "no-hook")
12
25
  return new Response(
13
26
  JSON.stringify({ error: "No deploy hook configured. Set CANCIA_DEPLOY_HOOK." }),
14
27
  { status: 503 }
15
28
  );
16
- try {
17
- const res = await fetch(hook, { method: "POST" });
18
- if (!res.ok)
19
- return new Response(
20
- JSON.stringify({ error: `Deploy hook responded with ${res.status}` }),
21
- { status: 502 }
22
- );
23
- return new Response(JSON.stringify({ ok: true }), {
24
- headers: { "Content-Type": "application/json" }
25
- });
26
- } catch {
27
- return new Response(JSON.stringify({ error: "Failed to reach deploy hook" }), {
28
- status: 502
29
- });
30
- }
29
+ if (result.kind === "bad-response")
30
+ return new Response(
31
+ JSON.stringify({ error: `Deploy hook responded with ${result.status}` }),
32
+ { status: 502 }
33
+ );
34
+ return new Response(JSON.stringify({ error: "Failed to reach deploy hook" }), {
35
+ status: 502
36
+ });
31
37
  }
32
38
  export {
33
39
  POST
@@ -0,0 +1,98 @@
1
+ import { C as CanciaStorage, a as CanciaStorageV2 } from './types-BMlLS-OS.js';
2
+ import { G as GitHubCommitter, a as GitHubClient, F as FetchLike } from './github-client-BAZ1pW24.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
+ interface SqliteV2Options {
21
+ /** Absolute path to the .db file. Defaults to <projectRoot>/cancia.db. */
22
+ dbPath?: string;
23
+ /** Project root. Defaults to process.cwd(). Used to derive dbPath. */
24
+ projectRoot?: string;
25
+ }
26
+ declare function createSqliteAdapterV2(opts?: SqliteV2Options): CanciaStorageV2;
27
+ /**
28
+ * Close and forget the cached connection for a db path (or all connections
29
+ * when no path is given). The store interface has no lifecycle hook, so this
30
+ * is the seam for graceful shutdown and for tests that need to release the
31
+ * file handle before deleting the .db (Windows keeps WAL files locked while
32
+ * the connection is open). No-op if the path was never opened.
33
+ */
34
+ declare function closeSqliteAdapterV2(dbPath?: string): void;
35
+
36
+ interface GitBackedContentPaths {
37
+ /** KV file path. Default <projectRoot>/cancia-content.json. */
38
+ kvPath?: string;
39
+ /** Pages file path. Default <projectRoot>/.cancia/pages.json. */
40
+ pagesPath?: string;
41
+ /** Lists directory. Default <projectRoot>/.cancia/lists. */
42
+ listsDir?: string;
43
+ }
44
+ interface GitBackedOptions {
45
+ /** The wrapped local adapter — the on-disk source of truth. */
46
+ local: CanciaStorageV2;
47
+ /** "owner/name" of the GitHub repo whose builds carry the content. */
48
+ repo: string;
49
+ /** Branch to commit onto. Default "main". */
50
+ branch?: string;
51
+ /**
52
+ * GitHub PAT. Reads CANCIA_GITHUB_TOKEN if omitted. When absent entirely the
53
+ * adapter runs in local-only mode (disk writes only; no commits) + warns once.
54
+ */
55
+ token?: string;
56
+ /** Optional committer identity for commits. */
57
+ committer?: GitHubCommitter;
58
+ /**
59
+ * Project root the local adapter writes under — needed to turn absolute
60
+ * on-disk paths into repo-relative commit paths. Default process.cwd().
61
+ */
62
+ projectRoot?: string;
63
+ /** Override where the local adapter's content lives (must match `local`). */
64
+ contentPaths?: GitBackedContentPaths;
65
+ /** Quiet window (ms) before a flush fires. Default 3000. */
66
+ debounceMs?: number;
67
+ /** Commit message for content updates. */
68
+ commitMessage?: string;
69
+ /** Injected GitHub client (tests pass a mock). Overrides token/fetch. */
70
+ client?: GitHubClient;
71
+ /** Injected fetch, forwarded to the default GitHub client. */
72
+ fetch?: FetchLike;
73
+ /** API base override, forwarded to the default GitHub client (tests). */
74
+ apiBase?: string;
75
+ /** Warn sink (tests capture). Default console.warn. */
76
+ warn?: (msg: string) => void;
77
+ /** Error sink for push failures (tests capture). Default console.error. */
78
+ onError?: (msg: string, err: unknown) => void;
79
+ }
80
+ /** The extra control surface the git adapter adds on top of CanciaStorageV2. */
81
+ interface GitBackedControls {
82
+ /**
83
+ * Force any pending dirty files to commit now, bypassing the debounce.
84
+ * Resolves once the flush completes (or rejects if the push failed — the
85
+ * files stay dirty for the next flush). For tests + graceful shutdown.
86
+ */
87
+ flush(): Promise<void>;
88
+ /** True if git commits are active (token present). */
89
+ readonly gitEnabled: boolean;
90
+ /** Snapshot of currently-dirty repo-relative paths (for tests/inspection). */
91
+ pendingPaths(): string[];
92
+ }
93
+ type GitBackedStorage = CanciaStorageV2 & {
94
+ git: GitBackedControls;
95
+ };
96
+ declare function createGitBackedAdapter(opts: GitBackedOptions): GitBackedStorage;
97
+
98
+ export { type GitBackedContentPaths as G, type SqliteV2Options as S, type GitBackedControls as a, type GitBackedOptions as b, type GitBackedStorage as c, closeSqliteAdapterV2 as d, createGitBackedAdapter as e, createJsonFileAdapter as f, createJsonFileAdapterV2 as g, createSQLiteAdapter as h, createSqliteAdapterV2 as i };
@@ -0,0 +1,48 @@
1
+ /** Minimal fetch signature — matches the global `fetch` we depend on. */
2
+ type FetchLike = (input: string, init?: {
3
+ method?: string;
4
+ headers?: Record<string, string>;
5
+ body?: string;
6
+ }) => Promise<{
7
+ ok: boolean;
8
+ status: number;
9
+ json(): Promise<unknown>;
10
+ text(): Promise<string>;
11
+ }>;
12
+ interface GitHubCommitter {
13
+ name: string;
14
+ email: string;
15
+ }
16
+ interface GitHubClientOptions {
17
+ /** "owner/name" */
18
+ repo: string;
19
+ /** Branch to commit onto, e.g. "main". */
20
+ branch: string;
21
+ /** Fine-grained PAT with contents:write on the one repo. */
22
+ token: string;
23
+ /** Optional committer identity. GitHub uses the token's user if omitted. */
24
+ committer?: GitHubCommitter;
25
+ /** Injectable fetch (defaults to global fetch) — tests mock this. */
26
+ fetch?: FetchLike;
27
+ /** API base — defaults to https://api.github.com. Overridable for tests. */
28
+ apiBase?: string;
29
+ }
30
+ interface CommitFile {
31
+ /** Repo-relative path, forward slashes, no leading slash. */
32
+ path: string;
33
+ /** Raw file content (UTF-8 text). Encoded to base64 before PUT. */
34
+ content: string;
35
+ }
36
+ interface GitHubClient {
37
+ /** Current blob sha for `path`, or null if the file doesn't exist yet. */
38
+ getFileSha(path: string): Promise<string | null>;
39
+ /**
40
+ * Commit each file to the branch. Updates pass the current sha; creates omit
41
+ * it. Resolves once every PUT succeeds; rejects (without partial silence) if
42
+ * any PUT fails so the caller can keep the batch dirty and retry.
43
+ */
44
+ commitFiles(files: CommitFile[], message: string): Promise<void>;
45
+ }
46
+ declare function createGitHubClient(opts: GitHubClientOptions): GitHubClient;
47
+
48
+ export { type CommitFile as C, type FetchLike as F, type GitHubCommitter as G, type GitHubClient as a, type GitHubClientOptions as b, createGitHubClient as c };
package/dist/index.d.ts CHANGED
@@ -3,9 +3,10 @@ import { C as CanciaStorage, a as CanciaStorageV2 } from './types-BMlLS-OS.js';
3
3
  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';
4
4
  import { U as UploadHandler } from './upload-DwCGjXbz.js';
5
5
  export { m as makeLocalUploadHandler } from './upload-DwCGjXbz.js';
6
+ import { G as GitHubCommitter } from './github-client-BAZ1pW24.js';
6
7
  export { CanciaLoaderOptions, canciaLoader } from './loader/index.js';
7
8
  export { FieldDescription, FieldMeta, FieldMetaBase, FieldWidget, ListDescription, ListSchema, SchemasModule, defineField, defineList, describeList } from './schema/index.js';
8
- export { GitBackedContentPaths, GitBackedControls, GitBackedOptions, GitBackedStorage, createGitBackedAdapter, createJsonFileAdapter, createJsonFileAdapterV2, createSQLiteAdapter } from './storage/index.js';
9
+ 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';
9
10
  export { z } from 'zod';
10
11
  import 'astro/loaders';
11
12
  import './portable-text-BikSqS9T.js';
@@ -47,6 +48,17 @@ interface CanciaIntegrationOptions {
47
48
  accentColor?: string;
48
49
  /** Deploy hook URL. Reads CANCIA_DEPLOY_HOOK env var if not set. */
49
50
  deployHook?: string;
51
+ /**
52
+ * Auth token for the deploy hook (e.g. Coolify's "auth required" hook).
53
+ * When set, the publish POST sends `Authorization: Bearer <token>`.
54
+ * SECRET — prefer the CANCIA_DEPLOY_HOOK_TOKEN env var (read at runtime,
55
+ * never baked into the build). This option is a dev/testing convenience.
56
+ */
57
+ deployHookToken?: string;
58
+ /** Optional HTTP method for the deploy hook. Default: "POST". */
59
+ deployHookMethod?: string;
60
+ /** Optional extra headers merged into the deploy-hook request. */
61
+ deployHookHeaders?: Record<string, string>;
50
62
  /**
51
63
  * Custom storage adapter.
52
64
  * Default: SQLite (cancia.db in project root) — works anywhere with Node.
@@ -89,6 +101,50 @@ interface CanciaIntegrationOptions {
89
101
  * (named or default) — a record of list name → defineList() output.
90
102
  */
91
103
  schemasPath?: string;
104
+ /**
105
+ * Declarative v2 storage backend — bakes SERIALISABLY into the runtime
106
+ * descriptor so a fresh production SSR server reconstructs the same adapter
107
+ * from config + `process.cwd()` (no live object crosses the build→deploy
108
+ * boundary). Prefer this over a live `storageV2` object for production.
109
+ *
110
+ * - `{ kind: "sqlite" }` (recommended) — the everything-store: KV + pages +
111
+ * lists in ONE `.db` per site (default `<root>/cancia.db`; override with
112
+ * `path`, relative paths resolve against the project root). Gitignore the
113
+ * `.db` for webhook-publish sites — it is NOT committed (contrast the
114
+ * git-backed model).
115
+ * - `{ kind: "json-file" }` — the JSON-file v2 default (files under
116
+ * `<root>/.cancia/`).
117
+ *
118
+ * Ignored when `git` is set (git-backed wins) or `storageV2` is passed as a
119
+ * live object (dev uses that eagerly).
120
+ */
121
+ db?: {
122
+ kind: "sqlite" | "json-file";
123
+ path?: string;
124
+ };
125
+ /**
126
+ * Git-backed storage, described SERIALISABLY so a production SSR server can
127
+ * reconstruct the adapter in its own (fresh) process from this config + the
128
+ * `CANCIA_GITHUB_TOKEN` env var. Prefer this over passing a live
129
+ * `storageV2: createGitBackedAdapter(...)` object: a live adapter constructed
130
+ * during config evaluation cannot cross the build→deploy process boundary,
131
+ * so it would silently degrade to local-only in production. When `git` is
132
+ * set, Cancia builds the git-backed adapter (wrapping a JSON-file v2 local
133
+ * store) lazily at first request. The token is NEVER baked — it is read from
134
+ * `process.env.CANCIA_GITHUB_TOKEN` at runtime.
135
+ */
136
+ git?: {
137
+ /** "owner/name" of the GitHub repo whose builds carry the content. */
138
+ repo: string;
139
+ /** Branch to commit onto. Default "main". */
140
+ branch?: string;
141
+ /** Optional committer identity for commits. */
142
+ committer?: GitHubCommitter;
143
+ /** Quiet window (ms) before a flush fires. Default 3000. */
144
+ debounceMs?: number;
145
+ /** Commit message for content updates. */
146
+ commitMessage?: string;
147
+ };
92
148
  }
93
149
  declare function canciaIntegration(opts?: CanciaIntegrationOptions): AstroIntegration;
94
150