smoodly 0.0.34 → 0.0.36
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 +807 -1326
- package/dist/admin/drift.js +3 -2
- package/dist/admin/editor/EditorView.js +10 -3
- package/dist/admin/editor/SeoSettings.d.ts +7 -1
- package/dist/admin/editor/SeoSettings.js +2 -2
- package/dist/admin/editor/autosave.d.ts +6 -1
- package/dist/admin/editor/autosave.js +43 -3
- package/dist/admin/field-label.d.ts +2 -0
- package/dist/admin/field-label.js +2 -0
- package/dist/admin/forms/FieldWidget.js +2 -2
- package/dist/admin/forms/list.js +10 -1
- package/dist/admin/next/create-admin.d.ts +4 -4
- package/dist/admin/next/create-admin.js +5 -5
- package/dist/admin/next/index.d.ts +1 -1
- package/dist/admin/serialize.d.ts +8 -1
- package/dist/admin/serialize.js +23 -7
- package/dist/admin/shell/AdminApp.d.ts +3 -3
- package/dist/admin/shell/AdminApp.js +2 -2
- package/dist/admin/shell/EntryForm.js +11 -3
- package/dist/admin/shell/GlobalForm.js +3 -2
- package/dist/admin/shell/presence-realtime.d.ts +2 -2
- package/dist/admin/shell/router.d.ts +5 -0
- package/dist/admin/shell/router.js +13 -0
- package/dist/cli/space-io.js +1 -1
- package/dist/collections.d.ts +4 -0
- package/dist/collections.js +9 -1
- package/dist/entry-page.d.ts +12 -10
- package/dist/entry-store.d.ts +29 -0
- package/dist/entry-store.js +53 -0
- package/dist/env.d.ts +5 -5
- package/dist/env.js +2 -2
- package/dist/fields.d.ts +13 -7
- package/dist/fields.js +2 -2
- package/dist/global.d.ts +4 -7
- package/dist/index.d.ts +10 -39
- package/dist/index.js +1 -1
- package/dist/migrations.js +1 -1
- package/dist/next/canvas-slot.d.ts +7 -0
- package/dist/next/canvas-slot.js +9 -0
- package/dist/next/index.d.ts +3 -1
- package/dist/next/index.js +3 -1
- package/dist/next/meta.d.ts +6 -4
- package/dist/next/meta.js +14 -17
- package/dist/next/page-renderer.d.ts +2 -0
- package/dist/next/page-renderer.js +18 -11
- package/dist/next/preview-pill.d.ts +9 -0
- package/dist/next/preview-pill.js +56 -0
- package/dist/next/preview-route.d.ts +1 -0
- package/dist/next/preview-route.js +30 -0
- package/dist/next/queries.d.ts +10 -11
- package/dist/next/queries.js +7 -2
- package/dist/page.d.ts +5 -2
- package/dist/page.js +3 -1
- package/dist/pairing.d.ts +10 -10
- package/dist/path-index.d.ts +3 -0
- package/dist/path-index.js +7 -0
- package/dist/read-shapes.d.ts +64 -0
- package/dist/read-shapes.js +52 -0
- package/dist/render.d.ts +3 -3
- package/dist/resolve.d.ts +12 -0
- package/dist/resolve.js +29 -7
- package/dist/schema-version.d.ts +1 -1
- package/dist/schema-version.js +1 -1
- package/dist/schema.d.ts +22 -1
- package/dist/seo.d.ts +18 -0
- package/dist/seo.js +53 -0
- package/dist/site.d.ts +15 -16
- package/dist/site.js +64 -58
- package/dist/sql-space.d.ts +15 -0
- package/dist/sql-space.js +54 -39
- package/dist/sql.d.ts +11 -1
- package/dist/sql.js +78 -25
- package/dist/supabase-entry-store.d.ts +3 -1
- package/dist/supabase-entry-store.js +39 -0
- package/dist/supabase-path-index.d.ts +1 -0
- package/dist/supabase-path-index.js +16 -0
- package/migrations/20260929135523_smoodly_v6.sql +200 -0
- package/migrations/20260930040956_smoodly_v7.sql +84 -0
- package/package.json +1 -5
package/dist/sql.js
CHANGED
|
@@ -1,23 +1,18 @@
|
|
|
1
1
|
// Schema → SQL generation (Postgres/Supabase) for the stable schema
|
|
2
2
|
// (DESIGN.md §3): all collection content lives in the one `entries`
|
|
3
3
|
// table, applied once and never migrated. Per collection we emit only
|
|
4
|
-
// disposable
|
|
4
|
+
// a disposable guardrail — a published-only read view, created with
|
|
5
5
|
// security_invoker so it runs under the caller's own RLS policies
|
|
6
|
-
// rather than the view owner's privileges (
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
// is the `paths` primary key plus the stores' per-locale pre-check,
|
|
10
|
-
// not an index). A view joins the locale rows, one row per published
|
|
6
|
+
// rather than the view owner's privileges (SQL-queryable eject story;
|
|
7
|
+
// sibling slug uniqueness is the `paths` primary key plus the stores'
|
|
8
|
+
// per-locale pre-check, not an index). A view joins the locale rows, one row per published
|
|
11
9
|
// locale (spec 2026-09-06 §7); a versioned collection reads its
|
|
12
10
|
// fields from the published snapshot.
|
|
13
|
-
import {
|
|
11
|
+
import { SEO_KEY } from "./seo.js";
|
|
14
12
|
import { currentSpaceSQL, grantsSQL, SPACE_COL, spaceLayerSQL } from "./sql-space.js";
|
|
15
13
|
const snake = (s) => s.replace(/[A-Z]/g, (c) => "_" + c.toLowerCase());
|
|
16
14
|
const q = (s) => `"${s}"`;
|
|
17
|
-
// Casts are fine in views
|
|
18
|
-
// immutable — the order field's index stays on the raw jsonb
|
|
19
|
-
// expression: jsonb ordering compares numbers numerically and strings,
|
|
20
|
-
// ISO dates included, alphabetically, matching the memory comparator).
|
|
15
|
+
// Casts are fine in views.
|
|
21
16
|
function fieldExpr(key, d, source) {
|
|
22
17
|
switch (d.type) {
|
|
23
18
|
case "text":
|
|
@@ -45,6 +40,8 @@ export function collectionSQL(collection) {
|
|
|
45
40
|
for (const [key, d] of Object.entries(collection.fields)) {
|
|
46
41
|
if (key === "slug")
|
|
47
42
|
continue; // the segment is the row's column, never a field
|
|
43
|
+
if (key === SEO_KEY)
|
|
44
|
+
continue; // the panel's values are Smoodly-internal, not a view column
|
|
48
45
|
columns.push(`${fieldExpr(key, d, source)} as ${q(snake(key))}`);
|
|
49
46
|
}
|
|
50
47
|
const statements = [
|
|
@@ -58,17 +55,71 @@ export function collectionSQL(collection) {
|
|
|
58
55
|
`where e."collection" = '${name}' and l."status" = 'published';`,
|
|
59
56
|
].join("\n"),
|
|
60
57
|
];
|
|
61
|
-
//
|
|
62
|
-
// on the node's shared fields (an order field can be neither localized
|
|
63
|
-
// nor `slug`, which collection() refuses), the jsonb value (`->`) the
|
|
64
|
-
// adapter orders by, so the index serves the sort.
|
|
65
|
-
const order = collection.order;
|
|
66
|
-
if (order !== undefined && order !== "manual" && !BUILT_IN_ORDER_FIELDS.includes(order.by)) {
|
|
67
|
-
statements.push(`create index if not exists ${q(`entries_${name}_${snake(order.by)}_idx`)}\n` +
|
|
68
|
-
` on "entries" (("fields"->'${order.by}')) where "collection" = '${name}';`);
|
|
69
|
-
}
|
|
58
|
+
// Ordering reads the shown snapshot (smoodly_list_entries), so no index on the live row serves it (spec 2026-09-29 content API contract §5).
|
|
70
59
|
return statements.join("\n\n");
|
|
71
60
|
}
|
|
61
|
+
/** Foreign keys the planner needs an index for (spec 2026-09-29 pre-install
|
|
62
|
+
* fixes §3): a restrict or set-null check scans the referencing table
|
|
63
|
+
* without one, and the versions' read policies probe published_version_id
|
|
64
|
+
* on every public read. The composite indexes that exist lead with space_id. */
|
|
65
|
+
export function foreignKeyIndexesSQL() {
|
|
66
|
+
return [
|
|
67
|
+
`create index if not exists "page_locales_draft_version_idx" on "page_locales" ("draft_version_id");`,
|
|
68
|
+
`create index if not exists "entry_locales_draft_version_idx" on "entry_locales" ("draft_version_id");`,
|
|
69
|
+
`create index if not exists "page_locales_published_version_idx" on "page_locales" ("published_version_id");`,
|
|
70
|
+
`create index if not exists "entry_locales_published_version_idx" on "entry_locales" ("published_version_id");`,
|
|
71
|
+
`create index if not exists "pages_parent_id_idx" on "pages" ("parent_id");`,
|
|
72
|
+
`create index if not exists "entries_parent_id_idx" on "entries" ("parent_id");`,
|
|
73
|
+
`create index if not exists "assets_poster_idx" on "assets" ("poster_id");`,
|
|
74
|
+
].join("\n");
|
|
75
|
+
}
|
|
76
|
+
/** The shown snapshot of one collection's entries in a locale, filtered,
|
|
77
|
+
* ordered and limited in one query (spec 2026-09-29 content API contract
|
|
78
|
+
* §5). Invoker rights, so RLS decides what a key sees. Defined after the
|
|
79
|
+
* space layer, whose `smoodly_key_kind` it calls. */
|
|
80
|
+
export function listEntriesSQL() {
|
|
81
|
+
return `
|
|
82
|
+
-- The shown snapshot of one collection's entries in a locale, filtered,
|
|
83
|
+
-- ordered and limited in one query (spec 2026-09-29 content API contract
|
|
84
|
+
-- §5): the published snapshot, or the live merge in preview. Invoker
|
|
85
|
+
-- rights, so RLS decides what a key sees; a preview read needs the
|
|
86
|
+
-- service role or a write key, anything else reads published.
|
|
87
|
+
create or replace function "smoodly_list_entries"(
|
|
88
|
+
p_collection text, p_locale text, p_draft boolean, p_where jsonb, p_order jsonb,
|
|
89
|
+
p_limit integer, p_parent uuid, p_has_parent boolean, p_ids uuid[]
|
|
90
|
+
) returns table ("id" uuid, "fields" jsonb) language sql stable set search_path = public as $$
|
|
91
|
+
with mode as (
|
|
92
|
+
select p_draft and (current_user = 'service_role' or "smoodly_key_kind"() = 'write') as draft
|
|
93
|
+
), shown as (
|
|
94
|
+
select e."id", e."sort", e."created_at", l."updated_at",
|
|
95
|
+
case when (select draft from mode) then e."fields" || l."fields" || jsonb_build_object('slug', l."slug")
|
|
96
|
+
else coalesce(v."fields", e."fields" || l."fields" || jsonb_build_object('slug', l."slug")) end as "fields"
|
|
97
|
+
from "entries" e
|
|
98
|
+
join "entry_locales" l on l."entry_id" = e."id" and l."locale" = p_locale
|
|
99
|
+
left join "entry_versions" v on v."id" = l."published_version_id"
|
|
100
|
+
where e."collection" = p_collection
|
|
101
|
+
and ((select draft from mode) or l."status" = 'published')
|
|
102
|
+
and (not p_has_parent or e."parent_id" is not distinct from p_parent)
|
|
103
|
+
and (p_ids is null or e."id" = any(p_ids))
|
|
104
|
+
)
|
|
105
|
+
select s."id", s."fields" from shown s
|
|
106
|
+
where not exists (
|
|
107
|
+
select 1 from jsonb_each(coalesce(p_where, '{}'::jsonb)) w
|
|
108
|
+
where not (case when w.value ? 'in'
|
|
109
|
+
then (s."fields" ->> w.key) = any (select jsonb_array_elements_text(w.value -> 'in'))
|
|
110
|
+
else s."fields" @> jsonb_build_object(w.key, w.value -> 'contains') end))
|
|
111
|
+
order by
|
|
112
|
+
case when p_order ->> 'by' = 'manual' then s."sort" end asc nulls last,
|
|
113
|
+
case when p_order ->> 'direction' = 'asc' then
|
|
114
|
+
case p_order ->> 'by' when 'createdAt' then to_jsonb(s."created_at") when 'updatedAt' then to_jsonb(s."updated_at")
|
|
115
|
+
when 'manual' then null else nullif(nullif(s."fields" -> (p_order ->> 'by'), 'null'::jsonb), '""'::jsonb) end end asc nulls last,
|
|
116
|
+
case when p_order ->> 'direction' = 'desc' then
|
|
117
|
+
case p_order ->> 'by' when 'createdAt' then to_jsonb(s."created_at") when 'updatedAt' then to_jsonb(s."updated_at")
|
|
118
|
+
when 'manual' then null else nullif(nullif(s."fields" -> (p_order ->> 'by'), 'null'::jsonb), '""'::jsonb) end end desc nulls last,
|
|
119
|
+
s."created_at" asc, s."id" asc
|
|
120
|
+
limit p_limit;
|
|
121
|
+
$$;`.trim();
|
|
122
|
+
}
|
|
72
123
|
export function coreTablesSQL() {
|
|
73
124
|
return [
|
|
74
125
|
// The trigger/policy blocks below drop-before-create so the migration is
|
|
@@ -332,13 +383,14 @@ create or replace function "smoodly_resolve_path"(p_locale text, p_keys jsonb) r
|
|
|
332
383
|
select p.* from "paths" p
|
|
333
384
|
join jsonb_to_recordset(p_keys) as k(anchor text, path text) on p."anchor" = k.anchor and p."path" = k.path
|
|
334
385
|
where p."locale" = p_locale;
|
|
335
|
-
|
|
336
|
-
|
|
386
|
+
$$;`.trim(),
|
|
387
|
+
foreignKeyIndexesSQL(),
|
|
388
|
+
`
|
|
337
389
|
-- RLS on every core table. The service role bypasses it (self-host
|
|
338
390
|
-- adapters untouched). The policy set that follows (space layer) admits
|
|
339
|
-
-- a key to its own space
|
|
340
|
-
--
|
|
341
|
-
--
|
|
391
|
+
-- a write key to its own space and no other key to any content (spec
|
|
392
|
+
-- 2026-09-29 content API contract §8): the publishable key serves
|
|
393
|
+
-- sign-in and presence, and the generated per-collection views
|
|
342
394
|
-- (collectionSQL) run with security_invoker = true, so they are subject
|
|
343
395
|
-- to this same smoodly_read policy rather than their owner's privileges.
|
|
344
396
|
-- smoodly_meta: which version of this schema the database holds (spec
|
|
@@ -362,6 +414,7 @@ alter table "assets" enable row level security;
|
|
|
362
414
|
alter table "smoodly_meta" enable row level security;
|
|
363
415
|
`.trim(),
|
|
364
416
|
spaceLayerSQL(),
|
|
417
|
+
listEntriesSQL(),
|
|
365
418
|
grantsSQL(),
|
|
366
419
|
].join("\n\n");
|
|
367
420
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { SupabaseClient } from "@supabase/supabase-js";
|
|
2
2
|
import type { CollectionSchema } from "./collections.ts";
|
|
3
|
-
import type { AddEntryLocaleOptions, CreateEntryOptions, EntryRecord, EntryStore, EntryStoreOptions, EntryScan, EntryUsage, EntryVersion, ListOptions } from "./entry-store.ts";
|
|
3
|
+
import type { AddEntryLocaleOptions, CreateEntryOptions, EntryRecord, EntryStore, EntryStoreOptions, EntryScan, EntryUsage, EntryVersion, ListOptions, SnapshotQuery, SnapshotRow } from "./entry-store.ts";
|
|
4
4
|
import { type DraftToken } from "./draft-token.ts";
|
|
5
5
|
export declare class SupabaseEntryStore implements EntryStore {
|
|
6
6
|
private db;
|
|
@@ -57,6 +57,7 @@ export declare class SupabaseEntryStore implements EntryStore {
|
|
|
57
57
|
create(collection: string, fields: Record<string, unknown>, options: CreateEntryOptions, by?: string): Promise<EntryRecord>;
|
|
58
58
|
get(collection: string, id: string): Promise<EntryRecord | null>;
|
|
59
59
|
list(collection: string, options?: ListOptions): Promise<EntryRecord[]>;
|
|
60
|
+
query(collection: string, q: SnapshotQuery): Promise<SnapshotRow[]>;
|
|
60
61
|
getMany(collection: string, ids: string[], locale: string, options?: {
|
|
61
62
|
status?: "published";
|
|
62
63
|
}): Promise<Record<string, Record<string, unknown>>>;
|
|
@@ -79,6 +80,7 @@ export declare class SupabaseEntryStore implements EntryStore {
|
|
|
79
80
|
}): Promise<EntryRecord>;
|
|
80
81
|
delete(collection: string, id: string): Promise<void>;
|
|
81
82
|
resolvePath(locale: string, path: string): Promise<import("./path-index.ts").PathHit | null>;
|
|
83
|
+
pathsIn(collection: string, ids: string[], locale: string): Promise<Record<string, string>>;
|
|
82
84
|
pathsOf(collection: string, id: string): Promise<Record<string, string>>;
|
|
83
85
|
redirectsOf(collection: string, id: string): Promise<Record<string, string[]>>;
|
|
84
86
|
reorder(collection: string, orderedIds: string[]): Promise<void>;
|
|
@@ -433,6 +433,41 @@ export class SupabaseEntryStore {
|
|
|
433
433
|
this.fail(res.error.message);
|
|
434
434
|
return res.data.map(({ gate: _gate, ...row }) => toRecord(row));
|
|
435
435
|
}
|
|
436
|
+
async query(collection, q) {
|
|
437
|
+
const schema = this.schema(collection);
|
|
438
|
+
const where = {};
|
|
439
|
+
for (const [key, want] of Object.entries(q.where ?? {})) {
|
|
440
|
+
const inArray = ARRAY_FIELD_TYPES.has(schema.fields[key]?.type ?? "");
|
|
441
|
+
where[key] = Array.isArray(want) ? { in: want.map(String) } : { contains: inArray ? [want] : want };
|
|
442
|
+
}
|
|
443
|
+
const order = q.order ?? collectionOrder(schema);
|
|
444
|
+
const res = await this.db.rpc("smoodly_list_entries", {
|
|
445
|
+
p_collection: collection,
|
|
446
|
+
p_locale: q.locale,
|
|
447
|
+
p_draft: q.draft,
|
|
448
|
+
p_where: where,
|
|
449
|
+
p_order: order === "manual" ? { by: "manual" } : order,
|
|
450
|
+
p_limit: q.limit ?? null,
|
|
451
|
+
p_parent: q.parent ?? null,
|
|
452
|
+
p_has_parent: q.parent !== undefined,
|
|
453
|
+
p_ids: q.ids ? q.ids.filter((id) => UUID_RE.test(id)) : null,
|
|
454
|
+
});
|
|
455
|
+
if (res.error)
|
|
456
|
+
this.fail(res.error.message);
|
|
457
|
+
const shown = res.data;
|
|
458
|
+
if (shown.length === 0)
|
|
459
|
+
return [];
|
|
460
|
+
// In chunks: one in() over hundreds of ids would overflow the request URL.
|
|
461
|
+
const byId = new Map();
|
|
462
|
+
for (let i = 0; i < shown.length; i += 100) {
|
|
463
|
+
const recs = await this.db.from("entries").select(SELECT).in("id", shown.slice(i, i + 100).map((r) => r.id));
|
|
464
|
+
if (recs.error)
|
|
465
|
+
this.fail(recs.error.message);
|
|
466
|
+
for (const r of recs.data)
|
|
467
|
+
byId.set(r.id, toRecord(r));
|
|
468
|
+
}
|
|
469
|
+
return shown.flatMap((r) => (byId.has(r.id) ? [{ record: byId.get(r.id), fields: r.fields }] : []));
|
|
470
|
+
}
|
|
436
471
|
async getMany(collection, ids, locale, options) {
|
|
437
472
|
this.schema(collection);
|
|
438
473
|
const uuidIds = ids.filter((id) => UUID_RE.test(id));
|
|
@@ -728,6 +763,10 @@ export class SupabaseEntryStore {
|
|
|
728
763
|
resolvePath(locale, path) {
|
|
729
764
|
return this.paths.resolve(locale, path);
|
|
730
765
|
}
|
|
766
|
+
pathsIn(collection, ids, locale) {
|
|
767
|
+
this.schema(collection);
|
|
768
|
+
return this.paths.livePathsIn("entry", ids, locale);
|
|
769
|
+
}
|
|
731
770
|
async pathsOf(collection, id) {
|
|
732
771
|
return (await this.fetch(collection, id)) ? this.paths.pathsOf({ kind: "entry", id }) : {};
|
|
733
772
|
}
|
|
@@ -19,6 +19,7 @@ export declare class SupabasePathIndex implements PathIndex {
|
|
|
19
19
|
/** A locale's rows, sorted by URL in JS: the stored path is not the URL. */
|
|
20
20
|
private rowsIn;
|
|
21
21
|
pathsOf(target: PathTarget): Promise<Record<string, string>>;
|
|
22
|
+
livePathsIn(kind: "page" | "entry", ids: string[], locale: string): Promise<Record<string, string>>;
|
|
22
23
|
redirectsOf(target: PathTarget): Promise<PathRow[]>;
|
|
23
24
|
listLive(locale: string): Promise<PathRow[]>;
|
|
24
25
|
listRedirects(locale: string): Promise<PathRow[]>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { candidates, fullPath, pickHit, sameUrlKeys } from "./anchors.js";
|
|
2
2
|
import { pathLiveError, pathTakenError } from "./path-index.js";
|
|
3
3
|
const UNIQUE_VIOLATION = "23505";
|
|
4
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
4
5
|
const COLS = "locale, anchor, path, kind, target_id, redirect";
|
|
5
6
|
export class SupabasePathIndex {
|
|
6
7
|
/** Held by reference: the config's addresses (spec 2026-09-29 anchored paths §2). */
|
|
@@ -84,6 +85,21 @@ export class SupabasePathIndex {
|
|
|
84
85
|
out[r.locale] = r.path;
|
|
85
86
|
return out;
|
|
86
87
|
}
|
|
88
|
+
async livePathsIn(kind, ids, locale) {
|
|
89
|
+
const uuids = ids.filter((id) => UUID_RE.test(id));
|
|
90
|
+
if (uuids.length === 0)
|
|
91
|
+
return {};
|
|
92
|
+
const out = {};
|
|
93
|
+
// In chunks: one in() over hundreds of ids would overflow the request URL.
|
|
94
|
+
for (let i = 0; i < uuids.length; i += 100) {
|
|
95
|
+
const res = await this.db.from("paths").select(COLS).eq("kind", kind).eq("locale", locale).eq("redirect", false).in("target_id", uuids.slice(i, i + 100));
|
|
96
|
+
if (res.error)
|
|
97
|
+
this.fail(res.error.message);
|
|
98
|
+
for (const r of this.shown(res.data))
|
|
99
|
+
out[r.id] = r.path;
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
87
103
|
redirectsOf(target) {
|
|
88
104
|
return this.rowsOf(target, true);
|
|
89
105
|
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
-- Smoodly core schema, version 6 (docs/superpowers/specs/2026-09-29-pre-install-fixes-design.md §3).
|
|
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 5: one permissive policy per table and action (the
|
|
5
|
+
-- write key reads through smoodly_read; smoodly_write splits into insert,
|
|
6
|
+
-- update and delete where a public read exists; the media pair the same),
|
|
7
|
+
-- indexes on the foreign keys without one, and no anon call of the row cap.
|
|
8
|
+
-- Access is unchanged.
|
|
9
|
+
|
|
10
|
+
set client_min_messages = warning;
|
|
11
|
+
|
|
12
|
+
create index if not exists "page_locales_draft_version_idx" on "page_locales" ("draft_version_id");
|
|
13
|
+
create index if not exists "entry_locales_draft_version_idx" on "entry_locales" ("draft_version_id");
|
|
14
|
+
create index if not exists "page_locales_published_version_idx" on "page_locales" ("published_version_id");
|
|
15
|
+
create index if not exists "entry_locales_published_version_idx" on "entry_locales" ("published_version_id");
|
|
16
|
+
create index if not exists "pages_parent_id_idx" on "pages" ("parent_id");
|
|
17
|
+
create index if not exists "entries_parent_id_idx" on "entries" ("parent_id");
|
|
18
|
+
create index if not exists "assets_poster_idx" on "assets" ("poster_id");
|
|
19
|
+
|
|
20
|
+
drop policy if exists "smoodly_write" on "pages";
|
|
21
|
+
|
|
22
|
+
drop policy if exists "smoodly_insert" on "pages";
|
|
23
|
+
create policy "smoodly_insert" on "pages" for insert
|
|
24
|
+
with check ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
|
|
25
|
+
|
|
26
|
+
drop policy if exists "smoodly_update" on "pages";
|
|
27
|
+
create policy "smoodly_update" on "pages" for update
|
|
28
|
+
using ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write')
|
|
29
|
+
with check ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
|
|
30
|
+
|
|
31
|
+
drop policy if exists "smoodly_delete" on "pages";
|
|
32
|
+
create policy "smoodly_delete" on "pages" for delete
|
|
33
|
+
using ("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_read" on "pages";
|
|
36
|
+
create policy "smoodly_read" on "pages" for select
|
|
37
|
+
using ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and ((select "smoodly_key_kind"()) = 'write' or exists (select 1 from "page_locales" l where l."page_id" = "pages"."id" and l."status" = 'published')));
|
|
38
|
+
|
|
39
|
+
drop policy if exists "smoodly_write" on "page_locales";
|
|
40
|
+
|
|
41
|
+
drop policy if exists "smoodly_insert" on "page_locales";
|
|
42
|
+
create policy "smoodly_insert" on "page_locales" for insert
|
|
43
|
+
with check ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
|
|
44
|
+
|
|
45
|
+
drop policy if exists "smoodly_update" on "page_locales";
|
|
46
|
+
create policy "smoodly_update" on "page_locales" for update
|
|
47
|
+
using ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write')
|
|
48
|
+
with check ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
|
|
49
|
+
|
|
50
|
+
drop policy if exists "smoodly_delete" on "page_locales";
|
|
51
|
+
create policy "smoodly_delete" on "page_locales" for delete
|
|
52
|
+
using ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
|
|
53
|
+
|
|
54
|
+
drop policy if exists "smoodly_read" on "page_locales";
|
|
55
|
+
create policy "smoodly_read" on "page_locales" for select
|
|
56
|
+
using ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and ((select "smoodly_key_kind"()) = 'write' or "status" = 'published'));
|
|
57
|
+
|
|
58
|
+
drop policy if exists "smoodly_write" on "page_versions";
|
|
59
|
+
|
|
60
|
+
drop policy if exists "smoodly_insert" on "page_versions";
|
|
61
|
+
create policy "smoodly_insert" on "page_versions" for insert
|
|
62
|
+
with check ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
|
|
63
|
+
|
|
64
|
+
drop policy if exists "smoodly_update" on "page_versions";
|
|
65
|
+
create policy "smoodly_update" on "page_versions" for update
|
|
66
|
+
using ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write')
|
|
67
|
+
with check ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
|
|
68
|
+
|
|
69
|
+
drop policy if exists "smoodly_delete" on "page_versions";
|
|
70
|
+
create policy "smoodly_delete" on "page_versions" for delete
|
|
71
|
+
using ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
|
|
72
|
+
|
|
73
|
+
drop policy if exists "smoodly_read" on "page_versions";
|
|
74
|
+
create policy "smoodly_read" on "page_versions" for select
|
|
75
|
+
using ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and ((select "smoodly_key_kind"()) = 'write' or exists (select 1 from "page_locales" l where l."published_version_id" = "page_versions"."id")));
|
|
76
|
+
|
|
77
|
+
drop policy if exists "smoodly_write" on "entries";
|
|
78
|
+
|
|
79
|
+
drop policy if exists "smoodly_insert" on "entries";
|
|
80
|
+
create policy "smoodly_insert" on "entries" for insert
|
|
81
|
+
with check ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
|
|
82
|
+
|
|
83
|
+
drop policy if exists "smoodly_update" on "entries";
|
|
84
|
+
create policy "smoodly_update" on "entries" for update
|
|
85
|
+
using ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write')
|
|
86
|
+
with check ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
|
|
87
|
+
|
|
88
|
+
drop policy if exists "smoodly_delete" on "entries";
|
|
89
|
+
create policy "smoodly_delete" on "entries" for delete
|
|
90
|
+
using ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
|
|
91
|
+
|
|
92
|
+
drop policy if exists "smoodly_read" on "entries";
|
|
93
|
+
create policy "smoodly_read" on "entries" for select
|
|
94
|
+
using ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and ((select "smoodly_key_kind"()) = 'write' or exists (select 1 from "entry_locales" l where l."entry_id" = "entries"."id" and l."status" = 'published')));
|
|
95
|
+
|
|
96
|
+
drop policy if exists "smoodly_write" on "entry_locales";
|
|
97
|
+
|
|
98
|
+
drop policy if exists "smoodly_insert" on "entry_locales";
|
|
99
|
+
create policy "smoodly_insert" on "entry_locales" for insert
|
|
100
|
+
with check ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
|
|
101
|
+
|
|
102
|
+
drop policy if exists "smoodly_update" on "entry_locales";
|
|
103
|
+
create policy "smoodly_update" on "entry_locales" for update
|
|
104
|
+
using ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write')
|
|
105
|
+
with check ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
|
|
106
|
+
|
|
107
|
+
drop policy if exists "smoodly_delete" on "entry_locales";
|
|
108
|
+
create policy "smoodly_delete" on "entry_locales" for delete
|
|
109
|
+
using ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
|
|
110
|
+
|
|
111
|
+
drop policy if exists "smoodly_read" on "entry_locales";
|
|
112
|
+
create policy "smoodly_read" on "entry_locales" for select
|
|
113
|
+
using ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and ((select "smoodly_key_kind"()) = 'write' or "status" = 'published'));
|
|
114
|
+
|
|
115
|
+
drop policy if exists "smoodly_write" on "entry_versions";
|
|
116
|
+
|
|
117
|
+
drop policy if exists "smoodly_insert" on "entry_versions";
|
|
118
|
+
create policy "smoodly_insert" on "entry_versions" for insert
|
|
119
|
+
with check ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
|
|
120
|
+
|
|
121
|
+
drop policy if exists "smoodly_update" on "entry_versions";
|
|
122
|
+
create policy "smoodly_update" on "entry_versions" for update
|
|
123
|
+
using ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write')
|
|
124
|
+
with check ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
|
|
125
|
+
|
|
126
|
+
drop policy if exists "smoodly_delete" on "entry_versions";
|
|
127
|
+
create policy "smoodly_delete" on "entry_versions" for delete
|
|
128
|
+
using ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
|
|
129
|
+
|
|
130
|
+
drop policy if exists "smoodly_read" on "entry_versions";
|
|
131
|
+
create policy "smoodly_read" on "entry_versions" for select
|
|
132
|
+
using ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and ((select "smoodly_key_kind"()) = 'write' or exists (select 1 from "entry_locales" l where l."published_version_id" = "entry_versions"."id")));
|
|
133
|
+
|
|
134
|
+
drop policy if exists "smoodly_write" on "refs";
|
|
135
|
+
create policy "smoodly_write" on "refs" for all
|
|
136
|
+
using ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write')
|
|
137
|
+
with check ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
|
|
138
|
+
|
|
139
|
+
drop policy if exists "smoodly_write" on "paths";
|
|
140
|
+
|
|
141
|
+
drop policy if exists "smoodly_insert" on "paths";
|
|
142
|
+
create policy "smoodly_insert" on "paths" for insert
|
|
143
|
+
with check ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
|
|
144
|
+
|
|
145
|
+
drop policy if exists "smoodly_update" on "paths";
|
|
146
|
+
create policy "smoodly_update" on "paths" for update
|
|
147
|
+
using ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write')
|
|
148
|
+
with check ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
|
|
149
|
+
|
|
150
|
+
drop policy if exists "smoodly_delete" on "paths";
|
|
151
|
+
create policy "smoodly_delete" on "paths" for delete
|
|
152
|
+
using ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
|
|
153
|
+
|
|
154
|
+
drop policy if exists "smoodly_read" on "paths";
|
|
155
|
+
create policy "smoodly_read" on "paths" for select
|
|
156
|
+
using ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and ((select "smoodly_key_kind"()) = 'write' or (("kind" = 'page' and exists (select 1 from "page_locales" l where l."page_id" = "paths"."target_id" and l."locale" = "paths"."locale" and l."status" = 'published')) or ("kind" = 'entry' and exists (select 1 from "entry_locales" l where l."entry_id" = "paths"."target_id" and l."locale" = "paths"."locale" and l."status" = 'published')))));
|
|
157
|
+
|
|
158
|
+
drop policy if exists "smoodly_write" on "assets";
|
|
159
|
+
|
|
160
|
+
drop policy if exists "smoodly_insert" on "assets";
|
|
161
|
+
create policy "smoodly_insert" on "assets" for insert
|
|
162
|
+
with check ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
|
|
163
|
+
|
|
164
|
+
drop policy if exists "smoodly_update" on "assets";
|
|
165
|
+
create policy "smoodly_update" on "assets" for update
|
|
166
|
+
using ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write')
|
|
167
|
+
with check ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
|
|
168
|
+
|
|
169
|
+
drop policy if exists "smoodly_delete" on "assets";
|
|
170
|
+
create policy "smoodly_delete" on "assets" for delete
|
|
171
|
+
using ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
|
|
172
|
+
|
|
173
|
+
drop policy if exists "smoodly_read" on "assets";
|
|
174
|
+
create policy "smoodly_read" on "assets" for select
|
|
175
|
+
using ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and ((select "smoodly_key_kind"()) = 'write' or "status" = 'ready'));
|
|
176
|
+
|
|
177
|
+
drop policy if exists "smoodly_write" on "members";
|
|
178
|
+
create policy "smoodly_write" on "members" for all
|
|
179
|
+
using ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write')
|
|
180
|
+
with check ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
|
|
181
|
+
|
|
182
|
+
drop policy if exists "smoodly_media_write" on storage.objects;
|
|
183
|
+
|
|
184
|
+
drop policy if exists "smoodly_media_insert" on storage.objects;
|
|
185
|
+
create policy "smoodly_media_insert" on storage.objects for insert
|
|
186
|
+
with check ("bucket_id" = 'media' and (select public."smoodly_key_kind"()) = 'write' and not (select public."smoodly_space_key_revoked"()) and split_part("name", '/', 1) = (select public."smoodly_current_space"())::text);
|
|
187
|
+
|
|
188
|
+
drop policy if exists "smoodly_media_update" on storage.objects;
|
|
189
|
+
create policy "smoodly_media_update" on storage.objects for update
|
|
190
|
+
using ("bucket_id" = 'media' and (select public."smoodly_key_kind"()) = 'write' and not (select public."smoodly_space_key_revoked"()) and split_part("name", '/', 1) = (select public."smoodly_current_space"())::text)
|
|
191
|
+
with check ("bucket_id" = 'media' and (select public."smoodly_key_kind"()) = 'write' and not (select public."smoodly_space_key_revoked"()) and split_part("name", '/', 1) = (select public."smoodly_current_space"())::text);
|
|
192
|
+
|
|
193
|
+
drop policy if exists "smoodly_media_delete" on storage.objects;
|
|
194
|
+
create policy "smoodly_media_delete" on storage.objects for delete
|
|
195
|
+
using ("bucket_id" = 'media' and (select public."smoodly_key_kind"()) = 'write' and not (select public."smoodly_space_key_revoked"()) and split_part("name", '/', 1) = (select public."smoodly_current_space"())::text);
|
|
196
|
+
|
|
197
|
+
revoke execute on function "smoodly_space_row_cap"() from public, anon;
|
|
198
|
+
|
|
199
|
+
insert into "smoodly_meta" ("key", "value") values ('schema_version', '6')
|
|
200
|
+
on conflict ("key") do update set "value" = excluded."value";
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
-- Smoodly core schema, version 7 (docs/superpowers/specs/2026-09-29-content-api-contract-design.md §5).
|
|
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 6: smoodly_list_entries, which filters, orders and
|
|
5
|
+
-- limits a collection over the snapshot the reader is shown, and the read
|
|
6
|
+
-- policies of every content table admit a write key only (§8).
|
|
7
|
+
|
|
8
|
+
-- The shown snapshot of one collection's entries in a locale, filtered,
|
|
9
|
+
-- ordered and limited in one query (spec 2026-09-29 content API contract
|
|
10
|
+
-- §5): the published snapshot, or the live merge in preview. Invoker
|
|
11
|
+
-- rights, so RLS decides what a key sees; a preview read needs the
|
|
12
|
+
-- service role or a write key, anything else reads published.
|
|
13
|
+
create or replace function "smoodly_list_entries"(
|
|
14
|
+
p_collection text, p_locale text, p_draft boolean, p_where jsonb, p_order jsonb,
|
|
15
|
+
p_limit integer, p_parent uuid, p_has_parent boolean, p_ids uuid[]
|
|
16
|
+
) returns table ("id" uuid, "fields" jsonb) language sql stable set search_path = public as $$
|
|
17
|
+
with mode as (
|
|
18
|
+
select p_draft and (current_user = 'service_role' or "smoodly_key_kind"() = 'write') as draft
|
|
19
|
+
), shown as (
|
|
20
|
+
select e."id", e."sort", e."created_at", l."updated_at",
|
|
21
|
+
case when (select draft from mode) then e."fields" || l."fields" || jsonb_build_object('slug', l."slug")
|
|
22
|
+
else coalesce(v."fields", e."fields" || l."fields" || jsonb_build_object('slug', l."slug")) end as "fields"
|
|
23
|
+
from "entries" e
|
|
24
|
+
join "entry_locales" l on l."entry_id" = e."id" and l."locale" = p_locale
|
|
25
|
+
left join "entry_versions" v on v."id" = l."published_version_id"
|
|
26
|
+
where e."collection" = p_collection
|
|
27
|
+
and ((select draft from mode) or l."status" = 'published')
|
|
28
|
+
and (not p_has_parent or e."parent_id" is not distinct from p_parent)
|
|
29
|
+
and (p_ids is null or e."id" = any(p_ids))
|
|
30
|
+
)
|
|
31
|
+
select s."id", s."fields" from shown s
|
|
32
|
+
where not exists (
|
|
33
|
+
select 1 from jsonb_each(coalesce(p_where, '{}'::jsonb)) w
|
|
34
|
+
where not (case when w.value ? 'in'
|
|
35
|
+
then (s."fields" ->> w.key) = any (select jsonb_array_elements_text(w.value -> 'in'))
|
|
36
|
+
else s."fields" @> jsonb_build_object(w.key, w.value -> 'contains') end))
|
|
37
|
+
order by
|
|
38
|
+
case when p_order ->> 'by' = 'manual' then s."sort" end asc nulls last,
|
|
39
|
+
case when p_order ->> 'direction' = 'asc' then
|
|
40
|
+
case p_order ->> 'by' when 'createdAt' then to_jsonb(s."created_at") when 'updatedAt' then to_jsonb(s."updated_at")
|
|
41
|
+
when 'manual' then null else nullif(nullif(s."fields" -> (p_order ->> 'by'), 'null'::jsonb), '""'::jsonb) end end asc nulls last,
|
|
42
|
+
case when p_order ->> 'direction' = 'desc' then
|
|
43
|
+
case p_order ->> 'by' when 'createdAt' then to_jsonb(s."created_at") when 'updatedAt' then to_jsonb(s."updated_at")
|
|
44
|
+
when 'manual' then null else nullif(nullif(s."fields" -> (p_order ->> 'by'), 'null'::jsonb), '""'::jsonb) end end desc nulls last,
|
|
45
|
+
s."created_at" asc, s."id" asc
|
|
46
|
+
limit p_limit;
|
|
47
|
+
$$;
|
|
48
|
+
|
|
49
|
+
-- No public content reads (spec 2026-09-29 content API contract §8): the
|
|
50
|
+
-- publishable key and a read key read nothing; a write key reads its own space.
|
|
51
|
+
drop policy if exists "smoodly_read" on "pages";
|
|
52
|
+
create policy "smoodly_read" on "pages" for select
|
|
53
|
+
using ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
|
|
54
|
+
|
|
55
|
+
drop policy if exists "smoodly_read" on "page_locales";
|
|
56
|
+
create policy "smoodly_read" on "page_locales" for select
|
|
57
|
+
using ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
|
|
58
|
+
|
|
59
|
+
drop policy if exists "smoodly_read" on "page_versions";
|
|
60
|
+
create policy "smoodly_read" on "page_versions" for select
|
|
61
|
+
using ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
|
|
62
|
+
|
|
63
|
+
drop policy if exists "smoodly_read" on "entries";
|
|
64
|
+
create policy "smoodly_read" on "entries" for select
|
|
65
|
+
using ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
|
|
66
|
+
|
|
67
|
+
drop policy if exists "smoodly_read" on "entry_locales";
|
|
68
|
+
create policy "smoodly_read" on "entry_locales" for select
|
|
69
|
+
using ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
|
|
70
|
+
|
|
71
|
+
drop policy if exists "smoodly_read" on "entry_versions";
|
|
72
|
+
create policy "smoodly_read" on "entry_versions" for select
|
|
73
|
+
using ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
|
|
74
|
+
|
|
75
|
+
drop policy if exists "smoodly_read" on "paths";
|
|
76
|
+
create policy "smoodly_read" on "paths" for select
|
|
77
|
+
using ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
|
|
78
|
+
|
|
79
|
+
drop policy if exists "smoodly_read" on "assets";
|
|
80
|
+
create policy "smoodly_read" on "assets" for select
|
|
81
|
+
using ("space_id" = (select "smoodly_current_space"()) and not (select "smoodly_space_key_revoked"()) and (select "smoodly_key_kind"()) = 'write');
|
|
82
|
+
|
|
83
|
+
insert into "smoodly_meta" ("key", "value") values ('schema_version', '7')
|
|
84
|
+
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.
|
|
3
|
+
"version": "0.0.36",
|
|
4
4
|
"description": "Open-source, code-first CMS for Next.js on Supabase: define a component once, it becomes a typed, editable building block.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -43,10 +43,6 @@
|
|
|
43
43
|
"types": "./dist/admin/next/index.d.ts",
|
|
44
44
|
"default": "./dist/admin/next/index.js"
|
|
45
45
|
},
|
|
46
|
-
"./bridge": {
|
|
47
|
-
"types": "./dist/admin/next/bridge.d.ts",
|
|
48
|
-
"default": "./dist/admin/next/bridge.js"
|
|
49
|
-
},
|
|
50
46
|
"./migrations": {
|
|
51
47
|
"types": "./dist/migrations.d.ts",
|
|
52
48
|
"default": "./dist/migrations.js"
|