@agent-native/core 0.133.2 → 0.133.3
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 +6 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/client/use-action.ts +6 -0
- package/corpus/templates/content/actions/_batch-utils.ts +33 -0
- package/corpus/templates/content/actions/_builder-cms-read-client.ts +173 -81
- package/corpus/templates/content/actions/_content-files.ts +31 -21
- package/corpus/templates/content/actions/_database-source-utils.ts +653 -155
- package/corpus/templates/content/actions/_database-utils.ts +535 -41
- package/corpus/templates/content/actions/add-content-database-source-field-property.ts +291 -240
- package/corpus/templates/content/actions/add-database-item.ts +15 -1
- package/corpus/templates/content/actions/attach-content-database-source.ts +156 -58
- package/corpus/templates/content/actions/bind-content-database-source-field.ts +2 -2
- package/corpus/templates/content/actions/cancel-prepared-builder-source-update.ts +4 -1
- package/corpus/templates/content/actions/delete-database-items.ts +8 -2
- package/corpus/templates/content/actions/disconnect-content-database-source.ts +2 -2
- package/corpus/templates/content/actions/duplicate-database-item.ts +15 -1
- package/corpus/templates/content/actions/duplicate-database-items.ts +19 -1
- package/corpus/templates/content/actions/execute-builder-source-execution.ts +2 -1
- package/corpus/templates/content/actions/get-content-database.ts +12 -64
- package/corpus/templates/content/actions/move-database-item.ts +4 -1
- package/corpus/templates/content/actions/prepare-builder-source-execution.ts +1 -1
- package/corpus/templates/content/actions/prepare-builder-source-review.ts +4 -8
- package/corpus/templates/content/actions/preview-content-database-source-attach.ts +92 -0
- package/corpus/templates/content/actions/process-builder-body-hydration.ts +2 -1
- package/corpus/templates/content/actions/query-content-database-items.ts +64 -0
- package/corpus/templates/content/actions/refresh-content-database-source.ts +7 -0
- package/corpus/templates/content/actions/review-content-database-source-change-set.ts +1 -1
- package/corpus/templates/content/actions/set-content-database-source-write-mode.ts +1 -1
- package/corpus/templates/content/actions/stage-builder-revision.ts +1 -1
- package/corpus/templates/content/actions/update-content-database-personal-view.ts +29 -3
- package/corpus/templates/content/actions/update-content-database-view.ts +1 -1
- package/corpus/templates/content/actions/validate-builder-source-execution.ts +1 -1
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +3 -22
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +267 -105
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +82 -1
- package/corpus/templates/content/app/hooks/use-content-database.ts +285 -24
- package/corpus/templates/content/app/hooks/use-document-properties.ts +7 -6
- package/corpus/templates/content/app/i18n-data.ts +10 -0
- package/corpus/templates/content/changelog/2026-07-29-large-databases-keep-useful-rows-visible.md +6 -0
- package/corpus/templates/content/changelog/2026-07-30-builder-source-columns-now-appear-immediately-when-connected.md +6 -0
- package/corpus/templates/content/changelog/2026-07-30-large-builder-backed-tables-show-useful-rows-sooner-and-fini.md +6 -0
- package/corpus/templates/content/changelog/2026-08-01-large-builder-databases-now-show-rows-immediately-and-finish.md +6 -0
- package/corpus/templates/content/parity/matrix.md +2 -1
- package/corpus/templates/content/parity/matrix.ts +25 -0
- package/corpus/templates/content/shared/api.ts +54 -4
- package/corpus/templates/content/shared/database-query.ts +359 -0
- package/dist/client/use-action.d.ts.map +1 -1
- package/dist/client/use-action.js +6 -0
- package/dist/client/use-action.js.map +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/package.json +1 -1
- package/src/client/use-action.ts +6 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import { getDialect } from "@agent-native/core/db";
|
|
2
3
|
import { assertAccess } from "@agent-native/core/sharing";
|
|
3
4
|
import { and, eq, isNull, sql } from "drizzle-orm";
|
|
4
5
|
import { z } from "zod";
|
|
@@ -37,6 +38,12 @@ const BUILDER_FIELD_REFRESH_MINIMUM_LIMIT = 500;
|
|
|
37
38
|
|
|
38
39
|
type SourceRow = typeof schema.contentDatabaseSourceRows.$inferSelect;
|
|
39
40
|
|
|
41
|
+
class MissingBuilderFieldValuesError extends Error {
|
|
42
|
+
constructor(readonly rowCount: number) {
|
|
43
|
+
super("Builder source field values are missing from the local snapshot.");
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
40
47
|
function parseSourceValues(
|
|
41
48
|
value: string | null | undefined,
|
|
42
49
|
): Record<string, DocumentPropertyValue> {
|
|
@@ -61,6 +68,32 @@ function hasSourceFieldValue(
|
|
|
61
68
|
);
|
|
62
69
|
}
|
|
63
70
|
|
|
71
|
+
function sourceFieldValueJsonProjection(sourceFieldKey: string) {
|
|
72
|
+
const sourceValuesJson = schema.contentDatabaseSourceRows.sourceValuesJson;
|
|
73
|
+
if (getDialect() === "postgres") {
|
|
74
|
+
return sql<
|
|
75
|
+
string | null
|
|
76
|
+
>`(${sourceValuesJson}::jsonb -> ${sourceFieldKey})::text`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const path = `$."${sourceFieldKey}"`;
|
|
80
|
+
const type = sql<string | null>`json_type(${sourceValuesJson}, ${path})`;
|
|
81
|
+
return sql<string | null>`CASE
|
|
82
|
+
WHEN ${type} IS NULL THEN NULL
|
|
83
|
+
WHEN ${type} = 'true' THEN 'true'
|
|
84
|
+
WHEN ${type} = 'false' THEN 'false'
|
|
85
|
+
ELSE json_quote(json_extract(${sourceValuesJson}, ${path}))
|
|
86
|
+
END`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function compactSourceValuesJson(
|
|
90
|
+
sourceFieldKey: string,
|
|
91
|
+
sourceFieldValueJson: string,
|
|
92
|
+
) {
|
|
93
|
+
const escapedKey = sourceFieldKey.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
94
|
+
return `{"${escapedKey}":${sourceFieldValueJson}}`;
|
|
95
|
+
}
|
|
96
|
+
|
|
64
97
|
function mergeBuilderFieldIntoSourceRows(args: {
|
|
65
98
|
rows: SourceRow[];
|
|
66
99
|
entries: BuilderCmsSourceEntry[];
|
|
@@ -383,266 +416,284 @@ export default defineAction({
|
|
|
383
416
|
}
|
|
384
417
|
const isSecondary = federationRole === "secondary";
|
|
385
418
|
|
|
386
|
-
const initialSourceRows = isSecondary
|
|
387
|
-
? []
|
|
388
|
-
: await db
|
|
389
|
-
.select()
|
|
390
|
-
.from(schema.contentDatabaseSourceRows)
|
|
391
|
-
.where(eq(schema.contentDatabaseSourceRows.sourceId, source.id));
|
|
392
|
-
const shouldRefreshBuilderField =
|
|
393
|
-
!isSecondary &&
|
|
394
|
-
source.sourceType === "builder-cms" &&
|
|
395
|
-
initialSourceRows.some(
|
|
396
|
-
(row) => !hasSourceFieldValue(row, field.sourceFieldKey),
|
|
397
|
-
);
|
|
398
|
-
let builderEntries: BuilderCmsSourceEntry[] | null = null;
|
|
399
|
-
if (shouldRefreshBuilderField) {
|
|
400
|
-
// Read before writing so a Builder outage cannot leave behind a cleanly
|
|
401
|
-
// reported but empty property. Start at zero even when the source's
|
|
402
|
-
// broader refresh is incremental: the stored rows we are backfilling
|
|
403
|
-
// begin with that first page.
|
|
404
|
-
const builderRead = await readBuilderCmsContentEntries({
|
|
405
|
-
model: source.sourceTable,
|
|
406
|
-
fieldPaths: [field.sourceFieldKey],
|
|
407
|
-
limit: Math.max(
|
|
408
|
-
BUILDER_FIELD_REFRESH_MINIMUM_LIMIT,
|
|
409
|
-
initialSourceRows.length,
|
|
410
|
-
),
|
|
411
|
-
offset: 0,
|
|
412
|
-
});
|
|
413
|
-
if (builderRead.state !== "live") {
|
|
414
|
-
throw new Error(
|
|
415
|
-
builderRead.message ??
|
|
416
|
-
"Builder CMS could not refresh this field. No property was created.",
|
|
417
|
-
);
|
|
418
|
-
}
|
|
419
|
-
if (initialSourceRows.length > 0 && builderRead.entries.length === 0) {
|
|
420
|
-
throw new Error(
|
|
421
|
-
"Builder CMS returned no entries for this source. No property was created; refresh the source and try again.",
|
|
422
|
-
);
|
|
423
|
-
}
|
|
424
|
-
builderEntries = builderRead.entries;
|
|
425
|
-
}
|
|
426
|
-
|
|
427
419
|
// Keep the local snapshot, property definition, mapping, and materialized
|
|
428
|
-
// values atomic.
|
|
429
|
-
//
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
.
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
throw new Error("
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
)
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
420
|
+
// values atomic. The common complete-snapshot path needs one projected row
|
|
421
|
+
// read. If values are missing, that transaction exits before writes, the
|
|
422
|
+
// provider read happens outside it, and a second transaction re-reads the
|
|
423
|
+
// rows so concurrent source refreshes cannot be overwritten.
|
|
424
|
+
const materializeProperty = (
|
|
425
|
+
builderEntries: BuilderCmsSourceEntry[] | null,
|
|
426
|
+
) =>
|
|
427
|
+
db.transaction(async (tx) => {
|
|
428
|
+
const now = new Date().toISOString();
|
|
429
|
+
const visibility = normalizePropertyVisibility(undefined);
|
|
430
|
+
const propertyId = nanoid();
|
|
431
|
+
const [currentField] = await tx
|
|
432
|
+
.select()
|
|
433
|
+
.from(schema.contentDatabaseSourceFields)
|
|
434
|
+
.where(eq(schema.contentDatabaseSourceFields.id, field.id));
|
|
435
|
+
if (!currentField) throw new Error("Source field not found.");
|
|
436
|
+
if (currentField.propertyId) {
|
|
437
|
+
throw new Error("Source field is already mapped to a property.");
|
|
438
|
+
}
|
|
439
|
+
if (currentField.mappingType === "title") {
|
|
440
|
+
throw new Error("The title source field is already mapped to Name.");
|
|
441
|
+
}
|
|
442
|
+
if (currentField.sourceId !== source.id) {
|
|
443
|
+
throw new Error("Source field does not belong to this database.");
|
|
444
|
+
}
|
|
445
|
+
const [currentSource] = await tx
|
|
446
|
+
.select()
|
|
447
|
+
.from(schema.contentDatabaseSources)
|
|
448
|
+
.where(
|
|
449
|
+
and(
|
|
450
|
+
eq(schema.contentDatabaseSources.id, source.id),
|
|
451
|
+
eq(schema.contentDatabaseSources.databaseId, database.id),
|
|
452
|
+
),
|
|
453
|
+
);
|
|
454
|
+
if (!currentSource) {
|
|
455
|
+
throw new Error("Source field does not belong to this database.");
|
|
456
|
+
}
|
|
460
457
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
458
|
+
let sourceRows: Array<{
|
|
459
|
+
databaseItemId: string;
|
|
460
|
+
documentId: string;
|
|
461
|
+
sourceValuesJson: string | null;
|
|
462
|
+
}> = [];
|
|
463
|
+
if (builderEntries) {
|
|
464
|
+
const currentSourceRows = await tx
|
|
464
465
|
.select()
|
|
465
466
|
.from(schema.contentDatabaseSourceRows)
|
|
466
467
|
.where(eq(schema.contentDatabaseSourceRows.sourceId, source.id));
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
);
|
|
479
|
-
}
|
|
480
|
-
for (let index = 0; index < merged.rows.length; index += 1) {
|
|
481
|
-
const currentRow = sourceRows[index];
|
|
482
|
-
const mergedRow = merged.rows[index];
|
|
483
|
-
if (
|
|
484
|
-
!currentRow ||
|
|
485
|
-
!mergedRow ||
|
|
486
|
-
currentRow.sourceValuesJson === mergedRow.sourceValuesJson
|
|
487
|
-
) {
|
|
488
|
-
continue;
|
|
468
|
+
const merged = mergeBuilderFieldIntoSourceRows({
|
|
469
|
+
rows: currentSourceRows,
|
|
470
|
+
entries: builderEntries,
|
|
471
|
+
sourceTable: source.sourceTable,
|
|
472
|
+
sourceFieldKey: currentField.sourceFieldKey,
|
|
473
|
+
now,
|
|
474
|
+
});
|
|
475
|
+
if (merged.matchedMissingRows < merged.missingRows) {
|
|
476
|
+
throw new Error(
|
|
477
|
+
"Builder CMS did not return entries for every stored source row. No property was created; refresh the source and try again.",
|
|
478
|
+
);
|
|
489
479
|
}
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
.
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
480
|
+
for (let index = 0; index < merged.rows.length; index += 1) {
|
|
481
|
+
const currentRow = currentSourceRows[index];
|
|
482
|
+
const mergedRow = merged.rows[index];
|
|
483
|
+
if (
|
|
484
|
+
!currentRow ||
|
|
485
|
+
!mergedRow ||
|
|
486
|
+
currentRow.sourceValuesJson === mergedRow.sourceValuesJson
|
|
487
|
+
) {
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
const [updatedRow] = await tx
|
|
491
|
+
.update(schema.contentDatabaseSourceRows)
|
|
492
|
+
.set({
|
|
493
|
+
sourceValuesJson: mergedRow.sourceValuesJson,
|
|
494
|
+
updatedAt: now,
|
|
495
|
+
})
|
|
496
|
+
.where(
|
|
497
|
+
and(
|
|
498
|
+
eq(schema.contentDatabaseSourceRows.id, currentRow.id),
|
|
499
|
+
eq(
|
|
500
|
+
schema.contentDatabaseSourceRows.sourceValuesJson,
|
|
501
|
+
currentRow.sourceValuesJson,
|
|
502
|
+
),
|
|
502
503
|
),
|
|
504
|
+
)
|
|
505
|
+
.returning({ id: schema.contentDatabaseSourceRows.id });
|
|
506
|
+
if (!updatedRow) {
|
|
507
|
+
throw new Error(
|
|
508
|
+
"The Builder source changed while adding this property. No property was created; try again.",
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
sourceRows = merged.rows;
|
|
513
|
+
} else if (!isSecondary) {
|
|
514
|
+
const projectedSourceRows = await tx
|
|
515
|
+
.select({
|
|
516
|
+
databaseItemId: schema.contentDatabaseSourceRows.databaseItemId,
|
|
517
|
+
documentId: schema.contentDatabaseSourceRows.documentId,
|
|
518
|
+
sourceFieldValueJson: sourceFieldValueJsonProjection(
|
|
519
|
+
currentField.sourceFieldKey,
|
|
503
520
|
),
|
|
504
|
-
)
|
|
505
|
-
.
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
521
|
+
})
|
|
522
|
+
.from(schema.contentDatabaseSourceRows)
|
|
523
|
+
.where(eq(schema.contentDatabaseSourceRows.sourceId, source.id));
|
|
524
|
+
if (
|
|
525
|
+
source.sourceType === "builder-cms" &&
|
|
526
|
+
projectedSourceRows.some((row) => row.sourceFieldValueJson === null)
|
|
527
|
+
) {
|
|
528
|
+
throw new MissingBuilderFieldValuesError(
|
|
529
|
+
projectedSourceRows.length,
|
|
509
530
|
);
|
|
510
531
|
}
|
|
532
|
+
sourceRows = projectedSourceRows.flatMap((row) =>
|
|
533
|
+
row.sourceFieldValueJson === null
|
|
534
|
+
? []
|
|
535
|
+
: [
|
|
536
|
+
{
|
|
537
|
+
databaseItemId: row.databaseItemId,
|
|
538
|
+
documentId: row.documentId,
|
|
539
|
+
sourceValuesJson: compactSourceValuesJson(
|
|
540
|
+
currentField.sourceFieldKey,
|
|
541
|
+
row.sourceFieldValueJson,
|
|
542
|
+
),
|
|
543
|
+
},
|
|
544
|
+
],
|
|
545
|
+
);
|
|
511
546
|
}
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
"The Builder source changed while adding this property. No property was created; try again.",
|
|
547
|
+
|
|
548
|
+
const builderMetadata = builderMetadataForSourceField({
|
|
549
|
+
sourceFieldKey: currentField.sourceFieldKey,
|
|
550
|
+
sourceMetadataJson: currentSource.metadataJson,
|
|
551
|
+
});
|
|
552
|
+
const type = propertyTypeForSourceField(
|
|
553
|
+
currentField.sourceFieldType,
|
|
554
|
+
builderMetadata,
|
|
521
555
|
);
|
|
522
|
-
|
|
556
|
+
const options = sourceFieldPropertyOptions({
|
|
557
|
+
type,
|
|
558
|
+
metadata: builderMetadata,
|
|
559
|
+
rows: sourceRows,
|
|
560
|
+
sourceFieldKey: currentField.sourceFieldKey,
|
|
561
|
+
});
|
|
562
|
+
const [maxPos] = await tx
|
|
563
|
+
.select({
|
|
564
|
+
max: sql<number>`COALESCE(MAX(position), -1)`,
|
|
565
|
+
})
|
|
566
|
+
.from(schema.documentPropertyDefinitions)
|
|
567
|
+
.where(
|
|
568
|
+
and(
|
|
569
|
+
eq(
|
|
570
|
+
schema.documentPropertyDefinitions.ownerEmail,
|
|
571
|
+
database.ownerEmail,
|
|
572
|
+
),
|
|
573
|
+
eq(schema.documentPropertyDefinitions.databaseId, database.id),
|
|
574
|
+
),
|
|
575
|
+
);
|
|
576
|
+
const position = (maxPos?.max ?? -1) + 1;
|
|
577
|
+
|
|
578
|
+
await tx.insert(schema.documentPropertyDefinitions).values({
|
|
579
|
+
id: propertyId,
|
|
580
|
+
ownerEmail: database.ownerEmail,
|
|
581
|
+
orgId: database.orgId ?? null,
|
|
582
|
+
databaseId: database.id,
|
|
583
|
+
name: currentField.sourceFieldLabel,
|
|
584
|
+
type,
|
|
585
|
+
visibility,
|
|
586
|
+
optionsJson: serializePropertyOptions(options),
|
|
587
|
+
position,
|
|
588
|
+
createdAt: now,
|
|
589
|
+
updatedAt: now,
|
|
590
|
+
});
|
|
523
591
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
sourceFieldKey: currentField.sourceFieldKey,
|
|
537
|
-
});
|
|
538
|
-
const [maxPos] = await tx
|
|
539
|
-
.select({
|
|
540
|
-
max: sql<number>`COALESCE(MAX(position), -1)`,
|
|
541
|
-
})
|
|
542
|
-
.from(schema.documentPropertyDefinitions)
|
|
543
|
-
.where(
|
|
544
|
-
and(
|
|
545
|
-
eq(
|
|
546
|
-
schema.documentPropertyDefinitions.ownerEmail,
|
|
547
|
-
database.ownerEmail,
|
|
592
|
+
const [mappedField] = await tx
|
|
593
|
+
.update(schema.contentDatabaseSourceFields)
|
|
594
|
+
.set({
|
|
595
|
+
propertyId,
|
|
596
|
+
localFieldKey: propertyId,
|
|
597
|
+
mappingType: "property",
|
|
598
|
+
updatedAt: now,
|
|
599
|
+
})
|
|
600
|
+
.where(
|
|
601
|
+
and(
|
|
602
|
+
eq(schema.contentDatabaseSourceFields.id, currentField.id),
|
|
603
|
+
isNull(schema.contentDatabaseSourceFields.propertyId),
|
|
548
604
|
),
|
|
549
|
-
|
|
550
|
-
)
|
|
551
|
-
)
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
await tx.insert(schema.documentPropertyDefinitions).values({
|
|
555
|
-
id: propertyId,
|
|
556
|
-
ownerEmail: database.ownerEmail,
|
|
557
|
-
orgId: database.orgId ?? null,
|
|
558
|
-
databaseId: database.id,
|
|
559
|
-
name: currentField.sourceFieldLabel,
|
|
560
|
-
type,
|
|
561
|
-
visibility,
|
|
562
|
-
optionsJson: serializePropertyOptions(options),
|
|
563
|
-
position,
|
|
564
|
-
createdAt: now,
|
|
565
|
-
updatedAt: now,
|
|
566
|
-
});
|
|
605
|
+
)
|
|
606
|
+
.returning({ id: schema.contentDatabaseSourceFields.id });
|
|
607
|
+
if (!mappedField) {
|
|
608
|
+
throw new Error("Source field is already mapped to a property.");
|
|
609
|
+
}
|
|
567
610
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
localFieldKey: propertyId,
|
|
573
|
-
mappingType: "property",
|
|
574
|
-
updatedAt: now,
|
|
575
|
-
})
|
|
576
|
-
.where(
|
|
577
|
-
and(
|
|
578
|
-
eq(schema.contentDatabaseSourceFields.id, currentField.id),
|
|
579
|
-
isNull(schema.contentDatabaseSourceFields.propertyId),
|
|
580
|
-
),
|
|
581
|
-
)
|
|
582
|
-
.returning({ id: schema.contentDatabaseSourceFields.id });
|
|
583
|
-
if (!mappedField) {
|
|
584
|
-
throw new Error("Source field is already mapped to a property.");
|
|
585
|
-
}
|
|
611
|
+
await tx
|
|
612
|
+
.update(schema.contentDatabaseSources)
|
|
613
|
+
.set({ updatedAt: now })
|
|
614
|
+
.where(eq(schema.contentDatabaseSources.id, currentSource.id));
|
|
586
615
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
616
|
+
const itemValues = sourceFieldPropertyValuesFromRows(
|
|
617
|
+
sourceRows,
|
|
618
|
+
currentField.sourceFieldKey,
|
|
619
|
+
type,
|
|
620
|
+
options,
|
|
621
|
+
);
|
|
622
|
+
if (itemValues.length > 0) {
|
|
623
|
+
for (const chunk of chunks(itemValues, 200)) {
|
|
624
|
+
await tx.insert(schema.documentPropertyValues).values(
|
|
625
|
+
chunk.map((row) => ({
|
|
626
|
+
id: nanoid(),
|
|
627
|
+
ownerEmail: database.ownerEmail,
|
|
628
|
+
documentId: row.documentId,
|
|
629
|
+
propertyId,
|
|
630
|
+
valueJson: serializePropertyValue(row.value),
|
|
631
|
+
createdAt: now,
|
|
632
|
+
updatedAt: now,
|
|
633
|
+
})),
|
|
634
|
+
);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
591
637
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
638
|
+
const sourceField = serializeSourceField(
|
|
639
|
+
{
|
|
640
|
+
...currentField,
|
|
641
|
+
propertyId,
|
|
642
|
+
localFieldKey: propertyId,
|
|
643
|
+
mappingType: "property",
|
|
644
|
+
updatedAt: now,
|
|
645
|
+
},
|
|
646
|
+
currentField.sourceFieldLabel,
|
|
647
|
+
);
|
|
648
|
+
|
|
649
|
+
return {
|
|
650
|
+
databaseId: database.id,
|
|
651
|
+
documentId: database.documentId,
|
|
652
|
+
property: {
|
|
653
|
+
definition: {
|
|
654
|
+
id: propertyId,
|
|
655
|
+
databaseId: database.id,
|
|
656
|
+
name: currentField.sourceFieldLabel,
|
|
657
|
+
type,
|
|
658
|
+
visibility,
|
|
659
|
+
options,
|
|
660
|
+
position,
|
|
607
661
|
createdAt: now,
|
|
608
662
|
updatedAt: now,
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
663
|
+
},
|
|
664
|
+
value: null,
|
|
665
|
+
editable: true,
|
|
666
|
+
},
|
|
667
|
+
sourceField,
|
|
668
|
+
itemValues,
|
|
669
|
+
};
|
|
670
|
+
});
|
|
613
671
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
localFieldKey: propertyId,
|
|
619
|
-
mappingType: "property",
|
|
620
|
-
updatedAt: now,
|
|
621
|
-
},
|
|
622
|
-
currentField.sourceFieldLabel,
|
|
623
|
-
);
|
|
672
|
+
try {
|
|
673
|
+
return await materializeProperty(null);
|
|
674
|
+
} catch (error) {
|
|
675
|
+
if (!(error instanceof MissingBuilderFieldValuesError)) throw error;
|
|
624
676
|
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
});
|
|
677
|
+
// Read before retrying the transaction so a Builder outage cannot leave
|
|
678
|
+
// behind a cleanly reported but empty property.
|
|
679
|
+
const builderRead = await readBuilderCmsContentEntries({
|
|
680
|
+
model: source.sourceTable,
|
|
681
|
+
fieldPaths: [field.sourceFieldKey],
|
|
682
|
+
limit: Math.max(BUILDER_FIELD_REFRESH_MINIMUM_LIMIT, error.rowCount),
|
|
683
|
+
offset: 0,
|
|
684
|
+
});
|
|
685
|
+
if (builderRead.state !== "live") {
|
|
686
|
+
throw new Error(
|
|
687
|
+
builderRead.message ??
|
|
688
|
+
"Builder CMS could not refresh this field. No property was created.",
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
if (error.rowCount > 0 && builderRead.entries.length === 0) {
|
|
692
|
+
throw new Error(
|
|
693
|
+
"Builder CMS returned no entries for this source. No property was created; refresh the source and try again.",
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
return await materializeProperty(builderRead.entries);
|
|
697
|
+
}
|
|
647
698
|
},
|
|
648
699
|
});
|
|
@@ -213,8 +213,22 @@ export default defineAction({
|
|
|
213
213
|
// SQLite writer briefly blocks this best-effort refresh hint.
|
|
214
214
|
});
|
|
215
215
|
|
|
216
|
+
const response = await getContentDatabaseResponse(databaseId, {
|
|
217
|
+
limit: 100,
|
|
218
|
+
offset: 0,
|
|
219
|
+
});
|
|
220
|
+
const createdItem =
|
|
221
|
+
response.items.find((item) => item.id === itemId) ??
|
|
222
|
+
(
|
|
223
|
+
await getContentDatabaseResponse(databaseId, {
|
|
224
|
+
limit: 1,
|
|
225
|
+
offset: 0,
|
|
226
|
+
documentIds: [documentId],
|
|
227
|
+
})
|
|
228
|
+
).items.find((item) => item.id === itemId);
|
|
216
229
|
return {
|
|
217
|
-
...
|
|
230
|
+
...response,
|
|
231
|
+
createdItem,
|
|
218
232
|
createdItemId: itemId,
|
|
219
233
|
createdDocumentId: documentId,
|
|
220
234
|
createdDocumentUpdatedAt: now,
|