@agent-native/core 0.154.3 → 0.154.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/content/.agents/skills/content/SKILL.md +2 -2
  3. package/corpus/templates/content/.agents/skills/document-editing/references/databases.md +20 -4
  4. package/corpus/templates/content/actions/_blocks-field-identity.ts +464 -0
  5. package/corpus/templates/content/actions/_database-row-mutation.ts +1490 -0
  6. package/corpus/templates/content/actions/_database-utils.ts +55 -3
  7. package/corpus/templates/content/actions/_property-utils.ts +141 -31
  8. package/corpus/templates/content/actions/add-database-item.ts +62 -240
  9. package/corpus/templates/content/actions/configure-document-property.ts +170 -1
  10. package/corpus/templates/content/actions/delete-document-property.ts +56 -17
  11. package/corpus/templates/content/actions/delete-document.ts +3 -0
  12. package/corpus/templates/content/actions/edit-document.ts +18 -1
  13. package/corpus/templates/content/actions/export-document.ts +41 -0
  14. package/corpus/templates/content/actions/remove-database-items.ts +10 -0
  15. package/corpus/templates/content/actions/set-document-property.ts +140 -3
  16. package/corpus/templates/content/actions/update-database-item.ts +62 -0
  17. package/corpus/templates/content/actions/update-document.ts +24 -0
  18. package/corpus/templates/content/actions/upsert-database-item-by-key.ts +43 -796
  19. package/corpus/templates/content/app/components/editor/DocumentBlockFields.tsx +99 -11
  20. package/corpus/templates/content/app/components/editor/blockFieldSaveController.ts +6 -0
  21. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +10 -8
  22. package/corpus/templates/content/app/hooks/use-content-database.ts +33 -32
  23. package/corpus/templates/content/app/hooks/use-document-properties.ts +4 -1
  24. package/corpus/templates/content/app/i18n-data.ts +20 -0
  25. package/corpus/templates/content/changelog/2026-08-10-database-blocks-fields-now-keep-logical-block-identity-throu.md +6 -0
  26. package/corpus/templates/content/changelog/2026-08-10-database-row-actions-now-validate-exact-schemas-and-safe-ret.md +6 -0
  27. package/corpus/templates/content/changelog/2026-08-13-blocks-field-conflicts-now-keep-the-newer-saved-version-visi.md +6 -0
  28. package/corpus/templates/content/docs/product/capabilities/content.object.block.md +5 -4
  29. package/corpus/templates/content/docs/product/capabilities/content.object.blocks-field.md +9 -4
  30. package/corpus/templates/content/docs/product/encyclopedia.md +4 -4
  31. package/corpus/templates/content/parity/matrix.md +1 -1
  32. package/corpus/templates/content/parity/matrix.ts +2 -0
  33. package/corpus/templates/content/server/db/schema.ts +100 -3
  34. package/corpus/templates/content/server/plugins/db.ts +85 -0
  35. package/corpus/templates/content/shared/api.ts +78 -2
  36. package/corpus/templates/content/shared/blocks-field-identity.ts +554 -0
  37. package/corpus/templates/content/shared/document-export.ts +33 -1
  38. package/dist/action.d.ts +22 -0
  39. package/dist/action.js +27 -0
  40. package/dist/collab/awareness.d.ts +2 -2
  41. package/dist/collab/routes.d.ts +1 -1
  42. package/dist/extensions/url-safety.d.ts +7 -2
  43. package/dist/extensions/url-safety.js +27 -12
  44. package/dist/index.browser.d.ts +1 -1
  45. package/dist/index.browser.js +1 -1
  46. package/dist/index.d.ts +1 -1
  47. package/dist/index.js +1 -1
  48. package/dist/mcp-client/oauth-client.d.ts +16 -1
  49. package/dist/mcp-client/oauth-client.js +177 -83
  50. package/dist/mcp-client/remote-store.js +46 -20
  51. package/dist/notifications/routes.d.ts +1 -1
  52. package/dist/oauth-tokens/index.d.ts +2 -1
  53. package/dist/oauth-tokens/index.js +2 -1
  54. package/dist/oauth-tokens/lifecycle.d.ts +87 -0
  55. package/dist/oauth-tokens/lifecycle.js +434 -0
  56. package/dist/oauth-tokens/migrations.js +10 -0
  57. package/dist/oauth-tokens/store.d.ts +22 -0
  58. package/dist/oauth-tokens/store.js +141 -18
  59. package/dist/observability/routes.d.ts +2 -2
  60. package/dist/progress/routes.d.ts +1 -1
  61. package/dist/secrets/routes.d.ts +6 -6
  62. package/dist/server/action-routes.js +10 -2
  63. package/dist/server/realtime-token.d.ts +1 -1
  64. package/package.json +2 -1
