smoodly 0.0.47 → 0.0.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/admin/browser/main.js +2 -0
  2. package/dist/admin/client-ops.js +1 -0
  3. package/dist/admin/delivery-mode.d.ts +5 -0
  4. package/dist/admin/delivery-mode.js +20 -0
  5. package/dist/admin/editor/EditorView.js +3 -0
  6. package/dist/admin/files.generated.js +1 -1
  7. package/dist/admin/handler.d.ts +5 -1
  8. package/dist/admin/handler.js +2 -1
  9. package/dist/admin/ops-impl.d.ts +13 -0
  10. package/dist/admin/ops-impl.js +46 -0
  11. package/dist/admin/ops.d.ts +11 -0
  12. package/dist/admin/page-data.d.ts +2 -0
  13. package/dist/admin/shell/AdminApp.js +47 -1
  14. package/dist/admin/shell/EntryForm.js +3 -0
  15. package/dist/admin/shell/GlobalForm.js +5 -1
  16. package/dist/admin/shell/IconRail.d.ts +8 -1
  17. package/dist/admin/shell/IconRail.js +8 -3
  18. package/dist/admin/shell/deploy-tile.d.ts +20 -0
  19. package/dist/admin/shell/deploy-tile.js +35 -0
  20. package/dist/admin/ui/PublishControl.js +3 -1
  21. package/dist/admin/ui/theme.js +12 -0
  22. package/dist/astro/deploy-deps.d.ts +10 -0
  23. package/dist/astro/deploy-deps.js +8 -0
  24. package/dist/astro/index.d.ts +9 -4
  25. package/dist/astro/index.js +75 -15
  26. package/dist/astro/middleware.js +4 -0
  27. package/dist/astro/routes/admin.js +2 -1
  28. package/dist/astro/routes/preview.astro +17 -5
  29. package/dist/astro/routes/static-page.astro +21 -0
  30. package/dist/astro/runtime.d.ts +6 -0
  31. package/dist/astro/runtime.js +4 -1
  32. package/dist/astro/server.d.ts +8 -0
  33. package/dist/astro/server.js +9 -1
  34. package/dist/astro/static.d.ts +39 -0
  35. package/dist/astro/static.js +63 -0
  36. package/dist/cli/load-config.d.ts +3 -0
  37. package/dist/cli/load-config.js +8 -1
  38. package/dist/deploy-state.d.ts +28 -0
  39. package/dist/deploy-state.js +31 -0
  40. package/dist/project.d.ts +3 -0
  41. package/dist/project.js +8 -5
  42. package/dist/published.d.ts +31 -4
  43. package/dist/published.js +35 -7
  44. package/dist/schema-version.d.ts +1 -1
  45. package/dist/schema-version.js +1 -1
  46. package/dist/site.js +2 -2
  47. package/dist/sql-space.d.ts +3 -2
  48. package/dist/sql-space.js +3 -2
  49. package/dist/sql.js +10 -0
  50. package/dist/supabase-published-store.d.ts +14 -3
  51. package/dist/supabase-published-store.js +72 -5
  52. package/dist/version.d.ts +1 -1
  53. package/dist/version.js +1 -1
  54. package/migrations/20261009120000_smoodly_v11.sql +49 -0
  55. package/package.json +1 -1
package/dist/published.js CHANGED
@@ -50,6 +50,8 @@ export class MemoryPublishedStore {
50
50
  this.pages = new Map();
51
51
  this.entryRows = new Map();
52
52
  this.recorded = null;
53
+ this.deploy = null;
54
+ this.removedAt = null;
53
55
  }
