@dalgoridim/headless-cms 0.5.1 → 0.7.0

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 CHANGED
@@ -234,11 +234,15 @@ ops. Hydrate the lists from the server via `initialCollections`:
234
234
  ```tsx
235
235
  import { usePageContext } from "@dalgoridim/headless-cms/client";
236
236
 
237
- const { collections, createItem, deleteItem, reorderItems } = usePageContext();
237
+ const { collections, createItem, updateItem, deleteItem, reorderItems } =
238
+ usePageContext();
238
239
 
239
240
  // add — PUT (upsert) with a generated id; returns the new id
240
241
  await createItem("projects", { title: "New project", order: collections.projects.length });
241
242
 
243
+ // edit fields on an existing item — PATCH
244
+ await updateItem("projects", id, { github: "https://github.com/me/repo" });
245
+
242
246
  // remove — DELETE
243
247
  await deleteItem("projects", id);
244
248
 
@@ -249,6 +253,7 @@ await reorderItems("projects", ["id-c", "id-a", "id-b"]);
249
253
  | Op | Persists via | Notes |
250
254
  |---|---|---|
251
255
  | `createItem(collection, data, opts?)` | `PUT {base}/{collection}/{id}` | `opts.id` to set the id, `opts.atStart` to prepend; returns the id |
256
+ | `updateItem(collection, id, patch)` | `PATCH {base}/{collection}/{id}` | patches fields on an existing item |
252
257
  | `deleteItem(collection, id)` | `DELETE {base}/{collection}/{id}` | |
253
258
  | `reorderItems(collection, orderedIds)` | `PATCH {base}/{collection}/{id}` ×N | writes `{ order: index }` per item |
254
259
 
@@ -330,9 +335,16 @@ const data = new PostgresDataAdapter({
330
335
  projects: { table: "projects", columns: { title: "text", views: "int", date: "date" } },
331
336
  },
332
337
  });
333
- await data.migrate(); // creates documents + registered tables if missing
338
+ await data.migrate(); // creates tables if missing, then adds any new columns
334
339
  ```
335
340
 
341
+ `migrate()` is **additive and idempotent**: it creates missing tables and
342
+ `ALTER TABLE ... ADD COLUMN IF NOT EXISTS` for every registered column, so adding
343
+ a column (or a whole collection) to your config and re-running it evolves an
344
+ existing database **without a destructive reset**. It never drops, renames, or
345
+ retypes columns — those still need a hand-written migration. Safe to run on every
346
+ boot/deploy.
347
+
336
348
  > **Caveat:** fields in the schemaless JSONB `documents` table are extracted as
337
349
  > **text**, so numeric ordering/comparison is lexicographic (`"10" < "5"`).
338
350
  > Register the collection with a typed `int`/`float` column for true numeric
@@ -93,7 +93,17 @@ var PostgresDataAdapter = class {
93
93
  cfg(collection) {
94
94
  return this.collections[collection];
95
95
  }
96
- /** Create the documents table and every registered typed table if missing. */
96
+ /**
97
+ * Create the documents table and every registered typed table if missing,
98
+ * then **additively** reconcile each table's columns: any registered column
99
+ * not yet present is added via `ALTER TABLE ... ADD COLUMN IF NOT EXISTS`.
100
+ *
101
+ * This makes `migrate()` idempotent *and* forward-compatible — adding a new
102
+ * column (or a whole new collection) to your config and re-running `migrate()`
103
+ * evolves an existing database without a destructive reset. It is additive
104
+ * only: it never drops, renames, or retypes columns (those need a real
105
+ * migration). Safe to run on every boot/deploy.
106
+ */
97
107
  async migrate() {
98
108
  await this.pool.query(`
99
109
  CREATE TABLE IF NOT EXISTS ${ident(this.documentsTable)} (
@@ -120,6 +130,11 @@ var PostgresDataAdapter = class {
120
130
  updated_at timestamptz DEFAULT now()
121
131
  );
122
132
  `);
133
+ for (const [name, type] of Object.entries(cfg.columns)) {
134
+ await this.pool.query(
135
+ `ALTER TABLE ${ident(cfg.table)} ADD COLUMN IF NOT EXISTS ${ident(name)} ${SQL_TYPE[type]};`
136
+ );
137
+ }
123
138
  }
124
139
  }
