agent-cms 0.1.0 → 0.3.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.
@@ -617,7 +617,7 @@ function isContentRow(row) {
617
617
  return typeof row === "object" && row !== null && "id" in row && "_status" in row;
618
618
  }
619
619
  /** Runtime check that a value is a plain object record */
620
- function isRecord(value) {
620
+ function isRecord$1(value) {
621
621
  return typeof value === "object" && value !== null && !Array.isArray(value);
622
622
  }
623
623
  /** Safely parse JSON to a Record, returning empty object on failure */
@@ -625,7 +625,7 @@ function parseJsonRecord(json) {
625
625
  if (!json) return {};
626
626
  try {
627
627
  const parsed = JSON.parse(json);
628
- if (isRecord(parsed)) return parsed;
628
+ if (isRecord$1(parsed)) return parsed;
629
629
  } catch {}
630
630
  return {};
631
631
  }
@@ -1287,6 +1287,185 @@ function markdownToDast(markdown) {
1287
1287
  });
1288
1288
  }
1289
1289
  //#endregion
1290
+ //#region src/dast/expand-shorthand.ts
1291
+ /**
1292
+ * Expand structured_text shorthand formats into the canonical
1293
+ * { value: DastDocument, blocks: Record<string, unknown> } shape.
1294
+ *
1295
+ * Accepted input formats:
1296
+ * 1. String → markdown, converted via markdownToDast
1297
+ * 2. Array → typed nodes, converted to DAST children
1298
+ * 3. Object with "markdown" key → markdown + optional blocks
1299
+ * 4. Object with "nodes" key → typed nodes + optional blocks
1300
+ * 5. Object with "value" key containing { schema: "dast" } → pass through (canonical)
1301
+ * 6. Any other object → pass through unchanged
1302
+ */
1303
+ /**
1304
+ * Parse a text string with inline markdown into DAST inline (span) nodes.
1305
+ * Returns the children of the first paragraph, or a single span fallback.
1306
+ */
1307
+ function parseInlineSpans(text) {
1308
+ const first = markdownToDast(text).document.children.at(0);
1309
+ if (first != null && "children" in first) return first.children;
1310
+ return [{
1311
+ type: "span",
1312
+ value: text
1313
+ }];
1314
+ }
1315
+ /**
1316
+ * Convert an array of typed node objects to DAST block-level children.
1317
+ */
1318
+ function typedNodesToDastChildren(nodes) {
1319
+ const children = [];
1320
+ for (const node of nodes) {
1321
+ if (node == null || typeof node !== "object") continue;
1322
+ const n = node;
1323
+ switch (n.type) {
1324
+ case "paragraph":
1325
+ children.push({
1326
+ type: "paragraph",
1327
+ children: parseInlineSpans(String(n.text ?? ""))
1328
+ });
1329
+ break;
1330
+ case "heading":
1331
+ children.push({
1332
+ type: "heading",
1333
+ level: n.level,
1334
+ children: parseInlineSpans(String(n.text ?? ""))
1335
+ });
1336
+ break;
1337
+ case "code":
1338
+ children.push({
1339
+ type: "code",
1340
+ code: n.code,
1341
+ ...n.language ? { language: n.language } : {}
1342
+ });
1343
+ break;
1344
+ case "blockquote":
1345
+ children.push({
1346
+ type: "blockquote",
1347
+ children: [{
1348
+ type: "paragraph",
1349
+ children: parseInlineSpans(String(n.text ?? ""))
1350
+ }]
1351
+ });
1352
+ break;
1353
+ case "list":
1354
+ children.push({
1355
+ type: "list",
1356
+ style: n.style ?? "bulleted",
1357
+ children: (Array.isArray(n.items) ? n.items : []).map((item) => ({
1358
+ type: "listItem",
1359
+ children: [{
1360
+ type: "paragraph",
1361
+ children: parseInlineSpans(String(item ?? ""))
1362
+ }]
1363
+ }))
1364
+ });
1365
+ break;
1366
+ case "thematicBreak":
1367
+ children.push({ type: "thematicBreak" });
1368
+ break;
1369
+ case "block":
1370
+ children.push({
1371
+ type: "block",
1372
+ item: n.ref
1373
+ });
1374
+ break;
1375
+ default: break;
1376
+ }
1377
+ }
1378
+ return children;
1379
+ }
1380
+ /**
1381
+ * Build a block map from an array of block entries.
1382
+ *
1383
+ * Accepts two entry formats:
1384
+ * - { id, type, data: { ...fields } } — shorthand (type becomes _type)
1385
+ * - { id, _type, ...fields } — canonical (matches get_record output)
1386
+ */
1387
+ function buildBlockMapFromArray(blocks) {
1388
+ const map = {};
1389
+ for (const b of blocks) {
1390
+ if (b == null || typeof b !== "object") continue;
1391
+ const entry = b;
1392
+ const id = entry.id;
1393
+ if (typeof id !== "string") continue;
1394
+ if (typeof entry._type === "string") {
1395
+ const { id: _, ...rest } = entry;
1396
+ map[id] = rest;
1397
+ continue;
1398
+ }
1399
+ const type = entry.type;
1400
+ if (typeof type === "string") {
1401
+ const data = entry.data;
1402
+ map[id] = {
1403
+ _type: type,
1404
+ ...data != null && typeof data === "object" && !Array.isArray(data) ? data : {}
1405
+ };
1406
+ }
1407
+ }
1408
+ return map;
1409
+ }
1410
+ /**
1411
+ * Normalize a blocks value to a canonical map.
1412
+ *
1413
+ * Accepts:
1414
+ * - Array of block entries (shorthand or canonical format)
1415
+ * - Object/map keyed by block ID (canonical DAST format, passed through)
1416
+ */
1417
+ function normalizeBlocks(blocks) {
1418
+ if (Array.isArray(blocks)) return buildBlockMapFromArray(blocks);
1419
+ if (blocks != null && typeof blocks === "object" && !Array.isArray(blocks)) return blocks;
1420
+ return {};
1421
+ }
1422
+ function isRecord(v) {
1423
+ return v != null && typeof v === "object" && !Array.isArray(v);
1424
+ }
1425
+ /**
1426
+ * Expand a structured_text field value from shorthand formats to canonical form.
1427
+ *
1428
+ * Returns the value unchanged if it is already in canonical form or unrecognized.
1429
+ * For shorthand formats (string, array, or wrapper objects), returns the expanded
1430
+ * { value: DastDocument, blocks: Record<string, unknown> } shape.
1431
+ */
1432
+ function expandStructuredTextShorthand(rawValue) {
1433
+ if (typeof rawValue === "string") return {
1434
+ value: markdownToDast(rawValue),
1435
+ blocks: {}
1436
+ };
1437
+ if (Array.isArray(rawValue)) return {
1438
+ value: {
1439
+ schema: "dast",
1440
+ document: {
1441
+ type: "root",
1442
+ children: typedNodesToDastChildren(rawValue)
1443
+ }
1444
+ },
1445
+ blocks: {}
1446
+ };
1447
+ if (!isRecord(rawValue)) return rawValue;
1448
+ if ("markdown" in rawValue && typeof rawValue.markdown === "string") return {
1449
+ value: markdownToDast(rawValue.markdown),
1450
+ blocks: normalizeBlocks(rawValue.blocks)
1451
+ };
1452
+ if ("nodes" in rawValue && Array.isArray(rawValue.nodes)) {
1453
+ const children = typedNodesToDastChildren(rawValue.nodes);
1454
+ const blocks = normalizeBlocks(rawValue.blocks);
1455
+ return {
1456
+ value: {
1457
+ schema: "dast",
1458
+ document: {
1459
+ type: "root",
1460
+ children
1461
+ }
1462
+ },
1463
+ blocks
1464
+ };
1465
+ }
1466
+ return rawValue;
1467
+ }
1468
+ //#endregion
1290
1469
  //#region src/graphql/sql-metrics.ts
