@agent-native/core 0.80.5 → 0.80.7
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/core/CHANGELOG.md +13 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/cli/create.ts +1 -0
- package/corpus/core/src/client/extensions/EmbeddedExtension.tsx +45 -0
- package/corpus/core/src/sharing/registry.ts +17 -0
- package/corpus/core/src/templates/default/pnpm-workspace.yaml +1 -0
- package/corpus/core/src/templates/workspace-root/pnpm-workspace.yaml +1 -0
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +42 -4
- package/corpus/templates/analytics/actions/dashboard-mutation-api.ts +2 -1
- package/corpus/templates/analytics/actions/update-dashboard.ts +23 -9
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +72 -1
- package/corpus/templates/analytics/app/i18n/zh-TW.ts +2 -0
- package/corpus/templates/analytics/app/i18n-data.ts +34 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +88 -2
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +7 -1
- package/corpus/templates/analytics/changelog/2026-06-29-dashboards-can-now-include-extension-panels-that-embed-a-san.md +6 -0
- package/corpus/templates/content/AGENTS.md +45 -44
- package/corpus/templates/content/README.md +16 -0
- package/corpus/templates/content/actions/_builder-cms-read-client.ts +125 -0
- package/corpus/templates/content/actions/_builder-cms-source-adapter.ts +28 -6
- package/corpus/templates/content/actions/_builder-cms-write-adapter.ts +238 -69
- package/corpus/templates/content/actions/_builder-cms-write-settings.ts +125 -43
- package/corpus/templates/content/actions/_database-source-utils.ts +746 -97
- package/corpus/templates/content/actions/_database-utils.ts +17 -14
- package/corpus/templates/content/actions/_federation-join.ts +14 -2
- package/corpus/templates/content/actions/_join-suggestion.ts +89 -14
- package/corpus/templates/content/actions/_local-file-documents.ts +2 -1
- package/corpus/templates/content/actions/attach-content-database-source.ts +124 -5
- package/corpus/templates/content/actions/bind-content-database-source-field.ts +256 -0
- package/corpus/templates/content/actions/change-content-database-source-role.ts +420 -0
- package/corpus/templates/content/actions/create-document.ts +8 -0
- package/corpus/templates/content/actions/execute-builder-source-batch.ts +282 -0
- package/corpus/templates/content/actions/execute-builder-source-execution.ts +220 -8
- package/corpus/templates/content/actions/get-content-database.ts +10 -2
- package/corpus/templates/content/actions/prepare-builder-source-execution.ts +19 -2
- package/corpus/templates/content/actions/prepare-builder-source-review.ts +47 -15
- package/corpus/templates/content/actions/refresh-content-database-source.ts +11 -4
- package/corpus/templates/content/actions/review-content-database-source-change-set.ts +6 -2
- package/corpus/templates/content/actions/set-content-database-source-write-mode.ts +32 -12
- package/corpus/templates/content/actions/stage-builder-revision.ts +14 -10
- package/corpus/templates/content/actions/validate-builder-source-execution.ts +25 -3
- package/corpus/templates/content/app/components/editor/DocumentDatabase.tsx +1520 -553
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +150 -1
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +647 -66
- package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +543 -204
- package/corpus/templates/content/app/hooks/use-content-database.ts +62 -0
- package/corpus/templates/content/app/i18n/zh-TW.ts +64 -0
- package/corpus/templates/content/app/i18n-data.ts +784 -0
- package/corpus/templates/content/changelog/2026-06-29-builder-review-checks-now-preserve-publish-and-unpublish-cho.md +6 -0
- package/corpus/templates/content/changelog/2026-06-29-database-sources-can-now-be-added-as-more-rows-or-matched.md +6 -0
- package/corpus/templates/content/changelog/2026-06-29-fixed-loading-the-final-rows-in-larger-databases-without-bre.md +6 -0
- package/corpus/templates/content/shared/api.ts +91 -1
- package/corpus/templates/content/vite.config.ts +14 -7
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/create.js +1 -0
- package/dist/cli/create.js.map +1 -1
- package/dist/client/extensions/EmbeddedExtension.d.ts +10 -1
- package/dist/client/extensions/EmbeddedExtension.d.ts.map +1 -1
- package/dist/client/extensions/EmbeddedExtension.js +34 -3
- package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
- package/dist/collab/routes.d.ts +2 -2
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +7 -7
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/sharing/registry.d.ts.map +1 -1
- package/dist/sharing/registry.js +11 -0
- package/dist/sharing/registry.js.map +1 -1
- package/dist/templates/default/pnpm-workspace.yaml +1 -0
- package/dist/templates/workspace-root/pnpm-workspace.yaml +1 -0
- package/package.json +1 -1
- package/src/templates/default/pnpm-workspace.yaml +1 -0
- package/src/templates/workspace-root/pnpm-workspace.yaml +1 -0
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import { assertAccess } from "@agent-native/core/sharing";
|
|
3
|
+
import { and, asc, eq, inArray, ne } from "drizzle-orm";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
|
|
6
|
+
import { getDb, schema } from "../server/db/index.js";
|
|
7
|
+
import type {
|
|
8
|
+
BuilderCmsModelFieldSummary,
|
|
9
|
+
ContentDatabaseResponse,
|
|
10
|
+
ContentDatabaseSourceFederation,
|
|
11
|
+
ContentDatabaseSourceType,
|
|
12
|
+
} from "../shared/api.js";
|
|
13
|
+
import { sanitizeNormalizationFormula } from "../shared/properties.js";
|
|
14
|
+
import {
|
|
15
|
+
readBuilderCmsContentEntries,
|
|
16
|
+
readBuilderCmsModelFields,
|
|
17
|
+
} from "./_builder-cms-read-client.js";
|
|
18
|
+
import type { BuilderCmsSourceEntry } from "./_builder-cms-source-adapter.js";
|
|
19
|
+
import {
|
|
20
|
+
ensureDatabaseSourceProperty,
|
|
21
|
+
importBuilderCmsEntriesAsDatabaseItems,
|
|
22
|
+
mapBuilderCmsEntriesToLocalItems,
|
|
23
|
+
resolveDatabaseForSourceMutation,
|
|
24
|
+
seedMockSourceFields,
|
|
25
|
+
seedMockSourceRows,
|
|
26
|
+
seedSecondarySourceFields,
|
|
27
|
+
sourceSetupPayload,
|
|
28
|
+
storeSecondarySourceRows,
|
|
29
|
+
updateBuilderCmsSourceReadMetadata,
|
|
30
|
+
writeSourceFederation,
|
|
31
|
+
} from "./_database-source-utils.js";
|
|
32
|
+
import { getContentDatabaseResponse } from "./_database-utils.js";
|
|
33
|
+
import { readLocalTableEntries } from "./_local-table-source.js";
|
|
34
|
+
|
|
35
|
+
const normalizationFormulaSchema = z
|
|
36
|
+
.string()
|
|
37
|
+
.max(1000)
|
|
38
|
+
.refine((value) => sanitizeNormalizationFormula(value) !== null, {
|
|
39
|
+
message:
|
|
40
|
+
"Normalization formula contains an unsafe regex or invalid expression.",
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const joinSideSchema = z.object({
|
|
44
|
+
keyField: z.string(),
|
|
45
|
+
normalizationFormula: normalizationFormulaSchema,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const joinSchema = z.object({
|
|
49
|
+
canonicalKey: z.object({
|
|
50
|
+
propertyId: z.string().nullable().optional(),
|
|
51
|
+
label: z.string(),
|
|
52
|
+
type: z.string().default("text"),
|
|
53
|
+
}),
|
|
54
|
+
primary: joinSideSchema,
|
|
55
|
+
secondary: joinSideSchema,
|
|
56
|
+
columnBindings: z
|
|
57
|
+
.array(
|
|
58
|
+
z.object({
|
|
59
|
+
propertyId: z.string().nullable().optional(),
|
|
60
|
+
localFieldKey: z.string().nullable().optional(),
|
|
61
|
+
role: z.enum(["primary", "mirror"]),
|
|
62
|
+
primarySourceId: z.string().nullable().optional(),
|
|
63
|
+
sourceFieldKey: z.string(),
|
|
64
|
+
}),
|
|
65
|
+
)
|
|
66
|
+
.optional(),
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
type SourceRecord = typeof schema.contentDatabaseSources.$inferSelect;
|
|
70
|
+
|
|
71
|
+
function identityFederation(
|
|
72
|
+
role: "primary" | "secondary",
|
|
73
|
+
side: z.infer<typeof joinSideSchema>,
|
|
74
|
+
canonicalKey: {
|
|
75
|
+
propertyId?: string | null;
|
|
76
|
+
label: string;
|
|
77
|
+
type?: string;
|
|
78
|
+
},
|
|
79
|
+
columnBindings?: z.infer<typeof joinSchema>["columnBindings"],
|
|
80
|
+
): ContentDatabaseSourceFederation {
|
|
81
|
+
return {
|
|
82
|
+
role,
|
|
83
|
+
keyField: side.keyField,
|
|
84
|
+
normalizationFormula: side.normalizationFormula,
|
|
85
|
+
join: {
|
|
86
|
+
kind: "identity",
|
|
87
|
+
collection: null,
|
|
88
|
+
localExpr: "{canonical}",
|
|
89
|
+
remoteKeyField: side.keyField,
|
|
90
|
+
normalizationFormula: side.normalizationFormula,
|
|
91
|
+
},
|
|
92
|
+
canonicalKey: {
|
|
93
|
+
propertyId: canonicalKey.propertyId ?? null,
|
|
94
|
+
label: canonicalKey.label,
|
|
95
|
+
type: canonicalKey.type ?? "text",
|
|
96
|
+
},
|
|
97
|
+
columnBindings:
|
|
98
|
+
role === "secondary"
|
|
99
|
+
? columnBindings?.map((binding) => ({
|
|
100
|
+
propertyId: binding.propertyId ?? null,
|
|
101
|
+
localFieldKey: binding.localFieldKey ?? null,
|
|
102
|
+
role: binding.role,
|
|
103
|
+
primarySourceId: binding.primarySourceId ?? null,
|
|
104
|
+
sourceFieldKey: binding.sourceFieldKey,
|
|
105
|
+
}))
|
|
106
|
+
: undefined,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function sourceType(value: string): ContentDatabaseSourceType {
|
|
111
|
+
if (value === "builder-cms" || value === "local-table") return value;
|
|
112
|
+
return "mock-local";
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function sourceRole(source: SourceRecord): "primary" | "secondary" | null {
|
|
116
|
+
try {
|
|
117
|
+
const parsed = JSON.parse(source.metadataJson ?? "{}") as {
|
|
118
|
+
federation?: { role?: string };
|
|
119
|
+
};
|
|
120
|
+
return parsed.federation?.role === "primary" ||
|
|
121
|
+
parsed.federation?.role === "secondary"
|
|
122
|
+
? parsed.federation.role
|
|
123
|
+
: null;
|
|
124
|
+
} catch {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function clearSourceFederation(sourceId: string, now: string) {
|
|
130
|
+
const db = getDb();
|
|
131
|
+
const [current] = await db
|
|
132
|
+
.select({ metadataJson: schema.contentDatabaseSources.metadataJson })
|
|
133
|
+
.from(schema.contentDatabaseSources)
|
|
134
|
+
.where(eq(schema.contentDatabaseSources.id, sourceId));
|
|
135
|
+
let metadata: Record<string, unknown> = {};
|
|
136
|
+
try {
|
|
137
|
+
const parsed = JSON.parse(current?.metadataJson ?? "{}") as unknown;
|
|
138
|
+
metadata =
|
|
139
|
+
parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
140
|
+
? (parsed as Record<string, unknown>)
|
|
141
|
+
: {};
|
|
142
|
+
} catch {
|
|
143
|
+
metadata = {};
|
|
144
|
+
}
|
|
145
|
+
delete metadata.federation;
|
|
146
|
+
await db
|
|
147
|
+
.update(schema.contentDatabaseSources)
|
|
148
|
+
.set({ metadataJson: JSON.stringify(metadata), updatedAt: now })
|
|
149
|
+
.where(eq(schema.contentDatabaseSources.id, sourceId));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function removeRowsOwnedOnlyBySource(args: {
|
|
153
|
+
databaseId: string;
|
|
154
|
+
sourceId: string;
|
|
155
|
+
}) {
|
|
156
|
+
const db = getDb();
|
|
157
|
+
const [targetRows, otherRows] = await Promise.all([
|
|
158
|
+
db
|
|
159
|
+
.select()
|
|
160
|
+
.from(schema.contentDatabaseSourceRows)
|
|
161
|
+
.where(eq(schema.contentDatabaseSourceRows.sourceId, args.sourceId)),
|
|
162
|
+
db
|
|
163
|
+
.select()
|
|
164
|
+
.from(schema.contentDatabaseSourceRows)
|
|
165
|
+
.where(ne(schema.contentDatabaseSourceRows.sourceId, args.sourceId)),
|
|
166
|
+
]);
|
|
167
|
+
const otherDocumentIds = new Set(
|
|
168
|
+
otherRows.map((row) => row.documentId).filter(Boolean),
|
|
169
|
+
);
|
|
170
|
+
const itemIds = targetRows
|
|
171
|
+
.filter(
|
|
172
|
+
(row) =>
|
|
173
|
+
row.databaseItemId &&
|
|
174
|
+
row.documentId &&
|
|
175
|
+
!otherDocumentIds.has(row.documentId),
|
|
176
|
+
)
|
|
177
|
+
.map((row) => row.databaseItemId);
|
|
178
|
+
if (itemIds.length === 0) return;
|
|
179
|
+
await db
|
|
180
|
+
.delete(schema.contentDatabaseItems)
|
|
181
|
+
.where(
|
|
182
|
+
and(
|
|
183
|
+
eq(schema.contentDatabaseItems.databaseId, args.databaseId),
|
|
184
|
+
inArray(schema.contentDatabaseItems.id, itemIds),
|
|
185
|
+
),
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function readSourceEntries(args: {
|
|
190
|
+
sourceType: ContentDatabaseSourceType;
|
|
191
|
+
sourceTable: string;
|
|
192
|
+
limit: number;
|
|
193
|
+
offset: number;
|
|
194
|
+
}): Promise<{
|
|
195
|
+
entries: BuilderCmsSourceEntry[];
|
|
196
|
+
modelFields: BuilderCmsModelFieldSummary[];
|
|
197
|
+
readState: "live" | "unconfigured" | "error" | null;
|
|
198
|
+
fetchedAt: string | null;
|
|
199
|
+
message: string | null;
|
|
200
|
+
}> {
|
|
201
|
+
if (args.sourceType === "builder-cms") {
|
|
202
|
+
const read = await readBuilderCmsContentEntries({
|
|
203
|
+
model: args.sourceTable,
|
|
204
|
+
});
|
|
205
|
+
return {
|
|
206
|
+
entries: read.state === "live" ? read.entries : [],
|
|
207
|
+
modelFields: await readBuilderCmsModelFields({ model: args.sourceTable }),
|
|
208
|
+
readState: read.state,
|
|
209
|
+
fetchedAt: read.fetchedAt,
|
|
210
|
+
message: read.message,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
if (args.sourceType === "local-table") {
|
|
214
|
+
const { entries, modelFields } = await readLocalTableEntries(
|
|
215
|
+
args.sourceTable,
|
|
216
|
+
{ limit: args.limit, offset: args.offset },
|
|
217
|
+
);
|
|
218
|
+
return {
|
|
219
|
+
entries,
|
|
220
|
+
modelFields,
|
|
221
|
+
readState: null,
|
|
222
|
+
fetchedAt: null,
|
|
223
|
+
message: null,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
entries: [],
|
|
228
|
+
modelFields: [],
|
|
229
|
+
readState: null,
|
|
230
|
+
fetchedAt: null,
|
|
231
|
+
message: null,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export default defineAction({
|
|
236
|
+
description:
|
|
237
|
+
"Change how an attached content database source participates: add more item rows, or add details to existing rows through a confirmed match key.",
|
|
238
|
+
schema: z.object({
|
|
239
|
+
databaseId: z.string().optional().describe("Database ID"),
|
|
240
|
+
documentId: z.string().optional().describe("Database document/page ID"),
|
|
241
|
+
sourceId: z.string().describe("Attached source ID to change."),
|
|
242
|
+
relationshipMode: z
|
|
243
|
+
.enum(["items", "details"])
|
|
244
|
+
.describe("items adds rows; details joins fields onto existing rows."),
|
|
245
|
+
join: joinSchema
|
|
246
|
+
.optional()
|
|
247
|
+
.describe("Required when changing a source to add details."),
|
|
248
|
+
limit: z.coerce.number().int().min(1).max(500).default(100),
|
|
249
|
+
offset: z.coerce.number().int().min(0).default(0),
|
|
250
|
+
}),
|
|
251
|
+
run: async (args): Promise<ContentDatabaseResponse> => {
|
|
252
|
+
const database = await resolveDatabaseForSourceMutation(args);
|
|
253
|
+
if (!database) throw new Error("Database not found.");
|
|
254
|
+
await assertAccess("document", database.documentId, "editor");
|
|
255
|
+
|
|
256
|
+
const db = getDb();
|
|
257
|
+
const now = new Date().toISOString();
|
|
258
|
+
const sources = await db
|
|
259
|
+
.select()
|
|
260
|
+
.from(schema.contentDatabaseSources)
|
|
261
|
+
.where(eq(schema.contentDatabaseSources.databaseId, database.id))
|
|
262
|
+
.orderBy(asc(schema.contentDatabaseSources.createdAt));
|
|
263
|
+
const source = sources.find((item) => item.id === args.sourceId) ?? null;
|
|
264
|
+
if (!source) throw new Error("Source not found.");
|
|
265
|
+
|
|
266
|
+
const normalizedType = sourceType(source.sourceType);
|
|
267
|
+
|
|
268
|
+
if (args.relationshipMode === "details") {
|
|
269
|
+
if (!args.join) {
|
|
270
|
+
throw new Error("Choose a match key before adding source details.");
|
|
271
|
+
}
|
|
272
|
+
const primary =
|
|
273
|
+
sources.find(
|
|
274
|
+
(item) => item.id !== source.id && sourceRole(item) !== "secondary",
|
|
275
|
+
) ?? null;
|
|
276
|
+
if (!primary) {
|
|
277
|
+
throw new Error("Add an item source before adding source details.");
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const { entries, modelFields } = await readSourceEntries({
|
|
281
|
+
sourceType: normalizedType,
|
|
282
|
+
sourceTable: source.sourceTable,
|
|
283
|
+
limit: args.limit ?? 100,
|
|
284
|
+
offset: args.offset ?? 0,
|
|
285
|
+
});
|
|
286
|
+
await removeRowsOwnedOnlyBySource({
|
|
287
|
+
databaseId: database.id,
|
|
288
|
+
sourceId: source.id,
|
|
289
|
+
});
|
|
290
|
+
await storeSecondarySourceRows({
|
|
291
|
+
sourceId: source.id,
|
|
292
|
+
ownerEmail: database.ownerEmail,
|
|
293
|
+
sourceType: normalizedType,
|
|
294
|
+
sourceTable: source.sourceTable,
|
|
295
|
+
entries,
|
|
296
|
+
now,
|
|
297
|
+
});
|
|
298
|
+
await seedSecondarySourceFields({
|
|
299
|
+
sourceId: source.id,
|
|
300
|
+
ownerEmail: database.ownerEmail,
|
|
301
|
+
modelFields,
|
|
302
|
+
sampleEntry: entries[0],
|
|
303
|
+
now,
|
|
304
|
+
});
|
|
305
|
+
await writeSourceFederation({
|
|
306
|
+
sourceId: source.id,
|
|
307
|
+
federation: identityFederation(
|
|
308
|
+
"secondary",
|
|
309
|
+
args.join.secondary,
|
|
310
|
+
args.join.canonicalKey,
|
|
311
|
+
args.join.columnBindings,
|
|
312
|
+
),
|
|
313
|
+
now,
|
|
314
|
+
});
|
|
315
|
+
await writeSourceFederation({
|
|
316
|
+
sourceId: primary.id,
|
|
317
|
+
federation: identityFederation(
|
|
318
|
+
"primary",
|
|
319
|
+
args.join.primary,
|
|
320
|
+
args.join.canonicalKey,
|
|
321
|
+
),
|
|
322
|
+
now,
|
|
323
|
+
});
|
|
324
|
+
await ensureDatabaseSourceProperty({ database, now });
|
|
325
|
+
} else {
|
|
326
|
+
if (normalizedType !== "builder-cms") {
|
|
327
|
+
throw new Error("Only Builder sources can add more items right now.");
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const read = await readBuilderCmsContentEntries({
|
|
331
|
+
model: source.sourceTable,
|
|
332
|
+
});
|
|
333
|
+
const entries = read.state === "live" ? read.entries : [];
|
|
334
|
+
const builderModelFields = await readBuilderCmsModelFields({
|
|
335
|
+
model: source.sourceTable,
|
|
336
|
+
});
|
|
337
|
+
await db
|
|
338
|
+
.delete(schema.contentDatabaseSourceFields)
|
|
339
|
+
.where(eq(schema.contentDatabaseSourceFields.sourceId, source.id));
|
|
340
|
+
await db
|
|
341
|
+
.delete(schema.contentDatabaseSourceRows)
|
|
342
|
+
.where(eq(schema.contentDatabaseSourceRows.sourceId, source.id));
|
|
343
|
+
await clearSourceFederation(source.id, now);
|
|
344
|
+
|
|
345
|
+
if (read.state === "live") {
|
|
346
|
+
await importBuilderCmsEntriesAsDatabaseItems({
|
|
347
|
+
database,
|
|
348
|
+
entries,
|
|
349
|
+
now,
|
|
350
|
+
sourceTable: source.sourceTable,
|
|
351
|
+
existingSourceRows: [],
|
|
352
|
+
skipTitleDedup: true,
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
const refreshedSetup = await sourceSetupPayload(database.id);
|
|
356
|
+
const builderEntriesByDocumentId =
|
|
357
|
+
read.state === "live"
|
|
358
|
+
? mapBuilderCmsEntriesToLocalItems({
|
|
359
|
+
entries,
|
|
360
|
+
items: refreshedSetup.response.items,
|
|
361
|
+
sourceTable: source.sourceTable,
|
|
362
|
+
now,
|
|
363
|
+
existingRows: [],
|
|
364
|
+
})
|
|
365
|
+
: new Map<string, BuilderCmsSourceEntry>();
|
|
366
|
+
await seedMockSourceFields({
|
|
367
|
+
sourceId: source.id,
|
|
368
|
+
ownerEmail: database.ownerEmail,
|
|
369
|
+
sourceType: "builder-cms",
|
|
370
|
+
properties: refreshedSetup.properties,
|
|
371
|
+
builderModelFields,
|
|
372
|
+
builderSampleEntries: entries,
|
|
373
|
+
now,
|
|
374
|
+
});
|
|
375
|
+
await seedMockSourceRows({
|
|
376
|
+
sourceId: source.id,
|
|
377
|
+
ownerEmail: database.ownerEmail,
|
|
378
|
+
sourceType: "builder-cms",
|
|
379
|
+
sourceTable: source.sourceTable,
|
|
380
|
+
items: refreshedSetup.response.items.filter((item) =>
|
|
381
|
+
builderEntriesByDocumentId.has(item.document.id),
|
|
382
|
+
),
|
|
383
|
+
now,
|
|
384
|
+
builderEntriesByDocumentId,
|
|
385
|
+
});
|
|
386
|
+
await updateBuilderCmsSourceReadMetadata({
|
|
387
|
+
sourceId: source.id,
|
|
388
|
+
sourceTable: source.sourceTable,
|
|
389
|
+
readState: read.state,
|
|
390
|
+
entryCount: entries.length,
|
|
391
|
+
matchedRowCount: builderEntriesByDocumentId.size,
|
|
392
|
+
fetchedAt: read.fetchedAt,
|
|
393
|
+
now,
|
|
394
|
+
message: read.message,
|
|
395
|
+
syncState: "linked",
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
const latestSources = await db
|
|
399
|
+
.select()
|
|
400
|
+
.from(schema.contentDatabaseSources)
|
|
401
|
+
.where(eq(schema.contentDatabaseSources.databaseId, database.id));
|
|
402
|
+
const hasOtherDetails = latestSources.some(
|
|
403
|
+
(item) => item.id !== source.id && sourceRole(item) === "secondary",
|
|
404
|
+
);
|
|
405
|
+
if (!hasOtherDetails) {
|
|
406
|
+
for (const item of latestSources) {
|
|
407
|
+
if (sourceRole(item) === "primary") {
|
|
408
|
+
await clearSourceFederation(item.id, now);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
await ensureDatabaseSourceProperty({ database, now });
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return getContentDatabaseResponse(database.id, {
|
|
416
|
+
limit: args.limit ?? 100,
|
|
417
|
+
offset: args.offset ?? 0,
|
|
418
|
+
});
|
|
419
|
+
},
|
|
420
|
+
});
|
|
@@ -28,6 +28,13 @@ function nanoid(size = 12): string {
|
|
|
28
28
|
return id;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
function assertCanWriteAppState() {
|
|
32
|
+
if (getRequestUserEmail() || process.env.AGENT_USER_EMAIL) return;
|
|
33
|
+
throw new Error(
|
|
34
|
+
"Application state access requires an authenticated request context or AGENT_USER_EMAIL env var",
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
31
38
|
export default defineAction({
|
|
32
39
|
description: "Create a new document.",
|
|
33
40
|
schema: z.object({
|
|
@@ -53,6 +60,7 @@ export default defineAction({
|
|
|
53
60
|
},
|
|
54
61
|
run: async (args) => {
|
|
55
62
|
if (await isContentLocalFileMode()) {
|
|
63
|
+
assertCanWriteAppState();
|
|
56
64
|
const doc = await createLocalFileDocument(args);
|
|
57
65
|
await writeAppState("refresh-signal", { ts: Date.now() });
|
|
58
66
|
return {
|