@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.
Files changed (58) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +6 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/client/use-action.ts +6 -0
  5. package/corpus/templates/content/actions/_batch-utils.ts +33 -0
  6. package/corpus/templates/content/actions/_builder-cms-read-client.ts +173 -81
  7. package/corpus/templates/content/actions/_content-files.ts +31 -21
  8. package/corpus/templates/content/actions/_database-source-utils.ts +653 -155
  9. package/corpus/templates/content/actions/_database-utils.ts +535 -41
  10. package/corpus/templates/content/actions/add-content-database-source-field-property.ts +291 -240
  11. package/corpus/templates/content/actions/add-database-item.ts +15 -1
  12. package/corpus/templates/content/actions/attach-content-database-source.ts +156 -58
  13. package/corpus/templates/content/actions/bind-content-database-source-field.ts +2 -2
  14. package/corpus/templates/content/actions/cancel-prepared-builder-source-update.ts +4 -1
  15. package/corpus/templates/content/actions/delete-database-items.ts +8 -2
  16. package/corpus/templates/content/actions/disconnect-content-database-source.ts +2 -2
  17. package/corpus/templates/content/actions/duplicate-database-item.ts +15 -1
  18. package/corpus/templates/content/actions/duplicate-database-items.ts +19 -1
  19. package/corpus/templates/content/actions/execute-builder-source-execution.ts +2 -1
  20. package/corpus/templates/content/actions/get-content-database.ts +12 -64
  21. package/corpus/templates/content/actions/move-database-item.ts +4 -1
  22. package/corpus/templates/content/actions/prepare-builder-source-execution.ts +1 -1
  23. package/corpus/templates/content/actions/prepare-builder-source-review.ts +4 -8
  24. package/corpus/templates/content/actions/preview-content-database-source-attach.ts +92 -0
  25. package/corpus/templates/content/actions/process-builder-body-hydration.ts +2 -1
  26. package/corpus/templates/content/actions/query-content-database-items.ts +64 -0
  27. package/corpus/templates/content/actions/refresh-content-database-source.ts +7 -0
  28. package/corpus/templates/content/actions/review-content-database-source-change-set.ts +1 -1
  29. package/corpus/templates/content/actions/set-content-database-source-write-mode.ts +1 -1
  30. package/corpus/templates/content/actions/stage-builder-revision.ts +1 -1
  31. package/corpus/templates/content/actions/update-content-database-personal-view.ts +29 -3
  32. package/corpus/templates/content/actions/update-content-database-view.ts +1 -1
  33. package/corpus/templates/content/actions/validate-builder-source-execution.ts +1 -1
  34. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +3 -22
  35. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +267 -105
  36. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +82 -1
  37. package/corpus/templates/content/app/hooks/use-content-database.ts +285 -24
  38. package/corpus/templates/content/app/hooks/use-document-properties.ts +7 -6
  39. package/corpus/templates/content/app/i18n-data.ts +10 -0
  40. package/corpus/templates/content/changelog/2026-07-29-large-databases-keep-useful-rows-visible.md +6 -0
  41. package/corpus/templates/content/changelog/2026-07-30-builder-source-columns-now-appear-immediately-when-connected.md +6 -0
  42. package/corpus/templates/content/changelog/2026-07-30-large-builder-backed-tables-show-useful-rows-sooner-and-fini.md +6 -0
  43. package/corpus/templates/content/changelog/2026-08-01-large-builder-databases-now-show-rows-immediately-and-finish.md +6 -0
  44. package/corpus/templates/content/parity/matrix.md +2 -1
  45. package/corpus/templates/content/parity/matrix.ts +25 -0
  46. package/corpus/templates/content/shared/api.ts +54 -4
  47. package/corpus/templates/content/shared/database-query.ts +359 -0
  48. package/dist/client/use-action.d.ts.map +1 -1
  49. package/dist/client/use-action.js +6 -0
  50. package/dist/client/use-action.js.map +1 -1
  51. package/dist/notifications/routes.d.ts +1 -1
  52. package/dist/observability/routes.d.ts +3 -3
  53. package/dist/progress/routes.d.ts +1 -1
  54. package/dist/resources/handlers.d.ts +1 -1
  55. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  56. package/dist/server/transcribe-voice.d.ts +1 -1
  57. package/package.json +1 -1
  58. 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. Re-read rows after the provider request so a concurrent
