@ai-matrx/records 0.58.117 → 0.58.118

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.
@@ -10865,6 +10865,25 @@ interface RecordQuery {
10865
10865
  offset?: number;
10866
10866
  }
10867
10867
 
10868
+ /** One organization the client could not use, with the sentence that says why. */
10869
+ interface OrganizationUnavailable {
10870
+ organization_id: Uuid;
10871
+ reason: string;
10872
+ }
10873
+ /** `organizationsOpen`: the organizations whose store answers, and those that do not (with why). */
10874
+ interface OrganizationsOpenAnswer {
10875
+ open: Uuid[];
10876
+ unavailable: OrganizationUnavailable[];
10877
+ }
10878
+ /** `metadataSearch`: every match, each carrying the organization it came from. */
10879
+ interface MetadataSearchAnswer {
10880
+ matches: Array<Record<string, unknown> & {
10881
+ organization_id: Uuid | null;
10882
+ }>;
10883
+ /** Organizations asked in order (the ones that answered). */
10884
+ organizations_covered: Uuid[];
10885
+ unavailable: OrganizationUnavailable[];
10886
+ }
10868
10887
  interface RecordsClient {
10869
10888
  /** The config this client was built with, so a screen can show who it is acting as. */
10870
10889
  readonly config: RecordsConfig;
@@ -11930,6 +11949,13 @@ interface RecordsClient {
11930
11949
  */
11931
11950
  recordAggregate(args: {
11932
11951
  table_id: Uuid;
11952
+ /**
11953
+ * OPTIONAL FILTER (active-org law): with `config.organizationIds` set, the table is looked
11954
+ * up across all the person's organizations and the aggregate runs in the one that OWNS it
11955
+ * (each row then carries that `organization_id`); this narrows the lookup to one. Absent and
11956
+ * no `organizationIds` ⇒ this client's own organization, exactly as before.
11957
+ */
11958
+ organization_id?: Uuid | null;
11933
11959
  /** Field keys whose values become the groups. */
11934
11960
  groupBy?: string[];
11935
11961
  /** `count` needs no key; every other operation reads one Field. */
@@ -12279,9 +12305,37 @@ interface RecordsClient {
12279
12305
  record_id: Uuid;
12280
12306
  organization_id?: Uuid | null;
12281
12307
  }): Promise<RecordsResult<ReadValue[]>>;
12308
+ /**
12309
+ * Metadata search across EVERY organization the person belongs to (`config.organizationIds`;
12310
+ * `organization_id` narrows to one). Each match carries its own `organization_id`; an
12311
+ * organization whose store is off or refuses is listed in `unavailable` WITH its reason and
12312
+ * never hides the rest. Only when NO organization answers is the whole search a failure (the
12313
+ * first refusal). The door itself is not in the store's catalogue yet, so today every
12314
+ * organization refuses in the door-absent sentence — named, never faked.
12315
+ */
12282
12316
  metadataSearch(args: {
12283
12317
  text: string;
12284
- }): Promise<RecordsResult<never>>;
12318
+ organization_id?: Uuid | null;
12319
+ }): Promise<RecordsResult<MetadataSearchAnswer>>;
12320
+ /**
12321
+ * WHICH of the person's organizations can this client use right now? Asks every organization
12322
+ * in scope (`organization_id` narrows to one) whether its record store is open, so a host can
12323
+ * say "organization X is unavailable: <reason>" instead of silently dropping it. Organization
12324
+ * order is the person's own, never completion order.
12325
+ */
12326
+ organizationsOpen(args?: {
12327
+ organization_id?: Uuid | null;
12328
+ }): Promise<RecordsResult<OrganizationsOpenAnswer>>;
12329
+ /**
12330
+ * The organization that OWNS a table or record — the one a write to it must run in. A
12331
+ * `table_id` is looked up across all the person's organizations; a `record_id` is opened in its
12332
+ * own. `null` = found in none of them. Never a permission: the store's doors still decide.
12333
+ */
12334
+ ownerOrganization(args: {
12335
+ table_id?: Uuid | null;
12336
+ record_id?: Uuid | null;
12337
+ organization_id?: Uuid | null;
12338
+ }): Promise<RecordsResult<Uuid | null>>;
12285
12339
  /**
12286
12340
  * Mint an embed token. `mode` is `read` OR `write` and never both — the store
12287
12341
  * refuses a token carrying two decisions. The secret comes back ONCE, because
@@ -10865,6 +10865,25 @@ interface RecordQuery {
10865
10865
  offset?: number;
10866
10866
  }
10867
10867
 
10868
+ /** One organization the client could not use, with the sentence that says why. */
10869
+ interface OrganizationUnavailable {
10870
+ organization_id: Uuid;
10871
+ reason: string;
10872
+ }
10873
+ /** `organizationsOpen`: the organizations whose store answers, and those that do not (with why). */
10874
+ interface OrganizationsOpenAnswer {
10875
+ open: Uuid[];
10876
+ unavailable: OrganizationUnavailable[];
10877
+ }
10878
+ /** `metadataSearch`: every match, each carrying the organization it came from. */
10879
+ interface MetadataSearchAnswer {
10880
+ matches: Array<Record<string, unknown> & {
10881
+ organization_id: Uuid | null;
10882
+ }>;
10883
+ /** Organizations asked in order (the ones that answered). */
10884
+ organizations_covered: Uuid[];
10885
+ unavailable: OrganizationUnavailable[];
10886
+ }
10868
10887
  interface RecordsClient {
10869
10888
  /** The config this client was built with, so a screen can show who it is acting as. */
10870
10889
  readonly config: RecordsConfig;
@@ -11930,6 +11949,13 @@ interface RecordsClient {
11930
11949
  */
11931
11950
  recordAggregate(args: {
11932
11951
  table_id: Uuid;
11952
+ /**
11953
+ * OPTIONAL FILTER (active-org law): with `config.organizationIds` set, the table is looked
11954
+ * up across all the person's organizations and the aggregate runs in the one that OWNS it
11955
+ * (each row then carries that `organization_id`); this narrows the lookup to one. Absent and
11956
+ * no `organizationIds` ⇒ this client's own organization, exactly as before.
11957
+ */
11958
+ organization_id?: Uuid | null;
11933
11959
  /** Field keys whose values become the groups. */
11934
11960
  groupBy?: string[];
11935
11961
  /** `count` needs no key; every other operation reads one Field. */
@@ -12279,9 +12305,37 @@ interface RecordsClient {
12279
12305
  record_id: Uuid;
12280
12306
  organization_id?: Uuid | null;
12281
12307
  }): Promise<RecordsResult<ReadValue[]>>;
12308
+ /**
12309
+ * Metadata search across EVERY organization the person belongs to (`config.organizationIds`;
12310
+ * `organization_id` narrows to one). Each match carries its own `organization_id`; an
12311
+ * organization whose store is off or refuses is listed in `unavailable` WITH its reason and
12312
+ * never hides the rest. Only when NO organization answers is the whole search a failure (the
12313
+ * first refusal). The door itself is not in the store's catalogue yet, so today every
12314
+ * organization refuses in the door-absent sentence — named, never faked.
12315
+ */
12282
12316
  metadataSearch(args: {
12283
12317
  text: string;
12284
- }): Promise<RecordsResult<never>>;
12318
+ organization_id?: Uuid | null;
12319
+ }): Promise<RecordsResult<MetadataSearchAnswer>>;
12320
+ /**
12321
+ * WHICH of the person's organizations can this client use right now? Asks every organization
12322
+ * in scope (`organization_id` narrows to one) whether its record store is open, so a host can
12323
+ * say "organization X is unavailable: <reason>" instead of silently dropping it. Organization
12324
+ * order is the person's own, never completion order.
12325
+ */
12326
+ organizationsOpen(args?: {
12327
+ organization_id?: Uuid | null;
12328
+ }): Promise<RecordsResult<OrganizationsOpenAnswer>>;
12329
+ /**
12330
+ * The organization that OWNS a table or record — the one a write to it must run in. A
12331
+ * `table_id` is looked up across all the person's organizations; a `record_id` is opened in its
12332
+ * own. `null` = found in none of them. Never a permission: the store's doors still decide.
12333
+ */
12334
+ ownerOrganization(args: {
12335
+ table_id?: Uuid | null;
12336
+ record_id?: Uuid | null;
12337
+ organization_id?: Uuid | null;
12338
+ }): Promise<RecordsResult<Uuid | null>>;
12285
12339
  /**
12286
12340
  * Mint an embed token. `mode` is `read` OR `write` and never both — the store
12287
12341
  * refuses a token carrying two decisions. The secret comes back ONCE, because
@@ -39727,6 +39727,13 @@ function createRecordsClient(config) {
39727
39727
  }
39728
39728
  return last;
39729
39729
  }
39730
+ const spans = (filter) => Boolean(filter) || (config.organizationIds?.length ?? 0) > 0;
39731
+ const metadataSearchDoor = () => {
39732
+ const refusal = doorAbsent(DOORS.metadataSearch);
39733
+ scream({ code: refusal.code, message: refusal.message, ...refusal.hint ? { hint: refusal.hint } : {}, where: "metadataSearch" });
39734
+ return err(refusal);
39735
+ };
39736
+ const unavailableReason = (error) => `${error.message}${error.hint ? ` ${error.hint}` : ""}`;
39730
39737
  const client = {
39731
39738
  config,
39732
39739
  async recordWrite({ table_id, data }) {
@@ -41056,7 +41063,23 @@ function createRecordsClient(config) {
41056
41063
  "dashboardDelete"
41057
41064
  );
41058
41065
  },
41059
- async recordAggregate({ table_id, groupBy, measures, bucket, filter, limit, compare }) {
41066
+ async recordAggregate({ table_id, organization_id, groupBy, measures, bucket, filter, limit, compare }) {
41067
+ if (spans(organization_id)) {
41068
+ const owner = await client.ownerOrganization({ table_id, ...organization_id ? { organization_id } : {} });
41069
+ const target = owner.ok && owner.data ? owner.data : organization_id ?? org;
41070
+ if (target !== org) {
41071
+ const answered = await clientFor(target).recordAggregate({
41072
+ table_id,
41073
+ ...groupBy ? { groupBy } : {},
41074
+ ...measures ? { measures } : {},
41075
+ ...bucket !== void 0 ? { bucket } : {},
41076
+ ...filter ? { filter } : {},
41077
+ ...limit !== void 0 ? { limit } : {},
41078
+ ...compare !== void 0 ? { compare } : {}
41079
+ });
41080
+ return answered.ok ? ok(answered.data.map((row) => ({ ...row, organization_id: target }))) : answered;
41081
+ }
41082
+ }
41060
41083
  return callDoor(
41061
41084
  DOORS.recordAggregate,
41062
41085
  {
@@ -41972,10 +41995,80 @@ function createRecordsClient(config) {
41972
41995
  "ruleDeclare"
41973
41996
  );
41974
41997
  },
41975
- async metadataSearch() {
41976
- const refusal = doorAbsent(DOORS.metadataSearch);
41977
- scream({ code: refusal.code, message: refusal.message, ...refusal.hint ? { hint: refusal.hint } : {}, where: "metadataSearch" });
41978
- return err(refusal);
41998
+ async metadataSearch({ text, organization_id }) {
41999
+ if (spans(organization_id) && readOrgs(organization_id).length > 0) {
42000
+ const targets = readOrgs(organization_id).filter((t) => t !== null);
42001
+ const unavailable = [];
42002
+ const answers = await Promise.all(
42003
+ targets.map(async (t) => {
42004
+ const sub = clientFor(t);
42005
+ const open = await sub.storeIsOpen();
42006
+ if (!open.ok) {
42007
+ unavailable.push({ organization_id: t, reason: unavailableReason(open.error) });
42008
+ return null;
42009
+ }
42010
+ if (!open.data) {
42011
+ unavailable.push({ organization_id: t, reason: "The custom data store is switched off for this organization." });
42012
+ return null;
42013
+ }
42014
+ const found = metadataSearchDoor();
42015
+ if (!found.ok) {
42016
+ unavailable.push({ organization_id: t, reason: unavailableReason(found.error) });
42017
+ return { t, found };
42018
+ }
42019
+ return { t, found };
42020
+ })
42021
+ );
42022
+ const matches = [];
42023
+ const covered = [];
42024
+ let firstRefusal = null;
42025
+ for (const answer of answers) {
42026
+ if (!answer) continue;
42027
+ if (!answer.found.ok) {
42028
+ firstRefusal ??= answer.found.error;
42029
+ continue;
42030
+ }
42031
+ covered.push(answer.t);
42032
+ for (const m of answer.found.data.matches) matches.push({ ...m, organization_id: answer.t });
42033
+ }
42034
+ const inOrder = (a, b) => targets.indexOf(a.organization_id) - targets.indexOf(b.organization_id);
42035
+ unavailable.sort(inOrder);
42036
+ if (covered.length === 0 && firstRefusal) return err(firstRefusal);
42037
+ return ok({ matches, organizations_covered: covered, unavailable });
42038
+ }
42039
+ return metadataSearchDoor();
42040
+ },
42041
+ async organizationsOpen(args) {
42042
+ const targets = readOrgs(args?.organization_id).filter((t) => t !== null);
42043
+ const open = [];
42044
+ const unavailable = [];
42045
+ await Promise.all(
42046
+ targets.map(async (t) => {
42047
+ const result = await clientFor(t).storeIsOpen();
42048
+ if (!result.ok) unavailable.push({ organization_id: t, reason: unavailableReason(result.error) });
42049
+ else if (!result.data)
42050
+ unavailable.push({ organization_id: t, reason: "The custom data store is switched off for this organization." });
42051
+ else open.push(t);
42052
+ })
42053
+ );
42054
+ const position = (id) => targets.indexOf(id);
42055
+ open.sort((a, b) => position(a) - position(b));
42056
+ unavailable.sort((a, b) => position(a.organization_id) - position(b.organization_id));
42057
+ return ok({ open, unavailable });
42058
+ },
42059
+ async ownerOrganization({ table_id, record_id, organization_id }) {
42060
+ if (table_id) {
42061
+ const read = await client.tableRead({ table_id, ...organization_id ? { organization_id } : {} });
42062
+ if (!read.ok) return err(read.error);
42063
+ return ok(read.data ? read.data.organization_id ?? null : null);
42064
+ }
42065
+ if (record_id) {
42066
+ const read = await client.recordRead({ record_id, ...organization_id ? { organization_id } : {} });
42067
+ if (!read.ok) return ok(null);
42068
+ const targets = readOrgs(organization_id);
42069
+ return ok(recordOrgs.get(record_id) ?? organization_id ?? (targets.length === 1 ? targets[0] ?? null : null));
42070
+ }
42071
+ return ok(null);
41979
42072
  },
41980
42073
  async fieldPropose() {
41981
42074
  const refusal = doorAbsent(DOORS.fieldPropose);