1291
1470
  const sqlMetricsStorage = new AsyncLocalStorage();
1292
1471
  function withSqlMetrics(run) {
@@ -1573,11 +1752,11 @@ function compileStructuredText(ctx, container, params) {
1573
1752
  for (const field of blockModel.fields) {
1574
1753
  const value = blockData[field.api_key];
1575
1754
  if (value === void 0) continue;
1755
+ if (value === null) {
1756
+ row[field.api_key] = null;
1757
+ continue;
1758
+ }
1576
1759
  if (field.field_type === "structured_text") {
1577
- if (value === null) {
1578
- row[field.api_key] = null;
1579
- continue;
1580
- }
1581
1760
  const nestedInput = yield* decodeStructuredTextInput(field.api_key, value);
1582
1761
  const nestedCompiled = yield* compileStructuredText(ctx, {
1583
1762
  parentContainerModelApiKey: blockModel.apiKey,
@@ -1947,6 +2126,6 @@ function materializeRecordStructuredTextFields(params) {
1947
2126
  });
1948
2127
  }
1949
2128
  //#endregion
1950
- export { isSearchable as A, encodeJson as B, findUniqueConstraintViolations as C, getLinksTargets as D, getLinkTargets as E, parseMediaGalleryReferences as F, ReferenceConflictError as G, getFieldTypeDef as H, decodeJsonIfString as I, ValidationError as J, SchemaEngineError as K, decodeJsonRecordStringOr as L, supportsUniqueValidation as M, mergeAssetWithMediaReference as N, getSlugSource as O, parseMediaFieldReference as P, decodeJsonString as R, computeIsValid as S, getBlocksOnly as T, DuplicateError as U, FIELD_TYPE_REGISTRY as V, NotFoundError as W, isCmsError as X, errorToResponse as Y, extractLinkIds as _, materializeStructuredTextValue as a, isContentRow as b, FIELD_TYPES as c, getSqlMetrics as d, recordSqlMetrics as f, extractInlineBlockIds as g, extractBlockIds as h, materializeRecordStructuredTextFields as i, isUnique as j, isRequired as k, isFieldType as l, markdownToDast as m, deleteBlocksForField as n, materializeStructuredTextValues as o, withSqlMetrics as p, UnauthorizedError as q, getStructuredTextStorageKey as r, writeStructuredText as s, deleteBlockSubtrees as t, runBatchedQueries as u, pruneBlockNodes as v, getBlockWhitelist as w, parseFieldValidators as x, StructuredTextWriteInput as y, decodeJsonStringOr as z };
2129
+ export { isSearchable as A, encodeJson as B, findUniqueConstraintViolations as C, getLinksTargets as D, getLinkTargets as E, parseMediaGalleryReferences as F, ReferenceConflictError as G, getFieldTypeDef as H, decodeJsonIfString as I, ValidationError as J, SchemaEngineError as K, decodeJsonRecordStringOr as L, supportsUniqueValidation as M, mergeAssetWithMediaReference as N, getSlugSource as O, parseMediaFieldReference as P, decodeJsonString as R, computeIsValid as S, getBlocksOnly as T, DuplicateError as U, FIELD_TYPE_REGISTRY as V, NotFoundError as W, isCmsError as X, errorToResponse as Y, extractLinkIds as _, materializeStructuredTextValue as a, isContentRow as b, FIELD_TYPES as c, getSqlMetrics as d, recordSqlMetrics as f, extractInlineBlockIds as g, extractBlockIds as h, materializeRecordStructuredTextFields as i, isUnique as j, isRequired as k, isFieldType as l, expandStructuredTextShorthand as m, deleteBlocksForField as n, materializeStructuredTextValues as o, withSqlMetrics as p, UnauthorizedError as q, getStructuredTextStorageKey as r, writeStructuredText as s, deleteBlockSubtrees as t, runBatchedQueries as u, pruneBlockNodes as v, getBlockWhitelist as w, parseFieldValidators as x, StructuredTextWriteInput as y, decodeJsonStringOr as z };
1951
2130
 
1952
- //# sourceMappingURL=structured-text-service-B4xSlUg_.mjs.map
2131
+ //# sourceMappingURL=structured-text-service-BJkqWRkq.mjs.map
@@ -32,6 +32,7 @@ CREATE TABLE IF NOT EXISTS "models" (
32
32
  "has_draft" integer DEFAULT true NOT NULL,
33
33
  "all_locales_required" integer DEFAULT 0 NOT NULL,
34
34
  "ordering" text,
35
+ "canonical_path_template" text,
35
36
  "created_at" text NOT NULL,
36
37
  "updated_at" text NOT NULL
37
38
  );
@@ -116,3 +117,13 @@ CREATE TABLE IF NOT EXISTS "editor_tokens" (
116
117
 
117
118
  CREATE UNIQUE INDEX IF NOT EXISTS "idx_editor_tokens_secret_hash"
118
119
  ON "editor_tokens" ("secret_hash");
120
+
121
+ CREATE TABLE IF NOT EXISTS "preview_tokens" (
122
+ "id" text PRIMARY KEY,
123
+ "token_hash" text NOT NULL UNIQUE,
124
+ "expires_at" text NOT NULL,
125
+ "created_at" text NOT NULL DEFAULT (datetime('now'))
126
+ );
127
+
128
+ CREATE INDEX IF NOT EXISTS "idx_preview_tokens_hash"
129
+ ON "preview_tokens" ("token_hash");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-cms",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Agent-first headless CMS for Cloudflare Workers. Schema, content, and assets via MCP. GraphQL delivery.",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@10.32.1",
@@ -22,7 +22,6 @@
22
22
  ],
23
23
  "scripts": {
24
24
  "build": "tsdown",
25
- "dev": "wrangler dev",
26
25
  "test": "vitest",
27
26
  "test:run": "vitest run",
28
27
  "lint": "oxlint --type-check src/",
@@ -30,7 +29,6 @@
30
29
  "bench:blog": "node scripts/bench-blog.mjs",
31
30
  "dato:import": "node scripts/dato-import/cli.mjs",
32
31
  "prepublishOnly": "pnpm run build",
33
- "db:migrate": "wrangler d1 migrations apply agent-cms-db --local",
34
32
  "prepare": "effect-language-service patch"
35
33
  },
36
34
  "keywords": [
@@ -59,6 +57,7 @@
59
57
  "dependencies": {
60
58
  "@aws-sdk/client-s3": "^3.1014.0",
61
59
  "@aws-sdk/s3-request-presigner": "^3.1014.0",
60
+ "@cloudflare/codemode": "^0.3.2",
62
61
  "@effect/ai": "^0.35.0",
63
62
  "@effect/cli": "^0.75.0",
64
63
  "@effect/experimental": "^0.60.0",
@@ -69,13 +68,14 @@
69
68
  "@effect/rpc": "^0.75.0",
70
69
  "@effect/sql": "^0.51.0",
71
70
  "@effect/sql-d1": "^0.49.0",
71
+ "agents": "^0.8.6",
72
72
  "effect": "^3.21.0",
73
73
  "graphql": "^16.13.1",
74
74
  "graphql-yoga": "^5.18.1",
75
+ "nanoid": "^5.1.7",
75
76
  "remark-gfm": "^4.0.1",
76
77
  "remark-parse": "^11.0.0",
77
78
  "remark-stringify": "^11.0.0",
78
- "nanoid": "^5.1.7",
79
79
  "slugify": "^1.6.8",
80
80
  "unified": "^11.0.5"
81
81
  },
@@ -83,7 +83,7 @@
83
83
  "@cloudflare/workers-types": "^4.20260317.1",
84
84
  "@effect/language-service": "^0.81.0",
85
85
  "@effect/sql-sqlite-node": "^0.52.0",
86
- "@modelcontextprotocol/sdk": "^1.27.1",
86
+ "@modelcontextprotocol/sdk": "^1.28.0",
87
87
  "@types/better-sqlite3": "^7.6.13",
88
88
  "@types/mdast": "^4.0.4",
89
89
  "better-sqlite3": "^12.8.0",