429
- // source refresh cannot be overwritten by the older pre-request snapshot.
430
- return await db.transaction(async (tx) => {
431
- const now = new Date().toISOString();
432
- const visibility = normalizePropertyVisibility(undefined);
433
- const propertyId = nanoid();
434
- const [currentField] = await tx
435
- .select()
436
- .from(schema.contentDatabaseSourceFields)
437
- .where(eq(schema.contentDatabaseSourceFields.id, field.id));
438
- if (!currentField) throw new Error("Source field not found.");
439
- if (currentField.propertyId) {
440
- throw new Error("Source field is already mapped to a property.");
441
- }
442
- if (currentField.mappingType === "title") {
443
- throw new Error("The title source field is already mapped to Name.");
444
- }
445
- if (currentField.sourceId !== source.id) {
446
- throw new Error("Source field does not belong to this database.");
447
- }
448
- const [currentSource] = await tx
449
- .select()
450
- .from(schema.contentDatabaseSources)
451
- .where(
452
- and(
453
- eq(schema.contentDatabaseSources.id, source.id),
454
- eq(schema.contentDatabaseSources.databaseId, database.id),
455
- ),
456
- );
457
- if (!currentSource) {
458
- throw new Error("Source field does not belong to this database.");
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
- let sourceRows = isSecondary
462
- ? []
463
- : await tx
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
- if (builderEntries) {
468
- const merged = mergeBuilderFieldIntoSourceRows({
469
- rows: sourceRows,
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
- );
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
- 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,
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
- .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.",
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
- sourceRows = merged.rows;
513
- } else if (
514
- source.sourceType === "builder-cms" &&
515
- sourceRows.some(
516
- (row) => !hasSourceFieldValue(row, currentField.sourceFieldKey),
517
- )
518
- ) {
519
- throw new Error(
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
- const builderMetadata = builderMetadataForSourceField({
525
- sourceFieldKey: currentField.sourceFieldKey,
526
- sourceMetadataJson: currentSource.metadataJson,
527
- });
528
- const type = propertyTypeForSourceField(
529
- currentField.sourceFieldType,
530
- builderMetadata,
531
- );
532
- const options = sourceFieldPropertyOptions({
533
- type,
534
- metadata: builderMetadata,
535
- rows: sourceRows,
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
- eq(schema.documentPropertyDefinitions.databaseId, database.id),
550
- ),
551
- );
552
- const position = (maxPos?.max ?? -1) + 1;
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
- const [mappedField] = await tx
569
- .update(schema.contentDatabaseSourceFields)
570
- .set({
571
- propertyId,
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
- await tx
588
- .update(schema.contentDatabaseSources)
589
- .set({ updatedAt: now })
590
- .where(eq(schema.contentDatabaseSources.id, currentSource.id));
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
- const itemValues = sourceFieldPropertyValuesFromRows(
593
- sourceRows,
594
- currentField.sourceFieldKey,
595
- type,
596
- options,
597
- );
598
- if (itemValues.length > 0) {
599
- for (const chunk of chunks(itemValues, 200)) {
600
- await tx.insert(schema.documentPropertyValues).values(
601
- chunk.map((row) => ({
602
- id: nanoid(),
603
- ownerEmail: database.ownerEmail,
604
- documentId: row.documentId,
605
- propertyId,
606
- valueJson: serializePropertyValue(row.value),
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
- const sourceField = serializeSourceField(
615
- {
616
- ...currentField,
617
- propertyId,
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
- return {
626
- databaseId: database.id,
627
- documentId: database.documentId,
628
- property: {
629
- definition: {
630
- id: propertyId,
631
- databaseId: database.id,
632
- name: currentField.sourceFieldLabel,
633
- type,
634
- visibility,
635
- options,
636
- position,
637
- createdAt: now,
638
- updatedAt: now,
639
- },
640
- value: null,
641
- editable: true,
642
- },
643
- sourceField,
644
- itemValues,
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
- ...(await getContentDatabaseResponse(databaseId)),
230
+ ...response,
231
+ createdItem,
218
232
  createdItemId: itemId,
219
233
  createdDocumentId: documentId,
220
234
  createdDocumentUpdatedAt: now,