@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.
- package/corpus/README.md +1 -1
- package/corpus/templates/content/.agents/skills/content/SKILL.md +2 -2
- package/corpus/templates/content/.agents/skills/document-editing/references/databases.md +20 -4
- package/corpus/templates/content/actions/_blocks-field-identity.ts +464 -0
- package/corpus/templates/content/actions/_database-row-mutation.ts +1490 -0
- package/corpus/templates/content/actions/_database-utils.ts +55 -3
- package/corpus/templates/content/actions/_property-utils.ts +141 -31
- package/corpus/templates/content/actions/add-database-item.ts +62 -240
- package/corpus/templates/content/actions/configure-document-property.ts +170 -1
- package/corpus/templates/content/actions/delete-document-property.ts +56 -17
- package/corpus/templates/content/actions/delete-document.ts +3 -0
- package/corpus/templates/content/actions/edit-document.ts +18 -1
- package/corpus/templates/content/actions/export-document.ts +41 -0
- package/corpus/templates/content/actions/remove-database-items.ts +10 -0
- package/corpus/templates/content/actions/set-document-property.ts +140 -3
- package/corpus/templates/content/actions/update-database-item.ts +62 -0
- package/corpus/templates/content/actions/update-document.ts +24 -0
- package/corpus/templates/content/actions/upsert-database-item-by-key.ts +43 -796
- package/corpus/templates/content/app/components/editor/DocumentBlockFields.tsx +99 -11
- package/corpus/templates/content/app/components/editor/blockFieldSaveController.ts +6 -0
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +10 -8
- package/corpus/templates/content/app/hooks/use-content-database.ts +33 -32
- package/corpus/templates/content/app/hooks/use-document-properties.ts +4 -1
- package/corpus/templates/content/app/i18n-data.ts +20 -0
- package/corpus/templates/content/changelog/2026-08-10-database-blocks-fields-now-keep-logical-block-identity-throu.md +6 -0
- package/corpus/templates/content/changelog/2026-08-10-database-row-actions-now-validate-exact-schemas-and-safe-ret.md +6 -0
- package/corpus/templates/content/changelog/2026-08-13-blocks-field-conflicts-now-keep-the-newer-saved-version-visi.md +6 -0
- package/corpus/templates/content/docs/product/capabilities/content.object.block.md +5 -4
- package/corpus/templates/content/docs/product/capabilities/content.object.blocks-field.md +9 -4
- package/corpus/templates/content/docs/product/encyclopedia.md +4 -4
- package/corpus/templates/content/parity/matrix.md +1 -1
- package/corpus/templates/content/parity/matrix.ts +2 -0
- package/corpus/templates/content/server/db/schema.ts +100 -3
- package/corpus/templates/content/server/plugins/db.ts +85 -0
- package/corpus/templates/content/shared/api.ts +78 -2
- package/corpus/templates/content/shared/blocks-field-identity.ts +554 -0
- package/corpus/templates/content/shared/document-export.ts +33 -1
- package/dist/action.d.ts +22 -0
- package/dist/action.js +27 -0
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/routes.d.ts +1 -1
- package/dist/extensions/url-safety.d.ts +7 -2
- package/dist/extensions/url-safety.js +27 -12
- package/dist/index.browser.d.ts +1 -1
- package/dist/index.browser.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/mcp-client/oauth-client.d.ts +16 -1
- package/dist/mcp-client/oauth-client.js +177 -83
- package/dist/mcp-client/remote-store.js +46 -20
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/oauth-tokens/index.d.ts +2 -1
- package/dist/oauth-tokens/index.js +2 -1
- package/dist/oauth-tokens/lifecycle.d.ts +87 -0
- package/dist/oauth-tokens/lifecycle.js +434 -0
- package/dist/oauth-tokens/migrations.js +10 -0
- package/dist/oauth-tokens/store.d.ts +22 -0
- package/dist/oauth-tokens/store.js +141 -18
- package/dist/observability/routes.d.ts +2 -2
- package/dist/progress/routes.d.ts +1 -1
- package/dist/secrets/routes.d.ts +6 -6
- package/dist/server/action-routes.js +10 -2
- package/dist/server/realtime-token.d.ts +1 -1
- package/package.json +2 -1
|
@@ -12,6 +12,10 @@ import {
|
|
|
12
12
|
parsePropertyOptions,
|
|
13
13
|
type DocumentPropertyType,
|
|
14
14
|
} from "../shared/properties.js";
|
|
15
|
+
import {
|
|
16
|
+
lockPrimaryBlocksFields,
|
|
17
|
+
persistBlocksFieldIdentity,
|
|
18
|
+
} from "./_blocks-field-identity.js";
|
|
15
19
|
import { lockContentDatabaseMutation } from "./_content-database-mutation-lock.js";
|
|
16
20
|
import { resolveContentDocumentAccess } from "./_content-document-access.js";
|
|
17
21
|
import { lockDatabaseMemberships } from "./_database-membership-lock.js";
|
|
@@ -34,8 +38,15 @@ export default defineAction({
|
|
|
34
38
|
),
|
|
35
39
|
propertyId: z.string().describe("Property definition ID"),
|
|
36
40
|
value: z.unknown().describe("Value for the property type"),
|
|
41
|
+
expectedBlocksFieldRevision: z.number().int().nonnegative().optional(),
|
|
37
42
|
}),
|
|
38
|
-
run: async ({
|
|
43
|
+
run: async ({
|
|
44
|
+
documentId,
|
|
45
|
+
databaseId,
|
|
46
|
+
propertyId,
|
|
47
|
+
value,
|
|
48
|
+
expectedBlocksFieldRevision,
|
|
49
|
+
}) => {
|
|
39
50
|
const db = getDb();
|
|
40
51
|
const [definition] = await db
|
|
41
52
|
.select()
|
|
@@ -85,7 +96,10 @@ export default defineAction({
|
|
|
85
96
|
parsePropertyOptions(definition.optionsJson),
|
|
86
97
|
);
|
|
87
98
|
await db.transaction(async (tx) => {
|
|
88
|
-
await
|
|
99
|
+
const primaryBlocksFields = await lockPrimaryBlocksFields(
|
|
100
|
+
tx as unknown as ReturnType<typeof getDb>,
|
|
101
|
+
documentId,
|
|
102
|
+
);
|
|
89
103
|
const [lockedDefinition] = await tx
|
|
90
104
|
.select()
|
|
91
105
|
.from(schema.documentPropertyDefinitions)
|
|
@@ -116,12 +130,31 @@ export default defineAction({
|
|
|
116
130
|
target = blocksStorageTarget(
|
|
117
131
|
parsePropertyOptions(lockedDefinition.optionsJson),
|
|
118
132
|
);
|
|
133
|
+
let previousContent = "";
|
|
119
134
|
if (target === "document_body") {
|
|
135
|
+
const [currentDocument] = await tx
|
|
136
|
+
.select({ content: schema.documents.content })
|
|
137
|
+
.from(schema.documents)
|
|
138
|
+
.where(eq(schema.documents.id, documentId));
|
|
139
|
+
if (!currentDocument) {
|
|
140
|
+
throw new Error(`Document "${documentId}" not found`);
|
|
141
|
+
}
|
|
142
|
+
previousContent = currentDocument.content;
|
|
120
143
|
await tx
|
|
121
144
|
.update(schema.documents)
|
|
122
145
|
.set({ content, updatedAt: now })
|
|
123
146
|
.where(eq(schema.documents.id, documentId));
|
|
124
147
|
} else {
|
|
148
|
+
const [currentField] = await tx
|
|
149
|
+
.select({ content: schema.documentBlockFieldContents.content })
|
|
150
|
+
.from(schema.documentBlockFieldContents)
|
|
151
|
+
.where(
|
|
152
|
+
and(
|
|
153
|
+
eq(schema.documentBlockFieldContents.documentId, documentId),
|
|
154
|
+
eq(schema.documentBlockFieldContents.propertyId, propertyId),
|
|
155
|
+
),
|
|
156
|
+
);
|
|
157
|
+
previousContent = currentField?.content ?? "";
|
|
125
158
|
await tx
|
|
126
159
|
.insert(schema.documentBlockFieldContents)
|
|
127
160
|
.values({
|
|
@@ -141,6 +174,33 @@ export default defineAction({
|
|
|
141
174
|
set: { content, updatedAt: now },
|
|
142
175
|
});
|
|
143
176
|
}
|
|
177
|
+
const identityFields =
|
|
178
|
+
target === "document_body"
|
|
179
|
+
? primaryBlocksFields
|
|
180
|
+
: [{ propertyId, ownerEmail: database.ownerEmail }];
|
|
181
|
+
if (
|
|
182
|
+
target === "document_body" &&
|
|
183
|
+
!identityFields.some((field) => field.propertyId === propertyId)
|
|
184
|
+
) {
|
|
185
|
+
throw new Error(
|
|
186
|
+
"Primary Blocks membership changed before the operation completed.",
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
for (const field of identityFields) {
|
|
190
|
+
await persistBlocksFieldIdentity({
|
|
191
|
+
db: tx as unknown as ReturnType<typeof getDb>,
|
|
192
|
+
ownerEmail: field.ownerEmail,
|
|
193
|
+
documentId,
|
|
194
|
+
propertyId: field.propertyId,
|
|
195
|
+
previousMarkdown: previousContent,
|
|
196
|
+
markdown: content,
|
|
197
|
+
expectedRevision:
|
|
198
|
+
field.propertyId === propertyId
|
|
199
|
+
? expectedBlocksFieldRevision
|
|
200
|
+
: undefined,
|
|
201
|
+
now,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
144
204
|
});
|
|
145
205
|
return {
|
|
146
206
|
documentId,
|
|
@@ -166,7 +226,10 @@ export default defineAction({
|
|
|
166
226
|
database.id,
|
|
167
227
|
);
|
|
168
228
|
const [lockedDatabase] = await tx
|
|
169
|
-
.select({
|
|
229
|
+
.select({
|
|
230
|
+
id: schema.contentDatabases.id,
|
|
231
|
+
naturalKeyPropertyId: schema.contentDatabases.naturalKeyPropertyId,
|
|
232
|
+
})
|
|
170
233
|
.from(schema.contentDatabases)
|
|
171
234
|
.where(
|
|
172
235
|
and(
|
|
@@ -212,6 +275,41 @@ export default defineAction({
|
|
|
212
275
|
if (isComputedPropertyType(lockedType)) {
|
|
213
276
|
throw new Error("Computed properties cannot be edited.");
|
|
214
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
|
+
}
|
|
215
313
|
const [conflictingClaim] = await tx
|
|
216
314
|
.select({ id: schema.contentDatabaseItemKeyClaims.id })
|
|
217
315
|
.from(schema.contentDatabaseItemKeyClaims)
|
|
@@ -254,6 +352,45 @@ export default defineAction({
|
|
|
254
352
|
updatedAt: now,
|
|
255
353
|
});
|
|
256
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
|
+
}
|
|
257
394
|
await tx
|
|
258
395
|
.delete(schema.contentDatabaseItemKeyClaims)
|
|
259
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
|
+
});
|
|
@@ -19,6 +19,10 @@ import {
|
|
|
19
19
|
parseDocumentHideFromSearch,
|
|
20
20
|
} from "../server/lib/documents.js";
|
|
21
21
|
import type { DocumentUpdateResponse } from "../shared/api.js";
|
|
22
|
+
import {
|
|
23
|
+
lockPrimaryBlocksFields,
|
|
24
|
+
persistBlocksFieldIdentity,
|
|
25
|
+
} from "./_blocks-field-identity.js";
|
|
22
26
|
import { BUILDER_CMS_BODY_CONTENT_KEY } from "./_builder-cms-source-adapter.js";
|
|
23
27
|
import { reconcileInlineDatabasesForDocument } from "./_content-database-lifecycle.js";
|
|
24
28
|
import { resolveContentDocumentAccess } from "./_content-document-access.js";
|
|
@@ -510,6 +514,12 @@ export default defineAction({
|
|
|
510
514
|
if (iconChanged) updates.icon = args.icon;
|
|
511
515
|
let contentCasConflict = false;
|
|
512
516
|
await db.transaction(async (tx) => {
|
|
517
|
+
const primaryBlocksFields = contentChanged
|
|
518
|
+
? await lockPrimaryBlocksFields(
|
|
519
|
+
tx as unknown as ReturnType<typeof getDb>,
|
|
520
|
+
id,
|
|
521
|
+
)
|
|
522
|
+
: [];
|
|
513
523
|
if (useContentCas) {
|
|
514
524
|
const applied = await tx
|
|
515
525
|
.update(schema.documents)
|
|
@@ -532,6 +542,20 @@ export default defineAction({
|
|
|
532
542
|
.where(eq(schema.documents.id, id));
|
|
533
543
|
}
|
|
534
544
|
|
|
545
|
+
if (contentChanged && content !== undefined) {
|
|
546
|
+
for (const field of primaryBlocksFields) {
|
|
547
|
+
await persistBlocksFieldIdentity({
|
|
548
|
+
db: tx as unknown as ReturnType<typeof getDb>,
|
|
549
|
+
ownerEmail: field.ownerEmail,
|
|
550
|
+
documentId: id,
|
|
551
|
+
propertyId: field.propertyId,
|
|
552
|
+
previousMarkdown: existing.content,
|
|
553
|
+
markdown: content,
|
|
554
|
+
now: updates.updatedAt as string,
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
535
559
|
if (titleChanged || contentChanged) {
|
|
536
560
|
const [latestVersion] = await tx
|
|
537
561
|
.select({ createdAt: schema.documentVersions.createdAt })
|