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.
- package/README.md +14 -13
- package/dist/admin/auth.d.ts +3 -11
- package/dist/admin/auth.js +1 -30
- package/dist/admin/client-ops.d.ts +5 -4
- package/dist/admin/client-ops.js +15 -12
- package/dist/admin/delivery-mode.d.ts +1 -1
- package/dist/admin/delivery-mode.js +2 -2
- package/dist/admin/drift.d.ts +0 -2
- package/dist/admin/drift.js +1 -1
- package/dist/admin/editor/EditorView.d.ts +0 -3
- package/dist/admin/editor/EditorView.js +11 -16
- package/dist/admin/editor/FloatingToolbar.d.ts +0 -1
- package/dist/admin/editor/FloatingToolbar.js +1 -1
- package/dist/admin/editor/SeoSettings.d.ts +0 -2
- package/dist/admin/editor/SeoSettings.js +2 -2
- package/dist/admin/editor/canvas-address.d.ts +8 -0
- package/dist/admin/editor/canvas-address.js +18 -2
- package/dist/admin/editor/canvas-diagnosis.d.ts +2 -0
- package/dist/admin/editor/canvas-diagnosis.js +3 -0
- package/dist/admin/editor/inline-dom.d.ts +0 -4
- package/dist/admin/editor/inline-dom.js +1 -1
- package/dist/admin/editor/preview-grant.d.ts +6 -0
- package/dist/admin/editor/preview-grant.js +25 -0
- package/dist/admin/editor/useCanvasDiagnosis.js +2 -2
- package/dist/admin/files.generated.js +1 -1
- package/dist/admin/fixed-nodes.d.ts +0 -6
- package/dist/admin/fixed-nodes.js +1 -81
- package/dist/admin/forms/CropEditor.d.ts +4 -2
- package/dist/admin/forms/CropEditor.js +58 -5
- package/dist/admin/forms/ImagePreview.d.ts +3 -1
- package/dist/admin/forms/ImagePreview.js +9 -3
- package/dist/admin/forms/MediaGrid.js +2 -1
- package/dist/admin/forms/MediaPicker.js +9 -6
- package/dist/admin/forms/MediaWidget.js +11 -9
- package/dist/admin/forms/assets-context.d.ts +7 -3
- package/dist/admin/forms/assets-context.js +5 -3
- package/dist/admin/forms/crop-editor.d.ts +24 -2
- package/dist/admin/forms/crop-editor.js +36 -2
- package/dist/admin/forms/date-picker.d.ts +0 -1
- package/dist/admin/forms/date-picker.js +1 -1
- package/dist/admin/forms/tabs.d.ts +0 -1
- package/dist/admin/forms/tabs.js +1 -1
- package/dist/admin/forms/upload.d.ts +18 -11
- package/dist/admin/forms/upload.js +62 -24
- package/dist/admin/global-items.d.ts +0 -3
- package/dist/admin/global-items.js +0 -54
- package/dist/admin/handler.d.ts +22 -28
- package/dist/admin/handler.js +146 -47
- package/dist/admin/health.d.ts +0 -4
- package/dist/admin/index.d.ts +6 -9
- package/dist/admin/index.js +3 -6
- package/dist/admin/ops.d.ts +40 -32
- package/dist/admin/page-data.d.ts +14 -6
- package/dist/admin/page-data.js +1 -1
- package/dist/admin/session-flow.d.ts +65 -0
- package/dist/admin/session-flow.js +121 -0
- package/dist/admin/shell/AdminApp.d.ts +8 -3
- package/dist/admin/shell/AdminApp.js +37 -58
- package/dist/admin/shell/ContentHealthPage.js +1 -3
- package/dist/admin/shell/GlobalFields.d.ts +0 -3
- package/dist/admin/shell/GlobalFields.js +1 -1
- package/dist/admin/shell/IconRail.js +10 -8
- package/dist/admin/shell/MediaScreen.js +9 -6
- package/dist/admin/shell/MembersPage.d.ts +1 -2
- package/dist/admin/shell/MembersPage.js +19 -90
- package/dist/admin/shell/SettingsView.d.ts +1 -2
- package/dist/admin/shell/SettingsView.js +2 -2
- package/dist/admin/shell/SignedOutView.d.ts +5 -0
- package/dist/admin/shell/SignedOutView.js +9 -0
- package/dist/admin/shell/console-links.d.ts +5 -0
- package/dist/admin/shell/console-links.js +11 -0
- package/dist/admin/shell/members-list.d.ts +1 -14
- package/dist/admin/shell/members-list.js +2 -23
- package/dist/admin/shell/pages-tree.d.ts +0 -2
- package/dist/admin/shell/pages-tree.js +1 -1
- package/dist/admin/shell/presence-socket.d.ts +10 -0
- package/dist/admin/shell/presence-socket.js +191 -0
- package/dist/admin/shell/presence.d.ts +6 -18
- package/dist/admin/shell/presence.js +4 -11
- package/dist/admin/shell/session.d.ts +19 -12
- package/dist/admin/shell/session.js +114 -47
- package/dist/admin/ui/LocaleSwitcher.d.ts +0 -4
- package/dist/admin/ui/LocaleSwitcher.js +2 -2
- package/dist/admin/ui/primitives.d.ts +4 -14
- package/dist/admin/ui/primitives.js +7 -9
- package/dist/admin/ui/theme.js +13 -2
- package/dist/admin/validate.js +10 -1
- package/dist/api-client.d.ts +26 -0
- package/dist/api-client.js +107 -0
- package/dist/assemble.d.ts +4 -41
- package/dist/assemble.js +5 -303
- package/dist/asset-store.d.ts +28 -30
- package/dist/asset-store.js +26 -121
- package/dist/astro/cache.d.ts +1 -1
- package/dist/astro/components/Head.astro +4 -1
- package/dist/astro/components/Image.astro +12 -17
- package/dist/astro/components/Video.astro +5 -2
- package/dist/astro/head.d.ts +3 -0
- package/dist/astro/head.js +24 -0
- package/dist/astro/index.js +61 -27
- package/dist/astro/locale-paths.d.ts +5 -2
- package/dist/astro/locale-paths.js +10 -2
- package/dist/astro/routes/admin.js +9 -7
- package/dist/astro/runtime.d.ts +3 -2
- package/dist/astro/runtime.js +5 -4
- package/dist/astro/server.d.ts +7 -11
- package/dist/astro/server.js +22 -28
- package/dist/astro/shell.d.ts +3 -3
- package/dist/astro/shell.js +1 -1
- package/dist/astro/static-media-node.d.ts +4 -0
- package/dist/astro/static-media-node.js +25 -0
- package/dist/astro/static-media.d.ts +17 -0
- package/dist/astro/static-media.js +55 -0
- package/dist/astro/static.d.ts +5 -10
- package/dist/astro/static.js +13 -29
- package/dist/canvas-marker.d.ts +14 -0
- package/dist/canvas-marker.js +36 -0
- package/dist/cli/api.d.ts +14 -0
- package/dist/cli/api.js +48 -0
- package/dist/cli/app-env.d.ts +2 -0
- package/dist/cli/app-env.js +20 -0
- package/dist/cli/export.d.ts +16 -2
- package/dist/cli/export.js +52 -35
- package/dist/cli/import.d.ts +7 -11
- package/dist/cli/import.js +50 -54
- package/dist/cli/load-config.d.ts +0 -1
- package/dist/cli/load-config.js +1 -1
- package/dist/cli/main.d.ts +4 -12
- package/dist/cli/main.js +26 -72
- package/dist/cli/push.d.ts +6 -0
- package/dist/cli/push.js +31 -0
- package/dist/collections.d.ts +0 -1
- package/dist/collections.js +1 -1
- package/dist/config.d.ts +20 -5
- package/dist/config.js +18 -1
- package/dist/content/content-api.d.ts +23 -23
- package/dist/content/content-api.js +48 -56
- package/dist/content/mime.d.ts +1 -0
- package/dist/content/mime.js +6 -0
- package/dist/css.js +1 -1
- package/dist/deploy-store.d.ts +19 -0
- package/dist/entry-store.d.ts +8 -94
- package/dist/entry-store.js +9 -557
- package/dist/env.d.ts +27 -20
- package/dist/env.js +38 -50
- package/dist/fields.d.ts +11 -3
- package/dist/fields.js +33 -2
- package/dist/global-id.d.ts +2 -0
- package/dist/global-id.js +4 -2
- package/dist/image/SmoodlyImage.d.ts +9 -5
- package/dist/image/SmoodlyImage.js +29 -15
- package/dist/image/SmoodlyVideo.d.ts +1 -1
- package/dist/image/SmoodlyVideo.js +3 -2
- package/dist/image/index.d.ts +2 -3
- package/dist/image/index.js +0 -2
- package/dist/image/markup.d.ts +53 -0
- package/dist/image/markup.js +111 -0
- package/dist/index.d.ts +13 -23
- package/dist/index.js +7 -20
- package/dist/media-cut.d.ts +44 -0
- package/dist/media-cut.js +70 -0
- package/dist/media.d.ts +58 -4
- package/dist/media.js +126 -16
- package/dist/member-store.d.ts +2 -50
- package/dist/member-store.js +0 -56
- package/dist/next/admin-route.d.ts +1 -1
- package/dist/next/admin-route.js +5 -3
- package/dist/next/meta.js +2 -3
- package/dist/next/page-renderer.d.ts +2 -2
- package/dist/next/page-renderer.js +22 -11
- package/dist/next/queries.d.ts +2 -2
- package/dist/path-index.d.ts +0 -24
- package/dist/path-index.js +0 -98
- package/dist/paths.d.ts +1 -1
- package/dist/paths.js +1 -1
- package/dist/push.d.ts +30 -0
- package/dist/push.js +52 -0
- package/dist/read-counter.d.ts +2 -0
- package/dist/read-counter.js +6 -0
- package/dist/reads.d.ts +7 -7
- package/dist/reads.js +3 -0
- package/dist/registry-json.d.ts +11 -0
- package/dist/registry-json.js +81 -0
- package/dist/render.d.ts +0 -1
- package/dist/render.js +1 -1
- package/dist/resolve.d.ts +12 -6
- package/dist/resolve.js +11 -8
- package/dist/revalidate.d.ts +8 -1
- package/dist/revalidate.js +8 -1
- package/dist/richtext/index.d.ts +1 -1
- package/dist/section-wrapper.d.ts +1 -1
- package/dist/section-wrapper.js +0 -9
- package/dist/signed-message.d.ts +26 -0
- package/dist/signed-message.js +103 -0
- package/dist/site.d.ts +59 -18
- package/dist/site.js +21 -252
- package/dist/space-transfer.d.ts +6 -3
- package/dist/space-transfer.js +18 -6
- package/dist/store.d.ts +2 -80
- package/dist/store.js +3 -489
- package/dist/toolset.d.ts +2 -1
- package/dist/toolset.js +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -15
- package/dist/admin/auth-config.d.ts +0 -6
- package/dist/admin/browser/fetch-op.d.ts +0 -10
- package/dist/admin/browser/fetch-op.js +0 -44
- package/dist/admin/browser/main.d.ts +0 -1
- package/dist/admin/browser/main.js +0 -23
- package/dist/admin/ops-impl.d.ts +0 -51
- package/dist/admin/ops-impl.js +0 -1403
- package/dist/admin/shell/InviteModal.d.ts +0 -6
- package/dist/admin/shell/InviteModal.js +0 -28
- package/dist/admin/shell/LoginView.d.ts +0 -5
- package/dist/admin/shell/LoginView.js +0 -80
- package/dist/admin/shell/SchemaNotice.d.ts +0 -4
- package/dist/admin/shell/SchemaNotice.js +0 -4
- package/dist/admin/shell/SetPasswordView.d.ts +0 -7
- package/dist/admin/shell/SetPasswordView.js +0 -41
- package/dist/admin/shell/presence-realtime.d.ts +0 -3
- package/dist/admin/shell/presence-realtime.js +0 -32
- package/dist/admin/supabase-auth.d.ts +0 -7
- package/dist/admin/supabase-auth.js +0 -30
- package/dist/astro/deploy-deps.d.ts +0 -10
- package/dist/astro/deploy-deps.js +0 -8
- package/dist/astro/image.d.ts +0 -27
- package/dist/astro/image.js +0 -29
- package/dist/bind.d.ts +0 -19
- package/dist/bind.js +0 -34
- package/dist/cli/add-admin.d.ts +0 -11
- package/dist/cli/add-admin.js +0 -25
- package/dist/cli/load-collections.d.ts +0 -2
- package/dist/cli/load-collections.js +0 -27
- package/dist/cli/migrate.d.ts +0 -43
- package/dist/cli/migrate.js +0 -138
- package/dist/cli/rebuild.d.ts +0 -23
- package/dist/cli/rebuild.js +0 -22
- package/dist/cli/space-io.d.ts +0 -41
- package/dist/cli/space-io.js +0 -84
- package/dist/client.d.ts +0 -11
- package/dist/client.js +0 -54
- package/dist/current-space.d.ts +0 -4
- package/dist/current-space.js +0 -9
- package/dist/document-stamp.d.ts +0 -7
- package/dist/document-stamp.js +0 -48
- package/dist/image/loaders.d.ts +0 -15
- package/dist/image/loaders.js +0 -5
- package/dist/image/next-config.d.ts +0 -18
- package/dist/image/next-config.js +0 -32
- package/dist/image/remote-pattern.d.ts +0 -1
- package/dist/image/remote-pattern.js +0 -5
- package/dist/migrations.d.ts +0 -37
- package/dist/migrations.js +0 -73
- package/dist/project.d.ts +0 -88
- package/dist/project.js +0 -231
- package/dist/published.d.ts +0 -142
- package/dist/published.js +0 -164
- package/dist/ref-index.d.ts +0 -19
- package/dist/ref-index.js +0 -0
- package/dist/schema-read.d.ts +0 -23
- package/dist/schema-read.js +0 -50
- package/dist/schema-version.d.ts +0 -19
- package/dist/schema-version.js +0 -34
- package/dist/sql-space.d.ts +0 -44
- package/dist/sql-space.js +0 -289
- package/dist/sql.d.ts +0 -15
- package/dist/sql.js +0 -556
- package/dist/supabase-asset-store.d.ts +0 -21
- package/dist/supabase-asset-store.js +0 -170
- package/dist/supabase-entry-store.d.ts +0 -92
- package/dist/supabase-entry-store.js +0 -824
- package/dist/supabase-member-store.d.ts +0 -21
- package/dist/supabase-member-store.js +0 -96
- package/dist/supabase-path-index.d.ts +0 -30
- package/dist/supabase-path-index.js +0 -138
- package/dist/supabase-published-store.d.ts +0 -56
- package/dist/supabase-published-store.js +0 -314
- package/dist/supabase-store.d.ts +0 -103
- package/dist/supabase-store.js +0 -678
- package/migrations/20260926120000_smoodly_v1.sql +0 -535
- package/migrations/20260926130000_smoodly_v2.sql +0 -26
- package/migrations/20260926140000_smoodly_v3.sql +0 -38
- package/migrations/20260926150000_smoodly_v4.sql +0 -41
- package/migrations/20260929120000_smoodly_v5.sql +0 -56
- package/migrations/20260929135523_smoodly_v6.sql +0 -200
- package/migrations/20260930040956_smoodly_v7.sql +0 -84
- package/migrations/20261007120000_smoodly_v8.sql +0 -154
- package/migrations/20261007200000_smoodly_v9.sql +0 -30
- package/migrations/20261008120000_smoodly_v10.sql +0 -47
- package/migrations/20261009120000_smoodly_v11.sql +0 -49
- /package/dist/{admin/auth-config.js → deploy-store.js} +0 -0
package/dist/resolve.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// chain gets a shallow copy (refs as ids), so traversal terminates by
|
|
8
8
|
// construction and the output stays JSON-serializable.
|
|
9
9
|
import { globalLink, GLOBAL_NODE_TYPE, SETTINGS_KEY, STYLES_KEY, CSS_KEY } from "./global.js";
|
|
10
|
-
import { applyAssets, collectAssetIds } from "./media.js";
|
|
10
|
+
import { applyAssets, collectAssetIds, signCuts } from "./media.js";
|
|
11
11
|
import { elementSchemas } from "./richtext-walk.js";
|
|
12
12
|
import { SEO_KEY } from "./seo.js";
|
|
13
13
|
import { CUSTOM_NODE_TYPE, customSchema, customToolset } from "./custom/index.js";
|
|
@@ -62,7 +62,7 @@ export async function resolveTree(tree, options) {
|
|
|
62
62
|
sets.push({ values: resolved.fields, descriptors: options.page.fields });
|
|
63
63
|
if (resolved.meta)
|
|
64
64
|
sets.push({ values: resolved.meta, descriptors: META_DESCRIPTORS });
|
|
65
|
-
await resolveMediaSets([...sets, ...hydrated], options.assets, elementSchemas(options.elements));
|
|
65
|
+
await resolveMediaSets([...sets, ...hydrated], options.assets, elementSchemas(options.elements), options.signer, options.images);
|
|
66
66
|
}
|
|
67
67
|
return resolved;
|
|
68
68
|
}
|
|
@@ -83,19 +83,22 @@ async function fetchAssets(fetch, ids) {
|
|
|
83
83
|
return out;
|
|
84
84
|
}
|
|
85
85
|
/** One value set's media, resolved in place. Exported for the site layer's global-item settings. */
|
|
86
|
-
export async function resolveMedia(values, descriptors, fetch, elements = {}) {
|
|
87
|
-
await resolveMediaSets([{ values, descriptors }], fetch, elements);
|
|
86
|
+
export async function resolveMedia(values, descriptors, fetch, elements = {}, signer, defaults) {
|
|
87
|
+
await resolveMediaSets([{ values, descriptors }], fetch, elements, signer, defaults);
|
|
88
88
|
}
|
|
89
89
|
/** Many value sets, one asset round (plus one for posters): the tree's
|
|
90
90
|
* nodes and every entry hydrated through a ref, or a list page's items.
|
|
91
91
|
* Exported for the site layer's content API. */
|
|
92
|
-
|
|
92
|
+
async function resolveMediaSets(sets, fetch, elements = {}, signer, defaults) {
|
|
93
93
|
const ids = new Set();
|
|
94
94
|
for (const { values, descriptors } of sets)
|
|
95
95
|
collectAssetIds(values, descriptors, ids, elements);
|
|
96
96
|
const assets = await fetchAssets(fetch, ids);
|
|
97
|
+
const cuts = signer ? [] : undefined;
|
|
97
98
|
for (const { values, descriptors } of sets)
|
|
98
|
-
applyAssets(values, descriptors, assets, elements);
|
|
99
|
+
applyAssets(values, descriptors, assets, elements, cuts, defaults);
|
|
100
|
+
if (signer && cuts)
|
|
101
|
+
await signCuts(cuts, signer);
|
|
99
102
|
}
|
|
100
103
|
/** Placements first (spec 2026-09-07 §4): fetch every linked item in one
|
|
101
104
|
* call per item, rewrite each node into the item's section node — the
|
|
@@ -148,7 +151,7 @@ export async function resolveFields(fields, descriptors, options) {
|
|
|
148
151
|
collectTasks(resolved, descriptors, tasks);
|
|
149
152
|
const hydrated = await runResolution(tasks, { ...options, maxDepth: options.maxDepth ?? Infinity });
|
|
150
153
|
if (options.assets)
|
|
151
|
-
await resolveMediaSets([{ values: resolved, descriptors }, ...hydrated], options.assets, elementSchemas(options.elements));
|
|
154
|
+
await resolveMediaSets([{ values: resolved, descriptors }, ...hydrated], options.assets, elementSchemas(options.elements), options.signer, options.images);
|
|
152
155
|
return resolved;
|
|
153
156
|
}
|
|
154
157
|
/** Many value sets (a list's entries), one resolution: every ref batched per
|
|
@@ -159,7 +162,7 @@ export async function resolveFieldSets(sets, options) {
|
|
|
159
162
|
collectTasks(values, descriptors, tasks);
|
|
160
163
|
const hydrated = await runResolution(tasks, { ...options, maxDepth: options.maxDepth ?? Infinity });
|
|
161
164
|
if (options.assets)
|
|
162
|
-
await resolveMediaSets([...sets, ...hydrated], options.assets, elementSchemas(options.elements));
|
|
165
|
+
await resolveMediaSets([...sets, ...hydrated], options.assets, elementSchemas(options.elements), options.signer, options.images);
|
|
163
166
|
}
|
|
164
167
|
/** Hydrates every task's refs in place and returns each hydrated entry
|
|
165
168
|
* copy with its collection's descriptors — the media pass walks those
|
package/dist/revalidate.d.ts
CHANGED
|
@@ -21,8 +21,15 @@ export declare const collectionTag: (name: string) => string;
|
|
|
21
21
|
/** Every address that may begin or stop resolving: a miss, a redirect and a code-owned page cache
|
|
22
22
|
* under it on Next (reads.ts `missUnit`), since Next caches a 404 like a hit (plan 2026-10-09 next
|
|
23
23
|
* cached routes, decision 4). Fired by every page op that fires the tree tag, every entry write and
|
|
24
|
-
* every redirect change (
|
|
24
|
+
* every redirect change (the service's ops/ops-impl.ts). */
|
|
25
25
|
export declare const addressesTag: () => string;
|
|
26
|
+
/** Every published response of a space carries it (spec 2026-10-16 the control plane §4): a push that changed the
|
|
27
|
+
* registry purges this one tag, and the new schema applies on the next read. */
|
|
28
|
+
export declare const spaceTag: (space: string) => string;
|
|
29
|
+
/** An asset's original and every cut of it at the edge (spec 2026-10-18 media on R2 and Images §5): its delete purges it. */
|
|
30
|
+
export declare const mediaTag: (asset: string) => string;
|
|
31
|
+
/** Every media file of a space at the edge: the space's delete purges it. Never a content tag, so a publish purges no bytes. */
|
|
32
|
+
export declare const mediaSpaceTag: (space: string) => string;
|
|
26
33
|
export declare function affectedTargets(stores: {
|
|
27
34
|
pages: PageStore;
|
|
28
35
|
entries: EntryStore;
|
package/dist/revalidate.js
CHANGED
|
@@ -20,8 +20,15 @@ export const collectionTag = (name) => `entries:${name}`;
|
|
|
20
20
|
/** Every address that may begin or stop resolving: a miss, a redirect and a code-owned page cache
|
|
21
21
|
* under it on Next (reads.ts `missUnit`), since Next caches a 404 like a hit (plan 2026-10-09 next
|
|
22
22
|
* cached routes, decision 4). Fired by every page op that fires the tree tag, every entry write and
|
|
23
|
-
* every redirect change (
|
|
23
|
+
* every redirect change (the service's ops/ops-impl.ts). */
|
|
24
24
|
export const addressesTag = () => "addresses";
|
|
25
|
+
/** Every published response of a space carries it (spec 2026-10-16 the control plane §4): a push that changed the
|
|
26
|
+
* registry purges this one tag, and the new schema applies on the next read. */
|
|
27
|
+
export const spaceTag = (space) => `space:${space}`;
|
|
28
|
+
/** An asset's original and every cut of it at the edge (spec 2026-10-18 media on R2 and Images §5): its delete purges it. */
|
|
29
|
+
export const mediaTag = (asset) => `media:${asset}`;
|
|
30
|
+
/** Every media file of a space at the edge: the space's delete purges it. Never a content tag, so a publish purges no bytes. */
|
|
31
|
+
export const mediaSpaceTag = (space) => `media-space:${space}`;
|
|
25
32
|
export async function affectedTargets(stores, entryId, options = {}) {
|
|
26
33
|
const maxDepth = options.maxDepth ?? Infinity;
|
|
27
34
|
const seen = new Set([entryId]);
|
package/dist/richtext/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { type RichTextElement } from "../richtext-render.tsx";
|
|
|
3
3
|
export type CreateRichTextOptions = {
|
|
4
4
|
/** The registered Elements a body may embed. */
|
|
5
5
|
elements?: RichTextElement[];
|
|
6
|
-
/**
|
|
6
|
+
/** `sizes` for body images; the site's text column width. */
|
|
7
7
|
sizes?: string;
|
|
8
8
|
};
|
|
9
9
|
/** The bound renderer; `elements` is the list it renders, which defineViews checks against the config's richtext elements. */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { CSSProperties, ReactNode } from "react";
|
|
2
2
|
import { type StyleProps } from "./styles.ts";
|
|
3
3
|
import { SMD_KEY, type SmdInternal } from "./smd-key.ts";
|
|
4
|
-
export {
|
|
4
|
+
export { type SmdInternal };
|
|
5
5
|
export declare function Section({ as, styles, className, style, children, ...rest }: {
|
|
6
6
|
as?: keyof React.JSX.IntrinsicElements;
|
|
7
7
|
styles: StyleProps & {
|
package/dist/section-wrapper.js
CHANGED
|
@@ -3,15 +3,6 @@ import { NODE_ATTR } from "./marks.js";
|
|
|
3
3
|
import { DEFAULT_STYLE_VALUES, inlineStyles } from "./styles.js";
|
|
4
4
|
import { safeCss, scopedCss } from "./css.js";
|
|
5
5
|
import { SMD_KEY } from "./smd-key.js";
|
|
6
|
-
// The root wrapper every section renders through (spec 2026-09-14 §2, §4).
|
|
7
|
-
// Owns the real root element, no extra div: element choice via `as`,
|
|
8
|
-
// Smoodly's styles set rendered INLINE (an editor's step beats the
|
|
9
|
-
// section's own class rule with no ordering and no !important), the
|
|
10
|
-
// node id as data-smd-node always (the custom-CSS scope needs it on the
|
|
11
|
-
// live site), the scoped <style> child, and every other prop spread onto
|
|
12
|
-
// the root — settings never become attributes; the view writes its own.
|
|
13
|
-
// The reserved key lives in smd-key.ts (pure), re-exported here.
|
|
14
|
-
export { SMD_KEY };
|
|
15
6
|
export function Section({ as = "section", styles, className, style, children, ...rest }) {
|
|
16
7
|
const { [SMD_KEY]: smd, ...steps } = (styles ?? {});
|
|
17
8
|
const inline = inlineStyles(steps, smd?.values ?? DEFAULT_STYLE_VALUES);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export declare const MESSAGE_MAX_AGE_MS = 60000;
|
|
2
|
+
/** The service's private key, with the id the site finds its public half by. */
|
|
3
|
+
export type SigningKey = {
|
|
4
|
+
kid: string;
|
|
5
|
+
privateKey: JsonWebKey;
|
|
6
|
+
};
|
|
7
|
+
export type PublicKey = {
|
|
8
|
+
kid: string;
|
|
9
|
+
publicKey: JsonWebKey;
|
|
10
|
+
};
|
|
11
|
+
export type Signed = {
|
|
12
|
+
ts: number;
|
|
13
|
+
kid: string;
|
|
14
|
+
sig: string;
|
|
15
|
+
};
|
|
16
|
+
/** The public keys a site trusts; `refresh` asks for the published set again (an unknown key id after a rotation). */
|
|
17
|
+
export type KeySet = (refresh?: boolean) => Promise<PublicKey[]>;
|
|
18
|
+
/** A new keypair. The key id defaults to 8 hex characters of the public key's SHA-256. */
|
|
19
|
+
export declare function generateSigningKey(kid?: string): Promise<SigningKey & PublicKey>;
|
|
20
|
+
/** The public half of a signing key: the private JWK carries it as `x`. Never the `d` member. */
|
|
21
|
+
export declare function publicOf(key: SigningKey): PublicKey;
|
|
22
|
+
export declare function signMessage(key: SigningKey, purpose: string, space: string, body: string, now?: number): Promise<Signed>;
|
|
23
|
+
/** True when a key of the set signed this purpose, space and body within the window. An unknown key id asks the set
|
|
24
|
+
* once more (a rotation published a new key); a bad shape is false, never a throw. */
|
|
25
|
+
export declare function verifyMessage(keys: KeySet, purpose: string, space: string, body: string, signed: Signed, now?: number, maxAgeMs?: number): Promise<boolean>;
|
|
26
|
+
export declare function fetchedKeySet(apiUrl: string, doFetch?: typeof fetch, now?: () => number): KeySet;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// A message the service signs for the site (spec 2026-10-16 the control plane §2): the service telling the site which
|
|
2
|
+
// tags to expire, the service granting the browser a draft cookie. The service signs with its own Ed25519 key; the
|
|
3
|
+
// site verifies with the public keys it fetches once from Smoodly's API, so no secret is shared between them. The
|
|
4
|
+
// message names its purpose, the key, the time and the space: the window keeps a captured message from being
|
|
5
|
+
// replayed later, the purpose keeps one kind from being replayed as another, and the space keeps one site's message
|
|
6
|
+
// from being accepted by another. The draft cookie is not one of these: the site makes and reads it alone, an HMAC
|
|
7
|
+
// under its own secret key. WebCrypto, so it runs in a Worker, in Node and in a Next route handler alike.
|
|
8
|
+
export const MESSAGE_MAX_AGE_MS = 60000;
|
|
9
|
+
const ED25519 = { name: "Ed25519" };
|
|
10
|
+
const encoder = new TextEncoder();
|
|
11
|
+
const base64url = (bytes) => btoa(String.fromCharCode(...bytes)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
12
|
+
function fromBase64url(text) {
|
|
13
|
+
if (!/^[A-Za-z0-9_-]*$/.test(text))
|
|
14
|
+
return null;
|
|
15
|
+
try {
|
|
16
|
+
const raw = atob(text.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - (text.length % 4)) % 4));
|
|
17
|
+
return Uint8Array.from(raw, (c) => c.charCodeAt(0));
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/** The bare OKP members of a key. Runtimes disagree on `alg` (newer Node writes "Ed25519", workerd refuses it and
|
|
24
|
+
* wants "EdDSA" or none) and on `key_ops`/`ext`, so a key is written, published and imported with none of them. */
|
|
25
|
+
const bare = (jwk) => ({ kty: "OKP", crv: "Ed25519", x: jwk.x, ...(jwk.d ? { d: jwk.d } : {}) });
|
|
26
|
+
const message = (purpose, kid, ts, space, body) => `${purpose}\n${kid}\n${ts}\n${space}\n${body}`;
|
|
27
|
+
/** A new keypair. The key id defaults to 8 hex characters of the public key's SHA-256. */
|
|
28
|
+
export async function generateSigningKey(kid) {
|
|
29
|
+
const pair = (await crypto.subtle.generateKey(ED25519, true, ["sign", "verify"]));
|
|
30
|
+
const privateKey = (await crypto.subtle.exportKey("jwk", pair.privateKey));
|
|
31
|
+
const publicKey = (await crypto.subtle.exportKey("jwk", pair.publicKey));
|
|
32
|
+
const id = kid ?? Array.from(new Uint8Array(await crypto.subtle.digest("SHA-256", encoder.encode(publicKey.x))).slice(0, 4), (b) => b.toString(16).padStart(2, "0")).join("");
|
|
33
|
+
return { kid: id, privateKey: bare(privateKey), publicKey: bare(publicKey) };
|
|
34
|
+
}
|
|
35
|
+
/** The public half of a signing key: the private JWK carries it as `x`. Never the `d` member. */
|
|
36
|
+
export function publicOf(key) {
|
|
37
|
+
return { kid: key.kid, publicKey: bare({ x: key.privateKey.x }) };
|
|
38
|
+
}
|
|
39
|
+
export async function signMessage(key, purpose, space, body, now = Date.now()) {
|
|
40
|
+
if (!key?.privateKey?.d)
|
|
41
|
+
throw new Error("smoodly: signing needs SMOODLY_SIGNING_KEY.");
|
|
42
|
+
const privateKey = await crypto.subtle.importKey("jwk", bare(key.privateKey), ED25519, false, ["sign"]);
|
|
43
|
+
const sig = new Uint8Array(await crypto.subtle.sign(ED25519, privateKey, encoder.encode(message(purpose, key.kid, now, space, body))));
|
|
44
|
+
return { ts: now, kid: key.kid, sig: base64url(sig) };
|
|
45
|
+
}
|
|
46
|
+
/** True when a key of the set signed this purpose, space and body within the window. An unknown key id asks the set
|
|
47
|
+
* once more (a rotation published a new key); a bad shape is false, never a throw. */
|
|
48
|
+
export async function verifyMessage(keys, purpose, space, body, signed, now = Date.now(), maxAgeMs = MESSAGE_MAX_AGE_MS) {
|
|
49
|
+
const { ts, kid, sig } = signed ?? {};
|
|
50
|
+
if (!Number.isFinite(ts) || typeof kid !== "string" || !kid || typeof sig !== "string")
|
|
51
|
+
return false;
|
|
52
|
+
if (Math.abs(now - ts) > maxAgeMs)
|
|
53
|
+
return false;
|
|
54
|
+
const bytes = fromBase64url(sig);
|
|
55
|
+
if (!bytes || bytes.length !== 64)
|
|
56
|
+
return false;
|
|
57
|
+
const found = (await keys()).find((k) => k.kid === kid) ?? (await keys(true)).find((k) => k.kid === kid);
|
|
58
|
+
if (!found)
|
|
59
|
+
return false;
|
|
60
|
+
try {
|
|
61
|
+
const publicKey = await crypto.subtle.importKey("jwk", bare(found.publicKey), ED25519, false, ["verify"]);
|
|
62
|
+
return await crypto.subtle.verify(ED25519, publicKey, bytes, encoder.encode(message(purpose, kid, ts, space, body)));
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/** The service's published keys (`GET /v1/signing-keys`), fetched on the first verify and kept for the process. A
|
|
69
|
+
* refresh (an unknown key id) fetches again at most once a minute, and concurrent asks share one fetch: the key id
|
|
70
|
+
* comes from an unauthenticated caller, who must not be able to make the site call the API per request. A failed
|
|
71
|
+
* fetch keeps what it had. */
|
|
72
|
+
const KEY_REFRESH_MS = 60000;
|
|
73
|
+
export function fetchedKeySet(apiUrl, doFetch = fetch, now = Date.now) {
|
|
74
|
+
let held = null;
|
|
75
|
+
let inflight = null;
|
|
76
|
+
let refreshedAt = -Infinity;
|
|
77
|
+
const load = () => (inflight ?? (inflight = (async () => {
|
|
78
|
+
try {
|
|
79
|
+
const res = await doFetch(`${apiUrl.replace(/\/+$/, "")}/v1/signing-keys`);
|
|
80
|
+
const body = (await res.json());
|
|
81
|
+
if (res.ok && Array.isArray(body.keys))
|
|
82
|
+
held = body.keys;
|
|
83
|
+
}
|
|
84
|
+
catch (e) {
|
|
85
|
+
console.error(`smoodly: fetching the service's signing keys from ${apiUrl} failed —`, e instanceof Error ? e.message : e);
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
inflight = null;
|
|
89
|
+
}
|
|
90
|
+
// A first fetch that failed still counts as loaded: the next one waits for a refresh's minute.
|
|
91
|
+
return (held ?? (held = []));
|
|
92
|
+
})()));
|
|
93
|
+
return async (refresh = false) => {
|
|
94
|
+
if (inflight)
|
|
95
|
+
return inflight;
|
|
96
|
+
if (!held)
|
|
97
|
+
return load();
|
|
98
|
+
if (!refresh || now() - refreshedAt < KEY_REFRESH_MS)
|
|
99
|
+
return held;
|
|
100
|
+
refreshedAt = now();
|
|
101
|
+
return load();
|
|
102
|
+
};
|
|
103
|
+
}
|
package/dist/site.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { AssetStore } from "./asset-store.ts";
|
|
2
|
+
import type { CutSigner } from "./media-cut.ts";
|
|
3
|
+
import type { ImageDefaults } from "./media.ts";
|
|
2
4
|
import type { CollectionSchema, EntryOrder } from "./collections.ts";
|
|
3
5
|
import type { Descriptor } from "./fields.ts";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
+
import type { EntryRecord, EntryStore, Where } from "./entry-store.ts";
|
|
7
|
+
import type { LocaleAlternate, LocaleSet, PageAlternate, PathTarget } from "./paths.ts";
|
|
6
8
|
import type { SectionLike } from "./refs.ts";
|
|
7
9
|
import type { PageContext, PageLink, PageTree } from "./page-context.ts";
|
|
8
10
|
import type { GlobalSchema } from "./global.ts";
|
|
@@ -10,8 +12,9 @@ import type { StyleValues, StyleValuesInput } from "./styles.ts";
|
|
|
10
12
|
import type { CustomConfigInput, ResolvedCustom } from "./custom/index.ts";
|
|
11
13
|
import type { PageRecord, PageStore } from "./store.ts";
|
|
12
14
|
import type { PathHit } from "./path-index.ts";
|
|
13
|
-
import {
|
|
15
|
+
import type { EntryItem, PageItem, Seo } from "./read-shapes.ts";
|
|
14
16
|
import type { SchemaOr } from "./registration.ts";
|
|
17
|
+
import type { ResolvedConfig } from "./config.ts";
|
|
15
18
|
/** An address read with its layout's globals: the hit as `lookup` answers it, and each item's value by name. */
|
|
16
19
|
export type AddressWithGlobals = {
|
|
17
20
|
hit: SiteHit | {
|
|
@@ -20,15 +23,11 @@ export type AddressWithGlobals = {
|
|
|
20
23
|
} | null;
|
|
21
24
|
globals: Map<string, unknown>;
|
|
22
25
|
};
|
|
23
|
-
import type { PublishedStore } from "./published.ts";
|
|
24
|
-
import { type Projector } from "./project.ts";
|
|
25
26
|
export type SiteOptions = {
|
|
26
|
-
/** `published`: the delivery documents outside draft mode read (spec 2026-10-07 delivery model §3). */
|
|
27
27
|
stores: () => {
|
|
28
28
|
pages: PageStore;
|
|
29
29
|
entries: EntryStore;
|
|
30
30
|
assets?: AssetStore;
|
|
31
|
-
published: PublishedStore;
|
|
32
31
|
};
|
|
33
32
|
collections: CollectionSchema<any>[];
|
|
34
33
|
sections: SectionLike[];
|
|
@@ -45,6 +44,10 @@ export type SiteOptions = {
|
|
|
45
44
|
* and of the fixed pages' addresses (spec 2026-09-29 nested paths §3) — `config.registry.pages`. Required: a site that
|
|
46
45
|
* missed one would take its code-owned record for an orphan and drop its URLs at the first lookup. */
|
|
47
46
|
pages: ReadonlyArray<SchemaOr<SitePageSchema>>;
|
|
47
|
+
/** Signs every image's cut where the service cuts (spec 2026-10-18 media on R2 and Images §5); absent, reads carry no cut. */
|
|
48
|
+
signer?: CutSigner;
|
|
49
|
+
/** The project's image quality and srcset cap, `config.media`'s (spec 2026-10-18 image quality and max width §6). */
|
|
50
|
+
images?: ImageDefaults;
|
|
48
51
|
};
|
|
49
52
|
/** A page type as the site needs it: its page fields, and a fixed page's address. */
|
|
50
53
|
export type SitePageSchema = {
|
|
@@ -73,6 +76,9 @@ export type SitePage = {
|
|
|
73
76
|
locales: Record<string, PageAlternate | null>;
|
|
74
77
|
/** The effective SEO values, for a template with `seo: true` (spec 2026-09-29 content API contract §7). */
|
|
75
78
|
seo?: Seo;
|
|
79
|
+
/** The tags of every record this read touched (spec 2026-10-15 the space object §4): its own, every entry its tree
|
|
80
|
+
* resolved at any depth, its ancestors and children. Absent on a draft read. */
|
|
81
|
+
tags?: string[];
|
|
76
82
|
};
|
|
77
83
|
export type SiteEntry = {
|
|
78
84
|
kind: "entry";
|
|
@@ -87,6 +93,8 @@ export type SiteEntry = {
|
|
|
87
93
|
locales: Record<string, LocaleAlternate | null>;
|
|
88
94
|
/** The effective SEO values, for a collection with `seo` (§7). */
|
|
89
95
|
seo?: Seo;
|
|
96
|
+
/** The tags of every record this read touched: its own and every entry it resolved. Absent on a draft read. */
|
|
97
|
+
tags?: string[];
|
|
90
98
|
};
|
|
91
99
|
export type SiteHit = SitePage | SiteEntry;
|
|
92
100
|
export type PageTreeItem = {
|
|
@@ -116,14 +124,14 @@ export type LoadOptions = {
|
|
|
116
124
|
draft?: boolean;
|
|
117
125
|
version?: string;
|
|
118
126
|
};
|
|
119
|
-
|
|
127
|
+
/** The read face of a site (spec 2026-10-14 §1): what the read layer, the shells and the renderer use. The service's
|
|
128
|
+
* own site (createSmoodlySite, the stores underneath) and the API client (api-client.ts, HTTP) both implement it. */
|
|
129
|
+
export interface ContentReads {
|
|
120
130
|
readonly locales: LocaleSet;
|
|
121
131
|
readonly homePageSlug: string;
|
|
122
132
|
/** The package version and a hash of the registered model: a part of every cache key, so an upgrade or a
|
|
123
133
|
* model change never serves a unit cached in the old shape (spec 2026-09-30 tester findings §3). */
|
|
124
134
|
readonly cacheKey: string;
|
|
125
|
-
/** The document stamp a delivery row carries (DESIGN §3): the format number and the model hash, never the version. */
|
|
126
|
-
readonly stamp: string;
|
|
127
135
|
/** The resolved styles values the renderer and `getGlobal` hand the wrapper. */
|
|
128
136
|
readonly styles: StyleValues;
|
|
129
137
|
/** The custom section's resolved options the renderer hands renderPage. */
|
|
@@ -134,18 +142,15 @@ export interface SmoodlySite {
|
|
|
134
142
|
targetPaths(target: PathTarget): Promise<Record<string, string>>;
|
|
135
143
|
/** Everything the renderer needs for one target: null when it is a folder, unpublished (outside draft), or gone. */
|
|
136
144
|
load(target: PathTarget, locale: string, path: string, options?: LoadOptions): Promise<SiteHit | null>;
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
* the code-owned page, then one locate that tells a redirect row from a record to assemble and from a 404. */
|
|
145
|
+
/** The published read of an address: one locate that tells a record to assemble from a redirect row, and the
|
|
146
|
+
* code-owned page where the index has no row. */
|
|
140
147
|
lookup(locale: string, path: string): Promise<SiteHit | {
|
|
141
148
|
kind: "redirect";
|
|
142
149
|
target: PathTarget;
|
|
143
150
|
} | null>;
|
|
144
|
-
/** The same lookup with the layout's global items riding along
|
|
145
|
-
*
|
|
151
|
+
/** The same lookup with the layout's global items riding along: the page and each published item's value by name.
|
|
152
|
+
* A miss and a redirect bring no globals. */
|
|
146
153
|
lookupWithGlobals(locale: string, path: string, items: readonly GlobalSchema<any>[]): Promise<AddressWithGlobals>;
|
|
147
|
-
/** Writes delivery rows from the editing tables: Publish calls it, and heal on read goes through it. */
|
|
148
|
-
readonly projector: Projector;
|
|
149
154
|
/** A fixed page type (or a collection's index page) as code alone describes it, when `path` is its address in
|
|
150
155
|
* `locale`: an empty tree, the page fields' defaults, the registration's title. The renderer serves it where
|
|
151
156
|
* the index has no row — a page type added after install, a locale added to the config — until the admin
|
|
@@ -176,7 +181,43 @@ export interface SmoodlySite {
|
|
|
176
181
|
draft?: boolean;
|
|
177
182
|
}): Promise<V | null>;
|
|
178
183
|
}
|
|
184
|
+
/** What a static build and the Deploy tile read (spec 2026-10-09 static mode §3, §5), over the API. Paths are
|
|
185
|
+
* locale-relative CMS paths; the adapter applies `href`. */
|
|
186
|
+
export interface DeliveryReads {
|
|
187
|
+
addresses(locale: string): Promise<{
|
|
188
|
+
pages: string[];
|
|
189
|
+
entries: string[];
|
|
190
|
+
redirects: {
|
|
191
|
+
from: string;
|
|
192
|
+
to: string;
|
|
193
|
+
}[];
|
|
194
|
+
}>;
|
|
195
|
+
newestPublishedAt(): Promise<number | null>;
|
|
196
|
+
}
|
|
179
197
|
/** The `page` prop for a loaded page. `href` is the app's seam from a
|
|
180
198
|
* locale-relative path to a site URL; the default keeps CMS paths as URLs. */
|
|
181
199
|
export declare function pageContextOf(hit: SitePage, href?: (path: string, locale: string) => string): PageContext;
|
|
182
|
-
|
|
200
|
+
/** The members of a site that need no store (the API client computes them from the same config, so a cache key or a
|
|
201
|
+
* code page never disagrees across the wire). */
|
|
202
|
+
export declare function siteStatics(options: Omit<SiteOptions, "stores">): {
|
|
203
|
+
shape: {
|
|
204
|
+
locales: LocaleSet;
|
|
205
|
+
pageSchemas: SitePageSchema[];
|
|
206
|
+
homePageSlug: string;
|
|
207
|
+
styles: StyleValues;
|
|
208
|
+
custom: ResolvedCustom;
|
|
209
|
+
elements: import("./richtext-walk.ts").ElementSchemas;
|
|
210
|
+
codeOwned: import("./addresses.ts").CodeAddress[];
|
|
211
|
+
addresses: import("./addresses.ts").Addresses;
|
|
212
|
+
pathOpts: import("./paths.ts").PagePathOptions;
|
|
213
|
+
anchors: import("./anchors.ts").Anchors;
|
|
214
|
+
};
|
|
215
|
+
locales: LocaleSet;
|
|
216
|
+
homePageSlug: string;
|
|
217
|
+
styles: StyleValues;
|
|
218
|
+
custom: ResolvedCustom;
|
|
219
|
+
cacheKey: string;
|
|
220
|
+
codePage: (locale: string, path: string) => SitePage | null;
|
|
221
|
+
};
|
|
222
|
+
/** What createSmoodlySite takes, read off the config. */
|
|
223
|
+
export declare function siteOptions(config: ResolvedConfig, stores?: SiteOptions["stores"]): SiteOptions;
|