125
140
  // --- row <-> section mapping ------------------------------------------------
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/adapters/postgres/index.ts","../../../src/types.ts"],"sourcesContent":["import { Pool } from \"pg\";\nimport { randomUUID } from \"node:crypto\";\nimport type {\n DataAdapter,\n Query,\n QueryCondition,\n QueryFilter,\n QueryFilterOp,\n} from \"../../types\";\nimport { isFilterGroup } from \"../../types\";\n\n/** Column type hints for a registered (typed) collection. */\nexport type ColumnType = \"text\" | \"int\" | \"float\" | \"bool\" | \"date\" | \"timestamptz\" | \"jsonb\";\n\nexport interface CollectionConfig {\n /** Physical table name. */\n table: string;\n /** Map of section field → SQL column type. Unlisted fields fall into `extra`. */\n columns: Record<string, ColumnType>;\n}\n\nexport interface PostgresAdapterConfig {\n /** Provide a connection string… */\n connectionString?: string;\n /** …or an existing pg Pool. */\n pool?: Pool;\n /**\n * Registry of typed collections. Anything not registered here is stored in\n * the shared JSONB `documents` table.\n */\n collections?: Record<string, CollectionConfig>;\n /** Name of the fallback JSONB table. Default `documents`. */\n documentsTable?: string;\n}\n\n/** Binary ops that render as `col <op> $n`. */\nconst BINARY_OP: Partial<Record<QueryFilterOp, string>> = {\n eq: \"=\",\n ne: \"<>\",\n lt: \"<\",\n lte: \"<=\",\n gt: \">\",\n gte: \">=\",\n};\n\nconst SQL_TYPE: Record<ColumnType, string> = {\n text: \"text\",\n int: \"integer\",\n float: \"double precision\",\n bool: \"boolean\",\n date: \"date\",\n timestamptz: \"timestamptz\",\n jsonb: \"jsonb\",\n};\n\n/**\n * Hybrid Postgres adapter. Unregistered collections live in a shared JSONB\n * `documents` table; registered collections map flat section fields onto typed\n * columns, with a JSONB `extra` column so unmapped fields are never dropped.\n */\nexport class PostgresDataAdapter implements DataAdapter {\n private readonly pool: Pool;\n private readonly collections: Record<string, CollectionConfig>;\n private readonly documentsTable: string;\n\n constructor(config: PostgresAdapterConfig = {}) {\n if (config.pool) {\n this.pool = config.pool;\n } else if (config.connectionString) {\n this.pool = new Pool({ connectionString: config.connectionString });\n } else {\n throw new Error(\n \"PostgresDataAdapter requires either `pool` or `connectionString`.\",\n );\n }\n this.collections = config.collections ?? {};\n this.documentsTable = config.documentsTable ?? \"documents\";\n }\n\n private cfg(collection: string): CollectionConfig | undefined {\n return this.collections[collection];\n }\n\n /** Create the documents table and every registered typed table if missing. */\n async migrate(): Promise<void> {\n await this.pool.query(`\n CREATE TABLE IF NOT EXISTS ${ident(this.documentsTable)} (\n id text NOT NULL,\n collection text NOT NULL,\n data jsonb NOT NULL,\n created_at timestamptz DEFAULT now(),\n updated_at timestamptz DEFAULT now(),\n PRIMARY KEY (collection, id)\n );\n `);\n await this.pool.query(\n `CREATE INDEX IF NOT EXISTS ${ident(this.documentsTable + \"_collection_created_idx\")}\n ON ${ident(this.documentsTable)} (collection, created_at DESC);`,\n );\n\n for (const cfg of Object.values(this.collections)) {\n const cols = Object.entries(cfg.columns)\n .map(([name, type]) => `${ident(name)} ${SQL_TYPE[type]}`)\n .join(\",\\n \");\n await this.pool.query(`\n CREATE TABLE IF NOT EXISTS ${ident(cfg.table)} (\n id text PRIMARY KEY,\n ${cols ? cols + \",\" : \"\"}\n extra jsonb NOT NULL DEFAULT '{}'::jsonb,\n created_at timestamptz DEFAULT now(),\n updated_at timestamptz DEFAULT now()\n );\n `);\n }\n }\n\n // --- row <-> section mapping ------------------------------------------------\n\n /** Split a section into typed columns + leftover `extra`. */\n private toTypedRow(cfg: CollectionConfig, data: Record<string, unknown>) {\n const known: Record<string, unknown> = {};\n const extra: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(data)) {\n if (key === \"id\" || key === \"collection\") continue;\n if (key in cfg.columns) known[key] = value;\n else extra[key] = value;\n }\n return { known, extra };\n }\n\n private fromTypedRow(\n collection: string,\n cfg: CollectionConfig,\n row: Record<string, unknown>,\n ) {\n const { id, extra, created_at, updated_at, ...rest } = row;\n void created_at;\n void updated_at;\n return {\n id: id as string,\n collection,\n ...(rest as Record<string, unknown>),\n ...((extra as Record<string, unknown>) ?? {}),\n };\n }\n\n // --- reads -----------------------------------------------------------------\n\n async fetchById<T = Record<string, unknown>>(\n collection: string,\n id: string,\n ): Promise<(T & { id: string }) | null> {\n const cfg = this.cfg(collection);\n if (cfg) {\n const { rows } = await this.pool.query(\n `SELECT * FROM ${ident(cfg.table)} WHERE id = $1`,\n [id],\n );\n if (!rows[0]) return null;\n return this.fromTypedRow(collection, cfg, rows[0]) as unknown as T & {\n id: string;\n };\n }\n const { rows } = await this.pool.query(\n `SELECT id, data FROM ${ident(this.documentsTable)} WHERE id = $1 AND collection = $2`,\n [id, collection],\n );\n if (!rows[0]) return null;\n return { id: rows[0].id, collection, ...rows[0].data } as unknown as T & {\n id: string;\n };\n }\n\n async fetchCollection<T = Record<string, unknown>>(\n collection: string,\n q?: Query,\n ): Promise<(T & { id: string })[]> {\n const cfg = this.cfg(collection);\n const params: unknown[] = [];\n const where: string[] = [];\n\n const colExpr = (field: string): string => {\n if (cfg) {\n return field in cfg.columns ? ident(field) : `extra->>${literal(field)}`;\n }\n return `data->>${literal(field)}`;\n };\n\n /** Render one field condition to SQL, pushing its bound params. */\n const renderFilter = (f: QueryFilter): string => {\n const col = colExpr(f.field);\n if (f.op === \"in\" || f.op === \"nin\") {\n params.push(f.value);\n const cmp = f.op === \"in\" ? \"= ANY\" : \"<> ALL\";\n return `${col} ${cmp}($${params.length})`;\n }\n if (f.op === \"contains\") {\n params.push(`%${String(f.value)}%`);\n return `${col} ILIKE $${params.length}`;\n }\n const op = BINARY_OP[f.op];\n if (!op) throw new Error(`Unsupported query op: ${f.op}`);\n params.push(f.value);\n return `${col} ${op} $${params.length}`;\n };\n\n /** Render an AND-level condition, expanding OR groups into `(a OR b)`. */\n const renderCondition = (c: QueryCondition): string => {\n if (isFilterGroup(c)) {\n if (!c.or.length) return \"TRUE\";\n return `(${c.or.map(renderFilter).join(\" OR \")})`;\n }\n return renderFilter(c);\n };\n\n if (!cfg) {\n params.push(collection);\n where.push(`collection = $${params.length}`);\n }\n\n for (const c of q?.filters ?? []) {\n where.push(renderCondition(c));\n }\n\n const orderBy =\n (q?.orderBy ?? []).map((o) => `${colExpr(o.field)} ${o.direction === \"asc\" ? \"ASC\" : \"DESC\"}`).join(\", \") ||\n \"created_at DESC\";\n\n const limit = q?.limit != null ? ` LIMIT ${Number(q.limit)}` : \"\";\n const offset = q?.offset != null ? ` OFFSET ${Number(q.offset)}` : \"\";\n const whereSql = where.length ? ` WHERE ${where.join(\" AND \")}` : \"\";\n const table = cfg ? cfg.table : this.documentsTable;\n const select = cfg ? \"*\" : \"id, data\";\n\n const { rows } = await this.pool.query(\n `SELECT ${select} FROM ${ident(table)}${whereSql} ORDER BY ${orderBy}${limit}${offset}`,\n params,\n );\n\n if (cfg) {\n return rows.map((r) => this.fromTypedRow(collection, cfg, r)) as unknown as (T & {\n id: string;\n })[];\n }\n return rows.map((r) => ({ id: r.id, collection, ...r.data })) as unknown as (T & {\n id: string;\n })[];\n }\n\n // --- writes ----------------------------------------------------------------\n\n async create<T = Record<string, unknown>>(\n collection: string,\n data: T,\n ): Promise<T & { id: string }> {\n return this.createWithId(collection, randomUUID(), data);\n }\n\n async createWithId<T = Record<string, unknown>>(\n collection: string,\n id: string,\n data: T,\n ): Promise<T & { id: string }> {\n const cfg = this.cfg(collection);\n const record = data as Record<string, unknown>;\n\n if (cfg) {\n const { known, extra } = this.toTypedRow(cfg, record);\n const cols = [\"id\", ...Object.keys(known), \"extra\"];\n const vals = [id, ...Object.values(known), JSON.stringify(extra)];\n const placeholders = cols.map((_, i) => `$${i + 1}`).join(\", \");\n await this.pool.query(\n `INSERT INTO ${ident(cfg.table)} (${cols.map(ident).join(\", \")})\n VALUES (${placeholders})`,\n vals,\n );\n } else {\n await this.pool.query(\n `INSERT INTO ${ident(this.documentsTable)} (id, collection, data)\n VALUES ($1, $2, $3)`,\n [id, collection, JSON.stringify(record)],\n );\n }\n return { id, ...(data as T) };\n }\n\n async update<T = Record<string, unknown>>(\n collection: string,\n id: string,\n data: Partial<T>,\n ): Promise<void> {\n await this.write(collection, id, data, \"update\");\n }\n\n async upsert<T = Record<string, unknown>>(\n collection: string,\n id: string,\n data: Partial<T>,\n ): Promise<void> {\n await this.write(collection, id, data, \"upsert\");\n }\n\n private async write(\n collection: string,\n id: string,\n data: Record<string, unknown>,\n mode: \"update\" | \"upsert\",\n ): Promise<void> {\n const cfg = this.cfg(collection);\n\n if (cfg) {\n const { known, extra } = this.toTypedRow(cfg, data);\n const setCols = Object.keys(known);\n const setExpr = setCols\n .map((c, i) => `${ident(c)} = $${i + 2}`)\n .join(\", \");\n // Merge into extra so unmapped fields aren't dropped.\n const extraIdx = setCols.length + 2;\n const sets = [\n setExpr,\n // Table-qualified: in ON CONFLICT DO UPDATE both the target table and the\n // `excluded` pseudo-relation expose `extra`, so a bare ref is ambiguous.\n `extra = ${ident(cfg.table)}.${ident(\"extra\")} || $${extraIdx}::jsonb`,\n `updated_at = now()`,\n ]\n .filter(Boolean)\n .join(\", \");\n const params = [id, ...Object.values(known), JSON.stringify(extra)];\n\n if (mode === \"upsert\") {\n const insertCols = [\"id\", ...setCols, \"extra\"];\n const insertVals = params;\n const placeholders = insertCols.map((_, i) => `$${i + 1}`).join(\", \");\n await this.pool.query(\n `INSERT INTO ${ident(cfg.table)} (${insertCols.map(ident).join(\", \")})\n VALUES (${placeholders})\n ON CONFLICT (id) DO UPDATE SET ${sets}`,\n insertVals,\n );\n } else {\n await this.pool.query(\n `UPDATE ${ident(cfg.table)} SET ${sets} WHERE id = $1`,\n params,\n );\n }\n return;\n }\n\n // JSONB documents table: shallow-merge the patch into `data`.\n if (mode === \"upsert\") {\n await this.pool.query(\n `INSERT INTO ${ident(this.documentsTable)} (id, collection, data)\n VALUES ($1, $2, $3)\n ON CONFLICT (collection, id) DO UPDATE\n SET data = ${ident(this.documentsTable)}.data || $3::jsonb,\n updated_at = now()`,\n [id, collection, JSON.stringify(data)],\n );\n } else {\n await this.pool.query(\n `UPDATE ${ident(this.documentsTable)}\n SET data = data || $2::jsonb, updated_at = now()\n WHERE id = $1 AND collection = $3`,\n [id, JSON.stringify(data), collection],\n );\n }\n }\n\n async delete(collection: string, id: string): Promise<void> {\n const cfg = this.cfg(collection);\n if (cfg) {\n await this.pool.query(`DELETE FROM ${ident(cfg.table)} WHERE id = $1`, [id]);\n } else {\n await this.pool.query(\n `DELETE FROM ${ident(this.documentsTable)} WHERE id = $1 AND collection = $2`,\n [id, collection],\n );\n }\n }\n}\n\n/** Quote a SQL identifier safely. */\nfunction ident(name: string): string {\n return `\"${name.replace(/\"/g, '\"\"')}\"`;\n}\n\n/** Quote a SQL string literal (used for static jsonb keys, never user values). */\nfunction literal(value: string): string {\n return `'${value.replace(/'/g, \"''\")}'`;\n}\n","/**\n * Shared, runtime-free types. Safe to import in any environment (client or\n * server) — this module pulls in zero runtime dependencies.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\n/**\n * Engine addressing fields. The engine needs to know an entity's `id` and which\n * `collection` it lives in to route saves; everything else is the consumer's own\n * shape. Kept separate from the user's content type `T` so we never force these\n * onto domain models — use {@link Editable}<YourType> when you want both.\n */\nexport interface EntityAddress {\n id: string;\n collection: string;\n}\n\n/**\n * The user's content type `T` decorated with the engine's addressing fields.\n * `T` is completely unconstrained — bring any shape; the engine only adds `id`\n * and `collection`.\n */\nexport type Editable<T = Record<string, any>> = T & EntityAddress;\n\n/**\n * A single editable record. Schemaless by design. Equivalent to\n * `Editable<Record<string, any>>`; kept as a named alias for the common case and\n * for backwards compatibility.\n */\nexport type Section = Editable;\n\nexport type SectionMap = Record<string, Section>;\n\n/**\n * The shape the engine holds in memory:\n * `{ [collection]: { [sectionKey]: Section } }`.\n */\nexport type NestedSections = {\n [collection: string]: {\n [sectionKey: string]: Section;\n };\n};\n\n/**\n * A single item in an editable collection list (e.g. a project or a tool). Just\n * a record with a stable `id`. Used by the client provider's collection ops\n * (create / delete / reorder) which manage *which* items exist and their order,\n * complementing {@link Section}s which manage a single item's editable fields.\n */\nexport type CollectionItem = Record<string, unknown> & { id: string };\n\n/**\n * An image edit queued in the provider. The actual upload is deferred until\n * save. `file` is null when the user pasted an external URL (`isExternal`).\n */\nexport interface PendingImage {\n file: File | null;\n localUrl: string;\n sectionKey: string;\n fieldKey: string;\n collection: string;\n docId: string;\n isExternal?: boolean;\n}\n\n/**\n * Neutral query language. Keeps any backend's native query type (Firestore's\n * `Query`, SQL, etc.) from leaking through the engine.\n *\n * Op support is backend-dependent — see each adapter. `contains` is a\n * case-insensitive substring match; `in`/`nin` take an array value.\n */\nexport type QueryFilterOp =\n | \"eq\"\n | \"ne\"\n | \"lt\"\n | \"lte\"\n | \"gt\"\n | \"gte\"\n | \"in\"\n | \"nin\"\n | \"contains\";\n\n/** A single field condition. */\nexport type QueryFilter = { field: string; op: QueryFilterOp; value: unknown };\n\n/**\n * A disjunction: the inner filters are combined with OR. Sits alongside plain\n * filters in `Query.filters`, which are combined with AND at the top level.\n */\nexport type QueryFilterGroup = { or: QueryFilter[] };\n\n/** Either a bare condition (AND-ed) or an OR group. */\nexport type QueryCondition = QueryFilter | QueryFilterGroup;\n\nexport type Query = {\n /** Top-level conditions combined with AND. Use `{ or: [...] }` for disjunction. */\n filters?: QueryCondition[];\n orderBy?: { field: string; direction: \"asc\" | \"desc\" }[];\n limit?: number;\n /** Skip this many rows (offset pagination). */\n offset?: number;\n /**\n * Fields holding references to other documents to inline-resolve after the\n * primary fetch. Resolved by {@link resolveRelations}, never by the adapter.\n */\n populate?: string[];\n};\n\n/** Narrow a {@link QueryCondition} to an OR group. */\nexport function isFilterGroup(c: QueryCondition): c is QueryFilterGroup {\n return typeof c === \"object\" && c !== null && \"or\" in c;\n}\n\n/**\n * A reference to another document. Either self-describing (`{ collection, id }`)\n * or a bare id string resolved via a {@link RelationConfig}.\n */\nexport type Ref = { collection: string; id: string };\n\n/**\n * Maps a reference field name → the collection it points to. Needed only when\n * refs are stored as bare id strings (self-describing `Ref` objects don't need\n * it). Used by {@link resolveRelations}.\n */\nexport type RelationConfig = Record<string, { collection: string }>;\n\n/**\n * Persistence contract. Every backend (Firestore, Postgres, …) implements this;\n * the engine and the route factory only ever speak to this interface.\n */\nexport interface DataAdapter {\n fetchCollection<T = Record<string, any>>(\n collection: string,\n q?: Query,\n ): Promise<(T & { id: string })[]>;\n fetchById<T = Record<string, any>>(\n collection: string,\n id: string,\n ): Promise<(T & { id: string }) | null>;\n create<T = Record<string, any>>(\n collection: string,\n data: T,\n ): Promise<T & { id: string }>;\n createWithId<T = Record<string, any>>(\n collection: string,\n id: string,\n data: T,\n ): Promise<T & { id: string }>;\n update<T = Record<string, any>>(\n collection: string,\n id: string,\n data: Partial<T>,\n ): Promise<void>;\n upsert<T = Record<string, any>>(\n collection: string,\n id: string,\n data: Partial<T>,\n ): Promise<void>;\n delete(collection: string, id: string): Promise<void>;\n}\n\n/**\n * The identity resolved from an incoming request by an {@link AuthAdapter}.\n * Minimal by design: only `isAdmin` is meaningful to the default gate. Carry any\n * additional claims (roles, scopes, tenant, …) as extra keys and authorize on\n * them with a custom `authorize` predicate. `userId`/`email` are conventional\n * but optional.\n */\nexport interface AuthIdentity {\n isAdmin: boolean;\n userId?: string;\n email?: string;\n [claim: string]: unknown;\n}\n\n/**\n * Decides whether a resolved identity may perform admin actions. Defaults to\n * `identity.isAdmin === true`; override to gate on roles/scopes/etc.\n */\nexport type AuthorizeFn = (\n identity: AuthIdentity,\n req: Request,\n) => boolean | Promise<boolean>;\n\n/**\n * Server-side auth contract. Gates every admin API route. Return `null` to\n * reject outright; otherwise the gate's `authorize` predicate decides.\n */\nexport interface AuthAdapter {\n verifyRequest(req: Request): Promise<AuthIdentity | null>;\n}\n\n/**\n * Client half of storage: performs the browser-side upload and returns the\n * final URL. Has zero server dependencies, so it is safe to import in client\n * components. Built from e.g. `@dalgoridim/headless-cms/storage/cloudinary`.\n */\nexport interface ClientStorageAdapter {\n upload(file: File): Promise<{ url: string }>;\n}\n\n/**\n * Server half of storage: issues a presign / signature (or, for local storage,\n * writes the file). Mounted by the route factory at `${apiBasePath}/sign`.\n * Pulls in server-only SDKs, so it lives in a `/server` subpath. Built from e.g.\n * `@dalgoridim/headless-cms/storage/cloudinary/server`.\n */\nexport interface ServerStorageAdapter {\n sign(req: Request): Promise<unknown>;\n}\n\n/** Full two-sided contract (rarely needed; client and server halves are split). */\nexport interface StorageAdapter extends ClientStorageAdapter {\n sign?(req: Request): Promise<unknown>;\n}\n\n/**\n * Minimal client-side auth state the edit primitives depend on. Any auth\n * implementation (Firebase, NextAuth, custom) provides this via a context;\n * `@dalgoridim/headless-cms/auth/firebase/client` ships the default.\n */\nexport interface CmsAuthState {\n isAdmin: boolean;\n isEditing: boolean;\n toggleEdit: () => void;\n}\n"],"mappings":"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","names":["rows"]}
1
+ {"version":3,"sources":["../../../src/adapters/postgres/index.ts","../../../src/types.ts"],"sourcesContent":["import { Pool } from \"pg\";\nimport { randomUUID } from \"node:crypto\";\nimport type {\n DataAdapter,\n Query,\n QueryCondition,\n QueryFilter,\n QueryFilterOp,\n} from \"../../types\";\nimport { isFilterGroup } from \"../../types\";\n\n/** Column type hints for a registered (typed) collection. */\nexport type ColumnType = \"text\" | \"int\" | \"float\" | \"bool\" | \"date\" | \"timestamptz\" | \"jsonb\";\n\nexport interface CollectionConfig {\n /** Physical table name. */\n table: string;\n /** Map of section field → SQL column type. Unlisted fields fall into `extra`. */\n columns: Record<string, ColumnType>;\n}\n\nexport interface PostgresAdapterConfig {\n /** Provide a connection string… */\n connectionString?: string;\n /** …or an existing pg Pool. */\n pool?: Pool;\n /**\n * Registry of typed collections. Anything not registered here is stored in\n * the shared JSONB `documents` table.\n */\n collections?: Record<string, CollectionConfig>;\n /** Name of the fallback JSONB table. Default `documents`. */\n documentsTable?: string;\n}\n\n/** Binary ops that render as `col <op> $n`. */\nconst BINARY_OP: Partial<Record<QueryFilterOp, string>> = {\n eq: \"=\",\n ne: \"<>\",\n lt: \"<\",\n lte: \"<=\",\n gt: \">\",\n gte: \">=\",\n};\n\nconst SQL_TYPE: Record<ColumnType, string> = {\n text: \"text\",\n int: \"integer\",\n float: \"double precision\",\n bool: \"boolean\",\n date: \"date\",\n timestamptz: \"timestamptz\",\n jsonb: \"jsonb\",\n};\n\n/**\n * Hybrid Postgres adapter. Unregistered collections live in a shared JSONB\n * `documents` table; registered collections map flat section fields onto typed\n * columns, with a JSONB `extra` column so unmapped fields are never dropped.\n */\nexport class PostgresDataAdapter implements DataAdapter {\n private readonly pool: Pool;\n private readonly collections: Record<string, CollectionConfig>;\n private readonly documentsTable: string;\n\n constructor(config: PostgresAdapterConfig = {}) {\n if (config.pool) {\n this.pool = config.pool;\n } else if (config.connectionString) {\n this.pool = new Pool({ connectionString: config.connectionString });\n } else {\n throw new Error(\n \"PostgresDataAdapter requires either `pool` or `connectionString`.\",\n );\n }\n this.collections = config.collections ?? {};\n this.documentsTable = config.documentsTable ?? \"documents\";\n }\n\n private cfg(collection: string): CollectionConfig | undefined {\n return this.collections[collection];\n }\n\n /**\n * Create the documents table and every registered typed table if missing,\n * then **additively** reconcile each table's columns: any registered column\n * not yet present is added via `ALTER TABLE ... ADD COLUMN IF NOT EXISTS`.\n *\n * This makes `migrate()` idempotent *and* forward-compatible — adding a new\n * column (or a whole new collection) to your config and re-running `migrate()`\n * evolves an existing database without a destructive reset. It is additive\n * only: it never drops, renames, or retypes columns (those need a real\n * migration). Safe to run on every boot/deploy.\n */\n async migrate(): Promise<void> {\n await this.pool.query(`\n CREATE TABLE IF NOT EXISTS ${ident(this.documentsTable)} (\n id text NOT NULL,\n collection text NOT NULL,\n data jsonb NOT NULL,\n created_at timestamptz DEFAULT now(),\n updated_at timestamptz DEFAULT now(),\n PRIMARY KEY (collection, id)\n );\n `);\n await this.pool.query(\n `CREATE INDEX IF NOT EXISTS ${ident(this.documentsTable + \"_collection_created_idx\")}\n ON ${ident(this.documentsTable)} (collection, created_at DESC);`,\n );\n\n for (const cfg of Object.values(this.collections)) {\n const cols = Object.entries(cfg.columns)\n .map(([name, type]) => `${ident(name)} ${SQL_TYPE[type]}`)\n .join(\",\\n \");\n await this.pool.query(`\n CREATE TABLE IF NOT EXISTS ${ident(cfg.table)} (\n id text PRIMARY KEY,\n ${cols ? cols + \",\" : \"\"}\n extra jsonb NOT NULL DEFAULT '{}'::jsonb,\n created_at timestamptz DEFAULT now(),\n updated_at timestamptz DEFAULT now()\n );\n `);\n\n // Reconcile columns added to the config after the table already existed.\n for (const [name, type] of Object.entries(cfg.columns)) {\n await this.pool.query(\n `ALTER TABLE ${ident(cfg.table)} ADD COLUMN IF NOT EXISTS ${ident(name)} ${SQL_TYPE[type]};`,\n );\n }\n }\n }\n\n // --- row <-> section mapping ------------------------------------------------\n\n /** Split a section into typed columns + leftover `extra`. */\n private toTypedRow(cfg: CollectionConfig, data: Record<string, unknown>) {\n const known: Record<string, unknown> = {};\n const extra: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(data)) {\n if (key === \"id\" || key === \"collection\") continue;\n if (key in cfg.columns) known[key] = value;\n else extra[key] = value;\n }\n return { known, extra };\n }\n\n private fromTypedRow(\n collection: string,\n cfg: CollectionConfig,\n row: Record<string, unknown>,\n ) {\n const { id, extra, created_at, updated_at, ...rest } = row;\n void created_at;\n void updated_at;\n return {\n id: id as string,\n collection,\n ...(rest as Record<string, unknown>),\n ...((extra as Record<string, unknown>) ?? {}),\n };\n }\n\n // --- reads -----------------------------------------------------------------\n\n async fetchById<T = Record<string, unknown>>(\n collection: string,\n id: string,\n ): Promise<(T & { id: string }) | null> {\n const cfg = this.cfg(collection);\n if (cfg) {\n const { rows } = await this.pool.query(\n `SELECT * FROM ${ident(cfg.table)} WHERE id = $1`,\n [id],\n );\n if (!rows[0]) return null;\n return this.fromTypedRow(collection, cfg, rows[0]) as unknown as T & {\n id: string;\n };\n }\n const { rows } = await this.pool.query(\n `SELECT id, data FROM ${ident(this.documentsTable)} WHERE id = $1 AND collection = $2`,\n [id, collection],\n );\n if (!rows[0]) return null;\n return { id: rows[0].id, collection, ...rows[0].data } as unknown as T & {\n id: string;\n };\n }\n\n async fetchCollection<T = Record<string, unknown>>(\n collection: string,\n q?: Query,\n ): Promise<(T & { id: string })[]> {\n const cfg = this.cfg(collection);\n const params: unknown[] = [];\n const where: string[] = [];\n\n const colExpr = (field: string): string => {\n if (cfg) {\n return field in cfg.columns ? ident(field) : `extra->>${literal(field)}`;\n }\n return `data->>${literal(field)}`;\n };\n\n /** Render one field condition to SQL, pushing its bound params. */\n const renderFilter = (f: QueryFilter): string => {\n const col = colExpr(f.field);\n if (f.op === \"in\" || f.op === \"nin\") {\n params.push(f.value);\n const cmp = f.op === \"in\" ? \"= ANY\" : \"<> ALL\";\n return `${col} ${cmp}($${params.length})`;\n }\n if (f.op === \"contains\") {\n params.push(`%${String(f.value)}%`);\n return `${col} ILIKE $${params.length}`;\n }\n const op = BINARY_OP[f.op];\n if (!op) throw new Error(`Unsupported query op: ${f.op}`);\n params.push(f.value);\n return `${col} ${op} $${params.length}`;\n };\n\n /** Render an AND-level condition, expanding OR groups into `(a OR b)`. */\n const renderCondition = (c: QueryCondition): string => {\n if (isFilterGroup(c)) {\n if (!c.or.length) return \"TRUE\";\n return `(${c.or.map(renderFilter).join(\" OR \")})`;\n }\n return renderFilter(c);\n };\n\n if (!cfg) {\n params.push(collection);\n where.push(`collection = $${params.length}`);\n }\n\n for (const c of q?.filters ?? []) {\n where.push(renderCondition(c));\n }\n\n const orderBy =\n (q?.orderBy ?? []).map((o) => `${colExpr(o.field)} ${o.direction === \"asc\" ? \"ASC\" : \"DESC\"}`).join(\", \") ||\n \"created_at DESC\";\n\n const limit = q?.limit != null ? ` LIMIT ${Number(q.limit)}` : \"\";\n const offset = q?.offset != null ? ` OFFSET ${Number(q.offset)}` : \"\";\n const whereSql = where.length ? ` WHERE ${where.join(\" AND \")}` : \"\";\n const table = cfg ? cfg.table : this.documentsTable;\n const select = cfg ? \"*\" : \"id, data\";\n\n const { rows } = await this.pool.query(\n `SELECT ${select} FROM ${ident(table)}${whereSql} ORDER BY ${orderBy}${limit}${offset}`,\n params,\n );\n\n if (cfg) {\n return rows.map((r) => this.fromTypedRow(collection, cfg, r)) as unknown as (T & {\n id: string;\n })[];\n }\n return rows.map((r) => ({ id: r.id, collection, ...r.data })) as unknown as (T & {\n id: string;\n })[];\n }\n\n // --- writes ----------------------------------------------------------------\n\n async create<T = Record<string, unknown>>(\n collection: string,\n data: T,\n ): Promise<T & { id: string }> {\n return this.createWithId(collection, randomUUID(), data);\n }\n\n async createWithId<T = Record<string, unknown>>(\n collection: string,\n id: string,\n data: T,\n ): Promise<T & { id: string }> {\n const cfg = this.cfg(collection);\n const record = data as Record<string, unknown>;\n\n if (cfg) {\n const { known, extra } = this.toTypedRow(cfg, record);\n const cols = [\"id\", ...Object.keys(known), \"extra\"];\n const vals = [id, ...Object.values(known), JSON.stringify(extra)];\n const placeholders = cols.map((_, i) => `$${i + 1}`).join(\", \");\n await this.pool.query(\n `INSERT INTO ${ident(cfg.table)} (${cols.map(ident).join(\", \")})\n VALUES (${placeholders})`,\n vals,\n );\n } else {\n await this.pool.query(\n `INSERT INTO ${ident(this.documentsTable)} (id, collection, data)\n VALUES ($1, $2, $3)`,\n [id, collection, JSON.stringify(record)],\n );\n }\n return { id, ...(data as T) };\n }\n\n async update<T = Record<string, unknown>>(\n collection: string,\n id: string,\n data: Partial<T>,\n ): Promise<void> {\n await this.write(collection, id, data, \"update\");\n }\n\n async upsert<T = Record<string, unknown>>(\n collection: string,\n id: string,\n data: Partial<T>,\n ): Promise<void> {\n await this.write(collection, id, data, \"upsert\");\n }\n\n private async write(\n collection: string,\n id: string,\n data: Record<string, unknown>,\n mode: \"update\" | \"upsert\",\n ): Promise<void> {\n const cfg = this.cfg(collection);\n\n if (cfg) {\n const { known, extra } = this.toTypedRow(cfg, data);\n const setCols = Object.keys(known);\n const setExpr = setCols\n .map((c, i) => `${ident(c)} = $${i + 2}`)\n .join(\", \");\n // Merge into extra so unmapped fields aren't dropped.\n const extraIdx = setCols.length + 2;\n const sets = [\n setExpr,\n // Table-qualified: in ON CONFLICT DO UPDATE both the target table and the\n // `excluded` pseudo-relation expose `extra`, so a bare ref is ambiguous.\n `extra = ${ident(cfg.table)}.${ident(\"extra\")} || $${extraIdx}::jsonb`,\n `updated_at = now()`,\n ]\n .filter(Boolean)\n .join(\", \");\n const params = [id, ...Object.values(known), JSON.stringify(extra)];\n\n if (mode === \"upsert\") {\n const insertCols = [\"id\", ...setCols, \"extra\"];\n const insertVals = params;\n const placeholders = insertCols.map((_, i) => `$${i + 1}`).join(\", \");\n await this.pool.query(\n `INSERT INTO ${ident(cfg.table)} (${insertCols.map(ident).join(\", \")})\n VALUES (${placeholders})\n ON CONFLICT (id) DO UPDATE SET ${sets}`,\n insertVals,\n );\n } else {\n await this.pool.query(\n `UPDATE ${ident(cfg.table)} SET ${sets} WHERE id = $1`,\n params,\n );\n }\n return;\n }\n\n // JSONB documents table: shallow-merge the patch into `data`.\n if (mode === \"upsert\") {\n await this.pool.query(\n `INSERT INTO ${ident(this.documentsTable)} (id, collection, data)\n VALUES ($1, $2, $3)\n ON CONFLICT (collection, id) DO UPDATE\n SET data = ${ident(this.documentsTable)}.data || $3::jsonb,\n updated_at = now()`,\n [id, collection, JSON.stringify(data)],\n );\n } else {\n await this.pool.query(\n `UPDATE ${ident(this.documentsTable)}\n SET data = data || $2::jsonb, updated_at = now()\n WHERE id = $1 AND collection = $3`,\n [id, JSON.stringify(data), collection],\n );\n }\n }\n\n async delete(collection: string, id: string): Promise<void> {\n const cfg = this.cfg(collection);\n if (cfg) {\n await this.pool.query(`DELETE FROM ${ident(cfg.table)} WHERE id = $1`, [id]);\n } else {\n await this.pool.query(\n `DELETE FROM ${ident(this.documentsTable)} WHERE id = $1 AND collection = $2`,\n [id, collection],\n );\n }\n }\n}\n\n/** Quote a SQL identifier safely. */\nfunction ident(name: string): string {\n return `\"${name.replace(/\"/g, '\"\"')}\"`;\n}\n\n/** Quote a SQL string literal (used for static jsonb keys, never user values). */\nfunction literal(value: string): string {\n return `'${value.replace(/'/g, \"''\")}'`;\n}\n","/**\n * Shared, runtime-free types. Safe to import in any environment (client or\n * server) — this module pulls in zero runtime dependencies.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\n/**\n * Engine addressing fields. The engine needs to know an entity's `id` and which\n * `collection` it lives in to route saves; everything else is the consumer's own\n * shape. Kept separate from the user's content type `T` so we never force these\n * onto domain models — use {@link Editable}<YourType> when you want both.\n */\nexport interface EntityAddress {\n id: string;\n collection: string;\n}\n\n/**\n * The user's content type `T` decorated with the engine's addressing fields.\n * `T` is completely unconstrained — bring any shape; the engine only adds `id`\n * and `collection`.\n */\nexport type Editable<T = Record<string, any>> = T & EntityAddress;\n\n/**\n * A single editable record. Schemaless by design. Equivalent to\n * `Editable<Record<string, any>>`; kept as a named alias for the common case and\n * for backwards compatibility.\n */\nexport type Section = Editable;\n\nexport type SectionMap = Record<string, Section>;\n\n/**\n * The shape the engine holds in memory:\n * `{ [collection]: { [sectionKey]: Section } }`.\n */\nexport type NestedSections = {\n [collection: string]: {\n [sectionKey: string]: Section;\n };\n};\n\n/**\n * A single item in an editable collection list (e.g. a project or a tool). Just\n * a record with a stable `id`. Used by the client provider's collection ops\n * (create / delete / reorder) which manage *which* items exist and their order,\n * complementing {@link Section}s which manage a single item's editable fields.\n */\nexport type CollectionItem = Record<string, unknown> & { id: string };\n\n/**\n * An image edit queued in the provider. The actual upload is deferred until\n * save. `file` is null when the user pasted an external URL (`isExternal`).\n */\nexport interface PendingImage {\n file: File | null;\n localUrl: string;\n sectionKey: string;\n fieldKey: string;\n collection: string;\n docId: string;\n isExternal?: boolean;\n}\n\n/**\n * Neutral query language. Keeps any backend's native query type (Firestore's\n * `Query`, SQL, etc.) from leaking through the engine.\n *\n * Op support is backend-dependent — see each adapter. `contains` is a\n * case-insensitive substring match; `in`/`nin` take an array value.\n */\nexport type QueryFilterOp =\n | \"eq\"\n | \"ne\"\n | \"lt\"\n | \"lte\"\n | \"gt\"\n | \"gte\"\n | \"in\"\n | \"nin\"\n | \"contains\";\n\n/** A single field condition. */\nexport type QueryFilter = { field: string; op: QueryFilterOp; value: unknown };\n\n/**\n * A disjunction: the inner filters are combined with OR. Sits alongside plain\n * filters in `Query.filters`, which are combined with AND at the top level.\n */\nexport type QueryFilterGroup = { or: QueryFilter[] };\n\n/** Either a bare condition (AND-ed) or an OR group. */\nexport type QueryCondition = QueryFilter | QueryFilterGroup;\n\nexport type Query = {\n /** Top-level conditions combined with AND. Use `{ or: [...] }` for disjunction. */\n filters?: QueryCondition[];\n orderBy?: { field: string; direction: \"asc\" | \"desc\" }[];\n limit?: number;\n /** Skip this many rows (offset pagination). */\n offset?: number;\n /**\n * Fields holding references to other documents to inline-resolve after the\n * primary fetch. Resolved by {@link resolveRelations}, never by the adapter.\n */\n populate?: string[];\n};\n\n/** Narrow a {@link QueryCondition} to an OR group. */\nexport function isFilterGroup(c: QueryCondition): c is QueryFilterGroup {\n return typeof c === \"object\" && c !== null && \"or\" in c;\n}\n\n/**\n * A reference to another document. Either self-describing (`{ collection, id }`)\n * or a bare id string resolved via a {@link RelationConfig}.\n */\nexport type Ref = { collection: string; id: string };\n\n/**\n * Maps a reference field name → the collection it points to. Needed only when\n * refs are stored as bare id strings (self-describing `Ref` objects don't need\n * it). Used by {@link resolveRelations}.\n */\nexport type RelationConfig = Record<string, { collection: string }>;\n\n/**\n * Persistence contract. Every backend (Firestore, Postgres, …) implements this;\n * the engine and the route factory only ever speak to this interface.\n */\nexport interface DataAdapter {\n fetchCollection<T = Record<string, any>>(\n collection: string,\n q?: Query,\n ): Promise<(T & { id: string })[]>;\n fetchById<T = Record<string, any>>(\n collection: string,\n id: string,\n ): Promise<(T & { id: string }) | null>;\n create<T = Record<string, any>>(\n collection: string,\n data: T,\n ): Promise<T & { id: string }>;\n createWithId<T = Record<string, any>>(\n collection: string,\n id: string,\n data: T,\n ): Promise<T & { id: string }>;\n update<T = Record<string, any>>(\n collection: string,\n id: string,\n data: Partial<T>,\n ): Promise<void>;\n upsert<T = Record<string, any>>(\n collection: string,\n id: string,\n data: Partial<T>,\n ): Promise<void>;\n delete(collection: string, id: string): Promise<void>;\n}\n\n/**\n * The identity resolved from an incoming request by an {@link AuthAdapter}.\n * Minimal by design: only `isAdmin` is meaningful to the default gate. Carry any\n * additional claims (roles, scopes, tenant, …) as extra keys and authorize on\n * them with a custom `authorize` predicate. `userId`/`email` are conventional\n * but optional.\n */\nexport interface AuthIdentity {\n isAdmin: boolean;\n userId?: string;\n email?: string;\n [claim: string]: unknown;\n}\n\n/**\n * Decides whether a resolved identity may perform admin actions. Defaults to\n * `identity.isAdmin === true`; override to gate on roles/scopes/etc.\n */\nexport type AuthorizeFn = (\n identity: AuthIdentity,\n req: Request,\n) => boolean | Promise<boolean>;\n\n/**\n * Server-side auth contract. Gates every admin API route. Return `null` to\n * reject outright; otherwise the gate's `authorize` predicate decides.\n */\nexport interface AuthAdapter {\n verifyRequest(req: Request): Promise<AuthIdentity | null>;\n}\n\n/**\n * Client half of storage: performs the browser-side upload and returns the\n * final URL. Has zero server dependencies, so it is safe to import in client\n * components. Built from e.g. `@dalgoridim/headless-cms/storage/cloudinary`.\n */\nexport interface ClientStorageAdapter {\n upload(file: File): Promise<{ url: string }>;\n}\n\n/**\n * Server half of storage: issues a presign / signature (or, for local storage,\n * writes the file). Mounted by the route factory at `${apiBasePath}/sign`.\n * Pulls in server-only SDKs, so it lives in a `/server` subpath. Built from e.g.\n * `@dalgoridim/headless-cms/storage/cloudinary/server`.\n */\nexport interface ServerStorageAdapter {\n sign(req: Request): Promise<unknown>;\n}\n\n/** Full two-sided contract (rarely needed; client and server halves are split). */\nexport interface StorageAdapter extends ClientStorageAdapter {\n sign?(req: Request): Promise<unknown>;\n}\n\n/**\n * Minimal client-side auth state the edit primitives depend on. Any auth\n * implementation (Firebase, NextAuth, custom) provides this via a context;\n * `@dalgoridim/headless-cms/auth/firebase/client` ships the default.\n */\nexport interface CmsAuthState {\n isAdmin: boolean;\n isEditing: boolean;\n toggleEdit: () => void;\n}\n"],"mappings":"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","names":["rows"]}
@@ -33,7 +33,17 @@ declare class PostgresDataAdapter implements DataAdapter {
33
33
  private readonly documentsTable;
34
34
  constructor(config?: PostgresAdapterConfig);
35
35
  private cfg;
36
- /** Create the documents table and every registered typed table if missing. */
36
+ /**
37
+ * Create the documents table and every registered typed table if missing,
38
+ * then **additively** reconcile each table's columns: any registered column
39
+ * not yet present is added via `ALTER TABLE ... ADD COLUMN IF NOT EXISTS`.
40
+ *
41
+ * This makes `migrate()` idempotent *and* forward-compatible — adding a new
42
+ * column (or a whole new collection) to your config and re-running `migrate()`
43
+ * evolves an existing database without a destructive reset. It is additive
44
+ * only: it never drops, renames, or retypes columns (those need a real
45
+ * migration). Safe to run on every boot/deploy.
46
+ */
37
47
  migrate(): Promise<void>;
38
48
  /** Split a section into typed columns + leftover `extra`. */
39
49
  private toTypedRow;
@@ -33,7 +33,17 @@ declare class PostgresDataAdapter implements DataAdapter {
33
33
  private readonly documentsTable;
34
34
  constructor(config?: PostgresAdapterConfig);
35
35
  private cfg;
36
- /** Create the documents table and every registered typed table if missing. */
36
+ /**
37
+ * Create the documents table and every registered typed table if missing,
38
+ * then **additively** reconcile each table's columns: any registered column
39
+ * not yet present is added via `ALTER TABLE ... ADD COLUMN IF NOT EXISTS`.
40
+ *
41
+ * This makes `migrate()` idempotent *and* forward-compatible — adding a new
42
+ * column (or a whole new collection) to your config and re-running `migrate()`
43
+ * evolves an existing database without a destructive reset. It is additive
44
+ * only: it never drops, renames, or retypes columns (those need a real
45
+ * migration). Safe to run on every boot/deploy.
46
+ */
37
47
  migrate(): Promise<void>;
38
48
  /** Split a section into typed columns + leftover `extra`. */
39
49
  private toTypedRow;
@@ -72,7 +72,17 @@ var PostgresDataAdapter = class {
72
72
  cfg(collection) {
73
73
  return this.collections[collection];
74
74
  }
75
- /** Create the documents table and every registered typed table if missing. */
75
+ /**
76
+ * Create the documents table and every registered typed table if missing,
77
+ * then **additively** reconcile each table's columns: any registered column
78
+ * not yet present is added via `ALTER TABLE ... ADD COLUMN IF NOT EXISTS`.
79
+ *
80
+ * This makes `migrate()` idempotent *and* forward-compatible — adding a new
81
+ * column (or a whole new collection) to your config and re-running `migrate()`
82
+ * evolves an existing database without a destructive reset. It is additive
83
+ * only: it never drops, renames, or retypes columns (those need a real
84
+ * migration). Safe to run on every boot/deploy.
85
+ */
76
86
  async migrate() {
77
87
  await this.pool.query(`
78
88
  CREATE TABLE IF NOT EXISTS ${ident(this.documentsTable)} (
@@ -99,6 +109,11 @@ var PostgresDataAdapter = class {
99
109
  updated_at timestamptz DEFAULT now()
100
110
  );
101
111
  `);
112
+ for (const [name, type] of Object.entries(cfg.columns)) {
113
+ await this.pool.query(
114
+ `ALTER TABLE ${ident(cfg.table)} ADD COLUMN IF NOT EXISTS ${ident(name)} ${SQL_TYPE[type]};`
115
+ );
116
+ }
102
117
  }
103
118
  }
104
119
  // --- row <-> section mapping ------------------------------------------------
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/adapters/postgres/index.ts","../../../src/types.ts"],"sourcesContent":["import { Pool } from \"pg\";\nimport { randomUUID } from \"node:crypto\";\nimport type {\n DataAdapter,\n Query,\n QueryCondition,\n QueryFilter,\n QueryFilterOp,\n} from \"../../types\";\nimport { isFilterGroup } from \"../../types\";\n\n/** Column type hints for a registered (typed) collection. */\nexport type ColumnType = \"text\" | \"int\" | \"float\" | \"bool\" | \"date\" | \"timestamptz\" | \"jsonb\";\n\nexport interface CollectionConfig {\n /** Physical table name. */\n table: string;\n /** Map of section field → SQL column type. Unlisted fields fall into `extra`. */\n columns: Record<string, ColumnType>;\n}\n\nexport interface PostgresAdapterConfig {\n /** Provide a connection string… */\n connectionString?: string;\n /** …or an existing pg Pool. */\n pool?: Pool;\n /**\n * Registry of typed collections. Anything not registered here is stored in\n * the shared JSONB `documents` table.\n */\n collections?: Record<string, CollectionConfig>;\n /** Name of the fallback JSONB table. Default `documents`. */\n documentsTable?: string;\n}\n\n/** Binary ops that render as `col <op> $n`. */\nconst BINARY_OP: Partial<Record<QueryFilterOp, string>> = {\n eq: \"=\",\n ne: \"<>\",\n lt: \"<\",\n lte: \"<=\",\n gt: \">\",\n gte: \">=\",\n};\n\nconst SQL_TYPE: Record<ColumnType, string> = {\n text: \"text\",\n int: \"integer\",\n float: \"double precision\",\n bool: \"boolean\",\n date: \"date\",\n timestamptz: \"timestamptz\",\n jsonb: \"jsonb\",\n};\n\n/**\n * Hybrid Postgres adapter. Unregistered collections live in a shared JSONB\n * `documents` table; registered collections map flat section fields onto typed\n * columns, with a JSONB `extra` column so unmapped fields are never dropped.\n */\nexport class PostgresDataAdapter implements DataAdapter {\n private readonly pool: Pool;\n private readonly collections: Record<string, CollectionConfig>;\n private readonly documentsTable: string;\n\n constructor(config: PostgresAdapterConfig = {}) {\n if (config.pool) {\n this.pool = config.pool;\n } else if (config.connectionString) {\n this.pool = new Pool({ connectionString: config.connectionString });\n } else {\n throw new Error(\n \"PostgresDataAdapter requires either `pool` or `connectionString`.\",\n );\n }\n this.collections = config.collections ?? {};\n this.documentsTable = config.documentsTable ?? \"documents\";\n }\n\n private cfg(collection: string): CollectionConfig | undefined {\n return this.collections[collection];\n }\n\n /** Create the documents table and every registered typed table if missing. */\n async migrate(): Promise<void> {\n await this.pool.query(`\n CREATE TABLE IF NOT EXISTS ${ident(this.documentsTable)} (\n id text NOT NULL,\n collection text NOT NULL,\n data jsonb NOT NULL,\n created_at timestamptz DEFAULT now(),\n updated_at timestamptz DEFAULT now(),\n PRIMARY KEY (collection, id)\n );\n `);\n await this.pool.query(\n `CREATE INDEX IF NOT EXISTS ${ident(this.documentsTable + \"_collection_created_idx\")}\n ON ${ident(this.documentsTable)} (collection, created_at DESC);`,\n );\n\n for (const cfg of Object.values(this.collections)) {\n const cols = Object.entries(cfg.columns)\n .map(([name, type]) => `${ident(name)} ${SQL_TYPE[type]}`)\n .join(\",\\n \");\n await this.pool.query(`\n CREATE TABLE IF NOT EXISTS ${ident(cfg.table)} (\n id text PRIMARY KEY,\n ${cols ? cols + \",\" : \"\"}\n extra jsonb NOT NULL DEFAULT '{}'::jsonb,\n created_at timestamptz DEFAULT now(),\n updated_at timestamptz DEFAULT now()\n );\n `);\n }\n }\n\n // --- row <-> section mapping ------------------------------------------------\n\n /** Split a section into typed columns + leftover `extra`. */\n private toTypedRow(cfg: CollectionConfig, data: Record<string, unknown>) {\n const known: Record<string, unknown> = {};\n const extra: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(data)) {\n if (key === \"id\" || key === \"collection\") continue;\n if (key in cfg.columns) known[key] = value;\n else extra[key] = value;\n }\n return { known, extra };\n }\n\n private fromTypedRow(\n collection: string,\n cfg: CollectionConfig,\n row: Record<string, unknown>,\n ) {\n const { id, extra, created_at, updated_at, ...rest } = row;\n void created_at;\n void updated_at;\n return {\n id: id as string,\n collection,\n ...(rest as Record<string, unknown>),\n ...((extra as Record<string, unknown>) ?? {}),\n };\n }\n\n // --- reads -----------------------------------------------------------------\n\n async fetchById<T = Record<string, unknown>>(\n collection: string,\n id: string,\n ): Promise<(T & { id: string }) | null> {\n const cfg = this.cfg(collection);\n if (cfg) {\n const { rows } = await this.pool.query(\n `SELECT * FROM ${ident(cfg.table)} WHERE id = $1`,\n [id],\n );\n if (!rows[0]) return null;\n return this.fromTypedRow(collection, cfg, rows[0]) as unknown as T & {\n id: string;\n };\n }\n const { rows } = await this.pool.query(\n `SELECT id, data FROM ${ident(this.documentsTable)} WHERE id = $1 AND collection = $2`,\n [id, collection],\n );\n if (!rows[0]) return null;\n return { id: rows[0].id, collection, ...rows[0].data } as unknown as T & {\n id: string;\n };\n }\n\n async fetchCollection<T = Record<string, unknown>>(\n collection: string,\n q?: Query,\n ): Promise<(T & { id: string })[]> {\n const cfg = this.cfg(collection);\n const params: unknown[] = [];\n const where: string[] = [];\n\n const colExpr = (field: string): string => {\n if (cfg) {\n return field in cfg.columns ? ident(field) : `extra->>${literal(field)}`;\n }\n return `data->>${literal(field)}`;\n };\n\n /** Render one field condition to SQL, pushing its bound params. */\n const renderFilter = (f: QueryFilter): string => {\n const col = colExpr(f.field);\n if (f.op === \"in\" || f.op === \"nin\") {\n params.push(f.value);\n const cmp = f.op === \"in\" ? \"= ANY\" : \"<> ALL\";\n return `${col} ${cmp}($${params.length})`;\n }\n if (f.op === \"contains\") {\n params.push(`%${String(f.value)}%`);\n return `${col} ILIKE $${params.length}`;\n }\n const op = BINARY_OP[f.op];\n if (!op) throw new Error(`Unsupported query op: ${f.op}`);\n params.push(f.value);\n return `${col} ${op} $${params.length}`;\n };\n\n /** Render an AND-level condition, expanding OR groups into `(a OR b)`. */\n const renderCondition = (c: QueryCondition): string => {\n if (isFilterGroup(c)) {\n if (!c.or.length) return \"TRUE\";\n return `(${c.or.map(renderFilter).join(\" OR \")})`;\n }\n return renderFilter(c);\n };\n\n if (!cfg) {\n params.push(collection);\n where.push(`collection = $${params.length}`);\n }\n\n for (const c of q?.filters ?? []) {\n where.push(renderCondition(c));\n }\n\n const orderBy =\n (q?.orderBy ?? []).map((o) => `${colExpr(o.field)} ${o.direction === \"asc\" ? \"ASC\" : \"DESC\"}`).join(\", \") ||\n \"created_at DESC\";\n\n const limit = q?.limit != null ? ` LIMIT ${Number(q.limit)}` : \"\";\n const offset = q?.offset != null ? ` OFFSET ${Number(q.offset)}` : \"\";\n const whereSql = where.length ? ` WHERE ${where.join(\" AND \")}` : \"\";\n const table = cfg ? cfg.table : this.documentsTable;\n const select = cfg ? \"*\" : \"id, data\";\n\n const { rows } = await this.pool.query(\n `SELECT ${select} FROM ${ident(table)}${whereSql} ORDER BY ${orderBy}${limit}${offset}`,\n params,\n );\n\n if (cfg) {\n return rows.map((r) => this.fromTypedRow(collection, cfg, r)) as unknown as (T & {\n id: string;\n })[];\n }\n return rows.map((r) => ({ id: r.id, collection, ...r.data })) as unknown as (T & {\n id: string;\n })[];\n }\n\n // --- writes ----------------------------------------------------------------\n\n async create<T = Record<string, unknown>>(\n collection: string,\n data: T,\n ): Promise<T & { id: string }> {\n return this.createWithId(collection, randomUUID(), data);\n }\n\n async createWithId<T = Record<string, unknown>>(\n collection: string,\n id: string,\n data: T,\n ): Promise<T & { id: string }> {\n const cfg = this.cfg(collection);\n const record = data as Record<string, unknown>;\n\n if (cfg) {\n const { known, extra } = this.toTypedRow(cfg, record);\n const cols = [\"id\", ...Object.keys(known), \"extra\"];\n const vals = [id, ...Object.values(known), JSON.stringify(extra)];\n const placeholders = cols.map((_, i) => `$${i + 1}`).join(\", \");\n await this.pool.query(\n `INSERT INTO ${ident(cfg.table)} (${cols.map(ident).join(\", \")})\n VALUES (${placeholders})`,\n vals,\n );\n } else {\n await this.pool.query(\n `INSERT INTO ${ident(this.documentsTable)} (id, collection, data)\n VALUES ($1, $2, $3)`,\n [id, collection, JSON.stringify(record)],\n );\n }\n return { id, ...(data as T) };\n }\n\n async update<T = Record<string, unknown>>(\n collection: string,\n id: string,\n data: Partial<T>,\n ): Promise<void> {\n await this.write(collection, id, data, \"update\");\n }\n\n async upsert<T = Record<string, unknown>>(\n collection: string,\n id: string,\n data: Partial<T>,\n ): Promise<void> {\n await this.write(collection, id, data, \"upsert\");\n }\n\n private async write(\n collection: string,\n id: string,\n data: Record<string, unknown>,\n mode: \"update\" | \"upsert\",\n ): Promise<void> {\n const cfg = this.cfg(collection);\n\n if (cfg) {\n const { known, extra } = this.toTypedRow(cfg, data);\n const setCols = Object.keys(known);\n const setExpr = setCols\n .map((c, i) => `${ident(c)} = $${i + 2}`)\n .join(\", \");\n // Merge into extra so unmapped fields aren't dropped.\n const extraIdx = setCols.length + 2;\n const sets = [\n setExpr,\n // Table-qualified: in ON CONFLICT DO UPDATE both the target table and the\n // `excluded` pseudo-relation expose `extra`, so a bare ref is ambiguous.\n `extra = ${ident(cfg.table)}.${ident(\"extra\")} || $${extraIdx}::jsonb`,\n `updated_at = now()`,\n ]\n .filter(Boolean)\n .join(\", \");\n const params = [id, ...Object.values(known), JSON.stringify(extra)];\n\n if (mode === \"upsert\") {\n const insertCols = [\"id\", ...setCols, \"extra\"];\n const insertVals = params;\n const placeholders = insertCols.map((_, i) => `$${i + 1}`).join(\", \");\n await this.pool.query(\n `INSERT INTO ${ident(cfg.table)} (${insertCols.map(ident).join(\", \")})\n VALUES (${placeholders})\n ON CONFLICT (id) DO UPDATE SET ${sets}`,\n insertVals,\n );\n } else {\n await this.pool.query(\n `UPDATE ${ident(cfg.table)} SET ${sets} WHERE id = $1`,\n params,\n );\n }\n return;\n }\n\n // JSONB documents table: shallow-merge the patch into `data`.\n if (mode === \"upsert\") {\n await this.pool.query(\n `INSERT INTO ${ident(this.documentsTable)} (id, collection, data)\n VALUES ($1, $2, $3)\n ON CONFLICT (collection, id) DO UPDATE\n SET data = ${ident(this.documentsTable)}.data || $3::jsonb,\n updated_at = now()`,\n [id, collection, JSON.stringify(data)],\n );\n } else {\n await this.pool.query(\n `UPDATE ${ident(this.documentsTable)}\n SET data = data || $2::jsonb, updated_at = now()\n WHERE id = $1 AND collection = $3`,\n [id, JSON.stringify(data), collection],\n );\n }\n }\n\n async delete(collection: string, id: string): Promise<void> {\n const cfg = this.cfg(collection);\n if (cfg) {\n await this.pool.query(`DELETE FROM ${ident(cfg.table)} WHERE id = $1`, [id]);\n } else {\n await this.pool.query(\n `DELETE FROM ${ident(this.documentsTable)} WHERE id = $1 AND collection = $2`,\n [id, collection],\n );\n }\n }\n}\n\n/** Quote a SQL identifier safely. */\nfunction ident(name: string): string {\n return `\"${name.replace(/\"/g, '\"\"')}\"`;\n}\n\n/** Quote a SQL string literal (used for static jsonb keys, never user values). */\nfunction literal(value: string): string {\n return `'${value.replace(/'/g, \"''\")}'`;\n}\n","/**\n * Shared, runtime-free types. Safe to import in any environment (client or\n * server) — this module pulls in zero runtime dependencies.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\n/**\n * Engine addressing fields. The engine needs to know an entity's `id` and which\n * `collection` it lives in to route saves; everything else is the consumer's own\n * shape. Kept separate from the user's content type `T` so we never force these\n * onto domain models — use {@link Editable}<YourType> when you want both.\n */\nexport interface EntityAddress {\n id: string;\n collection: string;\n}\n\n/**\n * The user's content type `T` decorated with the engine's addressing fields.\n * `T` is completely unconstrained — bring any shape; the engine only adds `id`\n * and `collection`.\n */\nexport type Editable<T = Record<string, any>> = T & EntityAddress;\n\n/**\n * A single editable record. Schemaless by design. Equivalent to\n * `Editable<Record<string, any>>`; kept as a named alias for the common case and\n * for backwards compatibility.\n */\nexport type Section = Editable;\n\nexport type SectionMap = Record<string, Section>;\n\n/**\n * The shape the engine holds in memory:\n * `{ [collection]: { [sectionKey]: Section } }`.\n */\nexport type NestedSections = {\n [collection: string]: {\n [sectionKey: string]: Section;\n };\n};\n\n/**\n * A single item in an editable collection list (e.g. a project or a tool). Just\n * a record with a stable `id`. Used by the client provider's collection ops\n * (create / delete / reorder) which manage *which* items exist and their order,\n * complementing {@link Section}s which manage a single item's editable fields.\n */\nexport type CollectionItem = Record<string, unknown> & { id: string };\n\n/**\n * An image edit queued in the provider. The actual upload is deferred until\n * save. `file` is null when the user pasted an external URL (`isExternal`).\n */\nexport interface PendingImage {\n file: File | null;\n localUrl: string;\n sectionKey: string;\n fieldKey: string;\n collection: string;\n docId: string;\n isExternal?: boolean;\n}\n\n/**\n * Neutral query language. Keeps any backend's native query type (Firestore's\n * `Query`, SQL, etc.) from leaking through the engine.\n *\n * Op support is backend-dependent — see each adapter. `contains` is a\n * case-insensitive substring match; `in`/`nin` take an array value.\n */\nexport type QueryFilterOp =\n | \"eq\"\n | \"ne\"\n | \"lt\"\n | \"lte\"\n | \"gt\"\n | \"gte\"\n | \"in\"\n | \"nin\"\n | \"contains\";\n\n/** A single field condition. */\nexport type QueryFilter = { field: string; op: QueryFilterOp; value: unknown };\n\n/**\n * A disjunction: the inner filters are combined with OR. Sits alongside plain\n * filters in `Query.filters`, which are combined with AND at the top level.\n */\nexport type QueryFilterGroup = { or: QueryFilter[] };\n\n/** Either a bare condition (AND-ed) or an OR group. */\nexport type QueryCondition = QueryFilter | QueryFilterGroup;\n\nexport type Query = {\n /** Top-level conditions combined with AND. Use `{ or: [...] }` for disjunction. */\n filters?: QueryCondition[];\n orderBy?: { field: string; direction: \"asc\" | \"desc\" }[];\n limit?: number;\n /** Skip this many rows (offset pagination). */\n offset?: number;\n /**\n * Fields holding references to other documents to inline-resolve after the\n * primary fetch. Resolved by {@link resolveRelations}, never by the adapter.\n */\n populate?: string[];\n};\n\n/** Narrow a {@link QueryCondition} to an OR group. */\nexport function isFilterGroup(c: QueryCondition): c is QueryFilterGroup {\n return typeof c === \"object\" && c !== null && \"or\" in c;\n}\n\n/**\n * A reference to another document. Either self-describing (`{ collection, id }`)\n * or a bare id string resolved via a {@link RelationConfig}.\n */\nexport type Ref = { collection: string; id: string };\n\n/**\n * Maps a reference field name → the collection it points to. Needed only when\n * refs are stored as bare id strings (self-describing `Ref` objects don't need\n * it). Used by {@link resolveRelations}.\n */\nexport type RelationConfig = Record<string, { collection: string }>;\n\n/**\n * Persistence contract. Every backend (Firestore, Postgres, …) implements this;\n * the engine and the route factory only ever speak to this interface.\n */\nexport interface DataAdapter {\n fetchCollection<T = Record<string, any>>(\n collection: string,\n q?: Query,\n ): Promise<(T & { id: string })[]>;\n fetchById<T = Record<string, any>>(\n collection: string,\n id: string,\n ): Promise<(T & { id: string }) | null>;\n create<T = Record<string, any>>(\n collection: string,\n data: T,\n ): Promise<T & { id: string }>;\n createWithId<T = Record<string, any>>(\n collection: string,\n id: string,\n data: T,\n ): Promise<T & { id: string }>;\n update<T = Record<string, any>>(\n collection: string,\n id: string,\n data: Partial<T>,\n ): Promise<void>;\n upsert<T = Record<string, any>>(\n collection: string,\n id: string,\n data: Partial<T>,\n ): Promise<void>;\n delete(collection: string, id: string): Promise<void>;\n}\n\n/**\n * The identity resolved from an incoming request by an {@link AuthAdapter}.\n * Minimal by design: only `isAdmin` is meaningful to the default gate. Carry any\n * additional claims (roles, scopes, tenant, …) as extra keys and authorize on\n * them with a custom `authorize` predicate. `userId`/`email` are conventional\n * but optional.\n */\nexport interface AuthIdentity {\n isAdmin: boolean;\n userId?: string;\n email?: string;\n [claim: string]: unknown;\n}\n\n/**\n * Decides whether a resolved identity may perform admin actions. Defaults to\n * `identity.isAdmin === true`; override to gate on roles/scopes/etc.\n */\nexport type AuthorizeFn = (\n identity: AuthIdentity,\n req: Request,\n) => boolean | Promise<boolean>;\n\n/**\n * Server-side auth contract. Gates every admin API route. Return `null` to\n * reject outright; otherwise the gate's `authorize` predicate decides.\n */\nexport interface AuthAdapter {\n verifyRequest(req: Request): Promise<AuthIdentity | null>;\n}\n\n/**\n * Client half of storage: performs the browser-side upload and returns the\n * final URL. Has zero server dependencies, so it is safe to import in client\n * components. Built from e.g. `@dalgoridim/headless-cms/storage/cloudinary`.\n */\nexport interface ClientStorageAdapter {\n upload(file: File): Promise<{ url: string }>;\n}\n\n/**\n * Server half of storage: issues a presign / signature (or, for local storage,\n * writes the file). Mounted by the route factory at `${apiBasePath}/sign`.\n * Pulls in server-only SDKs, so it lives in a `/server` subpath. Built from e.g.\n * `@dalgoridim/headless-cms/storage/cloudinary/server`.\n */\nexport interface ServerStorageAdapter {\n sign(req: Request): Promise<unknown>;\n}\n\n/** Full two-sided contract (rarely needed; client and server halves are split). */\nexport interface StorageAdapter extends ClientStorageAdapter {\n sign?(req: Request): Promise<unknown>;\n}\n\n/**\n * Minimal client-side auth state the edit primitives depend on. Any auth\n * implementation (Firebase, NextAuth, custom) provides this via a context;\n * `@dalgoridim/headless-cms/auth/firebase/client` ships the default.\n */\nexport interface CmsAuthState {\n isAdmin: boolean;\n isEditing: boolean;\n toggleEdit: () => void;\n}\n"],"mappings":"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","names":["rows"]}
1
+ {"version":3,"sources":["../../../src/adapters/postgres/index.ts","../../../src/types.ts"],"sourcesContent":["import { Pool } from \"pg\";\nimport { randomUUID } from \"node:crypto\";\nimport type {\n DataAdapter,\n Query,\n QueryCondition,\n QueryFilter,\n QueryFilterOp,\n} from \"../../types\";\nimport { isFilterGroup } from \"../../types\";\n\n/** Column type hints for a registered (typed) collection. */\nexport type ColumnType = \"text\" | \"int\" | \"float\" | \"bool\" | \"date\" | \"timestamptz\" | \"jsonb\";\n\nexport interface CollectionConfig {\n /** Physical table name. */\n table: string;\n /** Map of section field → SQL column type. Unlisted fields fall into `extra`. */\n columns: Record<string, ColumnType>;\n}\n\nexport interface PostgresAdapterConfig {\n /** Provide a connection string… */\n connectionString?: string;\n /** …or an existing pg Pool. */\n pool?: Pool;\n /**\n * Registry of typed collections. Anything not registered here is stored in\n * the shared JSONB `documents` table.\n */\n collections?: Record<string, CollectionConfig>;\n /** Name of the fallback JSONB table. Default `documents`. */\n documentsTable?: string;\n}\n\n/** Binary ops that render as `col <op> $n`. */\nconst BINARY_OP: Partial<Record<QueryFilterOp, string>> = {\n eq: \"=\",\n ne: \"<>\",\n lt: \"<\",\n lte: \"<=\",\n gt: \">\",\n gte: \">=\",\n};\n\nconst SQL_TYPE: Record<ColumnType, string> = {\n text: \"text\",\n int: \"integer\",\n float: \"double precision\",\n bool: \"boolean\",\n date: \"date\",\n timestamptz: \"timestamptz\",\n jsonb: \"jsonb\",\n};\n\n/**\n * Hybrid Postgres adapter. Unregistered collections live in a shared JSONB\n * `documents` table; registered collections map flat section fields onto typed\n * columns, with a JSONB `extra` column so unmapped fields are never dropped.\n */\nexport class PostgresDataAdapter implements DataAdapter {\n private readonly pool: Pool;\n private readonly collections: Record<string, CollectionConfig>;\n private readonly documentsTable: string;\n\n constructor(config: PostgresAdapterConfig = {}) {\n if (config.pool) {\n this.pool = config.pool;\n } else if (config.connectionString) {\n this.pool = new Pool({ connectionString: config.connectionString });\n } else {\n throw new Error(\n \"PostgresDataAdapter requires either `pool` or `connectionString`.\",\n );\n }\n this.collections = config.collections ?? {};\n this.documentsTable = config.documentsTable ?? \"documents\";\n }\n\n private cfg(collection: string): CollectionConfig | undefined {\n return this.collections[collection];\n }\n\n /**\n * Create the documents table and every registered typed table if missing,\n * then **additively** reconcile each table's columns: any registered column\n * not yet present is added via `ALTER TABLE ... ADD COLUMN IF NOT EXISTS`.\n *\n * This makes `migrate()` idempotent *and* forward-compatible — adding a new\n * column (or a whole new collection) to your config and re-running `migrate()`\n * evolves an existing database without a destructive reset. It is additive\n * only: it never drops, renames, or retypes columns (those need a real\n * migration). Safe to run on every boot/deploy.\n */\n async migrate(): Promise<void> {\n await this.pool.query(`\n CREATE TABLE IF NOT EXISTS ${ident(this.documentsTable)} (\n id text NOT NULL,\n collection text NOT NULL,\n data jsonb NOT NULL,\n created_at timestamptz DEFAULT now(),\n updated_at timestamptz DEFAULT now(),\n PRIMARY KEY (collection, id)\n );\n `);\n await this.pool.query(\n `CREATE INDEX IF NOT EXISTS ${ident(this.documentsTable + \"_collection_created_idx\")}\n ON ${ident(this.documentsTable)} (collection, created_at DESC);`,\n );\n\n for (const cfg of Object.values(this.collections)) {\n const cols = Object.entries(cfg.columns)\n .map(([name, type]) => `${ident(name)} ${SQL_TYPE[type]}`)\n .join(\",\\n \");\n await this.pool.query(`\n CREATE TABLE IF NOT EXISTS ${ident(cfg.table)} (\n id text PRIMARY KEY,\n ${cols ? cols + \",\" : \"\"}\n extra jsonb NOT NULL DEFAULT '{}'::jsonb,\n created_at timestamptz DEFAULT now(),\n updated_at timestamptz DEFAULT now()\n );\n `);\n\n // Reconcile columns added to the config after the table already existed.\n for (const [name, type] of Object.entries(cfg.columns)) {\n await this.pool.query(\n `ALTER TABLE ${ident(cfg.table)} ADD COLUMN IF NOT EXISTS ${ident(name)} ${SQL_TYPE[type]};`,\n );\n }\n }\n }\n\n // --- row <-> section mapping ------------------------------------------------\n\n /** Split a section into typed columns + leftover `extra`. */\n private toTypedRow(cfg: CollectionConfig, data: Record<string, unknown>) {\n const known: Record<string, unknown> = {};\n const extra: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(data)) {\n if (key === \"id\" || key === \"collection\") continue;\n if (key in cfg.columns) known[key] = value;\n else extra[key] = value;\n }\n return { known, extra };\n }\n\n private fromTypedRow(\n collection: string,\n cfg: CollectionConfig,\n row: Record<string, unknown>,\n ) {\n const { id, extra, created_at, updated_at, ...rest } = row;\n void created_at;\n void updated_at;\n return {\n id: id as string,\n collection,\n ...(rest as Record<string, unknown>),\n ...((extra as Record<string, unknown>) ?? {}),\n };\n }\n\n // --- reads -----------------------------------------------------------------\n\n async fetchById<T = Record<string, unknown>>(\n collection: string,\n id: string,\n ): Promise<(T & { id: string }) | null> {\n const cfg = this.cfg(collection);\n if (cfg) {\n const { rows } = await this.pool.query(\n `SELECT * FROM ${ident(cfg.table)} WHERE id = $1`,\n [id],\n );\n if (!rows[0]) return null;\n return this.fromTypedRow(collection, cfg, rows[0]) as unknown as T & {\n id: string;\n };\n }\n const { rows } = await this.pool.query(\n `SELECT id, data FROM ${ident(this.documentsTable)} WHERE id = $1 AND collection = $2`,\n [id, collection],\n );\n if (!rows[0]) return null;\n return { id: rows[0].id, collection, ...rows[0].data } as unknown as T & {\n id: string;\n };\n }\n\n async fetchCollection<T = Record<string, unknown>>(\n collection: string,\n q?: Query,\n ): Promise<(T & { id: string })[]> {\n const cfg = this.cfg(collection);\n const params: unknown[] = [];\n const where: string[] = [];\n\n const colExpr = (field: string): string => {\n if (cfg) {\n return field in cfg.columns ? ident(field) : `extra->>${literal(field)}`;\n }\n return `data->>${literal(field)}`;\n };\n\n /** Render one field condition to SQL, pushing its bound params. */\n const renderFilter = (f: QueryFilter): string => {\n const col = colExpr(f.field);\n if (f.op === \"in\" || f.op === \"nin\") {\n params.push(f.value);\n const cmp = f.op === \"in\" ? \"= ANY\" : \"<> ALL\";\n return `${col} ${cmp}($${params.length})`;\n }\n if (f.op === \"contains\") {\n params.push(`%${String(f.value)}%`);\n return `${col} ILIKE $${params.length}`;\n }\n const op = BINARY_OP[f.op];\n if (!op) throw new Error(`Unsupported query op: ${f.op}`);\n params.push(f.value);\n return `${col} ${op} $${params.length}`;\n };\n\n /** Render an AND-level condition, expanding OR groups into `(a OR b)`. */\n const renderCondition = (c: QueryCondition): string => {\n if (isFilterGroup(c)) {\n if (!c.or.length) return \"TRUE\";\n return `(${c.or.map(renderFilter).join(\" OR \")})`;\n }\n return renderFilter(c);\n };\n\n if (!cfg) {\n params.push(collection);\n where.push(`collection = $${params.length}`);\n }\n\n for (const c of q?.filters ?? []) {\n where.push(renderCondition(c));\n }\n\n const orderBy =\n (q?.orderBy ?? []).map((o) => `${colExpr(o.field)} ${o.direction === \"asc\" ? \"ASC\" : \"DESC\"}`).join(\", \") ||\n \"created_at DESC\";\n\n const limit = q?.limit != null ? ` LIMIT ${Number(q.limit)}` : \"\";\n const offset = q?.offset != null ? ` OFFSET ${Number(q.offset)}` : \"\";\n const whereSql = where.length ? ` WHERE ${where.join(\" AND \")}` : \"\";\n const table = cfg ? cfg.table : this.documentsTable;\n const select = cfg ? \"*\" : \"id, data\";\n\n const { rows } = await this.pool.query(\n `SELECT ${select} FROM ${ident(table)}${whereSql} ORDER BY ${orderBy}${limit}${offset}`,\n params,\n );\n\n if (cfg) {\n return rows.map((r) => this.fromTypedRow(collection, cfg, r)) as unknown as (T & {\n id: string;\n })[];\n }\n return rows.map((r) => ({ id: r.id, collection, ...r.data })) as unknown as (T & {\n id: string;\n })[];\n }\n\n // --- writes ----------------------------------------------------------------\n\n async create<T = Record<string, unknown>>(\n collection: string,\n data: T,\n ): Promise<T & { id: string }> {\n return this.createWithId(collection, randomUUID(), data);\n }\n\n async createWithId<T = Record<string, unknown>>(\n collection: string,\n id: string,\n data: T,\n ): Promise<T & { id: string }> {\n const cfg = this.cfg(collection);\n const record = data as Record<string, unknown>;\n\n if (cfg) {\n const { known, extra } = this.toTypedRow(cfg, record);\n const cols = [\"id\", ...Object.keys(known), \"extra\"];\n const vals = [id, ...Object.values(known), JSON.stringify(extra)];\n const placeholders = cols.map((_, i) => `$${i + 1}`).join(\", \");\n await this.pool.query(\n `INSERT INTO ${ident(cfg.table)} (${cols.map(ident).join(\", \")})\n VALUES (${placeholders})`,\n vals,\n );\n } else {\n await this.pool.query(\n `INSERT INTO ${ident(this.documentsTable)} (id, collection, data)\n VALUES ($1, $2, $3)`,\n [id, collection, JSON.stringify(record)],\n );\n }\n return { id, ...(data as T) };\n }\n\n async update<T = Record<string, unknown>>(\n collection: string,\n id: string,\n data: Partial<T>,\n ): Promise<void> {\n await this.write(collection, id, data, \"update\");\n }\n\n async upsert<T = Record<string, unknown>>(\n collection: string,\n id: string,\n data: Partial<T>,\n ): Promise<void> {\n await this.write(collection, id, data, \"upsert\");\n }\n\n private async write(\n collection: string,\n id: string,\n data: Record<string, unknown>,\n mode: \"update\" | \"upsert\",\n ): Promise<void> {\n const cfg = this.cfg(collection);\n\n if (cfg) {\n const { known, extra } = this.toTypedRow(cfg, data);\n const setCols = Object.keys(known);\n const setExpr = setCols\n .map((c, i) => `${ident(c)} = $${i + 2}`)\n .join(\", \");\n // Merge into extra so unmapped fields aren't dropped.\n const extraIdx = setCols.length + 2;\n const sets = [\n setExpr,\n // Table-qualified: in ON CONFLICT DO UPDATE both the target table and the\n // `excluded` pseudo-relation expose `extra`, so a bare ref is ambiguous.\n `extra = ${ident(cfg.table)}.${ident(\"extra\")} || $${extraIdx}::jsonb`,\n `updated_at = now()`,\n ]\n .filter(Boolean)\n .join(\", \");\n const params = [id, ...Object.values(known), JSON.stringify(extra)];\n\n if (mode === \"upsert\") {\n const insertCols = [\"id\", ...setCols, \"extra\"];\n const insertVals = params;\n const placeholders = insertCols.map((_, i) => `$${i + 1}`).join(\", \");\n await this.pool.query(\n `INSERT INTO ${ident(cfg.table)} (${insertCols.map(ident).join(\", \")})\n VALUES (${placeholders})\n ON CONFLICT (id) DO UPDATE SET ${sets}`,\n insertVals,\n );\n } else {\n await this.pool.query(\n `UPDATE ${ident(cfg.table)} SET ${sets} WHERE id = $1`,\n params,\n );\n }\n return;\n }\n\n // JSONB documents table: shallow-merge the patch into `data`.\n if (mode === \"upsert\") {\n await this.pool.query(\n `INSERT INTO ${ident(this.documentsTable)} (id, collection, data)\n VALUES ($1, $2, $3)\n ON CONFLICT (collection, id) DO UPDATE\n SET data = ${ident(this.documentsTable)}.data || $3::jsonb,\n updated_at = now()`,\n [id, collection, JSON.stringify(data)],\n );\n } else {\n await this.pool.query(\n `UPDATE ${ident(this.documentsTable)}\n SET data = data || $2::jsonb, updated_at = now()\n WHERE id = $1 AND collection = $3`,\n [id, JSON.stringify(data), collection],\n );\n }\n }\n\n async delete(collection: string, id: string): Promise<void> {\n const cfg = this.cfg(collection);\n if (cfg) {\n await this.pool.query(`DELETE FROM ${ident(cfg.table)} WHERE id = $1`, [id]);\n } else {\n await this.pool.query(\n `DELETE FROM ${ident(this.documentsTable)} WHERE id = $1 AND collection = $2`,\n [id, collection],\n );\n }\n }\n}\n\n/** Quote a SQL identifier safely. */\nfunction ident(name: string): string {\n return `\"${name.replace(/\"/g, '\"\"')}\"`;\n}\n\n/** Quote a SQL string literal (used for static jsonb keys, never user values). */\nfunction literal(value: string): string {\n return `'${value.replace(/'/g, \"''\")}'`;\n}\n","/**\n * Shared, runtime-free types. Safe to import in any environment (client or\n * server) — this module pulls in zero runtime dependencies.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\n/**\n * Engine addressing fields. The engine needs to know an entity's `id` and which\n * `collection` it lives in to route saves; everything else is the consumer's own\n * shape. Kept separate from the user's content type `T` so we never force these\n * onto domain models — use {@link Editable}<YourType> when you want both.\n */\nexport interface EntityAddress {\n id: string;\n collection: string;\n}\n\n/**\n * The user's content type `T` decorated with the engine's addressing fields.\n * `T` is completely unconstrained — bring any shape; the engine only adds `id`\n * and `collection`.\n */\nexport type Editable<T = Record<string, any>> = T & EntityAddress;\n\n/**\n * A single editable record. Schemaless by design. Equivalent to\n * `Editable<Record<string, any>>`; kept as a named alias for the common case and\n * for backwards compatibility.\n */\nexport type Section = Editable;\n\nexport type SectionMap = Record<string, Section>;\n\n/**\n * The shape the engine holds in memory:\n * `{ [collection]: { [sectionKey]: Section } }`.\n */\nexport type NestedSections = {\n [collection: string]: {\n [sectionKey: string]: Section;\n };\n};\n\n/**\n * A single item in an editable collection list (e.g. a project or a tool). Just\n * a record with a stable `id`. Used by the client provider's collection ops\n * (create / delete / reorder) which manage *which* items exist and their order,\n * complementing {@link Section}s which manage a single item's editable fields.\n */\nexport type CollectionItem = Record<string, unknown> & { id: string };\n\n/**\n * An image edit queued in the provider. The actual upload is deferred until\n * save. `file` is null when the user pasted an external URL (`isExternal`).\n */\nexport interface PendingImage {\n file: File | null;\n localUrl: string;\n sectionKey: string;\n fieldKey: string;\n collection: string;\n docId: string;\n isExternal?: boolean;\n}\n\n/**\n * Neutral query language. Keeps any backend's native query type (Firestore's\n * `Query`, SQL, etc.) from leaking through the engine.\n *\n * Op support is backend-dependent — see each adapter. `contains` is a\n * case-insensitive substring match; `in`/`nin` take an array value.\n */\nexport type QueryFilterOp =\n | \"eq\"\n | \"ne\"\n | \"lt\"\n | \"lte\"\n | \"gt\"\n | \"gte\"\n | \"in\"\n | \"nin\"\n | \"contains\";\n\n/** A single field condition. */\nexport type QueryFilter = { field: string; op: QueryFilterOp; value: unknown };\n\n/**\n * A disjunction: the inner filters are combined with OR. Sits alongside plain\n * filters in `Query.filters`, which are combined with AND at the top level.\n */\nexport type QueryFilterGroup = { or: QueryFilter[] };\n\n/** Either a bare condition (AND-ed) or an OR group. */\nexport type QueryCondition = QueryFilter | QueryFilterGroup;\n\nexport type Query = {\n /** Top-level conditions combined with AND. Use `{ or: [...] }` for disjunction. */\n filters?: QueryCondition[];\n orderBy?: { field: string; direction: \"asc\" | \"desc\" }[];\n limit?: number;\n /** Skip this many rows (offset pagination). */\n offset?: number;\n /**\n * Fields holding references to other documents to inline-resolve after the\n * primary fetch. Resolved by {@link resolveRelations}, never by the adapter.\n */\n populate?: string[];\n};\n\n/** Narrow a {@link QueryCondition} to an OR group. */\nexport function isFilterGroup(c: QueryCondition): c is QueryFilterGroup {\n return typeof c === \"object\" && c !== null && \"or\" in c;\n}\n\n/**\n * A reference to another document. Either self-describing (`{ collection, id }`)\n * or a bare id string resolved via a {@link RelationConfig}.\n */\nexport type Ref = { collection: string; id: string };\n\n/**\n * Maps a reference field name → the collection it points to. Needed only when\n * refs are stored as bare id strings (self-describing `Ref` objects don't need\n * it). Used by {@link resolveRelations}.\n */\nexport type RelationConfig = Record<string, { collection: string }>;\n\n/**\n * Persistence contract. Every backend (Firestore, Postgres, …) implements this;\n * the engine and the route factory only ever speak to this interface.\n */\nexport interface DataAdapter {\n fetchCollection<T = Record<string, any>>(\n collection: string,\n q?: Query,\n ): Promise<(T & { id: string })[]>;\n fetchById<T = Record<string, any>>(\n collection: string,\n id: string,\n ): Promise<(T & { id: string }) | null>;\n create<T = Record<string, any>>(\n collection: string,\n data: T,\n ): Promise<T & { id: string }>;\n createWithId<T = Record<string, any>>(\n collection: string,\n id: string,\n data: T,\n ): Promise<T & { id: string }>;\n update<T = Record<string, any>>(\n collection: string,\n id: string,\n data: Partial<T>,\n ): Promise<void>;\n upsert<T = Record<string, any>>(\n collection: string,\n id: string,\n data: Partial<T>,\n ): Promise<void>;\n delete(collection: string, id: string): Promise<void>;\n}\n\n/**\n * The identity resolved from an incoming request by an {@link AuthAdapter}.\n * Minimal by design: only `isAdmin` is meaningful to the default gate. Carry any\n * additional claims (roles, scopes, tenant, …) as extra keys and authorize on\n * them with a custom `authorize` predicate. `userId`/`email` are conventional\n * but optional.\n */\nexport interface AuthIdentity {\n isAdmin: boolean;\n userId?: string;\n email?: string;\n [claim: string]: unknown;\n}\n\n/**\n * Decides whether a resolved identity may perform admin actions. Defaults to\n * `identity.isAdmin === true`; override to gate on roles/scopes/etc.\n */\nexport type AuthorizeFn = (\n identity: AuthIdentity,\n req: Request,\n) => boolean | Promise<boolean>;\n\n/**\n * Server-side auth contract. Gates every admin API route. Return `null` to\n * reject outright; otherwise the gate's `authorize` predicate decides.\n */\nexport interface AuthAdapter {\n verifyRequest(req: Request): Promise<AuthIdentity | null>;\n}\n\n/**\n * Client half of storage: performs the browser-side upload and returns the\n * final URL. Has zero server dependencies, so it is safe to import in client\n * components. Built from e.g. `@dalgoridim/headless-cms/storage/cloudinary`.\n */\nexport interface ClientStorageAdapter {\n upload(file: File): Promise<{ url: string }>;\n}\n\n/**\n * Server half of storage: issues a presign / signature (or, for local storage,\n * writes the file). Mounted by the route factory at `${apiBasePath}/sign`.\n * Pulls in server-only SDKs, so it lives in a `/server` subpath. Built from e.g.\n * `@dalgoridim/headless-cms/storage/cloudinary/server`.\n */\nexport interface ServerStorageAdapter {\n sign(req: Request): Promise<unknown>;\n}\n\n/** Full two-sided contract (rarely needed; client and server halves are split). */\nexport interface StorageAdapter extends ClientStorageAdapter {\n sign?(req: Request): Promise<unknown>;\n}\n\n/**\n * Minimal client-side auth state the edit primitives depend on. Any auth\n * implementation (Firebase, NextAuth, custom) provides this via a context;\n * `@dalgoridim/headless-cms/auth/firebase/client` ships the default.\n */\nexport interface CmsAuthState {\n isAdmin: boolean;\n isEditing: boolean;\n toggleEdit: () => void;\n}\n"],"mappings":"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","names":["rows"]}
@@ -292,6 +292,34 @@ var PageProvider = ({
292
292
  },
293
293
  [apiBasePath, notify]
294
294
  );
