@ai-matrx/records 0.41.0 → 0.43.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,46 @@
1
1
  # Changelog — @ai-matrx/records
2
2
 
3
+ ## 0.43.0
4
+
5
+ **The realtime port stops being half a contract, and a nudge re-reads only what moved.**
6
+
7
+ 🚨 **BREAKING (one implementor):** `RecordsRealtimePort.subscribeRecords(args, onChange)` now
8
+ takes a `RecordsChangeSink` instead of `(recordId) => void`:
9
+
10
+ ```ts
11
+ subscribeRecords(args, {
12
+ records(ids: readonly Uuid[] | null) { … }, // null = "I cannot name them" = read the page
13
+ shape() { … }, // a column moved, not a row
14
+ })
15
+ ```
16
+
17
+ *Consumer action:* a host binding this port passes the two-call sink and **deletes its
18
+ `tableShapeChanged` import**. That import was the tell: a host that wanted to say "the SHAPE
19
+ moved" reached around the port into `@ai-matrx/records/react`, because the contract had no way
20
+ to say it. A column appearing is not a row changing and the package owns the difference now.
21
+
22
+ * **`listByIds({ table_id, ids })`** — DOOR-1 addressed by an id SET, through the new
23
+ `custom.read_records_by_ids`. The same ladder (`custom.visible_set`), the same level, the
24
+ same field masking and the same page ceiling as `list`; an id the reader may not see simply
25
+ does not come back. A separate door NAME rather than a sixth argument to `read_records`,
26
+ because a defaulted argument beside a live Postgres function makes every existing call
27
+ ambiguous — `function custom.read_records(uuid, uuid, boolean, integer, integer) is not
28
+ unique`.
29
+ * **`useRecords` re-reads only the ids it was nudged with.** A whole-page read is now reserved
30
+ for the three cases that need one: `records(null)`, a subset read the door refused, and a
31
+ named id that is NEW to this page (only the page door knows where it belongs in the order,
32
+ so the hook does not guess).
33
+ * **Echo suppression by op id.** Every `recordWrite` / `recordWriteMany` / `recordUpdate` mints
34
+ a uuid and sends it as the platform envelope key `_op_id`. The store lifts it out before its
35
+ undeclared-key guard and before storage — **it is never written onto the record** — and
36
+ carries it back in the realtime notice as `op_id`. `isOwnOp(opId)` (new, from `ops.ts`) tells
37
+ a port that a notice is this browser's own echo, so a host can delete
38
+ `acceptEchoFromSelf: true` and stop re-reading a page it has already updated. One call is one
39
+ operation: a `recordWriteMany` batch carries ONE id, and the store refuses a batch carrying
40
+ two by name.
41
+ * `useAsync` gained `setData`, so a caller that has just read fresher rows **through the same
42
+ door** can place them instead of throwing them away and reading the page again.
43
+
3
44
  ## 0.41.0
4
45
 
5
46
  **`recordWriteMany` — the door for a paste that is bigger than a page.**
