smoodly 0.0.51 → 0.0.53

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 (292) hide show
  1. package/README.md +14 -13
  2. package/dist/admin/auth.d.ts +3 -11
  3. package/dist/admin/auth.js +1 -30
  4. package/dist/admin/client-ops.d.ts +5 -4
  5. package/dist/admin/client-ops.js +15 -12
  6. package/dist/admin/delivery-mode.d.ts +1 -1
  7. package/dist/admin/delivery-mode.js +2 -2
  8. package/dist/admin/drift.d.ts +0 -2
  9. package/dist/admin/drift.js +1 -1
  10. package/dist/admin/editor/EditorView.d.ts +0 -3
  11. package/dist/admin/editor/EditorView.js +11 -16
  12. package/dist/admin/editor/FloatingToolbar.d.ts +0 -1
  13. package/dist/admin/editor/FloatingToolbar.js +1 -1
  14. package/dist/admin/editor/SeoSettings.d.ts +0 -2
  15. package/dist/admin/editor/SeoSettings.js +2 -2
  16. package/dist/admin/editor/canvas-address.d.ts +8 -0
  17. package/dist/admin/editor/canvas-address.js +18 -2
  18. package/dist/admin/editor/canvas-diagnosis.d.ts +2 -0
  19. package/dist/admin/editor/canvas-diagnosis.js +3 -0
  20. package/dist/admin/editor/inline-dom.d.ts +0 -4
  21. package/dist/admin/editor/inline-dom.js +1 -1
  22. package/dist/admin/editor/preview-grant.d.ts +6 -0
  23. package/dist/admin/editor/preview-grant.js +25 -0
  24. package/dist/admin/editor/useCanvasDiagnosis.js +2 -2
  25. package/dist/admin/files.generated.js +1 -1
  26. package/dist/admin/fixed-nodes.d.ts +0 -6
  27. package/dist/admin/fixed-nodes.js +1 -81
  28. package/dist/admin/forms/CropEditor.d.ts +4 -2
  29. package/dist/admin/forms/CropEditor.js +58 -5
  30. package/dist/admin/forms/ImagePreview.d.ts +3 -1
  31. package/dist/admin/forms/ImagePreview.js +9 -3
  32. package/dist/admin/forms/MediaGrid.js +2 -1
  33. package/dist/admin/forms/MediaPicker.js +9 -6
  34. package/dist/admin/forms/MediaWidget.js +11 -9
  35. package/dist/admin/forms/assets-context.d.ts +7 -3
  36. package/dist/admin/forms/assets-context.js +5 -3
  37. package/dist/admin/forms/crop-editor.d.ts +24 -2
  38. package/dist/admin/forms/crop-editor.js +36 -2
  39. package/dist/admin/forms/date-picker.d.ts +0 -1
  40. package/dist/admin/forms/date-picker.js +1 -1
  41. package/dist/admin/forms/tabs.d.ts +0 -1
  42. package/dist/admin/forms/tabs.js +1 -1
  43. package/dist/admin/forms/upload.d.ts +18 -11
  44. package/dist/admin/forms/upload.js +62 -24
  45. package/dist/admin/global-items.d.ts +0 -3
  46. package/dist/admin/global-items.js +0 -54
  47. package/dist/admin/handler.d.ts +22 -28
  48. package/dist/admin/handler.js +146 -47
  49. package/dist/admin/health.d.ts +0 -4
  50. package/dist/admin/index.d.ts +6 -9
  51. package/dist/admin/index.js +3 -6
  52. package/dist/admin/ops.d.ts +40 -32
  53. package/dist/admin/page-data.d.ts +14 -6
  54. package/dist/admin/page-data.js +1 -1
  55. package/dist/admin/session-flow.d.ts +65 -0
  56. package/dist/admin/session-flow.js +121 -0
  57. package/dist/admin/shell/AdminApp.d.ts +8 -3
  58. package/dist/admin/shell/AdminApp.js +37 -58
  59. package/dist/admin/shell/ContentHealthPage.js +1 -3
  60. package/dist/admin/shell/GlobalFields.d.ts +0 -3
  61. package/dist/admin/shell/GlobalFields.js +1 -1
  62. package/dist/admin/shell/IconRail.js +10 -8
  63. package/dist/admin/shell/MediaScreen.js +9 -6
  64. package/dist/admin/shell/MembersPage.d.ts +1 -2
  65. package/dist/admin/shell/MembersPage.js +19 -90
  66. package/dist/admin/shell/SettingsView.d.ts +1 -2
  67. package/dist/admin/shell/SettingsView.js +2 -2
  68. package/dist/admin/shell/SignedOutView.d.ts +5 -0
  69. package/dist/admin/shell/SignedOutView.js +9 -0
  70. package/dist/admin/shell/console-links.d.ts +5 -0
  71. package/dist/admin/shell/console-links.js +11 -0
  72. package/dist/admin/shell/members-list.d.ts +1 -14
  73. package/dist/admin/shell/members-list.js +2 -23
  74. package/dist/admin/shell/pages-tree.d.ts +0 -2
  75. package/dist/admin/shell/pages-tree.js +1 -1
  76. package/dist/admin/shell/presence-socket.d.ts +10 -0
  77. package/dist/admin/shell/presence-socket.js +191 -0
  78. package/dist/admin/shell/presence.d.ts +6 -18
  79. package/dist/admin/shell/presence.js +4 -11
  80. package/dist/admin/shell/session.d.ts +19 -12
  81. package/dist/admin/shell/session.js +114 -47
  82. package/dist/admin/ui/LocaleSwitcher.d.ts +0 -4
  83. package/dist/admin/ui/LocaleSwitcher.js +2 -2
  84. package/dist/admin/ui/primitives.d.ts +4 -14
  85. package/dist/admin/ui/primitives.js +7 -9
  86. package/dist/admin/ui/theme.js +13 -2
  87. package/dist/admin/validate.js +10 -1
  88. package/dist/api-client.d.ts +26 -0
  89. package/dist/api-client.js +107 -0
  90. package/dist/assemble.d.ts +4 -41
  91. package/dist/assemble.js +5 -303
  92. package/dist/asset-store.d.ts +28 -30
  93. package/dist/asset-store.js +26 -121
  94. package/dist/astro/cache.d.ts +1 -1
  95. package/dist/astro/components/Head.astro +4 -1
  96. package/dist/astro/components/Image.astro +12 -17
  97. package/dist/astro/components/Video.astro +5 -2
  98. package/dist/astro/head.d.ts +3 -0
  99. package/dist/astro/head.js +24 -0
  100. package/dist/astro/index.js +61 -27
  101. package/dist/astro/locale-paths.d.ts +5 -2
  102. package/dist/astro/locale-paths.js +10 -2
  103. package/dist/astro/routes/admin.js +9 -7
  104. package/dist/astro/runtime.d.ts +3 -2
  105. package/dist/astro/runtime.js +5 -4
  106. package/dist/astro/server.d.ts +7 -11
  107. package/dist/astro/server.js +22 -28
  108. package/dist/astro/shell.d.ts +3 -3
  109. package/dist/astro/shell.js +1 -1
  110. package/dist/astro/static-media-node.d.ts +4 -0
  111. package/dist/astro/static-media-node.js +25 -0
  112. package/dist/astro/static-media.d.ts +17 -0
  113. package/dist/astro/static-media.js +55 -0
  114. package/dist/astro/static.d.ts +5 -10
  115. package/dist/astro/static.js +13 -29
  116. package/dist/canvas-marker.d.ts +14 -0
  117. package/dist/canvas-marker.js +36 -0
  118. package/dist/cli/api.d.ts +14 -0
  119. package/dist/cli/api.js +48 -0
  120. package/dist/cli/app-env.d.ts +2 -0
  121. package/dist/cli/app-env.js +20 -0
  122. package/dist/cli/export.d.ts +16 -2
  123. package/dist/cli/export.js +52 -35
  124. package/dist/cli/import.d.ts +7 -11
  125. package/dist/cli/import.js +50 -54
  126. package/dist/cli/load-config.d.ts +0 -1
  127. package/dist/cli/load-config.js +1 -1
  128. package/dist/cli/main.d.ts +4 -12
  129. package/dist/cli/main.js +26 -72
  130. package/dist/cli/push.d.ts +6 -0
  131. package/dist/cli/push.js +31 -0
  132. package/dist/collections.d.ts +0 -1
  133. package/dist/collections.js +1 -1
  134. package/dist/config.d.ts +20 -5
  135. package/dist/config.js +18 -1
  136. package/dist/content/content-api.d.ts +23 -23
  137. package/dist/content/content-api.js +48 -56
  138. package/dist/content/mime.d.ts +1 -0
  139. package/dist/content/mime.js +6 -0
  140. package/dist/css.js +1 -1
  141. package/dist/deploy-store.d.ts +19 -0
  142. package/dist/entry-store.d.ts +8 -94
  143. package/dist/entry-store.js +9 -557
  144. package/dist/env.d.ts +27 -20
  145. package/dist/env.js +38 -50
  146. package/dist/fields.d.ts +11 -3
  147. package/dist/fields.js +33 -2
  148. package/dist/global-id.d.ts +2 -0
  149. package/dist/global-id.js +4 -2
  150. package/dist/image/SmoodlyImage.d.ts +9 -5
  151. package/dist/image/SmoodlyImage.js +29 -15
  152. package/dist/image/SmoodlyVideo.d.ts +1 -1
  153. package/dist/image/SmoodlyVideo.js +3 -2
  154. package/dist/image/index.d.ts +2 -3
  155. package/dist/image/index.js +0 -2
  156. package/dist/image/markup.d.ts +53 -0
  157. package/dist/image/markup.js +111 -0
  158. package/dist/index.d.ts +13 -23
  159. package/dist/index.js +7 -20
  160. package/dist/media-cut.d.ts +44 -0
  161. package/dist/media-cut.js +70 -0
  162. package/dist/media.d.ts +58 -4
  163. package/dist/media.js +126 -16
  164. package/dist/member-store.d.ts +2 -50
  165. package/dist/member-store.js +0 -56
  166. package/dist/next/admin-route.d.ts +1 -1
  167. package/dist/next/admin-route.js +5 -3
  168. package/dist/next/meta.js +2 -3
  169. package/dist/next/page-renderer.d.ts +2 -2
  170. package/dist/next/page-renderer.js +22 -11
  171. package/dist/next/queries.d.ts +2 -2
  172. package/dist/path-index.d.ts +0 -24
  173. package/dist/path-index.js +0 -98
  174. package/dist/paths.d.ts +1 -1
  175. package/dist/paths.js +1 -1
  176. package/dist/push.d.ts +30 -0
  177. package/dist/push.js +52 -0
  178. package/dist/read-counter.d.ts +2 -0
  179. package/dist/read-counter.js +6 -0
  180. package/dist/reads.d.ts +7 -7
  181. package/dist/reads.js +3 -0
  182. package/dist/registry-json.d.ts +11 -0
  183. package/dist/registry-json.js +81 -0
  184. package/dist/render.d.ts +0 -1
  185. package/dist/render.js +1 -1
  186. package/dist/resolve.d.ts +12 -6
  187. package/dist/resolve.js +11 -8
  188. package/dist/revalidate.d.ts +8 -1
  189. package/dist/revalidate.js +8 -1
  190. package/dist/richtext/index.d.ts +1 -1
  191. package/dist/section-wrapper.d.ts +1 -1
  192. package/dist/section-wrapper.js +0 -9
  193. package/dist/signed-message.d.ts +26 -0
  194. package/dist/signed-message.js +103 -0
  195. package/dist/site.d.ts +59 -18
  196. package/dist/site.js +21 -252
  197. package/dist/space-transfer.d.ts +6 -3
  198. package/dist/space-transfer.js +18 -6
  199. package/dist/store.d.ts +2 -80
  200. package/dist/store.js +3 -489
  201. package/dist/toolset.d.ts +2 -1
  202. package/dist/toolset.js +1 -1
  203. package/dist/version.d.ts +1 -1
  204. package/dist/version.js +1 -1
  205. package/package.json +4 -15
  206. package/dist/admin/auth-config.d.ts +0 -6
  207. package/dist/admin/browser/fetch-op.d.ts +0 -10
  208. package/dist/admin/browser/fetch-op.js +0 -44
  209. package/dist/admin/browser/main.d.ts +0 -1
  210. package/dist/admin/browser/main.js +0 -23
  211. package/dist/admin/ops-impl.d.ts +0 -51
  212. package/dist/admin/ops-impl.js +0 -1403
  213. package/dist/admin/shell/InviteModal.d.ts +0 -6
  214. package/dist/admin/shell/InviteModal.js +0 -28
  215. package/dist/admin/shell/LoginView.d.ts +0 -5
  216. package/dist/admin/shell/LoginView.js +0 -80
  217. package/dist/admin/shell/SchemaNotice.d.ts +0 -4
  218. package/dist/admin/shell/SchemaNotice.js +0 -4
  219. package/dist/admin/shell/SetPasswordView.d.ts +0 -7
  220. package/dist/admin/shell/SetPasswordView.js +0 -41
  221. package/dist/admin/shell/presence-realtime.d.ts +0 -3
  222. package/dist/admin/shell/presence-realtime.js +0 -32
  223. package/dist/admin/supabase-auth.d.ts +0 -7
  224. package/dist/admin/supabase-auth.js +0 -30
  225. package/dist/astro/deploy-deps.d.ts +0 -10
  226. package/dist/astro/deploy-deps.js +0 -8
  227. package/dist/astro/image.d.ts +0 -27
  228. package/dist/astro/image.js +0 -29
  229. package/dist/bind.d.ts +0 -19
  230. package/dist/bind.js +0 -34
  231. package/dist/cli/add-admin.d.ts +0 -11
  232. package/dist/cli/add-admin.js +0 -25
  233. package/dist/cli/load-collections.d.ts +0 -2
  234. package/dist/cli/load-collections.js +0 -27
  235. package/dist/cli/migrate.d.ts +0 -43
  236. package/dist/cli/migrate.js +0 -138
  237. package/dist/cli/rebuild.d.ts +0 -23
  238. package/dist/cli/rebuild.js +0 -22
  239. package/dist/cli/space-io.d.ts +0 -41
  240. package/dist/cli/space-io.js +0 -84
  241. package/dist/client.d.ts +0 -11
  242. package/dist/client.js +0 -54
  243. package/dist/current-space.d.ts +0 -4
  244. package/dist/current-space.js +0 -9
  245. package/dist/document-stamp.d.ts +0 -7
  246. package/dist/document-stamp.js +0 -48
  247. package/dist/image/loaders.d.ts +0 -15
  248. package/dist/image/loaders.js +0 -5
  249. package/dist/image/next-config.d.ts +0 -18
  250. package/dist/image/next-config.js +0 -32
  251. package/dist/image/remote-pattern.d.ts +0 -1
  252. package/dist/image/remote-pattern.js +0 -5
  253. package/dist/migrations.d.ts +0 -37
  254. package/dist/migrations.js +0 -73
  255. package/dist/project.d.ts +0 -88
  256. package/dist/project.js +0 -231
  257. package/dist/published.d.ts +0 -142
  258. package/dist/published.js +0 -164
  259. package/dist/ref-index.d.ts +0 -19
  260. package/dist/ref-index.js +0 -0
  261. package/dist/schema-read.d.ts +0 -23
  262. package/dist/schema-read.js +0 -50
  263. package/dist/schema-version.d.ts +0 -19
  264. package/dist/schema-version.js +0 -34
  265. package/dist/sql-space.d.ts +0 -44
  266. package/dist/sql-space.js +0 -289
  267. package/dist/sql.d.ts +0 -15
  268. package/dist/sql.js +0 -556
  269. package/dist/supabase-asset-store.d.ts +0 -21
  270. package/dist/supabase-asset-store.js +0 -170
  271. package/dist/supabase-entry-store.d.ts +0 -92
  272. package/dist/supabase-entry-store.js +0 -824
  273. package/dist/supabase-member-store.d.ts +0 -21
  274. package/dist/supabase-member-store.js +0 -96
  275. package/dist/supabase-path-index.d.ts +0 -30
  276. package/dist/supabase-path-index.js +0 -138
  277. package/dist/supabase-published-store.d.ts +0 -56
  278. package/dist/supabase-published-store.js +0 -314
  279. package/dist/supabase-store.d.ts +0 -103
  280. package/dist/supabase-store.js +0 -678
  281. package/migrations/20260926120000_smoodly_v1.sql +0 -535
  282. package/migrations/20260926130000_smoodly_v2.sql +0 -26
  283. package/migrations/20260926140000_smoodly_v3.sql +0 -38
  284. package/migrations/20260926150000_smoodly_v4.sql +0 -41
  285. package/migrations/20260929120000_smoodly_v5.sql +0 -56
  286. package/migrations/20260929135523_smoodly_v6.sql +0 -200
  287. package/migrations/20260930040956_smoodly_v7.sql +0 -84
  288. package/migrations/20261007120000_smoodly_v8.sql +0 -154
  289. package/migrations/20261007200000_smoodly_v9.sql +0 -30
  290. package/migrations/20261008120000_smoodly_v10.sql +0 -47
  291. package/migrations/20261009120000_smoodly_v11.sql +0 -49
  292. /package/dist/{admin/auth-config.js → deploy-store.js} +0 -0
