@geekity/cms 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.
Files changed (238) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +870 -0
  3. package/admin/layouts/_flash.njk +5 -0
  4. package/admin/layouts/base.njk +27 -0
  5. package/admin/layouts/dashboard.njk +78 -0
  6. package/admin/layouts/document-conflict.njk +49 -0
  7. package/admin/layouts/document-editor.njk +167 -0
  8. package/admin/layouts/document-list.njk +92 -0
  9. package/admin/layouts/federation.njk +145 -0
  10. package/admin/layouts/login.njk +23 -0
  11. package/admin/layouts/placeholder.njk +10 -0
  12. package/admin/layouts/settings.njk +116 -0
  13. package/admin/layouts/setup.njk +27 -0
  14. package/admin/layouts/shell.njk +37 -0
  15. package/admin/layouts/users.njk +91 -0
  16. package/admin/static/admin.css +726 -0
  17. package/admin/static/editor.js +30 -0
  18. package/dist/admin/assets.d.ts +26 -0
  19. package/dist/admin/assets.d.ts.map +1 -0
  20. package/dist/admin/assets.js +38 -0
  21. package/dist/admin/assets.js.map +1 -0
  22. package/dist/admin/credentials.d.ts +28 -0
  23. package/dist/admin/credentials.d.ts.map +1 -0
  24. package/dist/admin/credentials.js +39 -0
  25. package/dist/admin/credentials.js.map +1 -0
  26. package/dist/admin/documents.d.ts +134 -0
  27. package/dist/admin/documents.d.ts.map +1 -0
  28. package/dist/admin/documents.js +613 -0
  29. package/dist/admin/documents.js.map +1 -0
  30. package/dist/admin/federation.d.ts +194 -0
  31. package/dist/admin/federation.d.ts.map +1 -0
  32. package/dist/admin/federation.js +299 -0
  33. package/dist/admin/federation.js.map +1 -0
  34. package/dist/admin/flash.d.ts +24 -0
  35. package/dist/admin/flash.d.ts.map +1 -0
  36. package/dist/admin/flash.js +31 -0
  37. package/dist/admin/flash.js.map +1 -0
  38. package/dist/admin/index.d.ts +23 -0
  39. package/dist/admin/index.d.ts.map +1 -0
  40. package/dist/admin/index.js +15 -0
  41. package/dist/admin/index.js.map +1 -0
  42. package/dist/admin/passwords.d.ts +49 -0
  43. package/dist/admin/passwords.d.ts.map +1 -0
  44. package/dist/admin/passwords.js +122 -0
  45. package/dist/admin/passwords.js.map +1 -0
  46. package/dist/admin/preview.d.ts +18 -0
  47. package/dist/admin/preview.d.ts.map +1 -0
  48. package/dist/admin/preview.js +83 -0
  49. package/dist/admin/preview.js.map +1 -0
  50. package/dist/admin/routes.d.ts +54 -0
  51. package/dist/admin/routes.d.ts.map +1 -0
  52. package/dist/admin/routes.js +383 -0
  53. package/dist/admin/routes.js.map +1 -0
  54. package/dist/admin/session.d.ts +44 -0
  55. package/dist/admin/session.d.ts.map +1 -0
  56. package/dist/admin/session.js +69 -0
  57. package/dist/admin/session.js.map +1 -0
  58. package/dist/admin/settings.d.ts +194 -0
  59. package/dist/admin/settings.d.ts.map +1 -0
  60. package/dist/admin/settings.js +470 -0
  61. package/dist/admin/settings.js.map +1 -0
  62. package/dist/admin/store.d.ts +407 -0
  63. package/dist/admin/store.d.ts.map +1 -0
  64. package/dist/admin/store.js +704 -0
  65. package/dist/admin/store.js.map +1 -0
  66. package/dist/admin/templates.d.ts +36 -0
  67. package/dist/admin/templates.d.ts.map +1 -0
  68. package/dist/admin/templates.js +43 -0
  69. package/dist/admin/templates.js.map +1 -0
  70. package/dist/admin/uploads.d.ts +96 -0
  71. package/dist/admin/uploads.d.ts.map +1 -0
  72. package/dist/admin/uploads.js +179 -0
  73. package/dist/admin/uploads.js.map +1 -0
  74. package/dist/admin/users.d.ts +95 -0
  75. package/dist/admin/users.d.ts.map +1 -0
  76. package/dist/admin/users.js +233 -0
  77. package/dist/admin/users.js.map +1 -0
  78. package/dist/cli.d.ts +85 -0
  79. package/dist/cli.d.ts.map +1 -0
  80. package/dist/cli.js +424 -0
  81. package/dist/cli.js.map +1 -0
  82. package/dist/config.d.ts +172 -0
  83. package/dist/config.d.ts.map +1 -0
  84. package/dist/config.js +184 -0
  85. package/dist/config.js.map +1 -0
  86. package/dist/content/document.d.ts +66 -0
  87. package/dist/content/document.d.ts.map +1 -0
  88. package/dist/content/document.js +13 -0
  89. package/dist/content/document.js.map +1 -0
  90. package/dist/content/index.d.ts +17 -0
  91. package/dist/content/index.d.ts.map +1 -0
  92. package/dist/content/index.js +10 -0
  93. package/dist/content/index.js.map +1 -0
  94. package/dist/content/markdown.d.ts +3 -0
  95. package/dist/content/markdown.d.ts.map +1 -0
  96. package/dist/content/markdown.js +39 -0
  97. package/dist/content/markdown.js.map +1 -0
  98. package/dist/content/media.d.ts +65 -0
  99. package/dist/content/media.d.ts.map +1 -0
  100. package/dist/content/media.js +143 -0
  101. package/dist/content/media.js.map +1 -0
  102. package/dist/content/parser.d.ts +24 -0
  103. package/dist/content/parser.d.ts.map +1 -0
  104. package/dist/content/parser.js +165 -0
  105. package/dist/content/parser.js.map +1 -0
  106. package/dist/content/save.d.ts +60 -0
  107. package/dist/content/save.d.ts.map +1 -0
  108. package/dist/content/save.js +103 -0
  109. package/dist/content/save.js.map +1 -0
  110. package/dist/content/slug.d.ts +25 -0
  111. package/dist/content/slug.d.ts.map +1 -0
  112. package/dist/content/slug.js +75 -0
  113. package/dist/content/slug.js.map +1 -0
  114. package/dist/content/store.d.ts +133 -0
  115. package/dist/content/store.d.ts.map +1 -0
  116. package/dist/content/store.js +411 -0
  117. package/dist/content/store.js.map +1 -0
  118. package/dist/content/sync.d.ts +146 -0
  119. package/dist/content/sync.d.ts.map +1 -0
  120. package/dist/content/sync.js +421 -0
  121. package/dist/content/sync.js.map +1 -0
  122. package/dist/content/writer.d.ts +37 -0
  123. package/dist/content/writer.d.ts.map +1 -0
  124. package/dist/content/writer.js +97 -0
  125. package/dist/content/writer.js.map +1 -0
  126. package/dist/env.d.ts +48 -0
  127. package/dist/env.d.ts.map +1 -0
  128. package/dist/env.js +2 -0
  129. package/dist/env.js.map +1 -0
  130. package/dist/federation/actor.d.ts +55 -0
  131. package/dist/federation/actor.d.ts.map +1 -0
  132. package/dist/federation/actor.js +101 -0
  133. package/dist/federation/actor.js.map +1 -0
  134. package/dist/federation/article.d.ts +83 -0
  135. package/dist/federation/article.d.ts.map +1 -0
  136. package/dist/federation/article.js +169 -0
  137. package/dist/federation/article.js.map +1 -0
  138. package/dist/federation/delivery.d.ts +90 -0
  139. package/dist/federation/delivery.d.ts.map +1 -0
  140. package/dist/federation/delivery.js +289 -0
  141. package/dist/federation/delivery.js.map +1 -0
  142. package/dist/federation/federation.d.ts +75 -0
  143. package/dist/federation/federation.d.ts.map +1 -0
  144. package/dist/federation/federation.js +190 -0
  145. package/dist/federation/federation.js.map +1 -0
  146. package/dist/federation/followers.d.ts +43 -0
  147. package/dist/federation/followers.d.ts.map +1 -0
  148. package/dist/federation/followers.js +69 -0
  149. package/dist/federation/followers.js.map +1 -0
  150. package/dist/federation/inbox.d.ts +68 -0
  151. package/dist/federation/inbox.d.ts.map +1 -0
  152. package/dist/federation/inbox.js +171 -0
  153. package/dist/federation/inbox.js.map +1 -0
  154. package/dist/federation/index.d.ts +14 -0
  155. package/dist/federation/index.d.ts.map +1 -0
  156. package/dist/federation/index.js +10 -0
  157. package/dist/federation/index.js.map +1 -0
  158. package/dist/federation/keys.d.ts +27 -0
  159. package/dist/federation/keys.d.ts.map +1 -0
  160. package/dist/federation/keys.js +60 -0
  161. package/dist/federation/keys.js.map +1 -0
  162. package/dist/federation/mount.d.ts +23 -0
  163. package/dist/federation/mount.d.ts.map +1 -0
  164. package/dist/federation/mount.js +71 -0
  165. package/dist/federation/mount.js.map +1 -0
  166. package/dist/federation/paths.d.ts +85 -0
  167. package/dist/federation/paths.d.ts.map +1 -0
  168. package/dist/federation/paths.js +97 -0
  169. package/dist/federation/paths.js.map +1 -0
  170. package/dist/index.d.ts +106 -0
  171. package/dist/index.d.ts.map +1 -0
  172. package/dist/index.js +148 -0
  173. package/dist/index.js.map +1 -0
  174. package/dist/init.d.ts +71 -0
  175. package/dist/init.d.ts.map +1 -0
  176. package/dist/init.js +125 -0
  177. package/dist/init.js.map +1 -0
  178. package/dist/web/assets.d.ts +65 -0
  179. package/dist/web/assets.d.ts.map +1 -0
  180. package/dist/web/assets.js +152 -0
  181. package/dist/web/assets.js.map +1 -0
  182. package/dist/web/context.d.ts +121 -0
  183. package/dist/web/context.d.ts.map +1 -0
  184. package/dist/web/context.js +112 -0
  185. package/dist/web/context.js.map +1 -0
  186. package/dist/web/documents.d.ts +28 -0
  187. package/dist/web/documents.d.ts.map +1 -0
  188. package/dist/web/documents.js +39 -0
  189. package/dist/web/documents.js.map +1 -0
  190. package/dist/web/feeds.d.ts +122 -0
  191. package/dist/web/feeds.d.ts.map +1 -0
  192. package/dist/web/feeds.js +229 -0
  193. package/dist/web/feeds.js.map +1 -0
  194. package/dist/web/index.d.ts +17 -0
  195. package/dist/web/index.d.ts.map +1 -0
  196. package/dist/web/index.js +10 -0
  197. package/dist/web/index.js.map +1 -0
  198. package/dist/web/negotiate.d.ts +219 -0
  199. package/dist/web/negotiate.d.ts.map +1 -0
  200. package/dist/web/negotiate.js +409 -0
  201. package/dist/web/negotiate.js.map +1 -0
  202. package/dist/web/pagination.d.ts +48 -0
  203. package/dist/web/pagination.d.ts.map +1 -0
  204. package/dist/web/pagination.js +30 -0
  205. package/dist/web/pagination.js.map +1 -0
  206. package/dist/web/render.d.ts +72 -0
  207. package/dist/web/render.d.ts.map +1 -0
  208. package/dist/web/render.js +73 -0
  209. package/dist/web/render.js.map +1 -0
  210. package/dist/web/routes.d.ts +31 -0
  211. package/dist/web/routes.d.ts.map +1 -0
  212. package/dist/web/routes.js +366 -0
  213. package/dist/web/routes.js.map +1 -0
  214. package/dist/web/templates.d.ts +34 -0
  215. package/dist/web/templates.d.ts.map +1 -0
  216. package/dist/web/templates.js +106 -0
  217. package/dist/web/templates.js.map +1 -0
  218. package/package.json +68 -0
  219. package/templates/site/content/_data/site.json +8 -0
  220. package/templates/site/content/pages/about.md +11 -0
  221. package/templates/site/content/pages/pages.json +3 -0
  222. package/templates/site/content/posts/2026-01-01-hello-world.md +34 -0
  223. package/templates/site/content/posts/posts.json +4 -0
  224. package/templates/site/geekity.config.ts +11 -0
  225. package/templates/site/gitignore +3 -0
  226. package/templates/site/server.ts +20 -0
  227. package/templates/site/tsconfig.json +18 -0
  228. package/themes/default/README.md +148 -0
  229. package/themes/default/layouts/404.njk +9 -0
  230. package/themes/default/layouts/base.njk +59 -0
  231. package/themes/default/layouts/home.njk +9 -0
  232. package/themes/default/layouts/page.njk +13 -0
  233. package/themes/default/layouts/post.njk +30 -0
  234. package/themes/default/layouts/tag.njk +19 -0
  235. package/themes/default/partials/pagination.njk +14 -0
  236. package/themes/default/partials/post-list.njk +24 -0
  237. package/themes/default/partials/tags.njk +12 -0
  238. package/themes/default/static/style.css +327 -0
