@agent-native/core 0.154.4 → 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 (34) 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/_database-row-mutation.ts +1490 -0
  5. package/corpus/templates/content/actions/_database-utils.ts +44 -0
  6. package/corpus/templates/content/actions/_property-utils.ts +4 -1
  7. package/corpus/templates/content/actions/add-database-item.ts +62 -240
  8. package/corpus/templates/content/actions/configure-document-property.ts +165 -1
  9. package/corpus/templates/content/actions/delete-document-property.ts +18 -0
  10. package/corpus/templates/content/actions/set-document-property.ts +78 -1
  11. package/corpus/templates/content/actions/update-database-item.ts +62 -0
  12. package/corpus/templates/content/actions/upsert-database-item-by-key.ts +43 -796
  13. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +10 -8
  14. package/corpus/templates/content/app/hooks/use-content-database.ts +33 -32
  15. package/corpus/templates/content/changelog/2026-08-10-database-row-actions-now-validate-exact-schemas-and-safe-ret.md +6 -0
  16. package/corpus/templates/content/parity/matrix.md +1 -1
  17. package/corpus/templates/content/parity/matrix.ts +2 -0
  18. package/corpus/templates/content/server/db/schema.ts +39 -3
  19. package/corpus/templates/content/server/plugins/db.ts +30 -0
  20. package/corpus/templates/content/shared/api.ts +75 -2
  21. package/dist/action.d.ts +22 -0
  22. package/dist/action.js +27 -0
  23. package/dist/collab/awareness.d.ts +2 -2
  24. package/dist/collab/routes.d.ts +1 -1
  25. package/dist/index.browser.d.ts +1 -1
  26. package/dist/index.browser.js +1 -1
  27. package/dist/index.d.ts +1 -1
  28. package/dist/index.js +1 -1
  29. package/dist/notifications/routes.d.ts +1 -1
  30. package/dist/observability/routes.d.ts +6 -6
  31. package/dist/secrets/routes.d.ts +3 -3
  32. package/dist/server/action-routes.js +10 -2
  33. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  34. package/package.json +1 -1