@@ -2,13 +2,11 @@
2
2
  // operations with no signed-in editor, as a few verbs that throw. Scripts
3
3
  // and route handlers write content through this, never through the stores,
4
4
  // so validation, the code-owned address guards and publish checks apply.
5
- import { createAdminOps } from "../admin/ops-impl.js";
5
+ // One surface over a transport (spec 2026-10-14 §1): the content API with the
6
+ // secret key for a seed or a script, the stores in one process for the service.
7
+ import { makeClientOps } from "../admin/client-ops.js";
6
8
  import { imageDimensions } from "../image-dimensions.js";
7
- const MIME = {
8
- jpg: "image/jpeg", jpeg: "image/jpeg", png: "image/png", webp: "image/webp", gif: "image/gif", avif: "image/avif",
9
- svg: "image/svg+xml", mp4: "video/mp4", webm: "video/webm", pdf: "application/pdf",
10
- };
11
- const EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
9
+ import { mimeOf } from "./mime.js";
12
10
  export class SmoodlyContentError extends Error {
13
11
  constructor(error) {
14
12
  const list = (error.fields ?? []).map((f) => `\n ${f.field}: ${f.message}`).join("");
@@ -24,42 +22,57 @@ async function must(result) {
24
22
  throw new SmoodlyContentError(r);
25
23
  return r.data;
26
24
  }
27
- /** With a password: the account is created (or an existing one kept) and made an admin. Without: an invite mail,
28
- * whose link goes to `redirectTo` or the Auth project's Site URL. Running it again changes nothing.
29
- * Its own function because `smoodly add-admin` has a member store and no config. */
30
- export async function addAdmin(members, email, options = {}) {
31
- if (!members)
32
- throw new SmoodlyContentError({ code: "internal", message: "smoodly: no member store — pass `members` to createSmoodlyContent." });
33
- if (!EMAIL.test(email))
34
- throw new SmoodlyContentError({ code: "validation", message: `smoodly: "${email}" is not an email address.` });
35
- if (options.password !== undefined && options.password.length < 8) {
36
- throw new SmoodlyContentError({ code: "validation", message: "smoodly: the password needs at least 8 characters." });
37
- }
38
- try {
39
- // Where smoodly.io manages the members, nothing here writes them — as the admin's Members page refuses.
40
- if ((await members.consoleUrl()) !== null) {
41
- throw new SmoodlyContentError({ code: "blocked", message: "smoodly: this site's members are managed at smoodly.io." });
25
+ /** Over the content API with the secret key (a seed, a script, the installer). A refusal throws SmoodlyContentError
26
+ * with the service's code and message; a network failure names SMOODLY_API_URL. */
27
+ export function apiTransport(options) {
28
+ const apiUrl = options.apiUrl.replace(/\/+$/, "");
29
+ const base = `${apiUrl}/v1/spaces/${encodeURIComponent(options.spaceId)}`;
30
+ const doFetch = options.fetch ?? fetch;
31
+ async function send(path, init = {}) {
32
+ let res;
33
+ try {
34
+ res = await doFetch(`${base}${path}`, { ...init, headers: { ...init.headers, authorization: `Bearer ${options.key}`, accept: "application/json" } });
42
35
  }
43
- if (options.password !== undefined) {
44
- await members.createAccount({ email, password: options.password, role: "admin" });
45
- return { email, invited: false };
36
+ catch (e) {
37
+ throw new SmoodlyContentError({ code: "internal", message: `smoodly: can't reach SMOODLY_API_URL (${apiUrl}): ${e instanceof Error ? e.message : String(e)}` });
46
38
  }
47
- const { invited } = await members.invite({ email, role: "admin", redirectTo: options.redirectTo ?? "" });
48
- return { email, invited };
49
- }
50
- catch (e) {
51
- if (e instanceof SmoodlyContentError)
52
- throw e;
53
- throw new SmoodlyContentError({ code: "internal", message: e instanceof Error ? e.message : String(e) });
39
+ const body = (await res.json().catch(() => null));
40
+ if (!body || typeof body.ok !== "boolean") {
41
+ return { ok: false, code: "internal", message: `smoodly: SMOODLY_API_URL (${apiUrl}) answered ${res.status} with no message.` };
42
+ }
43
+ return body;
54
44
  }
45
+ const json = (body) => ({ method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) });
46
+ return {
47
+ op: (call) => send("/op", json({ path: call.path, args: call.args })),
48
+ // A draft read (DESIGN §1 Content API › Writes): the caller holds the secret key and asks about what it is writing,
49
+ // and a draft read is never stored at the edge, so a script sees its own create at once.
50
+ locate: (locale, path) => must(send(`/locate?${new URLSearchParams({ locale, path, draft: "1" })}`)),
51
+ // The bytes as the body with their length, the rest in the query (spec 2026-10-18 media on R2 and Images §4).
52
+ async upload(bytes, filename, mime, meta) {
53
+ const type = mime ?? mimeOf(filename);
54
+ const params = { name: filename, mime: type };
55
+ const dims = imageDimensions(bytes);
56
+ if (dims) {
57
+ params.width = String(dims.width);
58
+ params.height = String(dims.height);
59
+ }
60
+ for (const key of ["title", "alt", "caption", "credit"])
61
+ if (meta[key] !== undefined)
62
+ params[key] = meta[key];
63
+ const record = await must(send(`/media?${new URLSearchParams(params)}`, { method: "PUT", headers: { "content-type": type }, body: bytes }));
64
+ return { asset: record.id, ...(meta.alt !== undefined ? { alt: meta.alt } : {}) };
65
+ },
66
+ };
55
67
  }
56
68
  export function createSmoodlyContent(deps) {
57
- const ops = createAdminOps(deps);
69
+ const { transport } = deps;
70
+ const ops = makeClientOps((call) => transport.op(call));
58
71
  const def = deps.config.locales.default;
59
72
  async function findPage(locale, path) {
60
- const hit = await deps.pages.resolvePath(locale, path);
73
+ const hit = await transport.locate(locale, path);
61
74
  if (hit?.kind === "page" && !hit.redirect)
62
- return deps.pages.getPage(hit.id);
75
+ return (await must(ops.pages.get(hit.id, locale))).record;
63
76
  return null;
64
77
  }
65
78
  async function updateEntry(collection, id, locale, fields) {
@@ -145,32 +158,11 @@ export function createSmoodlyContent(deps) {
145
158
  media: {
146
159
  /** A file path or bytes in, a ready asset and the value an image field stores out. */
147
160
  async upload(file, meta = {}) {
148
- if (!deps.assets)
149
- throw new SmoodlyContentError({ code: "internal", message: "smoodly: no media store — pass `assets` to createSmoodlyContent." });
150
161
  // Loaded here, not at the top: this module is reachable from `smoodly/admin`, which a bundler may walk.
151
162
  const bytes = typeof file === "string" ? new Uint8Array(await (await import("node:fs/promises")).readFile(file)) : file;
152
163
  const filename = meta.filename ?? (typeof file === "string" ? file.split(/[\\/]/).pop() : "upload");
153
- const mime = meta.mime ?? MIME[filename.split(".").pop().toLowerCase()] ?? "application/octet-stream";
154
- // The media library's own rules, as the admin's upload applies them (config.media).
155
- const { accept, maxSize } = deps.config.media;
156
- if (!accept.includes(mime))
157
- throw new SmoodlyContentError({ code: "validation", message: `smoodly: "${filename}" is ${mime}, not an accepted file type.` });
158
- if (bytes.byteLength > maxSize) {
159
- throw new SmoodlyContentError({ code: "validation", message: `smoodly: "${filename}" is larger than ${Math.round(maxSize / 1024 / 1024)} MB.` });
160
- }
161
- const { alt, title, caption, credit } = meta;
162
- let asset;
163
- try {
164
- asset = await deps.assets.create({ filename, mime, size: bytes.byteLength, bytes, alt, title, caption, credit, ...(imageDimensions(bytes) ?? {}) });
165
- }
166
- catch (e) {
167
- throw new SmoodlyContentError({ code: "internal", message: e instanceof Error ? e.message : String(e) });
168
- }
169
- return { asset: asset.id, ...(alt !== undefined ? { alt } : {}) };
164
+ return transport.upload(bytes, filename, meta.mime, meta);
170
165
  },
171
166
  },
172
- members: {
173
- addAdmin: (email, options = {}) => addAdmin(deps.members, email, options),
174
- },
175
167
  };
176
168
  }
@@ -0,0 +1 @@
1
+ export declare const mimeOf: (filename: string) => string;
@@ -0,0 +1,6 @@
1
+ // A file's type from its extension, for an upload that names none (a script's file path, bytes with a filename).
2
+ const MIME = {
3
+ jpg: "image/jpeg", jpeg: "image/jpeg", png: "image/png", webp: "image/webp", gif: "image/gif", avif: "image/avif",
4
+ svg: "image/svg+xml", mp4: "video/mp4", webm: "video/webm", pdf: "application/pdf",
5
+ };
6
+ export const mimeOf = (filename) => MIME[filename.split(".").pop().toLowerCase()] ?? "application/octet-stream";
package/dist/css.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // The custom-CSS box (spec 2026-09-14 §4): a per-section snippet the
2
2
  // wrapper nests under the section's own [data-smd-node] selector. CSS
3
3
  // can do two things beyond styling — pull a remote stylesheet and beacon
4
- // to a host — so those, the two legacy code hooks and the tag-closing
4
+ // to a host — so those, the two old code hooks (`expression()`, `behavior:`) and the tag-closing
5
5
  // sequence are refused at save and stripped at render, in that order.
6
6
  // The checks run on the snippet with its CSS escapes decoded, so
7
7
  // `\75 rl(` cannot spell `url(` past them; and the braces must balance,
@@ -0,0 +1,19 @@
1
+ export interface DeployStore {
2
+ /** The latest published_at over pages and entries (ms); null when nothing is published. The static build's watermark. */
3
+ newestPublishedAt(): Promise<number | null>;
4
+ /** Locale rows published strictly after `since` (ms), pages plus entries: the tile's "changes waiting". */
5
+ countPublishedAfter(since: number): Promise<number>;
6
+ /** The space's last Deploy press; null until the first. */
7
+ deployRequest(): Promise<{
8
+ at: number;
9
+ by: string | null;
10
+ } | null>;
11
+ requestDeploy(by: string | null, at?: number): Promise<void>;
12
+ /** When a published row last went (an unpublish, a delete): a change the count of newer rows cannot see. */
13
+ lastRemovalAt(): Promise<number | null>;
14
+ /** Written by the operations on an unpublish or a delete of a published row. */
15
+ recordRemoval(at?: number): Promise<void>;
16
+ /** Written by the operations on every publish: the SQLite store ignores it (the locale row carries published_at),
17
+ * the memory one counts it. */
18
+ recordPublish(at?: number): Promise<void>;
19
+ }
@@ -1,9 +1,8 @@
1
1
  import type { CollectionSchema, EntryOrder } from "./collections.ts";
2
2
  import type { GlobalSchema } from "./global.ts";
3
3
  import { type RefEdge, type SectionLike } from "./refs.ts";
4
- import { MemoryRefIndex } from "./ref-index.ts";
5
- import { type PathHit, type PathIndex } from "./path-index.ts";
6
- import { type DraftToken } from "./draft-token.ts";
4
+ import type { PathHit, PathIndex } from "./path-index.ts";
5
+ import type { DraftToken } from "./draft-token.ts";
7
6
  import { type LocaleSet } from "./paths.ts";
8
7
  export type EntryLocale = {
9
8
  /** The entry's segment in this locale; null claims no URL. */
@@ -59,8 +58,8 @@ export type Where = Record<string, WhereValue | WhereValue[]>;
59
58
  * adapters read the text form of the whole array, never its elements). */
60
59
  export declare const ARRAY_FIELD_TYPES: ReadonlySet<string>;
61
60
  /** The memory adapter's and the tests' reading of `where`: a scalar matches an
62
- * equal value or an array holding it (JSONB containment); an array is any-of,
63
- * compared as text like PostgREST's `->>`. */
61
+ * equal value or an array holding it (containment); an array is any-of,
62
+ * compared as text (textForm). */
64
63
  export declare function matchesWhere(fields: Record<string, unknown>, where: Where): boolean;
65
64
  export type ListOptions = {
66
65
  /** Equality / any-of on shared fields, executed by the store (spec 2026-09-11 §3). */
@@ -254,96 +253,11 @@ export declare function entryTitleIn(record: EntryRecord, locale: string, schema
254
253
  * Sort comparator for one order rule; `seq` breaks ties by creation. A
255
254
  * declared order field is SHARED (collection() refuses a localized one),
256
255
  * so `e.fields[order.by]` reads the node. Nulls sort last in BOTH
257
- * directions (the Supabase adapter uses `nullsFirst: false` to match);
256
+ * directions (the SQLite list compiler sorts nulls last to match);
258
257
  * the creation tiebreak follows `direction` for non-manual orders.
259
258
  * Declared fields compare numerically when both values are numbers,
260
- * otherwise as strings via `localeCompare` — the same order Postgres
261
- * gives the jsonb VALUE (`fields->x`) for one type; see MAINTAINERS.md's
262
- * follow-ups for the mixed-type and JSON-null edges.
259
+ * otherwise as strings via `localeCompare` — the order the SQLite store's
260
+ * `COLLATE NOCASE` gives for one type (ASCII only; see MAINTAINERS.md's
261
+ * follow-ups for the mixed-type, JSON-null and non-ASCII edges).
263
262
  */
264
263
  export declare function compareEntries(order: EntryOrder, seq: (e: EntryRecord) => number): (a: EntryRecord, b: EntryRecord) => number;
265
- /** Comparator for `query`: like compareEntries, but a declared field reads the
266
- * merged snapshot, `updatedAt` the locale row's, and an absent, null or empty-string value
267
- * sorts last in both directions; ties break by creation order (ascending). */
268
- export declare function compareSnapshots(order: EntryOrder, locale: string, seq: (id: string) => number): (a: SnapshotRow, b: SnapshotRow) => number;
269
- export declare class MemoryEntryStore implements EntryStore {
270
- private refs;
271
- private entries;
272
- private versions;
273
- /** Insertion order per version — `savedAt` alone can tie within a millisecond. */
274
- private versionSeq;
275
- private seq;
276
- private order;
277
- private paths;
278
- private locales;
279
- private schemas;
280
- private elements;
281
- constructor(collections: CollectionSchema<any>[], refs?: MemoryRefIndex, options?: EntryStoreOptions);
282
- private schema;
283
- private row;
284
- private must;
285
- private byId;
286
- private snapshot;
287
- private subtree;
288
- /** An edit bumps the node and the edited row; `move` and `reorder` never call this. */
289
- private bump;
290
- /** The draft rule (spec 2026-09-13 history §1) for every collection (spec 2026-09-14 §1):
291
- * the unpublished draft row is rewritten in place and a new one starts only when the
292
- * draft IS the published one. */
293
- private recordVersion;
294
- /** A published row from before every collection was versioned has no snapshot; give it
295
- * one from what is live BEFORE a write changes the fields, so the site keeps serving it
296
- * and the write lands in a new draft row (spec 2026-09-14 §1). */
297
- private ensureLiveSnapshot;
298
- private dropVersions;
299
- /** Every field set the site can render for this entry: each present
300
- * locale's live view, plus a published snapshot the draft has moved
301
- * past (still rendered by the published site). */
302
- private liveFieldSets;
303
- private writePaths;
304
- private writeRefs;
305
- private siblings;
306
- /** The friendly pre-check (spec 2026-09-06 §11): a string slug must be
307
- * unique among the siblings PRESENT in that locale; null never
308
- * conflicts. The paths primary key remains the invariant. */
309
- private assertSiblingSlug;
310
- /** Same collection, within depth. Returns the parent (null at the root). */
311
- private assertParent;
312
- /** The parent rule: a locale can only be added where the parent has it. */
313
- private assertParentHasLocale;
314
- create(collection: string, fields: Record<string, unknown>, options: CreateEntryOptions, by?: string): Promise<EntryRecord>;
315
- get(collection: string, id: string): Promise<EntryRecord | null>;
316
- list(collection: string, options?: ListOptions): Promise<EntryRecord[]>;
317
- query(collection: string, q: SnapshotQuery): Promise<SnapshotRow[]>;
318
- getMany(collection: string, ids: string[], locale: string, options?: {
319
- status?: "published";
320
- }): Promise<Record<string, Record<string, unknown>>>;
321
- update(collection: string, id: string, locale: string, fields: Record<string, unknown>, by?: string, expected?: DraftToken | null, options?: {
322
- redirect?: boolean;
323
- }): Promise<EntryRecord>;
324
- setStatus(collection: string, id: string, locale: string, status: "draft" | "published", by?: string, expected?: DraftToken | null): Promise<EntryRecord>;
325
- discardDraft(collection: string, id: string, locale: string, by?: string, expected?: DraftToken | null): Promise<EntryRecord>;
326
- addLocale(collection: string, id: string, locale: string, options: AddEntryLocaleOptions, by?: string): Promise<EntryRecord>;
327
- removeLocale(collection: string, id: string, locale: string): Promise<EntryRecord>;
328
- scanEntries(): Promise<EntryScan[]>;
329
- listVersions(collection: string, id: string, locale: string): Promise<EntryVersion[]>;
330
- restore(collection: string, id: string, locale: string, versionId: string, by?: string, expected?: DraftToken | null): Promise<EntryRecord>;
331
- getVersion(collection: string, versionId: string): Promise<EntryVersion | null>;
332
- move(collection: string, id: string, to: {
333
- parentId: string | null;
334
- index?: number;
335
- }, options?: {
336
- redirect?: boolean;
337
- }): Promise<EntryRecord>;
338
- delete(collection: string, id: string): Promise<void>;
339
- reorder(collection: string, orderedIds: string[]): Promise<void>;
340
- referencesTo(collection: string, id: string): Promise<EntryUsage[]>;
341
- incoming(targetId: string): Promise<{
342
- sourceKind: string;
343
- sourceId: string;
344
- }[]>;
345
- resolvePath(locale: string, path: string): Promise<PathHit | null>;
346
- pathsIn(collection: string, ids: string[], locale: string): Promise<Record<string, string>>;
347
- pathsOf(collection: string, id: string): Promise<{}>;
348
- redirectsOf(collection: string, id: string): Promise<Record<string, string[]>>;
349
- }