@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.
@@ -9,6 +9,45 @@ import {
9
9
  useMemo
10
10
  } from "react";
11
11
 
12
+ // src/ops.ts
13
+ var REMEMBER_MS = 3e4;
14
+ var REMEMBER_MAX = 500;
15
+ var ours = /* @__PURE__ */ new Map();
16
+ function trim(now) {
17
+ for (const [id, at] of ours) {
18
+ if (now - at <= REMEMBER_MS) break;
19
+ ours.delete(id);
20
+ }
21
+ while (ours.size > REMEMBER_MAX) {
22
+ const oldest = ours.keys().next();
23
+ if (oldest.done === true) break;
24
+ ours.delete(oldest.value);
25
+ }
26
+ }
27
+ function mintOpId() {
28
+ const id = crypto.randomUUID();
29
+ const now = Date.now();
30
+ ours.set(id, now);
31
+ trim(now);
32
+ return id;
33
+ }
34
+ function isOwnOp(opId) {
35
+ if (!opId) return false;
36
+ const at = ours.get(opId);
37
+ if (at === void 0) return false;
38
+ if (Date.now() - at > REMEMBER_MS) {
39
+ ours.delete(opId);
40
+ return false;
41
+ }
42
+ return true;
43
+ }
44
+ function forgetAllOps() {
45
+ ours.clear();
46
+ }
47
+ function rememberedOpCount() {
48
+ return ours.size;
49
+ }
50
+
12
51
  // src/level.ts
