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/store.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// The page persistence contract. Writes always go through this API —
|
|
2
2
|
// never raw table access — so validation, versioning and publish hooks
|
|
3
|
-
// can't be bypassed (DESIGN.md §1). MemoryPageStore
|
|
4
|
-
// implementation and the contract-test subject; the
|
|
3
|
+
// can't be bypassed (DESIGN.md §1). MemoryPageStore (packages/service/src/stores)
|
|
4
|
+
// is the reference implementation and the contract-test subject; the space object's SQLite store
|
|
5
5
|
// implements the same interface over pages/page_locales/page_versions.
|
|
6
6
|
//
|
|
7
7
|
// The model (spec 2026-09-06): a page is ONE node with ONE row per
|
|
@@ -12,10 +12,7 @@
|
|
|
12
12
|
import { ASSET_COLLECTION } from "./media.js";
|
|
13
13
|
import { collectTreeRefs } from "./refs.js";
|
|
14
14
|
import { customToolset, withCustomSchema } from "./custom/index.js";
|
|
15
|
-
import {
|
|
16
|
-
import { assertFresh } from "./draft-token.js";
|
|
17
|
-
import { SCHEMA_VERSION } from "./schema-version.js";
|
|
18
|
-
import { assertDepth, assertSegment, assertSupportedLocale, chainOf, isHomeNode, pagePathRows, unsupportedLocale, rewriteTargets, } from "./paths.js";
|
|
15
|
+
import { unsupportedLocale, } from "./paths.js";
|
|
19
16
|
export const DEFAULT_LOCALES = { default: "en", supported: ["en"] };
|
|
20
17
|
/** The section list every adapter walks refs with: the registered ones plus the custom section's schema (spec 2026-09-14 §6). */
|
|
21
18
|
/** Group an index's redirect rows by locale, paths sorted. */
|
|
@@ -65,7 +62,6 @@ export const pageErrors = {
|
|
|
65
62
|
fixedTaken: (key) => new Error(`smoodly: a page owned by "${key}" already exists.`),
|
|
66
63
|
fixedAtRoot: () => new Error("smoodly: a code-owned page is a root record."),
|
|
67
64
|
};
|
|
68
|
-
export { assertSupportedLocale } from "./paths.js";
|
|
69
65
|
/** The page's row for a locale — the "does not exist in locale" guard every per-locale write runs. */
|
|
70
66
|
export function localeRow(page, locale) {
|
|
71
67
|
const row = page.locales[locale];
|
|
@@ -107,485 +103,3 @@ export async function assertPublishable(label, tree, sections, entries, locale)
|
|
|
107
103
|
throw new Error(`smoodly: cannot publish "${label}" — it references entries that are not published: ${offenders.join(", ")}.`);
|
|
108
104
|
}
|
|
109
105
|
}
|
|
110
|
-
let counter = 0;
|
|
111
|
-
const uid = () => `id-${++counter}-${Date.now().toString(36)}`;
|
|
112
|
-
/** A version's saved time: wall-clock, but strictly increasing, so two saves in one
|
|
113
|
-
* millisecond still move the draft token (spec 2026-09-26 concurrency §1) the way
|
|
114
|
-
* Postgres's microsecond `now()` does. */
|
|
115
|
-
let lastSavedAt = 0;
|
|
116
|
-
const savedClock = () => (lastSavedAt = Math.max(Date.now(), lastSavedAt + 1));
|
|
117
|
-
/** The row's audit pair: every write that touches a locale row sets both (spec 2026-09-13 history §2). */
|
|
118
|
-
function touchRow(row, by) {
|
|
119
|
-
row.updatedAt = Date.now();
|
|
120
|
-
row.updatedBy = by ?? null;
|
|
121
|
-
}
|
|
122
|
-
export class MemoryPageStore {
|
|
123
|
-
constructor(options = {}) {
|
|
124
|
-
this.options = options;
|
|
125
|
-
this.pages = new Map();
|
|
126
|
-
this.versions = new Map();
|
|
127
|
-
/** Append-only creation-order tie-break; `size + 1` stays fresh only because entries are never deleted. */
|
|
128
|
-
this.seq = new Map();
|
|
129
|
-
this.byId = (id) => this.pages.get(id);
|
|
130
|
-
this.options = { ...options, sections: refSections(options) };
|
|
131
|
-
this.opts = pathOptions(options);
|
|
132
|
-
// Alone, the store's own index reads the addresses it was given (spec 2026-09-29 anchored paths §2).
|
|
133
|
-
this.paths = options.paths ?? new MemoryPathIndex(storeAnchors(options));
|
|
134
|
-
}
|
|
135
|
-
must(id) {
|
|
136
|
-
const page = this.pages.get(id);
|
|
137
|
-
if (!page)
|
|
138
|
-
throw pageErrors.notFound(id);
|
|
139
|
-
return page;
|
|
140
|
-
}
|
|
141
|
-
isHome(page) {
|
|
142
|
-
return isHomeNode(page, this.opts);
|
|
143
|
-
}
|
|
144
|
-
snapshot(page) {
|
|
145
|
-
return { ...page, locales: structuredClone(page.locales) };
|
|
146
|
-
}
|
|
147
|
-
children(id) {
|
|
148
|
-
return [...this.pages.values()].filter((p) => p.parentId === id);
|
|
149
|
-
}
|
|
150
|
-
hasChildren(id) {
|
|
151
|
-
return this.children(id).length > 0;
|
|
152
|
-
}
|
|
153
|
-
siblings(parentId, except) {
|
|
154
|
-
return [...this.pages.values()]
|
|
155
|
-
.filter((p) => p.parentId === parentId && p.id !== except)
|
|
156
|
-
// null sorts last; the SQL equivalent is ORDER BY sort ASC NULLS LAST, created_at ASC.
|
|
157
|
-
.sort((a, b) => (a.sort ?? Infinity) - (b.sort ?? Infinity) || this.seq.get(a.id) - this.seq.get(b.id));
|
|
158
|
-
}
|
|
159
|
-
/** Sibling slugs are unique per parent PER LOCALE — a friendlier error
|
|
160
|
-
* than the path index's, which stays the real invariant. Code-owned
|
|
161
|
-
* roots are left out: their path is their address, and the path index
|
|
162
|
-
* guards it (spec 2026-09-29 nested paths §3). */
|
|
163
|
-
assertSlugFree(parentId, locale, slug, except) {
|
|
164
|
-
if (this.siblings(parentId, except).some((p) => !p.fixed && p.locales[locale]?.slug === slug))
|
|
165
|
-
throw pageErrors.slugTaken(slug);
|
|
166
|
-
}
|
|
167
|
-
subtree(id) {
|
|
168
|
-
const out = [];
|
|
169
|
-
const walk = (parentId) => {
|
|
170
|
-
for (const p of this.pages.values())
|
|
171
|
-
if (p.parentId === parentId) {
|
|
172
|
-
out.push(p);
|
|
173
|
-
walk(p.id);
|
|
174
|
-
}
|
|
175
|
-
};
|
|
176
|
-
const root = this.must(id);
|
|
177
|
-
out.push(root);
|
|
178
|
-
walk(id);
|
|
179
|
-
return out;
|
|
180
|
-
}
|
|
181
|
-
async writePaths(nodes, redirect = true) {
|
|
182
|
-
await this.paths.rewrite(rewriteTargets("page", nodes, redirect), pagePathRows(nodes, this.byId, this.opts));
|
|
183
|
-
}
|
|
184
|
-
/** Every locale's draft and published trees, deduped by version id. */
|
|
185
|
-
liveTrees(page) {
|
|
186
|
-
const ids = new Set();
|
|
187
|
-
for (const row of Object.values(page.locales)) {
|
|
188
|
-
if (row.draftVersionId)
|
|
189
|
-
ids.add(row.draftVersionId);
|
|
190
|
-
if (row.publishedVersionId)
|
|
191
|
-
ids.add(row.publishedVersionId);
|
|
192
|
-
}
|
|
193
|
-
const trees = [];
|
|
194
|
-
for (const id of ids) {
|
|
195
|
-
const tree = this.versions.get(id)?.tree;
|
|
196
|
-
if (tree)
|
|
197
|
-
trees.push(tree);
|
|
198
|
-
}
|
|
199
|
-
return trees;
|
|
200
|
-
}
|
|
201
|
-
writeRefs(page) {
|
|
202
|
-
const { sections, elements, refs } = this.options;
|
|
203
|
-
if (!sections || !refs)
|
|
204
|
-
return;
|
|
205
|
-
refs.rewrite({ sourceKind: "page", sourceId: page.id }, pageRefEdges(this.liveTrees(page), sections, elements));
|
|
206
|
-
}
|
|
207
|
-
newVersion(pageId, locale, tree) {
|
|
208
|
-
const version = { id: uid(), pageId, locale, tree: structuredClone(tree), savedAt: savedClock() };
|
|
209
|
-
this.versions.set(version.id, version);
|
|
210
|
-
return version;
|
|
211
|
-
}
|
|
212
|
-
/** The draft rule (spec 2026-09-13 history §1): the unpublished draft row is
|
|
213
|
-
* rewritten in place; a new row starts only when the draft IS the published one. */
|
|
214
|
-
writeDraftVersion(page, locale, tree, by) {
|
|
215
|
-
const row = localeRow(page, locale);
|
|
216
|
-
const current = row.draftVersionId ? this.versions.get(row.draftVersionId) : undefined;
|
|
217
|
-
let version;
|
|
218
|
-
if (current && row.draftVersionId !== row.publishedVersionId) {
|
|
219
|
-
current.tree = structuredClone(tree);
|
|
220
|
-
current.savedAt = savedClock();
|
|
221
|
-
row.draftSavedAt = String(current.savedAt);
|
|
222
|
-
version = current;
|
|
223
|
-
}
|
|
224
|
-
else {
|
|
225
|
-
version = this.newVersion(page.id, locale, tree);
|
|
226
|
-
row.draftVersionId = version.id;
|
|
227
|
-
row.draftSavedAt = String(version.savedAt);
|
|
228
|
-
}
|
|
229
|
-
touchRow(row, by);
|
|
230
|
-
return version;
|
|
231
|
-
}
|
|
232
|
-
dropVersions(pageId, locale) {
|
|
233
|
-
for (const [vid, v] of this.versions) {
|
|
234
|
-
if (v.pageId === pageId && (locale === undefined || v.locale === locale))
|
|
235
|
-
this.versions.delete(vid);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
async createPage(input, by) {
|
|
239
|
-
assertSupportedLocale(input.locale, this.opts.locales);
|
|
240
|
-
assertSegment(input.slug, "page slug");
|
|
241
|
-
const parentId = input.parentId ?? null;
|
|
242
|
-
if (input.fixed !== undefined) {
|
|
243
|
-
if (parentId !== null)
|
|
244
|
-
throw pageErrors.fixedAtRoot();
|
|
245
|
-
if ([...this.pages.values()].some((p) => p.fixed === input.fixed))
|
|
246
|
-
throw pageErrors.fixedTaken(input.fixed);
|
|
247
|
-
}
|
|
248
|
-
if (parentId !== null) {
|
|
249
|
-
const parent = this.must(parentId);
|
|
250
|
-
if (this.isHome(parent))
|
|
251
|
-
throw pageErrors.homeChildren();
|
|
252
|
-
if (!parent.locales[input.locale])
|
|
253
|
-
throw pageErrors.parentNotInLocale(input.locale);
|
|
254
|
-
assertDepth(chainOf(parent, this.byId).length + 1, this.opts.depth, "the page");
|
|
255
|
-
}
|
|
256
|
-
if (input.fixed === undefined)
|
|
257
|
-
this.assertSlugFree(parentId, input.locale, input.slug);
|
|
258
|
-
const id = uid();
|
|
259
|
-
const version = input.template === null ? null : this.newVersion(id, input.locale, { zones: {} });
|
|
260
|
-
const page = {
|
|
261
|
-
id,
|
|
262
|
-
parentId,
|
|
263
|
-
template: input.template,
|
|
264
|
-
// An ordering hint, not a unique key: duplicates are possible after
|
|
265
|
-
// moves/deletes, and the creation-order tie-break settles them.
|
|
266
|
-
sort: this.siblings(parentId).length + 1,
|
|
267
|
-
fixed: input.fixed ?? null,
|
|
268
|
-
locales: {
|
|
269
|
-
[input.locale]: {
|
|
270
|
-
slug: input.slug,
|
|
271
|
-
title: input.title ?? input.slug,
|
|
272
|
-
status: "draft",
|
|
273
|
-
draftVersionId: version?.id ?? null,
|
|
274
|
-
draftSavedAt: version ? String(version.savedAt) : null,
|
|
275
|
-
publishedVersionId: null,
|
|
276
|
-
updatedAt: Date.now(),
|
|
277
|
-
updatedBy: by ?? null,
|
|
278
|
-
},
|
|
279
|
-
},
|
|
280
|
-
};
|
|
281
|
-
this.pages.set(id, page);
|
|
282
|
-
this.seq.set(id, this.seq.size + 1);
|
|
283
|
-
try {
|
|
284
|
-
await this.writePaths([page]);
|
|
285
|
-
}
|
|
286
|
-
catch (e) {
|
|
287
|
-
this.pages.delete(id);
|
|
288
|
-
if (version)
|
|
289
|
-
this.versions.delete(version.id);
|
|
290
|
-
throw e;
|
|
291
|
-
}
|
|
292
|
-
return this.snapshot(page);
|
|
293
|
-
}
|
|
294
|
-
async getPage(id) {
|
|
295
|
-
const page = this.pages.get(id);
|
|
296
|
-
return page ? this.snapshot(page) : null;
|
|
297
|
-
}
|
|
298
|
-
async scanTrees() {
|
|
299
|
-
const tree = (id) => (id ? structuredClone(this.versions.get(id)?.tree ?? null) : null);
|
|
300
|
-
return (await this.listPages()).map((record) => ({
|
|
301
|
-
record,
|
|
302
|
-
draft: Object.fromEntries(Object.entries(record.locales).map(([l, row]) => [l, tree(row.draftVersionId)])),
|
|
303
|
-
live: Object.fromEntries(Object.entries(record.locales).map(([l, row]) => [l, tree(row.publishedVersionId)])),
|
|
304
|
-
}));
|
|
305
|
-
}
|
|
306
|
-
async listPages() {
|
|
307
|
-
return [...this.pages.values()]
|
|
308
|
-
// null sorts last; the SQL equivalent is ORDER BY sort ASC NULLS LAST, created_at ASC.
|
|
309
|
-
.sort((a, b) => (a.sort ?? Infinity) - (b.sort ?? Infinity) || this.seq.get(a.id) - this.seq.get(b.id))
|
|
310
|
-
.map((p) => this.snapshot(p));
|
|
311
|
-
}
|
|
312
|
-
resolvePath(locale, path) {
|
|
313
|
-
return this.paths.resolve(locale, path);
|
|
314
|
-
}
|
|
315
|
-
schemaVersion() {
|
|
316
|
-
return Promise.resolve(SCHEMA_VERSION);
|
|
317
|
-
}
|
|
318
|
-
pathsOf(id) {
|
|
319
|
-
return this.paths.pathsOf({ kind: "page", id });
|
|
320
|
-
}
|
|
321
|
-
targetPaths(target) { return this.paths.pathsOf(target); }
|
|
322
|
-
async redirectsOf(id) { return groupRedirects(await this.paths.redirectsOf({ kind: "page", id })); }
|
|
323
|
-
listLive(locale) { return this.paths.listLive(locale); }
|
|
324
|
-
listRedirects(locale) { return this.paths.listRedirects(locale); }
|
|
325
|
-
addRedirect(row) { return this.paths.addRedirect(row); }
|
|
326
|
-
removeRedirect(locale, path) { return this.paths.removeRedirect(locale, path); }
|
|
327
|
-
async addLocale(id, locale, options, by) {
|
|
328
|
-
assertSupportedLocale(locale, this.opts.locales);
|
|
329
|
-
const page = this.must(id);
|
|
330
|
-
if (page.locales[locale])
|
|
331
|
-
throw pageErrors.alreadyInLocale(id, locale);
|
|
332
|
-
const source = localeRow(page, options.from);
|
|
333
|
-
const slug = options.slug ?? source.slug;
|
|
334
|
-
if (options.slug !== undefined)
|
|
335
|
-
assertSegment(slug, `page slug (${locale})`);
|
|
336
|
-
if (page.parentId !== null && !this.must(page.parentId).locales[locale])
|
|
337
|
-
throw pageErrors.parentNotInLocale(locale);
|
|
338
|
-
if (!page.fixed)
|
|
339
|
-
this.assertSlugFree(page.parentId, locale, slug, id);
|
|
340
|
-
// The SOURCE'S DRAFT is the copy: what the editor sees on screen when
|
|
341
|
-
// they add the locale (spec §3). A folder has no tree to copy.
|
|
342
|
-
const version = page.template === null
|
|
343
|
-
? null
|
|
344
|
-
: this.newVersion(id, locale, (source.draftVersionId && this.versions.get(source.draftVersionId)?.tree) || { zones: {} });
|
|
345
|
-
page.locales[locale] = {
|
|
346
|
-
slug,
|
|
347
|
-
title: options.title ?? source.title,
|
|
348
|
-
status: "draft",
|
|
349
|
-
draftVersionId: version?.id ?? null,
|
|
350
|
-
draftSavedAt: version ? String(version.savedAt) : null,
|
|
351
|
-
publishedVersionId: null,
|
|
352
|
-
updatedAt: Date.now(),
|
|
353
|
-
updatedBy: by ?? null,
|
|
354
|
-
};
|
|
355
|
-
try {
|
|
356
|
-
await this.writePaths([page]);
|
|
357
|
-
}
|
|
358
|
-
catch (e) {
|
|
359
|
-
delete page.locales[locale];
|
|
360
|
-
if (version)
|
|
361
|
-
this.versions.delete(version.id);
|
|
362
|
-
throw e;
|
|
363
|
-
}
|
|
364
|
-
this.writeRefs(page);
|
|
365
|
-
return this.snapshot(page);
|
|
366
|
-
}
|
|
367
|
-
async removeLocale(id, locale) {
|
|
368
|
-
const page = this.must(id);
|
|
369
|
-
const removed = localeRow(page, locale);
|
|
370
|
-
if (Object.keys(page.locales).length === 1)
|
|
371
|
-
throw pageErrors.lastLocale(id);
|
|
372
|
-
// Under the parent rule only direct children can hold the locale, but
|
|
373
|
-
// the walk is cheap and the message must name every blocker.
|
|
374
|
-
const blockers = this.subtree(id).filter((n) => n.id !== id && n.locales[locale]);
|
|
375
|
-
if (blockers.length > 0)
|
|
376
|
-
throw pageErrors.localeInUse(locale, blockers.map((b) => b.locales[locale].title));
|
|
377
|
-
delete page.locales[locale];
|
|
378
|
-
try {
|
|
379
|
-
await this.writePaths([page], false);
|
|
380
|
-
}
|
|
381
|
-
catch (e) {
|
|
382
|
-
page.locales[locale] = removed;
|
|
383
|
-
throw e;
|
|
384
|
-
}
|
|
385
|
-
this.dropVersions(id, locale);
|
|
386
|
-
this.writeRefs(page);
|
|
387
|
-
return this.snapshot(page);
|
|
388
|
-
}
|
|
389
|
-
async rename(id, patch, by, options) {
|
|
390
|
-
const page = this.must(id);
|
|
391
|
-
const row = localeRow(page, patch.locale);
|
|
392
|
-
if (patch.slug !== undefined)
|
|
393
|
-
assertSegment(patch.slug, `page slug (${patch.locale})`);
|
|
394
|
-
// A rename that turns this node INTO the home node (root, default-locale
|
|
395
|
-
// slug === homePageSlug) is subject to the same invariant as createPage:
|
|
396
|
-
// the home page cannot have child pages.
|
|
397
|
-
if (patch.locale === this.opts.locales.default &&
|
|
398
|
-
patch.slug !== undefined &&
|
|
399
|
-
page.parentId === null &&
|
|
400
|
-
patch.slug === this.opts.homePageSlug &&
|
|
401
|
-
this.hasChildren(id)) {
|
|
402
|
-
throw pageErrors.homeChildren();
|
|
403
|
-
}
|
|
404
|
-
if (patch.slug !== undefined && !page.fixed)
|
|
405
|
-
this.assertSlugFree(page.parentId, patch.locale, patch.slug, id);
|
|
406
|
-
const before = { ...row };
|
|
407
|
-
if (patch.slug !== undefined)
|
|
408
|
-
row.slug = patch.slug;
|
|
409
|
-
if (patch.title !== undefined)
|
|
410
|
-
row.title = patch.title;
|
|
411
|
-
try {
|
|
412
|
-
await this.writePaths(this.subtree(id), options?.redirect ?? true);
|
|
413
|
-
}
|
|
414
|
-
catch (e) {
|
|
415
|
-
Object.assign(row, before);
|
|
416
|
-
throw e;
|
|
417
|
-
}
|
|
418
|
-
touchRow(row, by);
|
|
419
|
-
return this.snapshot(page);
|
|
420
|
-
}
|
|
421
|
-
async move(id, to, options) {
|
|
422
|
-
const page = this.must(id);
|
|
423
|
-
if (page.fixed && to.parentId !== null)
|
|
424
|
-
throw pageErrors.fixedAtRoot();
|
|
425
|
-
if (to.parentId !== null) {
|
|
426
|
-
const parent = this.must(to.parentId);
|
|
427
|
-
if (to.parentId === id || chainOf(parent, this.byId).some((n) => n.id === id))
|
|
428
|
-
throw pageErrors.cycle();
|
|
429
|
-
if (this.isHome(parent))
|
|
430
|
-
throw pageErrors.homeChildren();
|
|
431
|
-
// The parent rule, on the move: every locale the page has must be
|
|
432
|
-
// present at the destination, or its chain would break there.
|
|
433
|
-
for (const locale of Object.keys(page.locales))
|
|
434
|
-
if (!parent.locales[locale])
|
|
435
|
-
throw pageErrors.moveLocales(locale);
|
|
436
|
-
const subtreeDepth = Math.max(...this.subtree(id).map((n) => chainOf(n, this.byId).length)) - chainOf(page, this.byId).length + 1;
|
|
437
|
-
assertDepth(chainOf(parent, this.byId).length + subtreeDepth, this.opts.depth, "the page");
|
|
438
|
-
}
|
|
439
|
-
else if (page.locales[this.opts.locales.default]?.slug === this.opts.homePageSlug && this.hasChildren(id)) {
|
|
440
|
-
// Moving a home-slugged node TO the root is the mirror case of the
|
|
441
|
-
// rename guard above: it would make it the home node while it still
|
|
442
|
-
// has children.
|
|
443
|
-
throw pageErrors.homeChildren();
|
|
444
|
-
}
|
|
445
|
-
if (to.parentId !== page.parentId) {
|
|
446
|
-
for (const [locale, row] of Object.entries(page.locales))
|
|
447
|
-
this.assertSlugFree(to.parentId, locale, row.slug, id);
|
|
448
|
-
}
|
|
449
|
-
const before = { parentId: page.parentId, sort: page.sort };
|
|
450
|
-
page.parentId = to.parentId;
|
|
451
|
-
const siblings = this.siblings(to.parentId, id);
|
|
452
|
-
const index = Math.min(Math.max(to.index ?? siblings.length, 0), siblings.length);
|
|
453
|
-
siblings.splice(index, 0, page);
|
|
454
|
-
const previousSort = new Map(siblings.map((s) => [s.id, s.sort]));
|
|
455
|
-
siblings.forEach((s, i) => (s.sort = i + 1));
|
|
456
|
-
try {
|
|
457
|
-
await this.writePaths(this.subtree(id), options?.redirect ?? true);
|
|
458
|
-
}
|
|
459
|
-
catch (e) {
|
|
460
|
-
Object.assign(page, before);
|
|
461
|
-
for (const s of siblings)
|
|
462
|
-
s.sort = previousSort.get(s.id) ?? null;
|
|
463
|
-
throw e;
|
|
464
|
-
}
|
|
465
|
-
return this.snapshot(page);
|
|
466
|
-
}
|
|
467
|
-
async setTemplate(id, template, by) {
|
|
468
|
-
const page = this.must(id);
|
|
469
|
-
if (page.template !== null)
|
|
470
|
-
throw pageErrors.hasTemplate(id);
|
|
471
|
-
page.template = template;
|
|
472
|
-
for (const [locale, row] of Object.entries(page.locales)) {
|
|
473
|
-
const version = this.newVersion(id, locale, { zones: {} });
|
|
474
|
-
row.draftVersionId = version.id;
|
|
475
|
-
row.draftSavedAt = String(version.savedAt);
|
|
476
|
-
touchRow(row, by);
|
|
477
|
-
}
|
|
478
|
-
return this.snapshot(page);
|
|
479
|
-
}
|
|
480
|
-
async saveDraft(id, locale, tree, by, expected) {
|
|
481
|
-
const page = this.must(id);
|
|
482
|
-
if (page.template === null)
|
|
483
|
-
throw pageErrors.folder(id);
|
|
484
|
-
assertFresh(localeRow(page, locale), expected);
|
|
485
|
-
const version = this.writeDraftVersion(page, locale, tree, by);
|
|
486
|
-
this.writeRefs(page);
|
|
487
|
-
return { ...version, tree: structuredClone(version.tree) };
|
|
488
|
-
}
|
|
489
|
-
async publish(id, locale, by, expected) {
|
|
490
|
-
const page = this.must(id);
|
|
491
|
-
if (page.template === null)
|
|
492
|
-
throw pageErrors.folder(id);
|
|
493
|
-
const row = localeRow(page, locale);
|
|
494
|
-
assertFresh(row, expected);
|
|
495
|
-
if (!row.draftVersionId)
|
|
496
|
-
throw pageErrors.folder(id);
|
|
497
|
-
const { sections, entries } = this.options;
|
|
498
|
-
const draft = this.versions.get(row.draftVersionId)?.tree ?? { zones: {} };
|
|
499
|
-
if (sections && entries)
|
|
500
|
-
await assertPublishable(row.slug, draft, sections, entries, locale);
|
|
501
|
-
row.publishedVersionId = row.draftVersionId;
|
|
502
|
-
row.status = "published";
|
|
503
|
-
touchRow(row, by);
|
|
504
|
-
this.writeRefs(page);
|
|
505
|
-
return this.snapshot(page);
|
|
506
|
-
}
|
|
507
|
-
async discardDraft(id, locale, by, expected) {
|
|
508
|
-
const page = this.must(id);
|
|
509
|
-
const row = localeRow(page, locale);
|
|
510
|
-
assertFresh(row, expected);
|
|
511
|
-
if (!row.publishedVersionId)
|
|
512
|
-
throw pageErrors.nothingPublished(id, locale);
|
|
513
|
-
const abandoned = row.draftVersionId;
|
|
514
|
-
row.draftVersionId = row.publishedVersionId;
|
|
515
|
-
row.draftSavedAt = String(this.versions.get(row.publishedVersionId).savedAt);
|
|
516
|
-
if (abandoned && abandoned !== row.publishedVersionId)
|
|
517
|
-
this.versions.delete(abandoned); // never shown as a stray draft
|
|
518
|
-
touchRow(row, by);
|
|
519
|
-
this.writeRefs(page); // the refs union (draft ∪ published) just shrank to one tree
|
|
520
|
-
return this.snapshot(page);
|
|
521
|
-
}
|
|
522
|
-
async unpublish(id, locale, by) {
|
|
523
|
-
const page = this.must(id);
|
|
524
|
-
const row = localeRow(page, locale);
|
|
525
|
-
row.status = "draft";
|
|
526
|
-
touchRow(row, by);
|
|
527
|
-
this.writeRefs(page);
|
|
528
|
-
return this.snapshot(page);
|
|
529
|
-
}
|
|
530
|
-
async restore(id, locale, versionId, by, expected) {
|
|
531
|
-
const page = this.must(id);
|
|
532
|
-
if (page.template === null)
|
|
533
|
-
throw pageErrors.folder(id);
|
|
534
|
-
const row = localeRow(page, locale);
|
|
535
|
-
const version = this.versions.get(versionId);
|
|
536
|
-
if (!version || version.pageId !== id || version.locale !== locale)
|
|
537
|
-
throw pageErrors.foreignVersion(id, locale, versionId);
|
|
538
|
-
// The memory store is single-threaded: the read-side check IS the atomic check.
|
|
539
|
-
assertFresh(row, expected);
|
|
540
|
-
if (versionId === row.publishedVersionId)
|
|
541
|
-
return this.discardDraft(id, locale, by, expected);
|
|
542
|
-
this.writeDraftVersion(page, locale, version.tree, by);
|
|
543
|
-
this.writeRefs(page);
|
|
544
|
-
return this.snapshot(page);
|
|
545
|
-
}
|
|
546
|
-
async getVersion(versionId) {
|
|
547
|
-
const v = this.versions.get(versionId);
|
|
548
|
-
return v ? { ...v, tree: structuredClone(v.tree) } : null;
|
|
549
|
-
}
|
|
550
|
-
async getDraftTree(id, locale) {
|
|
551
|
-
const row = this.pages.get(id)?.locales[locale];
|
|
552
|
-
if (!row?.draftVersionId)
|
|
553
|
-
return null;
|
|
554
|
-
return structuredClone(this.versions.get(row.draftVersionId)?.tree ?? null);
|
|
555
|
-
}
|
|
556
|
-
async fieldsOf(versionIds) {
|
|
557
|
-
const out = {};
|
|
558
|
-
for (const id of versionIds) {
|
|
559
|
-
const v = this.versions.get(id);
|
|
560
|
-
if (v)
|
|
561
|
-
out[id] = structuredClone(v.tree.fields ?? {});
|
|
562
|
-
}
|
|
563
|
-
return out;
|
|
564
|
-
}
|
|
565
|
-
async getPublishedTree(id, locale) {
|
|
566
|
-
const row = this.pages.get(id)?.locales[locale];
|
|
567
|
-
if (!row?.publishedVersionId)
|
|
568
|
-
return null;
|
|
569
|
-
return structuredClone(this.versions.get(row.publishedVersionId)?.tree ?? null);
|
|
570
|
-
}
|
|
571
|
-
async listVersions(id, locale) {
|
|
572
|
-
return [...this.versions.values()]
|
|
573
|
-
.filter((v) => v.pageId === id && v.locale === locale)
|
|
574
|
-
.sort((a, b) => b.savedAt - a.savedAt || b.id.localeCompare(a.id))
|
|
575
|
-
.map((v) => ({ ...v, tree: structuredClone(v.tree) }));
|
|
576
|
-
}
|
|
577
|
-
async deletePage(id) {
|
|
578
|
-
const page = this.pages.get(id);
|
|
579
|
-
if (!page)
|
|
580
|
-
return;
|
|
581
|
-
if (this.hasChildren(id))
|
|
582
|
-
throw pageErrors.hasChildren(id);
|
|
583
|
-
// The index removal goes first: in the Supabase adapter it is a network
|
|
584
|
-
// call that can fail, and a failed rewrite must leave this store's own
|
|
585
|
-
// state (versions, refs, the page map) untouched.
|
|
586
|
-
await this.paths.remove([{ kind: "page", id }]);
|
|
587
|
-
this.dropVersions(id);
|
|
588
|
-
this.options.refs?.remove({ sourceKind: "page", sourceId: id });
|
|
589
|
-
this.pages.delete(id);
|
|
590
|
-
}
|
|
591
|
-
}
|
package/dist/toolset.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export declare const TOOLSET_MARKS: readonly ["bold", "italic", "link"];
|
|
2
|
-
|
|
2
|
+
declare const TOOLSET_NODES: readonly ["h1", "h2", "h3", "h4", "h5", "h6", "bulletList", "orderedList", "blockquote", "image"];
|
|
3
3
|
export type ToolsetMark = (typeof TOOLSET_MARKS)[number];
|
|
4
4
|
export type ToolsetNode = (typeof TOOLSET_NODES)[number];
|
|
5
5
|
export type Toolset = {
|
|
@@ -32,3 +32,4 @@ export type Allowed = {
|
|
|
32
32
|
elements: string[];
|
|
33
33
|
};
|
|
34
34
|
export declare function allowedOf(input: unknown): Allowed;
|
|
35
|
+
export {};
|
package/dist/toolset.js
CHANGED
|
@@ -5,7 +5,7 @@ import { schemaOnly } from "./registration.js";
|
|
|
5
5
|
// registration; a field without a toolset carries the default. `Allowed`
|
|
6
6
|
// is the resolved form the widget reads (spec 2026-09-08 §1).
|
|
7
7
|
export const TOOLSET_MARKS = ["bold", "italic", "link"];
|
|
8
|
-
|
|
8
|
+
const TOOLSET_NODES = ["h1", "h2", "h3", "h4", "h5", "h6", "bulletList", "orderedList", "blockquote", "image"];
|
|
9
9
|
/** What a richtext field gets when it declares nothing. `h1` is left out:
|
|
10
10
|
* the section or page shape owns the page's first heading. */
|
|
11
11
|
export const DEFAULT_TOOLSET = {
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const SMOODLY_VERSION = "0.0.
|
|
1
|
+
export declare const SMOODLY_VERSION = "0.0.53";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Generated by scripts/write-version.mjs from package.json — do not edit.
|
|
2
|
-
export const SMOODLY_VERSION = "0.0.
|
|
2
|
+
export const SMOODLY_VERSION = "0.0.53";
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "smoodly",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "Open-source, code-first CMS for Astro and Next.js
|
|
3
|
+
"version": "0.0.53",
|
|
4
|
+
"description": "Open-source, code-first CMS for Astro and Next.js: define a component once, it becomes a typed, editable building block.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
|
-
"url": "git+https://github.com/
|
|
8
|
+
"url": "git+https://github.com/smoodly-io/smoodly.git",
|
|
9
9
|
"directory": "packages/smoodly"
|
|
10
10
|
},
|
|
11
11
|
"type": "module",
|
|
@@ -47,10 +47,6 @@
|
|
|
47
47
|
"types": "./dist/image/index.d.ts",
|
|
48
48
|
"default": "./dist/image/index.js"
|
|
49
49
|
},
|
|
50
|
-
"./image/config": {
|
|
51
|
-
"types": "./dist/image/next-config.d.ts",
|
|
52
|
-
"default": "./dist/image/next-config.js"
|
|
53
|
-
},
|
|
54
50
|
"./richtext": {
|
|
55
51
|
"types": "./dist/richtext/index.d.ts",
|
|
56
52
|
"default": "./dist/richtext/index.js"
|
|
@@ -58,15 +54,10 @@
|
|
|
58
54
|
"./admin": {
|
|
59
55
|
"types": "./dist/admin/index.d.ts",
|
|
60
56
|
"default": "./dist/admin/index.js"
|
|
61
|
-
},
|
|
62
|
-
"./migrations": {
|
|
63
|
-
"types": "./dist/migrations.d.ts",
|
|
64
|
-
"default": "./dist/migrations.js"
|
|
65
57
|
}
|
|
66
58
|
},
|
|
67
59
|
"files": [
|
|
68
60
|
"dist",
|
|
69
|
-
"migrations",
|
|
70
61
|
"bin"
|
|
71
62
|
],
|
|
72
63
|
"scripts": {
|
|
@@ -76,8 +67,7 @@
|
|
|
76
67
|
"pretest": "node scripts/write-version.mjs && npm run -s build:admin",
|
|
77
68
|
"test": "vitest run",
|
|
78
69
|
"test:watch": "vitest",
|
|
79
|
-
"typecheck": "tsc --noEmit"
|
|
80
|
-
"test:supabase": "bash scripts/test-supabase.sh"
|
|
70
|
+
"typecheck": "tsc --noEmit"
|
|
81
71
|
},
|
|
82
72
|
"peerDependencies": {
|
|
83
73
|
"astro": "^7.3.5",
|
|
@@ -123,7 +113,6 @@
|
|
|
123
113
|
"vitest": "^3"
|
|
124
114
|
},
|
|
125
115
|
"dependencies": {
|
|
126
|
-
"@supabase/supabase-js": "^2.113.0",
|
|
127
116
|
"tsx": "^4.23.15"
|
|
128
117
|
}
|
|
129
118
|
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { AdminOpAction } from "../ops.ts";
|
|
2
|
-
/** JSON has no `undefined`: in an array it becomes null, which several ops read as a value (`expected:
|
|
3
|
-
* null` is "no draft", `undefined` is "skip the check"). Trailing ones are dropped; one between two
|
|
4
|
-
* arguments cannot be sent and is named here. */
|
|
5
|
-
export declare function wireArgs(args: unknown[]): unknown[];
|
|
6
|
-
/** `fetcher` is the test seam. A network failure rejects; every answer resolves to a typed result.
|
|
7
|
-
* One operation at a time, in call order: Next queued server actions per browser, and the admin's reads
|
|
8
|
-
* and writes rely on it (a read sent after a save must see that save's token; a save typed during a
|
|
9
|
-
* publish must follow the publish). A call that fails does not hold the ones behind it. */
|
|
10
|
-
export declare function fetchOp(url: string, fetcher?: typeof fetch): AdminOpAction;
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
/** JSON has no `undefined`: in an array it becomes null, which several ops read as a value (`expected:
|
|
2
|
-
* null` is "no draft", `undefined` is "skip the check"). Trailing ones are dropped; one between two
|
|
3
|
-
* arguments cannot be sent and is named here. */
|
|
4
|
-
export function wireArgs(args) {
|
|
5
|
-
let end = args.length;
|
|
6
|
-
while (end > 0 && args[end - 1] === undefined)
|
|
7
|
-
end--;
|
|
8
|
-
const out = args.slice(0, end);
|
|
9
|
-
if (out.some((a) => a === undefined)) {
|
|
10
|
-
throw new Error("smoodly: an operation argument is undefined between two others — pass null or a value; JSON cannot carry undefined.");
|
|
11
|
-
}
|
|
12
|
-
return out;
|
|
13
|
-
}
|
|
14
|
-
const noResult = (status) => ({ ok: false, code: "internal", message: `smoodly: the admin's endpoint answered ${status} without a result — see the server log.` });
|
|
15
|
-
/** `fetcher` is the test seam. A network failure rejects; every answer resolves to a typed result.
|
|
16
|
-
* One operation at a time, in call order: Next queued server actions per browser, and the admin's reads
|
|
17
|
-
* and writes rely on it (a read sent after a save must see that save's token; a save typed during a
|
|
18
|
-
* publish must follow the publish). A call that fails does not hold the ones behind it. */
|
|
19
|
-
export function fetchOp(url, fetcher = fetch) {
|
|
20
|
-
const send = async (call) => {
|
|
21
|
-
const res = await fetcher(url, {
|
|
22
|
-
method: "POST",
|
|
23
|
-
credentials: "same-origin",
|
|
24
|
-
headers: { "content-type": "application/json", ...(call.token ? { authorization: `Bearer ${call.token}` } : {}) },
|
|
25
|
-
body: JSON.stringify({ path: call.path, args: wireArgs(call.args) }),
|
|
26
|
-
});
|
|
27
|
-
let body;
|
|
28
|
-
try {
|
|
29
|
-
body = await res.json();
|
|
30
|
-
}
|
|
31
|
-
catch {
|
|
32
|
-
return noResult(res.status);
|
|
33
|
-
}
|
|
34
|
-
if (typeof body === "object" && body !== null && typeof body.ok === "boolean")
|
|
35
|
-
return body;
|
|
36
|
-
return noResult(res.status);
|
|
37
|
-
};
|
|
38
|
-
let last = Promise.resolve();
|
|
39
|
-
return (call) => {
|
|
40
|
-
const run = last.then(() => send(call));
|
|
41
|
-
last = run.catch(() => { });
|
|
42
|
-
return run;
|
|
43
|
-
};
|
|
44
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|