forgepress 0.0.1 → 0.0.2

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 (56) hide show
  1. package/dist/THIRD-PARTY-LICENSES.md +28 -83
  2. package/dist/_chunks/client.d.mts +1 -1
  3. package/dist/_chunks/config.mjs +61 -19
  4. package/dist/_chunks/{validate.mjs → content.mjs} +350 -217
  5. package/dist/_chunks/fetch.mjs +2 -2
  6. package/dist/_chunks/files.mjs +32 -0
  7. package/dist/_chunks/libs/diff.mjs +485 -0
  8. package/dist/_chunks/media.mjs +282 -0
  9. package/dist/_chunks/once.mjs +16 -0
  10. package/dist/_chunks/output.mjs +98 -252
  11. package/dist/_chunks/plugin.mjs +87 -0
  12. package/dist/_chunks/preview.mjs +248 -0
  13. package/dist/_chunks/project.d.mts +7 -0
  14. package/dist/_chunks/reader.mjs +5 -9
  15. package/dist/_chunks/reader2.mjs +150 -202
  16. package/dist/_chunks/references.mjs +8 -5
  17. package/dist/_chunks/resolve.d.mts +2 -0
  18. package/dist/_chunks/response.mjs +5 -0
  19. package/dist/_chunks/routes.mjs +9 -0
  20. package/dist/_chunks/serialize.mjs +82 -0
  21. package/dist/_chunks/settings.mjs +328 -0
  22. package/dist/_chunks/settings2.mjs +2 -0
  23. package/dist/_chunks/types.d.mts +229 -24
  24. package/dist/_chunks/types2.d.mts +25 -0
  25. package/dist/_chunks/value.mjs +123 -1
  26. package/dist/cli/bin.mjs +50 -37
  27. package/dist/editor/index.mjs +9961 -8653
  28. package/dist/index.d.mts +2 -3
  29. package/dist/index.mjs +14 -24
  30. package/dist/next/preview.d.mts +1 -0
  31. package/dist/next/preview.mjs +3 -0
  32. package/dist/next/reload.d.mts +1 -0
  33. package/dist/next/reload.mjs +20 -0
  34. package/dist/next/settings.d.mts +12 -0
  35. package/dist/next/settings.mjs +2 -0
  36. package/dist/plugin/next.d.mts +11 -0
  37. package/dist/plugin/next.mjs +213 -0
  38. package/dist/plugin/nuxt.d.mts +7 -0
  39. package/dist/plugin/nuxt.mjs +52 -0
  40. package/dist/plugin/watcher.d.mts +1 -0
  41. package/dist/plugin/watcher.mjs +23 -0
  42. package/dist/preview/index.d.mts +1 -2
  43. package/dist/preview/index.mjs +1 -243
  44. package/dist/preview/react.d.mts +1 -0
  45. package/dist/preview/react.mjs +34 -0
  46. package/dist/unplugin.d.mts +12 -17
  47. package/dist/unplugin.mjs +1 -500
  48. package/package.json +72 -8
  49. package/dist/_chunks/config.d.mts +0 -35
  50. package/dist/_chunks/entry.d.mts +0 -194
  51. package/dist/_chunks/libs/@oxc-project/types.d.mts +0 -1297
  52. package/dist/_chunks/libs/@rolldown/pluginutils.d.mts +0 -62
  53. package/dist/_chunks/libs/rolldown.d.mts +0 -4711
  54. package/dist/_chunks/output2.mjs +0 -333
  55. package/dist/_chunks/parse.mjs +0 -22
  56. package/dist/_chunks/types.mjs +0 -2
@@ -1,7 +1,11 @@
1
1
  import { isRecord, plain, same } from "./value.mjs";
