@cancia/astro 0.1.0 → 0.2.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-SXKZ2WUL.js +305 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -1
- package/dist/storage/index.d.ts +110 -1
- package/dist/storage/index.js +5 -1
- package/package.json +1 -1
- package/dist/chunk-AE4SIY24.js +0 -63
|
@@ -0,0 +1,305 @@
|
|
|
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
|
+
// src/storage/github-client.ts
|
|
62
|
+
function toBase64(text) {
|
|
63
|
+
if (typeof Buffer !== "undefined") {
|
|
64
|
+
return Buffer.from(text, "utf-8").toString("base64");
|
|
65
|
+
}
|
|
66
|
+
const bytes = new TextEncoder().encode(text);
|
|
67
|
+
let binary = "";
|
|
68
|
+
for (const b of bytes) binary += String.fromCharCode(b);
|
|
69
|
+
return btoa(binary);
|
|
70
|
+
}
|
|
71
|
+
function createGitHubClient(opts) {
|
|
72
|
+
const { repo, branch, token, committer } = opts;
|
|
73
|
+
const doFetch = opts.fetch ?? globalThis.fetch;
|
|
74
|
+
const apiBase = (opts.apiBase ?? "https://api.github.com").replace(/\/$/, "");
|
|
75
|
+
if (!doFetch) {
|
|
76
|
+
throw new Error("createGitHubClient: no fetch available (pass opts.fetch)");
|
|
77
|
+
}
|
|
78
|
+
const headers = () => ({
|
|
79
|
+
Authorization: `Bearer ${token}`,
|
|
80
|
+
Accept: "application/vnd.github+json",
|
|
81
|
+
"X-GitHub-Api-Version": "2022-11-28"
|
|
82
|
+
});
|
|
83
|
+
const contentsUrl = (path) => {
|
|
84
|
+
const encoded = path.split("/").map((seg) => encodeURIComponent(seg)).join("/");
|
|
85
|
+
return `${apiBase}/repos/${repo}/contents/${encoded}`;
|
|
86
|
+
};
|
|
87
|
+
async function getFileSha(path) {
|
|
88
|
+
const url = `${contentsUrl(path)}?ref=${encodeURIComponent(branch)}`;
|
|
89
|
+
const res = await doFetch(url, { method: "GET", headers: headers() });
|
|
90
|
+
if (res.status === 404) return null;
|
|
91
|
+
if (!res.ok) {
|
|
92
|
+
const detail = await res.text().catch(() => "");
|
|
93
|
+
throw new Error(`GitHub getFileSha ${path} failed: ${res.status} ${detail}`);
|
|
94
|
+
}
|
|
95
|
+
const body = await res.json();
|
|
96
|
+
return body.sha ?? null;
|
|
97
|
+
}
|
|
98
|
+
async function putFile(file, message) {
|
|
99
|
+
const sha = await getFileSha(file.path);
|
|
100
|
+
const payload = {
|
|
101
|
+
message,
|
|
102
|
+
content: toBase64(file.content),
|
|
103
|
+
branch
|
|
104
|
+
};
|
|
105
|
+
if (sha) payload.sha = sha;
|
|
106
|
+
if (committer) payload.committer = committer;
|
|
107
|
+
const res = await doFetch(contentsUrl(file.path), {
|
|
108
|
+
method: "PUT",
|
|
109
|
+
headers: headers(),
|
|
110
|
+
body: JSON.stringify(payload)
|
|
111
|
+
});
|
|
112
|
+
if (!res.ok) {
|
|
113
|
+
const detail = await res.text().catch(() => "");
|
|
114
|
+
throw new Error(`GitHub commit ${file.path} failed: ${res.status} ${detail}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async function commitFiles(files, message) {
|
|
118
|
+
for (const file of files) {
|
|
119
|
+
await putFile(file, message);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return { getFileSha, commitFiles };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// src/storage/git-backed.ts
|
|
126
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "fs";
|
|
127
|
+
import { join, relative } from "path";
|
|
128
|
+
function createGitBackedAdapter(opts) {
|
|
129
|
+
const projectRoot = opts.projectRoot ?? process.cwd();
|
|
130
|
+
const branch = opts.branch ?? "main";
|
|
131
|
+
const debounceMs = opts.debounceMs ?? 3e3;
|
|
132
|
+
const commitMessage = opts.commitMessage ?? "Cancia: content update";
|
|
133
|
+
const warn = opts.warn ?? ((m) => console.warn(m));
|
|
134
|
+
const onError = opts.onError ?? ((m, e) => console.error(m, e));
|
|
135
|
+
const kvPath = opts.contentPaths?.kvPath ?? join(projectRoot, "cancia-content.json");
|
|
136
|
+
const pagesPath = opts.contentPaths?.pagesPath ?? join(projectRoot, ".cancia", "pages.json");
|
|
137
|
+
const listsDir = opts.contentPaths?.listsDir ?? join(projectRoot, ".cancia", "lists");
|
|
138
|
+
const token = opts.token ?? process.env.CANCIA_GITHUB_TOKEN ?? "";
|
|
139
|
+
let client = null;
|
|
140
|
+
if (opts.client) {
|
|
141
|
+
client = opts.client;
|
|
142
|
+
} else if (token) {
|
|
143
|
+
client = createGitHubClient({
|
|
144
|
+
repo: opts.repo,
|
|
145
|
+
branch,
|
|
146
|
+
token,
|
|
147
|
+
committer: opts.committer,
|
|
148
|
+
fetch: opts.fetch,
|
|
149
|
+
apiBase: opts.apiBase
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
const gitEnabled = client !== null;
|
|
153
|
+
if (!gitEnabled) {
|
|
154
|
+
warn(
|
|
155
|
+
"[cancia] Git-backed storage: no GitHub token (CANCIA_GITHUB_TOKEN) \u2014 running local-only. Edits save to disk but are NOT committed/pushed."
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
const dirty = /* @__PURE__ */ new Set();
|
|
159
|
+
let timer = null;
|
|
160
|
+
let flushing = null;
|
|
161
|
+
let rerunRequested = false;
|
|
162
|
+
function toRepoPath(absPath) {
|
|
163
|
+
return relative(projectRoot, absPath).split("\\").join("/");
|
|
164
|
+
}
|
|
165
|
+
function markDirty(absPath) {
|
|
166
|
+
dirty.add(absPath);
|
|
167
|
+
}
|
|
168
|
+
function markListDirty(listName, site) {
|
|
169
|
+
const siteDir = join(listsDir, listName, site);
|
|
170
|
+
if (!existsSync(siteDir)) return;
|
|
171
|
+
const walk = (dir) => {
|
|
172
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
173
|
+
const full = join(dir, entry.name);
|
|
174
|
+
if (entry.isDirectory()) walk(full);
|
|
175
|
+
else if (entry.isFile()) markDirty(full);
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
walk(siteDir);
|
|
179
|
+
}
|
|
180
|
+
function scheduleFlush() {
|
|
181
|
+
if (!gitEnabled) return;
|
|
182
|
+
if (timer) clearTimeout(timer);
|
|
183
|
+
timer = setTimeout(() => {
|
|
184
|
+
timer = null;
|
|
185
|
+
void runFlush();
|
|
186
|
+
}, debounceMs);
|
|
187
|
+
}
|
|
188
|
+
async function runFlush() {
|
|
189
|
+
if (flushing) {
|
|
190
|
+
rerunRequested = true;
|
|
191
|
+
return flushing;
|
|
192
|
+
}
|
|
193
|
+
flushing = doFlush().finally(() => {
|
|
194
|
+
flushing = null;
|
|
195
|
+
if (rerunRequested) {
|
|
196
|
+
rerunRequested = false;
|
|
197
|
+
void runFlush();
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
return flushing;
|
|
201
|
+
}
|
|
202
|
+
async function doFlush() {
|
|
203
|
+
if (!client || dirty.size === 0) return;
|
|
204
|
+
const batch = [...dirty];
|
|
205
|
+
const files = [];
|
|
206
|
+
for (const abs of batch) {
|
|
207
|
+
if (!existsSync(abs) || !statSync(abs).isFile()) continue;
|
|
208
|
+
files.push({ path: toRepoPath(abs), content: readFileSync(abs, "utf-8") });
|
|
209
|
+
}
|
|
210
|
+
if (files.length === 0) {
|
|
211
|
+
for (const abs of batch) dirty.delete(abs);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
try {
|
|
215
|
+
await client.commitFiles(files, commitMessage);
|
|
216
|
+
for (const abs of batch) dirty.delete(abs);
|
|
217
|
+
} catch (err) {
|
|
218
|
+
onError(
|
|
219
|
+
"[cancia] Git-backed storage: commit failed \u2014 data saved locally, will retry on next flush.",
|
|
220
|
+
err
|
|
221
|
+
);
|
|
222
|
+
throw err;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
async function flush() {
|
|
226
|
+
if (!gitEnabled) return;
|
|
227
|
+
if (timer) {
|
|
228
|
+
clearTimeout(timer);
|
|
229
|
+
timer = null;
|
|
230
|
+
}
|
|
231
|
+
await runFlush();
|
|
232
|
+
}
|
|
233
|
+
const kv = {
|
|
234
|
+
get: (site, key, lang) => opts.local.kv.get(site, key, lang),
|
|
235
|
+
getAll: (site) => opts.local.kv.getAll(site),
|
|
236
|
+
async set(site, key, lang, value) {
|
|
237
|
+
await opts.local.kv.set(site, key, lang, value);
|
|
238
|
+
markDirty(kvPath);
|
|
239
|
+
scheduleFlush();
|
|
240
|
+
},
|
|
241
|
+
async delete(site, key, lang) {
|
|
242
|
+
await opts.local.kv.delete(site, key, lang);
|
|
243
|
+
markDirty(kvPath);
|
|
244
|
+
scheduleFlush();
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
const pages = {
|
|
248
|
+
get: (site, route) => opts.local.pages.get(site, route),
|
|
249
|
+
list: (site) => opts.local.pages.list(site),
|
|
250
|
+
async set(site, route, meta, rev) {
|
|
251
|
+
const result = await opts.local.pages.set(site, route, meta, rev);
|
|
252
|
+
markDirty(pagesPath);
|
|
253
|
+
scheduleFlush();
|
|
254
|
+
return result;
|
|
255
|
+
},
|
|
256
|
+
async delete(site, route) {
|
|
257
|
+
await opts.local.pages.delete(site, route);
|
|
258
|
+
markDirty(pagesPath);
|
|
259
|
+
scheduleFlush();
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
const lists = {
|
|
263
|
+
list: (site, listName, locale) => opts.local.lists.list(site, listName, locale),
|
|
264
|
+
get: (site, listName, id, locale) => opts.local.lists.get(site, listName, id, locale),
|
|
265
|
+
translations: (site, listName) => opts.local.lists.translations(site, listName),
|
|
266
|
+
async create(site, listName, data, locale, id) {
|
|
267
|
+
const entry = await opts.local.lists.create(site, listName, data, locale, id);
|
|
268
|
+
markListDirty(listName, site);
|
|
269
|
+
scheduleFlush();
|
|
270
|
+
return entry;
|
|
271
|
+
},
|
|
272
|
+
async update(site, listName, id, locale, data, rev) {
|
|
273
|
+
const entry = await opts.local.lists.update(site, listName, id, locale, data, rev);
|
|
274
|
+
markListDirty(listName, site);
|
|
275
|
+
scheduleFlush();
|
|
276
|
+
return entry;
|
|
277
|
+
},
|
|
278
|
+
async delete(site, listName, id, locale) {
|
|
279
|
+
await opts.local.lists.delete(site, listName, id, locale);
|
|
280
|
+
markListDirty(listName, site);
|
|
281
|
+
scheduleFlush();
|
|
282
|
+
},
|
|
283
|
+
async reorder(site, listName, ids) {
|
|
284
|
+
await opts.local.lists.reorder(site, listName, ids);
|
|
285
|
+
markListDirty(listName, site);
|
|
286
|
+
scheduleFlush();
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
const git = {
|
|
290
|
+
flush,
|
|
291
|
+
get gitEnabled() {
|
|
292
|
+
return gitEnabled;
|
|
293
|
+
},
|
|
294
|
+
pendingPaths() {
|
|
295
|
+
return [...dirty].map(toRepoPath);
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
return { kv, pages, lists, git };
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export {
|
|
302
|
+
createSQLiteAdapter,
|
|
303
|
+
createGitHubClient,
|
|
304
|
+
createGitBackedAdapter
|
|
305
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ 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
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
|
+
export { GitBackedContentPaths, GitBackedControls, GitBackedOptions, GitBackedStorage, createGitBackedAdapter, createJsonFileAdapter, createJsonFileAdapterV2, createSQLiteAdapter } from './storage/index.js';
|
|
9
9
|
export { z } from 'zod';
|
|
10
10
|
import 'astro/loaders';
|
|
11
11
|
import './portable-text-BikSqS9T.js';
|
package/dist/index.js
CHANGED
|
@@ -18,8 +18,9 @@ import {
|
|
|
18
18
|
canciaLoader
|
|
19
19
|
} from "./chunk-337LJIKX.js";
|
|
20
20
|
import {
|
|
21
|
+
createGitBackedAdapter,
|
|
21
22
|
createSQLiteAdapter
|
|
22
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-SXKZ2WUL.js";
|
|
23
24
|
import {
|
|
24
25
|
createJsonFileAdapter,
|
|
25
26
|
createJsonFileAdapterV2
|
|
@@ -645,6 +646,7 @@ export {
|
|
|
645
646
|
RevConflictError,
|
|
646
647
|
canciaIntegration,
|
|
647
648
|
canciaLoader,
|
|
649
|
+
createGitBackedAdapter,
|
|
648
650
|
createJsonFileAdapter,
|
|
649
651
|
createJsonFileAdapterV2,
|
|
650
652
|
createSQLiteAdapter,
|
package/dist/storage/index.d.ts
CHANGED
|
@@ -17,4 +17,113 @@ declare function createJsonFileAdapterV2(opts?: JsonFileV2Options): CanciaStorag
|
|
|
17
17
|
|
|
18
18
|
declare function createSQLiteAdapter(dbPath?: string): CanciaStorage;
|
|
19
19
|
|
|
20
|
-
|
|
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 };
|
package/dist/storage/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
+
createGitBackedAdapter,
|
|
3
|
+
createGitHubClient,
|
|
2
4
|
createSQLiteAdapter
|
|
3
|
-
} from "../chunk-
|
|
5
|
+
} from "../chunk-SXKZ2WUL.js";
|
|
4
6
|
import {
|
|
5
7
|
createJsonFileAdapter,
|
|
6
8
|
createJsonFileAdapterV2
|
|
@@ -10,6 +12,8 @@ import {
|
|
|
10
12
|
} from "../chunk-7IA5B5CF.js";
|
|
11
13
|
export {
|
|
12
14
|
RevConflictError,
|
|
15
|
+
createGitBackedAdapter,
|
|
16
|
+
createGitHubClient,
|
|
13
17
|
createJsonFileAdapter,
|
|
14
18
|
createJsonFileAdapterV2,
|
|
15
19
|
createSQLiteAdapter
|
package/package.json
CHANGED
package/dist/chunk-AE4SIY24.js
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
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
|
-
};
|