smoodly 0.0.51 → 0.0.52
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-config.d.ts +6 -5
- package/dist/admin/auth.d.ts +3 -11
- package/dist/admin/auth.js +1 -30
- package/dist/admin/browser/fetch-op.js +3 -2
- package/dist/admin/browser/main.js +6 -2
- package/dist/admin/client-ops.d.ts +5 -4
- package/dist/admin/client-ops.js +14 -11
- 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 +5 -8
- package/dist/admin/index.js +3 -6
- package/dist/admin/ops.d.ts +36 -22
- package/dist/admin/page-data.d.ts +14 -6
- package/dist/admin/page-data.js +1 -1
- package/dist/admin/session-flow.d.ts +43 -0
- package/dist/admin/session-flow.js +94 -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 +5 -4
- 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 +20 -12
- package/dist/admin/shell/session.js +113 -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 +5 -8
- 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 +4 -302
- package/dist/asset-store.d.ts +28 -30
- package/dist/asset-store.js +26 -121
- 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 +54 -25
- 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 +2 -1
- package/dist/astro/runtime.js +4 -3
- package/dist/astro/server.d.ts +7 -11
- package/dist/astro/server.js +22 -28
- package/dist/astro/shell.d.ts +2 -2
- 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 +22 -23
- package/dist/content/content-api.js +46 -56
- package/dist/content/mime.d.ts +1 -0
- package/dist/content/mime.js +6 -0
- package/dist/deploy-store.d.ts +19 -0
- package/dist/deploy-store.js +1 -0
- package/dist/entry-store.d.ts +3 -89
- package/dist/entry-store.js +2 -550
- 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 +12 -22
- 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 +57 -3
- package/dist/media.js +125 -15
- package/dist/member-store.d.ts +3 -22
- package/dist/member-store.js +1 -53
- 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/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 +5 -2
- package/dist/space-transfer.js +17 -5
- 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/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
|
@@ -1,33 +1,71 @@
|
|
|
1
|
-
|
|
1
|
+
// The browser half of an upload (spec 2026-10-18 media on R2 and Images §4): the browser measures the file, then
|
|
2
|
+
// sends it in one request to the service's admin media route, the bytes as the body and the rest as query
|
|
3
|
+
// parameters; the service streams it into the bucket and answers the ready asset. A video's poster frame is uploaded
|
|
4
|
+
// first as its own image asset and linked. The send and the measure are injected so the flow is testable without a
|
|
5
|
+
// DOM.
|
|
6
|
+
import { ASSET_REFUSED, assertMediaRules, isAssetRefused } from "../../asset-store.js";
|
|
2
7
|
const stem = (name) => name.replace(/\.[^.]+$/, "");
|
|
3
|
-
export async function uploadFile(
|
|
4
|
-
const
|
|
5
|
-
if (!begun.ok)
|
|
6
|
-
return begun;
|
|
7
|
-
try {
|
|
8
|
-
await deps.put(begun.data.uploadUrl, file, file.type);
|
|
9
|
-
}
|
|
10
|
-
catch (e) {
|
|
11
|
-
return { ok: false, code: "internal", message: `smoodly: upload failed — ${e instanceof Error ? e.message : String(e)}` };
|
|
12
|
-
}
|
|
13
|
-
const m = await deps.measure(file, file.type);
|
|
8
|
+
export async function uploadFile(send, file, measure, onProgress) {
|
|
9
|
+
const m = await measure(file, file.type);
|
|
14
10
|
let posterId;
|
|
15
11
|
if (m.poster) {
|
|
16
|
-
const poster = await uploadFile(
|
|
12
|
+
const poster = await uploadFile(send, new File([m.poster], `${stem(file.name)}-poster.jpg`, { type: "image/jpeg" }), measure);
|
|
17
13
|
if (poster.ok)
|
|
18
14
|
posterId = poster.data.id;
|
|
19
15
|
}
|
|
20
|
-
|
|
16
|
+
const params = { name: file.name, mime: file.type };
|
|
17
|
+
for (const [key, value] of Object.entries({ width: m.width, height: m.height, duration: m.duration, posterId })) {
|
|
18
|
+
if (value !== undefined)
|
|
19
|
+
params[key] = String(value);
|
|
20
|
+
}
|
|
21
|
+
return send(file, params, onProgress);
|
|
22
|
+
}
|
|
23
|
+
export const uploadQuery = (params) => new URLSearchParams(params).toString();
|
|
24
|
+
/** A send that first applies the space's media rules (its registry's `media`) with the store's own words, so a file
|
|
25
|
+
* the space refuses is never sent; the service checks again, and its word is final. */
|
|
26
|
+
export function checkedSend(send, rules) {
|
|
27
|
+
return async (file, params, onProgress) => {
|
|
28
|
+
try {
|
|
29
|
+
assertMediaRules(rules, { filename: params.name, mime: params.mime, size: file.size });
|
|
30
|
+
}
|
|
31
|
+
catch (e) {
|
|
32
|
+
if (isAssetRefused(e))
|
|
33
|
+
return { ok: false, code: "validation", message: e.message.slice(ASSET_REFUSED.length).trim() };
|
|
34
|
+
throw e;
|
|
35
|
+
}
|
|
36
|
+
return send(file, params, onProgress);
|
|
37
|
+
};
|
|
21
38
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
*
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
39
|
+
const failed = (message) => ({ ok: false, code: "internal", message });
|
|
40
|
+
/** The admin's send: an XMLHttpRequest PUT (fetch reports no upload progress) to the admin media route with the
|
|
41
|
+
* editor's pass. A pass refused as expired renews once and the file is sent again, as an operation is
|
|
42
|
+
* (client-ops.ts). */
|
|
43
|
+
export function xhrSend(url, token, onExpired) {
|
|
44
|
+
const once = (file, params, onProgress) => new Promise((resolve) => {
|
|
45
|
+
const xhr = new XMLHttpRequest();
|
|
46
|
+
xhr.open("PUT", `${url}?${uploadQuery(params)}`);
|
|
47
|
+
const t = token();
|
|
48
|
+
if (t)
|
|
49
|
+
xhr.setRequestHeader("authorization", `Bearer ${t}`);
|
|
50
|
+
xhr.setRequestHeader("content-type", params.mime || "application/octet-stream");
|
|
51
|
+
xhr.upload.onprogress = (e) => { if (e.lengthComputable && e.total > 0)
|
|
52
|
+
onProgress?.(e.loaded / e.total); };
|
|
53
|
+
xhr.onload = () => {
|
|
54
|
+
try {
|
|
55
|
+
const body = JSON.parse(xhr.responseText);
|
|
56
|
+
if (typeof body === "object" && body !== null && typeof body.ok === "boolean")
|
|
57
|
+
return resolve(body);
|
|
58
|
+
}
|
|
59
|
+
catch { /* answered below */ }
|
|
60
|
+
resolve(failed(`smoodly: the upload answered ${xhr.status} without a result — see the service's log.`));
|
|
61
|
+
};
|
|
62
|
+
xhr.onerror = () => resolve(failed("smoodly: the upload did not reach the service — check the connection and try again."));
|
|
63
|
+
xhr.send(file);
|
|
30
64
|
});
|
|
31
|
-
|
|
32
|
-
|
|
65
|
+
return async (file, params, onProgress) => {
|
|
66
|
+
const r = await once(file, params, onProgress);
|
|
67
|
+
if (r.ok || !r.expired)
|
|
68
|
+
return r;
|
|
69
|
+
return (await onExpired?.()) === true ? once(file, params, onProgress) : r;
|
|
70
|
+
};
|
|
33
71
|
}
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import type { ResolvedConfig } from "../config.ts";
|
|
2
1
|
import type { Descriptor } from "../fields.ts";
|
|
3
|
-
import type { EntryRecord, EntryStore } from "../entry-store.ts";
|
|
4
2
|
import { type GlobalSchema } from "../global.ts";
|
|
5
3
|
/** The merged fields a fresh item starts from: sampled field values, and
|
|
6
4
|
* for a visual item `$settings` = the section's settings defaults with the
|
|
7
5
|
* sample's settings values on top. Unsampled fields stay absent. */
|
|
8
6
|
export declare function initialGlobalFields(item: GlobalSchema, settingsDescriptors: Record<string, Descriptor>): Record<string, unknown>;
|
|
9
|
-
export declare function ensureGlobalItems(entries: EntryStore, config: ResolvedConfig): Promise<Record<string, EntryRecord>>;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { SETTINGS_KEY } from "../global.js";
|
|
2
|
-
import { sectionSettings } from "./serialize.js";
|
|
3
2
|
/** The merged fields a fresh item starts from: sampled field values, and
|
|
4
3
|
* for a visual item `$settings` = the section's settings defaults with the
|
|
5
4
|
* sample's settings values on top. Unsampled fields stay absent. */
|
|
@@ -21,56 +20,3 @@ export function initialGlobalFields(item, settingsDescriptors) {
|
|
|
21
20
|
}
|
|
22
21
|
return out;
|
|
23
22
|
}
|
|
24
|
-
export async function ensureGlobalItems(entries, config) {
|
|
25
|
-
const { default: def, supported } = config.locales;
|
|
26
|
-
const out = {};
|
|
27
|
-
for (const item of config.registry.globals ?? []) {
|
|
28
|
-
let [record] = await entries.list(item.name);
|
|
29
|
-
if (!record) {
|
|
30
|
-
try {
|
|
31
|
-
const settings = item.section !== undefined ? sectionSettings(config, item.section) : {};
|
|
32
|
-
record = await entries.create(item.name, initialGlobalFields(item, settings), { locale: def });
|
|
33
|
-
}
|
|
34
|
-
catch (e) {
|
|
35
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
36
|
-
// Another admin load won the create: read theirs. Anything else is
|
|
37
|
-
// worth saying out loud, and must not take the other items down.
|
|
38
|
-
if (message.includes("it is a singleton"))
|
|
39
|
-
[record] = await entries.list(item.name);
|
|
40
|
-
else
|
|
41
|
-
console.warn(`smoodly: could not materialize global item "${item.name}": ${message}`);
|
|
42
|
-
if (!record)
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
for (const locale of supported) {
|
|
47
|
-
if (record.locales[locale])
|
|
48
|
-
continue;
|
|
49
|
-
try {
|
|
50
|
-
record = await entries.addLocale(item.name, record.id, locale, { from: def });
|
|
51
|
-
}
|
|
52
|
-
catch (e) {
|
|
53
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
54
|
-
if (!message.includes("already exists in locale")) {
|
|
55
|
-
console.warn(`smoodly: could not materialize global item "${item.name}" in "${locale}": ${message}`);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
// Every global item is versioned (decided 2026-09-13). A row published before that has
|
|
60
|
-
// no pointers, so its next save would go live: publish it once more here, which snapshots
|
|
61
|
-
// what is live and points both pointers at it. Idempotent — pointed rows are skipped.
|
|
62
|
-
for (const locale of supported) {
|
|
63
|
-
const row = record.locales[locale];
|
|
64
|
-
if (!row || row.status !== "published" || row.publishedVersionId !== null)
|
|
65
|
-
continue;
|
|
66
|
-
try {
|
|
67
|
-
record = await entries.setStatus(item.name, record.id, locale, "published");
|
|
68
|
-
}
|
|
69
|
-
catch (e) {
|
|
70
|
-
console.warn(`smoodly: could not version global item "${item.name}" in "${locale}": ${e instanceof Error ? e.message : String(e)}`);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
out[item.name] = record;
|
|
74
|
-
}
|
|
75
|
-
return out;
|
|
76
|
-
}
|
package/dist/admin/handler.d.ts
CHANGED
|
@@ -1,44 +1,38 @@
|
|
|
1
|
-
import type { AssetStore } from "../asset-store.ts";
|
|
2
1
|
import type { ResolvedConfig } from "../config.ts";
|
|
3
|
-
import type { EntryStore } from "../entry-store.ts";
|
|
4
2
|
import type { PresenceConfig } from "../env.ts";
|
|
5
|
-
import type
|
|
6
|
-
import type { PageStore } from "../store.ts";
|
|
7
|
-
import type { Projector } from "../project.ts";
|
|
8
|
-
import { type AdminAuth } from "./auth.ts";
|
|
3
|
+
import { type KeySet } from "../signed-message.ts";
|
|
9
4
|
import { type AdminFiles } from "./files.ts";
|
|
10
|
-
import {
|
|
11
|
-
import type {
|
|
5
|
+
import type { AdminEffects } from "./ops.ts";
|
|
6
|
+
import type { SignInConfig } from "./auth-config.ts";
|
|
12
7
|
export type AdminHandlerDeps = {
|
|
13
8
|
config: ResolvedConfig;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
/**
|
|
9
|
+
/** Where the browser sends a person to sign in, and the dashboard it links to: public values only. Absent, the page
|
|
10
|
+
* asks Smoodly's API (`GET /v1/auth`, spec 2026-10-16 sign-in and the dashboard §4) once per process and API
|
|
11
|
+
* address. */
|
|
12
|
+
signIn?: SignInConfig;
|
|
13
|
+
/** Test seam for the sign-in fetch; default the global fetch. */
|
|
14
|
+
fetch?: typeof fetch;
|
|
15
|
+
/** Smoodly's API: the admin posts its operations to `${apiUrl}/v1/spaces/${spaceId}/admin/op`. */
|
|
16
|
+
apiUrl: string;
|
|
17
|
+
/** The site's space (SMOODLY_SPACE_ID): the service's admin routes live under it. */
|
|
18
|
+
spaceId: string;
|
|
19
|
+
/** The service's public signing keys; default the set published at `${apiUrl}/v1/signing-keys`, fetched once per
|
|
20
|
+
* process. The revalidate call and the preview grant are verified against it. */
|
|
21
|
+
keys?: KeySet;
|
|
22
|
+
/** Where the browser opens the editing-presence socket; absent or failed, presence is off. */
|
|
28
23
|
presence?: () => PresenceConfig | Promise<PresenceConfig>;
|
|
29
24
|
effects?: AdminEffects;
|
|
30
25
|
/** Where drafts are served, when not at the page's own address (page-data.ts). */
|
|
31
26
|
previewBase?: string;
|
|
32
|
-
/**
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
27
|
+
/** How the canvas marks its draft render (page-data.ts); the query by default, the path on Next. */
|
|
28
|
+
canvasMarks?: "path" | "query";
|
|
29
|
+
/** Static mode (spec 2026-10-09) shows the Deploy tile; absent = live. */
|
|
30
|
+
delivery?: "live" | "static";
|
|
36
31
|
/** The prebuilt files; default: the ones the package's build wrote. Tests pass their own. */
|
|
37
32
|
files?: AdminFiles;
|
|
38
33
|
};
|
|
39
34
|
/** The request came from a page of this site: its Origin names the host the browser asked for. Behind a
|
|
40
35
|
* proxy that host is X-Forwarded-Host (the first, when a chain appended more), as in Next's own check for
|
|
41
|
-
* server actions. No Origin, no
|
|
36
|
+
* server actions. No Origin, no preview: every browser sends one with a POST. */
|
|
42
37
|
export declare function sameOrigin(request: Request): boolean;
|
|
43
|
-
/** `deps` is read on every request, so nothing is read at import and a build with no keys succeeds. */
|
|
44
38
|
export declare function createAdminHandler(deps: () => AdminHandlerDeps): (request: Request) => Promise<Response>;
|
package/dist/admin/handler.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
// packages/smoodly/src/admin/handler.ts
|
|
2
|
-
// The admin's whole server face (spec 2026-10-
|
|
2
|
+
// The admin's whole server face on the site (spec 2026-10-14 §1), framework-free: a web Request in, a
|
|
3
3
|
// Response out. GET under the admin path is the HTML page, GET under the reserved segment is one of the
|
|
4
|
-
// package's files, POST to `<admin path>/_smoodly/
|
|
5
|
-
//
|
|
4
|
+
// package's files, POST to `<admin path>/_smoodly/revalidate` is the service expiring tags after a publish
|
|
5
|
+
// (signed with the service's own key, spec 2026-10-16 the control plane §2), POST to `<admin path>/_smoodly/preview`
|
|
6
|
+
// is the browser posting a draft grant the service signed. Operations run in the service. Each framework's adapter supplies the
|
|
7
|
+
// effects (next/admin-route.ts).
|
|
6
8
|
import { mountMismatch } from "../admin-path.js";
|
|
7
|
-
import {
|
|
9
|
+
import { fetchedKeySet, verifyMessage } from "../signed-message.js";
|
|
8
10
|
import { loadAdminFiles } from "./files.js";
|
|
9
|
-
import { createAdminOps, runAdminOp } from "./ops-impl.js";
|
|
10
11
|
import { ADMIN_INTERNAL } from "./page-data.js";
|
|
11
12
|
import { adminErrorHtml, adminPageHtml } from "./page.js";
|
|
12
13
|
import { serializeAdminConfig } from "./serialize.js";
|
|
@@ -19,7 +20,7 @@ const html = (status, body) => new Response(body, {
|
|
|
19
20
|
const refused = (code, message) => ({ ok: false, code, message });
|
|
20
21
|
/** The request came from a page of this site: its Origin names the host the browser asked for. Behind a
|
|
21
22
|
* proxy that host is X-Forwarded-Host (the first, when a chain appended more), as in Next's own check for
|
|
22
|
-
* server actions. No Origin, no
|
|
23
|
+
* server actions. No Origin, no preview: every browser sends one with a POST. */
|
|
23
24
|
export function sameOrigin(request) {
|
|
24
25
|
return originOf(request).same;
|
|
25
26
|
}
|
|
@@ -34,16 +35,76 @@ function originOf(request) {
|
|
|
34
35
|
catch { /* not a URL: not the same */ }
|
|
35
36
|
return { same, origin, host };
|
|
36
37
|
}
|
|
37
|
-
/**
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
/** The two routes' bodies: a tag list or a grant. Both are read before anything proves who sent them (the
|
|
39
|
+
* signature covers the body), so the size is the first gate: a declared length above it is refused unread,
|
|
40
|
+
* and a stream without one is cut off when it passes it. */
|
|
41
|
+
const MAX_ROUTE_BODY_BYTES = 256 * 1024;
|
|
42
|
+
const tooLarge = () => json(413, refused("validation", "smoodly: the call is larger than 256 KB."));
|
|
43
|
+
async function cappedText(request) {
|
|
44
|
+
if (Number(request.headers.get("content-length") ?? 0) > MAX_ROUTE_BODY_BYTES)
|
|
45
|
+
return null;
|
|
46
|
+
if (!request.body)
|
|
47
|
+
return "";
|
|
48
|
+
const reader = request.body.getReader();
|
|
49
|
+
const chunks = [];
|
|
50
|
+
let size = 0;
|
|
51
|
+
for (;;) {
|
|
52
|
+
const { done, value } = await reader.read();
|
|
53
|
+
if (done)
|
|
54
|
+
break;
|
|
55
|
+
size += value.byteLength;
|
|
56
|
+
if (size > MAX_ROUTE_BODY_BYTES) {
|
|
57
|
+
await reader.cancel();
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
chunks.push(value);
|
|
61
|
+
}
|
|
62
|
+
const bytes = new Uint8Array(size);
|
|
63
|
+
let at = 0;
|
|
64
|
+
for (const chunk of chunks) {
|
|
65
|
+
bytes.set(chunk, at);
|
|
66
|
+
at += chunk.byteLength;
|
|
67
|
+
}
|
|
68
|
+
return new TextDecoder().decode(bytes);
|
|
44
69
|
}
|
|
45
70
|
const decode = (base64) => Uint8Array.from(atob(base64), (c) => c.charCodeAt(0));
|
|
71
|
+
/** Where editors sign in, from the service, per fetch function and API address: one answer kept for the process, and a
|
|
72
|
+
* failure forgotten so the next page asks again. */
|
|
73
|
+
const signIns = new WeakMap();
|
|
74
|
+
const isSignIn = (v) => typeof v === "object" && v !== null && typeof v.signInUrl === "string" && typeof v.consoleUrl === "string";
|
|
75
|
+
function signInOf(d) {
|
|
76
|
+
const doFetch = d.fetch ?? fetch;
|
|
77
|
+
let byApi = signIns.get(doFetch);
|
|
78
|
+
if (!byApi)
|
|
79
|
+
signIns.set(doFetch, (byApi = new Map()));
|
|
80
|
+
const apiUrl = d.apiUrl.replace(/\/+$/, "");
|
|
81
|
+
let asked = byApi.get(apiUrl);
|
|
82
|
+
if (!asked) {
|
|
83
|
+
asked = (async () => {
|
|
84
|
+
const res = await doFetch(`${apiUrl}/v1/auth`, { headers: { accept: "application/json" } });
|
|
85
|
+
const body = (await res.json().catch(() => null));
|
|
86
|
+
if (!res.ok || !body?.ok || !isSignIn(body.data))
|
|
87
|
+
throw new Error(`${apiUrl}/v1/auth answered ${res.status} with no sign-in address`);
|
|
88
|
+
return { signInUrl: body.data.signInUrl, consoleUrl: body.data.consoleUrl };
|
|
89
|
+
})();
|
|
90
|
+
byApi.set(apiUrl, asked);
|
|
91
|
+
asked.catch(() => byApi.delete(apiUrl));
|
|
92
|
+
}
|
|
93
|
+
return asked;
|
|
94
|
+
}
|
|
46
95
|
/** `deps` is read on every request, so nothing is read at import and a build with no keys succeeds. */
|
|
96
|
+
/** The published key set per API address, so a process fetches it once whatever builds the deps per request. */
|
|
97
|
+
const keySets = new Map();
|
|
98
|
+
const keysOf = (d) => {
|
|
99
|
+
if (d.keys)
|
|
100
|
+
return d.keys;
|
|
101
|
+
let set = keySets.get(d.apiUrl);
|
|
102
|
+
if (!set) {
|
|
103
|
+
set = fetchedKeySet(d.apiUrl);
|
|
104
|
+
keySets.set(d.apiUrl, set);
|
|
105
|
+
}
|
|
106
|
+
return set;
|
|
107
|
+
};
|
|
47
108
|
export function createAdminHandler(deps) {
|
|
48
109
|
const registries = new WeakMap();
|
|
49
110
|
const registryOf = (config) => {
|
|
@@ -63,8 +124,7 @@ export function createAdminHandler(deps) {
|
|
|
63
124
|
const mismatch = mountMismatch(pathname, base);
|
|
64
125
|
if (mismatch)
|
|
65
126
|
return html(500, adminErrorHtml(mismatch));
|
|
66
|
-
// A
|
|
67
|
-
// so the shell still reaches sign-in and the schema notice.
|
|
127
|
+
// A glue that throws (a missing env value) is logged and turns presence off, so the shell still reaches sign-in.
|
|
68
128
|
let presence = null;
|
|
69
129
|
try {
|
|
70
130
|
presence = (await d.presence?.()) ?? null;
|
|
@@ -72,15 +132,28 @@ export function createAdminHandler(deps) {
|
|
|
72
132
|
catch (e) {
|
|
73
133
|
console.error("smoodly: editing presence is off —", e instanceof Error ? e.message : e);
|
|
74
134
|
}
|
|
135
|
+
let signIn;
|
|
136
|
+
try {
|
|
137
|
+
signIn = d.signIn ?? (await signInOf(d));
|
|
138
|
+
}
|
|
139
|
+
catch (e) {
|
|
140
|
+
const message = `smoodly: the admin could not reach Smoodly's API for its sign-in values — ${e instanceof Error ? e.message : String(e)}`;
|
|
141
|
+
console.error(message);
|
|
142
|
+
return html(500, adminErrorHtml(message));
|
|
143
|
+
}
|
|
75
144
|
const files = d.files ?? (await loadAdminFiles());
|
|
76
145
|
const data = {
|
|
77
146
|
registry: registryOf(d.config),
|
|
78
|
-
|
|
147
|
+
signIn,
|
|
148
|
+
apiUrl: d.apiUrl.replace(/\/+$/, ""),
|
|
149
|
+
spaceId: d.spaceId,
|
|
79
150
|
presence,
|
|
80
|
-
opUrl: `${
|
|
151
|
+
opUrl: `${d.apiUrl.replace(/\/+$/, "")}/v1/spaces/${encodeURIComponent(d.spaceId)}/admin/op`,
|
|
152
|
+
previewUrl: `${base}/${ADMIN_INTERNAL}/preview`,
|
|
81
153
|
filesBase: `${base}/${ADMIN_INTERNAL}/${files.hash}`,
|
|
82
154
|
previewBase: d.previewBase ?? "",
|
|
83
|
-
|
|
155
|
+
canvasMarks: d.canvasMarks ?? "query",
|
|
156
|
+
delivery: d.delivery ?? "live",
|
|
84
157
|
};
|
|
85
158
|
return html(200, adminPageHtml(data));
|
|
86
159
|
}
|
|
@@ -100,37 +173,59 @@ export function createAdminHandler(deps) {
|
|
|
100
173
|
},
|
|
101
174
|
});
|
|
102
175
|
}
|
|
103
|
-
/** The
|
|
104
|
-
*
|
|
105
|
-
async function
|
|
176
|
+
/** The service, after a publish: which tags to expire. The signature is the only admission (no origin:
|
|
177
|
+
* it is server to server), and the window keeps a captured call from being replayed. */
|
|
178
|
+
async function revalidate(request, d) {
|
|
179
|
+
const body = await cappedText(request);
|
|
180
|
+
if (body === null)
|
|
181
|
+
return tooLarge();
|
|
182
|
+
const h = request.headers;
|
|
183
|
+
const signed = { ts: Number(h.get("x-smoodly-timestamp")), kid: h.get("x-smoodly-key-id") ?? "", sig: h.get("x-smoodly-signature") ?? "" };
|
|
184
|
+
// The message is verified for the site's own space: a call signed for another names another space and fails.
|
|
185
|
+
const space = h.get("x-smoodly-space");
|
|
186
|
+
if ((space !== null && space !== d.spaceId) || !(await verifyMessage(keysOf(d), "revalidate", d.spaceId, body, signed))) {
|
|
187
|
+
return json(401, refused("unauthorized", "smoodly: the revalidate call is not signed by Smoodly's current key, for another space, or older than a minute."));
|
|
188
|
+
}
|
|
189
|
+
let tags;
|
|
190
|
+
try {
|
|
191
|
+
tags = JSON.parse(body).tags;
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
return json(400, refused("validation", "smoodly: the revalidate call was not JSON."));
|
|
195
|
+
}
|
|
196
|
+
if (!Array.isArray(tags) || !tags.every((t) => typeof t === "string"))
|
|
197
|
+
return json(400, refused("validation", "smoodly: the revalidate call needs `tags`, a list of strings."));
|
|
198
|
+
for (const tag of tags)
|
|
199
|
+
await d.effects?.revalidate?.(tag);
|
|
200
|
+
return json(200, { ok: true, data: { expired: tags.length } });
|
|
201
|
+
}
|
|
202
|
+
/** The browser, holding a grant the service signed: the draft cookie is set on the site's own origin,
|
|
203
|
+
* so the origin check comes first and the grant second. */
|
|
204
|
+
async function preview(request, d) {
|
|
106
205
|
const from = originOf(request);
|
|
107
|
-
if (!from.same)
|
|
108
|
-
return json(403, refused("unauthorized", `smoodly:
|
|
206
|
+
if (!from.same)
|
|
207
|
+
return json(403, refused("unauthorized", `smoodly: the preview route is called from the site's own address only — this request's Origin is ${String(from.origin).slice(0, 200)}.`));
|
|
208
|
+
const text = await cappedText(request);
|
|
209
|
+
if (text === null)
|
|
210
|
+
return tooLarge();
|
|
211
|
+
let sent;
|
|
212
|
+
try {
|
|
213
|
+
sent = JSON.parse(text);
|
|
109
214
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
return json(401, refused("unauthorized", "smoodly: sign in to use the admin."));
|
|
113
|
-
if (Number(request.headers.get("content-length") ?? 0) > MAX_BODY_BYTES) {
|
|
114
|
-
return json(413, refused("validation", "smoodly: the operation is larger than 4 MB."));
|
|
215
|
+
catch {
|
|
216
|
+
return json(400, refused("validation", "smoodly: the preview call was not JSON."));
|
|
115
217
|
}
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
// The token is the header's alone: a `token` in the body is dropped here.
|
|
129
|
-
const sent = typeof body === "object" && body !== null && !Array.isArray(body) ? body : {};
|
|
130
|
-
// Ops are built per call so the verified session rides into every store write as `by` (spec 2026-09-13 history §2).
|
|
131
|
-
return runAdminOp(createAdminOps({ ...base, actor: session }), { path: sent.path, args: sent.args });
|
|
132
|
-
});
|
|
133
|
-
return json(!result.ok && result.code === "unauthorized" ? 401 : status, result);
|
|
218
|
+
const action = sent.action === "enable" || sent.action === "disable" ? sent.action : null;
|
|
219
|
+
const grant = sent.grant;
|
|
220
|
+
const ok = action !== null && grant?.space === d.spaceId
|
|
221
|
+
&& (await verifyMessage(keysOf(d), "preview", d.spaceId, action, { ts: Number(grant?.ts), kid: String(grant?.kid ?? ""), sig: String(grant?.sig ?? "") }));
|
|
222
|
+
if (!ok)
|
|
223
|
+
return json(401, refused("unauthorized", "smoodly: the preview grant is missing, for another action, or older than a minute — open the page again."));
|
|
224
|
+
if (action === "enable")
|
|
225
|
+
await d.effects?.draft?.enable();
|
|
226
|
+
else
|
|
227
|
+
await d.effects?.draft?.disable();
|
|
228
|
+
return json(200, { ok: true, data: null });
|
|
134
229
|
}
|
|
135
230
|
return async function handle(request) {
|
|
136
231
|
try {
|
|
@@ -139,7 +234,11 @@ export function createAdminHandler(deps) {
|
|
|
139
234
|
const internal = `${d.config.admin.path}/${ADMIN_INTERNAL}/`;
|
|
140
235
|
const method = request.method.toUpperCase();
|
|
141
236
|
if (method === "POST") {
|
|
142
|
-
|
|
237
|
+
if (pathname === `${internal}revalidate`)
|
|
238
|
+
return await revalidate(request, d);
|
|
239
|
+
if (pathname === `${internal}preview`)
|
|
240
|
+
return await preview(request, d);
|
|
241
|
+
return json(404, refused("not_found", "smoodly: the admin mount serves the page, its files, the revalidate call and the preview grant; operations go to the service."));
|
|
143
242
|
}
|
|
144
243
|
if (method !== "GET" && method !== "HEAD")
|
|
145
244
|
return new Response(null, { status: 405, headers: { allow: "GET, HEAD, POST" } });
|
package/dist/admin/health.d.ts
CHANGED
|
@@ -13,15 +13,11 @@ export type HealthDocument = {
|
|
|
13
13
|
collection: string | null;
|
|
14
14
|
findings: HealthFinding[];
|
|
15
15
|
};
|
|
16
|
-
import type { DeliveryStatus } from "../project.ts";
|
|
17
|
-
/** `delivery` (DESIGN §3, the document stamp): the delivery rows behind the current stamp, with the recorded one;
|
|
18
|
-
* absent only in a report built without a projector (static tests). */
|
|
19
16
|
export type HealthReport = {
|
|
20
17
|
documents: HealthDocument[];
|
|
21
18
|
collections: {
|
|
22
19
|
name: string;
|
|
23
20
|
count: number;
|
|
24
21
|
}[];
|
|
25
|
-
delivery?: DeliveryStatus;
|
|
26
22
|
};
|
|
27
23
|
export declare function buildHealthReport(registry: AdminRegistry, pages: PageScan[], entries: EntryScan[]): HealthReport;
|
package/dist/admin/index.d.ts
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
|
-
export type { AdminOps, AdminOpCall, AdminOpAction, OpResult, OpError, PageCreateInput, PageGetResult, GlobalListItem, GlobalGetResult, AssetUsageItem, MembersListResult } from "./ops.ts";
|
|
1
|
+
export type { AdminEffects, AdminOps, AdminOpCall, AdminOpAction, OpResult, OpError, PageCreateInput, PageGetResult, GlobalListItem, GlobalGetResult, AssetUsageItem, MembersListResult } from "./ops.ts";
|
|
2
2
|
export type { InviteInput, InviteResult, MemberRecord } from "../member-store.ts";
|
|
3
3
|
export type { MemberRole } from "../permissions.ts";
|
|
4
|
-
export {
|
|
5
|
-
export {
|
|
6
|
-
export { ensureGlobalItems, initialGlobalFields } from "./global-items.ts";
|
|
4
|
+
export { fixedNodeSpecs, fixedNodeOf, type FixedNode } from "./fixed-nodes.ts";
|
|
5
|
+
export { initialGlobalFields } from "./global-items.ts";
|
|
7
6
|
export { makeClientOps } from "./client-ops.ts";
|
|
8
|
-
export {
|
|
9
|
-
export { supabaseAdminAuth } from "./supabase-auth.ts";
|
|
10
|
-
export type { AuthConfig } from "./auth-config.ts";
|
|
7
|
+
export type { SignInConfig } from "./auth-config.ts";
|
|
11
8
|
export { serializeAdminConfig } from "./serialize.ts";
|
|
12
9
|
export type { AdminRegistry, AdminSection, AdminCollection, AdminPageTemplate, AdminGlobal, AdminField } from "./serialize.ts";
|
|
13
10
|
export * from "./tree-ops.ts";
|
|
14
11
|
export { validateFields, type FieldError } from "./validate.ts";
|
|
15
|
-
export { createSmoodlyContent, SmoodlyContentError, type SmoodlyContent, type ContentDeps } from "../content/content-api.ts";
|
|
12
|
+
export { apiTransport, createSmoodlyContent, SmoodlyContentError, type SmoodlyContent, type ContentDeps, type ContentTransport } from "../content/content-api.ts";
|
package/dist/admin/index.js
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export {
|
|
3
|
-
export { ensureGlobalItems, initialGlobalFields } from "./global-items.js";
|
|
1
|
+
export { fixedNodeSpecs, fixedNodeOf } from "./fixed-nodes.js";
|
|
2
|
+
export { initialGlobalFields } from "./global-items.js";
|
|
4
3
|
export { makeClientOps } from "./client-ops.js";
|
|
5
|
-
export { gateAdminOp } from "./auth.js";
|
|
6
|
-
export { supabaseAdminAuth } from "./supabase-auth.js";
|
|
7
4
|
export { serializeAdminConfig } from "./serialize.js";
|
|
8
5
|
export * from "./tree-ops.js";
|
|
9
6
|
export { validateFields } from "./validate.js";
|
|
10
|
-
export { createSmoodlyContent, SmoodlyContentError } from "../content/content-api.js";
|
|
7
|
+
export { apiTransport, createSmoodlyContent, SmoodlyContentError } from "../content/content-api.js";
|