@@ -226,7 +226,10 @@ export default defineAction({
226
226
  database.id,
227
227
  );
228
228
  const [lockedDatabase] = await tx
229
- .select({ id: schema.contentDatabases.id })
229
+ .select({
230
+ id: schema.contentDatabases.id,
231
+ naturalKeyPropertyId: schema.contentDatabases.naturalKeyPropertyId,
232
+ })
230
233
  .from(schema.contentDatabases)
231
234
  .where(
232
235
  and(
@@ -272,6 +275,41 @@ export default defineAction({
272
275
  if (isComputedPropertyType(lockedType)) {
273
276
  throw new Error("Computed properties cannot be edited.");
274
277
  }
278
+ const isNaturalKey = lockedDatabase.naturalKeyPropertyId === propertyId;
279
+ if (isNaturalKey) {
280
+ let parsed: unknown;
281
+ try {
282
+ parsed = JSON.parse(valueJson);
283
+ } catch {
284
+ parsed = null;
285
+ }
286
+ if (typeof parsed !== "string" || !parsed.trim()) {
287
+ throw new Error(
288
+ "A database natural key must remain a non-empty string.",
289
+ );
290
+ }
291
+ const [existingNaturalKeyClaim] = await tx
292
+ .select({
293
+ keyValueJson: schema.contentDatabaseItemKeyClaims.keyValueJson,
294
+ })
295
+ .from(schema.contentDatabaseItemKeyClaims)
296
+ .where(
297
+ and(
298
+ eq(schema.contentDatabaseItemKeyClaims.databaseId, database.id),
299
+ eq(schema.contentDatabaseItemKeyClaims.propertyId, propertyId),
300
+ eq(schema.contentDatabaseItemKeyClaims.documentId, documentId),
301
+ ),
302
+ )
303
+ .limit(1);
304
+ if (
305
+ existingNaturalKeyClaim &&
306
+ existingNaturalKeyClaim.keyValueJson !== valueJson
307
+ ) {
308
+ throw new Error(
309
+ "A claimed database natural key cannot be changed. Create a new row instead.",
310
+ );
311
+ }
312
+ }
275
313
  const [conflictingClaim] = await tx
276
314
  .select({ id: schema.contentDatabaseItemKeyClaims.id })
277
315
  .from(schema.contentDatabaseItemKeyClaims)
@@ -314,6 +352,45 @@ export default defineAction({
314
352
  updatedAt: now,
315
353
  });
316
354
  }
355
+ if (isNaturalKey) {
356
+ await tx
357
+ .insert(schema.contentDatabaseItemKeyClaims)
358
+ .values({
359
+ id: nanoid(),
360
+ ownerEmail: database.ownerEmail,
361
+ orgId: database.orgId,
362
+ databaseId: database.id,
363
+ propertyId,
364
+ keyValueJson: valueJson,
365
+ itemId: membership.id,
366
+ documentId,
367
+ createdAt: now,
368
+ updatedAt: now,
369
+ })
370
+ .onConflictDoNothing();
371
+ const [claim] = await tx
372
+ .select({
373
+ itemId: schema.contentDatabaseItemKeyClaims.itemId,
374
+ documentId: schema.contentDatabaseItemKeyClaims.documentId,
375
+ })
376
+ .from(schema.contentDatabaseItemKeyClaims)
377
+ .where(
378
+ and(
379
+ eq(schema.contentDatabaseItemKeyClaims.databaseId, database.id),
380
+ eq(schema.contentDatabaseItemKeyClaims.propertyId, propertyId),
381
+ eq(schema.contentDatabaseItemKeyClaims.keyValueJson, valueJson),
382
+ ),
383
+ );
384
+ if (
385
+ !claim ||
386
+ claim.itemId !== membership.id ||
387
+ claim.documentId !== documentId
388
+ ) {
389
+ throw new Error(
390
+ "This natural key is already claimed by another database row.",
391
+ );
392
+ }
393
+ }
317
394
  await tx
318
395
  .delete(schema.contentDatabaseItemKeyClaims)
319
396
  .where(
@@ -0,0 +1,62 @@
1
+ import { defineAction } from "@agent-native/core";
2
+ import { buildDeepLink } from "@agent-native/core/server";
3
+ import { z } from "zod";
4
+
5
+ import type { ContentDatabaseRowMutationResult } from "../shared/api.js";
6
+ import {
7
+ databaseMutationEnvelopeSchema,
8
+ updateDatabaseRow,
9
+ } from "./_database-row-mutation.js";
10
+
11
+ const schema = databaseMutationEnvelopeSchema.extend({
12
+ itemId: z.string().min(1).describe("Exact database membership row ID"),
13
+ documentId: z.string().min(1).describe("Exact row page ID"),
14
+ expectedRowRevision: z
15
+ .string()
16
+ .min(1)
17
+ .describe("Row revision returned by get-content-database"),
18
+ title: z.string().trim().min(1).max(500).optional(),
19
+ propertyValues: z
20
+ .record(z.string(), z.unknown())
21
+ .optional()
22
+ .describe(
23
+ "Sparse strict patch keyed by property definition ID; omitted fields are preserved and explicit null clears a value",
24
+ ),
25
+ });
26
+
27
+ export default defineAction({
28
+ description:
29
+ "Sparsely update one exact Content database row by stable item and document IDs. Requires schema and row revisions, validates every non-Blocks property, and returns a verified idempotent receipt.",
30
+ schema,
31
+ http: { method: "PUT" },
32
+ audit: {
33
+ recordInputs: false,
34
+ target: (args) => ({
35
+ type: "document",
36
+ id: args.documentId,
37
+ visibility: "private",
38
+ }),
39
+ summary: (_args, result) => {
40
+ const receipt = (result as ContentDatabaseRowMutationResult | null)
41
+ ?.receipt;
42
+ return receipt
43
+ ? `${receipt.outcome === "unchanged" ? "Checked" : "Updated"} Content database row ${receipt.row.itemId}`
44
+ : "Updated Content database row";
45
+ },
46
+ },
47
+ run: updateDatabaseRow,
48
+ link: ({ result }) => {
49
+ const documentId = (result as ContentDatabaseRowMutationResult | null)
50
+ ?.receipt.row.documentId;
51
+ if (!documentId) return null;
52
+ return {
53
+ url: buildDeepLink({
54
+ app: "content",
55
+ view: "editor",
56
+ params: { documentId },
57
+ }),
58
+ label: "Open database row",
59
+ view: "editor",
60
+ };
61
+ },
62
+ });