@@ -87,6 +87,7 @@ __export(core_exports, {
87
87
  err: () => err,
88
88
  exportCsv: () => exportCsv,
89
89
  exportXlsx: () => exportXlsx,
90
+ forgetAllOps: () => forgetAllOps,
90
91
  highestLevel: () => highestLevel,
91
92
  holdsARetiredChoice: () => holdsARetiredChoice,
92
93
  importCsv: () => importCsv,
@@ -94,6 +95,7 @@ __export(core_exports, {
94
95
  indexedDbStore: () => indexedDbStore,
95
96
  intersectIds: () => intersectIds,
96
97
  isDecided: () => isDecided,
98
+ isOwnOp: () => isOwnOp,
97
99
  isRecordsErr: () => isRecordsErr,
98
100
  isTheChosen: () => isTheChosen,
99
101
  looksLikeAnId: () => looksLikeAnId,
@@ -102,6 +104,7 @@ __export(core_exports, {
102
104
  measureKey: () => measureKey,
103
105
  memoryStore: () => memoryStore,
104
106
  mintClientKey: () => mintClientKey,
107
+ mintOpId: () => mintOpId,
105
108
  ok: () => ok,
106
109
  openCaptureQueue: () => openCaptureQueue,
107
110
  optionKey: () => optionKey,
@@ -114,6 +117,7 @@ __export(core_exports, {
114
117
  predictValueRefusals: () => predictValueRefusals,
115
118
  predictWriteRefusals: () => predictWriteRefusals,
116
119
  publicFormPath: () => publicFormPath,
120
+ rememberedOpCount: () => rememberedOpCount,
117
121
  scopeChip: () => scopeChip,
118
122
  staleWriteDetail: () => staleWriteDetail,
119
123
  supabaseDataSource: () => supabaseDataSource,
@@ -559,6 +563,45 @@ function isRecordsErr(result) {
559
563
  var ok = (data) => ({ ok: true, data });
560
564
  var err = (error) => ({ ok: false, error });
561
565
 
566
+ // src/ops.ts
567
+ var REMEMBER_MS = 3e4;
568
+ var REMEMBER_MAX = 500;
569
+ var ours = /* @__PURE__ */ new Map();
570
+ function trim(now) {
571
+ for (const [id, at] of ours) {
572
+ if (now - at <= REMEMBER_MS) break;
573
+ ours.delete(id);
574
+ }
575
+ while (ours.size > REMEMBER_MAX) {
576
+ const oldest = ours.keys().next();
577
+ if (oldest.done === true) break;
578
+ ours.delete(oldest.value);
579
+ }
580
+ }
581
+ function mintOpId() {
582
+ const id = crypto.randomUUID();
583
+ const now = Date.now();
584
+ ours.set(id, now);
585
+ trim(now);
586
+ return id;
587
+ }
588
+ function isOwnOp(opId) {
589
+ if (!opId) return false;
590
+ const at = ours.get(opId);
591
+ if (at === void 0) return false;
592
+ if (Date.now() - at > REMEMBER_MS) {
593
+ ours.delete(opId);
594
+ return false;
595
+ }
596
+ return true;
597
+ }
598
+ function forgetAllOps() {
599
+ ours.clear();
600
+ }
601
+ function rememberedOpCount() {
602
+ return ours.size;
603
+ }
604
+
562
605
  // src/store.generated.ts
563
606
  var ACTOR_VOCABULARY = ["user", "agent", "system"];
564
607
  var RETIRED_ACTOR_WORDS = { "ai": "agent", "code": "system", "human": "user" };
@@ -1002,6 +1045,7 @@ var STORE_DOORS = [
1002
1045
  { name: "read_paths_outside_the_door", args: "", returns: "TABLE(object_name text, why text)", security: "invoker" },
1003
1046
  { name: "read_record", args: "p_organization_id uuid, p_record_id uuid, p_by_id boolean DEFAULT false", returns: "jsonb", security: "definer" },
1004
1047
  { 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" },
1048
+ { 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" },
1005
1049
  { name: "realtime_topic_admits", args: "p_topic text", returns: "boolean", security: "definer" },
1006
1050
  { 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" },
1007
1051
  { 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" },
@@ -1026,6 +1070,7 @@ var STORE_DOORS = [
1026
1070
  { name: "record_values", args: "p_organization_id uuid, p_record_id uuid", returns: "jsonb", security: "invoker" },
1027
1071
  { name: "record_values_of", args: "p_row custom.record", returns: "jsonb", security: "invoker" },
1028
1072
  { 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" },
1073
+ { name: "record_words", args: "p_organization_id uuid, p_record_id uuid, p_noun text DEFAULT NULL::text", returns: "text", security: "definer" },
1029
1074
  { name: "record_write", args: "p_organization_id uuid, p_table_id uuid, p_data jsonb", returns: "uuid", security: "definer" },
1030
1075
  { 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" },
1031
1076
  { name: "refusals_claiming_a_level_never_asked", args: "", returns: "TABLE(function_name text, identity_args text, why text)", security: "invoker" },
@@ -1226,18 +1271,18 @@ var STORE_KNOBS = [
1226
1271
  { key: "world_publish_enabled", value: "false", type: "boolean" }
1227
1272
  ];
1228
1273
  var STORE_REFUSAL_CODES = [
1229
- { 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" },
1274
+ { 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" },
1230
1275
  { 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" },
1231
1276
  { code: "22003", raised_by: "context_resolve" },
1232
1277
  { 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" },
1233
1278
  { code: "22007", raised_by: "dashboard_window_sql" },
1234
1279
  { code: "22012", raised_by: "rule_eval" },
1235
- { 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" },
1280
+ { 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" },
1236
1281
  { 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" },
1237
1282
  { 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" },
1238
- { 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" },
1283
+ { 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,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" },
1239
1284
  { code: "40001", raised_by: "has_visibility_at" },
1240
- { 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" },
1285
+ { 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" },
1241
1286
  { code: "42703", raised_by: "entity_table,portal_declare" },
1242
1287
  { code: "53400", raised_by: "anon_rate_take,delete_cascade_closure,enrich_due,enrich_land,promote_field" },
1243
1288
  { code: "54001", raised_by: "record_delete" },
@@ -2325,6 +2370,13 @@ var DOORS = {
2325
2370
  // this person could see.
2326
2371
  readRecord: "read_record",
2327
2372
  readRecords: "read_records",
2373
+ // THE SAME READ DOOR, ADDRESSED BY AN ID SET. A realtime notice names the exact ids that
2374
+ // moved; before this existed the only door that took them was the PAGE door, so a
2375
+ // three-cell change re-read fifty rows in every watching browser. It is a separate NAME
2376
+ // rather than a sixth argument to `read_records` because adding a defaulted argument to a
2377
+ // live Postgres function makes every existing call ambiguous — `is not unique`. Same
2378
+ // ladder, same level, same masking, same page ceiling.
2379
+ readRecordsByIds: "read_records_by_ids",
2328
2380
  // DOOR-8, the two clocks. `p_recorded_at` is what the store SAID then;
2329
2381
  // `p_world_on` is what was TRUE of the world on that date, read out of dated
2330
2382
  // values. A client that answered either one itself would be guessing.
@@ -2909,17 +2961,18 @@ function createRecordsClient(config) {
2909
2961
  async recordWrite({ table_id, data }) {
2910
2962
  return callDoor(
2911
2963
  DOORS.recordWrite,
2912
- { p_organization_id: org, p_table_id: table_id, p_data: data },
2964
+ { p_organization_id: org, p_table_id: table_id, p_data: { ...data, _op_id: mintOpId() } },
2913
2965
  "recordWrite"
2914
2966
  );
2915
2967
  },
2916
2968
  async recordWriteMany({ table_id, rows, ids }) {
2969
+ const opId = mintOpId();
2917
2970
  return callDoor(
2918
2971
  DOORS.recordWriteMany,
2919
2972
  {
2920
2973
  p_organization_id: org,
2921
2974
  p_table_id: table_id,
2922
- p_rows: rows,
2975
+ p_rows: rows.map((row) => ({ ...row, _op_id: opId })),
2923
2976
  ...ids === void 0 ? {} : { p_ids: ids }
2924
2977
  },
2925
2978
  "recordWriteMany"
@@ -2931,7 +2984,7 @@ function createRecordsClient(config) {
2931
2984
  {
2932
2985
  p_organization_id: org,
2933
2986
  p_record_id: record_id,
2934
- p_patch: patch,
2987
+ p_patch: { ...patch, _op_id: mintOpId() },
2935
2988
  ...expectedVersion === void 0 ? {} : { p_expected_version: expectedVersion }
2936
2989
  },
2937
2990
  "recordUpdate"
@@ -2973,6 +3026,21 @@ function createRecordsClient(config) {
2973
3026
  }
2974
3027
  return ok({ record_id, document, hidden, computed, computedUnavailable });
2975
3028
  },
3029
+ async listByIds({ table_id, ids }) {
3030
+ if (ids.length === 0) return ok([]);
3031
+ const door = await callDoor(
3032
+ DOORS.readRecordsByIds,
3033
+ { p_organization_id: org, p_table_id: table_id, p_record_ids: ids },
3034
+ "listByIds"
3035
+ );
3036
+ if (!door.ok) return err(door.error);
3037
+ return ok(
3038
+ (door.data ?? []).map((row) => {
3039
+ const { document, hidden } = unfold(row.document);
3040
+ return { id: row.id, document, level: row.level, hidden };
3041
+ })
3042
+ );
3043
+ },
2976
3044
  async list({ table_id, limit = 50, offset = 0 }) {
2977
3045
  const door = await callDoor(
2978
3046
  DOORS.readRecords,