@ai-matrx/records 0.42.0 → 0.44.1

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.
@@ -67,10 +67,12 @@ __export(react_exports, {
67
67
  choiceValuesOf: () => choiceValuesOf,
68
68
  choicesOf: () => choicesOf,
69
69
  err: () => err,
70
+ forgetAllOps: () => forgetAllOps,
70
71
  highestLevel: () => highestLevel,
71
72
  holdsARetiredChoice: () => holdsARetiredChoice,
72
73
  indexedDbStore: () => indexedDbStore,
73
74
  isDecided: () => isDecided,
75
+ isOwnOp: () => isOwnOp,
74
76
  isRecordsErr: () => isRecordsErr,
75
77
  isTheChosen: () => isTheChosen,
76
78
  looksLikeAnId: () => looksLikeAnId,
@@ -78,12 +80,14 @@ __export(react_exports, {
78
80
  measureKey: () => measureKey,
79
81
  memoryStore: () => memoryStore,
80
82
  mintClientKey: () => mintClientKey,
83
+ mintOpId: () => mintOpId,
81
84
  ok: () => ok,
82
85
  openCaptureQueue: () => openCaptureQueue,
83
86
  optionKey: () => optionKey,
84
87
  optionLabel: () => optionLabel,
85
88
  portalPath: () => portalPath,
86
89
  publicFormPath: () => publicFormPath,
90
+ rememberedOpCount: () => rememberedOpCount,
87
91
  scopeChip: () => scopeChip,
88
92
  tableShapeChanged: () => tableShapeChanged,
89
93
  useEntityFieldMutation: () => useEntityFieldMutation,
@@ -110,6 +114,45 @@ module.exports = __toCommonJS(react_exports);
110
114
  // src/react/context.tsx
111
115
  var import_react = require("react");
112
116
 
117
+ // src/ops.ts
118
+ var REMEMBER_MS = 3e4;
119
+ var REMEMBER_MAX = 500;
120
+ var ours = /* @__PURE__ */ new Map();
121
+ function trim(now) {
122
+ for (const [id, at] of ours) {
123
+ if (now - at <= REMEMBER_MS) break;
124
+ ours.delete(id);
125
+ }
126
+ while (ours.size > REMEMBER_MAX) {
127
+ const oldest = ours.keys().next();
128
+ if (oldest.done === true) break;
129
+ ours.delete(oldest.value);
130
+ }
131
+ }
132
+ function mintOpId() {
133
+ const id = crypto.randomUUID();
134
+ const now = Date.now();
135
+ ours.set(id, now);
136
+ trim(now);
137
+ return id;
138
+ }
139
+ function isOwnOp(opId) {
140
+ if (!opId) return false;
141
+ const at = ours.get(opId);
142
+ if (at === void 0) return false;
143
+ if (Date.now() - at > REMEMBER_MS) {
144
+ ours.delete(opId);
145
+ return false;
146
+ }
147
+ return true;
148
+ }
149
+ function forgetAllOps() {
150
+ ours.clear();
151
+ }
152
+ function rememberedOpCount() {
153
+ return ours.size;
154
+ }
155
+
113
156
  // src/level.ts
114
157
  var PERMISSION_LEVELS = [
115
158
  "viewer",
@@ -587,6 +630,7 @@ var STORE_DOORS = [
587
630
  { name: "read_paths_outside_the_door", args: "", returns: "TABLE(object_name text, why text)", security: "invoker" },
588
631
  { name: "read_record", args: "p_organization_id uuid, p_record_id uuid, p_by_id boolean DEFAULT false", returns: "jsonb", security: "definer" },
589
632
  { name: "read_records", args: "p_organization_id uuid, p_table_id uuid, p_by_id boolean DEFAULT false, p_limit integer DEFAULT 200, p_offset integer DEFAULT 0", returns: "TABLE(id uuid, document jsonb, level permission_level)", security: "definer" },
633
+ { name: "read_records_by_ids", args: "p_organization_id uuid, p_table_id uuid, p_record_ids uuid[], p_by_id boolean DEFAULT false", returns: "TABLE(id uuid, document jsonb, level permission_level)", security: "definer" },
590
634
  { name: "realtime_topic_admits", args: "p_topic text", returns: "boolean", security: "definer" },
591
635
  { name: "record_aggregate", args: "p_organization_id uuid, p_table_id uuid, p_group_by jsonb DEFAULT '[]'::jsonb, p_measures jsonb DEFAULT '[]'::jsonb, p_bucket jsonb DEFAULT NULL::jsonb, p_filter jsonb DEFAULT '{}'::jsonb, p_limit integer DEFAULT 200, p_required text DEFAULT 'viewer'::text", returns: "TABLE(groups jsonb, measures jsonb, row_count bigint)", security: "definer" },
592
636
  { name: "record_applicability", args: "p_organization_id uuid, p_record_id uuid", returns: "TABLE(rule_id uuid, rule_name text, rule_version integer, applies boolean, answer jsonb)", security: "invoker" },
@@ -611,6 +655,7 @@ var STORE_DOORS = [
611
655
  { name: "record_values", args: "p_organization_id uuid, p_record_id uuid", returns: "jsonb", security: "invoker" },
612
656
  { name: "record_values_of", args: "p_row custom.record", returns: "jsonb", security: "invoker" },
613
657
  { name: "record_values_versioned", args: "p_organization_id uuid, p_record_id uuid", returns: "TABLE(field_key text, field_id uuid, value jsonb, value_version integer, source jsonb, absent_reason text, actor text, on_behalf_of text, written_at timestamp with time zone, alternates jsonb)", security: "definer" },
658
+ { name: "record_words", args: "p_organization_id uuid, p_record_id uuid, p_noun text DEFAULT NULL::text", returns: "text", security: "definer" },
614
659
  { name: "record_write", args: "p_organization_id uuid, p_table_id uuid, p_data jsonb", returns: "uuid", security: "definer" },
615
660
  { name: "record_write_many", args: "p_organization_id uuid, p_table_id uuid, p_rows jsonb[], p_ids uuid[] DEFAULT NULL::uuid[]", returns: "uuid[]", security: "definer" },
616
661
  { name: "refusals_claiming_a_level_never_asked", args: "", returns: "TABLE(function_name text, identity_args text, why text)", security: "invoker" },
@@ -622,6 +667,8 @@ var STORE_DOORS = [
622
667
  { name: "relation_target_ours", args: "p_entity_token text, p_row_id uuid", returns: "jsonb", security: "definer" },
623
668
  { name: "relation_targets", args: "p_organization_id uuid, p_record_id uuid, p_via_key text", returns: "SETOF uuid", security: "definer" },
624
669
  { name: "relation_uncarry", args: "p_organization_id uuid, p_container_id uuid, p_item_id uuid", returns: "integer", security: "definer" },
670
+ { name: "relation_words", args: "p_organization_id uuid, p_field_id uuid, p_value text", returns: "text", security: "definer" },
671
+ { name: "relation_words_many", args: "p_organization_id uuid, p_field_id uuid, p_record_ids uuid[]", returns: "TABLE(record_id uuid, words text)", security: "definer" },
625
672
  { name: "reopen_declared_doors", args: "", returns: "TABLE(reopened text)", security: "definer" },
626
673
  { name: "required_epoch", args: "p_container_type text, p_container_id uuid, p_item_type text, p_item_id uuid", returns: "bigint", security: "definer" },
627
674
  { name: "resolve_first_match", args: "p_organization_id uuid, p_record_id uuid", returns: "jsonb", security: "invoker" },
@@ -811,18 +858,18 @@ var STORE_KNOBS = [
811
858
  { key: "world_publish_enabled", value: "false", type: "boolean" }
812
859
  ];
813
860
  var STORE_REFUSAL_CODES = [
814
- { code: "02000", raised_by: "_checklist_instantiate,checklist_declare,checklist_run,checklist_step_complete,checklist_step_refusal,checklist_template_shape,conversation_scope_bind,dashboard_delete,dashboard_run,doc_letterhead,doc_render_body,doc_render_document,doc_render_read,doc_sign,doc_signature_intact,doc_signature_read,doc_template_delete,doc_template_read,doc_template_save,enrich_pin,entity_field_retire,entity_field_update,entity_record_read,entity_value_write,external_history_event,external_rows,external_stub_upsert,external_writes_set,io_restore,migrate_delete,migrate_extract_parent,migrate_merge,migrate_reclass,migrate_rename,migrate_reparent,migrate_retype,migrate_split,migrate_undo,organization_clear,portal_card,portal_declare,portal_invite,portal_preview,portal_principal_bind,portal_revoke,read_record,record_delete,record_restore,record_restore_preview,record_table,record_update,relation_uncarry,share_access,share_grant,share_lane_set,share_people,share_revoke,sign_request_cancel,sign_request_create,sign_request_remind,sign_request_unchanged,table_share_outside_accept,table_share_outside_invite,table_share_outside_resend,table_share_outside_revoke,visibility_as_of,work_approval_decide,work_approval_read,work_approval_request,work_assign,work_record_states,work_set_state" },
861
+ { code: "02000", raised_by: "_checklist_instantiate,_table_share_invite_payload,checklist_declare,checklist_run,checklist_step_complete,checklist_step_refusal,checklist_template_shape,conversation_scope_bind,dashboard_delete,dashboard_run,doc_letterhead,doc_render_body,doc_render_document,doc_render_read,doc_sign,doc_signature_intact,doc_signature_read,doc_template_delete,doc_template_read,doc_template_save,enrich_pin,entity_field_retire,entity_field_update,entity_record_read,entity_value_write,external_history_event,external_rows,external_stub_upsert,external_writes_set,io_restore,migrate_delete,migrate_extract_parent,migrate_merge,migrate_reclass,migrate_rename,migrate_reparent,migrate_retype,migrate_split,migrate_undo,organization_clear,portal_card,portal_declare,portal_invite,portal_preview,portal_principal_bind,portal_revoke,read_record,record_delete,record_restore,record_restore_preview,record_table,record_update,relation_uncarry,share_access,share_grant,share_lane_set,share_people,share_revoke,sign_request_cancel,sign_request_create,sign_request_remind,sign_request_unchanged,table_share_outside_accept,table_share_outside_invite,table_share_outside_resend,table_share_outside_revoke,visibility_as_of,work_approval_decide,work_approval_read,work_approval_request,work_assign,work_record_states,work_set_state" },
815
862
  { code: "0A000", raised_by: "checklist_step_complete,external_rows,external_source_declare,external_write_through,promote_field,promoted_index_expr,promoted_query_sql,promoted_read,rule_eval,work_assign" },
816
863
  { code: "22003", raised_by: "context_resolve" },
817
864
  { code: "22004", raised_by: "_entity_custom_fields_guard,_value_envelope,actor_word,agg_assert_key,agg_sql,anon_capture,anon_token_issue,anon_write,assert_client_may_reach,booking_declare,capture_submit,checklist_declare,context_resolve,dashboard_block_normalize,dashboard_declare,dashboard_window_sql,doc_render_write,doc_sign,doc_signature_write,doc_template_save,enrich_land,enrich_normalize,external_history_event,external_rows,external_source_declare,external_stub_upsert,external_write_through,external_writes_set,field_history,form_declare,form_submit,home_add,io_comment_write,io_import_begin,io_infer_column,io_outbox_drain,io_restore,migrate_choice_keys,migrate_purge,migrate_reclass,migrate_split,migrate_undo,organization_clear,organization_contents,pipeline_declare,pipeline_gate_preview,portal_declare,portal_invite,portal_principal_bind,provenance_retention_days,provenance_write,query_across_homes,query_by_coordinates,query_rollup,record_delete,record_reparent,record_resolve,record_restore,record_update,record_write,record_write_many,relation_carry,relation_own,rule_declare,share_grant,sign_request_create,sign_request_sign,subscription_declare,table_declare,table_share_outside_invite,value_restore,view_declare,work_approval_decide,work_approval_request,work_template_declare" },
818
865
  { code: "22007", raised_by: "dashboard_window_sql" },
819
866
  { code: "22012", raised_by: "rule_eval" },
820
- { code: "22023", raised_by: "_booking_availability,_entity_custom_fields_guard,_value_envelope,_work_shape_guard,actor_word,agg_assert_key,agg_sql,anon_token_issue,booking_declare,containment_parent,dashboard_block_normalize,dashboard_window_sql,doc_render_body,doc_render_write,doc_signature_write,enrich_declare,enrich_land,enrich_normalize,enrich_pin,entity_value_write,external_history_event,external_source_declare,external_stub_upsert,external_write_through,field_history,io_export,io_import_begin,io_import_finish,io_import_plan,io_import_report,io_proposal_accept,migrate_merge,migrate_reclass,my_levels,organization_clear,page_size,pin_agent_cells,pipeline_move,portal_declare,provenance_write,query_by_coordinates,query_rollup,record_update,record_write_many,relation_carry,rule_eval,rule_members,share_grant,share_lane_set,sign_request_create,sign_request_sign,subscription_declare,table_rules,visibility_as_of,work_approval_request,work_list,work_slot_hold" },
821
- { code: "23503", raised_by: "_booking_availability,_checklist_instantiate,_containment_guard,_field_document_for,_pipeline_stage_key,_record_rule_uses,agg_digest_assemble,anon_publish,anon_rate_take,anon_token_issue,anon_write,assert_organization_wall,booking_cancel,booking_confirm,booking_declare,booking_hold,booking_reschedule,capture_publish,checklist_declare,checklist_steps_table,comment_write,dashboard_declare,delete_rule,doc_sign,doc_template_save,enrich_declare,enrich_due,enrich_land,form_declare,form_submit,home_add,inbound_declare,inbound_mail_land,inbound_set,io_comment_write,io_import_begin,io_import_finish,io_import_report,io_import_rows,io_proposal_accept,migrate_demote,migrate_promote,pipeline_board,pipeline_declare,pipeline_move,pipeline_read,pipeline_transition_refusal,promote_field,promote_table,record_applicability,record_reparent,relation_carry,relation_own,resolve_first_match,rule_applies,rule_declare,rule_eval,rule_members,rule_run,sign_request_create,subscription_mute,subscription_preview,view_declare,work_set_state,work_take_assignment,work_template_instantiate" },
867
+ { code: "22023", raised_by: "_booking_availability,_entity_custom_fields_guard,_take_op_id,_value_envelope,_work_shape_guard,actor_word,agg_assert_key,agg_sql,anon_token_issue,booking_declare,containment_parent,dashboard_block_normalize,dashboard_window_sql,doc_render_body,doc_render_write,doc_signature_write,enrich_declare,enrich_land,enrich_normalize,enrich_pin,entity_value_write,external_history_event,external_source_declare,external_stub_upsert,external_write_through,field_history,io_export,io_import_begin,io_import_finish,io_import_plan,io_import_report,io_proposal_accept,migrate_merge,migrate_reclass,my_levels,organization_clear,page_size,pin_agent_cells,pipeline_move,portal_declare,provenance_write,query_by_coordinates,query_rollup,record_update,record_write_many,relation_carry,rule_eval,rule_members,share_grant,share_lane_set,sign_request_create,sign_request_sign,subscription_declare,table_rules,visibility_as_of,work_approval_request,work_list,work_slot_hold" },
868
+ { code: "23503", raised_by: "_booking_availability,_checklist_instantiate,_containment_guard,_display_spec_for,_field_document_for,_pipeline_stage_key,_record_rule_uses,agg_digest_assemble,anon_publish,anon_rate_take,anon_token_issue,anon_write,assert_organization_wall,booking_cancel,booking_confirm,booking_declare,booking_hold,booking_reschedule,capture_publish,checklist_declare,checklist_steps_table,comment_write,dashboard_declare,delete_rule,doc_sign,doc_template_save,enrich_declare,enrich_due,enrich_land,form_declare,form_submit,home_add,inbound_declare,inbound_mail_land,inbound_set,io_comment_write,io_import_begin,io_import_finish,io_import_report,io_import_rows,io_proposal_accept,migrate_demote,migrate_promote,pipeline_board,pipeline_declare,pipeline_move,pipeline_read,pipeline_transition_refusal,promote_field,promote_table,record_applicability,record_reparent,relation_carry,relation_own,resolve_first_match,rule_applies,rule_declare,rule_eval,rule_members,rule_run,sign_request_create,subscription_mute,subscription_preview,view_declare,work_set_state,work_take_assignment,work_template_instantiate" },
822
869
  { code: "23505", raised_by: "_unique_rule_holds,doc_sign,doc_signature_write,entity_field_declare,field_declare,home_add,inbound_declare,io_proposal_accept,relation_carry,relation_own,share_grant,sign_request_cancel,sign_request_create,table_share_outside_invite,table_share_outside_resend,work_approval_decide,work_slot_hold" },
823
- { code: "23514", raised_by: "_checklist_instantiate,_checklist_step_guard,_containment_guard,_dated_values_guard,_derived_fields,_entity_custom_fields_guard,_field_class_guard,_field_document_for,_field_shape_guard,_field_type_parity_guard,_merge_field_shape_guard,_merge_field_temporal_guard,_promoted_field_cap_guard,_record_rule_uses,_resolve_choice_words,_rule_shape_guard,_rule_topology_guard,_store_relation_edge_names_its_field,_table_columns_word_guard,_table_shape_guard,_undeclared_key_guard,_value_envelope,_work_shape_guard,_workdoors_approval_guard,assert_columns_are_defined,assert_entity_is_organization_scoped,capture_publish,capture_sheet_declare,checklist_declare,checklist_step_complete,doc_sign,doc_signature_write,doc_template_save,entity_field_declare,entity_field_retire,entity_field_update,entity_records_find,entity_table,field_declare,field_retire,field_update,home_add,io_import_rows,migrate_merge,relation_carry,relation_own,sign_request_create,sign_request_sign,table_declare,validate_value_envelope,validate_values,work_set_state,work_slots_declare,work_take_assignment,work_template_instantiate" },
870
+ { code: "23514", raised_by: "_checklist_instantiate,_checklist_step_guard,_containment_guard,_dated_values_guard,_derived_fields,_display_spec_for,_entity_custom_fields_guard,_field_class_guard,_field_document_for,_field_shape_guard,_field_type_parity_guard,_merge_field_shape_guard,_merge_field_temporal_guard,_promoted_field_cap_guard,_record_rule_uses,_resolve_choice_words,_rule_shape_guard,_rule_topology_guard,_store_relation_edge_names_its_field,_table_columns_word_guard,_table_shape_guard,_undeclared_key_guard,_value_envelope,_with_display,_work_shape_guard,_workdoors_approval_guard,assert_columns_are_defined,assert_entity_is_organization_scoped,booking_declare,capture_publish,capture_sheet_declare,checklist_declare,checklist_step_complete,doc_sign,doc_signature_write,doc_template_save,entity_field_declare,entity_field_retire,entity_field_update,entity_records_find,entity_table,field_declare,field_retire,field_update,home_add,io_import_rows,migrate_merge,relation_carry,relation_own,sign_request_create,sign_request_sign,table_declare,validate_value_envelope,validate_values,work_set_state,work_slots_declare,work_take_assignment,work_template_instantiate" },
824
871
  { code: "40001", raised_by: "has_visibility_at" },
825
- { code: "42501", raised_by: "_doc_render_immutable,_doc_signature_immutable,_field_definition_write,_field_write_door,_rule_definition_write,_store_door,anon_capture,anon_inbound_land,anon_publish,anon_token_issue,anon_token_verify,anon_write,assert_client_may_change,assert_client_may_open,assert_client_may_reach,assert_may_know_table,assert_organization_admin,assert_store_door,booking_confirm,booking_declare,booking_hold,capture_submit,checklist_run,comment_write,conversation_scope_bind,conversation_scope_unbind,enrich_declare,enrich_due,enrich_land,enrich_normalize,entity_field_declare,entity_field_retire,entity_field_update,entity_value_write,external_write_through,form_declare,form_submit,inbound_mail_land,io_comment_resolve,io_comment_write,io_import_begin,io_import_finish,io_restore,migrate_purge,migrate_reclass,my_levels,organization_clear,portal_declare,portal_invitation,portal_invite,portal_principal_bind,promote_table,query_visibility_parity,read_record,read_records,record_table,share_grant,subscription_mute,subscription_preview,table_share_outside_accept,table_share_outside_invite,table_share_outside_resend,visibility_as_of,work_approval_decide,work_approval_read,work_approval_request,work_person" },
872
+ { code: "42501", raised_by: "_doc_render_immutable,_doc_signature_immutable,_field_definition_write,_field_write_door,_rule_definition_write,_store_door,anon_capture,anon_inbound_land,anon_publish,anon_token_issue,anon_token_verify,anon_write,assert_client_may_change,assert_client_may_open,assert_client_may_reach,assert_may_know_table,assert_organization_admin,assert_store_door,booking_confirm,booking_declare,booking_hold,capture_submit,checklist_run,comment_write,conversation_scope_bind,conversation_scope_unbind,enrich_declare,enrich_due,enrich_land,enrich_normalize,entity_field_declare,entity_field_retire,entity_field_update,entity_value_write,external_write_through,form_declare,form_submit,inbound_mail_land,io_comment_resolve,io_comment_write,io_import_begin,io_import_finish,io_restore,migrate_purge,migrate_reclass,my_levels,organization_clear,portal_declare,portal_invitation,portal_invite,portal_principal_bind,promote_table,query_visibility_parity,read_record,read_records,read_records_by_ids,record_table,share_grant,subscription_mute,subscription_preview,table_share_outside_accept,table_share_outside_invite,table_share_outside_resend,visibility_as_of,work_approval_decide,work_approval_read,work_approval_request,work_person" },
826
873
  { code: "42703", raised_by: "entity_table,portal_declare" },
827
874
  { code: "53400", raised_by: "anon_rate_take,delete_cascade_closure,enrich_due,enrich_land,promote_field" },
828
875
  { code: "54001", raised_by: "record_delete" },
@@ -1905,6 +1952,13 @@ var DOORS = {
1905
1952
  // this person could see.
1906
1953
  readRecord: "read_record",
1907
1954
  readRecords: "read_records",
1955
+ // THE SAME READ DOOR, ADDRESSED BY AN ID SET. A realtime notice names the exact ids that
1956
+ // moved; before this existed the only door that took them was the PAGE door, so a
1957
+ // three-cell change re-read fifty rows in every watching browser. It is a separate NAME
1958
+ // rather than a sixth argument to `read_records` because adding a defaulted argument to a
1959
+ // live Postgres function makes every existing call ambiguous — `is not unique`. Same
1960
+ // ladder, same level, same masking, same page ceiling.
1961
+ readRecordsByIds: "read_records_by_ids",
1908
1962
  // DOOR-8, the two clocks. `p_recorded_at` is what the store SAID then;
1909
1963
  // `p_world_on` is what was TRUE of the world on that date, read out of dated
1910
1964
  // values. A client that answered either one itself would be guessing.
@@ -1964,6 +2018,18 @@ var DOORS = {
1964
2018
  promotedRead: "promoted_read",
1965
2019
  relationTargets: "relation_targets",
1966
2020
  relationOwn: "relation_own",
2021
+ // REL-DISP, 2026-09-21. WHAT A RELATION CELL READS, through that column's own
2022
+ // display spec. `custom.record_words` is declared server_only — it takes a bare
2023
+ // record id with no field to scope it — so these are how a screen gets the
2024
+ // words, and they are the ONE way in: three separate copies of "read tableList,
2025
+ // find title_field, call rowName" lived in labels.tsx, RelationPicker.tsx and
2026
+ // PortalsPanel.tsx, and none of them could ever have shown two columns joined.
2027
+ // The `_many` door is the batch TAILS-2 recorded as missing ("there is still no
2028
+ // batch door on the store"), which is why the browser resolved labels one id at
2029
+ // a time. The ladder is NOT batched: the store asks it per record inside
2030
+ // `custom._words_for`, so batching buys a round trip and never a disclosure.
2031
+ relationWords: "relation_words",
2032
+ relationWordsMany: "relation_words_many",
1967
2033
  homeAdd: "home_add",
1968
2034
  recordReparent: "record_reparent",
1969
2035
  containmentChain: "containment_chain",
@@ -2481,17 +2547,18 @@ function createRecordsClient(config) {
2481
2547
  async recordWrite({ table_id, data }) {
2482
2548
  return callDoor(
2483
2549
  DOORS.recordWrite,
2484
- { p_organization_id: org, p_table_id: table_id, p_data: data },
2550
+ { p_organization_id: org, p_table_id: table_id, p_data: { ...data, _op_id: mintOpId() } },
2485
2551
  "recordWrite"
2486
2552
  );
2487
2553
  },
2488
2554
  async recordWriteMany({ table_id, rows, ids }) {
2555
+ const opId = mintOpId();
2489
2556
  return callDoor(
2490
2557
  DOORS.recordWriteMany,
2491
2558
  {
2492
2559
  p_organization_id: org,
2493
2560
  p_table_id: table_id,
2494
- p_rows: rows,
2561
+ p_rows: rows.map((row) => ({ ...row, _op_id: opId })),
2495
2562
  ...ids === void 0 ? {} : { p_ids: ids }
2496
2563
  },
2497
2564
  "recordWriteMany"
@@ -2503,7 +2570,7 @@ function createRecordsClient(config) {
2503
2570
  {
2504
2571
  p_organization_id: org,
2505
2572
  p_record_id: record_id,
2506
- p_patch: patch,
2573
+ p_patch: { ...patch, _op_id: mintOpId() },
2507
2574
  ...expectedVersion === void 0 ? {} : { p_expected_version: expectedVersion }
2508
2575
  },
2509
2576
  "recordUpdate"
@@ -2545,6 +2612,21 @@ function createRecordsClient(config) {
2545
2612
  }
2546
2613
  return ok({ record_id, document, hidden, computed, computedUnavailable });
2547
2614
  },
2615
+ async listByIds({ table_id, ids }) {
2616
+ if (ids.length === 0) return ok([]);
2617
+ const door = await callDoor(
2618
+ DOORS.readRecordsByIds,
2619
+ { p_organization_id: org, p_table_id: table_id, p_record_ids: ids },
2620
+ "listByIds"
2621
+ );
2622
+ if (!door.ok) return err(door.error);
2623
+ return ok(
2624
+ (door.data ?? []).map((row) => {
2625
+ const { document, hidden } = unfold(row.document);
2626
+ return { id: row.id, document, level: row.level, hidden };
2627
+ })
2628
+ );
2629
+ },
2548
2630
  async list({ table_id, limit = 50, offset = 0 }) {
2549
2631
  const door = await callDoor(
2550
2632
  DOORS.readRecords,
@@ -2819,6 +2901,26 @@ function createRecordsClient(config) {
2819
2901
  "fieldRetire"
2820
2902
  );
2821
2903
  },
2904
+ async relationWords({ field_id, value }) {
2905
+ return callDoor(
2906
+ DOORS.relationWords,
2907
+ { p_organization_id: org, p_field_id: field_id, p_value: value },
2908
+ "relationWords"
2909
+ );
2910
+ },
2911
+ async relationWordsMany({ field_id, record_ids }) {
2912
+ const result = await callDoor(
2913
+ DOORS.relationWordsMany,
2914
+ { p_organization_id: org, p_field_id: field_id, p_record_ids: record_ids },
2915
+ "relationWordsMany"
2916
+ );
2917
+ if (!result.ok) return result;
2918
+ const map = {};
2919
+ for (const row of result.data ?? []) {
2920
+ if (row?.record_id && typeof row.words === "string") map[row.record_id] = row.words;
2921
+ }
2922
+ return { ok: true, data: map };
2923
+ },
2822
2924
  async tableDeclare({ spec, homeId }) {
2823
2925
  return callDoor(
2824
2926
  DOORS.tableDeclare,
@@ -4263,7 +4365,11 @@ function useAsync(run, deps) {
4263
4365
  };
4264
4366
  }, [client, nonce, ...deps]);
4265
4367
  const reload = (0, import_react2.useCallback)(() => setNonce((n) => n + 1), []);
4266
- return { ...state, reload };
4368
+ const setData = (0, import_react2.useCallback)((update) => {
4369
+ if (!alive.current) return;
4370
+ setState((s) => ({ ...s, data: update(s.data) }));
4371
+ }, []);
4372
+ return { ...state, reload, setData };
4267
4373
  }
4268
4374
  function useTable(tableId) {
4269
4375
  return useAsync(
@@ -4352,14 +4458,49 @@ function useRecords(tableId, options = {}) {
4352
4458
  [tableId, pageSize, page]
4353
4459
  );
4354
4460
  const realtime = client.config.realtime;
4355
- const { reload } = base;
4461
+ const { reload, setData } = base;
4356
4462
  (0, import_react2.useEffect)(() => {
4357
4463
  if (!realtime || !tableId) return;
4358
4464
  return realtime.subscribeRecords(
4359
4465
  { organization_id: client.config.organizationId, table_id: tableId },
4360
- () => reload()
4466
+ {
4467
+ records(ids) {
4468
+ if (ids === null) {
4469
+ void reload();
4470
+ return;
4471
+ }
4472
+ if (ids.length === 0) return;
4473
+ void (async () => {
4474
+ const answered = await client.listByIds({ table_id: tableId, ids });
4475
+ if (!answered.ok) {
4476
+ void reload();
4477
+ return;
4478
+ }
4479
+ const fresh = new Map(answered.data.map((row) => [row.id, row]));
4480
+ const onThisPage = (id) => (base.data?.rows ?? []).some((row) => row.id === id);
4481
+ const needsThePage = ids.some((id) => !onThisPage(id) && fresh.has(id));
4482
+ if (needsThePage) {
4483
+ void reload();
4484
+ return;
4485
+ }
4486
+ setData(
4487
+ (current) => current === null ? current : {
4488
+ ...current,
4489
+ // A named id that came back replaces the row on screen; one that did NOT
4490
+ // is a record this reader may no longer see (deleted, or unshared), so it
4491
+ // leaves the page — exactly what the page read would have done by
4492
+ // omission.
4493
+ rows: current.rows.filter((row) => !(ids.includes(row.id) && !fresh.has(row.id))).map((row) => fresh.get(row.id) ?? row)
4494
+ }
4495
+ );
4496
+ })();
4497
+ },
4498
+ shape() {
4499
+ if (tableId) tableShapeChanged(tableId);
4500
+ }
4501
+ }
4361
4502
  );
4362
- }, [realtime, tableId, client.config.organizationId, reload]);
4503
+ }, [realtime, tableId, client, reload, setData, base.data]);
4363
4504
  const live = Boolean(realtime && tableId);
4364
4505
  const liveReason = realtime ? tableId ? "Live: changes to this table re-read through the read door." : "Not live: no table selected." : "Not live: this host bound no realtime port, so this list updates when you reload it.";
4365
4506
  return { ...base, live, liveReason };