2
- import { assetUrl, base64ToText, createOutput, createPaths, isEntryFile, isEntryId, isMediaFile, prefixer, sortByCreation, textToBase64 } from "./output.mjs";
3
- import "./validate.mjs";
4
- import { parseEntry, parseSchema } from "./parse.mjs";
2
+ import { once } from "./once.mjs";
3
+ import { assetUrl, base64ToText, createOutput, createPaths, isEntryId, isMediaFile, repositoryPaths, sortByCreation, textToBase64 } from "./media.mjs";
4
+ import { createFileSource } from "./content.mjs";
5
+ import "./serialize.mjs";
6
+ import "./libs/diff.mjs";
7
+ import { AuthorizationResponseError, None, ResponseBodyError, allowInsecureRequests, processRefreshTokenResponse, refreshTokenGrantRequest } from "oauth4webapi";
8
+ import { clear, createStore, del, delMany, entries, get, set } from "idb-keyval";
5
9
  function mergeEntries(rows, overlay) {
6
10
  if (!overlay) return [...rows];
7
11
  const merged = new Map(rows.map((row) => [row.id, row]));
@@ -41,6 +45,10 @@ function createContentChanges(base, ready, mutate) {
41
45
  function publishedUploads(changes) {
42
46
  return Object.values(changes.publishedMedia ?? {}).filter(Boolean);
43
47
  }
48
+ function localUploads(changes) {
49
+ const removed = new Set(changes.removed);
50
+ return [...new Map([...Object.values(changes.uploads), ...publishedUploads(changes)].map((upload) => [upload.name, upload])).values()].filter((upload) => !removed.has(upload.name));
51
+ }
44
52
  function createPreviews() {
45
53
  const urls = /* @__PURE__ */ new Map();
46
54
  function preview(upload) {
@@ -76,6 +84,19 @@ function createPreviews() {
76
84
  }
77
85
  };
78
86
  }
87
+ function emptyChanges() {
88
+ return {
89
+ entries: {},
90
+ uploads: {},
91
+ removed: []
92
+ };
93
+ }
94
+ async function readChanges(store) {
95
+ return {
96
+ ...emptyChanges(),
97
+ ...await store.read()
98
+ };
99
+ }
79
100
  const PROVIDERS = {
80
101
  github: {
81
102
  name: "GitHub",
@@ -118,6 +139,7 @@ function describe(config) {
118
139
  api: `${root}/api/v4`,
119
140
  scopes: config.scopes ?? ["api"],
120
141
  oauth: {
142
+ issuer: root,
121
143
  authorize: `${root}/oauth/authorize`,
122
144
  token: `${root}/oauth/token`
123
145
  }
@@ -127,6 +149,7 @@ function describe(config) {
127
149
  api: `${root}/api/v1`,
128
150
  scopes: config.scopes ?? ["read:user", "write:repository"],
129
151
  oauth: {
152
+ issuer: root,
130
153
  authorize: `${root}/login/oauth/authorize`,
131
154
  token: `${root}/login/oauth/access_token`
132
155
  }
@@ -136,6 +159,19 @@ function publishable(files) {
136
159
  if (files.length === 0) throw new Error("[forgepress] there is nothing to publish");
137
160
  return files;
138
161
  }
162
+ function toIdentity(login, name, avatar) {
163
+ return {
164
+ login,
165
+ ...name ? { name } : {},
166
+ ...avatar ? { avatar } : {}
167
+ };
168
+ }
169
+ function blobs(directory, items) {
170
+ return items.filter((item) => item.type === "blob").map((item) => ({
171
+ path: `${directory}/${item.path}`,
172
+ sha: item.sha
173
+ }));
174
+ }
139
175
  function createRepositoryApi(config, base, token, headers = {}) {
140
176
  const { name } = describe(config);
141
177
  let known = config.repository.branch;
@@ -158,6 +194,11 @@ function createRepositoryApi(config, base, token, headers = {}) {
158
194
  const response = await check(await send(path, init));
159
195
  return response.status === 204 ? void 0 : await response.json();
160
196
  }
197
+ async function find(path, missing = [404]) {
198
+ const response = await send(path);
199
+ if (missing.includes(response.status)) return void 0;
200
+ return await (await check(response)).json();
201
+ }
161
202
  function post(path, body) {
162
203
  return call(path, {
163
204
  method: "POST",
@@ -176,6 +217,7 @@ function createRepositoryApi(config, base, token, headers = {}) {
176
217
  send,
177
218
  check,
178
219
  call,
220
+ find,
179
221
  post,
180
222
  details,
181
223
  branch
@@ -213,19 +255,13 @@ function createForgejoForge(config, token) {
213
255
  }
214
256
  }
215
257
  async function sha(path, ref) {
216
- const response = await repo.send(`/contents/${encodePath(path)}?ref=${encodeURIComponent(ref)}`);
217
- if (response.status === 404) return void 0;
218
- return (await (await repo.check(response)).json()).sha;
258
+ return (await repo.find(`/contents/${encodePath(path)}?ref=${encodeURIComponent(ref)}`))?.sha;
219
259
  }
220
260
  return {
221
261
  async access() {
222
262
  const [user, repository] = await Promise.all([repo.call(`${api}/user`), repo.details()]);
223
263
  return {
224
- identity: {
225
- login: user.login,
226
- ...user.full_name ? { name: user.full_name } : {},
227
- ...user.avatar_url ? { avatar: user.avatar_url } : {}
228
- },
264
+ identity: toIdentity(user.login, user.full_name, user.avatar_url),
229
265
  writable: repository.permissions?.push === true,
230
266
  branch: await repo.branch()
231
267
  };
@@ -236,10 +272,7 @@ function createForgejoForge(config, token) {
236
272
  async files(commit, directory) {
237
273
  const tree = await findTree(commit, directory, (found) => entries(found, false));
238
274
  if (!tree) return [];
239
- return (await entries(tree, true)).filter((item) => item.type === "blob").map((item) => ({
240
- path: `${directory}/${item.path}`,
241
- sha: item.sha
242
- }));
275
+ return blobs(directory, await entries(tree, true));
243
276
  },
244
277
  async read(blob) {
245
278
  return base64ToText((await repo.call(`/git/blobs/${blob}`)).content);
@@ -288,6 +321,7 @@ const FAILED = /* @__PURE__ */ new Set([
288
321
  "timed_out",
289
322
  "startup_failure"
290
323
  ]);
324
+ const UNREADABLE = [403, 404];
291
325
  function runState(status, conclusion) {
292
326
  if (status !== "completed" || conclusion === "action_required") return "pending";
293
327
  if (conclusion === "success") return "success";
@@ -307,27 +341,22 @@ function createGitHubForge(config, token) {
307
341
  "x-github-api-version": "2022-11-28"
308
342
  });
309
343
  let apps = true;
310
- async function readable(path) {
311
- const response = await repo.send(path);
312
- if (response.status === 403 || response.status === 404) return void 0;
313
- return await (await repo.check(response)).json();
314
- }
315
344
  async function runs(commit) {
316
- return (await readable(`/actions/runs?head_sha=${commit}&per_page=100`))?.workflow_runs.filter((run) => BUILD_EVENTS.has(run.event) || run.path.startsWith("dynamic/pages/")).map((run) => ({
345
+ return (await repo.find(`/actions/runs?head_sha=${commit}&per_page=100`, UNREADABLE))?.workflow_runs.filter((run) => BUILD_EVENTS.has(run.event) || run.path.startsWith("dynamic/pages/")).map((run) => ({
317
346
  name: run.name ?? run.path,
318
347
  state: runState(run.status, run.conclusion),
319
348
  url: run.html_url
320
349
  }));
321
350
  }
322
351
  async function statuses(commit) {
323
- return (await readable(`/commits/${commit}/status?per_page=100`))?.statuses.map((status) => ({
352
+ return (await repo.find(`/commits/${commit}/status?per_page=100`, UNREADABLE))?.statuses.map((status) => ({
324
353
  name: status.context,
325
354
  state: statusState(status.state),
326
355
  ...linked(status.target_url)
327
356
  }));
328
357
  }
329
358
  async function others(commit) {
330
- const found = apps ? await readable(`/commits/${commit}/check-runs?per_page=100`) : void 0;
359
+ const found = apps ? await repo.find(`/commits/${commit}/check-runs?per_page=100`, UNREADABLE) : void 0;
331
360
  if (!found) {
332
361
  apps = false;
333
362
  return [];
@@ -342,11 +371,7 @@ function createGitHubForge(config, token) {
342
371
  async access() {
343
372
  const [user, repository] = await Promise.all([repo.call(`${api}/user`), repo.details()]);
344
373
  return {
345
- identity: {
346
- login: user.login,
347
- ...user.name ? { name: user.name } : {},
348
- ...user.avatar_url ? { avatar: user.avatar_url } : {}
349
- },
374
+ identity: toIdentity(user.login, user.name, user.avatar_url),
350
375
  writable: repository.permissions?.push === true,
351
376
  branch: await repo.branch()
352
377
  };
@@ -359,10 +384,7 @@ function createGitHubForge(config, token) {
359
384
  if (!sha) return [];
360
385
  const tree = await repo.call(`/git/trees/${sha}?recursive=1`);
361
386
  if (tree.truncated) throw new Error(`[forgepress] GitHub lists too many files in ${directory} to read them in one request`);
362
- return tree.tree.filter((item) => item.type === "blob").map((item) => ({
363
- path: `${directory}/${item.path}`,
364
- sha: item.sha
365
- }));
387
+ return blobs(directory, tree.tree);
366
388
  },
367
389
  async read(sha) {
368
390
  return (await repo.check(await repo.send(`/git/blobs/${sha}`, { headers: { accept: "application/vnd.github.raw+json" } }))).text();
@@ -449,20 +471,14 @@ function nextPage(link) {
449
471
  function createGitLabForge(config, token) {
450
472
  const { api } = describe(config);
451
473
  const repo = createRepositoryApi(config, `${api}/projects/${encodeURIComponent(`${config.repository.owner}/${config.repository.name}`)}`, token);
452
- async function existing(path, ref) {
453
- const response = await repo.send(`/repository/files/${encodeURIComponent(path)}?ref=${encodeURIComponent(ref)}`);
454
- if (response.status === 404) return void 0;
455
- return await (await repo.check(response)).json();
474
+ function existing(path, ref) {
475
+ return repo.find(`/repository/files/${encodeURIComponent(path)}?ref=${encodeURIComponent(ref)}`);
456
476
  }
457
477
  return {
458
478
  async access() {
459
479
  const [user, project] = await Promise.all([repo.call(`${api}/user`), repo.details()]);
460
480
  return {
461
- identity: {
462
- login: user.username,
463
- ...user.name ? { name: user.name } : {},
464
- ...user.avatar_url ? { avatar: user.avatar_url } : {}
465
- },
481
+ identity: toIdentity(user.username, user.name, user.avatar_url),
466
482
  writable: writable(project),
467
483
  branch: await repo.branch()
468
484
  };
@@ -534,38 +550,40 @@ function createForge(config, token) {
534
550
  return createGitHubForge(config, token);
535
551
  }
536
552
  const SKEW = 3e4;
537
- function toTokens(payload, now = Date.now()) {
538
- const access = payload.access_token;
539
- if (typeof access !== "string" || !access) throw new Error("[forgepress] the forge returned no access token");
540
- const refresh = payload.refresh_token;
541
- const expires = payload.expires_in;
553
+ function toTokens(response, now = Date.now()) {
542
554
  return {
543
- access,
544
- ...typeof refresh === "string" && refresh ? { refresh } : {},
545
- ...typeof expires === "number" ? { expires: now + expires * 1e3 } : {}
555
+ access: response.access_token,
556
+ ...response.refresh_token ? { refresh: response.refresh_token } : {},
557
+ ...response.expires_in === void 0 ? {} : { expires: now + response.expires_in * 1e3 }
546
558
  };
547
559
  }
548
560
  function expired(tokens, now = Date.now()) {
549
561
  return tokens.expires !== void 0 && tokens.expires - SKEW <= now;
550
562
  }
551
- async function form(endpoints, body) {
552
- const response = await fetch(endpoints.token, {
553
- method: "POST",
554
- headers: {
555
- "accept": "application/json",
556
- "content-type": "application/x-www-form-urlencoded"
557
- },
558
- body: new URLSearchParams(body).toString()
559
- });
560
- if (!response.ok) throw new Error(`[forgepress] the forge rejected the token request (${response.status}): ${(await response.text()).slice(0, 200)}`);
561
- return toTokens(await response.json());
562
- }
563
- function renew(endpoints, clientId, refresh) {
564
- return form(endpoints, {
565
- client_id: clientId,
566
- grant_type: "refresh_token",
567
- refresh_token: refresh
568
- });
563
+ function server(endpoints) {
564
+ return {
565
+ issuer: endpoints.issuer,
566
+ authorization_endpoint: endpoints.authorize,
567
+ token_endpoint: endpoints.token
568
+ };
569
+ }
570
+ function requestOptions(endpoints) {
571
+ return { [allowInsecureRequests]: new URL(endpoints.token).protocol === "http:" };
572
+ }
573
+ function refusal(error) {
574
+ if (error instanceof AuthorizationResponseError) return new Error(`[forgepress] the forge refused the sign-in: ${error.error_description || error.error}`, { cause: error });
575
+ if (error instanceof ResponseBodyError) return new Error(`[forgepress] the forge rejected the token request: ${error.error_description || error.error}`, { cause: error });
576
+ return error;
577
+ }
578
+ async function renew(endpoints, clientId, refresh) {
579
+ const as = server(endpoints);
580
+ const client = { client_id: clientId };
581
+ try {
582
+ const response = await refreshTokenGrantRequest(as, client, None(), refresh, requestOptions(endpoints));
583
+ return toTokens(await processRefreshTokenResponse(as, client, response));
584
+ } catch (error) {
585
+ throw refusal(error);
586
+ }
569
587
  }
570
588
  function storedTokens(value) {
571
589
  if (typeof value === "string") return value ? { access: value } : void 0;
@@ -576,6 +594,22 @@ async function refreshed(tokens, config) {
576
594
  if (!expired(tokens) || !tokens.refresh || !oauth || !config.clientId) return tokens;
577
595
  return renew(oauth, config.clientId, tokens.refresh);
578
596
  }
597
+ function createTokenGetter(config, read, write) {
598
+ let renewal;
599
+ async function renewed(tokens) {
600
+ const next = await refreshed(tokens, config);
601
+ if (next !== tokens) await write(next);
602
+ return next;
603
+ }
604
+ return async () => {
605
+ const tokens = await read();
606
+ if (!expired(tokens)) return tokens.access;
607
+ renewal ??= renewed(tokens).finally(() => {
608
+ renewal = void 0;
609
+ });
610
+ return (await renewal).access;
611
+ };
612
+ }
579
613
  const CONCURRENCY = 8;
580
614
  function limit(size) {
581
615
  const waiting = [];
@@ -592,12 +626,12 @@ function limit(size) {
592
626
  }
593
627
  };
594
628
  }
595
- function createForgeSource(forge, paths, base, cache, mediaDir) {
596
- const at = prefixer(base);
629
+ function createForgeSource(forge, target, cache) {
630
+ const { paths, mediaDir } = target;
597
631
  const queue = limit(CONCURRENCY);
598
632
  const texts = /* @__PURE__ */ new Map();
599
- let snapshot;
600
633
  let pinned;
634
+ let snapshot = once(load);
601
635
  function client() {
602
636
  const current = forge();
603
637
  if (!current) throw new Error("[forgepress] sign in to read the content from the repository");
@@ -614,25 +648,20 @@ function createForgeSource(forge, paths, base, cache, mediaDir) {
614
648
  if (forge()) cache?.writeListing(commit, directory, listed).catch(() => void 0);
615
649
  return listed;
616
650
  }
651
+ async function uploads(commit) {
652
+ if (mediaDir === void 0) return [];
653
+ return [...(await listing(commit, mediaDir)).keys()].map((path) => path.slice(mediaDir.length + 1)).filter(isMediaFile);
654
+ }
617
655
  async function load() {
618
656
  const commit = pinned ?? await client().head();
619
- const listed = await listing(commit, at(paths.dir));
657
+ const listed = await listing(commit, paths.dir);
620
658
  await restore(new Set(listed.values()));
621
659
  return {
622
660
  commit,
623
- files: listed
661
+ files: listed,
662
+ media: once(() => uploads(commit))
624
663
  };
625
664
  }
626
- function current() {
627
- if (!snapshot) {
628
- const loading = load();
629
- snapshot = loading;
630
- loading.catch(() => {
631
- if (snapshot === loading) snapshot = void 0;
632
- });
633
- }
634
- return snapshot;
635
- }
636
665
  function text(sha) {
637
666
  let pending = texts.get(sha);
638
667
  if (!pending) {
@@ -642,129 +671,57 @@ function createForgeSource(forge, paths, base, cache, mediaDir) {
642
671
  }
643
672
  return pending;
644
673
  }
645
- async function files() {
646
- return (await current()).files;
647
- }
648
674
  async function hash(path) {
649
- return (await files()).get(path);
650
- }
651
- async function media() {
652
- if (mediaDir === void 0) return [];
653
- const found = await current();
654
- const directory = `${at(mediaDir)}/`;
655
- if (!found.media) {
656
- const loading = listing(found.commit, at(mediaDir)).then((listed) => [...listed.keys()].map((path) => path.slice(directory.length)).filter(isMediaFile));
657
- found.media = loading;
658
- loading.catch(() => {
659
- if (found.media === loading) delete found.media;
660
- });
661
- }
662
- return found.media;
663
- }
664
- async function list(collection) {
665
- const directory = `${at(paths.collection(collection))}/`;
666
- const rows = [...await files()].filter(([path]) => path.startsWith(directory) && isEntryFile(path.slice(directory.length))).map(async ([path, sha]) => parseEntry(await text(sha), path));
667
- return sortByCreation(await Promise.all(rows));
675
+ return (await snapshot()).files.get(path);
668
676
  }
669
677
  return {
670
- schema: async () => {
671
- const path = at(paths.schema);
672
- const sha = await hash(path);
673
- if (sha === void 0) throw new Error(`[forgepress] ${path} does not exist in the repository`);
674
- return parseSchema(await text(sha), path);
675
- },
676
- list,
677
- entry: async (collection, id) => {
678
- const path = at(paths.entry(collection, id));
679
- const sha = isEntryId(id) ? await hash(path) : void 0;
680
- return sha === void 0 ? void 0 : parseEntry(await text(sha), path);
681
- },
682
- hashes: { entry: async (collection, id) => isEntryId(id) ? hash(at(paths.entry(collection, id))) : void 0 },
683
- media,
678
+ ...createFileSource({
679
+ list: async (directory) => [...(await snapshot()).files.keys()].filter((path) => path.startsWith(`${directory}/`)),
680
+ read: async (path) => {
681
+ const sha = await hash(path);
682
+ return sha === void 0 ? void 0 : text(sha);
683
+ }
684
+ }, paths),
685
+ hashes: { entry: async (collection, id) => isEntryId(id) ? hash(paths.entry(collection, id)) : void 0 },
686
+ media: async () => mediaDir === void 0 ? [] : (await snapshot()).media(),
684
687
  read: text,
685
688
  reset: (commit) => {
686
689
  pinned = commit;
687
- snapshot = void 0;
690
+ snapshot = once(load);
688
691
  }
689
692
  };
690
693
  }
691
- const DATABASE = "forgepress";
692
- const STORE = "changes";
693
- const CACHE = "forgepress-cache";
694
- const FILES = "files";
695
- const LISTINGS = "listings";
696
- const STORES = {
697
- [DATABASE]: [STORE],
698
- [CACHE]: [FILES, LISTINGS]
699
- };
700
- function settle(request) {
701
- return new Promise((resolve, reject) => {
702
- request.onsuccess = () => resolve(request.result);
703
- request.onerror = () => reject(request.error ?? /* @__PURE__ */ new Error("[forgepress] IndexedDB request failed"));
704
- });
705
- }
706
- function open(name) {
707
- return new Promise((resolve, reject) => {
708
- const opening = indexedDB.open(name, 1);
709
- opening.onupgradeneeded = () => {
710
- for (const store of STORES[name] ?? []) if (!opening.result.objectStoreNames.contains(store)) opening.result.createObjectStore(store);
711
- };
712
- opening.onsuccess = () => resolve(opening.result);
713
- opening.onerror = () => reject(opening.error ?? /* @__PURE__ */ new Error("[forgepress] IndexedDB is unavailable"));
714
- opening.onblocked = () => reject(/* @__PURE__ */ new Error("[forgepress] IndexedDB is blocked by another open tab"));
715
- });
716
- }
717
- const databases = /* @__PURE__ */ new Map();
718
- async function transact(name, store, mode, run) {
719
- let database = databases.get(name);
720
- if (!database) {
721
- database = open(name);
722
- databases.set(name, database);
723
- }
724
- return await run((await database).transaction(store, mode).objectStore(store));
725
- }
694
+ const CHANGES = createStore("forgepress", "changes");
695
+ const FILES = createStore("forgepress-cache", "files");
696
+ const LISTINGS = createStore("forgepress-listings", "listings");
726
697
  function createIdbStore(key) {
727
698
  return {
728
- read: () => transact(DATABASE, STORE, "readonly", (objects) => settle(objects.get(key))),
729
- write: async (value) => {
730
- await transact(DATABASE, STORE, "readwrite", (objects) => settle(objects.put(value, key)));
731
- },
732
- clear: async () => {
733
- await transact(DATABASE, STORE, "readwrite", (objects) => settle(objects.delete(key)));
734
- }
699
+ read: () => get(key, CHANGES),
700
+ write: (value) => set(key, value, CHANGES),
701
+ clear: () => del(key, CHANGES)
735
702
  };
736
703
  }
737
704
  function createIdbCache() {
738
705
  return {
739
706
  readListing: async (commit, directory) => {
740
- const stored = await transact(CACHE, LISTINGS, "readonly", (objects) => settle(objects.get(directory)));
707
+ const stored = await get(directory, LISTINGS);
741
708
  return stored?.commit === commit ? stored.files : void 0;
742
709
  },
743
- writeListing: async (commit, directory, files) => {
744
- await transact(CACHE, LISTINGS, "readwrite", (objects) => settle(objects.put({
745
- commit,
746
- files
747
- }, directory)));
748
- },
749
- keep: (hashes) => transact(CACHE, FILES, "readwrite", (objects) => new Promise((resolve, reject) => {
750
- const keys = objects.getAllKeys();
751
- const values = objects.getAll();
752
- keys.onerror = () => reject(keys.error ?? /* @__PURE__ */ new Error("[forgepress] IndexedDB request failed"));
753
- values.onerror = () => reject(values.error ?? /* @__PURE__ */ new Error("[forgepress] IndexedDB request failed"));
754
- values.onsuccess = () => {
755
- const kept = /* @__PURE__ */ new Map();
756
- keys.result.forEach((key, index) => {
757
- if (typeof key === "string" && hashes.has(key)) kept.set(key, values.result[index]);
758
- else objects.delete(key);
759
- });
760
- resolve(kept);
761
- };
762
- })),
763
- writeFile: async (hash, text) => {
764
- await transact(CACHE, FILES, "readwrite", (objects) => settle(objects.put(text, hash)));
710
+ writeListing: (commit, directory, files) => set(directory, {
711
+ commit,
712
+ files
713
+ }, LISTINGS),
714
+ keep: async (hashes) => {
715
+ const kept = /* @__PURE__ */ new Map();
716
+ const stale = [];
717
+ for (const [key, text] of await entries(FILES)) if (typeof key === "string" && hashes.has(key)) kept.set(key, text);
718
+ else stale.push(key);
719
+ if (stale.length > 0) await delMany(stale, FILES);
720
+ return kept;
765
721
  },
722
+ writeFile: (hash, text) => set(hash, text, FILES),
766
723
  clear: async () => {
767
- await Promise.all([FILES, LISTINGS].map((store) => transact(CACHE, store, "readwrite", (objects) => settle(objects.clear()))));
724
+ await Promise.all([clear(FILES), clear(LISTINGS)]);
768
725
  }
769
726
  };
770
727
  }
@@ -794,29 +751,25 @@ function swap(value, urls) {
794
751
  if (isRecord(value)) return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, swap(item, urls)]));
795
752
  return value;
796
753
  }
797
- function uploads(changes) {
798
- return [...Object.values(changes.uploads), ...publishedUploads(changes)];
799
- }
800
754
  function createPreviewReader(settings) {
801
755
  const tokens = persist(TOKEN_KEY);
802
756
  const pending = persist(CHANGES_KEY);
803
757
  const previews = createPreviews();
804
758
  const shown = /* @__PURE__ */ new Set();
805
759
  let token;
806
- async function access() {
807
- const saved = storedTokens(await tokens.read());
808
- if (!saved) throw new Error("[forgepress] sign in to the editor to preview unpublished content");
809
- const current = await refreshed(saved, settings.provider);
810
- if (current !== saved) await tokens.write(current);
811
- return current.access;
760
+ async function saved() {
761
+ const found = storedTokens(await tokens.read());
762
+ if (!found) throw new Error("[forgepress] sign in to the editor to preview unpublished content");
763
+ return found;
812
764
  }
765
+ const access = createTokenGetter(settings.provider, saved, (next) => tokens.write(next));
813
766
  const forge = createForge(settings.provider, () => {
814
767
  token ??= access();
815
768
  return token;
816
769
  });
817
- const source = createForgeSource(() => forge, createPaths(settings.contentPath), settings.provider.base, repositoryCache());
770
+ const source = createForgeSource(() => forge, { paths: repositoryPaths(createPaths(settings.contentPath), settings.provider.base) }, repositoryCache());
818
771
  function local(changes) {
819
- const assets = uploads(changes).map((upload) => previews.asset(upload, settings.mediaUrl));
772
+ const assets = localUploads(changes).map((upload) => previews.asset(upload, settings.mediaUrl));
820
773
  const names = new Set(assets.map((asset) => asset.name));
821
774
  for (const name of shown) if (!names.has(name)) previews.forget(name);
822
775
  shown.clear();
@@ -826,12 +779,7 @@ function createPreviewReader(settings) {
826
779
  return { build: async () => {
827
780
  token = void 0;
828
781
  source.reset();
829
- const changes = {
830
- entries: {},
831
- uploads: {},
832
- removed: [],
833
- ...await pending.read()
834
- };
782
+ const changes = await readChanges(pending);
835
783
  const content = createContentChanges(source, async () => changes, async () => {
836
784
  throw new Error("[forgepress] the preview only reads content");
837
785
  });
@@ -1,7 +1,10 @@
1
- import { isRecord, quote } from "./value.mjs";
1
+ import { isRecord, isTranslated, quote } from "./value.mjs";
2
2
  function entryKey(collection, id) {
3
3
  return `${collection}/${id}`;
4
4
  }
5
+ function isEntryRef(value) {
6
+ return isRecord(value) && typeof value.collection === "string" && typeof value.id === "string";
7
+ }
5
8
  function localized(value, path, translated) {
6
9
  if (!translated) return [[path, value]];
7
10
  return isRecord(value) ? Object.entries(value).map(([locale, item]) => [[...path, locale], item]) : [];
@@ -18,7 +21,7 @@ function references(field, value, path) {
18
21
  collection: field.collection,
19
22
  id
20
23
  }] : []);
21
- if (field.type === "dynamic") return value.flatMap((block, index) => isRecord(block) && typeof block.id === "string" && typeof block.collection === "string" && field.collections.includes(block.collection) ? [{
24
+ if (field.type === "dynamic") return value.flatMap((block, index) => isEntryRef(block) && field.collections.includes(block.collection) ? [{
22
25
  path: [...path, index],
23
26
  collection: block.collection,
24
27
  id: block.id
@@ -26,8 +29,8 @@ function references(field, value, path) {
26
29
  return [];
27
30
  }
28
31
  function entryReferences(schema, collection, row) {
29
- const translatable = (schema.locales ?? []).length > 0;
30
- return Object.entries(schema.collections[collection]?.fields ?? {}).flatMap(([key, field]) => localized(row[key], [key], translatable && field.translate === true).flatMap(([path, value]) => references(field, value, path)));
32
+ const locales = schema.locales ?? [];
33
+ return Object.entries(schema.collections[collection]?.fields ?? {}).flatMap(([key, field]) => localized(row[key], [key], isTranslated(field, locales)).flatMap(([path, value]) => references(field, value, path)));
31
34
  }
32
35
  function validateReferences(schema, content) {
33
36
  const issues = [];
@@ -50,4 +53,4 @@ function validateReferences(schema, content) {
50
53
  }
51
54
  return issues;
52
55
  }
53
- export { entryKey, validateReferences };
56
+ export { entryKey, isEntryRef, validateReferences };
@@ -0,0 +1,2 @@
1
+ import "./types.mjs";
2
+ import "./types2.mjs";
@@ -0,0 +1,5 @@
1
+ const OUTPUT_INDEX = "index.json";
2
+ function isPage(response) {
3
+ return response.headers.get("content-type")?.includes("text/html") === true;
4
+ }
5
+ export { OUTPUT_INDEX, isPage };
@@ -0,0 +1,9 @@
1
+ const ENDPOINT = "/__forgepress";
2
+ const EVENTS = `${ENDPOINT}/events`;
3
+ const ROUTES = {
4
+ schema: "/schema",
5
+ content: "/content",
6
+ entry: "/entry",
7
+ media: "/media"
8
+ };
9
+ export { ENDPOINT, EVENTS, ROUTES };