smoodly 0.0.46 → 0.0.47

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/dist/sql.js CHANGED
@@ -9,7 +9,7 @@
9
9
  // locale (spec 2026-09-06 §7); a versioned collection reads its
10
10
  // fields from the published snapshot.
11
11
  import { SEO_KEY } from "./seo.js";
12
- import { currentSpaceSQL, grantsSQL, SPACE_COL, spaceLayerSQL } from "./sql-space.js";
12
+ import { currentSpaceSQL, grantsSQL, SPACE_COL, spaceLayerSQL, DEFAULT_SPACE } from "./sql-space.js";
13
13
  const snake = (s) => s.replace(/[A-Z]/g, (c) => "_" + c.toLowerCase());
14
14
  const q = (s) => `"${s}"`;
15
15
  // Casts are fine in views.
@@ -173,8 +173,17 @@ create unique index if not exists "published_entries_address_idx"
173
173
  create index if not exists "published_entries_fields_idx"
174
174
  on "published_entries" using gin ("fields");
175
175
 
176
+ -- The document stamp a space recorded as current (DESIGN §3, schema v10): set by \`npx smoodly rebuild\`
177
+ -- and by the first writer into an empty record; only a reader at this stamp rewrites the rows above.
178
+ create table if not exists "published_models" (
179
+ "space_id" uuid primary key default ${DEFAULT_SPACE},
180
+ "model" text not null,
181
+ "recorded_at" timestamptz not null default now()
182
+ );
183
+
176
184
  alter table "published_pages" enable row level security;
177
185
  alter table "published_entries" enable row level security;
186
+ alter table "published_models" enable row level security;
178
187
 
179
188
  -- smoodly_list_entries without the draft branch, over the delivery table (spec 2026-10-07 §3):
180
189
  -- \`where\` and \`order\` read the raw published snapshot, the row answers with its document.
@@ -20,6 +20,16 @@ export declare class SupabasePublishedStore implements PublishedStore {
20
20
  /** One request per kind and locale filter: the keys without a locale go in one in(). */
21
21
  private eachGroup;
22
22
  remove(keys: PublishedKey[]): Promise<void>;
23
+ /** The space's recorded stamp (DESIGN §3): one row per space in published_models; the key's space, or the
24
+ * service role's default one. */
25
+ recordedModel(): Promise<string | null>;
26
+ recordModel(model: string): Promise<void>;
27
+ /** Four head counts (no rows travel): each table's rows, and its rows at another stamp. The policies scope a
28
+ * write key to its space; the service role counts the default space's rows by the explicit filter. */
29
+ countBehind(model: string): Promise<{
30
+ behind: number;
31
+ total: number;
32
+ }>;
23
33
  markStale(keys: PublishedKey[]): Promise<void>;
24
34
  /** A route row is for serving, not for the tree: a page route carries the
25
35
  * document, model, stale flag and tags; its tree columns are empty, and an
@@ -117,6 +117,38 @@ export class SupabasePublishedStore {
117
117
  this.fail(res.error.message);
118
118
  });
119
119
  }
120
+ /** The space's recorded stamp (DESIGN §3): one row per space in published_models; the key's space, or the
121
+ * service role's default one. */
122
+ async recordedModel() {
123
+ const res = await this.db.from("published_models").select("model").eq("space_id", await this.space()).maybeSingle();
124
+ if (res.error)
125
+ this.fail(res.error.message);
126
+ return res.data?.model ?? null;
127
+ }
128
+ async recordModel(model) {
129
+ const res = await this.db.from("published_models")
130
+ .upsert({ space_id: await this.space(), model, recorded_at: new Date().toISOString() }, { onConflict: "space_id" });
131
+ if (res.error)
132
+ this.fail(res.error.message);
133
+ }
134
+ /** Four head counts (no rows travel): each table's rows, and its rows at another stamp. The policies scope a
135
+ * write key to its space; the service role counts the default space's rows by the explicit filter. */
136
+ async countBehind(model) {
137
+ const space = await this.space();
138
+ const count = async (table, behind) => {
139
+ let q = this.db.from(table).select("*", { count: "exact", head: true }).eq("space_id", space);
140
+ if (behind)
141
+ q = q.neq("model", model);
142
+ const res = await q;
143
+ if (res.error)
144
+ this.fail(res.error.message);
145
+ return res.count ?? 0;
146
+ };
147
+ const [pages, entries, pagesBehind, entriesBehind] = await Promise.all([
148
+ count("published_pages", false), count("published_entries", false), count("published_pages", true), count("published_entries", true),
149
+ ]);
150
+ return { behind: pagesBehind + entriesBehind, total: pages + entries };
151
+ }
120
152
  async markStale(keys) {
121
153
  await this.eachGroup(keys, async (table, column, ids, locale) => {
122
154
  let q = this.db.from(table).update({ stale: true }).in(column, ids);
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const SMOODLY_VERSION = "0.0.46";
1
+ export declare const SMOODLY_VERSION = "0.0.47";
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.46";
2
+ export const SMOODLY_VERSION = "0.0.47";
@@ -0,0 +1,47 @@
1
+ -- Smoodly core schema, version 10 (DESIGN §3, the document stamp, decided 2026-10-08).
2
+ -- Shipped in the smoodly package; `npx smoodly migrate` copies it into supabase/migrations/.
3
+ -- Never edit a published file: the next change is the next file.
4
+ -- The delta over version 9: published_models, one row per space holding the
5
+ -- document stamp the space recorded as current, so only the deployment at that
6
+ -- stamp rewrites delivery rows (a preview deployment on a newer package serves
7
+ -- assembled documents and writes nothing). Additive: no function changes.
8
+
9
+ set client_min_messages = warning;
10
+
11
+ -- The document stamp a space recorded as current: set by `npx smoodly rebuild` and by the first writer
12
+ -- into an empty record; only a reader at this stamp rewrites published_pages and published_entries.
13
+ create table if not exists "published_models" (
14
+ "space_id" uuid primary key default '00000000-0000-0000-0000-000000000000',
15
+ "model" text not null,
16
+ "recorded_at" timestamptz not null default now()
17
+ );
18
+
19
+ alter table "published_models" enable row level security;
20
+
21
+ -- Stamped with the caller's space on insert, as every content table.
22
+ drop trigger if exists "smoodly_a_set_space" on "published_models";
23
+ create trigger "smoodly_a_set_space" before insert on "published_models"
24
+ for each row execute function "smoodly_set_space"();
25
+
26
+ -- A write key reads and writes its own space's record; no other key reads (spec 2026-09-29 content API contract §8).
27
+ drop policy if exists "smoodly_write" on "published_models";
28
+
29
+ drop policy if exists "smoodly_insert" on "published_models";
30
+ create policy "smoodly_insert" on "published_models" for insert
31
+ with check ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
32
+
33
+ drop policy if exists "smoodly_update" on "published_models";
34
+ create policy "smoodly_update" on "published_models" for update
35
+ using ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write')
36
+ with check ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
37
+
38
+ drop policy if exists "smoodly_delete" on "published_models";
39
+ create policy "smoodly_delete" on "published_models" for delete
40
+ using ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
41
+
42
+ drop policy if exists "smoodly_read" on "published_models";
43
+ create policy "smoodly_read" on "published_models" for select
44
+ using ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
45
+
46
+ insert into "smoodly_meta" ("key", "value") values ('schema_version', '10')
47
+ on conflict ("key") do update set "value" = excluded."value";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smoodly",
3
- "version": "0.0.46",
3
+ "version": "0.0.47",
4
4
  "description": "Open-source, code-first CMS for Astro and Next.js on Supabase: define a component once, it becomes a typed, editable building block.",
5
5
  "license": "MIT",
6
6
  "repository": {