295
+ const updateItem = (0, import_react.useCallback)(
296
+ async (collection, id, patch) => {
297
+ let previous = [];
298
+ setCollections((prev) => {
299
+ var _a;
300
+ previous = (_a = prev[collection]) != null ? _a : [];
301
+ return __spreadProps(__spreadValues({}, prev), {
302
+ [collection]: previous.map(
303
+ (it) => it.id === id ? __spreadValues(__spreadValues({}, it), patch) : it
304
+ )
305
+ });
306
+ });
307
+ try {
308
+ const res = await fetch(`${apiBasePath}/${collection}/${id}`, {
309
+ method: "PATCH",
310
+ headers: { "Content-Type": "application/json" },
311
+ body: JSON.stringify(patch)
312
+ });
313
+ if (!res.ok) throw new Error("Failed to update item");
314
+ notify.success("Saved");
315
+ } catch (error) {
316
+ setCollections((prev) => __spreadProps(__spreadValues({}, prev), { [collection]: previous }));
317
+ notify.error("Failed to save");
318
+ throw error;
319
+ }
320
+ },
321
+ [apiBasePath, notify]
322
+ );
295
323
  const deleteItem = (0, import_react.useCallback)(
296
324
  async (collection, id) => {
297
325
  let removed = [];
@@ -365,6 +393,7 @@ var PageProvider = ({
365
393
  saveAll,
366
394
  collections,
367
395
  createItem,
396
+ updateItem,
368
397
  deleteItem,
369
398
  reorderItems
370
399
  },
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/client/index.ts","../../src/client/PageProvider.tsx","../../src/client/auth.tsx","../../src/client/ContentEditSpan.tsx","../../src/client/EditableImage.tsx","../../src/client/MarkdownEditor.tsx"],"sourcesContent":["\"use client\";\n\nexport {\n PageProvider,\n usePageContext,\n type PageProviderProps,\n type Notifier,\n type PendingImage,\n} from \"./PageProvider\";\nexport { CmsAuthContext, CmsAuthProvider, useCmsAuth } from \"./auth\";\nexport { default as ContentEditSpan } from \"./ContentEditSpan\";\nexport {\n default as EditableImage,\n type EditableImageRenderState,\n} from \"./EditableImage\";\nexport {\n useMarkdownEditor,\n type UseMarkdownEditorOptions,\n type MarkdownEditorApi,\n} from \"./MarkdownEditor\";\n\n// Re-export shared types for convenience.\nexport type {\n Section,\n SectionMap,\n NestedSections,\n CollectionItem,\n Editable,\n EntityAddress,\n Query,\n QueryFilter,\n QueryFilterGroup,\n QueryCondition,\n QueryFilterOp,\n Ref,\n RelationConfig,\n DataAdapter,\n AuthAdapter,\n AuthIdentity,\n StorageAdapter,\n ClientStorageAdapter,\n ServerStorageAdapter,\n CmsAuthState,\n} from \"../types\";\n","\"use client\";\n\nimport {\n createContext,\n useContext,\n useState,\n useCallback,\n type ReactNode,\n} from \"react\";\nimport type {\n ClientStorageAdapter,\n CollectionItem,\n NestedSections,\n PendingImage,\n Section,\n} from \"../types\";\n\nexport type { PendingImage } from \"../types\";\n\n/**\n * Notification sink. The package ships a dependency-free console default so it\n * imposes no toast library; pass your own (e.g. a `sonner`-backed sink) via the\n * `notify` prop to surface UI toasts.\n */\nexport interface Notifier {\n success: (message: string) => void;\n error: (message: string) => void;\n}\n\nconst defaultNotifier: Notifier = {\n success: (m) => console.info(`[cms] ${m}`),\n error: (m) => console.error(`[cms] ${m}`),\n};\n\ninterface PageContextType {\n sections: NestedSections;\n hasUnsavedChanges: boolean;\n saving: boolean;\n pendingImages: PendingImage[];\n setSection: (collection: string, key: string, section: Section) => void;\n editField: (\n collection: string,\n sectionKey: string,\n fieldKey: string,\n value: unknown,\n ) => void;\n setPendingImage: (image: PendingImage) => void;\n saveSection: (collection: string, sectionKey: string) => Promise<void>;\n saveAll: () => Promise<void>;\n /** Ordered item lists per collection (for add/remove/reorder). */\n collections: Record<string, CollectionItem[]>;\n /**\n * Append (or prepend) a new item to a collection and persist it immediately\n * via `PUT`. Returns the new item's id. Optimistic with rollback on failure.\n */\n createItem: (\n collection: string,\n data: Record<string, unknown>,\n opts?: { id?: string; atStart?: boolean },\n ) => Promise<string>;\n /** Remove an item from a collection and persist via `DELETE`. Optimistic. */\n deleteItem: (collection: string, id: string) => Promise<void>;\n /**\n * Reorder a collection to match `orderedIds`, persisting each item's new\n * integer `order` via `PATCH`. Optimistic with rollback on failure.\n */\n reorderItems: (collection: string, orderedIds: string[]) => Promise<void>;\n}\n\nconst PageContext = createContext<PageContextType | undefined>(undefined);\n\nconst dirtyKey = (collection: string, sectionKey: string) =>\n `${collection}:${sectionKey}`;\n\nexport interface PageProviderProps {\n children: ReactNode;\n /** Server-rendered sections to hydrate from. */\n initialSections?: NestedSections;\n /** Server-rendered collection item lists to hydrate from (for add/remove/reorder). */\n initialCollections?: Record<string, CollectionItem[]>;\n /**\n * Base path of the CMS API route mounted via `createCmsHandlers`. Saves\n * `PATCH` to `${apiBasePath}/{collection}/{id}`. Defaults to `/api/admin`.\n */\n apiBasePath?: string;\n /** Client storage adapter used to upload pending (non-external) images on save. */\n storage?: ClientStorageAdapter;\n /** Notification sink. Defaults to `sonner` toasts. */\n notify?: Notifier;\n}\n\nexport const PageProvider = ({\n children,\n initialSections = {},\n initialCollections = {},\n apiBasePath = \"/api/admin\",\n storage,\n notify = defaultNotifier,\n}: PageProviderProps) => {\n const [saving, setSaving] = useState(false);\n const [sections, setSections] = useState<NestedSections>(initialSections);\n const [collections, setCollections] =\n useState<Record<string, CollectionItem[]>>(initialCollections);\n const [pendingImages, setPendingImages] = useState<PendingImage[]>([]);\n const [dirtySections, setDirtySections] = useState<Set<string>>(new Set());\n\n const hasUnsavedChanges = dirtySections.size > 0;\n\n const resolveImageUrl = useCallback(\n async (img: PendingImage): Promise<string> => {\n if (img.isExternal) return img.localUrl;\n if (!storage) {\n throw new Error(\n \"PageProvider received a file upload but no `storage` adapter was provided.\",\n );\n }\n const { url } = await storage.upload(img.file!);\n return url;\n },\n [storage],\n );\n\n const setSection = useCallback(\n (collection: string, key: string, section: Section) => {\n setSections((prev) => ({\n ...prev,\n [collection]: { ...prev[collection], [key]: section },\n }));\n },\n [],\n );\n\n const editField = useCallback(\n (\n collection: string,\n sectionKey: string,\n fieldKey: string,\n value: unknown,\n ) => {\n setSections((prev) => {\n const currentSection = prev[collection]?.[sectionKey];\n\n if (!currentSection) {\n console.error(`Section not found: ${collection}/${sectionKey}`);\n return prev;\n }\n\n const keys = fieldKey.split(\".\");\n const updated: Section = { ...currentSection };\n\n if (keys.length === 1) {\n updated[fieldKey] = value;\n } else {\n let current: Record<string, unknown> = updated;\n for (let i = 0; i < keys.length - 1; i++) {\n if (!current[keys[i]]) current[keys[i]] = {};\n current[keys[i]] = { ...(current[keys[i]] as object) };\n current = current[keys[i]] as Record<string, unknown>;\n }\n current[keys[keys.length - 1]] = value;\n }\n\n return {\n ...prev,\n [collection]: { ...prev[collection], [sectionKey]: updated },\n };\n });\n\n setDirtySections((prev) => {\n const next = new Set(prev);\n next.add(dirtyKey(collection, sectionKey));\n return next;\n });\n },\n [],\n );\n\n const setPendingImage = useCallback((image: PendingImage) => {\n setPendingImages((prev) => [\n ...prev.filter(\n (img) =>\n !(\n img.collection === image.collection &&\n img.sectionKey === image.sectionKey &&\n img.fieldKey === image.fieldKey\n ),\n ),\n image,\n ]);\n\n setDirtySections((prev) => {\n const next = new Set(prev);\n next.add(dirtyKey(image.collection, image.sectionKey));\n return next;\n });\n }, []);\n\n const persist = useCallback(\n async (section: Section) => {\n const response = await fetch(\n `${apiBasePath}/${section.collection}/${section.id}`,\n {\n method: \"PATCH\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(section),\n },\n );\n if (!response.ok) throw new Error(\"Failed to save section\");\n },\n [apiBasePath],\n );\n\n const saveSection = useCallback(\n async (collection: string, sectionKey: string) => {\n if (saving) return;\n setSaving(true);\n\n try {\n const section = sections[collection]?.[sectionKey];\n if (!section?.id || !section?.collection) {\n console.error(`Invalid section: ${collection}/${sectionKey}`);\n setSaving(false);\n return;\n }\n\n const images = pendingImages.filter(\n (img) =>\n img.collection === collection && img.sectionKey === sectionKey,\n );\n\n let updatedSection: Section = { ...section };\n\n for (const img of images) {\n const url = await resolveImageUrl(img);\n const keys = img.fieldKey.split(\".\");\n if (keys.length === 1) {\n updatedSection = { ...updatedSection, [img.fieldKey]: url };\n } else {\n let current: Record<string, unknown> = updatedSection;\n for (let i = 0; i < keys.length - 1; i++) {\n if (!current[keys[i]]) current[keys[i]] = {};\n current = current[keys[i]] as Record<string, unknown>;\n }\n current[keys[keys.length - 1]] = url;\n }\n }\n\n await persist(updatedSection);\n\n setSections((prev) => ({\n ...prev,\n [collection]: { ...prev[collection], [sectionKey]: updatedSection },\n }));\n\n setPendingImages((prev) =>\n prev.filter(\n (img) =>\n !(img.collection === collection && img.sectionKey === sectionKey),\n ),\n );\n\n setDirtySections((prev) => {\n const next = new Set(prev);\n next.delete(dirtyKey(collection, sectionKey));\n return next;\n });\n\n notify.success(\"Changes saved successfully!\");\n } catch (error) {\n console.error(\"Save failed:\", error);\n notify.error(\"Failed to save changes\");\n } finally {\n setSaving(false);\n }\n },\n [sections, pendingImages, saving, resolveImageUrl, persist, notify],\n );\n\n const saveAll = useCallback(async () => {\n if (saving || dirtySections.size === 0) return;\n setSaving(true);\n\n try {\n const updatedSections: NestedSections = { ...sections };\n\n for (const img of pendingImages) {\n const url = await resolveImageUrl(img);\n\n if (!updatedSections[img.collection])\n updatedSections[img.collection] = {};\n\n if (!updatedSections[img.collection][img.sectionKey]) {\n updatedSections[img.collection][img.sectionKey] = {\n id: img.docId,\n collection: img.collection,\n };\n }\n\n const keys = img.fieldKey.split(\".\");\n let current: Record<string, unknown> =\n updatedSections[img.collection][img.sectionKey];\n\n if (keys.length === 1) {\n updatedSections[img.collection][img.sectionKey] = {\n ...(current as Section),\n [img.fieldKey]: url,\n };\n } else {\n for (let i = 0; i < keys.length - 1; i++) {\n if (!current[keys[i]]) current[keys[i]] = {};\n current = current[keys[i]] as Record<string, unknown>;\n }\n current[keys[keys.length - 1]] = url;\n }\n }\n\n for (const entry of dirtySections) {\n const [collection, key] = entry.split(\":\");\n const section = updatedSections[collection]?.[key];\n\n if (!section?.id || !section?.collection) {\n console.error(`Invalid section for save: ${entry}`);\n continue;\n }\n\n await persist(section);\n }\n\n setSections(updatedSections);\n setPendingImages([]);\n setDirtySections(new Set());\n notify.success(\"All changes saved successfully!\");\n } catch (error) {\n console.error(\"Save all failed:\", error);\n notify.error(\"Failed to save changes\");\n } finally {\n setSaving(false);\n }\n }, [sections, pendingImages, dirtySections, saving, resolveImageUrl, persist, notify]);\n\n const createItem = useCallback(\n async (\n collection: string,\n data: Record<string, unknown>,\n opts?: { id?: string; atStart?: boolean },\n ): Promise<string> => {\n const id =\n opts?.id ??\n globalThis.crypto?.randomUUID?.() ??\n `${collection}-${Date.now()}`;\n const item: CollectionItem = { id, ...data };\n\n // Optimistic insert.\n setCollections((prev) => {\n const list = prev[collection] ?? [];\n return {\n ...prev,\n [collection]: opts?.atStart ? [item, ...list] : [...list, item],\n };\n });\n\n try {\n const res = await fetch(`${apiBasePath}/${collection}/${id}`, {\n method: \"PUT\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(data),\n });\n if (!res.ok) throw new Error(\"Failed to create item\");\n notify.success(\"Item added\");\n return id;\n } catch (error) {\n // Roll back the optimistic insert.\n setCollections((prev) => ({\n ...prev,\n [collection]: (prev[collection] ?? []).filter((it) => it.id !== id),\n }));\n notify.error(\"Failed to add item\");\n throw error;\n }\n },\n [apiBasePath, notify],\n );\n\n const deleteItem = useCallback(\n async (collection: string, id: string): Promise<void> => {\n let removed: CollectionItem[] = [];\n setCollections((prev) => {\n removed = prev[collection] ?? [];\n return {\n ...prev,\n [collection]: removed.filter((it) => it.id !== id),\n };\n });\n\n try {\n const res = await fetch(`${apiBasePath}/${collection}/${id}`, {\n method: \"DELETE\",\n });\n if (!res.ok) throw new Error(\"Failed to delete item\");\n notify.success(\"Item removed\");\n } catch (error) {\n setCollections((prev) => ({ ...prev, [collection]: removed })); // rollback\n notify.error(\"Failed to remove item\");\n throw error;\n }\n },\n [apiBasePath, notify],\n );\n\n const reorderItems = useCallback(\n async (collection: string, orderedIds: string[]): Promise<void> => {\n let previous: CollectionItem[] = [];\n let next: CollectionItem[] = [];\n setCollections((prev) => {\n previous = prev[collection] ?? [];\n const byId = new Map(previous.map((it) => [it.id, it]));\n next = orderedIds.flatMap((id, index) => {\n const item = byId.get(id);\n return item ? [{ ...item, order: index }] : [];\n });\n return { ...prev, [collection]: next };\n });\n\n try {\n await Promise.all(\n next.map(async (it) => {\n const res = await fetch(`${apiBasePath}/${collection}/${it.id}`, {\n method: \"PATCH\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ order: it.order }),\n });\n if (!res.ok) throw new Error(\"Failed to reorder\");\n }),\n );\n notify.success(\"Order updated\");\n } catch (error) {\n setCollections((prev) => ({ ...prev, [collection]: previous })); // rollback\n notify.error(\"Failed to update order\");\n throw error;\n }\n },\n [apiBasePath, notify],\n );\n\n return (\n <PageContext.Provider\n value={{\n sections,\n hasUnsavedChanges,\n pendingImages,\n saving,\n setSection,\n editField,\n setPendingImage,\n saveSection,\n saveAll,\n collections,\n createItem,\n deleteItem,\n reorderItems,\n }}\n >\n {children}\n </PageContext.Provider>\n );\n};\n\nexport const usePageContext = () => {\n const context = useContext(PageContext);\n if (!context)\n throw new Error(\"usePageContext must be used within a PageProvider\");\n return context;\n};\n","\"use client\";\n\nimport { createContext, useContext, type ReactNode } from \"react\";\nimport type { CmsAuthState } from \"../types\";\n\n/**\n * The single source of client-side auth state for the engine. Every built-in\n * auth provider (and any custom one) feeds this context; the edit primitives\n * read it via {@link useCmsAuth}.\n *\n * Cross-entry note: provider packages (e.g. `…/auth/firebase/client`) import\n * this context through the package's public `…/client` specifier so they share\n * the *same* context instance as the consumer's primitives at runtime.\n */\nexport const CmsAuthContext = createContext<CmsAuthState | undefined>(undefined);\n\nexport function useCmsAuth(): CmsAuthState {\n const ctx = useContext(CmsAuthContext);\n if (!ctx) {\n throw new Error(\n \"useCmsAuth must be used within a CmsAuthProvider (or a built-in auth provider such as FirebaseAuthProvider).\",\n );\n }\n return ctx;\n}\n\n/**\n * Controlled provider for consumers wiring their own auth. Pass the resolved\n * {@link CmsAuthState}; the built-in providers wrap this for you.\n */\nexport function CmsAuthProvider({\n value,\n children,\n}: {\n value: CmsAuthState;\n children: ReactNode;\n}) {\n return (\n <CmsAuthContext.Provider value={value}>{children}</CmsAuthContext.Provider>\n );\n}\n","\"use client\";\n\nimport React, { useRef, useEffect, useState, useCallback } from \"react\";\nimport { usePageContext } from \"./PageProvider\";\nimport { useCmsAuth } from \"./auth\";\n\n/**\n * Inline-editable text primitive. Headless: it wires `contentEditable`, reads\n * and persists the field, and exposes edit state via `data-*` attributes —\n * styling and rich-text rendering are entirely yours.\n *\n * - Style it with `className` and the `data-cms-editing` / `data-cms-focused`\n * attributes (no built-in look, no Tailwind, no design tokens).\n * - Supply `renderValue` to turn the stored raw string into rich nodes (e.g. a\n * markdown parser). Defaults to plain text.\n */\ninterface ContentEditSpanProps {\n collection: string;\n sectionKey: string;\n fieldKey: string;\n className?: string;\n children?: React.ReactNode;\n /** Element/tag to render. Defaults to `span`. */\n as?: React.ElementType;\n /** Render the stored raw string into nodes. Defaults to plain text. */\n renderValue?: (raw: string) => React.ReactNode;\n}\n\nconst defaultRenderValue = (raw: string): React.ReactNode => raw;\n\nfunction getNestedValue(obj: unknown, path: string): unknown {\n const keys = path.split(\".\");\n let current: unknown = obj;\n\n for (const key of keys) {\n if (current == null || (current as Record<string, unknown>)[key] === undefined)\n return undefined;\n current = (current as Record<string, unknown>)[key];\n }\n\n return current;\n}\n\nexport default function ContentEditSpan({\n collection,\n sectionKey,\n fieldKey,\n className,\n children,\n as = \"span\",\n renderValue = defaultRenderValue,\n}: ContentEditSpanProps) {\n const { sections, editField } = usePageContext();\n const { isEditing } = useCmsAuth();\n\n const section = sections[collection]?.[sectionKey];\n const raw =\n (getNestedValue(section, fieldKey) as string) ??\n (typeof children === \"string\" ? children : \"\");\n\n const Component = as;\n\n if (!isEditing) {\n return <Component className={className}>{renderValue(raw)}</Component>;\n }\n\n return (\n <EditableContentSpan\n collection={collection}\n sectionKey={sectionKey}\n fieldKey={fieldKey}\n className={className}\n raw={raw}\n editField={editField}\n as={as}\n renderValue={renderValue}\n />\n );\n}\n\nfunction EditableContentSpan({\n collection,\n sectionKey,\n fieldKey,\n className,\n raw,\n editField,\n as: Component = \"span\",\n renderValue,\n}: {\n collection: string;\n sectionKey: string;\n fieldKey: string;\n className?: string;\n raw: string;\n editField: (\n collection: string,\n sectionKey: string,\n fieldKey: string,\n value: string,\n ) => void;\n as?: React.ElementType;\n renderValue: (raw: string) => React.ReactNode;\n}) {\n const { isEditing } = useCmsAuth();\n const [isFocused, setIsFocused] = useState(false);\n const [editValue, setEditValue] = useState(raw);\n const contentRef = useRef<HTMLElement>(null);\n const rawRef = useRef(raw);\n\n useEffect(() => {\n if (!isFocused && rawRef.current !== raw) {\n rawRef.current = raw;\n }\n }, [raw, isFocused]);\n\n useEffect(() => {\n if (!isFocused && rawRef.current !== editValue) {\n setEditValue(rawRef.current);\n }\n }, [isFocused, editValue]);\n\n const handleInput = useCallback(() => {\n if (contentRef.current) {\n setEditValue(contentRef.current.textContent || \"\");\n }\n }, []);\n\n const handleBlur = useCallback(() => {\n setIsFocused(false);\n if (editValue !== raw) {\n editField(collection, sectionKey, fieldKey, editValue);\n }\n }, [editValue, raw, collection, sectionKey, fieldKey, editField]);\n\n const handleFocus = useCallback(() => {\n setIsFocused(true);\n if (contentRef.current) {\n contentRef.current.textContent = editValue;\n setTimeout(() => {\n if (contentRef.current) {\n const range = document.createRange();\n const sel = window.getSelection();\n range.selectNodeContents(contentRef.current);\n range.collapse(false);\n sel?.removeAllRanges();\n sel?.addRange(range);\n }\n }, 0);\n }\n }, [editValue]);\n\n return (\n <Component\n key={isFocused ? \"editing\" : \"static\"}\n ref={contentRef}\n className={className}\n data-cms-editable=\"\"\n data-cms-editing={isEditing ? \"\" : undefined}\n data-cms-focused={isFocused ? \"\" : undefined}\n contentEditable={isEditing}\n suppressContentEditableWarning\n onInput={handleInput}\n onBlur={handleBlur}\n onFocus={handleFocus}\n >\n {!isFocused && renderValue(editValue)}\n </Component>\n );\n}\n","\"use client\";\n\nimport React, { useRef, useState } from \"react\";\nimport { useCmsAuth } from \"./auth\";\nimport { usePageContext } from \"./PageProvider\";\n\n/** State + actions handed to the {@link EditableImage} render-prop. */\nexport interface EditableImageRenderState {\n /** URL to display (pending upload preview, external URL, or the saved src). */\n src: string;\n isEditing: boolean;\n saving: boolean;\n /** True after the current `src` failed to load. */\n hasError: boolean;\n /** Open the native file picker (a hidden input is managed for you). */\n openFilePicker: () => void;\n /**\n * Queue an external image URL. Returns `false` if it isn't a valid http(s)\n * URL (nothing is changed in that case).\n */\n setExternalUrl: (url: string) => boolean;\n /** Convenience props for an `<img>`: `{ src, onError }`. */\n imgProps: { src: string; onError: () => void };\n}\n\ninterface EditableImageProps {\n sectionKey: string;\n fieldKey: string;\n src: string;\n collection: string;\n docId: string;\n className?: string;\n /**\n * Render the image and any editing chrome (overlay, buttons, URL modal). The\n * package ships no visual look — bring your own. When omitted, a bare\n * unstyled `<img>` is rendered.\n */\n children?: (state: EditableImageRenderState) => React.ReactNode;\n}\n\nexport default function EditableImage({\n sectionKey,\n fieldKey,\n src,\n collection,\n docId,\n className,\n children,\n}: EditableImageProps) {\n const { isEditing } = useCmsAuth();\n const { editField, setPendingImage, pendingImages, saving } =\n usePageContext();\n\n const [preview, setPreview] = useState(src);\n const [hasError, setHasError] = useState(false);\n const inputRef = useRef<HTMLInputElement>(null);\n\n const pendingImage = pendingImages.find(\n (img) => img.sectionKey === sectionKey && img.fieldKey === fieldKey,\n );\n const imgSrc = pendingImage?.localUrl || preview;\n\n const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n if (saving) return;\n const file = e.target.files?.[0];\n if (!file) return;\n\n const localUrl = URL.createObjectURL(file);\n setPreview(localUrl);\n setHasError(false);\n\n editField(collection, sectionKey, fieldKey, localUrl);\n setPendingImage({\n file,\n localUrl,\n sectionKey,\n fieldKey,\n collection,\n docId,\n isExternal: false,\n });\n };\n\n const openFilePicker = () => {\n if (saving) return;\n inputRef.current?.click();\n };\n\n const setExternalUrl = (value: string): boolean => {\n let valid = false;\n try {\n const url = new URL(value);\n valid = url.protocol === \"http:\" || url.protocol === \"https:\";\n } catch {\n valid = false;\n }\n if (!valid) return false;\n\n setPreview(value);\n setHasError(false);\n editField(collection, sectionKey, fieldKey, value);\n setPendingImage({\n file: null,\n localUrl: value,\n sectionKey,\n fieldKey,\n collection,\n docId,\n isExternal: true,\n });\n return true;\n };\n\n const state: EditableImageRenderState = {\n src: imgSrc,\n isEditing,\n saving,\n hasError,\n openFilePicker,\n setExternalUrl,\n imgProps: { src: imgSrc, onError: () => setHasError(true) },\n };\n\n return (\n <div className={className}>\n {/* Internal hidden file input — driven via openFilePicker(). */}\n <input\n ref={inputRef}\n type=\"file\"\n accept=\"image/*\"\n disabled={saving}\n onChange={handleFileChange}\n style={{ display: \"none\" }}\n />\n {children ? (\n children(state)\n ) : (\n // eslint-disable-next-line @next/next/no-img-element\n <img {...state.imgProps} alt=\"\" />\n )}\n </div>\n );\n}\n","\"use client\";\n\nimport { useCallback, useRef, useState } from \"react\";\n\nexport interface UseMarkdownEditorOptions {\n initialValue: string;\n onSave: (content: string) => void | Promise<void>;\n}\n\nexport interface MarkdownEditorApi {\n /** Current editor text. */\n value: string;\n setValue: (next: string) => void;\n /** Attach to your `<textarea>` so `insert` can target the selection. */\n textareaRef: React.RefObject<HTMLTextAreaElement | null>;\n /**\n * Wrap the current selection (or insert a placeholder) with `before`/`after`\n * markers — e.g. `insert(\"**\", \"**\", \"bold text\")`. Restores focus and caret.\n */\n insert: (before: string, after?: string, placeholder?: string) => void;\n /** Reset back to `initialValue` (or a provided value). */\n reset: (to?: string) => void;\n /** Persist the current value via the provided `onSave`. */\n save: () => void | Promise<void>;\n charCount: number;\n}\n\n/**\n * Headless markdown-editing logic: value state, a selection-aware `insert`\n * command, and save/reset. The package ships **no** modal, toolbar, icons, or\n * preview renderer — compose those yourself (e.g. with `react-markdown`). This\n * keeps the package free of any UI library or visual opinion.\n *\n * ```tsx\n * const md = useMarkdownEditor({ initialValue, onSave });\n * <textarea ref={md.textareaRef} value={md.value}\n * onChange={(e) => md.setValue(e.target.value)} />\n * <button onClick={() => md.insert(\"**\", \"**\", \"bold\")}>Bold</button>\n * ```\n */\nexport function useMarkdownEditor({\n initialValue,\n onSave,\n}: UseMarkdownEditorOptions): MarkdownEditorApi {\n const [value, setValue] = useState(initialValue);\n const textareaRef = useRef<HTMLTextAreaElement>(null);\n\n const insert = useCallback(\n (before: string, after = \"\", placeholder = \"text\") => {\n const textarea = textareaRef.current;\n const start = textarea?.selectionStart ?? value.length;\n const end = textarea?.selectionEnd ?? value.length;\n const selected = value.substring(start, end) || placeholder;\n const next =\n value.substring(0, start) +\n before +\n selected +\n after +\n value.substring(end);\n\n setValue(next);\n\n setTimeout(() => {\n if (!textarea) return;\n textarea.focus();\n const caret = start + before.length + selected.length;\n textarea.setSelectionRange(caret, caret);\n }, 0);\n },\n [value],\n );\n\n const reset = useCallback(\n (to: string = initialValue) => setValue(to),\n [initialValue],\n );\n\n const save = useCallback(() => onSave(value), [onSave, value]);\n\n return {\n value,\n setValue,\n textareaRef,\n insert,\n reset,\n save,\n charCount: value.length,\n };\n}\n"],"mappings":"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","names":["_a","import_react","import_jsx_runtime","import_react","import_jsx_runtime","import_react","import_jsx_runtime","import_react"]}
1
+ {"version":3,"sources":["../../src/client/index.ts","../../src/client/PageProvider.tsx","../../src/client/auth.tsx","../../src/client/ContentEditSpan.tsx","../../src/client/EditableImage.tsx","../../src/client/MarkdownEditor.tsx"],"sourcesContent":["\"use client\";\n\nexport {\n PageProvider,\n usePageContext,\n type PageProviderProps,\n type Notifier,\n type PendingImage,\n} from \"./PageProvider\";\nexport { CmsAuthContext, CmsAuthProvider, useCmsAuth } from \"./auth\";\nexport { default as ContentEditSpan } from \"./ContentEditSpan\";\nexport {\n default as EditableImage,\n type EditableImageRenderState,\n} from \"./EditableImage\";\nexport {\n useMarkdownEditor,\n type UseMarkdownEditorOptions,\n type MarkdownEditorApi,\n} from \"./MarkdownEditor\";\n\n// Re-export shared types for convenience.\nexport type {\n Section,\n SectionMap,\n NestedSections,\n CollectionItem,\n Editable,\n EntityAddress,\n Query,\n QueryFilter,\n QueryFilterGroup,\n QueryCondition,\n QueryFilterOp,\n Ref,\n RelationConfig,\n DataAdapter,\n AuthAdapter,\n AuthIdentity,\n StorageAdapter,\n ClientStorageAdapter,\n ServerStorageAdapter,\n CmsAuthState,\n} from \"../types\";\n","\"use client\";\n\nimport {\n createContext,\n useContext,\n useState,\n useCallback,\n type ReactNode,\n} from \"react\";\nimport type {\n ClientStorageAdapter,\n CollectionItem,\n NestedSections,\n PendingImage,\n Section,\n} from \"../types\";\n\nexport type { PendingImage } from \"../types\";\n\n/**\n * Notification sink. The package ships a dependency-free console default so it\n * imposes no toast library; pass your own (e.g. a `sonner`-backed sink) via the\n * `notify` prop to surface UI toasts.\n */\nexport interface Notifier {\n success: (message: string) => void;\n error: (message: string) => void;\n}\n\nconst defaultNotifier: Notifier = {\n success: (m) => console.info(`[cms] ${m}`),\n error: (m) => console.error(`[cms] ${m}`),\n};\n\ninterface PageContextType {\n sections: NestedSections;\n hasUnsavedChanges: boolean;\n saving: boolean;\n pendingImages: PendingImage[];\n setSection: (collection: string, key: string, section: Section) => void;\n editField: (\n collection: string,\n sectionKey: string,\n fieldKey: string,\n value: unknown,\n ) => void;\n setPendingImage: (image: PendingImage) => void;\n saveSection: (collection: string, sectionKey: string) => Promise<void>;\n saveAll: () => Promise<void>;\n /** Ordered item lists per collection (for add/remove/reorder). */\n collections: Record<string, CollectionItem[]>;\n /**\n * Append (or prepend) a new item to a collection and persist it immediately\n * via `PUT`. Returns the new item's id. Optimistic with rollback on failure.\n */\n createItem: (\n collection: string,\n data: Record<string, unknown>,\n opts?: { id?: string; atStart?: boolean },\n ) => Promise<string>;\n /**\n * Patch fields on an existing collection item and persist via `PATCH`.\n * Optimistic with rollback on failure.\n */\n updateItem: (\n collection: string,\n id: string,\n patch: Record<string, unknown>,\n ) => Promise<void>;\n /** Remove an item from a collection and persist via `DELETE`. Optimistic. */\n deleteItem: (collection: string, id: string) => Promise<void>;\n /**\n * Reorder a collection to match `orderedIds`, persisting each item's new\n * integer `order` via `PATCH`. Optimistic with rollback on failure.\n */\n reorderItems: (collection: string, orderedIds: string[]) => Promise<void>;\n}\n\nconst PageContext = createContext<PageContextType | undefined>(undefined);\n\nconst dirtyKey = (collection: string, sectionKey: string) =>\n `${collection}:${sectionKey}`;\n\nexport interface PageProviderProps {\n children: ReactNode;\n /** Server-rendered sections to hydrate from. */\n initialSections?: NestedSections;\n /** Server-rendered collection item lists to hydrate from (for add/remove/reorder). */\n initialCollections?: Record<string, CollectionItem[]>;\n /**\n * Base path of the CMS API route mounted via `createCmsHandlers`. Saves\n * `PATCH` to `${apiBasePath}/{collection}/{id}`. Defaults to `/api/admin`.\n */\n apiBasePath?: string;\n /** Client storage adapter used to upload pending (non-external) images on save. */\n storage?: ClientStorageAdapter;\n /** Notification sink. Defaults to `sonner` toasts. */\n notify?: Notifier;\n}\n\nexport const PageProvider = ({\n children,\n initialSections = {},\n initialCollections = {},\n apiBasePath = \"/api/admin\",\n storage,\n notify = defaultNotifier,\n}: PageProviderProps) => {\n const [saving, setSaving] = useState(false);\n const [sections, setSections] = useState<NestedSections>(initialSections);\n const [collections, setCollections] =\n useState<Record<string, CollectionItem[]>>(initialCollections);\n const [pendingImages, setPendingImages] = useState<PendingImage[]>([]);\n const [dirtySections, setDirtySections] = useState<Set<string>>(new Set());\n\n const hasUnsavedChanges = dirtySections.size > 0;\n\n const resolveImageUrl = useCallback(\n async (img: PendingImage): Promise<string> => {\n if (img.isExternal) return img.localUrl;\n if (!storage) {\n throw new Error(\n \"PageProvider received a file upload but no `storage` adapter was provided.\",\n );\n }\n const { url } = await storage.upload(img.file!);\n return url;\n },\n [storage],\n );\n\n const setSection = useCallback(\n (collection: string, key: string, section: Section) => {\n setSections((prev) => ({\n ...prev,\n [collection]: { ...prev[collection], [key]: section },\n }));\n },\n [],\n );\n\n const editField = useCallback(\n (\n collection: string,\n sectionKey: string,\n fieldKey: string,\n value: unknown,\n ) => {\n setSections((prev) => {\n const currentSection = prev[collection]?.[sectionKey];\n\n if (!currentSection) {\n console.error(`Section not found: ${collection}/${sectionKey}`);\n return prev;\n }\n\n const keys = fieldKey.split(\".\");\n const updated: Section = { ...currentSection };\n\n if (keys.length === 1) {\n updated[fieldKey] = value;\n } else {\n let current: Record<string, unknown> = updated;\n for (let i = 0; i < keys.length - 1; i++) {\n if (!current[keys[i]]) current[keys[i]] = {};\n current[keys[i]] = { ...(current[keys[i]] as object) };\n current = current[keys[i]] as Record<string, unknown>;\n }\n current[keys[keys.length - 1]] = value;\n }\n\n return {\n ...prev,\n [collection]: { ...prev[collection], [sectionKey]: updated },\n };\n });\n\n setDirtySections((prev) => {\n const next = new Set(prev);\n next.add(dirtyKey(collection, sectionKey));\n return next;\n });\n },\n [],\n );\n\n const setPendingImage = useCallback((image: PendingImage) => {\n setPendingImages((prev) => [\n ...prev.filter(\n (img) =>\n !(\n img.collection === image.collection &&\n img.sectionKey === image.sectionKey &&\n img.fieldKey === image.fieldKey\n ),\n ),\n image,\n ]);\n\n setDirtySections((prev) => {\n const next = new Set(prev);\n next.add(dirtyKey(image.collection, image.sectionKey));\n return next;\n });\n }, []);\n\n const persist = useCallback(\n async (section: Section) => {\n const response = await fetch(\n `${apiBasePath}/${section.collection}/${section.id}`,\n {\n method: \"PATCH\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(section),\n },\n );\n if (!response.ok) throw new Error(\"Failed to save section\");\n },\n [apiBasePath],\n );\n\n const saveSection = useCallback(\n async (collection: string, sectionKey: string) => {\n if (saving) return;\n setSaving(true);\n\n try {\n const section = sections[collection]?.[sectionKey];\n if (!section?.id || !section?.collection) {\n console.error(`Invalid section: ${collection}/${sectionKey}`);\n setSaving(false);\n return;\n }\n\n const images = pendingImages.filter(\n (img) =>\n img.collection === collection && img.sectionKey === sectionKey,\n );\n\n let updatedSection: Section = { ...section };\n\n for (const img of images) {\n const url = await resolveImageUrl(img);\n const keys = img.fieldKey.split(\".\");\n if (keys.length === 1) {\n updatedSection = { ...updatedSection, [img.fieldKey]: url };\n } else {\n let current: Record<string, unknown> = updatedSection;\n for (let i = 0; i < keys.length - 1; i++) {\n if (!current[keys[i]]) current[keys[i]] = {};\n current = current[keys[i]] as Record<string, unknown>;\n }\n current[keys[keys.length - 1]] = url;\n }\n }\n\n await persist(updatedSection);\n\n setSections((prev) => ({\n ...prev,\n [collection]: { ...prev[collection], [sectionKey]: updatedSection },\n }));\n\n setPendingImages((prev) =>\n prev.filter(\n (img) =>\n !(img.collection === collection && img.sectionKey === sectionKey),\n ),\n );\n\n setDirtySections((prev) => {\n const next = new Set(prev);\n next.delete(dirtyKey(collection, sectionKey));\n return next;\n });\n\n notify.success(\"Changes saved successfully!\");\n } catch (error) {\n console.error(\"Save failed:\", error);\n notify.error(\"Failed to save changes\");\n } finally {\n setSaving(false);\n }\n },\n [sections, pendingImages, saving, resolveImageUrl, persist, notify],\n );\n\n const saveAll = useCallback(async () => {\n if (saving || dirtySections.size === 0) return;\n setSaving(true);\n\n try {\n const updatedSections: NestedSections = { ...sections };\n\n for (const img of pendingImages) {\n const url = await resolveImageUrl(img);\n\n if (!updatedSections[img.collection])\n updatedSections[img.collection] = {};\n\n if (!updatedSections[img.collection][img.sectionKey]) {\n updatedSections[img.collection][img.sectionKey] = {\n id: img.docId,\n collection: img.collection,\n };\n }\n\n const keys = img.fieldKey.split(\".\");\n let current: Record<string, unknown> =\n updatedSections[img.collection][img.sectionKey];\n\n if (keys.length === 1) {\n updatedSections[img.collection][img.sectionKey] = {\n ...(current as Section),\n [img.fieldKey]: url,\n };\n } else {\n for (let i = 0; i < keys.length - 1; i++) {\n if (!current[keys[i]]) current[keys[i]] = {};\n current = current[keys[i]] as Record<string, unknown>;\n }\n current[keys[keys.length - 1]] = url;\n }\n }\n\n for (const entry of dirtySections) {\n const [collection, key] = entry.split(\":\");\n const section = updatedSections[collection]?.[key];\n\n if (!section?.id || !section?.collection) {\n console.error(`Invalid section for save: ${entry}`);\n continue;\n }\n\n await persist(section);\n }\n\n setSections(updatedSections);\n setPendingImages([]);\n setDirtySections(new Set());\n notify.success(\"All changes saved successfully!\");\n } catch (error) {\n console.error(\"Save all failed:\", error);\n notify.error(\"Failed to save changes\");\n } finally {\n setSaving(false);\n }\n }, [sections, pendingImages, dirtySections, saving, resolveImageUrl, persist, notify]);\n\n const createItem = useCallback(\n async (\n collection: string,\n data: Record<string, unknown>,\n opts?: { id?: string; atStart?: boolean },\n ): Promise<string> => {\n const id =\n opts?.id ??\n globalThis.crypto?.randomUUID?.() ??\n `${collection}-${Date.now()}`;\n const item: CollectionItem = { id, ...data };\n\n // Optimistic insert.\n setCollections((prev) => {\n const list = prev[collection] ?? [];\n return {\n ...prev,\n [collection]: opts?.atStart ? [item, ...list] : [...list, item],\n };\n });\n\n try {\n const res = await fetch(`${apiBasePath}/${collection}/${id}`, {\n method: \"PUT\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(data),\n });\n if (!res.ok) throw new Error(\"Failed to create item\");\n notify.success(\"Item added\");\n return id;\n } catch (error) {\n // Roll back the optimistic insert.\n setCollections((prev) => ({\n ...prev,\n [collection]: (prev[collection] ?? []).filter((it) => it.id !== id),\n }));\n notify.error(\"Failed to add item\");\n throw error;\n }\n },\n [apiBasePath, notify],\n );\n\n const updateItem = useCallback(\n async (\n collection: string,\n id: string,\n patch: Record<string, unknown>,\n ): Promise<void> => {\n let previous: CollectionItem[] = [];\n setCollections((prev) => {\n previous = prev[collection] ?? [];\n return {\n ...prev,\n [collection]: previous.map((it) =>\n it.id === id ? { ...it, ...patch } : it,\n ),\n };\n });\n\n try {\n const res = await fetch(`${apiBasePath}/${collection}/${id}`, {\n method: \"PATCH\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(patch),\n });\n if (!res.ok) throw new Error(\"Failed to update item\");\n notify.success(\"Saved\");\n } catch (error) {\n setCollections((prev) => ({ ...prev, [collection]: previous })); // rollback\n notify.error(\"Failed to save\");\n throw error;\n }\n },\n [apiBasePath, notify],\n );\n\n const deleteItem = useCallback(\n async (collection: string, id: string): Promise<void> => {\n let removed: CollectionItem[] = [];\n setCollections((prev) => {\n removed = prev[collection] ?? [];\n return {\n ...prev,\n [collection]: removed.filter((it) => it.id !== id),\n };\n });\n\n try {\n const res = await fetch(`${apiBasePath}/${collection}/${id}`, {\n method: \"DELETE\",\n });\n if (!res.ok) throw new Error(\"Failed to delete item\");\n notify.success(\"Item removed\");\n } catch (error) {\n setCollections((prev) => ({ ...prev, [collection]: removed })); // rollback\n notify.error(\"Failed to remove item\");\n throw error;\n }\n },\n [apiBasePath, notify],\n );\n\n const reorderItems = useCallback(\n async (collection: string, orderedIds: string[]): Promise<void> => {\n let previous: CollectionItem[] = [];\n let next: CollectionItem[] = [];\n setCollections((prev) => {\n previous = prev[collection] ?? [];\n const byId = new Map(previous.map((it) => [it.id, it]));\n next = orderedIds.flatMap((id, index) => {\n const item = byId.get(id);\n return item ? [{ ...item, order: index }] : [];\n });\n return { ...prev, [collection]: next };\n });\n\n try {\n await Promise.all(\n next.map(async (it) => {\n const res = await fetch(`${apiBasePath}/${collection}/${it.id}`, {\n method: \"PATCH\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ order: it.order }),\n });\n if (!res.ok) throw new Error(\"Failed to reorder\");\n }),\n );\n notify.success(\"Order updated\");\n } catch (error) {\n setCollections((prev) => ({ ...prev, [collection]: previous })); // rollback\n notify.error(\"Failed to update order\");\n throw error;\n }\n },\n [apiBasePath, notify],\n );\n\n return (\n <PageContext.Provider\n value={{\n sections,\n hasUnsavedChanges,\n pendingImages,\n saving,\n setSection,\n editField,\n setPendingImage,\n saveSection,\n saveAll,\n collections,\n createItem,\n updateItem,\n deleteItem,\n reorderItems,\n }}\n >\n {children}\n </PageContext.Provider>\n );\n};\n\nexport const usePageContext = () => {\n const context = useContext(PageContext);\n if (!context)\n throw new Error(\"usePageContext must be used within a PageProvider\");\n return context;\n};\n","\"use client\";\n\nimport { createContext, useContext, type ReactNode } from \"react\";\nimport type { CmsAuthState } from \"../types\";\n\n/**\n * The single source of client-side auth state for the engine. Every built-in\n * auth provider (and any custom one) feeds this context; the edit primitives\n * read it via {@link useCmsAuth}.\n *\n * Cross-entry note: provider packages (e.g. `…/auth/firebase/client`) import\n * this context through the package's public `…/client` specifier so they share\n * the *same* context instance as the consumer's primitives at runtime.\n */\nexport const CmsAuthContext = createContext<CmsAuthState | undefined>(undefined);\n\nexport function useCmsAuth(): CmsAuthState {\n const ctx = useContext(CmsAuthContext);\n if (!ctx) {\n throw new Error(\n \"useCmsAuth must be used within a CmsAuthProvider (or a built-in auth provider such as FirebaseAuthProvider).\",\n );\n }\n return ctx;\n}\n\n/**\n * Controlled provider for consumers wiring their own auth. Pass the resolved\n * {@link CmsAuthState}; the built-in providers wrap this for you.\n */\nexport function CmsAuthProvider({\n value,\n children,\n}: {\n value: CmsAuthState;\n children: ReactNode;\n}) {\n return (\n <CmsAuthContext.Provider value={value}>{children}</CmsAuthContext.Provider>\n );\n}\n","\"use client\";\n\nimport React, { useRef, useEffect, useState, useCallback } from \"react\";\nimport { usePageContext } from \"./PageProvider\";\nimport { useCmsAuth } from \"./auth\";\n\n/**\n * Inline-editable text primitive. Headless: it wires `contentEditable`, reads\n * and persists the field, and exposes edit state via `data-*` attributes —\n * styling and rich-text rendering are entirely yours.\n *\n * - Style it with `className` and the `data-cms-editing` / `data-cms-focused`\n * attributes (no built-in look, no Tailwind, no design tokens).\n * - Supply `renderValue` to turn the stored raw string into rich nodes (e.g. a\n * markdown parser). Defaults to plain text.\n */\ninterface ContentEditSpanProps {\n collection: string;\n sectionKey: string;\n fieldKey: string;\n className?: string;\n children?: React.ReactNode;\n /** Element/tag to render. Defaults to `span`. */\n as?: React.ElementType;\n /** Render the stored raw string into nodes. Defaults to plain text. */\n renderValue?: (raw: string) => React.ReactNode;\n}\n\nconst defaultRenderValue = (raw: string): React.ReactNode => raw;\n\nfunction getNestedValue(obj: unknown, path: string): unknown {\n const keys = path.split(\".\");\n let current: unknown = obj;\n\n for (const key of keys) {\n if (current == null || (current as Record<string, unknown>)[key] === undefined)\n return undefined;\n current = (current as Record<string, unknown>)[key];\n }\n\n return current;\n}\n\nexport default function ContentEditSpan({\n collection,\n sectionKey,\n fieldKey,\n className,\n children,\n as = \"span\",\n renderValue = defaultRenderValue,\n}: ContentEditSpanProps) {\n const { sections, editField } = usePageContext();\n const { isEditing } = useCmsAuth();\n\n const section = sections[collection]?.[sectionKey];\n const raw =\n (getNestedValue(section, fieldKey) as string) ??\n (typeof children === \"string\" ? children : \"\");\n\n const Component = as;\n\n if (!isEditing) {\n return <Component className={className}>{renderValue(raw)}</Component>;\n }\n\n return (\n <EditableContentSpan\n collection={collection}\n sectionKey={sectionKey}\n fieldKey={fieldKey}\n className={className}\n raw={raw}\n editField={editField}\n as={as}\n renderValue={renderValue}\n />\n );\n}\n\nfunction EditableContentSpan({\n collection,\n sectionKey,\n fieldKey,\n className,\n raw,\n editField,\n as: Component = \"span\",\n renderValue,\n}: {\n collection: string;\n sectionKey: string;\n fieldKey: string;\n className?: string;\n raw: string;\n editField: (\n collection: string,\n sectionKey: string,\n fieldKey: string,\n value: string,\n ) => void;\n as?: React.ElementType;\n renderValue: (raw: string) => React.ReactNode;\n}) {\n const { isEditing } = useCmsAuth();\n const [isFocused, setIsFocused] = useState(false);\n const [editValue, setEditValue] = useState(raw);\n const contentRef = useRef<HTMLElement>(null);\n const rawRef = useRef(raw);\n\n useEffect(() => {\n if (!isFocused && rawRef.current !== raw) {\n rawRef.current = raw;\n }\n }, [raw, isFocused]);\n\n useEffect(() => {\n if (!isFocused && rawRef.current !== editValue) {\n setEditValue(rawRef.current);\n }\n }, [isFocused, editValue]);\n\n const handleInput = useCallback(() => {\n if (contentRef.current) {\n setEditValue(contentRef.current.textContent || \"\");\n }\n }, []);\n\n const handleBlur = useCallback(() => {\n setIsFocused(false);\n if (editValue !== raw) {\n editField(collection, sectionKey, fieldKey, editValue);\n }\n }, [editValue, raw, collection, sectionKey, fieldKey, editField]);\n\n const handleFocus = useCallback(() => {\n setIsFocused(true);\n if (contentRef.current) {\n contentRef.current.textContent = editValue;\n setTimeout(() => {\n if (contentRef.current) {\n const range = document.createRange();\n const sel = window.getSelection();\n range.selectNodeContents(contentRef.current);\n range.collapse(false);\n sel?.removeAllRanges();\n sel?.addRange(range);\n }\n }, 0);\n }\n }, [editValue]);\n\n return (\n <Component\n key={isFocused ? \"editing\" : \"static\"}\n ref={contentRef}\n className={className}\n data-cms-editable=\"\"\n data-cms-editing={isEditing ? \"\" : undefined}\n data-cms-focused={isFocused ? \"\" : undefined}\n contentEditable={isEditing}\n suppressContentEditableWarning\n onInput={handleInput}\n onBlur={handleBlur}\n onFocus={handleFocus}\n >\n {!isFocused && renderValue(editValue)}\n </Component>\n );\n}\n","\"use client\";\n\nimport React, { useRef, useState } from \"react\";\nimport { useCmsAuth } from \"./auth\";\nimport { usePageContext } from \"./PageProvider\";\n\n/** State + actions handed to the {@link EditableImage} render-prop. */\nexport interface EditableImageRenderState {\n /** URL to display (pending upload preview, external URL, or the saved src). */\n src: string;\n isEditing: boolean;\n saving: boolean;\n /** True after the current `src` failed to load. */\n hasError: boolean;\n /** Open the native file picker (a hidden input is managed for you). */\n openFilePicker: () => void;\n /**\n * Queue an external image URL. Returns `false` if it isn't a valid http(s)\n * URL (nothing is changed in that case).\n */\n setExternalUrl: (url: string) => boolean;\n /** Convenience props for an `<img>`: `{ src, onError }`. */\n imgProps: { src: string; onError: () => void };\n}\n\ninterface EditableImageProps {\n sectionKey: string;\n fieldKey: string;\n src: string;\n collection: string;\n docId: string;\n className?: string;\n /**\n * Render the image and any editing chrome (overlay, buttons, URL modal). The\n * package ships no visual look — bring your own. When omitted, a bare\n * unstyled `<img>` is rendered.\n */\n children?: (state: EditableImageRenderState) => React.ReactNode;\n}\n\nexport default function EditableImage({\n sectionKey,\n fieldKey,\n src,\n collection,\n docId,\n className,\n children,\n}: EditableImageProps) {\n const { isEditing } = useCmsAuth();\n const { editField, setPendingImage, pendingImages, saving } =\n usePageContext();\n\n const [preview, setPreview] = useState(src);\n const [hasError, setHasError] = useState(false);\n const inputRef = useRef<HTMLInputElement>(null);\n\n const pendingImage = pendingImages.find(\n (img) => img.sectionKey === sectionKey && img.fieldKey === fieldKey,\n );\n const imgSrc = pendingImage?.localUrl || preview;\n\n const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n if (saving) return;\n const file = e.target.files?.[0];\n if (!file) return;\n\n const localUrl = URL.createObjectURL(file);\n setPreview(localUrl);\n setHasError(false);\n\n editField(collection, sectionKey, fieldKey, localUrl);\n setPendingImage({\n file,\n localUrl,\n sectionKey,\n fieldKey,\n collection,\n docId,\n isExternal: false,\n });\n };\n\n const openFilePicker = () => {\n if (saving) return;\n inputRef.current?.click();\n };\n\n const setExternalUrl = (value: string): boolean => {\n let valid = false;\n try {\n const url = new URL(value);\n valid = url.protocol === \"http:\" || url.protocol === \"https:\";\n } catch {\n valid = false;\n }\n if (!valid) return false;\n\n setPreview(value);\n setHasError(false);\n editField(collection, sectionKey, fieldKey, value);\n setPendingImage({\n file: null,\n localUrl: value,\n sectionKey,\n fieldKey,\n collection,\n docId,\n isExternal: true,\n });\n return true;\n };\n\n const state: EditableImageRenderState = {\n src: imgSrc,\n isEditing,\n saving,\n hasError,\n openFilePicker,\n setExternalUrl,\n imgProps: { src: imgSrc, onError: () => setHasError(true) },\n };\n\n return (\n <div className={className}>\n {/* Internal hidden file input — driven via openFilePicker(). */}\n <input\n ref={inputRef}\n type=\"file\"\n accept=\"image/*\"\n disabled={saving}\n onChange={handleFileChange}\n style={{ display: \"none\" }}\n />\n {children ? (\n children(state)\n ) : (\n // eslint-disable-next-line @next/next/no-img-element\n <img {...state.imgProps} alt=\"\" />\n )}\n </div>\n );\n}\n","\"use client\";\n\nimport { useCallback, useRef, useState } from \"react\";\n\nexport interface UseMarkdownEditorOptions {\n initialValue: string;\n onSave: (content: string) => void | Promise<void>;\n}\n\nexport interface MarkdownEditorApi {\n /** Current editor text. */\n value: string;\n setValue: (next: string) => void;\n /** Attach to your `<textarea>` so `insert` can target the selection. */\n textareaRef: React.RefObject<HTMLTextAreaElement | null>;\n /**\n * Wrap the current selection (or insert a placeholder) with `before`/`after`\n * markers — e.g. `insert(\"**\", \"**\", \"bold text\")`. Restores focus and caret.\n */\n insert: (before: string, after?: string, placeholder?: string) => void;\n /** Reset back to `initialValue` (or a provided value). */\n reset: (to?: string) => void;\n /** Persist the current value via the provided `onSave`. */\n save: () => void | Promise<void>;\n charCount: number;\n}\n\n/**\n * Headless markdown-editing logic: value state, a selection-aware `insert`\n * command, and save/reset. The package ships **no** modal, toolbar, icons, or\n * preview renderer — compose those yourself (e.g. with `react-markdown`). This\n * keeps the package free of any UI library or visual opinion.\n *\n * ```tsx\n * const md = useMarkdownEditor({ initialValue, onSave });\n * <textarea ref={md.textareaRef} value={md.value}\n * onChange={(e) => md.setValue(e.target.value)} />\n * <button onClick={() => md.insert(\"**\", \"**\", \"bold\")}>Bold</button>\n * ```\n */\nexport function useMarkdownEditor({\n initialValue,\n onSave,\n}: UseMarkdownEditorOptions): MarkdownEditorApi {\n const [value, setValue] = useState(initialValue);\n const textareaRef = useRef<HTMLTextAreaElement>(null);\n\n const insert = useCallback(\n (before: string, after = \"\", placeholder = \"text\") => {\n const textarea = textareaRef.current;\n const start = textarea?.selectionStart ?? value.length;\n const end = textarea?.selectionEnd ?? value.length;\n const selected = value.substring(start, end) || placeholder;\n const next =\n value.substring(0, start) +\n before +\n selected +\n after +\n value.substring(end);\n\n setValue(next);\n\n setTimeout(() => {\n if (!textarea) return;\n textarea.focus();\n const caret = start + before.length + selected.length;\n textarea.setSelectionRange(caret, caret);\n }, 0);\n },\n [value],\n );\n\n const reset = useCallback(\n (to: string = initialValue) => setValue(to),\n [initialValue],\n );\n\n const save = useCallback(() => onSave(value), [onSave, value]);\n\n return {\n value,\n setValue,\n textareaRef,\n insert,\n reset,\n save,\n charCount: value.length,\n };\n}\n"],"mappings":"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","names":["_a","import_react","import_jsx_runtime","import_react","import_jsx_runtime","import_react","import_jsx_runtime","import_react"]}
@@ -32,6 +32,11 @@ interface PageContextType {
32
32
  id?: string;
33
33
  atStart?: boolean;
34
34
  }) => Promise<string>;