54
56
  async recordedModel() {
55
57
  return this.recorded;
@@ -57,27 +59,53 @@ export class MemoryPublishedStore {
57
59
  async recordModel(model) {
58
60
  this.recorded = model;
59
61
  }
62
+ async newestPublishedAt() {
63
+ const all = [...this.pages.values(), ...this.entryRows.values()].map((r) => r.publishedAt);
64
+ return all.length === 0 ? null : Math.max(...all);
65
+ }
66
+ async countPublishedAfter(since) {
67
+ return [...this.pages.values(), ...this.entryRows.values()].filter((r) => r.publishedAt > since).length;
68
+ }
69
+ async deployRequest() {
70
+ return this.deploy;
71
+ }
72
+ async requestDeploy(by, at = Date.now()) {
73
+ this.deploy = { at, by };
74
+ }
60
75
  async countBehind(model) {
61
76
  const rows = [...this.pages.values(), ...this.entryRows.values()];
62
77
  return { behind: rows.filter((r) => r.model !== model).length, total: rows.length };
63
78
  }
64
- async putPages(rows) {
79
+ async putPages(rows, options) {
65
80
  vacate(this.pages, rows);
66
- for (const row of rows)
67
- this.pages.set(keyOf(row.id, row.locale), structuredClone(row));
81
+ for (const row of rows) {
82
+ const kept = options?.keep ? this.pages.get(keyOf(row.id, row.locale))?.publishedAt : undefined;
83
+ this.pages.set(keyOf(row.id, row.locale), structuredClone(kept === undefined ? row : { ...row, publishedAt: kept }));
84
+ }
68
85
  }
69
- async putEntries(rows) {
86
+ async putEntries(rows, options) {
70
87
  vacate(this.entryRows, rows);
71
- for (const row of rows)
72
- this.entryRows.set(keyOf(row.id, row.locale), structuredClone(row));
88
+ for (const row of rows) {
89
+ const kept = options?.keep ? this.entryRows.get(keyOf(row.id, row.locale))?.publishedAt : undefined;
90
+ this.entryRows.set(keyOf(row.id, row.locale), structuredClone(kept === undefined ? row : { ...row, publishedAt: kept }));
91
+ }
73
92
  }
74
93
  async remove(keys) {
94
+ let deleted = 0;
75
95
  for (const key of keys) {
76
96
  const map = key.kind === "page" ? this.pages : this.entryRows;
77
97
  for (const [k, row] of map)
78
- if (row.id === key.id && (key.locale === undefined || row.locale === key.locale))
98
+ if (row.id === key.id && (key.locale === undefined || row.locale === key.locale)) {
79
99
  map.delete(k);
100
+ deleted += 1;
101
+ }
80
102
  }
103
+ // Only a row that went is a change (a key with no row is not): the Deploy tile counts it (spec 2026-10-09 §5).
104
+ if (deleted > 0)
105
+ this.removedAt = Date.now();
106
+ }
107
+ async lastRemovalAt() {
108
+ return this.removedAt;
81
109
  }
82
110
  async byAddress(locale, path, globals) {
83
111
  const found = () => {
@@ -1,7 +1,7 @@
1
1
  /** The minimum schema version the package needs. Moves ONLY in a release
2
2
  * that adds a table, column, function or policy the package reads; a
3
3
  * unit test holds it equal to the highest file in `migrations/`. */
4
- export declare const SCHEMA_VERSION = 10;
4
+ export declare const SCHEMA_VERSION = 11;
5
5
  export declare const SCHEMA_VERSION_KEY = "schema_version";
6
6
  export declare const CORE_FILE_RE: RegExp;
7
7
  export declare const VIEWS_FILE_RE: RegExp;
@@ -7,7 +7,7 @@
7
7
  /** The minimum schema version the package needs. Moves ONLY in a release
8
8
  * that adds a table, column, function or policy the package reads; a
9
9
  * unit test holds it equal to the highest file in `migrations/`. */
10
- export const SCHEMA_VERSION = 10;
10
+ export const SCHEMA_VERSION = 11;
11
11
  export const SCHEMA_VERSION_KEY = "schema_version";
12
12
  export const CORE_FILE_RE = /^(\d{14})_smoodly_v(\d+)\.sql$/;
13
13
  export const VIEWS_FILE_RE = /^(\d{14})_smoodly_views\.sql$/;
package/dist/site.js CHANGED
@@ -109,7 +109,7 @@ export function createSmoodlySite(options) {
109
109
  return (await assembler.entries(collection, { ...o, locale, draft: false })).map(assembledEntry(collection, locale));
110
110
  // A failed rebuild never fails the read: the rows as read are served, and the next read tries again.
111
111
  try {
112
- await projector.entries(behind, { cap: Infinity });
112
+ await projector.entries(behind, { cap: Infinity, stamp: false });
113
113
  rows = await read();
114
114
  }
115
115
  catch (e) {
@@ -139,7 +139,7 @@ export function createSmoodlySite(options) {
139
139
  // One batched projection for every row behind (one tree read in the projector), then one more tree read.
140
140
  // A failed rebuild never fails the read: the rows as read are served, and the next read tries again.
141
141
  try {
142
- await projector.pages(behind, { cap: Infinity });
142
+ await projector.pages(behind, { cap: Infinity, stamp: false });
143
143
  rows = await published().tree(locale);
144
144
  }
145
145
  catch (e) {
@@ -9,8 +9,9 @@ export type ContentTable = (typeof CONTENT_TABLES)[number];
9
9
  /** The delivery model's derived tables (spec 2026-10-07 §1): policy-covered and
10
10
  * space-stamped like content, but not transferred (export skips them, import
11
11
  * rebuilds them) and not counted toward the row cap, since they are derived.
12
- * `published_models` (schema v10) holds one row per space: the document stamp the space recorded as current. */
13
- export declare const DELIVERY_TABLES: readonly ["published_pages", "published_entries", "published_models"];
12
+ * `published_models` (schema v10) holds one row per space: the document stamp the space recorded as current;
13
+ * `space_deploys` (schema v11) one row per space: the last Deploy press of static mode. */
14
+ export declare const DELIVERY_TABLES: readonly ["published_pages", "published_entries", "published_models", "space_deploys"];
14
15
  export declare function currentSpaceSQL(): string;
15
16
  /** One permissive policy per action (spec 2026-09-29 pre-install fixes §3):
16
17
  * the write key's insert, update and delete, each alone for its action; its
package/dist/sql-space.js CHANGED
@@ -22,8 +22,9 @@ export const CONTENT_TABLES = [
22
22
  /** The delivery model's derived tables (spec 2026-10-07 §1): policy-covered and
23
23
  * space-stamped like content, but not transferred (export skips them, import
24
24
  * rebuilds them) and not counted toward the row cap, since they are derived.
25
- * `published_models` (schema v10) holds one row per space: the document stamp the space recorded as current. */
26
- export const DELIVERY_TABLES = ["published_pages", "published_entries", "published_models"];
25
+ * `published_models` (schema v10) holds one row per space: the document stamp the space recorded as current;
26
+ * `space_deploys` (schema v11) one row per space: the last Deploy press of static mode. */
27
+ export const DELIVERY_TABLES = ["published_pages", "published_entries", "published_models", "space_deploys"];
27
28
  export function currentSpaceSQL() {
28
29
  return `
29
30
  -- The space a request acts in: the key's claim in Smoodly Cloud, the
package/dist/sql.js CHANGED
@@ -181,9 +181,19 @@ create table if not exists "published_models" (
181
181
  "recorded_at" timestamptz not null default now()
182
182
  );
183
183
 
184
+ -- The last Deploy press of a space (spec 2026-10-09 static mode §7, schema v11): who pressed and when;
185
+ -- the admin compares it with the build marker the live site serves.
186
+ create table if not exists "space_deploys" (
187
+ "space_id" uuid primary key default ${DEFAULT_SPACE},
188
+ "requested_at" timestamptz,
189
+ "requested_by" text,
190
+ "removed_at" timestamptz
191
+ );
192
+
184
193
  alter table "published_pages" enable row level security;
185
194
  alter table "published_entries" enable row level security;
186
195
  alter table "published_models" enable row level security;
196
+ alter table "space_deploys" enable row level security;
187
197
 
188
198
  -- smoodly_list_entries without the draft branch, over the delivery table (spec 2026-10-07 §3):
189
199
  -- \`where\` and \`order\` read the raw published snapshot, the row answers with its document.
@@ -1,6 +1,6 @@
1
1
  import type { SupabaseClient } from "@supabase/supabase-js";
2
2
  import type { CollectionSchema } from "./collections.ts";
3
- import type { PublishedEntryRow, PublishedKey, PublishedPageRow, PublishedQuery, PublishedRoute, PublishedStore, PublishedTreeRow } from "./published.ts";
3
+ import type { PublishedEntryRow, PublishedKey, PublishedPageRow, PublishedQuery, PublishedRoute, PublishedStore, PublishedTreeRow, PutOptions } from "./published.ts";
4
4
  export declare class SupabasePublishedStore implements PublishedStore {
5
5
  private db;
6
6
  private schemas;
@@ -8,8 +8,8 @@ export declare class SupabasePublishedStore implements PublishedStore {
8
8
  /** `collections` encode a `where` on an array field as containment, as SupabaseEntryStore.query does. */
9
9
  constructor(db: SupabaseClient, collections?: CollectionSchema<any>[]);
10
10
  private fail;
11
- putPages(rows: PublishedPageRow[]): Promise<void>;
12
- putEntries(rows: PublishedEntryRow[]): Promise<void>;
11
+ putPages(rows: PublishedPageRow[], options?: PutOptions): Promise<void>;
12
+ putEntries(rows: PublishedEntryRow[], options?: PutOptions): Promise<void>;
13
13
  /** The space this client writes in (the service role's is the default): a path delete must not reach past it,
14
14
  * since the service role passes every policy. Cached for the life of the client; a failed lookup is not. */
15
15
  private space;
@@ -20,6 +20,7 @@ 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
+ lastRemovalAt(): Promise<number | null>;
23
24
  /** The space's recorded stamp (DESIGN §3): one row per space in published_models; the key's space, or the
24
25
  * service role's default one. */
25
26
  recordedModel(): Promise<string | null>;
@@ -30,6 +31,16 @@ export declare class SupabasePublishedStore implements PublishedStore {
30
31
  behind: number;
31
32
  total: number;
32
33
  }>;
34
+ /** The static build's watermark (spec 2026-10-09 static mode §5): the newest published_at over both tables. */
35
+ newestPublishedAt(): Promise<number | null>;
36
+ /** Rows published strictly after `since`: the Deploy tile's "changes waiting". Two head counts. */
37
+ countPublishedAfter(since: number): Promise<number>;
38
+ /** The space's last Deploy press, one row in space_deploys (schema v11). */
39
+ deployRequest(): Promise<{
40
+ at: number;
41
+ by: string | null;
42
+ } | null>;
43
+ requestDeploy(by: string | null, at?: number): Promise<void>;
33
44
  markStale(keys: PublishedKey[]): Promise<void>;
34
45
  /** A route row is for serving, not for the tree: a page route carries the
35
46
  * document, model, stale flag and tags; its tree columns are empty, and an
@@ -13,6 +13,13 @@ const fromPage = (r) => ({
13
13
  id: r.page_id, locale: r.locale, parentId: r.parent_id, sort: num(r.sort), path: r.path, title: r.title, hasPage: r.has_page,
14
14
  linkFields: r.link_fields, document: r.document, tags: r.tags, model: r.model, stale: r.stale, publishedAt: Date.parse(r.published_at),
15
15
  });
16
+ /** `keep` strips published_at from an upsert's payload (PutOptions): the row keeps its own, a new row is dated by the default. */
17
+ function keeping(row, options) {
18
+ if (!options?.keep)
19
+ return row;
20
+ const { published_at: _at, ...rest } = row;
21
+ return rest;
22
+ }
16
23
  const toPage = (r) => ({
17
24
  page_id: r.id, locale: r.locale, parent_id: r.parentId, sort: r.sort, path: r.path, title: r.title, has_page: r.hasPage,
18
25
  link_fields: r.linkFields, document: r.document, tags: r.tags, model: r.model, stale: r.stale, published_at: iso(r.publishedAt),
@@ -36,20 +43,21 @@ export class SupabasePublishedStore {
36
43
  fail(message) {
37
44
  throw new Error(`smoodly: published store: ${message}`);
38
45
  }
39
- async putPages(rows) {
46
+ async putPages(rows, options) {
40
47
  for (let i = 0; i < rows.length; i += CHUNK) {
41
48
  const chunk = rows.slice(i, i + CHUNK);
42
49
  await this.vacate("published_pages", "page_id", chunk);
43
- const res = await this.db.from("published_pages").upsert(chunk.map(toPage), { onConflict: "page_id,locale" });
50
+ // `keep`: published_at leaves the payload, so an updated row keeps its own and an inserted one takes the default (now).
51
+ const res = await this.db.from("published_pages").upsert(chunk.map((r) => keeping(toPage(r), options)), { onConflict: "page_id,locale" });
44
52
  if (res.error)
45
53
  this.fail(res.error.message);
46
54
  }
47
55
  }
48
- async putEntries(rows) {
56
+ async putEntries(rows, options) {
49
57
  for (let i = 0; i < rows.length; i += CHUNK) {
50
58
  const chunk = rows.slice(i, i + CHUNK);
51
59
  await this.vacate("published_entries", "entry_id", chunk);
52
- const res = await this.db.from("published_entries").upsert(chunk.map(toEntry), { onConflict: "entry_id,locale" });
60
+ const res = await this.db.from("published_entries").upsert(chunk.map((r) => keeping(toEntry(r), options)), { onConflict: "entry_id,locale" });
53
61
  if (res.error)
54
62
  this.fail(res.error.message);
55
63
  }
@@ -108,14 +116,33 @@ export class SupabasePublishedStore {
108
116
  }
109
117
  }
110
118
  async remove(keys) {
119
+ if (keys.length === 0)
120
+ return;
121
+ let deleted = 0;
111
122
  await this.eachGroup(keys, async (table, column, ids, locale) => {
112
- let q = this.db.from(table).delete().in(column, ids);
123
+ let q = this.db.from(table).delete({ count: "exact" }).in(column, ids);
113
124
  if (locale !== undefined)
114
125
  q = q.eq("locale", locale);
115
126
  const res = await q;
116
127
  if (res.error)
117
128
  this.fail(res.error.message);
129
+ deleted += res.count ?? 0;
118
130
  });
131
+ // A row that went is a change the count of newer rows cannot see (spec 2026-10-09 static mode §5): the space's
132
+ // row keeps when one last happened. A key with no row (a publish vacating a locale that never had one) is no
133
+ // change. The press's columns are absent from the payload, so they stay as they are.
134
+ if (deleted === 0)
135
+ return;
136
+ const res = await this.db.from("space_deploys").upsert({ space_id: await this.space(), removed_at: new Date().toISOString() }, { onConflict: "space_id" });
137
+ if (res.error)
138
+ this.fail(res.error.message);
139
+ }
140
+ async lastRemovalAt() {
141
+ const res = await this.db.from("space_deploys").select("removed_at").eq("space_id", await this.space()).maybeSingle();
142
+ if (res.error)
143
+ this.fail(res.error.message);
144
+ const at = res.data?.removed_at;
145
+ return at ? Date.parse(at) : null;
119
146
  }
120
147
  /** The space's recorded stamp (DESIGN §3): one row per space in published_models; the key's space, or the
121
148
  * service role's default one. */
@@ -149,6 +176,46 @@ export class SupabasePublishedStore {
149
176
  ]);
150
177
  return { behind: pagesBehind + entriesBehind, total: pages + entries };
151
178
  }
179
+ /** The static build's watermark (spec 2026-10-09 static mode §5): the newest published_at over both tables. */
180
+ async newestPublishedAt() {
181
+ const space = await this.space();
182
+ const latest = async (table) => {
183
+ const res = await this.db.from(table).select("published_at").eq("space_id", space).order("published_at", { ascending: false }).limit(1).maybeSingle();
184
+ if (res.error)
185
+ this.fail(res.error.message);
186
+ const at = res.data?.published_at;
187
+ return at ? Date.parse(at) : null;
188
+ };
189
+ const [p, e] = await Promise.all([latest("published_pages"), latest("published_entries")]);
190
+ return p === null && e === null ? null : Math.max(p ?? 0, e ?? 0);
191
+ }
192
+ /** Rows published strictly after `since`: the Deploy tile's "changes waiting". Two head counts. */
193
+ async countPublishedAfter(since) {
194
+ const space = await this.space();
195
+ const count = async (table) => {
196
+ const res = await this.db.from(table).select("*", { count: "exact", head: true }).eq("space_id", space).gt("published_at", new Date(since).toISOString());
197
+ if (res.error)
198
+ this.fail(res.error.message);
199
+ return res.count ?? 0;
200
+ };
201
+ const [p, e] = await Promise.all([count("published_pages"), count("published_entries")]);
202
+ return p + e;
203
+ }
204
+ /** The space's last Deploy press, one row in space_deploys (schema v11). */
205
+ async deployRequest() {
206
+ const res = await this.db.from("space_deploys").select("requested_at, requested_by").eq("space_id", await this.space()).maybeSingle();
207
+ if (res.error)
208
+ this.fail(res.error.message);
209
+ // A removal may have made the row before any press: no requested_at is no press.
210
+ const row = res.data;
211
+ return row?.requested_at ? { at: Date.parse(row.requested_at), by: row.requested_by } : null;
212
+ }
213
+ async requestDeploy(by, at = Date.now()) {
214
+ const res = await this.db.from("space_deploys")
215
+ .upsert({ space_id: await this.space(), requested_at: new Date(at).toISOString(), requested_by: by }, { onConflict: "space_id" });
216
+ if (res.error)
217
+ this.fail(res.error.message);
218
+ }
152
219
  async markStale(keys) {
153
220
  await this.eachGroup(keys, async (table, column, ids, locale) => {
154
221
  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.47";
1
+ export declare const SMOODLY_VERSION = "0.0.49";
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.47";
2
+ export const SMOODLY_VERSION = "0.0.49";
@@ -0,0 +1,49 @@
1
+ -- Smoodly core schema, version 11 (DESIGN §3, static mode, decided 2026-10-09).
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 10: space_deploys, one row per space holding the last
5
+ -- Deploy press (static mode's button), so a serverless function can tell a
6
+ -- build in flight from one that landed. Additive: no function changes.
7
+
8
+ set client_min_messages = warning;
9
+
10
+ -- The last Deploy press of a space (spec 2026-10-09 static mode §7): who pressed and when; the admin
11
+ -- compares it with the build marker the live site serves to show building, landed or not landed.
12
+ create table if not exists "space_deploys" (
13
+ "space_id" uuid primary key default '00000000-0000-0000-0000-000000000000',
14
+ -- Null until the first press: a removal may make the row first.
15
+ "requested_at" timestamptz,
16
+ "requested_by" text,
17
+ -- When a delivery row last went (an unpublish, a delete): a change the count of newer rows cannot see.
18
+ "removed_at" timestamptz
19
+ );
20
+
21
+ alter table "space_deploys" enable row level security;
22
+
23
+ -- Stamped with the caller's space on insert, as every content table.
24
+ drop trigger if exists "smoodly_a_set_space" on "space_deploys";
25
+ create trigger "smoodly_a_set_space" before insert on "space_deploys"
26
+ for each row execute function "smoodly_set_space"();
27
+
28
+ -- A write key reads and writes its own space's row; no other key reads (spec 2026-09-29 content API contract §8).
29
+ drop policy if exists "smoodly_write" on "space_deploys";
30
+
31
+ drop policy if exists "smoodly_insert" on "space_deploys";
32
+ create policy "smoodly_insert" on "space_deploys" for insert
33
+ with check ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
34
+
35
+ drop policy if exists "smoodly_update" on "space_deploys";
36
+ create policy "smoodly_update" on "space_deploys" for update
37
+ using ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write')
38
+ with check ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
39
+
40
+ drop policy if exists "smoodly_delete" on "space_deploys";
41
+ create policy "smoodly_delete" on "space_deploys" for delete
42
+ using ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
43
+
44
+ drop policy if exists "smoodly_read" on "space_deploys";
45
+ create policy "smoodly_read" on "space_deploys" for select
46
+ using ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
47
+
48
+ insert into "smoodly_meta" ("key", "value") values ('schema_version', '11')
49
+ 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.47",
3
+ "version": "0.0.49",
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": {