@krantho/server 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/LICENSE +21 -0
  2. package/dist/cdn/cloudfront.d.ts +9 -0
  3. package/dist/cdn/cloudfront.d.ts.map +1 -0
  4. package/dist/cdn/cloudfront.js +35 -0
  5. package/dist/cdn/cloudfront.js.map +1 -0
  6. package/dist/cdn/gcp.d.ts +15 -0
  7. package/dist/cdn/gcp.d.ts.map +1 -0
  8. package/dist/cdn/gcp.js +60 -0
  9. package/dist/cdn/gcp.js.map +1 -0
  10. package/dist/cdn/index.d.ts +9 -0
  11. package/dist/cdn/index.d.ts.map +1 -0
  12. package/dist/cdn/index.js +30 -0
  13. package/dist/cdn/index.js.map +1 -0
  14. package/dist/cdn/listener.d.ts +14 -0
  15. package/dist/cdn/listener.d.ts.map +1 -0
  16. package/dist/cdn/listener.js +53 -0
  17. package/dist/cdn/listener.js.map +1 -0
  18. package/dist/cdn/retry.d.ts +3 -0
  19. package/dist/cdn/retry.d.ts.map +1 -0
  20. package/dist/cdn/retry.js +17 -0
  21. package/dist/cdn/retry.js.map +1 -0
  22. package/dist/cdn/types.d.ts +12 -0
  23. package/dist/cdn/types.d.ts.map +1 -0
  24. package/dist/cdn/types.js +2 -0
  25. package/dist/cdn/types.js.map +1 -0
  26. package/dist/cdn/vercel.d.ts +9 -0
  27. package/dist/cdn/vercel.d.ts.map +1 -0
  28. package/dist/cdn/vercel.js +31 -0
  29. package/dist/cdn/vercel.js.map +1 -0
  30. package/dist/content-conflicts.d.ts +18 -0
  31. package/dist/content-conflicts.d.ts.map +1 -0
  32. package/dist/content-conflicts.js +22 -0
  33. package/dist/content-conflicts.js.map +1 -0
  34. package/dist/content-dates.d.ts +35 -0
  35. package/dist/content-dates.d.ts.map +1 -0
  36. package/dist/content-dates.js +85 -0
  37. package/dist/content-dates.js.map +1 -0
  38. package/dist/content-loader.d.ts +37 -0
  39. package/dist/content-loader.d.ts.map +1 -0
  40. package/dist/content-loader.js +223 -0
  41. package/dist/content-loader.js.map +1 -0
  42. package/dist/content-migration.d.ts +101 -0
  43. package/dist/content-migration.d.ts.map +1 -0
  44. package/dist/content-migration.js +291 -0
  45. package/dist/content-migration.js.map +1 -0
  46. package/dist/convert.d.ts +18 -0
  47. package/dist/convert.d.ts.map +1 -0
  48. package/dist/convert.js +152 -0
  49. package/dist/convert.js.map +1 -0
  50. package/dist/file-store.d.ts +11 -0
  51. package/dist/file-store.d.ts.map +1 -0
  52. package/dist/file-store.js +39 -0
  53. package/dist/file-store.js.map +1 -0
  54. package/dist/file-watcher.d.ts +19 -0
  55. package/dist/file-watcher.d.ts.map +1 -0
  56. package/dist/file-watcher.js +99 -0
  57. package/dist/file-watcher.js.map +1 -0
  58. package/dist/handler.d.ts +61 -0
  59. package/dist/handler.d.ts.map +1 -0
  60. package/dist/handler.js +197 -0
  61. package/dist/handler.js.map +1 -0
  62. package/dist/import.d.ts +25 -0
  63. package/dist/import.d.ts.map +1 -0
  64. package/dist/import.js +71 -0
  65. package/dist/import.js.map +1 -0
  66. package/dist/index.d.ts +28 -0
  67. package/dist/index.d.ts.map +1 -0
  68. package/dist/index.js +19 -0
  69. package/dist/index.js.map +1 -0
  70. package/dist/indexnow.d.ts +38 -0
  71. package/dist/indexnow.d.ts.map +1 -0
  72. package/dist/indexnow.js +89 -0
  73. package/dist/indexnow.js.map +1 -0
  74. package/dist/lazy-handler.d.ts +7 -0
  75. package/dist/lazy-handler.d.ts.map +1 -0
  76. package/dist/lazy-handler.js +16 -0
  77. package/dist/lazy-handler.js.map +1 -0
  78. package/dist/llm-seed.d.ts +25 -0
  79. package/dist/llm-seed.d.ts.map +1 -0
  80. package/dist/llm-seed.js +42 -0
  81. package/dist/llm-seed.js.map +1 -0
  82. package/dist/lock-adapters.d.ts +21 -0
  83. package/dist/lock-adapters.d.ts.map +1 -0
  84. package/dist/lock-adapters.js +69 -0
  85. package/dist/lock-adapters.js.map +1 -0
  86. package/dist/manifest.d.ts +15 -0
  87. package/dist/manifest.d.ts.map +1 -0
  88. package/dist/manifest.js +39 -0
  89. package/dist/manifest.js.map +1 -0
  90. package/dist/mime-types.d.ts +3 -0
  91. package/dist/mime-types.d.ts.map +1 -0
  92. package/dist/mime-types.js +15 -0
  93. package/dist/mime-types.js.map +1 -0
  94. package/dist/page-cache-rules.d.ts +41 -0
  95. package/dist/page-cache-rules.d.ts.map +1 -0
  96. package/dist/page-cache-rules.js +94 -0
  97. package/dist/page-cache-rules.js.map +1 -0
  98. package/dist/page-cache-store.d.ts +11 -0
  99. package/dist/page-cache-store.d.ts.map +1 -0
  100. package/dist/page-cache-store.js +46 -0
  101. package/dist/page-cache-store.js.map +1 -0
  102. package/dist/page-request.d.ts +26 -0
  103. package/dist/page-request.d.ts.map +1 -0
  104. package/dist/page-request.js +29 -0
  105. package/dist/page-request.js.map +1 -0
  106. package/dist/persistence.d.ts +36 -0
  107. package/dist/persistence.d.ts.map +1 -0
  108. package/dist/persistence.js +108 -0
  109. package/dist/persistence.js.map +1 -0
  110. package/dist/preview-token.d.ts +9 -0
  111. package/dist/preview-token.d.ts.map +1 -0
  112. package/dist/preview-token.js +14 -0
  113. package/dist/preview-token.js.map +1 -0
  114. package/dist/read-cache.d.ts +17 -0
  115. package/dist/read-cache.d.ts.map +1 -0
  116. package/dist/read-cache.js +33 -0
  117. package/dist/read-cache.js.map +1 -0
  118. package/dist/route-matcher.d.ts +21 -0
  119. package/dist/route-matcher.d.ts.map +1 -0
  120. package/dist/route-matcher.js +75 -0
  121. package/dist/route-matcher.js.map +1 -0
  122. package/dist/secret-vault.d.ts +20 -0
  123. package/dist/secret-vault.d.ts.map +1 -0
  124. package/dist/secret-vault.js +60 -0
  125. package/dist/secret-vault.js.map +1 -0
  126. package/dist/seo-config-io.d.ts +51 -0
  127. package/dist/seo-config-io.d.ts.map +1 -0
  128. package/dist/seo-config-io.js +151 -0
  129. package/dist/seo-config-io.js.map +1 -0
  130. package/dist/seo-seed.d.ts +12 -0
  131. package/dist/seo-seed.d.ts.map +1 -0
  132. package/dist/seo-seed.js +100 -0
  133. package/dist/seo-seed.js.map +1 -0
  134. package/dist/studio-gate.d.ts +25 -0
  135. package/dist/studio-gate.d.ts.map +1 -0
  136. package/dist/studio-gate.js +70 -0
  137. package/dist/studio-gate.js.map +1 -0
  138. package/dist/studio.d.ts +17 -0
  139. package/dist/studio.d.ts.map +1 -0
  140. package/dist/studio.js +107 -0
  141. package/dist/studio.js.map +1 -0
  142. package/dist/webhook-sweep.d.ts +22 -0
  143. package/dist/webhook-sweep.d.ts.map +1 -0
  144. package/dist/webhook-sweep.js +33 -0
  145. package/dist/webhook-sweep.js.map +1 -0
  146. package/package.json +87 -3
  147. package/README.md +0 -3