35
+ /**
36
+ * Patch fields on an existing collection item and persist via `PATCH`.
37
+ * Optimistic with rollback on failure.
38
+ */
39
+ updateItem: (collection: string, id: string, patch: Record<string, unknown>) => Promise<void>;
35
40
  /** Remove an item from a collection and persist via `DELETE`. Optimistic. */
36
41
  deleteItem: (collection: string, id: string) => Promise<void>;
37
42
  /**
@@ -32,6 +32,11 @@ interface PageContextType {
32
32
  id?: string;
33
33
  atStart?: boolean;
34
34
  }) => Promise<string>;
35
+ /**
36
+ * Patch fields on an existing collection item and persist via `PATCH`.
37
+ * Optimistic with rollback on failure.
38
+ */
39
+ updateItem: (collection: string, id: string, patch: Record<string, unknown>) => Promise<void>;
35
40
  /** Remove an item from a collection and persist via `DELETE`. Optimistic. */
36
41
  deleteItem: (collection: string, id: string) => Promise<void>;
37
42
  /**
@@ -267,6 +267,34 @@ var PageProvider = ({
267
267
  },
268
268
  [apiBasePath, notify]
269
269
  );
270
+ const updateItem = useCallback(
271
+ async (collection, id, patch) => {
272
+ let previous = [];
273
+ setCollections((prev) => {
274
+ var _a;
275
+ previous = (_a = prev[collection]) != null ? _a : [];
276
+ return __spreadProps(__spreadValues({}, prev), {
277
+ [collection]: previous.map(
278
+ (it) => it.id === id ? __spreadValues(__spreadValues({}, it), patch) : it
279
+ )
280
+ });
281
+ });
282
+ try {
283
+ const res = await fetch(`${apiBasePath}/${collection}/${id}`, {
284
+ method: "PATCH",
285
+ headers: { "Content-Type": "application/json" },
286
+ body: JSON.stringify(patch)
287
+ });
288
+ if (!res.ok) throw new Error("Failed to update item");
289
+ notify.success("Saved");
290
+ } catch (error) {
291
+ setCollections((prev) => __spreadProps(__spreadValues({}, prev), { [collection]: previous }));
292
+ notify.error("Failed to save");
293
+ throw error;
294
+ }
295
+ },
296
+ [apiBasePath, notify]
297
+ );
270
298
  const deleteItem = useCallback(
271
299
  async (collection, id) => {
272
300
  let removed = [];
@@ -340,6 +368,7 @@ var PageProvider = ({
340
368
  saveAll,
341
369
  collections,
342
370
  createItem,
371
+ updateItem,
343
372
  deleteItem,
344
373
  reorderItems
345
374
  },
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/client/PageProvider.tsx","../../src/client/auth.tsx","../../src/client/ContentEditSpan.tsx","../../src/client/EditableImage.tsx","../../src/client/MarkdownEditor.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n createContext,\n useContext,\n useState,\n useCallback,\n type ReactNode,\n} from \"react\";\nimport type {\n ClientStorageAdapter,\n CollectionItem,\n NestedSections,\n PendingImage,\n Section,\n} from \"../types\";\n\nexport type { PendingImage } from \"../types\";\n\n/**\n * Notification sink. The package ships a dependency-free console default so it\n * imposes no toast library; pass your own (e.g. a `sonner`-backed sink) via the\n * `notify` prop to surface UI toasts.\n */\nexport interface Notifier {\n success: (message: string) => void;\n error: (message: string) => void;\n}\n\nconst defaultNotifier: Notifier = {\n success: (m) => console.info(`[cms] ${m}`),\n error: (m) => console.error(`[cms] ${m}`),\n};\n\ninterface PageContextType {\n sections: NestedSections;\n hasUnsavedChanges: boolean;\n saving: boolean;\n pendingImages: PendingImage[];\n setSection: (collection: string, key: string, section: Section) => void;\n editField: (\n collection: string,\n sectionKey: string,\n fieldKey: string,\n value: unknown,\n ) => void;\n setPendingImage: (image: PendingImage) => void;\n saveSection: (collection: string, sectionKey: string) => Promise<void>;\n saveAll: () => Promise<void>;\n /** Ordered item lists per collection (for add/remove/reorder). */\n collections: Record<string, CollectionItem[]>;\n /**\n * Append (or prepend) a new item to a collection and persist it immediately\n * via `PUT`. Returns the new item's id. Optimistic with rollback on failure.\n */\n createItem: (\n collection: string,\n data: Record<string, unknown>,\n opts?: { id?: string; atStart?: boolean },\n ) => Promise<string>;\n /** Remove an item from a collection and persist via `DELETE`. Optimistic. */\n deleteItem: (collection: string, id: string) => Promise<void>;\n /**\n * Reorder a collection to match `orderedIds`, persisting each item's new\n * integer `order` via `PATCH`. Optimistic with rollback on failure.\n */\n reorderItems: (collection: string, orderedIds: string[]) => Promise<void>;\n}\n\nconst PageContext = createContext<PageContextType | undefined>(undefined);\n\nconst dirtyKey = (collection: string, sectionKey: string) =>\n `${collection}:${sectionKey}`;\n\nexport interface PageProviderProps {\n children: ReactNode;\n /** Server-rendered sections to hydrate from. */\n initialSections?: NestedSections;\n /** Server-rendered collection item lists to hydrate from (for add/remove/reorder). */\n initialCollections?: Record<string, CollectionItem[]>;\n /**\n * Base path of the CMS API route mounted via `createCmsHandlers`. Saves\n * `PATCH` to `${apiBasePath}/{collection}/{id}`. Defaults to `/api/admin`.\n */\n apiBasePath?: string;\n /** Client storage adapter used to upload pending (non-external) images on save. */\n storage?: ClientStorageAdapter;\n /** Notification sink. Defaults to `sonner` toasts. */\n notify?: Notifier;\n}\n\nexport const PageProvider = ({\n children,\n initialSections = {},\n initialCollections = {},\n apiBasePath = \"/api/admin\",\n storage,\n notify = defaultNotifier,\n}: PageProviderProps) => {\n const [saving, setSaving] = useState(false);\n const [sections, setSections] = useState<NestedSections>(initialSections);\n const [collections, setCollections] =\n useState<Record<string, CollectionItem[]>>(initialCollections);\n const [pendingImages, setPendingImages] = useState<PendingImage[]>([]);\n const [dirtySections, setDirtySections] = useState<Set<string>>(new Set());\n\n const hasUnsavedChanges = dirtySections.size > 0;\n\n const resolveImageUrl = useCallback(\n async (img: PendingImage): Promise<string> => {\n if (img.isExternal) return img.localUrl;\n if (!storage) {\n throw new Error(\n \"PageProvider received a file upload but no `storage` adapter was provided.\",\n );\n }\n const { url } = await storage.upload(img.file!);\n return url;\n },\n [storage],\n );\n\n const setSection = useCallback(\n (collection: string, key: string, section: Section) => {\n setSections((prev) => ({\n ...prev,\n [collection]: { ...prev[collection], [key]: section },\n }));\n },\n [],\n );\n\n const editField = useCallback(\n (\n collection: string,\n sectionKey: string,\n fieldKey: string,\n value: unknown,\n ) => {\n setSections((prev) => {\n const currentSection = prev[collection]?.[sectionKey];\n\n if (!currentSection) {\n console.error(`Section not found: ${collection}/${sectionKey}`);\n return prev;\n }\n\n const keys = fieldKey.split(\".\");\n const updated: Section = { ...currentSection };\n\n if (keys.length === 1) {\n updated[fieldKey] = value;\n } else {\n let current: Record<string, unknown> = updated;\n for (let i = 0; i < keys.length - 1; i++) {\n if (!current[keys[i]]) current[keys[i]] = {};\n current[keys[i]] = { ...(current[keys[i]] as object) };\n current = current[keys[i]] as Record<string, unknown>;\n }\n current[keys[keys.length - 1]] = value;\n }\n\n return {\n ...prev,\n [collection]: { ...prev[collection], [sectionKey]: updated },\n };\n });\n\n setDirtySections((prev) => {\n const next = new Set(prev);\n next.add(dirtyKey(collection, sectionKey));\n return next;\n });\n },\n [],\n );\n\n const setPendingImage = useCallback((image: PendingImage) => {\n setPendingImages((prev) => [\n ...prev.filter(\n (img) =>\n !(\n img.collection === image.collection &&\n img.sectionKey === image.sectionKey &&\n img.fieldKey === image.fieldKey\n ),\n ),\n image,\n ]);\n\n setDirtySections((prev) => {\n const next = new Set(prev);\n next.add(dirtyKey(image.collection, image.sectionKey));\n return next;\n });\n }, []);\n\n const persist = useCallback(\n async (section: Section) => {\n const response = await fetch(\n `${apiBasePath}/${section.collection}/${section.id}`,\n {\n method: \"PATCH\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(section),\n },\n );\n if (!response.ok) throw new Error(\"Failed to save section\");\n },\n [apiBasePath],\n );\n\n const saveSection = useCallback(\n async (collection: string, sectionKey: string) => {\n if (saving) return;\n setSaving(true);\n\n try {\n const section = sections[collection]?.[sectionKey];\n if (!section?.id || !section?.collection) {\n console.error(`Invalid section: ${collection}/${sectionKey}`);\n setSaving(false);\n return;\n }\n\n const images = pendingImages.filter(\n (img) =>\n img.collection === collection && img.sectionKey === sectionKey,\n );\n\n let updatedSection: Section = { ...section };\n\n for (const img of images) {\n const url = await resolveImageUrl(img);\n const keys = img.fieldKey.split(\".\");\n if (keys.length === 1) {\n updatedSection = { ...updatedSection, [img.fieldKey]: url };\n } else {\n let current: Record<string, unknown> = updatedSection;\n for (let i = 0; i < keys.length - 1; i++) {\n if (!current[keys[i]]) current[keys[i]] = {};\n current = current[keys[i]] as Record<string, unknown>;\n }\n current[keys[keys.length - 1]] = url;\n }\n }\n\n await persist(updatedSection);\n\n setSections((prev) => ({\n ...prev,\n [collection]: { ...prev[collection], [sectionKey]: updatedSection },\n }));\n\n setPendingImages((prev) =>\n prev.filter(\n (img) =>\n !(img.collection === collection && img.sectionKey === sectionKey),\n ),\n );\n\n setDirtySections((prev) => {\n const next = new Set(prev);\n next.delete(dirtyKey(collection, sectionKey));\n return next;\n });\n\n notify.success(\"Changes saved successfully!\");\n } catch (error) {\n console.error(\"Save failed:\", error);\n notify.error(\"Failed to save changes\");\n } finally {\n setSaving(false);\n }\n },\n [sections, pendingImages, saving, resolveImageUrl, persist, notify],\n );\n\n const saveAll = useCallback(async () => {\n if (saving || dirtySections.size === 0) return;\n setSaving(true);\n\n try {\n const updatedSections: NestedSections = { ...sections };\n\n for (const img of pendingImages) {\n const url = await resolveImageUrl(img);\n\n if (!updatedSections[img.collection])\n updatedSections[img.collection] = {};\n\n if (!updatedSections[img.collection][img.sectionKey]) {\n updatedSections[img.collection][img.sectionKey] = {\n id: img.docId,\n collection: img.collection,\n };\n }\n\n const keys = img.fieldKey.split(\".\");\n let current: Record<string, unknown> =\n updatedSections[img.collection][img.sectionKey];\n\n if (keys.length === 1) {\n updatedSections[img.collection][img.sectionKey] = {\n ...(current as Section),\n [img.fieldKey]: url,\n };\n } else {\n for (let i = 0; i < keys.length - 1; i++) {\n if (!current[keys[i]]) current[keys[i]] = {};\n current = current[keys[i]] as Record<string, unknown>;\n }\n current[keys[keys.length - 1]] = url;\n }\n }\n\n for (const entry of dirtySections) {\n const [collection, key] = entry.split(\":\");\n const section = updatedSections[collection]?.[key];\n\n if (!section?.id || !section?.collection) {\n console.error(`Invalid section for save: ${entry}`);\n continue;\n }\n\n await persist(section);\n }\n\n setSections(updatedSections);\n setPendingImages([]);\n setDirtySections(new Set());\n notify.success(\"All changes saved successfully!\");\n } catch (error) {\n console.error(\"Save all failed:\", error);\n notify.error(\"Failed to save changes\");\n } finally {\n setSaving(false);\n }\n }, [sections, pendingImages, dirtySections, saving, resolveImageUrl, persist, notify]);\n\n const createItem = useCallback(\n async (\n collection: string,\n data: Record<string, unknown>,\n opts?: { id?: string; atStart?: boolean },\n ): Promise<string> => {\n const id =\n opts?.id ??\n globalThis.crypto?.randomUUID?.() ??\n `${collection}-${Date.now()}`;\n const item: CollectionItem = { id, ...data };\n\n // Optimistic insert.\n setCollections((prev) => {\n const list = prev[collection] ?? [];\n return {\n ...prev,\n [collection]: opts?.atStart ? [item, ...list] : [...list, item],\n };\n });\n\n try {\n const res = await fetch(`${apiBasePath}/${collection}/${id}`, {\n method: \"PUT\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(data),\n });\n if (!res.ok) throw new Error(\"Failed to create item\");\n notify.success(\"Item added\");\n return id;\n } catch (error) {\n // Roll back the optimistic insert.\n setCollections((prev) => ({\n ...prev,\n [collection]: (prev[collection] ?? []).filter((it) => it.id !== id),\n }));\n notify.error(\"Failed to add item\");\n throw error;\n }\n },\n [apiBasePath, notify],\n );\n\n const deleteItem = useCallback(\n async (collection: string, id: string): Promise<void> => {\n let removed: CollectionItem[] = [];\n setCollections((prev) => {\n removed = prev[collection] ?? [];\n return {\n ...prev,\n [collection]: removed.filter((it) => it.id !== id),\n };\n });\n\n try {\n const res = await fetch(`${apiBasePath}/${collection}/${id}`, {\n method: \"DELETE\",\n });\n if (!res.ok) throw new Error(\"Failed to delete item\");\n notify.success(\"Item removed\");\n } catch (error) {\n setCollections((prev) => ({ ...prev, [collection]: removed })); // rollback\n notify.error(\"Failed to remove item\");\n throw error;\n }\n },\n [apiBasePath, notify],\n );\n\n const reorderItems = useCallback(\n async (collection: string, orderedIds: string[]): Promise<void> => {\n let previous: CollectionItem[] = [];\n let next: CollectionItem[] = [];\n setCollections((prev) => {\n previous = prev[collection] ?? [];\n const byId = new Map(previous.map((it) => [it.id, it]));\n next = orderedIds.flatMap((id, index) => {\n const item = byId.get(id);\n return item ? [{ ...item, order: index }] : [];\n });\n return { ...prev, [collection]: next };\n });\n\n try {\n await Promise.all(\n next.map(async (it) => {\n const res = await fetch(`${apiBasePath}/${collection}/${it.id}`, {\n method: \"PATCH\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ order: it.order }),\n });\n if (!res.ok) throw new Error(\"Failed to reorder\");\n }),\n );\n notify.success(\"Order updated\");\n } catch (error) {\n setCollections((prev) => ({ ...prev, [collection]: previous })); // rollback\n notify.error(\"Failed to update order\");\n throw error;\n }\n },\n [apiBasePath, notify],\n );\n\n return (\n <PageContext.Provider\n value={{\n sections,\n hasUnsavedChanges,\n pendingImages,\n saving,\n setSection,\n editField,\n setPendingImage,\n saveSection,\n saveAll,\n collections,\n createItem,\n deleteItem,\n reorderItems,\n }}\n >\n {children}\n </PageContext.Provider>\n );\n};\n\nexport const usePageContext = () => {\n const context = useContext(PageContext);\n if (!context)\n throw new Error(\"usePageContext must be used within a PageProvider\");\n return context;\n};\n","\"use client\";\n\nimport { createContext, useContext, type ReactNode } from \"react\";\nimport type { CmsAuthState } from \"../types\";\n\n/**\n * The single source of client-side auth state for the engine. Every built-in\n * auth provider (and any custom one) feeds this context; the edit primitives\n * read it via {@link useCmsAuth}.\n *\n * Cross-entry note: provider packages (e.g. `…/auth/firebase/client`) import\n * this context through the package's public `…/client` specifier so they share\n * the *same* context instance as the consumer's primitives at runtime.\n */\nexport const CmsAuthContext = createContext<CmsAuthState | undefined>(undefined);\n\nexport function useCmsAuth(): CmsAuthState {\n const ctx = useContext(CmsAuthContext);\n if (!ctx) {\n throw new Error(\n \"useCmsAuth must be used within a CmsAuthProvider (or a built-in auth provider such as FirebaseAuthProvider).\",\n );\n }\n return ctx;\n}\n\n/**\n * Controlled provider for consumers wiring their own auth. Pass the resolved\n * {@link CmsAuthState}; the built-in providers wrap this for you.\n */\nexport function CmsAuthProvider({\n value,\n children,\n}: {\n value: CmsAuthState;\n children: ReactNode;\n}) {\n return (\n <CmsAuthContext.Provider value={value}>{children}</CmsAuthContext.Provider>\n );\n}\n","\"use client\";\n\nimport React, { useRef, useEffect, useState, useCallback } from \"react\";\nimport { usePageContext } from \"./PageProvider\";\nimport { useCmsAuth } from \"./auth\";\n\n/**\n * Inline-editable text primitive. Headless: it wires `contentEditable`, reads\n * and persists the field, and exposes edit state via `data-*` attributes —\n * styling and rich-text rendering are entirely yours.\n *\n * - Style it with `className` and the `data-cms-editing` / `data-cms-focused`\n * attributes (no built-in look, no Tailwind, no design tokens).\n * - Supply `renderValue` to turn the stored raw string into rich nodes (e.g. a\n * markdown parser). Defaults to plain text.\n */\ninterface ContentEditSpanProps {\n collection: string;\n sectionKey: string;\n fieldKey: string;\n className?: string;\n children?: React.ReactNode;\n /** Element/tag to render. Defaults to `span`. */\n as?: React.ElementType;\n /** Render the stored raw string into nodes. Defaults to plain text. */\n renderValue?: (raw: string) => React.ReactNode;\n}\n\nconst defaultRenderValue = (raw: string): React.ReactNode => raw;\n\nfunction getNestedValue(obj: unknown, path: string): unknown {\n const keys = path.split(\".\");\n let current: unknown = obj;\n\n for (const key of keys) {\n if (current == null || (current as Record<string, unknown>)[key] === undefined)\n return undefined;\n current = (current as Record<string, unknown>)[key];\n }\n\n return current;\n}\n\nexport default function ContentEditSpan({\n collection,\n sectionKey,\n fieldKey,\n className,\n children,\n as = \"span\",\n renderValue = defaultRenderValue,\n}: ContentEditSpanProps) {\n const { sections, editField } = usePageContext();\n const { isEditing } = useCmsAuth();\n\n const section = sections[collection]?.[sectionKey];\n const raw =\n (getNestedValue(section, fieldKey) as string) ??\n (typeof children === \"string\" ? children : \"\");\n\n const Component = as;\n\n if (!isEditing) {\n return <Component className={className}>{renderValue(raw)}</Component>;\n }\n\n return (\n <EditableContentSpan\n collection={collection}\n sectionKey={sectionKey}\n fieldKey={fieldKey}\n className={className}\n raw={raw}\n editField={editField}\n as={as}\n renderValue={renderValue}\n />\n );\n}\n\nfunction EditableContentSpan({\n collection,\n sectionKey,\n fieldKey,\n className,\n raw,\n editField,\n as: Component = \"span\",\n renderValue,\n}: {\n collection: string;\n sectionKey: string;\n fieldKey: string;\n className?: string;\n raw: string;\n editField: (\n collection: string,\n sectionKey: string,\n fieldKey: string,\n value: string,\n ) => void;\n as?: React.ElementType;\n renderValue: (raw: string) => React.ReactNode;\n}) {\n const { isEditing } = useCmsAuth();\n const [isFocused, setIsFocused] = useState(false);\n const [editValue, setEditValue] = useState(raw);\n const contentRef = useRef<HTMLElement>(null);\n const rawRef = useRef(raw);\n\n useEffect(() => {\n if (!isFocused && rawRef.current !== raw) {\n rawRef.current = raw;\n }\n }, [raw, isFocused]);\n\n useEffect(() => {\n if (!isFocused && rawRef.current !== editValue) {\n setEditValue(rawRef.current);\n }\n }, [isFocused, editValue]);\n\n const handleInput = useCallback(() => {\n if (contentRef.current) {\n setEditValue(contentRef.current.textContent || \"\");\n }\n }, []);\n\n const handleBlur = useCallback(() => {\n setIsFocused(false);\n if (editValue !== raw) {\n editField(collection, sectionKey, fieldKey, editValue);\n }\n }, [editValue, raw, collection, sectionKey, fieldKey, editField]);\n\n const handleFocus = useCallback(() => {\n setIsFocused(true);\n if (contentRef.current) {\n contentRef.current.textContent = editValue;\n setTimeout(() => {\n if (contentRef.current) {\n const range = document.createRange();\n const sel = window.getSelection();\n range.selectNodeContents(contentRef.current);\n range.collapse(false);\n sel?.removeAllRanges();\n sel?.addRange(range);\n }\n }, 0);\n }\n }, [editValue]);\n\n return (\n <Component\n key={isFocused ? \"editing\" : \"static\"}\n ref={contentRef}\n className={className}\n data-cms-editable=\"\"\n data-cms-editing={isEditing ? \"\" : undefined}\n data-cms-focused={isFocused ? \"\" : undefined}\n contentEditable={isEditing}\n suppressContentEditableWarning\n onInput={handleInput}\n onBlur={handleBlur}\n onFocus={handleFocus}\n >\n {!isFocused && renderValue(editValue)}\n </Component>\n );\n}\n","\"use client\";\n\nimport React, { useRef, useState } from \"react\";\nimport { useCmsAuth } from \"./auth\";\nimport { usePageContext } from \"./PageProvider\";\n\n/** State + actions handed to the {@link EditableImage} render-prop. */\nexport interface EditableImageRenderState {\n /** URL to display (pending upload preview, external URL, or the saved src). */\n src: string;\n isEditing: boolean;\n saving: boolean;\n /** True after the current `src` failed to load. */\n hasError: boolean;\n /** Open the native file picker (a hidden input is managed for you). */\n openFilePicker: () => void;\n /**\n * Queue an external image URL. Returns `false` if it isn't a valid http(s)\n * URL (nothing is changed in that case).\n */\n setExternalUrl: (url: string) => boolean;\n /** Convenience props for an `<img>`: `{ src, onError }`. */\n imgProps: { src: string; onError: () => void };\n}\n\ninterface EditableImageProps {\n sectionKey: string;\n fieldKey: string;\n src: string;\n collection: string;\n docId: string;\n className?: string;\n /**\n * Render the image and any editing chrome (overlay, buttons, URL modal). The\n * package ships no visual look — bring your own. When omitted, a bare\n * unstyled `<img>` is rendered.\n */\n children?: (state: EditableImageRenderState) => React.ReactNode;\n}\n\nexport default function EditableImage({\n sectionKey,\n fieldKey,\n src,\n collection,\n docId,\n className,\n children,\n}: EditableImageProps) {\n const { isEditing } = useCmsAuth();\n const { editField, setPendingImage, pendingImages, saving } =\n usePageContext();\n\n const [preview, setPreview] = useState(src);\n const [hasError, setHasError] = useState(false);\n const inputRef = useRef<HTMLInputElement>(null);\n\n const pendingImage = pendingImages.find(\n (img) => img.sectionKey === sectionKey && img.fieldKey === fieldKey,\n );\n const imgSrc = pendingImage?.localUrl || preview;\n\n const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n if (saving) return;\n const file = e.target.files?.[0];\n if (!file) return;\n\n const localUrl = URL.createObjectURL(file);\n setPreview(localUrl);\n setHasError(false);\n\n editField(collection, sectionKey, fieldKey, localUrl);\n setPendingImage({\n file,\n localUrl,\n sectionKey,\n fieldKey,\n collection,\n docId,\n isExternal: false,\n });\n };\n\n const openFilePicker = () => {\n if (saving) return;\n inputRef.current?.click();\n };\n\n const setExternalUrl = (value: string): boolean => {\n let valid = false;\n try {\n const url = new URL(value);\n valid = url.protocol === \"http:\" || url.protocol === \"https:\";\n } catch {\n valid = false;\n }\n if (!valid) return false;\n\n setPreview(value);\n setHasError(false);\n editField(collection, sectionKey, fieldKey, value);\n setPendingImage({\n file: null,\n localUrl: value,\n sectionKey,\n fieldKey,\n collection,\n docId,\n isExternal: true,\n });\n return true;\n };\n\n const state: EditableImageRenderState = {\n src: imgSrc,\n isEditing,\n saving,\n hasError,\n openFilePicker,\n setExternalUrl,\n imgProps: { src: imgSrc, onError: () => setHasError(true) },\n };\n\n return (\n <div className={className}>\n {/* Internal hidden file input — driven via openFilePicker(). */}\n <input\n ref={inputRef}\n type=\"file\"\n accept=\"image/*\"\n disabled={saving}\n onChange={handleFileChange}\n style={{ display: \"none\" }}\n />\n {children ? (\n children(state)\n ) : (\n // eslint-disable-next-line @next/next/no-img-element\n <img {...state.imgProps} alt=\"\" />\n )}\n </div>\n );\n}\n","\"use client\";\n\nimport { useCallback, useRef, useState } from \"react\";\n\nexport interface UseMarkdownEditorOptions {\n initialValue: string;\n onSave: (content: string) => void | Promise<void>;\n}\n\nexport interface MarkdownEditorApi {\n /** Current editor text. */\n value: string;\n setValue: (next: string) => void;\n /** Attach to your `<textarea>` so `insert` can target the selection. */\n textareaRef: React.RefObject<HTMLTextAreaElement | null>;\n /**\n * Wrap the current selection (or insert a placeholder) with `before`/`after`\n * markers — e.g. `insert(\"**\", \"**\", \"bold text\")`. Restores focus and caret.\n */\n insert: (before: string, after?: string, placeholder?: string) => void;\n /** Reset back to `initialValue` (or a provided value). */\n reset: (to?: string) => void;\n /** Persist the current value via the provided `onSave`. */\n save: () => void | Promise<void>;\n charCount: number;\n}\n\n/**\n * Headless markdown-editing logic: value state, a selection-aware `insert`\n * command, and save/reset. The package ships **no** modal, toolbar, icons, or\n * preview renderer — compose those yourself (e.g. with `react-markdown`). This\n * keeps the package free of any UI library or visual opinion.\n *\n * ```tsx\n * const md = useMarkdownEditor({ initialValue, onSave });\n * <textarea ref={md.textareaRef} value={md.value}\n * onChange={(e) => md.setValue(e.target.value)} />\n * <button onClick={() => md.insert(\"**\", \"**\", \"bold\")}>Bold</button>\n * ```\n */\nexport function useMarkdownEditor({\n initialValue,\n onSave,\n}: UseMarkdownEditorOptions): MarkdownEditorApi {\n const [value, setValue] = useState(initialValue);\n const textareaRef = useRef<HTMLTextAreaElement>(null);\n\n const insert = useCallback(\n (before: string, after = \"\", placeholder = \"text\") => {\n const textarea = textareaRef.current;\n const start = textarea?.selectionStart ?? value.length;\n const end = textarea?.selectionEnd ?? value.length;\n const selected = value.substring(start, end) || placeholder;\n const next =\n value.substring(0, start) +\n before +\n selected +\n after +\n value.substring(end);\n\n setValue(next);\n\n setTimeout(() => {\n if (!textarea) return;\n textarea.focus();\n const caret = start + before.length + selected.length;\n textarea.setSelectionRange(caret, caret);\n }, 0);\n },\n [value],\n );\n\n const reset = useCallback(\n (to: string = initialValue) => setValue(to),\n [initialValue],\n );\n\n const save = useCallback(() => onSave(value), [onSave, value]);\n\n return {\n value,\n setValue,\n textareaRef,\n insert,\n reset,\n save,\n charCount: value.length,\n };\n}\n"],"mappings":"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","names":["_a","createContext","useContext","jsx","useState","useCallback","jsx","useState","useCallback","useRef","useState","jsx","useState","useRef","useCallback","useRef","useState"]}
1
+ {"version":3,"sources":["../../src/client/PageProvider.tsx","../../src/client/auth.tsx","../../src/client/ContentEditSpan.tsx","../../src/client/EditableImage.tsx","../../src/client/MarkdownEditor.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n createContext,\n useContext,\n useState,\n useCallback,\n type ReactNode,\n} from \"react\";\nimport type {\n ClientStorageAdapter,\n CollectionItem,\n NestedSections,\n PendingImage,\n Section,\n} from \"../types\";\n\nexport type { PendingImage } from \"../types\";\n\n/**\n * Notification sink. The package ships a dependency-free console default so it\n * imposes no toast library; pass your own (e.g. a `sonner`-backed sink) via the\n * `notify` prop to surface UI toasts.\n */\nexport interface Notifier {\n success: (message: string) => void;\n error: (message: string) => void;\n}\n\nconst defaultNotifier: Notifier = {\n success: (m) => console.info(`[cms] ${m}`),\n error: (m) => console.error(`[cms] ${m}`),\n};\n\ninterface PageContextType {\n sections: NestedSections;\n hasUnsavedChanges: boolean;\n saving: boolean;\n pendingImages: PendingImage[];\n setSection: (collection: string, key: string, section: Section) => void;\n editField: (\n collection: string,\n sectionKey: string,\n fieldKey: string,\n value: unknown,\n ) => void;\n setPendingImage: (image: PendingImage) => void;\n saveSection: (collection: string, sectionKey: string) => Promise<void>;\n saveAll: () => Promise<void>;\n /** Ordered item lists per collection (for add/remove/reorder). */\n collections: Record<string, CollectionItem[]>;\n /**\n * Append (or prepend) a new item to a collection and persist it immediately\n * via `PUT`. Returns the new item's id. Optimistic with rollback on failure.\n */\n createItem: (\n collection: string,\n data: Record<string, unknown>,\n opts?: { id?: string; atStart?: boolean },\n ) => Promise<string>;\n /**\n * Patch fields on an existing collection item and persist via `PATCH`.\n * Optimistic with rollback on failure.\n */\n updateItem: (\n collection: string,\n id: string,\n patch: Record<string, unknown>,\n ) => Promise<void>;\n /** Remove an item from a collection and persist via `DELETE`. Optimistic. */\n deleteItem: (collection: string, id: string) => Promise<void>;\n /**\n * Reorder a collection to match `orderedIds`, persisting each item's new\n * integer `order` via `PATCH`. Optimistic with rollback on failure.\n */\n reorderItems: (collection: string, orderedIds: string[]) => Promise<void>;\n}\n\nconst PageContext = createContext<PageContextType | undefined>(undefined);\n\nconst dirtyKey = (collection: string, sectionKey: string) =>\n `${collection}:${sectionKey}`;\n\nexport interface PageProviderProps {\n children: ReactNode;\n /** Server-rendered sections to hydrate from. */\n initialSections?: NestedSections;\n /** Server-rendered collection item lists to hydrate from (for add/remove/reorder). */\n initialCollections?: Record<string, CollectionItem[]>;\n /**\n * Base path of the CMS API route mounted via `createCmsHandlers`. Saves\n * `PATCH` to `${apiBasePath}/{collection}/{id}`. Defaults to `/api/admin`.\n */\n apiBasePath?: string;\n /** Client storage adapter used to upload pending (non-external) images on save. */\n storage?: ClientStorageAdapter;\n /** Notification sink. Defaults to `sonner` toasts. */\n notify?: Notifier;\n}\n\nexport const PageProvider = ({\n children,\n initialSections = {},\n initialCollections = {},\n apiBasePath = \"/api/admin\",\n storage,\n notify = defaultNotifier,\n}: PageProviderProps) => {\n const [saving, setSaving] = useState(false);\n const [sections, setSections] = useState<NestedSections>(initialSections);\n const [collections, setCollections] =\n useState<Record<string, CollectionItem[]>>(initialCollections);\n const [pendingImages, setPendingImages] = useState<PendingImage[]>([]);\n const [dirtySections, setDirtySections] = useState<Set<string>>(new Set());\n\n const hasUnsavedChanges = dirtySections.size > 0;\n\n const resolveImageUrl = useCallback(\n async (img: PendingImage): Promise<string> => {\n if (img.isExternal) return img.localUrl;\n if (!storage) {\n throw new Error(\n \"PageProvider received a file upload but no `storage` adapter was provided.\",\n );\n }\n const { url } = await storage.upload(img.file!);\n return url;\n },\n [storage],\n );\n\n const setSection = useCallback(\n (collection: string, key: string, section: Section) => {\n setSections((prev) => ({\n ...prev,\n [collection]: { ...prev[collection], [key]: section },\n }));\n },\n [],\n );\n\n const editField = useCallback(\n (\n collection: string,\n sectionKey: string,\n fieldKey: string,\n value: unknown,\n ) => {\n setSections((prev) => {\n const currentSection = prev[collection]?.[sectionKey];\n\n if (!currentSection) {\n console.error(`Section not found: ${collection}/${sectionKey}`);\n return prev;\n }\n\n const keys = fieldKey.split(\".\");\n const updated: Section = { ...currentSection };\n\n if (keys.length === 1) {\n updated[fieldKey] = value;\n } else {\n let current: Record<string, unknown> = updated;\n for (let i = 0; i < keys.length - 1; i++) {\n if (!current[keys[i]]) current[keys[i]] = {};\n current[keys[i]] = { ...(current[keys[i]] as object) };\n current = current[keys[i]] as Record<string, unknown>;\n }\n current[keys[keys.length - 1]] = value;\n }\n\n return {\n ...prev,\n [collection]: { ...prev[collection], [sectionKey]: updated },\n };\n });\n\n setDirtySections((prev) => {\n const next = new Set(prev);\n next.add(dirtyKey(collection, sectionKey));\n return next;\n });\n },\n [],\n );\n\n const setPendingImage = useCallback((image: PendingImage) => {\n setPendingImages((prev) => [\n ...prev.filter(\n (img) =>\n !(\n img.collection === image.collection &&\n img.sectionKey === image.sectionKey &&\n img.fieldKey === image.fieldKey\n ),\n ),\n image,\n ]);\n\n setDirtySections((prev) => {\n const next = new Set(prev);\n next.add(dirtyKey(image.collection, image.sectionKey));\n return next;\n });\n }, []);\n\n const persist = useCallback(\n async (section: Section) => {\n const response = await fetch(\n `${apiBasePath}/${section.collection}/${section.id}`,\n {\n method: \"PATCH\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(section),\n },\n );\n if (!response.ok) throw new Error(\"Failed to save section\");\n },\n [apiBasePath],\n );\n\n const saveSection = useCallback(\n async (collection: string, sectionKey: string) => {\n if (saving) return;\n setSaving(true);\n\n try {\n const section = sections[collection]?.[sectionKey];\n if (!section?.id || !section?.collection) {\n console.error(`Invalid section: ${collection}/${sectionKey}`);\n setSaving(false);\n return;\n }\n\n const images = pendingImages.filter(\n (img) =>\n img.collection === collection && img.sectionKey === sectionKey,\n );\n\n let updatedSection: Section = { ...section };\n\n for (const img of images) {\n const url = await resolveImageUrl(img);\n const keys = img.fieldKey.split(\".\");\n if (keys.length === 1) {\n updatedSection = { ...updatedSection, [img.fieldKey]: url };\n } else {\n let current: Record<string, unknown> = updatedSection;\n for (let i = 0; i < keys.length - 1; i++) {\n if (!current[keys[i]]) current[keys[i]] = {};\n current = current[keys[i]] as Record<string, unknown>;\n }\n current[keys[keys.length - 1]] = url;\n }\n }\n\n await persist(updatedSection);\n\n setSections((prev) => ({\n ...prev,\n [collection]: { ...prev[collection], [sectionKey]: updatedSection },\n }));\n\n setPendingImages((prev) =>\n prev.filter(\n (img) =>\n !(img.collection === collection && img.sectionKey === sectionKey),\n ),\n );\n\n setDirtySections((prev) => {\n const next = new Set(prev);\n next.delete(dirtyKey(collection, sectionKey));\n return next;\n });\n\n notify.success(\"Changes saved successfully!\");\n } catch (error) {\n console.error(\"Save failed:\", error);\n notify.error(\"Failed to save changes\");\n } finally {\n setSaving(false);\n }\n },\n [sections, pendingImages, saving, resolveImageUrl, persist, notify],\n );\n\n const saveAll = useCallback(async () => {\n if (saving || dirtySections.size === 0) return;\n setSaving(true);\n\n try {\n const updatedSections: NestedSections = { ...sections };\n\n for (const img of pendingImages) {\n const url = await resolveImageUrl(img);\n\n if (!updatedSections[img.collection])\n updatedSections[img.collection] = {};\n\n if (!updatedSections[img.collection][img.sectionKey]) {\n updatedSections[img.collection][img.sectionKey] = {\n id: img.docId,\n collection: img.collection,\n };\n }\n\n const keys = img.fieldKey.split(\".\");\n let current: Record<string, unknown> =\n updatedSections[img.collection][img.sectionKey];\n\n if (keys.length === 1) {\n updatedSections[img.collection][img.sectionKey] = {\n ...(current as Section),\n [img.fieldKey]: url,\n };\n } else {\n for (let i = 0; i < keys.length - 1; i++) {\n if (!current[keys[i]]) current[keys[i]] = {};\n current = current[keys[i]] as Record<string, unknown>;\n }\n current[keys[keys.length - 1]] = url;\n }\n }\n\n for (const entry of dirtySections) {\n const [collection, key] = entry.split(\":\");\n const section = updatedSections[collection]?.[key];\n\n if (!section?.id || !section?.collection) {\n console.error(`Invalid section for save: ${entry}`);\n continue;\n }\n\n await persist(section);\n }\n\n setSections(updatedSections);\n setPendingImages([]);\n setDirtySections(new Set());\n notify.success(\"All changes saved successfully!\");\n } catch (error) {\n console.error(\"Save all failed:\", error);\n notify.error(\"Failed to save changes\");\n } finally {\n setSaving(false);\n }\n }, [sections, pendingImages, dirtySections, saving, resolveImageUrl, persist, notify]);\n\n const createItem = useCallback(\n async (\n collection: string,\n data: Record<string, unknown>,\n opts?: { id?: string; atStart?: boolean },\n ): Promise<string> => {\n const id =\n opts?.id ??\n globalThis.crypto?.randomUUID?.() ??\n `${collection}-${Date.now()}`;\n const item: CollectionItem = { id, ...data };\n\n // Optimistic insert.\n setCollections((prev) => {\n const list = prev[collection] ?? [];\n return {\n ...prev,\n [collection]: opts?.atStart ? [item, ...list] : [...list, item],\n };\n });\n\n try {\n const res = await fetch(`${apiBasePath}/${collection}/${id}`, {\n method: \"PUT\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(data),\n });\n if (!res.ok) throw new Error(\"Failed to create item\");\n notify.success(\"Item added\");\n return id;\n } catch (error) {\n // Roll back the optimistic insert.\n setCollections((prev) => ({\n ...prev,\n [collection]: (prev[collection] ?? []).filter((it) => it.id !== id),\n }));\n notify.error(\"Failed to add item\");\n throw error;\n }\n },\n [apiBasePath, notify],\n );\n\n const updateItem = useCallback(\n async (\n collection: string,\n id: string,\n patch: Record<string, unknown>,\n ): Promise<void> => {\n let previous: CollectionItem[] = [];\n setCollections((prev) => {\n previous = prev[collection] ?? [];\n return {\n ...prev,\n [collection]: previous.map((it) =>\n it.id === id ? { ...it, ...patch } : it,\n ),\n };\n });\n\n try {\n const res = await fetch(`${apiBasePath}/${collection}/${id}`, {\n method: \"PATCH\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(patch),\n });\n if (!res.ok) throw new Error(\"Failed to update item\");\n notify.success(\"Saved\");\n } catch (error) {\n setCollections((prev) => ({ ...prev, [collection]: previous })); // rollback\n notify.error(\"Failed to save\");\n throw error;\n }\n },\n [apiBasePath, notify],\n );\n\n const deleteItem = useCallback(\n async (collection: string, id: string): Promise<void> => {\n let removed: CollectionItem[] = [];\n setCollections((prev) => {\n removed = prev[collection] ?? [];\n return {\n ...prev,\n [collection]: removed.filter((it) => it.id !== id),\n };\n });\n\n try {\n const res = await fetch(`${apiBasePath}/${collection}/${id}`, {\n method: \"DELETE\",\n });\n if (!res.ok) throw new Error(\"Failed to delete item\");\n notify.success(\"Item removed\");\n } catch (error) {\n setCollections((prev) => ({ ...prev, [collection]: removed })); // rollback\n notify.error(\"Failed to remove item\");\n throw error;\n }\n },\n [apiBasePath, notify],\n );\n\n const reorderItems = useCallback(\n async (collection: string, orderedIds: string[]): Promise<void> => {\n let previous: CollectionItem[] = [];\n let next: CollectionItem[] = [];\n setCollections((prev) => {\n previous = prev[collection] ?? [];\n const byId = new Map(previous.map((it) => [it.id, it]));\n next = orderedIds.flatMap((id, index) => {\n const item = byId.get(id);\n return item ? [{ ...item, order: index }] : [];\n });\n return { ...prev, [collection]: next };\n });\n\n try {\n await Promise.all(\n next.map(async (it) => {\n const res = await fetch(`${apiBasePath}/${collection}/${it.id}`, {\n method: \"PATCH\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ order: it.order }),\n });\n if (!res.ok) throw new Error(\"Failed to reorder\");\n }),\n );\n notify.success(\"Order updated\");\n } catch (error) {\n setCollections((prev) => ({ ...prev, [collection]: previous })); // rollback\n notify.error(\"Failed to update order\");\n throw error;\n }\n },\n [apiBasePath, notify],\n );\n\n return (\n <PageContext.Provider\n value={{\n sections,\n hasUnsavedChanges,\n pendingImages,\n saving,\n setSection,\n editField,\n setPendingImage,\n saveSection,\n saveAll,\n collections,\n createItem,\n updateItem,\n deleteItem,\n reorderItems,\n }}\n >\n {children}\n </PageContext.Provider>\n );\n};\n\nexport const usePageContext = () => {\n const context = useContext(PageContext);\n if (!context)\n throw new Error(\"usePageContext must be used within a PageProvider\");\n return context;\n};\n","\"use client\";\n\nimport { createContext, useContext, type ReactNode } from \"react\";\nimport type { CmsAuthState } from \"../types\";\n\n/**\n * The single source of client-side auth state for the engine. Every built-in\n * auth provider (and any custom one) feeds this context; the edit primitives\n * read it via {@link useCmsAuth}.\n *\n * Cross-entry note: provider packages (e.g. `…/auth/firebase/client`) import\n * this context through the package's public `…/client` specifier so they share\n * the *same* context instance as the consumer's primitives at runtime.\n */\nexport const CmsAuthContext = createContext<CmsAuthState | undefined>(undefined);\n\nexport function useCmsAuth(): CmsAuthState {\n const ctx = useContext(CmsAuthContext);\n if (!ctx) {\n throw new Error(\n \"useCmsAuth must be used within a CmsAuthProvider (or a built-in auth provider such as FirebaseAuthProvider).\",\n );\n }\n return ctx;\n}\n\n/**\n * Controlled provider for consumers wiring their own auth. Pass the resolved\n * {@link CmsAuthState}; the built-in providers wrap this for you.\n */\nexport function CmsAuthProvider({\n value,\n children,\n}: {\n value: CmsAuthState;\n children: ReactNode;\n}) {\n return (\n <CmsAuthContext.Provider value={value}>{children}</CmsAuthContext.Provider>\n );\n}\n","\"use client\";\n\nimport React, { useRef, useEffect, useState, useCallback } from \"react\";\nimport { usePageContext } from \"./PageProvider\";\nimport { useCmsAuth } from \"./auth\";\n\n/**\n * Inline-editable text primitive. Headless: it wires `contentEditable`, reads\n * and persists the field, and exposes edit state via `data-*` attributes —\n * styling and rich-text rendering are entirely yours.\n *\n * - Style it with `className` and the `data-cms-editing` / `data-cms-focused`\n * attributes (no built-in look, no Tailwind, no design tokens).\n * - Supply `renderValue` to turn the stored raw string into rich nodes (e.g. a\n * markdown parser). Defaults to plain text.\n */\ninterface ContentEditSpanProps {\n collection: string;\n sectionKey: string;\n fieldKey: string;\n className?: string;\n children?: React.ReactNode;\n /** Element/tag to render. Defaults to `span`. */\n as?: React.ElementType;\n /** Render the stored raw string into nodes. Defaults to plain text. */\n renderValue?: (raw: string) => React.ReactNode;\n}\n\nconst defaultRenderValue = (raw: string): React.ReactNode => raw;\n\nfunction getNestedValue(obj: unknown, path: string): unknown {\n const keys = path.split(\".\");\n let current: unknown = obj;\n\n for (const key of keys) {\n if (current == null || (current as Record<string, unknown>)[key] === undefined)\n return undefined;\n current = (current as Record<string, unknown>)[key];\n }\n\n return current;\n}\n\nexport default function ContentEditSpan({\n collection,\n sectionKey,\n fieldKey,\n className,\n children,\n as = \"span\",\n renderValue = defaultRenderValue,\n}: ContentEditSpanProps) {\n const { sections, editField } = usePageContext();\n const { isEditing } = useCmsAuth();\n\n const section = sections[collection]?.[sectionKey];\n const raw =\n (getNestedValue(section, fieldKey) as string) ??\n (typeof children === \"string\" ? children : \"\");\n\n const Component = as;\n\n if (!isEditing) {\n return <Component className={className}>{renderValue(raw)}</Component>;\n }\n\n return (\n <EditableContentSpan\n collection={collection}\n sectionKey={sectionKey}\n fieldKey={fieldKey}\n className={className}\n raw={raw}\n editField={editField}\n as={as}\n renderValue={renderValue}\n />\n );\n}\n\nfunction EditableContentSpan({\n collection,\n sectionKey,\n fieldKey,\n className,\n raw,\n editField,\n as: Component = \"span\",\n renderValue,\n}: {\n collection: string;\n sectionKey: string;\n fieldKey: string;\n className?: string;\n raw: string;\n editField: (\n collection: string,\n sectionKey: string,\n fieldKey: string,\n value: string,\n ) => void;\n as?: React.ElementType;\n renderValue: (raw: string) => React.ReactNode;\n}) {\n const { isEditing } = useCmsAuth();\n const [isFocused, setIsFocused] = useState(false);\n const [editValue, setEditValue] = useState(raw);\n const contentRef = useRef<HTMLElement>(null);\n const rawRef = useRef(raw);\n\n useEffect(() => {\n if (!isFocused && rawRef.current !== raw) {\n rawRef.current = raw;\n }\n }, [raw, isFocused]);\n\n useEffect(() => {\n if (!isFocused && rawRef.current !== editValue) {\n setEditValue(rawRef.current);\n }\n }, [isFocused, editValue]);\n\n const handleInput = useCallback(() => {\n if (contentRef.current) {\n setEditValue(contentRef.current.textContent || \"\");\n }\n }, []);\n\n const handleBlur = useCallback(() => {\n setIsFocused(false);\n if (editValue !== raw) {\n editField(collection, sectionKey, fieldKey, editValue);\n }\n }, [editValue, raw, collection, sectionKey, fieldKey, editField]);\n\n const handleFocus = useCallback(() => {\n setIsFocused(true);\n if (contentRef.current) {\n contentRef.current.textContent = editValue;\n setTimeout(() => {\n if (contentRef.current) {\n const range = document.createRange();\n const sel = window.getSelection();\n range.selectNodeContents(contentRef.current);\n range.collapse(false);\n sel?.removeAllRanges();\n sel?.addRange(range);\n }\n }, 0);\n }\n }, [editValue]);\n\n return (\n <Component\n key={isFocused ? \"editing\" : \"static\"}\n ref={contentRef}\n className={className}\n data-cms-editable=\"\"\n data-cms-editing={isEditing ? \"\" : undefined}\n data-cms-focused={isFocused ? \"\" : undefined}\n contentEditable={isEditing}\n suppressContentEditableWarning\n onInput={handleInput}\n onBlur={handleBlur}\n onFocus={handleFocus}\n >\n {!isFocused && renderValue(editValue)}\n </Component>\n );\n}\n","\"use client\";\n\nimport React, { useRef, useState } from \"react\";\nimport { useCmsAuth } from \"./auth\";\nimport { usePageContext } from \"./PageProvider\";\n\n/** State + actions handed to the {@link EditableImage} render-prop. */\nexport interface EditableImageRenderState {\n /** URL to display (pending upload preview, external URL, or the saved src). */\n src: string;\n isEditing: boolean;\n saving: boolean;\n /** True after the current `src` failed to load. */\n hasError: boolean;\n /** Open the native file picker (a hidden input is managed for you). */\n openFilePicker: () => void;\n /**\n * Queue an external image URL. Returns `false` if it isn't a valid http(s)\n * URL (nothing is changed in that case).\n */\n setExternalUrl: (url: string) => boolean;\n /** Convenience props for an `<img>`: `{ src, onError }`. */\n imgProps: { src: string; onError: () => void };\n}\n\ninterface EditableImageProps {\n sectionKey: string;\n fieldKey: string;\n src: string;\n collection: string;\n docId: string;\n className?: string;\n /**\n * Render the image and any editing chrome (overlay, buttons, URL modal). The\n * package ships no visual look — bring your own. When omitted, a bare\n * unstyled `<img>` is rendered.\n */\n children?: (state: EditableImageRenderState) => React.ReactNode;\n}\n\nexport default function EditableImage({\n sectionKey,\n fieldKey,\n src,\n collection,\n docId,\n className,\n children,\n}: EditableImageProps) {\n const { isEditing } = useCmsAuth();\n const { editField, setPendingImage, pendingImages, saving } =\n usePageContext();\n\n const [preview, setPreview] = useState(src);\n const [hasError, setHasError] = useState(false);\n const inputRef = useRef<HTMLInputElement>(null);\n\n const pendingImage = pendingImages.find(\n (img) => img.sectionKey === sectionKey && img.fieldKey === fieldKey,\n );\n const imgSrc = pendingImage?.localUrl || preview;\n\n const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n if (saving) return;\n const file = e.target.files?.[0];\n if (!file) return;\n\n const localUrl = URL.createObjectURL(file);\n setPreview(localUrl);\n setHasError(false);\n\n editField(collection, sectionKey, fieldKey, localUrl);\n setPendingImage({\n file,\n localUrl,\n sectionKey,\n fieldKey,\n collection,\n docId,\n isExternal: false,\n });\n };\n\n const openFilePicker = () => {\n if (saving) return;\n inputRef.current?.click();\n };\n\n const setExternalUrl = (value: string): boolean => {\n let valid = false;\n try {\n const url = new URL(value);\n valid = url.protocol === \"http:\" || url.protocol === \"https:\";\n } catch {\n valid = false;\n }\n if (!valid) return false;\n\n setPreview(value);\n setHasError(false);\n editField(collection, sectionKey, fieldKey, value);\n setPendingImage({\n file: null,\n localUrl: value,\n sectionKey,\n fieldKey,\n collection,\n docId,\n isExternal: true,\n });\n return true;\n };\n\n const state: EditableImageRenderState = {\n src: imgSrc,\n isEditing,\n saving,\n hasError,\n openFilePicker,\n setExternalUrl,\n imgProps: { src: imgSrc, onError: () => setHasError(true) },\n };\n\n return (\n <div className={className}>\n {/* Internal hidden file input — driven via openFilePicker(). */}\n <input\n ref={inputRef}\n type=\"file\"\n accept=\"image/*\"\n disabled={saving}\n onChange={handleFileChange}\n style={{ display: \"none\" }}\n />\n {children ? (\n children(state)\n ) : (\n // eslint-disable-next-line @next/next/no-img-element\n <img {...state.imgProps} alt=\"\" />\n )}\n </div>\n );\n}\n","\"use client\";\n\nimport { useCallback, useRef, useState } from \"react\";\n\nexport interface UseMarkdownEditorOptions {\n initialValue: string;\n onSave: (content: string) => void | Promise<void>;\n}\n\nexport interface MarkdownEditorApi {\n /** Current editor text. */\n value: string;\n setValue: (next: string) => void;\n /** Attach to your `<textarea>` so `insert` can target the selection. */\n textareaRef: React.RefObject<HTMLTextAreaElement | null>;\n /**\n * Wrap the current selection (or insert a placeholder) with `before`/`after`\n * markers — e.g. `insert(\"**\", \"**\", \"bold text\")`. Restores focus and caret.\n */\n insert: (before: string, after?: string, placeholder?: string) => void;\n /** Reset back to `initialValue` (or a provided value). */\n reset: (to?: string) => void;\n /** Persist the current value via the provided `onSave`. */\n save: () => void | Promise<void>;\n charCount: number;\n}\n\n/**\n * Headless markdown-editing logic: value state, a selection-aware `insert`\n * command, and save/reset. The package ships **no** modal, toolbar, icons, or\n * preview renderer — compose those yourself (e.g. with `react-markdown`). This\n * keeps the package free of any UI library or visual opinion.\n *\n * ```tsx\n * const md = useMarkdownEditor({ initialValue, onSave });\n * <textarea ref={md.textareaRef} value={md.value}\n * onChange={(e) => md.setValue(e.target.value)} />\n * <button onClick={() => md.insert(\"**\", \"**\", \"bold\")}>Bold</button>\n * ```\n */\nexport function useMarkdownEditor({\n initialValue,\n onSave,\n}: UseMarkdownEditorOptions): MarkdownEditorApi {\n const [value, setValue] = useState(initialValue);\n const textareaRef = useRef<HTMLTextAreaElement>(null);\n\n const insert = useCallback(\n (before: string, after = \"\", placeholder = \"text\") => {\n const textarea = textareaRef.current;\n const start = textarea?.selectionStart ?? value.length;\n const end = textarea?.selectionEnd ?? value.length;\n const selected = value.substring(start, end) || placeholder;\n const next =\n value.substring(0, start) +\n before +\n selected +\n after +\n value.substring(end);\n\n setValue(next);\n\n setTimeout(() => {\n if (!textarea) return;\n textarea.focus();\n const caret = start + before.length + selected.length;\n textarea.setSelectionRange(caret, caret);\n }, 0);\n },\n [value],\n );\n\n const reset = useCallback(\n (to: string = initialValue) => setValue(to),\n [initialValue],\n );\n\n const save = useCallback(() => onSave(value), [onSave, value]);\n\n return {\n value,\n setValue,\n textareaRef,\n insert,\n reset,\n save,\n charCount: value.length,\n };\n}\n"],"mappings":"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","names":["_a","createContext","useContext","jsx","useState","useCallback","jsx","useState","useCallback","useRef","useState","jsx","useState","useRef","useCallback","useRef","useState"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dalgoridim/headless-cms",
3
- "version": "0.5.1",
3
+ "version": "0.7.0",
4
4
  "description": "Database-agnostic, inline-edit headless CMS engine for React / Next.js apps",
5
5
  "license": "UNLICENSED",
6
6
  "author": "dalgoridim",