@ai-matrx/records 0.58.0 → 0.58.2

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,29 @@
1
1
  # Changelog — @ai-matrx/records
2
2
 
3
+ ## 0.58.2
4
+
5
+ Automatic changed-only republish (docs/metadata drift since the last tag — see
6
+ `git diff npm/records/v0.58.1..npm/records/v0.58.2 -- apps/shared/records`).
7
+ No source changes intended and no consumer action required.
8
+
9
+ ## Unreleased — lane SC-1-TAILS (2026-09-24)
10
+
11
+ * **`client.tableHome({ table_id })` and `client.tableMove({ table_id, to_organization_id, expected_version })`** — the
12
+ store utility for "which organization is this table in, and move it" (`custom.table_home` / `custom.table_move`,
13
+ SC-1' + SC-1-TAILS). Asked by the TABLE's id, never the bound organization; `null` = not given to this person;
14
+ `door_absent` (and nothing called) on a store whose catalogue lacks the doors.
15
+ * `placement.ts` gains `TableHome`, `TableHomeDestination`, `TableMoved`, `parseTableHome`, `parseTableMoved` and
16
+ `moveConsequence` (the sentence said before a move: columns, rows, trash, the tables that live inside it, links
17
+ that reach back across the wall).
18
+ * **Consumer action:** matrx-frontend swaps `features/unified-data/where-it-lives/` for `@ai-matrx/records-ui`'s
19
+ `WhereItLives` once both are installed. `pnpm records:generate` after the chair applies the doors.
20
+
21
+ ## 0.58.1
22
+
23
+ Automatic changed-only republish (docs/metadata drift since the last tag — see
24
+ `git diff npm/records/v0.58.0..npm/records/v0.58.1 -- apps/shared/records`).
25
+ No source changes intended and no consumer action required.
26
+
3
27
  ## 0.58.0 (2026-09-24 — published by PUBLISH-SWEEP-5; folds every section below down to 0.57.3)
4
28
 
5
29
  * **`src/store.generated.ts` is regenerated from the LIVE main database at publish time** (608 doors).
@@ -2310,6 +2310,7 @@ __export(core_exports, {
2310
2310
  memoryStore: () => memoryStore,
2311
2311
  mintClientKey: () => mintClientKey,
2312
2312
  mintOpId: () => mintOpId,
2313
+ moveConsequence: () => moveConsequence,
2313
2314
  moveInOrder: () => moveInOrder,
2314
2315
  offeredAsContext: () => offeredAsContext,
2315
2316
  ok: () => ok,
@@ -2318,6 +2319,8 @@ __export(core_exports, {
2318
2319
  optionKey: () => optionKey,
2319
2320
  optionLabel: () => optionLabel,
2320
2321
  parseCsv: () => parseCsv,
2322
+ parseTableHome: () => parseTableHome,
2323
+ parseTableMoved: () => parseTableMoved,
2321
2324
  planQuery: () => planQuery,
2322
2325
  portalArchiveConsequence: () => portalArchiveConsequence,
2323
2326
  portalConfirmLine: () => portalConfirmLine,
@@ -3003,6 +3006,66 @@ function keptTableHref(fact, hrefs) {
3003
3006
  }
3004
3007
  return hrefs.table(fact.table_id);
3005
3008
  }
3009
+ function countOf(value) {
3010
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
3011
+ }
3012
+ function wordsOf(value) {
3013
+ return typeof value === "string" && value.length > 0 ? value : null;
3014
+ }
3015
+ function parseTableHome(raw) {
3016
+ if (!raw || typeof raw !== "object") return null;
3017
+ const row = raw;
3018
+ const table = row.table;
3019
+ const org = row.organization;
3020
+ if (!table || !org || !wordsOf(table.id) || !wordsOf(org.id) || !wordsOf(org.name)) return null;
3021
+ const carries = row.carries ?? {};
3022
+ const destinations = Array.isArray(row.destinations) ? row.destinations : [];
3023
+ return {
3024
+ table: { id: String(table.id), name: wordsOf(table.name) ?? "This table", version: countOf(table.version) },
3025
+ organization: { id: String(org.id), name: String(org.name) },
3026
+ mayMove: row.may_move === true,
3027
+ whyNot: wordsOf(row.why_not),
3028
+ heldBy: Array.isArray(row.held_by) ? row.held_by.filter((s) => typeof s === "string") : [],
3029
+ destinations: destinations.filter((d) => !!d && typeof d === "object" && !!wordsOf(d.id)).map((d) => ({
3030
+ id: String(d.id),
3031
+ name: wordsOf(d.name) ?? "An organization",
3032
+ role: wordsOf(d.role) ?? "member",
3033
+ ok: d.ok === true,
3034
+ why: wordsOf(d.why)
3035
+ })),
3036
+ carries: {
3037
+ fields: countOf(carries.fields),
3038
+ records: countOf(carries.records),
3039
+ inTrash: countOf(carries.in_trash),
3040
+ choiceLists: countOf(carries.choice_lists),
3041
+ withIt: countOf(carries.with_it),
3042
+ tablesInside: countOf(carries.tables_inside),
3043
+ linksAcross: countOf(carries.links_across)
3044
+ }
3045
+ };
3046
+ }
3047
+ function parseTableMoved(raw) {
3048
+ const row = raw ?? {};
3049
+ const to = row.to ?? {};
3050
+ const from = row.from ?? {};
3051
+ const table = row.table ?? {};
3052
+ if (row.moved !== true || !wordsOf(to.id)) return null;
3053
+ return {
3054
+ to: { id: String(to.id), name: wordsOf(to.name) ?? "the new organization" },
3055
+ from: { id: wordsOf(from.id) ?? "", name: wordsOf(from.name) ?? "its old organization" },
3056
+ tableName: wordsOf(table.name) ?? "The table"
3057
+ };
3058
+ }
3059
+ function plural(n, one, many) {
3060
+ return `${n} ${n === 1 ? one : many}`;
3061
+ }
3062
+ function moveConsequence(home, to) {
3063
+ const c = home.carries;
3064
+ const parts = [plural(c.fields, "column", "columns"), plural(c.records, "row", "rows")];
3065
+ if (c.inTrash > 0) parts.push(`${c.inTrash} in the trash`);
3066
+ const extra = c.choiceLists + c.withIt;
3067
+ return `${home.table.name} moves to ${to.name} with its ${parts.join(", ")}` + (c.tablesInside > 0 ? `, the ${plural(c.tablesInside, "table", "tables")} that live${c.tablesInside === 1 ? "s" : ""} inside it or its rows (with their own rows)` : "") + (extra > 0 ? `, and ${plural(extra, "thing", "things")} built only on it (choice lists, rules, dashboards, templates)` : "") + `, its comments, forms, saved views and history. ` + (c.linksAcross > 0 ? `${plural(c.linksAcross, "link", "links")} to rows that stay in ${home.organization.name} keep${c.linksAcross === 1 ? "s" : ""} pointing there, across the two organizations. ` : "") + `People who see it only because they are in ${home.organization.name} stop seeing it; people it was shared with by name keep it. You can move it back from the same place.`;
3068
+ }
3006
3069
 
3007
3070
  // src/capture-queue.ts
3008
3071
  var DB_NAME = "matrx-capture";
@@ -3072,7 +3135,7 @@ function memoryStore() {
3072
3135
  setMeta: async (key, value) => void meta.set(key, value)
3073
3136
  };
3074
3137
  }
3075
- function countOf(items) {
3138
+ function countOf2(items) {
3076
3139
  const c = { waiting: 0, sending: 0, refused: 0, landed: 0 };
3077
3140
  for (const i of items) c[i.state] += 1;
3078
3141
  return c;
@@ -3089,7 +3152,7 @@ function openCaptureQueue(opts) {
3089
3152
  const announce = async () => {
3090
3153
  if (!opts.onChange) return;
3091
3154
  const items = await store.all();
3092
- opts.onChange(countOf(items), items);
3155
+ opts.onChange(countOf2(items), items);
3093
3156
  };
3094
3157
  const oldestFirst = (items) => items.slice().sort((a, b) => a.captured_at.localeCompare(b.captured_at));
3095
3158
  const queue = {
@@ -3118,7 +3181,7 @@ function openCaptureQueue(opts) {
3118
3181
  return key;
3119
3182
  },
3120
3183
  list: () => store.all().then(oldestFirst),
3121
- counts: () => store.all().then(countOf),
3184
+ counts: () => store.all().then(countOf2),
3122
3185
  async sync() {
3123
3186
  if (syncing) return syncing;
3124
3187
  syncing = (async () => {
@@ -5368,6 +5431,8 @@ function scopeChip(scope) {
5368
5431
 
5369
5432
  // src/core/doors.ts
5370
5433
  var OWED_BY = {
5434
+ table_home: "SC-1' \u2014 matrx-frontend migrations/campaign/sc1p_a_table_says_where_it_lives_and_its_owner_can_move_it.sql (and its grant file), applied by the chair",
5435
+ table_move: "SC-1' / SC-1-TAILS \u2014 sc1p_a_table_says_where_it_lives_and_its_owner_can_move_it.sql, then sc1t_a_table_moves_with_the_tables_inside_it.sql, applied by the chair",
5371
5436
  metadata_search: "W5-SRV / W5-AGENT \u2014 the agent surface's eight verbs",
5372
5437
  record_aggregate: "W5-SRV / W5-AGENT \u2014 `record_aggregate` is one of the eight verbs",
5373
5438
  field_propose: "W5-AGENT \u2014 AGT-4/AGT-8, the proposal and its approvers",
@@ -5693,6 +5758,12 @@ var DOORS = {
5693
5758
  // ask, under the same wall and visibility gate as `portals` itself.
5694
5759
  portalTables: "portal_tables",
5695
5760
  tableFacts: "table_facts",
5761
+ // WHERE A TABLE LIVES, AND MOVING IT (SC-1' / SC-1-TAILS). `table_home` answers only when the
5762
+ // caller may open the Table (custom.where_id_opens) and names its organization FROM THE TABLE;
5763
+ // `table_move` moves it into another organization the caller belongs to, with every Table
5764
+ // inside it, or refuses with one sentence.
5765
+ tableHome: "table_home",
5766
+ tableMove: "table_move",
5696
5767
  portalCard: "portal_card",
5697
5768
  portalDeclare: "portal_declare",
5698
5769
  portalInvite: "portal_invite",
@@ -6215,6 +6286,40 @@ function createRecordsClient(config) {
6215
6286
  if (!doorExists(DOORS.tableFacts)) return ok(null);
6216
6287
  return callDoor(DOORS.tableFacts, { p_organization_id: org }, "tableFacts");
6217
6288
  },
6289
+ async tableHome({ table_id }) {
6290
+ const answered = await callDoor(DOORS.tableHome, { p_table_id: table_id }, "tableHome");
6291
+ if (!answered.ok) return answered;
6292
+ if (answered.data === null || answered.data === void 0) return ok(null);
6293
+ const home = parseTableHome(answered.data);
6294
+ if (!home) {
6295
+ const refusal = {
6296
+ code: "not_supported",
6297
+ message: "The record store answered where this table lives in a shape this client cannot read, so nothing is claimed about it.",
6298
+ hint: "Regenerate @ai-matrx/records against the store (pnpm records:generate) \u2014 the door and this client disagree."
6299
+ };
6300
+ scream({ code: refusal.code, message: refusal.message, ...refusal.hint ? { hint: refusal.hint } : {}, where: "tableHome" });
6301
+ return err(refusal);
6302
+ }
6303
+ return ok(home);
6304
+ },
6305
+ async tableMove({ table_id, to_organization_id, expected_version }) {
6306
+ const answered = await callDoor(
6307
+ DOORS.tableMove,
6308
+ { p_table_id: table_id, p_to_organization_id: to_organization_id, p_expected_version: expected_version ?? null },
6309
+ "tableMove"
6310
+ );
6311
+ if (!answered.ok) return answered;
6312
+ const moved = parseTableMoved(answered.data);
6313
+ if (!moved) {
6314
+ const refusal = {
6315
+ code: "not_supported",
6316
+ message: "The record store answered the move in a shape this client cannot read. Look where the table lives before trying again."
6317
+ };
6318
+ scream({ code: refusal.code, message: refusal.message, where: "tableMove" });
6319
+ return err(refusal);
6320
+ }
6321
+ return ok(moved);
6322
+ },
6218
6323
  async tableArchived({ table_id }) {
6219
6324
  const kernel = await callDoor(DOORS.tableKernelId, {}, "tableArchived.kernel");
6220
6325
  if (!kernel.ok) return err(kernel.error);