@@ -234,6 +234,7 @@ export const contentDatabases = table(
234
234
  ownerBlockId: text("owner_block_id"),
235
235
  title: text("title").notNull().default("Untitled database"),
236
236
  systemRole: text("system_role"),
237
+ naturalKeyPropertyId: text("natural_key_property_id"),
237
238
  viewConfigJson: text("view_config_json").notNull().default("{}"),
238
239
  filesSystemPropertiesSeeded: integer("files_system_properties_seeded")
239
240
  .notNull()
@@ -287,9 +288,9 @@ export const contentDatabaseItems = table(
287
288
  ],
288
289
  );
289
290
 
290
- // Opt-in stable-key claims are the durable concurrency fence for the generic
291
- // database-row upsert action. They intentionally do not constrain ordinary
292
- // property editing or change add-database-item behavior.
291
+ // Opt-in stable-key claims are the durable concurrency fence for configured
292
+ // natural-key upserts. Ordinary property editing stays independent until a
293
+ // text property is explicitly selected as the database's natural key.
293
294
  export const contentDatabaseItemKeyClaims = table(
294
295
  "content_database_item_key_claims",
295
296
  {
@@ -494,6 +495,41 @@ export const contentDatabaseMigrationReceipts = table(
494
495
  ],
495
496
  );
496
497
 
498
+ export const contentDatabaseRowMutationReceipts = table(
499
+ "content_database_row_mutation_receipts",
500
+ {
501
+ id: text("id").primaryKey(),
502
+ ownerEmail: text("owner_email").notNull().default("local@localhost"),
503
+ orgId: text("org_id"),
504
+ spaceId: text("space_id").notNull(),
505
+ databaseId: text("database_id").notNull(),
506
+ databaseDocumentId: text("database_document_id").notNull(),
507
+ operation: text("operation").notNull(),
508
+ itemId: text("item_id").notNull(),
509
+ documentId: text("document_id").notNull(),
510
+ idempotencyKey: text("idempotency_key").notNull(),
511
+ payloadDigest: text("payload_digest").notNull(),
512
+ schemaRevision: text("schema_revision").notNull(),
513
+ preRowRevision: text("pre_row_revision"),
514
+ postRowRevision: text("post_row_revision").notNull(),
515
+ resultJson: text("result_json").notNull().default("{}"),
516
+ createdAt: text("created_at").notNull().default(now()),
517
+ updatedAt: text("updated_at").notNull().default(now()),
518
+ },
519
+ (receipt) => [
520
+ uniqueIndex(
521
+ "content_database_row_mutation_receipts_database_key_unique",
522
+ ).on(receipt.databaseId, receipt.idempotencyKey),
523
+ index("content_database_row_mutation_receipts_owner_database_idx").on(
524
+ receipt.ownerEmail,
525
+ receipt.databaseId,
526
+ ),
527
+ index("content_database_row_mutation_receipts_document_idx").on(
528
+ receipt.documentId,
529
+ ),
530
+ ],
531
+ );
532
+
497
533
  export const documentPropertyValues = table("document_property_values", {
498
534
  id: text("id").primaryKey(),
499
535
  ownerEmail: text("owner_email").notNull().default("local@localhost"),
@@ -523,4 +559,65 @@ export const documentBlockFieldContents = table(
523
559
  },
524
560
  );
525
561
 
562
+ // Stable identity and revision boundary for one database Blocks property. The
563
+ // Markdown body remains in documents.content or document_block_field_contents;
564
+ // this row binds the ordered identity sidecar to those exact bytes.
565
+ export const documentBlockFields = table(
566
+ "document_block_fields",
567
+ {
568
+ id: text("id").primaryKey(),
569
+ ownerEmail: text("owner_email").notNull().default("local@localhost"),
570
+ documentId: text("document_id").notNull(),
571
+ propertyId: text("property_id").notNull(),
572
+ revision: integer("revision").notNull().default(0),
573
+ contentHash: text("content_hash").notNull(),
574
+ createdAt: text("created_at").notNull().default(now()),
575
+ updatedAt: text("updated_at").notNull().default(now()),
576
+ },
577
+ (field) => [
578
+ uniqueIndex("document_block_fields_document_property_unique").on(
579
+ field.documentId,
580
+ field.propertyId,
581
+ ),
582
+ index("document_block_fields_owner_document_idx").on(
583
+ field.ownerEmail,
584
+ field.documentId,
585
+ ),
586
+ ],
587
+ );
588
+
589
+ // Ordered block identity index plus bounded tombstones. This is deliberately
590
+ // not an actor-aware history log: it records only current nodes and the minimum
591
+ // deleted fragment needed for editor undo to recover the same logical ID.
592
+ export const documentBlocks = table(
593
+ "document_blocks",
594
+ {
595
+ id: text("id").primaryKey(),
596
+ ownerEmail: text("owner_email").notNull().default("local@localhost"),
597
+ fieldId: text("field_id").notNull(),
598
+ parentId: text("parent_id"),
599
+ kind: text("kind").notNull(),
600
+ position: integer("position").notNull(),
601
+ sortIndex: integer("sort_index").notNull(),
602
+ addressable: integer("addressable", { mode: "boolean" })
603
+ .notNull()
604
+ .default(true),
605
+ contentHash: text("content_hash").notNull(),
606
+ markdown: text("markdown").notNull().default(""),
607
+ state: text("state").notNull().default("live"),
608
+ deletedAtRevision: integer("deleted_at_revision"),
609
+ recoveredAtRevision: integer("recovered_at_revision"),
610
+ createdAt: text("created_at").notNull().default(now()),
611
+ updatedAt: text("updated_at").notNull().default(now()),
612
+ },
613
+ (block) => [
614
+ index("document_blocks_field_state_sort_idx").on(
615
+ block.fieldId,
616
+ block.state,
617
+ block.sortIndex,
618
+ ),
619
+ index("document_blocks_parent_idx").on(block.parentId),
620
+ ],
621
+ );
622
+
526
623
  export const documentShares = createSharesTable("document_shares");
@@ -956,6 +956,91 @@ export const runContentMigrations = runMigrations(
956
956
  CREATE INDEX IF NOT EXISTS content_database_migration_receipts_owner_database_idx
957
957
  ON content_database_migration_receipts (owner_email, database_id)`,
958
958
  },
959
+ {
960
+ version: 81,
961
+ name: "content-block-field-identities",
962
+ sql: `CREATE TABLE IF NOT EXISTS document_block_fields (
963
+ id TEXT PRIMARY KEY,
964
+ owner_email TEXT NOT NULL DEFAULT 'local@localhost',
965
+ document_id TEXT NOT NULL,
966
+ property_id TEXT NOT NULL,
967
+ revision INTEGER NOT NULL DEFAULT 0,
968
+ content_hash TEXT NOT NULL,
969
+ created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
970
+ updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
971
+ );
972
+ CREATE UNIQUE INDEX IF NOT EXISTS document_block_fields_document_property_unique
973
+ ON document_block_fields (document_id, property_id);
974
+ CREATE INDEX IF NOT EXISTS document_block_fields_owner_document_idx
975
+ ON document_block_fields (owner_email, document_id)`,
976
+ },
977
+ {
978
+ version: 82,
979
+ name: "content-block-identities-and-tombstones",
980
+ sql: `CREATE TABLE IF NOT EXISTS document_blocks (
981
+ id TEXT PRIMARY KEY,
982
+ owner_email TEXT NOT NULL DEFAULT 'local@localhost',
983
+ field_id TEXT NOT NULL,
984
+ parent_id TEXT,
985
+ kind TEXT NOT NULL,
986
+ position INTEGER NOT NULL,
987
+ sort_index INTEGER NOT NULL,
988
+ addressable INTEGER NOT NULL DEFAULT 1,
989
+ content_hash TEXT NOT NULL,
990
+ markdown TEXT NOT NULL DEFAULT '',
991
+ state TEXT NOT NULL DEFAULT 'live',
992
+ deleted_at_revision INTEGER,
993
+ recovered_at_revision INTEGER,
994
+ created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
995
+ updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
996
+ );
997
+ CREATE INDEX IF NOT EXISTS document_blocks_field_state_sort_idx
998
+ ON document_blocks (field_id, state, sort_index);
999
+ CREATE INDEX IF NOT EXISTS document_blocks_parent_idx
1000
+ ON document_blocks (parent_id)`,
1001
+ },
1002
+ // The portable schema maps integer({ mode: "boolean" }) to BOOLEAN on
1003
+ // Postgres, while the raw INTEGER migration above is adapted to BIGINT.
1004
+ // Convert the stored column before Drizzle sends boolean values.
1005
+ {
1006
+ version: 83,
1007
+ name: "content-block-addressable-postgres-boolean",
1008
+ sql: {
1009
+ postgres: `ALTER TABLE document_blocks ALTER COLUMN addressable DROP DEFAULT;
1010
+ ALTER TABLE document_blocks ALTER COLUMN addressable TYPE boolean USING addressable::text::boolean;
1011
+ ALTER TABLE document_blocks ALTER COLUMN addressable SET DEFAULT true`,
1012
+ },
1013
+ },
1014
+ {
1015
+ version: 84,
1016
+ name: "content-database-row-mutation-contract",
1017
+ sql: `ALTER TABLE content_databases ADD COLUMN IF NOT EXISTS natural_key_property_id TEXT;
1018
+ CREATE TABLE IF NOT EXISTS content_database_row_mutation_receipts (
1019
+ id TEXT PRIMARY KEY,
1020
+ owner_email TEXT NOT NULL DEFAULT 'local@localhost',
1021
+ org_id TEXT,
1022
+ space_id TEXT NOT NULL,
1023
+ database_id TEXT NOT NULL,
1024
+ database_document_id TEXT NOT NULL,
1025
+ operation TEXT NOT NULL,
1026
+ item_id TEXT NOT NULL,
1027
+ document_id TEXT NOT NULL,
1028
+ idempotency_key TEXT NOT NULL,
1029
+ payload_digest TEXT NOT NULL,
1030
+ schema_revision TEXT NOT NULL,
1031
+ pre_row_revision TEXT,
1032
+ post_row_revision TEXT NOT NULL,
1033
+ result_json TEXT NOT NULL DEFAULT '{}',
1034
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
1035
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
1036
+ );
1037
+ CREATE UNIQUE INDEX IF NOT EXISTS content_database_row_mutation_receipts_database_key_unique
1038
+ ON content_database_row_mutation_receipts (database_id, idempotency_key);
1039
+ CREATE INDEX IF NOT EXISTS content_database_row_mutation_receipts_owner_database_idx
1040
+ ON content_database_row_mutation_receipts (owner_email, database_id);
1041
+ CREATE INDEX IF NOT EXISTS content_database_row_mutation_receipts_document_idx
1042
+ ON content_database_row_mutation_receipts (document_id)`,
1043
+ },
959
1044
  ],
960
1045
  { table: "content_migrations" },
961
1046
  );
@@ -1,3 +1,4 @@
1
+ import type { BlocksFieldIdentity } from "./blocks-field-identity";
1
2
  import type {
2
3
  DocumentPropertyOptions,
3
4
  DocumentPropertyOption,
@@ -206,6 +207,7 @@ export interface DocumentProperty {
206
207
  definition: DocumentPropertyDefinition;
207
208
  value: DocumentPropertyValue;
208
209
  editable: boolean;
210
+ blocksField?: BlocksFieldIdentity;
209
211
  }
210
212
 
211
213
  export interface DocumentPropertiesResponse {
@@ -223,6 +225,7 @@ export interface ConfigureDocumentPropertyRequest {
223
225
  description?: string;
224
226
  visibility?: DocumentPropertyVisibility;
225
227
  options?: DocumentPropertyOptions;
228
+ naturalKey?: boolean;
226
229
  }
227
230
 
228
231
  export interface SetDocumentPropertyRequest {
@@ -230,6 +233,7 @@ export interface SetDocumentPropertyRequest {
230
233
  databaseId: string;
231
234
  propertyId: string;
232
235
  value: DocumentPropertyValue;
236
+ expectedBlocksFieldRevision?: number;
233
237
  }
234
238
 
235
239
  export interface DuplicateDocumentPropertyRequest {
@@ -255,8 +259,10 @@ export interface ReorderDocumentPropertyRequest {
255
259
  export interface ContentDatabase {
256
260
  id: string;
257
261
  documentId: string;
262
+ spaceId?: string | null;
258
263
  title: string;
259
264
  systemRole?: string | null;
265
+ naturalKeyPropertyId?: string | null;
260
266
  description?: string;
261
267
  viewConfig: ContentDatabaseViewConfig;
262
268
  createdAt: string;
@@ -466,6 +472,62 @@ export interface ContentDatabaseItem {
466
472
  // a secondary source contributes on top of it. Absent for non-federated rows.
467
473
  canonicalKey?: string | null;
468
474
  sourceOverlays?: ContentDatabaseSourceOverlay[];
475
+ rowRevision?: string;
476
+ }
477
+
478
+ export interface ContentDatabaseMutationTarget {
479
+ authorityScope:
480
+ | { kind: "personal"; id: string }
481
+ | { kind: "organization"; id: string };
482
+ spaceId: string;
483
+ databaseId: string;
484
+ databaseDocumentId: string;
485
+ }
486
+
487
+ export interface ContentDatabaseMutationContract {
488
+ target: ContentDatabaseMutationTarget;
489
+ schemaRevision: string;
490
+ naturalKeyPropertyId: string | null;
491
+ properties: Array<{
492
+ id: string;
493
+ name: string;
494
+ type: DocumentPropertyType;
495
+ writable: boolean;
496
+ sourceManaged: boolean;
497
+ acceptedShape: string | null;
498
+ options: DocumentPropertyOptions;
499
+ }>;
500
+ }
501
+
502
+ export interface ContentDatabaseRowMutationReceipt {
503
+ receiptId: string;
504
+ operation: "create" | "update" | "upsert";
505
+ outcome: "created" | "updated" | "unchanged";
506
+ target: ContentDatabaseMutationTarget;
507
+ schemaRevision: string;
508
+ row: {
509
+ itemId: string;
510
+ documentId: string;
511
+ urlPath: string;
512
+ rowRevision: string;
513
+ };
514
+ affected: { title: boolean; propertyIds: string[] };
515
+ idempotency: {
516
+ key: string;
517
+ result: "applied" | "replayed";
518
+ payloadDigest: string;
519
+ };
520
+ revisions: { before: string | null; after: string };
521
+ readback: {
522
+ verified: true;
523
+ title: string;
524
+ propertyValues: Record<string, DocumentPropertyValue>;
525
+ };
526
+ }
527
+
528
+ export interface ContentDatabaseRowMutationResult {
529
+ receipt: ContentDatabaseRowMutationReceipt;
530
+ createdItem?: ContentDatabaseItem;
469
531
  }
470
532
 
471
533
  // A secondary source's read-only contribution to a federated row, matched on the
@@ -825,6 +887,7 @@ export interface ContentDatabaseResponse {
825
887
  removedCount?: number;
826
888
  timings?: BuilderActionTiming[];
827
889
  tableQueryMode?: "server" | "client-required";
890
+ mutationContract?: ContentDatabaseMutationContract;
828
891
  /** Client-only optimistic state while real provider rows are being attached. */
829
892
  attachPreview?: {
830
893
  sourceTable: string;
@@ -889,9 +952,22 @@ export interface CreateInlineDatabaseResponse {
889
952
  }
890
953
 
891
954
  export interface AddDatabaseItemRequest {
892
- databaseId: string;
955
+ target: ContentDatabaseMutationTarget;
956
+ expectedSchemaRevision: string;
957
+ idempotencyKey: string;
893
958
  title?: string;
894
- propertyValues?: Record<string, DocumentPropertyValue>;
959
+ propertyValues?: Record<string, unknown>;
960
+ }
961
+
962
+ export interface UpdateDatabaseItemRequest extends AddDatabaseItemRequest {
963
+ itemId: string;
964
+ documentId: string;
965
+ expectedRowRevision: string;
966
+ }
967
+
968
+ export interface UpsertDatabaseItemByKeyRequest extends AddDatabaseItemRequest {
969
+ keyValue: string;
970
+ expectedRowRevision: string | null;
895
971
  }
896
972
 
897
973
  export interface SubmitContentDatabaseFormRequest {