13
52
  var PERMISSION_LEVELS = [
14
53
  "viewer",
@@ -486,6 +525,7 @@ var STORE_DOORS = [
486
525
  { name: "read_paths_outside_the_door", args: "", returns: "TABLE(object_name text, why text)", security: "invoker" },
487
526
  { name: "read_record", args: "p_organization_id uuid, p_record_id uuid, p_by_id boolean DEFAULT false", returns: "jsonb", security: "definer" },
488
527
  { 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" },
528
+ { 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" },
489
529
  { name: "realtime_topic_admits", args: "p_topic text", returns: "boolean", security: "definer" },
490
530
  { 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" },
491
531
  { 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" },
@@ -510,6 +550,7 @@ var STORE_DOORS = [
510
550
  { name: "record_values", args: "p_organization_id uuid, p_record_id uuid", returns: "jsonb", security: "invoker" },
511
551
  { name: "record_values_of", args: "p_row custom.record", returns: "jsonb", security: "invoker" },
512
552
  { 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" },
553
+ { name: "record_words", args: "p_organization_id uuid, p_record_id uuid, p_noun text DEFAULT NULL::text", returns: "text", security: "definer" },
513
554
  { name: "record_write", args: "p_organization_id uuid, p_table_id uuid, p_data jsonb", returns: "uuid", security: "definer" },
514
555
  { 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" },
515
556
  { name: "refusals_claiming_a_level_never_asked", args: "", returns: "TABLE(function_name text, identity_args text, why text)", security: "invoker" },
@@ -521,6 +562,8 @@ var STORE_DOORS = [
521
562
  { name: "relation_target_ours", args: "p_entity_token text, p_row_id uuid", returns: "jsonb", security: "definer" },
522
563
  { name: "relation_targets", args: "p_organization_id uuid, p_record_id uuid, p_via_key text", returns: "SETOF uuid", security: "definer" },
523
564
  { name: "relation_uncarry", args: "p_organization_id uuid, p_container_id uuid, p_item_id uuid", returns: "integer", security: "definer" },
565
+ { name: "relation_words", args: "p_organization_id uuid, p_field_id uuid, p_value text", returns: "text", security: "definer" },
566
+ { 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" },
524
567
  { name: "reopen_declared_doors", args: "", returns: "TABLE(reopened text)", security: "definer" },
525
568
  { name: "required_epoch", args: "p_container_type text, p_container_id uuid, p_item_type text, p_item_id uuid", returns: "bigint", security: "definer" },
526
569
  { name: "resolve_first_match", args: "p_organization_id uuid, p_record_id uuid", returns: "jsonb", security: "invoker" },
@@ -710,18 +753,18 @@ var STORE_KNOBS = [
710
753
  { key: "world_publish_enabled", value: "false", type: "boolean" }
711
754
  ];
712
755
  var STORE_REFUSAL_CODES = [
713
- { 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" },
756
+ { 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" },
714
757
  { 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" },
715
758
  { code: "22003", raised_by: "context_resolve" },
716
759
  { 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" },
717
760
  { code: "22007", raised_by: "dashboard_window_sql" },
718
761
  { code: "22012", raised_by: "rule_eval" },
719
- { 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" },
720
- { 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" },
762
+ { 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" },
763
+ { 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" },
721
764
  { 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" },
722
- { 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" },
765
+ { 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" },
723
766
  { code: "40001", raised_by: "has_visibility_at" },
724
- { 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" },
767
+ { 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" },
725
768
  { code: "42703", raised_by: "entity_table,portal_declare" },
726
769
  { code: "53400", raised_by: "anon_rate_take,delete_cascade_closure,enrich_due,enrich_land,promote_field" },
727
770
  { code: "54001", raised_by: "record_delete" },
@@ -1804,6 +1847,13 @@ var DOORS = {
1804
1847
  // this person could see.
1805
1848
  readRecord: "read_record",
1806
1849
  readRecords: "read_records",
1850
+ // THE SAME READ DOOR, ADDRESSED BY AN ID SET. A realtime notice names the exact ids that
1851
+ // moved; before this existed the only door that took them was the PAGE door, so a
1852
+ // three-cell change re-read fifty rows in every watching browser. It is a separate NAME
1853
+ // rather than a sixth argument to `read_records` because adding a defaulted argument to a
1854
+ // live Postgres function makes every existing call ambiguous — `is not unique`. Same
1855
+ // ladder, same level, same masking, same page ceiling.
1856
+ readRecordsByIds: "read_records_by_ids",
1807
1857
  // DOOR-8, the two clocks. `p_recorded_at` is what the store SAID then;
1808
1858
  // `p_world_on` is what was TRUE of the world on that date, read out of dated
1809
1859
  // values. A client that answered either one itself would be guessing.
@@ -1863,6 +1913,18 @@ var DOORS = {
1863
1913
  promotedRead: "promoted_read",
1864
1914
  relationTargets: "relation_targets",
1865
1915
  relationOwn: "relation_own",
1916
+ // REL-DISP, 2026-09-21. WHAT A RELATION CELL READS, through that column's own
1917
+ // display spec. `custom.record_words` is declared server_only — it takes a bare
1918
+ // record id with no field to scope it — so these are how a screen gets the
1919
+ // words, and they are the ONE way in: three separate copies of "read tableList,
1920
+ // find title_field, call rowName" lived in labels.tsx, RelationPicker.tsx and
1921
+ // PortalsPanel.tsx, and none of them could ever have shown two columns joined.
1922
+ // The `_many` door is the batch TAILS-2 recorded as missing ("there is still no
1923
+ // batch door on the store"), which is why the browser resolved labels one id at
1924
+ // a time. The ladder is NOT batched: the store asks it per record inside
1925
+ // `custom._words_for`, so batching buys a round trip and never a disclosure.
1926
+ relationWords: "relation_words",
1927
+ relationWordsMany: "relation_words_many",
1866
1928
  homeAdd: "home_add",
1867
1929
  recordReparent: "record_reparent",
1868
1930
  containmentChain: "containment_chain",
@@ -2380,17 +2442,18 @@ function createRecordsClient(config) {
2380
2442
  async recordWrite({ table_id, data }) {
2381
2443
  return callDoor(
2382
2444
  DOORS.recordWrite,
2383
- { p_organization_id: org, p_table_id: table_id, p_data: data },
2445
+ { p_organization_id: org, p_table_id: table_id, p_data: { ...data, _op_id: mintOpId() } },
2384
2446
  "recordWrite"
2385
2447
  );
2386
2448
  },
2387
2449
  async recordWriteMany({ table_id, rows, ids }) {
2450
+ const opId = mintOpId();
2388
2451
  return callDoor(
2389
2452
  DOORS.recordWriteMany,
2390
2453
  {
2391
2454
  p_organization_id: org,
2392
2455
  p_table_id: table_id,
2393
- p_rows: rows,
2456
+ p_rows: rows.map((row) => ({ ...row, _op_id: opId })),
2394
2457
  ...ids === void 0 ? {} : { p_ids: ids }
2395
2458
  },
2396
2459
  "recordWriteMany"
@@ -2402,7 +2465,7 @@ function createRecordsClient(config) {
2402
2465
  {
2403
2466
  p_organization_id: org,
2404
2467
  p_record_id: record_id,
2405
- p_patch: patch,
2468
+ p_patch: { ...patch, _op_id: mintOpId() },
2406
2469
  ...expectedVersion === void 0 ? {} : { p_expected_version: expectedVersion }
2407
2470
  },
2408
2471
  "recordUpdate"
@@ -2444,6 +2507,21 @@ function createRecordsClient(config) {
2444
2507
  }
2445
2508
  return ok({ record_id, document, hidden, computed, computedUnavailable });
2446
2509
  },
2510
+ async listByIds({ table_id, ids }) {
2511
+ if (ids.length === 0) return ok([]);
2512
+ const door = await callDoor(
2513
+ DOORS.readRecordsByIds,
2514
+ { p_organization_id: org, p_table_id: table_id, p_record_ids: ids },
2515
+ "listByIds"
2516
+ );
2517
+ if (!door.ok) return err(door.error);
2518
+ return ok(
2519
+ (door.data ?? []).map((row) => {
2520
+ const { document, hidden } = unfold(row.document);
2521
+ return { id: row.id, document, level: row.level, hidden };
2522
+ })
2523
+ );
2524
+ },
2447
2525
  async list({ table_id, limit = 50, offset = 0 }) {
2448
2526
  const door = await callDoor(
2449
2527
  DOORS.readRecords,
@@ -2718,6 +2796,26 @@ function createRecordsClient(config) {
2718
2796
  "fieldRetire"
2719
2797
  );
2720
2798
  },
2799
+ async relationWords({ field_id, value }) {
2800
+ return callDoor(
2801
+ DOORS.relationWords,
2802
+ { p_organization_id: org, p_field_id: field_id, p_value: value },
2803
+ "relationWords"
2804
+ );
2805
+ },
2806
+ async relationWordsMany({ field_id, record_ids }) {
2807
+ const result = await callDoor(
2808
+ DOORS.relationWordsMany,
2809
+ { p_organization_id: org, p_field_id: field_id, p_record_ids: record_ids },
2810
+ "relationWordsMany"
2811
+ );
2812
+ if (!result.ok) return result;
2813
+ const map = {};
2814
+ for (const row of result.data ?? []) {
2815
+ if (row?.record_id && typeof row.words === "string") map[row.record_id] = row.words;
2816
+ }
2817
+ return { ok: true, data: map };
2818
+ },
2721
2819
  async tableDeclare({ spec, homeId }) {
2722
2820
  return callDoor(
2723
2821
  DOORS.tableDeclare,
@@ -4162,7 +4260,11 @@ function useAsync(run, deps) {
4162
4260
  };
4163
4261
  }, [client, nonce, ...deps]);
4164
4262
  const reload = useCallback(() => setNonce((n) => n + 1), []);
4165
- return { ...state, reload };
4263
+ const setData = useCallback((update) => {
4264
+ if (!alive.current) return;
4265
+ setState((s) => ({ ...s, data: update(s.data) }));
4266
+ }, []);
4267
+ return { ...state, reload, setData };
4166
4268
  }
4167
4269
  function useTable(tableId) {
4168
4270
  return useAsync(
@@ -4251,14 +4353,49 @@ function useRecords(tableId, options = {}) {
4251
4353
  [tableId, pageSize, page]
4252
4354
  );
4253
4355
  const realtime = client.config.realtime;
4254
- const { reload } = base;
4356
+ const { reload, setData } = base;
4255
4357
  useEffect(() => {
4256
4358
  if (!realtime || !tableId) return;
4257
4359
  return realtime.subscribeRecords(
4258
4360
  { organization_id: client.config.organizationId, table_id: tableId },
4259
- () => reload()
4361
+ {
4362
+ records(ids) {
4363
+ if (ids === null) {
4364
+ void reload();
4365
+ return;
4366
+ }
4367
+ if (ids.length === 0) return;
4368
+ void (async () => {
4369
+ const answered = await client.listByIds({ table_id: tableId, ids });
4370
+ if (!answered.ok) {
4371
+ void reload();
4372
+ return;
4373
+ }
4374
+ const fresh = new Map(answered.data.map((row) => [row.id, row]));
4375
+ const onThisPage = (id) => (base.data?.rows ?? []).some((row) => row.id === id);
4376
+ const needsThePage = ids.some((id) => !onThisPage(id) && fresh.has(id));
4377
+ if (needsThePage) {
4378
+ void reload();
4379
+ return;
4380
+ }
4381
+ setData(
4382
+ (current) => current === null ? current : {
4383
+ ...current,
4384
+ // A named id that came back replaces the row on screen; one that did NOT
4385
+ // is a record this reader may no longer see (deleted, or unshared), so it
4386
+ // leaves the page — exactly what the page read would have done by
4387
+ // omission.
4388
+ rows: current.rows.filter((row) => !(ids.includes(row.id) && !fresh.has(row.id))).map((row) => fresh.get(row.id) ?? row)
4389
+ }
4390
+ );
4391
+ })();
4392
+ },
4393
+ shape() {
4394
+ if (tableId) tableShapeChanged(tableId);
4395
+ }
4396
+ }
4260
4397
  );
4261
- }, [realtime, tableId, client.config.organizationId, reload]);
4398
+ }, [realtime, tableId, client, reload, setData, base.data]);
4262
4399
  const live = Boolean(realtime && tableId);
4263
4400
  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.";
4264
4401
  return { ...base, live, liveReason };
@@ -4934,10 +5071,12 @@ export {
4934
5071
  choiceValuesOf,
4935
5072
  choicesOf,
4936
5073
  err,
5074
+ forgetAllOps,
4937
5075
  highestLevel,
4938
5076
  holdsARetiredChoice,
4939
5077
  indexedDbStore,
4940
5078
  isDecided,
5079
+ isOwnOp,
4941
5080
  isRecordsErr,
4942
5081
  isTheChosen,
4943
5082
  looksLikeAnId,
@@ -4945,12 +5084,14 @@ export {
4945
5084
  measureKey,
4946
5085
  memoryStore,
4947
5086
  mintClientKey,
5087
+ mintOpId,
4948
5088
  ok,
4949
5089
  openCaptureQueue,
4950
5090
  optionKey,
4951
5091
  optionLabel,
4952
5092
  portalPath,
4953
5093
  publicFormPath,
5094
+ rememberedOpCount,
4954
5095
  scopeChip,
4955
5096
  tableShapeChanged,
4956
5097
  useEntityFieldMutation,