@@ -0,0 +1,291 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { readdir, readFile } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { renderContent } from '@krantho/core';
5
+ import { canGetContent, publicationOfFile, } from '@krantho/persistence';
6
+ import { documentChecksum, parseRepositoryContent, resolveCollectionContract, } from '@krantho/schema';
7
+ import matter from 'gray-matter';
8
+ import { recordNewConflict, resolveConflictsOf } from './content-conflicts.js';
9
+ import { contentDateKey, contentDateLookup } from './content-dates.js';
10
+ import { autoImport } from './import.js';
11
+ import { CONTENT_MANIFEST_FILE } from './manifest.js';
12
+ /**
13
+ * Bring the database up to date with the repository's content on startup.
14
+ *
15
+ * Without `<contentDir>/manifest.json` (or a migration adapter) the content is
16
+ * imported into an empty database only. With one, every deployment whose
17
+ * manifest is newer than the database's compares each file with its document:
18
+ * a new or changed file is imported, unless an editor changed the document in
19
+ * Studio since — then the editor's version stands and a changed file is
20
+ * recorded as a conflict. A document deleted in Studio stays deleted.
21
+ */
22
+ export async function runContentMigration(adapter, contentConfig, migrationAdapter, checkpointAdapter, contentDir, defaultStatus = 'published') {
23
+ const manifest = await loadManifest(contentDir);
24
+ const dateOf = await contentDateLookup(contentDir);
25
+ if (!manifest || !migrationAdapter) {
26
+ const imported = await autoImport(adapter, contentConfig, dateOf, defaultStatus);
27
+ return {
28
+ action: imported > 0 ? 'full-import' : 'skip',
29
+ imported,
30
+ conflicts: 0,
31
+ rejected: 0,
32
+ checkpointVersion: null,
33
+ reason: !manifest ? `No ${CONTENT_MANIFEST_FILE} found` : 'Migration adapter not configured',
34
+ details: [],
35
+ };
36
+ }
37
+ const dbVersion = await migrationAdapter.getContentManifestVersion();
38
+ if (dbVersion !== null && dbVersion >= manifest.version) {
39
+ return {
40
+ action: 'skip',
41
+ imported: 0,
42
+ conflicts: 0,
43
+ rejected: 0,
44
+ checkpointVersion: null,
45
+ reason: `DB version ${dbVersion} >= repo version ${manifest.version}`,
46
+ details: [],
47
+ };
48
+ }
49
+ const previous = (await migrationAdapter.getContentManifest())?.fileChecksums ?? {};
50
+ const files = await readRepositoryFiles(adapter, contentConfig, contentDir);
51
+ const planned = await planFiles(adapter, files, previous);
52
+ const checkpointVersion = await checkpointBeforeImport(adapter, checkpointAdapter, planned, manifest.version);
53
+ await applyPlan(adapter, migrationAdapter, planned, dateOf, defaultStatus);
54
+ await migrationAdapter.saveContentManifest({
55
+ id: 0,
56
+ repoVersion: manifest.version,
57
+ repoCommit: manifest.commit ?? null,
58
+ importedAt: new Date().toISOString(),
59
+ fileChecksums: knownFiles(planned),
60
+ });
61
+ const count = (outcome) => planned.filter((p) => p.outcome === outcome).length;
62
+ return {
63
+ action: dbVersion === null ? 'full-import' : 'migrate',
64
+ imported: count('imported'),
65
+ conflicts: count('conflict'),
66
+ rejected: count('rejected'),
67
+ checkpointVersion,
68
+ details: planned.map(({ file, outcome }) => ({
69
+ collection: file.collection,
70
+ slug: file.slug,
71
+ outcome,
72
+ })),
73
+ };
74
+ }
75
+ async function planFiles(adapter, files, previous) {
76
+ const planned = [];
77
+ for (const file of files) {
78
+ const read = await adapter.getContent(file.collection, file.slug, file.document?.locale);
79
+ const current = file.document?.locale !== undefined && read?.meta.locale !== file.document.locale
80
+ ? null
81
+ : read;
82
+ planned.push({ file, current, outcome: decideFile(file, current, previous[file.key]) });
83
+ }
84
+ return planned;
85
+ }
86
+ /**
87
+ * What to do with one repository file, given its document's current version
88
+ * and the file's checksum when the database last took the repository's
89
+ * content (undefined: the database never saw this file).
90
+ *
91
+ * Content equal to a state the repository held (the document's basis, or what
92
+ * the last deployment took) is not an edit: a document that carries the
93
+ * repository's previous file, as one does after a pull of production's edits was
94
+ * merged, is not reported as edited, and a file at that state is not reported as changed.
95
+ *
96
+ * A version an import wrote is not an edit either, whatever its checksum: nobody changed it since,
97
+ * even when it predates the document's basis and contract. A restore copies the restored version's
98
+ * source but always names who restored it, so only a version with no `changedBy` is an import's.
99
+ */
100
+ export function decideFile(file, current, previousChecksum) {
101
+ if (!file.fields)
102
+ return 'rejected';
103
+ if (!current)
104
+ return previousChecksum === undefined ? 'imported' : 'deleted';
105
+ const repo = documentChecksum({ ...file.document, fields: file.fields, body: file.body });
106
+ const database = documentChecksum(current);
107
+ if (repo === database)
108
+ return 'unchanged';
109
+ if (current.meta.source === 'filesystem' && current.meta.changedBy === null)
110
+ return 'imported';
111
+ const basis = current.repositoryBasis?.checksum ?? previousChecksum;
112
+ if (basis === undefined)
113
+ return 'conflict';
114
+ const heldByRepository = (checksum) => checksum === basis || checksum === previousChecksum;
115
+ const repositoryChanged = !heldByRepository(repo);
116
+ const databaseChanged = !heldByRepository(database);
117
+ if (repositoryChanged && databaseChanged)
118
+ return 'conflict';
119
+ return repositoryChanged ? 'imported' : 'kept';
120
+ }
121
+ /** A checkpoint before the first import into a database that already holds content */
122
+ async function checkpointBeforeImport(adapter, checkpointAdapter, planned, version) {
123
+ if (!checkpointAdapter)
124
+ return null;
125
+ if (!planned.some((p) => p.outcome === 'imported'))
126
+ return null;
127
+ if ((await adapter.listSlugs()).length === 0)
128
+ return null;
129
+ const checkpoint = await checkpointAdapter.createCheckpoint(`pre-content-migration-v${version}`, 'pre-migration');
130
+ return checkpoint.version;
131
+ }
132
+ async function applyPlan(adapter, migrationAdapter, planned, dateOf, defaultStatus) {
133
+ const migrationId = (await migrationAdapter.getContentManifest())?.id ?? 0;
134
+ // Read once: each document is planned once, so no conflict this run touches is read again
135
+ const open = await migrationAdapter.listConflicts();
136
+ for (const plan of planned) {
137
+ const { file, current } = plan;
138
+ if (plan.outcome === 'imported' && file.fields) {
139
+ try {
140
+ await importFile(adapter, file, file.fields, dateOf, defaultStatus, current?.meta.version ?? 0);
141
+ }
142
+ catch (err) {
143
+ console.warn(`[content-migration] Failed to import ${file.key}:`, err);
144
+ plan.outcome = 'rejected';
145
+ continue;
146
+ }
147
+ await resolveConflictsOf(migrationAdapter, open, {
148
+ collection: file.collection,
149
+ slug: file.slug,
150
+ locale: file.document?.locale ?? current?.meta.locale,
151
+ });
152
+ }
153
+ else if (plan.outcome === 'conflict' && file.fields && current) {
154
+ await recordNewConflict(migrationAdapter, open, {
155
+ migrationId,
156
+ collection: file.collection,
157
+ slug: file.slug,
158
+ locale: current.meta.locale,
159
+ repoChecksum: documentChecksum({ ...file.document, fields: file.fields, body: file.body }),
160
+ dbChecksum: documentChecksum(current),
161
+ });
162
+ }
163
+ else if (plan.outcome === 'rejected') {
164
+ console.warn(`[import] Skipping ${file.key}: schema validation failed`);
165
+ for (const issue of file.issues)
166
+ console.warn(` - ${issue}`);
167
+ }
168
+ }
169
+ }
170
+ async function importFile(adapter, file, fields, dateOf, defaultStatus, expectedVersion) {
171
+ const { html, sections } = await renderContent(fields, file.body, { atNotation: false });
172
+ await adapter.upsertFromFile(file.collection, file.slug, fields, file.body, html, sections, file.filePath, file.document?.locale, dateOf(file.filePath), {
173
+ ...file.document,
174
+ publication: publicationOfFile(file.status ?? defaultStatus),
175
+ expectedVersion,
176
+ });
177
+ }
178
+ /** The files the database now knows, by key: every file it holds or chose not to */
179
+ function knownFiles(planned) {
180
+ return Object.fromEntries(planned
181
+ .filter((p) => p.outcome !== 'rejected')
182
+ .map((p) => [
183
+ p.file.key,
184
+ p.outcome === 'conflict' || p.outcome === 'kept'
185
+ ? (p.current?.repositoryBasis?.checksum ?? p.file.fileChecksum)
186
+ : p.file.fileChecksum,
187
+ ]));
188
+ }
189
+ /** Every markdown file in each collection's directory, parsed and validated */
190
+ async function readRepositoryFiles(adapter, contentConfig, contentDir) {
191
+ const files = [];
192
+ for (const [, collection] of contentConfig.collections) {
193
+ if (!collection.directory)
194
+ continue;
195
+ let entries;
196
+ try {
197
+ entries = await readdir(collection.directory);
198
+ }
199
+ catch {
200
+ continue;
201
+ }
202
+ const contract = resolveCollectionContract(collection, (await adapter.getRuntimeSchema?.(collection.name)) ?? null);
203
+ for (const name of entries.filter((e) => e.endsWith('.md'))) {
204
+ const filePath = join(collection.directory, name);
205
+ const raw = await readFile(filePath, 'utf-8');
206
+ const parsed = matter(raw);
207
+ const result = parseRepositoryContent(collection, name.slice(0, -3), parsed.data, parsed.content, collection.schema, contract);
208
+ files.push({
209
+ key: contentDateKey(contentDir, filePath),
210
+ collection: collection.name,
211
+ slug: name.slice(0, -3),
212
+ filePath,
213
+ fileChecksum: result.success
214
+ ? documentChecksum(result.data)
215
+ : createHash('sha256').update(raw).digest('hex'),
216
+ document: result.success ? result.data : undefined,
217
+ status: typeof parsed.data['status'] === 'string' ? parsed.data['status'] : undefined,
218
+ fields: result.success ? result.data.fields : null,
219
+ issues: result.success
220
+ ? []
221
+ : result.error.issues.map((i) => `${i.path.join('.') || '(root)'}: ${i.message}`),
222
+ body: parsed.content,
223
+ });
224
+ }
225
+ }
226
+ return files;
227
+ }
228
+ /** Read `<contentDir>/manifest.json`, or null when the build wrote none */
229
+ export async function loadManifest(contentDir) {
230
+ try {
231
+ const raw = await readFile(join(contentDir, CONTENT_MANIFEST_FILE), 'utf-8');
232
+ const parsed = JSON.parse(raw);
233
+ if (typeof parsed !== 'object' ||
234
+ parsed === null ||
235
+ typeof parsed['version'] !== 'number' ||
236
+ typeof parsed['files'] !== 'object') {
237
+ return null;
238
+ }
239
+ return parsed;
240
+ }
241
+ catch {
242
+ return null;
243
+ }
244
+ }
245
+ /** Canonical authored content checksum, independent of YAML spelling and key order. */
246
+ export function contentChecksum(fields, body) {
247
+ return documentChecksum({ fields, body });
248
+ }
249
+ /**
250
+ * Import the repository's content into the database when it needs it (an
251
+ * empty database, or a newer content manifest), logging what happened. Both
252
+ * the API handler and the page loader run it on first use, so whichever a
253
+ * fresh deployment serves first finds the content in place.
254
+ *
255
+ * @returns how many documents were imported
256
+ */
257
+ export async function importRepositoryContent(adapter, contentConfig, migrationAdapter, checkpointAdapter, contentDir, defaultStatus = 'published') {
258
+ if (!('upsertFromFile' in adapter && 'listSlugs' in adapter && canGetContent(adapter)))
259
+ return 0;
260
+ const result = await runContentMigration(adapter, contentConfig, migrationAdapter, checkpointAdapter, contentDir, defaultStatus);
261
+ if (result.imported > 0 || result.conflicts > 0 || result.rejected > 0) {
262
+ const parts = [`[krantho] Content migration: ${result.action}`];
263
+ parts.push(`${result.imported} imported`);
264
+ if (result.conflicts > 0)
265
+ parts.push(`${result.conflicts} conflicts`);
266
+ if (result.rejected > 0)
267
+ parts.push(`${result.rejected} rejected`);
268
+ console.log(parts.join(', '));
269
+ }
270
+ return result.imported;
271
+ }
272
+ /** The site setting that holds the last content version the site announced */
273
+ const ANNOUNCED_CONTENT_VERSION = 'content.announcedVersion';
274
+ /**
275
+ * Whether the database holds a content version the site has not announced
276
+ * yet, recording it as announced. The page loader can import a deployment's
277
+ * content before the API handler starts, and only the handler can announce a
278
+ * change, so it announces each new content version once, whoever imported it.
279
+ */
280
+ export async function claimContentAnnouncement(migrationAdapter, settings) {
281
+ if (!migrationAdapter || !settings)
282
+ return false;
283
+ const version = await migrationAdapter.getContentManifestVersion();
284
+ if (version === null)
285
+ return false;
286
+ if ((await settings.getSetting(ANNOUNCED_CONTENT_VERSION)) === String(version))
287
+ return false;
288
+ await settings.setSetting(ANNOUNCED_CONTENT_VERSION, String(version));
289
+ return true;
290
+ }
291
+ //# sourceMappingURL=content-migration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-migration.js","sourceRoot":"","sources":["../src/content-migration.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import type { ApiRequest, ApiResponse } from '@krantho/api';
2
+ /**
3
+ * Convert a Web Request to a framework-agnostic ApiRequest.
4
+ *
5
+ * Extracts method, path, query parameters, headers, and body.
6
+ * Supports both JSON and multipart/form-data content types.
7
+ * The `/api` prefix is preserved — the API router expects it.
8
+ */
9
+ export declare function toApiRequest(request: Request): Promise<ApiRequest>;
10
+ /**
11
+ * Convert a framework-agnostic ApiResponse to a Web Response.
12
+ *
13
+ * Handles JSON bodies (default), binary bodies (Buffer/Uint8Array for image
14
+ * serving) and stream bodies (a `ReadableStream`, passed through unread). Content-Type is set from response headers if present,
15
+ * otherwise defaults to application/json.
16
+ */
17
+ export declare function toResponse(apiResponse: ApiResponse): Response;
18
+ //# sourceMappingURL=convert.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"convert.d.ts","sourceRoot":"","sources":["../src/convert.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAyC3D;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC,CA2CxE;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,WAAW,EAAE,WAAW,GAAG,QAAQ,CAyE7D"}
@@ -0,0 +1,152 @@
1
+ const METHODS_WITH_BODY = new Set(['POST', 'PUT', 'PATCH', 'DELETE']);
2
+ /**
3
+ * Check whether the content-type header indicates multipart form data.
4
+ */
5
+ function isMultipart(headers) {
6
+ const ct = headers['content-type'] ?? '';
7
+ return ct.startsWith('multipart/form-data');
8
+ }
9
+ /**
10
+ * Parse multipart form data from a Request into the files array format
11
+ * expected by ApiRequest.
12
+ */
13
+ async function parseMultipartFiles(request) {
14
+ const formData = await request.formData();
15
+ const files = [];
16
+ const body = {};
17
+ for (const [key, value] of formData) {
18
+ if (value instanceof File) {
19
+ const buffer = await value.arrayBuffer();
20
+ files.push({
21
+ name: key,
22
+ data: new Uint8Array(buffer),
23
+ filename: value.name,
24
+ mimeType: value.type || 'application/octet-stream',
25
+ });
26
+ }
27
+ else {
28
+ body[key] = value;
29
+ }
30
+ }
31
+ return { files, body };
32
+ }
33
+ /**
34
+ * Convert a Web Request to a framework-agnostic ApiRequest.
35
+ *
36
+ * Extracts method, path, query parameters, headers, and body.
37
+ * Supports both JSON and multipart/form-data content types.
38
+ * The `/api` prefix is preserved — the API router expects it.
39
+ */
40
+ export async function toApiRequest(request) {
41
+ const url = new URL(request.url);
42
+ const method = request.method.toUpperCase();
43
+ const query = {};
44
+ for (const [key, value] of url.searchParams) {
45
+ query[key] = value;
46
+ }
47
+ const headers = {};
48
+ for (const [key, value] of request.headers) {
49
+ headers[key.toLowerCase()] = value;
50
+ }
51
+ let body;
52
+ let files;
53
+ if (METHODS_WITH_BODY.has(method) && request.body) {
54
+ if (isMultipart(headers)) {
55
+ const parsed = await parseMultipartFiles(request);
56
+ files = parsed.files;
57
+ body = parsed.body;
58
+ }
59
+ else {
60
+ try {
61
+ body = await request.json();
62
+ }
63
+ catch {
64
+ // Non-JSON body or empty — leave undefined
65
+ }
66
+ }
67
+ }
68
+ return {
69
+ method,
70
+ path: url.pathname,
71
+ params: {},
72
+ query,
73
+ body,
74
+ headers,
75
+ rawRequest: request,
76
+ files,
77
+ };
78
+ }
79
+ /**
80
+ * Convert a framework-agnostic ApiResponse to a Web Response.
81
+ *
82
+ * Handles JSON bodies (default), binary bodies (Buffer/Uint8Array for image
83
+ * serving) and stream bodies (a `ReadableStream`, passed through unread). Content-Type is set from response headers if present,
84
+ * otherwise defaults to application/json.
85
+ */
86
+ export function toResponse(apiResponse) {
87
+ const responseHeaders = new Headers();
88
+ if (apiResponse.headers) {
89
+ for (const [key, value] of Object.entries(apiResponse.headers)) {
90
+ responseHeaders.set(key, value);
91
+ }
92
+ }
93
+ // Emit each cookie as its own Set-Cookie header line (RFC 6265). Headers.append
94
+ // accumulates, and the resulting Response exposes them via headers.getSetCookie().
95
+ if (apiResponse.cookies) {
96
+ for (const cookie of apiResponse.cookies) {
97
+ responseHeaders.append('set-cookie', cookie);
98
+ }
99
+ }
100
+ // Stream body (e.g. server-sent events) — passed through unread
101
+ if (apiResponse.body instanceof ReadableStream) {
102
+ if (!responseHeaders.has('content-type')) {
103
+ responseHeaders.set('content-type', 'application/octet-stream');
104
+ }
105
+ return new Response(apiResponse.body, {
106
+ status: apiResponse.status,
107
+ headers: responseHeaders,
108
+ });
109
+ }
110
+ // Binary body (Buffer or Uint8Array) — pass raw bytes
111
+ if (apiResponse.body instanceof Uint8Array || Buffer.isBuffer(apiResponse.body)) {
112
+ if (!responseHeaders.has('content-type')) {
113
+ responseHeaders.set('content-type', 'application/octet-stream');
114
+ }
115
+ const raw = Buffer.isBuffer(apiResponse.body)
116
+ ? apiResponse.body
117
+ : Buffer.from(apiResponse.body);
118
+ // Copy to a new ArrayBuffer to satisfy BodyInit type (avoids SharedArrayBuffer issue)
119
+ const ab = new ArrayBuffer(raw.byteLength);
120
+ new Uint8Array(ab).set(raw);
121
+ return new Response(ab, {
122
+ status: apiResponse.status,
123
+ headers: responseHeaders,
124
+ });
125
+ }
126
+ // Null body (e.g., 304 Not Modified)
127
+ if (apiResponse.body === null || apiResponse.body === undefined) {
128
+ return new Response(null, {
129
+ status: apiResponse.status,
130
+ headers: responseHeaders,
131
+ });
132
+ }
133
+ // String body with explicit Content-Type (e.g., text/html)
134
+ if (typeof apiResponse.body === 'string') {
135
+ if (!responseHeaders.has('content-type')) {
136
+ responseHeaders.set('content-type', 'text/plain');
137
+ }
138
+ return new Response(apiResponse.body, {
139
+ status: apiResponse.status,
140
+ headers: responseHeaders,
141
+ });
142
+ }
143
+ // Default: JSON
144
+ if (!responseHeaders.has('content-type')) {
145
+ responseHeaders.set('content-type', 'application/json');
146
+ }
147
+ return new Response(JSON.stringify(apiResponse.body), {
148
+ status: apiResponse.status,
149
+ headers: responseHeaders,
150
+ });
151
+ }
152
+ //# sourceMappingURL=convert.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"convert.js","sourceRoot":"","sources":["../src/convert.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { MediaSection } from '@krantho/config';
2
+ import type { IFileStore } from '@krantho/persistence';
3
+ /**
4
+ * Build the file store `media.provider` selects.
5
+ *
6
+ * Each store is its own optional peer, loaded by a literal dynamic import so
7
+ * bundlers and Next/Vercel file tracing can follow it; an app installs only
8
+ * the store it uses.
9
+ */
10
+ export declare function createFileStore(media: MediaSection): Promise<IFileStore>;
11
+ //# sourceMappingURL=file-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-store.d.ts","sourceRoot":"","sources":["../src/file-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAClE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AAEtD;;;;;;GAMG;AACH,wBAAsB,eAAe,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC,CAyB9E"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Build the file store `media.provider` selects.
3
+ *
4
+ * Each store is its own optional peer, loaded by a literal dynamic import so
5
+ * bundlers and Next/Vercel file tracing can follow it; an app installs only
6
+ * the store it uses.
7
+ */
8
+ export async function createFileStore(media) {
9
+ switch (media.provider) {
10
+ case 'local': {
11
+ const mod = await load('local', '@krantho/media-local', () => import('@krantho/media-local'));
12
+ return mod.createLocalFileStore(media.local);
13
+ }
14
+ case 'vercel-blob': {
15
+ const mod = await load('vercel-blob', '@krantho/media-vercel-blob', () => import('@krantho/media-vercel-blob'));
16
+ return mod.createVercelBlobFileStore(media.vercelBlob);
17
+ }
18
+ case 's3': {
19
+ const mod = await load('s3', '@krantho/media-s3', () => import('@krantho/media-s3'));
20
+ return mod.createS3FileStore(media.s3);
21
+ }
22
+ case 'gcs': {
23
+ const mod = await load('gcs', '@krantho/media-gcs', () => import('@krantho/media-gcs'));
24
+ return mod.createGcsFileStore(media.gcs);
25
+ }
26
+ case undefined:
27
+ throw new Error('media.provider is not set');
28
+ }
29
+ }
30
+ async function load(provider, pkg, importer) {
31
+ try {
32
+ return await importer();
33
+ }
34
+ catch (err) {
35
+ throw new Error(`media.provider is "${provider}", but ${pkg} could not be loaded. ` +
36
+ 'Add it to the dependencies of the app that runs krantho.', { cause: err });
37
+ }
38
+ }
39
+ //# sourceMappingURL=file-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-store.js","sourceRoot":"","sources":["../src/file-store.ts"],"names":[],"mappings":"AAGA;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,KAAmB;IACvD,QAAQ,KAAK,CAAC,QAAQ,EAAE,CAAC;QACvB,KAAK,OAAO,CAAC,CAAC,CAAC;YACb,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,sBAAsB,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,sBAAsB,CAAC,CAAC,CAAA;YAC7F,OAAO,GAAG,CAAC,oBAAoB,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;QAC9C,CAAC;QACD,KAAK,aAAa,CAAC,CAAC,CAAC;YACnB,MAAM,GAAG,GAAG,MAAM,IAAI,CACpB,aAAa,EACb,4BAA4B,EAC5B,GAAG,EAAE,CAAC,MAAM,CAAC,4BAA4B,CAAC,CAC3C,CAAA;YACD,OAAO,GAAG,CAAC,yBAAyB,CAAC,KAAK,CAAC,UAAU,CAAC,CAAA;QACxD,CAAC;QACD,KAAK,IAAI,CAAC,CAAC,CAAC;YACV,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,mBAAmB,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC,CAAA;YACpF,OAAO,GAAG,CAAC,iBAAiB,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;QACxC,CAAC;QACD,KAAK,KAAK,CAAC,CAAC,CAAC;YACX,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,EAAE,oBAAoB,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC,CAAA;YACvF,OAAO,GAAG,CAAC,kBAAkB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QAC1C,CAAC;QACD,KAAK,SAAS;YACZ,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAA;IAChD,CAAC;AACH,CAAC;AAED,KAAK,UAAU,IAAI,CACjB,QAAuB,EACvB,GAAW,EACX,QAA0B;IAE1B,IAAI,CAAC;QACH,OAAO,MAAM,QAAQ,EAAE,CAAA;IACzB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,sBAAsB,QAAQ,UAAU,GAAG,wBAAwB;YACjE,0DAA0D,EAC5D,EAAE,KAAK,EAAE,GAAG,EAAE,CACf,CAAA;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,19 @@
1
+ import type { ApiHandlers } from '@krantho/api';
2
+ import { type ContentConfig } from '@krantho/schema';
3
+ import type { ImportableAdapter } from './import.js';
4
+ export interface FileWatcherOptions {
5
+ contentConfig: ContentConfig;
6
+ adapter: ImportableAdapter;
7
+ saveDocument: ApiHandlers['saveRepositoryDocument'];
8
+ }
9
+ /**
10
+ * Start watching filesystem collection directories for changes.
11
+ *
12
+ * When a `.md` file is added or modified, it is parsed, validated against
13
+ * the collection contract, and saved as a draft through the common document write
14
+ * path. When a file is deleted, the document stays in the database until explicitly archived.
15
+ *
16
+ * Returns a cleanup function to stop watching.
17
+ */
18
+ export declare function startFileWatcher(options: FileWatcherOptions): Promise<() => void>;
19
+ //# sourceMappingURL=file-watcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-watcher.d.ts","sourceRoot":"","sources":["../src/file-watcher.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,EAEL,KAAK,aAAa,EAInB,MAAM,iBAAiB,CAAA;AAGxB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAkEpD,MAAM,WAAW,kBAAkB;IACjC,aAAa,EAAE,aAAa,CAAA;IAC5B,OAAO,EAAE,iBAAiB,CAAA;IAC1B,YAAY,EAAE,WAAW,CAAC,wBAAwB,CAAC,CAAA;CACpD;AAED;;;;;;;;GAQG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC,CAoDvF"}