@@ -0,0 +1,411 @@
1
+ import { mkdirSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { DatabaseSync } from 'node:sqlite';
4
+ /** File name of the derived index inside a site's data directory. */
5
+ export const DATABASE_FILE = 'geekity.db';
6
+ /** Directory name that marks a document as thrown away but not yet deleted. */
7
+ export const TRASH_DIRECTORY = '_trash';
8
+ /**
9
+ * Thrown when two files claim the same permalink. Two documents at one URL is
10
+ * a content mistake the site owner has to fix, so the index refuses it rather
11
+ * than picking a winner.
12
+ */
13
+ export class DuplicatePermalinkError extends Error {
14
+ name = 'DuplicatePermalinkError';
15
+ /** The permalink both documents claim. */
16
+ permalink;
17
+ /** The path that was being indexed. */
18
+ path;
19
+ /** The path that already held the permalink, when it could be looked up. */
20
+ conflictingPath;
21
+ constructor(permalink, path, conflictingPath) {
22
+ const owner = conflictingPath === undefined ? 'another document' : `"${conflictingPath}"`;
23
+ super(`Cannot index "${path}": its permalink ${permalink} is already used by ${owner}. ` +
24
+ 'Two documents cannot share a URL; change one of their permalinks.');
25
+ this.permalink = permalink;
26
+ this.path = path;
27
+ this.conflictingPath = conflictingPath;
28
+ }
29
+ }
30
+ /**
31
+ * Whether a content path is in the trash. Trashed documents stay indexed so
32
+ * the admin can restore them, and stay out of every public listing.
33
+ */
34
+ export function isTrashedPath(contentPath) {
35
+ return contentPath.split('/').includes(TRASH_DIRECTORY);
36
+ }
37
+ /**
38
+ * The value a document sorts by: its date as a UTC instant, so
39
+ * `2026-09-02T09:00:00-05:00` and `2026-09-02T14:00:00Z` compare equal.
40
+ *
41
+ * A document whose date is missing or unreadable sorts last, because SQLite
42
+ * orders NULL below every string.
43
+ */
44
+ export function dateSortKey(date) {
45
+ if (date === undefined)
46
+ return null;
47
+ const parsed = new Date(date);
48
+ return Number.isNaN(parsed.getTime()) ? null : parsed.toISOString();
49
+ }
50
+ /**
51
+ * Open (and if needed create) the index in `dataDir`, applying every migration
52
+ * the package ships. Applying them is idempotent, so reopening an up-to-date
53
+ * database does nothing.
54
+ */
55
+ export function openContentStore(options) {
56
+ const file = path.join(options.dataDir, DATABASE_FILE);
57
+ mkdirSync(options.dataDir, { recursive: true });
58
+ const db = new DatabaseSync(file);
59
+ db.exec('PRAGMA journal_mode = WAL');
60
+ db.exec('PRAGMA foreign_keys = ON');
61
+ migrate(db);
62
+ const statements = {
63
+ insert: db.prepare(`
64
+ INSERT INTO documents (
65
+ path, type, slug, permalink, title, date, date_sort, updated, draft, trashed,
66
+ description, author, activitypub, extra, body, html, hash
67
+ ) VALUES (
68
+ :path, :type, :slug, :permalink, :title, :date, :date_sort, :updated, :draft, :trashed,
69
+ :description, :author, :activitypub, :extra, :body, :html, :hash
70
+ )
71
+ ON CONFLICT (path) DO UPDATE SET
72
+ type = excluded.type,
73
+ slug = excluded.slug,
74
+ permalink = excluded.permalink,
75
+ title = excluded.title,
76
+ date = excluded.date,
77
+ date_sort = excluded.date_sort,
78
+ updated = excluded.updated,
79
+ draft = excluded.draft,
80
+ trashed = excluded.trashed,
81
+ description = excluded.description,
82
+ author = excluded.author,
83
+ activitypub = excluded.activitypub,
84
+ extra = excluded.extra,
85
+ body = excluded.body,
86
+ html = excluded.html,
87
+ hash = excluded.hash
88
+ `),
89
+ deleteTags: db.prepare('DELETE FROM document_tags WHERE path = ?'),
90
+ insertTag: db.prepare('INSERT INTO document_tags (path, tag, position) VALUES (?, ?, ?)'),
91
+ pathForPermalink: db.prepare('SELECT path FROM documents WHERE permalink = ?'),
92
+ remove: db.prepare('DELETE FROM documents WHERE path = ?'),
93
+ byPath: db.prepare('SELECT * FROM documents WHERE path = ?'),
94
+ byPermalink: db.prepare('SELECT * FROM documents WHERE permalink = ?'),
95
+ bySlug: db.prepare(`SELECT * FROM documents WHERE slug = ? ORDER BY date_sort DESC, path DESC LIMIT 1`),
96
+ tagsFor: db.prepare('SELECT tag FROM document_tags WHERE path = ? ORDER BY position'),
97
+ paths: db.prepare('SELECT path FROM documents ORDER BY path'),
98
+ counts: db.prepare(`
99
+ SELECT
100
+ COUNT(*) AS total,
101
+ COALESCE(SUM(type = 'post' AND draft = 0 AND trashed = 0), 0) AS posts,
102
+ COALESCE(SUM(type = 'page' AND draft = 0 AND trashed = 0), 0) AS pages,
103
+ COALESCE(SUM(draft = 1 AND trashed = 0), 0) AS drafts,
104
+ COALESCE(SUM(trashed = 1), 0) AS trashed
105
+ FROM documents
106
+ `),
107
+ tagCounts: db.prepare(`
108
+ SELECT document_tags.tag AS tag, COUNT(*) AS count
109
+ FROM document_tags
110
+ JOIN documents ON documents.path = document_tags.path
111
+ WHERE documents.draft = 0 AND documents.trashed = 0
112
+ GROUP BY document_tags.tag
113
+ ORDER BY count DESC, tag ASC
114
+ `),
115
+ };
116
+ let open = true;
117
+ function tagsOf(contentPath) {
118
+ return statements.tagsFor.all(contentPath).map((row) => String(row['tag']));
119
+ }
120
+ function hydrate(row) {
121
+ if (row === undefined)
122
+ return undefined;
123
+ return toDocument(row, tagsOf(String(row['path'])));
124
+ }
125
+ function hydrateAll(rows) {
126
+ return rows.map((row) => toDocument(row, tagsOf(String(row['path']))));
127
+ }
128
+ function writeOne(document) {
129
+ const contentPath = document.path;
130
+ try {
131
+ statements.insert.run(toRow(document));
132
+ }
133
+ catch (error) {
134
+ throw translateWriteError(error, document, statements.pathForPermalink);
135
+ }
136
+ statements.deleteTags.run(contentPath);
137
+ document.tags.forEach((tag, position) => {
138
+ statements.insertTag.run(contentPath, tag, position);
139
+ });
140
+ }
141
+ function select(where, params, options) {
142
+ const clause = where.length === 0 ? '' : `WHERE ${where.join(' AND ')}`;
143
+ const rows = db
144
+ .prepare(`SELECT * FROM documents ${clause} ORDER BY date_sort DESC, path DESC ${limitClause(options)}`)
145
+ .all(...params, ...limitParams(options));
146
+ return hydrateAll(rows);
147
+ }
148
+ return {
149
+ file,
150
+ upsert(document) {
151
+ writeOne(document);
152
+ },
153
+ upsertAll(documents) {
154
+ db.exec('BEGIN');
155
+ try {
156
+ for (const document of documents)
157
+ writeOne(document);
158
+ db.exec('COMMIT');
159
+ }
160
+ catch (error) {
161
+ db.exec('ROLLBACK');
162
+ throw error;
163
+ }
164
+ },
165
+ remove(contentPath) {
166
+ return statements.remove.run(contentPath).changes > 0;
167
+ },
168
+ getByPath(contentPath) {
169
+ return hydrate(statements.byPath.get(contentPath));
170
+ },
171
+ getByPermalink(permalink) {
172
+ return hydrate(statements.byPermalink.get(permalink));
173
+ },
174
+ getBySlug(slug) {
175
+ return hydrate(statements.bySlug.get(slug));
176
+ },
177
+ listPosts(options = {}) {
178
+ return select(["type = 'post'", 'draft = 0', 'trashed = 0'], [], options);
179
+ },
180
+ listByTag(tag, options = {}) {
181
+ const where = [
182
+ 'draft = 0',
183
+ 'trashed = 0',
184
+ 'path IN (SELECT path FROM document_tags WHERE tag = ?)',
185
+ ];
186
+ const params = [tag];
187
+ if (options.type !== undefined) {
188
+ where.unshift('type = ?');
189
+ params.unshift(options.type);
190
+ }
191
+ return select(where, params, options);
192
+ },
193
+ listAll(options = {}) {
194
+ const where = [];
195
+ const params = [];
196
+ if (options.type !== undefined) {
197
+ where.push('type = ?');
198
+ params.push(options.type);
199
+ }
200
+ if (options.draft !== undefined) {
201
+ where.push('draft = ?');
202
+ params.push(options.draft ? 1 : 0);
203
+ }
204
+ where.push('trashed = ?');
205
+ params.push(options.trashed === true ? 1 : 0);
206
+ if (options.tag !== undefined) {
207
+ where.push('path IN (SELECT path FROM document_tags WHERE tag = ?)');
208
+ params.push(options.tag);
209
+ }
210
+ return select(where, params, options);
211
+ },
212
+ listPaths() {
213
+ return statements.paths.all().map((row) => String(row['path']));
214
+ },
215
+ counts() {
216
+ const row = statements.counts.get();
217
+ return {
218
+ total: Number(row['total']),
219
+ posts: Number(row['posts']),
220
+ pages: Number(row['pages']),
221
+ drafts: Number(row['drafts']),
222
+ trashed: Number(row['trashed']),
223
+ };
224
+ },
225
+ countByTag(tag, options = {}) {
226
+ const where = ['documents.draft = 0', 'documents.trashed = 0', 'document_tags.tag = ?'];
227
+ const params = [tag];
228
+ if (options.type !== undefined) {
229
+ where.push('documents.type = ?');
230
+ params.push(options.type);
231
+ }
232
+ const row = db
233
+ .prepare(`SELECT COUNT(*) AS count FROM document_tags
234
+ JOIN documents ON documents.path = document_tags.path
235
+ WHERE ${where.join(' AND ')}`)
236
+ .get(...params);
237
+ return Number(row['count']);
238
+ },
239
+ listTags() {
240
+ return statements.tagCounts.all().map((row) => ({
241
+ tag: String(row['tag']),
242
+ count: Number(row['count']),
243
+ }));
244
+ },
245
+ close() {
246
+ if (!open)
247
+ return;
248
+ open = false;
249
+ db.close();
250
+ },
251
+ };
252
+ }
253
+ function limitClause(options) {
254
+ if (options.limit === undefined && options.offset === undefined)
255
+ return '';
256
+ return 'LIMIT ? OFFSET ?';
257
+ }
258
+ function limitParams(options) {
259
+ if (options.limit === undefined && options.offset === undefined)
260
+ return [];
261
+ return [options.limit ?? -1, options.offset ?? 0];
262
+ }
263
+ /** A {@link Document} as the columns of the `documents` table. */
264
+ function toRow(document) {
265
+ return {
266
+ path: document.path,
267
+ type: document.type,
268
+ slug: document.slug,
269
+ permalink: document.permalink,
270
+ title: document.title,
271
+ date: document.date ?? null,
272
+ date_sort: dateSortKey(document.date),
273
+ updated: document.updated ?? null,
274
+ draft: document.draft ? 1 : 0,
275
+ trashed: isTrashedPath(document.path) ? 1 : 0,
276
+ description: document.description ?? null,
277
+ author: document.author ?? null,
278
+ activitypub: document.activitypub === undefined ? null : JSON.stringify(document.activitypub),
279
+ extra: JSON.stringify(document.extra),
280
+ body: document.body,
281
+ html: document.html,
282
+ hash: document.hash,
283
+ };
284
+ }
285
+ /**
286
+ * A row back into a {@link Document}.
287
+ *
288
+ * Optional fields are left off entirely when the column is NULL, so a document
289
+ * that went through the index deep-equals the one that was parsed from the file.
290
+ */
291
+ function toDocument(row, tags) {
292
+ return {
293
+ type: String(row['type']),
294
+ path: String(row['path']),
295
+ slug: String(row['slug']),
296
+ permalink: String(row['permalink']),
297
+ title: String(row['title']),
298
+ ...optional('date', text(row['date'])),
299
+ ...optional('updated', text(row['updated'])),
300
+ tags,
301
+ draft: row['draft'] === 1,
302
+ ...optional('description', text(row['description'])),
303
+ ...optional('author', text(row['author'])),
304
+ ...optional('activitypub', json(row['activitypub'])),
305
+ extra: json(row['extra']) ?? {},
306
+ body: String(row['body']),
307
+ html: String(row['html']),
308
+ hash: String(row['hash']),
309
+ };
310
+ }
311
+ function text(value) {
312
+ return typeof value === 'string' ? value : undefined;
313
+ }
314
+ function json(value) {
315
+ if (typeof value !== 'string')
316
+ return undefined;
317
+ return JSON.parse(value);
318
+ }
319
+ function optional(key, value) {
320
+ return value === undefined ? {} : { [key]: value };
321
+ }
322
+ /**
323
+ * Turn SQLite's unique-constraint message into something a site owner can act
324
+ * on. Everything else is rethrown untouched.
325
+ */
326
+ function translateWriteError(error, document, pathForPermalink) {
327
+ if (!isUniqueViolation(error, 'documents.permalink'))
328
+ return error;
329
+ const row = pathForPermalink.get(document.permalink);
330
+ return new DuplicatePermalinkError(document.permalink, document.path, text(row?.['path']));
331
+ }
332
+ function isUniqueViolation(error, column) {
333
+ if (!(error instanceof Error))
334
+ return false;
335
+ const code = error.code;
336
+ if (code !== 'ERR_SQLITE_ERROR')
337
+ return false;
338
+ return error.message.includes('UNIQUE constraint failed') && error.message.includes(column);
339
+ }
340
+ /**
341
+ * Schema versions, applied in order. Never edit a migration that has shipped;
342
+ * append a new one, so a site that upgrades lands on the same schema as a site
343
+ * that starts fresh.
344
+ */
345
+ const MIGRATIONS = [
346
+ {
347
+ version: 1,
348
+ sql: `
349
+ CREATE TABLE documents (
350
+ path TEXT PRIMARY KEY,
351
+ type TEXT NOT NULL,
352
+ slug TEXT NOT NULL,
353
+ permalink TEXT NOT NULL,
354
+ title TEXT NOT NULL,
355
+ date TEXT,
356
+ date_sort TEXT,
357
+ updated TEXT,
358
+ draft INTEGER NOT NULL,
359
+ trashed INTEGER NOT NULL,
360
+ description TEXT,
361
+ author TEXT,
362
+ activitypub TEXT,
363
+ extra TEXT NOT NULL,
364
+ body TEXT NOT NULL,
365
+ html TEXT NOT NULL,
366
+ hash TEXT NOT NULL
367
+ );
368
+
369
+ CREATE UNIQUE INDEX documents_permalink ON documents (permalink);
370
+ CREATE INDEX documents_slug ON documents (slug);
371
+ CREATE INDEX documents_listing ON documents (type, draft, trashed, date_sort DESC);
372
+
373
+ CREATE TABLE document_tags (
374
+ path TEXT NOT NULL REFERENCES documents (path) ON DELETE CASCADE,
375
+ tag TEXT NOT NULL,
376
+ position INTEGER NOT NULL,
377
+ PRIMARY KEY (path, tag)
378
+ );
379
+
380
+ CREATE INDEX document_tags_tag ON document_tags (tag);
381
+ `,
382
+ },
383
+ ];
384
+ function migrate(db) {
385
+ db.exec(`
386
+ CREATE TABLE IF NOT EXISTS migrations (
387
+ version INTEGER PRIMARY KEY,
388
+ applied_at TEXT NOT NULL
389
+ )
390
+ `);
391
+ const applied = new Set(db
392
+ .prepare('SELECT version FROM migrations')
393
+ .all()
394
+ .map((row) => Number(row['version'])));
395
+ const record = db.prepare('INSERT INTO migrations (version, applied_at) VALUES (?, ?)');
396
+ for (const migration of MIGRATIONS) {
397
+ if (applied.has(migration.version))
398
+ continue;
399
+ db.exec('BEGIN');
400
+ try {
401
+ db.exec(migration.sql);
402
+ record.run(migration.version, new Date().toISOString());
403
+ db.exec('COMMIT');
404
+ }
405
+ catch (error) {
406
+ db.exec('ROLLBACK');
407
+ throw error;
408
+ }
409
+ }
410
+ }
411
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/content/store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,146 @@
1
+ import type { Document } from './document.ts';
2
+ import type { ContentStore } from './store.ts';
3
+ /** How long the watcher waits for a path to stop changing before re-reading it. */
4
+ export declare const DEFAULT_DEBOUNCE_MS = 100;
5
+ /** What happened to one document. */
6
+ export type DocumentChangeType = 'created' | 'updated' | 'deleted';
7
+ /**
8
+ * Where a change came from.
9
+ *
10
+ * `scan` is a full walk of the content directory, the boot scan included;
11
+ * `watch` is a file that changed under the running watcher; `admin` is a write
12
+ * the CMS made itself and announced, because the admin corrects the index as
13
+ * soon as the bytes land (doc-1) and the watcher's later re-read of that file
14
+ * is a hash no-op that would otherwise emit nothing at all.
15
+ */
16
+ export type ChangeOrigin = 'scan' | 'watch' | 'admin';
17
+ /**
18
+ * One change to the index, as the subscriber sees it.
19
+ *
20
+ * `previous` and `next` are the whole documents, so a subscriber can compare
21
+ * them itself — a draft going live, a title changing, a tag appearing.
22
+ */
23
+ export interface DocumentChange {
24
+ /** Which of the three primary changes this is. */
25
+ type: DocumentChangeType;
26
+ /** Content-relative path of the file, with `/` separators. */
27
+ path: string;
28
+ /** The indexed document before the change, `undefined` when there was none. */
29
+ previous: Document | undefined;
30
+ /** The document after the change, `undefined` when the file is gone. */
31
+ next: Document | undefined;
32
+ /** Where the change came from; see {@link ChangeOrigin}. */
33
+ origin: ChangeOrigin;
34
+ }
35
+ /** What a full scan did. */
36
+ export interface SyncResult {
37
+ /** Document files found on disk. */
38
+ scanned: number;
39
+ /** Files indexed for the first time. */
40
+ created: number;
41
+ /** Files whose row was rewritten because the content changed. */
42
+ updated: number;
43
+ /** Rows dropped because the file is gone. */
44
+ removed: number;
45
+ /** Files whose hash matched the indexed row, so nothing was written. */
46
+ unchanged: number;
47
+ /** Files that could not be parsed or indexed. Their previous row is left alone. */
48
+ failed: number;
49
+ }
50
+ /** Where parse failures go. Anything with a `warn` will do, including `console`. */
51
+ export interface SyncLogger {
52
+ warn(message: string): void;
53
+ }
54
+ /** How to build a {@link ContentSync}. */
55
+ export interface CreateContentSyncOptions {
56
+ /** The index to keep in step with the files. */
57
+ store: ContentStore;
58
+ /** Absolute path of the content directory. It need not exist yet. */
59
+ contentDir: string;
60
+ /** Watch for changes after the boot scan. Default `true`. */
61
+ watch?: boolean | undefined;
62
+ /** Per-path debounce for watcher events, in milliseconds. Default 100. */
63
+ debounceMs?: number | undefined;
64
+ /** Where parse failures are reported. Defaults to `console`. */
65
+ logger?: SyncLogger | undefined;
66
+ }
67
+ /** The events a subscriber can listen for, and what each one carries. */
68
+ export interface ContentEventMap {
69
+ /** A document was indexed for the first time. */
70
+ created: [DocumentChange];
71
+ /** An indexed document's content changed. */
72
+ updated: [DocumentChange];
73
+ /** An indexed document's file is gone. */
74
+ deleted: [DocumentChange];
75
+ /**
76
+ * A document became visible on the public site: created already published,
77
+ * a draft that was published, or a document restored from the trash. Fires
78
+ * after the primary event for the same change.
79
+ */
80
+ published: [DocumentChange];
81
+ /** The reverse: published to draft, trashed, or deleted while published. */
82
+ unpublished: [DocumentChange];
83
+ /** Every `created`, `updated` and `deleted`, for subscribers that want them all. */
84
+ change: [DocumentChange];
85
+ }
86
+ /**
87
+ * A listener for one kind of change.
88
+ *
89
+ * The return is `unknown` rather than `void` so an `async` listener type
90
+ * checks: the sync ignores the value but awaits a promise, which is what lets
91
+ * a subscriber that writes to disk finish before the change is called done.
92
+ */
93
+ export type ContentEventListener = (change: DocumentChange) => unknown;
94
+ /**
95
+ * Subscription to index changes.
96
+ *
97
+ * A listener that throws — or, when it is `async`, whose promise rejects — is
98
+ * reported through the logger and does not stop the sync, so one bad
99
+ * subscriber cannot wedge the watcher.
100
+ */
101
+ export interface ContentEvents {
102
+ /** Listen for an event. Returns the function that unsubscribes. */
103
+ on<K extends keyof ContentEventMap>(event: K, listener: ContentEventListener): () => void;
104
+ /** Listen for the next occurrence only. Returns the function that unsubscribes. */
105
+ once<K extends keyof ContentEventMap>(event: K, listener: ContentEventListener): () => void;
106
+ /** Stop listening. */
107
+ off<K extends keyof ContentEventMap>(event: K, listener: ContentEventListener): void;
108
+ }
109
+ /** Keeps the content index in step with the files on disk. */
110
+ export interface ContentSync {
111
+ /** Subscribe to index changes. */
112
+ readonly events: ContentEvents;
113
+ /**
114
+ * Report a change the sync did not make, and wait for every listener to
115
+ * finish with it.
116
+ *
117
+ * The admin writes a file and corrects the index in the same request rather
118
+ * than waiting for the watcher (doc-1), which means the watcher's re-read
119
+ * finds a hash that already matches and emits nothing. Without this, no
120
+ * subscriber would ever hear about an admin save. The promise settles once
121
+ * every listener has, so a caller that has to know the change was seen — the
122
+ * federation stamping `activitypub` into the file it just wrote — can wait
123
+ * for it.
124
+ */
125
+ announce(change: DocumentChange): Promise<void>;
126
+ /** Walk the content directory once, reconciling every file and dropping rows whose file is gone. */
127
+ sync(): Promise<SyncResult>;
128
+ /**
129
+ * Run the boot scan and, unless watching is off, start the watcher.
130
+ * Resolves with what the scan did.
131
+ */
132
+ start(): Promise<SyncResult>;
133
+ /** Stop the watcher. Safe to call before starting and safe to call twice. */
134
+ stop(): Promise<void>;
135
+ }
136
+ /**
137
+ * Build the sync for one content directory.
138
+ *
139
+ * Files are the source of truth (decision-1). A scan parses every document
140
+ * under `content/`, writes the ones whose hash differs from the indexed row,
141
+ * and drops rows whose file has gone. Nothing else in the directory is
142
+ * touched: uploads, directory data files and every underscore-prefixed
143
+ * directory but `_trash` are skipped.
144
+ */
145
+ export declare function createContentSync(options: CreateContentSyncOptions): ContentSync;
146
+ //# sourceMappingURL=sync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/content/sync.ts"],"names":[],"mappings":"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"}