@ai-matrx/auth 0.0.0 → 0.1.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/LICENSE +21 -0
  3. package/README.md +31 -1
  4. package/dist/auth-cookie-DWx6Q3xo.d.cts +168 -0
  5. package/dist/auth-cookie-DWx6Q3xo.d.ts +168 -0
  6. package/dist/cookies.cjs +233 -0
  7. package/dist/cookies.d.cts +109 -0
  8. package/dist/cookies.d.ts +109 -0
  9. package/dist/cookies.js +210 -0
  10. package/dist/credentials.cjs +68 -0
  11. package/dist/credentials.d.cts +82 -0
  12. package/dist/credentials.d.ts +82 -0
  13. package/dist/credentials.js +47 -0
  14. package/dist/index.cjs +189 -0
  15. package/dist/index.d.cts +86 -0
  16. package/dist/index.d.ts +86 -0
  17. package/dist/index.js +166 -0
  18. package/dist/next.cjs +655 -0
  19. package/dist/next.d.cts +389 -0
  20. package/dist/next.d.ts +389 -0
  21. package/dist/next.js +637 -0
  22. package/dist/node.cjs +61 -0
  23. package/dist/node.d.cts +33 -0
  24. package/dist/node.d.ts +33 -0
  25. package/dist/node.js +36 -0
  26. package/dist/organizations.cjs +389 -0
  27. package/dist/organizations.d.cts +226 -0
  28. package/dist/organizations.d.ts +226 -0
  29. package/dist/organizations.js +366 -0
  30. package/dist/react.cjs +206 -0
  31. package/dist/react.d.cts +163 -0
  32. package/dist/react.d.ts +163 -0
  33. package/dist/react.js +186 -0
  34. package/dist/session-DhRO4euL.d.cts +102 -0
  35. package/dist/session-DhRO4euL.d.ts +102 -0
  36. package/dist/session-identity.cjs +125 -0
  37. package/dist/session-identity.d.cts +100 -0
  38. package/dist/session-identity.d.ts +100 -0
  39. package/dist/session-identity.js +104 -0
  40. package/dist/testing.cjs +67 -0
  41. package/dist/testing.d.cts +38 -0
  42. package/dist/testing.d.ts +38 -0
  43. package/dist/testing.js +46 -0
  44. package/package.json +186 -6
@@ -0,0 +1,226 @@
1
+ import { SupabaseClient } from '@supabase/supabase-js';
2
+ import { A as AuthClient } from './session-DhRO4euL.cjs';
3
+
4
+ /**
5
+ * @ai-matrx/auth/organizations (moved from @ai-matrx/data/organizations, 2026-10-09) — THE active-organization ladder and the account's organization
6
+ * facts, for every Matrx client (the web, Matrx 2, the extension, Studio).
7
+ *
8
+ * 🚨 ONE ACTIVE ORGANIZATION, SET ONCE WHEN THE APP LOADS, NEVER NONE (Arman, 2026-10-07;
9
+ * organizations STATE rules 10–15). The ladder picks what the UI OPENS to; from then on every
10
+ * request carries the one active organization the person can see. Reads never filter by it.
11
+ *
12
+ * THE RUNGS, each kept only if it is a CURRENT membership:
13
+ * 1. HELD — what this tab (web) or this running app (desktop, in memory) already works in;
14
+ * 2. LAST ACTIVE — `users.user_preferences.last_active_organization_id` (follows the person
15
+ * across devices: STATE rule 12);
16
+ * 3. START-UP — `users.user_preferences.startup_organization_id` (the "Start-up organization"
17
+ * setting);
18
+ * 4. FIRST — the oldest active membership.
19
+ * Zero memberships → null. A failed read THROWS (the host shows "could not check" and a retry,
20
+ * never a guess). The ladder's own answer never writes anything (rule 14): only a person's click
21
+ * writes last active.
22
+ *
23
+ * Hosts with extra rungs keep them (the web's `?org=` link rung) and call the pure core here.
24
+ */
25
+
26
+ /** One organization the person is an active member of, as every picker draws it. */
27
+ interface OrganizationMembership {
28
+ readonly id: string;
29
+ readonly name: string;
30
+ readonly slug: string | null;
31
+ /** The database-validated 2–3 letters, when the organization has them. */
32
+ readonly abbreviation: string | null;
33
+ readonly logoUrl: string | null;
34
+ /** owner | admin | member (the membership's role). */
35
+ readonly role: string;
36
+ /** The person created it (drawn under the favourites, before the rest). */
37
+ readonly isOwn: boolean;
38
+ /** Classified in `iam.organizations.settings.test_fixture` — never guessed from a name. */
39
+ readonly isTestFixture: boolean;
40
+ /** When the membership began (ISO): the oldest is rung 4. */
41
+ readonly joinedAt: string;
42
+ }
43
+ interface AccountOrganizationChoices {
44
+ readonly lastActiveOrganizationId: string | null;
45
+ readonly startupOrganizationId: string | null;
46
+ }
47
+ type OrganizationRung = "held" | "last-active" | "startup" | "first";
48
+ interface ResolvedOrganization<T extends {
49
+ id: string;
50
+ }> {
51
+ readonly organization: T;
52
+ /** Which rung answered (for logs and tests; never shown as a choice). */
53
+ readonly rung: OrganizationRung;
54
+ }
55
+ interface LadderInput<T extends {
56
+ id: string;
57
+ }> {
58
+ readonly memberships: readonly T[];
59
+ readonly held?: string | null;
60
+ readonly account?: AccountOrganizationChoices | null;
61
+ /** Rung 4: the oldest active membership. Omitted: the oldest by `joinedAt`, else the first given. */
62
+ readonly first?: T | null;
63
+ }
64
+ /**
65
+ * THE LADDER. Pure: no read, no write. Null only when there are no memberships.
66
+ */
67
+ declare function resolveActiveOrganization<T extends {
68
+ id: string;
69
+ joinedAt?: string;
70
+ }>(input: LadderInput<T>): ResolvedOrganization<T> | null;
71
+ /**
72
+ * The ladder with its reads deferred: the account's choices are read only when no held
73
+ * organization answers, and the first membership only when nothing else does. A failed read
74
+ * throws through.
75
+ */
76
+ declare function resolveActiveOrganizationLazily<T extends {
77
+ id: string;
78
+ joinedAt?: string;
79
+ }>(input: {
80
+ readonly memberships: readonly T[];
81
+ readonly held?: string | null;
82
+ readonly readAccount: () => Promise<AccountOrganizationChoices>;
83
+ readonly first?: () => Promise<T | null>;
84
+ }): Promise<ResolvedOrganization<T> | null>;
85
+ /** The oldest membership by `joinedAt` (ties and missing dates keep the given order). */
86
+ declare function oldestMembership<T extends {
87
+ joinedAt?: string;
88
+ }>(memberships: readonly T[]): T | null;
89
+ /**
90
+ * THE ORDER every organization list uses (the shared OrganizationPicker's): favourites, then the
91
+ * person's own organizations, then the rest; inside each band the given order is kept.
92
+ */
93
+ declare function orderOrganizations<T extends {
94
+ id: string;
95
+ isOwn?: boolean;
96
+ }>(organizations: readonly T[], favouriteIds: readonly string[]): T[];
97
+ /** Search over the two things a person types: the name and the abbreviation. */
98
+ declare function organizationMatches(org: {
99
+ name: string;
100
+ abbreviation?: string | null;
101
+ }, query: string): boolean;
102
+ /** Any supabase-js client (typed or not). */
103
+ type OrganizationsDb = SupabaseClient<any, any, any>;
104
+ /**
105
+ * Every organization the signed-in person is an ACTIVE member of (archived organizations left
106
+ * out), through the canonical membership door (`mbr_for_user`, paged) and the organizations'
107
+ * own rows. Throws when either read fails; [] when there are none. Ordered by name.
108
+ */
109
+ declare function readMemberships(db: OrganizationsDb, userId: string): Promise<OrganizationMembership[]>;
110
+ interface ChoicesRow {
111
+ last_active_organization_id: string | null;
112
+ startup_organization_id: string | null;
113
+ }
114
+ /** The two columns as the ladder's input. A person with no preferences row has neither. */
115
+ declare function toAccountOrganizationChoices(row: Partial<ChoicesRow> | null | undefined): AccountOrganizationChoices;
116
+ /**
117
+ * The account's last active and start-up organizations. 🚨 ONLY THE LADDER READS THESE: they
118
+ * choose what the shell opens to and nothing else. Throws when the read fails.
119
+ */
120
+ declare function readAccountOrganizationChoices(db: OrganizationsDb, userId: string): Promise<AccountOrganizationChoices>;
121
+ /** Save where the person now works, for their next load on any device. Only a click calls this. Throws on refusal. */
122
+ declare function writeLastActiveOrganization(db: OrganizationsDb, organizationId: string): Promise<void>;
123
+ /** Set (or clear, with null) the Start-up organization setting. Throws on refusal. */
124
+ declare function writeStartupOrganization(db: OrganizationsDb, organizationId: string | null): Promise<void>;
125
+ /** The favourites store's entity type (`platform.user_entity_state`). */
126
+ declare const ORGANIZATION_ENTITY_TYPE = "organization";
127
+ /**
128
+ * The person's favourite organizations: the platform's per-person flag store
129
+ * (`public.ues_list`, kind favorite), the same store agent and model favourites use. Throws on
130
+ * failure.
131
+ */
132
+ declare function readOrganizationFavourites(db: OrganizationsDb): Promise<string[]>;
133
+ /** Star (true) or unstar (false) one organization. `ues_set` refuses one the person cannot open. Throws on refusal. */
134
+ declare function setOrganizationFavourite(db: OrganizationsDb, organizationId: string, favourite: boolean): Promise<void>;
135
+ /**
136
+ * What the AI Matrx server's refusal of a request's organization means for the ladder
137
+ * (`matrx_connect` AuthMiddleware._organization_admission_words via `organization_free.py`):
138
+ * - 400 with `detail.organization_admission` "denied" (not a member), "unknown" (no such
139
+ * organization) or "archived" → the organization is gone for this person: re-run the ladder;
140
+ * - 503 with "unverifiable" (the lookup failed; Retry-After 5) → keep it and retry;
141
+ * - anything else → not an organization refusal.
142
+ */
143
+ type OrganizationRefusal = "lapsed" | "retry" | null;
144
+ declare function classifyOrganizationRefusal(status: number, body: unknown): OrganizationRefusal;
145
+
146
+ /**
147
+ * THE ACTIVE ORGANIZATION — one per tab / running app, set once when the app loads, never none
148
+ * while the person has a membership (Arman, 2026-10-07; organizations STATE rules 10–15).
149
+ *
150
+ * const active = createActiveOrganization({ client: supabase });
151
+ * await active.load(); // the ladder: held → last active → start-up → first
152
+ * const orgId = await active.ensureOrgId(); // HELD until the ladder answers; never a guess
153
+ * await active.switchTo(otherId); // a person's click: saves last active for next load
154
+ *
155
+ * HELD-REQUEST SEMANTICS. A write or server call that needs the organization asks
156
+ * `ensureOrgId()`: while the ladder is still resolving the call WAITS (it is held, never sent
157
+ * without an organization and never sent with a guessed one); once resolved it gets the one
158
+ * active organization; with zero memberships or a failed read it is REFUSED with a named error.
159
+ * Acting on a record passes that record's own organization (`ensureOrgId(record.organization_id)`),
160
+ * which wins and never switches the active one.
161
+ *
162
+ * 🚨 NEVER A LIST FILTER. Reads show everything the person can see across all their
163
+ * organizations; nothing here narrows a read. The active organization is only where new things
164
+ * are saved and which organization a server call runs in.
165
+ *
166
+ * The held id is identity-keyed: a different person signing in on the same device never inherits
167
+ * the last person's organization, and a sign-out resets the store.
168
+ */
169
+
170
+ /** Where this tab / app remembers its organization between reloads (identity-keyed). */
171
+ interface HeldOrganizationStore {
172
+ read(userId: string): string | null | Promise<string | null>;
173
+ write(userId: string, organizationId: string): void | Promise<void>;
174
+ clear(): void | Promise<void>;
175
+ }
176
+ type ActiveOrganizationStatus = "idle" | "resolving" | "ready" | "none" | "error";
177
+ interface ActiveOrganizationSnapshot {
178
+ readonly status: ActiveOrganizationStatus;
179
+ readonly userId: string | null;
180
+ readonly organizationId: string | null;
181
+ readonly organization: OrganizationMembership | null;
182
+ readonly memberships: readonly OrganizationMembership[];
183
+ /** Which ladder rung answered (or "switched" after a person's click). */
184
+ readonly rung: OrganizationRung | "switched" | null;
185
+ readonly error: string | null;
186
+ }
187
+ declare class NoActiveOrganizationError extends Error {
188
+ readonly reason: "signed-out" | "no-memberships" | "unreadable";
189
+ constructor(reason: "signed-out" | "no-memberships" | "unreadable", detail?: string);
190
+ }
191
+ declare class NotAMemberError extends Error {
192
+ readonly organizationId: string;
193
+ constructor(organizationId: string);
194
+ }
195
+ interface ActiveOrganization {
196
+ snapshot(): ActiveOrganizationSnapshot;
197
+ subscribe(listener: () => void): () => void;
198
+ /** Run the ladder (again). Resolves with the settled snapshot; never throws. */
199
+ load(): Promise<ActiveOrganizationSnapshot>;
200
+ /** The active organization id now, or null while resolving / with none. Sync (api's getter). */
201
+ current(): string | null;
202
+ /** THE HELD REQUEST: the record's own organization, else the active one once resolved. */
203
+ ensureOrgId(recordOrganizationId?: string | null): Promise<string>;
204
+ /** A person's click: switch, remember for this tab, save as last active for the next load. */
205
+ switchTo(organizationId: string): Promise<void>;
206
+ /** Feed a server refusal in: a lapsed organization re-runs the ladder. Returns the class. */
207
+ handleRefusal(status: number, body: unknown): Promise<"lapsed" | "retry" | null>;
208
+ dispose(): void;
209
+ }
210
+ interface CreateActiveOrganizationOptions {
211
+ /** The person's Supabase client (any supabase-js client). */
212
+ readonly client: OrganizationsDb & AuthClient;
213
+ /** Where this tab/app holds its choice. Default: browser `sessionStorage` (per tab), else memory. */
214
+ readonly held?: HeldOrganizationStore | undefined;
215
+ /** An extra first rung (the web's `?org=` link). Kept only if it is a current membership. */
216
+ readonly linked?: (() => string | null | undefined) | undefined;
217
+ /** Load as soon as it is created (default true). */
218
+ readonly autoLoad?: boolean | undefined;
219
+ }
220
+ /** A held store over a `Storage` (browser `sessionStorage` = per tab), identity-keyed. */
221
+ declare function storageHeldOrganization(storage: Pick<Storage, "getItem" | "setItem" | "removeItem">, key?: string): HeldOrganizationStore;
222
+ /** Held in memory: the running app's own choice (desktop, scripts, tests). */
223
+ declare function memoryHeldOrganization(): HeldOrganizationStore;
224
+ declare function createActiveOrganization(options: CreateActiveOrganizationOptions): ActiveOrganization;
225
+
226
+ export { type AccountOrganizationChoices, type ActiveOrganization, type ActiveOrganizationSnapshot, type ActiveOrganizationStatus, type CreateActiveOrganizationOptions, type HeldOrganizationStore, type LadderInput, NoActiveOrganizationError, NotAMemberError, ORGANIZATION_ENTITY_TYPE, type OrganizationMembership, type OrganizationRefusal, type OrganizationRung, type OrganizationsDb, type ResolvedOrganization, classifyOrganizationRefusal, createActiveOrganization, memoryHeldOrganization, oldestMembership, orderOrganizations, organizationMatches, readAccountOrganizationChoices, readMemberships, readOrganizationFavourites, resolveActiveOrganization, resolveActiveOrganizationLazily, setOrganizationFavourite, storageHeldOrganization, toAccountOrganizationChoices, writeLastActiveOrganization, writeStartupOrganization };
@@ -0,0 +1,226 @@
1
+ import { SupabaseClient } from '@supabase/supabase-js';
2
+ import { A as AuthClient } from './session-DhRO4euL.js';
3
+
4
+ /**
5
+ * @ai-matrx/auth/organizations (moved from @ai-matrx/data/organizations, 2026-10-09) — THE active-organization ladder and the account's organization
6
+ * facts, for every Matrx client (the web, Matrx 2, the extension, Studio).
7
+ *
8
+ * 🚨 ONE ACTIVE ORGANIZATION, SET ONCE WHEN THE APP LOADS, NEVER NONE (Arman, 2026-10-07;
9
+ * organizations STATE rules 10–15). The ladder picks what the UI OPENS to; from then on every
10
+ * request carries the one active organization the person can see. Reads never filter by it.
11
+ *
12
+ * THE RUNGS, each kept only if it is a CURRENT membership:
13
+ * 1. HELD — what this tab (web) or this running app (desktop, in memory) already works in;
14
+ * 2. LAST ACTIVE — `users.user_preferences.last_active_organization_id` (follows the person
15
+ * across devices: STATE rule 12);
16
+ * 3. START-UP — `users.user_preferences.startup_organization_id` (the "Start-up organization"
17
+ * setting);
18
+ * 4. FIRST — the oldest active membership.
19
+ * Zero memberships → null. A failed read THROWS (the host shows "could not check" and a retry,
20
+ * never a guess). The ladder's own answer never writes anything (rule 14): only a person's click
21
+ * writes last active.
22
+ *
23
+ * Hosts with extra rungs keep them (the web's `?org=` link rung) and call the pure core here.
24
+ */
25
+
26
+ /** One organization the person is an active member of, as every picker draws it. */
27
+ interface OrganizationMembership {
28
+ readonly id: string;
29
+ readonly name: string;
30
+ readonly slug: string | null;
31
+ /** The database-validated 2–3 letters, when the organization has them. */
32
+ readonly abbreviation: string | null;
33
+ readonly logoUrl: string | null;
34
+ /** owner | admin | member (the membership's role). */
35
+ readonly role: string;
36
+ /** The person created it (drawn under the favourites, before the rest). */
37
+ readonly isOwn: boolean;
38
+ /** Classified in `iam.organizations.settings.test_fixture` — never guessed from a name. */
39
+ readonly isTestFixture: boolean;
40
+ /** When the membership began (ISO): the oldest is rung 4. */
41
+ readonly joinedAt: string;
42
+ }
43
+ interface AccountOrganizationChoices {
44
+ readonly lastActiveOrganizationId: string | null;
45
+ readonly startupOrganizationId: string | null;
46
+ }
47
+ type OrganizationRung = "held" | "last-active" | "startup" | "first";
48
+ interface ResolvedOrganization<T extends {
49
+ id: string;
50
+ }> {
51
+ readonly organization: T;
52
+ /** Which rung answered (for logs and tests; never shown as a choice). */
53
+ readonly rung: OrganizationRung;
54
+ }
55
+ interface LadderInput<T extends {
56
+ id: string;
57
+ }> {
58
+ readonly memberships: readonly T[];
59
+ readonly held?: string | null;
60
+ readonly account?: AccountOrganizationChoices | null;
61
+ /** Rung 4: the oldest active membership. Omitted: the oldest by `joinedAt`, else the first given. */
62
+ readonly first?: T | null;
63
+ }
64
+ /**
65
+ * THE LADDER. Pure: no read, no write. Null only when there are no memberships.
66
+ */
67
+ declare function resolveActiveOrganization<T extends {
68
+ id: string;
69
+ joinedAt?: string;
70
+ }>(input: LadderInput<T>): ResolvedOrganization<T> | null;
71
+ /**
72
+ * The ladder with its reads deferred: the account's choices are read only when no held
73
+ * organization answers, and the first membership only when nothing else does. A failed read
74
+ * throws through.
75
+ */
76
+ declare function resolveActiveOrganizationLazily<T extends {
77
+ id: string;
78
+ joinedAt?: string;
79
+ }>(input: {
80
+ readonly memberships: readonly T[];
81
+ readonly held?: string | null;
82
+ readonly readAccount: () => Promise<AccountOrganizationChoices>;
83
+ readonly first?: () => Promise<T | null>;
84
+ }): Promise<ResolvedOrganization<T> | null>;
85
+ /** The oldest membership by `joinedAt` (ties and missing dates keep the given order). */
86
+ declare function oldestMembership<T extends {
87
+ joinedAt?: string;
88
+ }>(memberships: readonly T[]): T | null;
89
+ /**
90
+ * THE ORDER every organization list uses (the shared OrganizationPicker's): favourites, then the
91
+ * person's own organizations, then the rest; inside each band the given order is kept.
92
+ */
93
+ declare function orderOrganizations<T extends {
94
+ id: string;
95
+ isOwn?: boolean;
96
+ }>(organizations: readonly T[], favouriteIds: readonly string[]): T[];
97
+ /** Search over the two things a person types: the name and the abbreviation. */
98
+ declare function organizationMatches(org: {
99
+ name: string;
100
+ abbreviation?: string | null;
101
+ }, query: string): boolean;
102
+ /** Any supabase-js client (typed or not). */
103
+ type OrganizationsDb = SupabaseClient<any, any, any>;
104
+ /**
105
+ * Every organization the signed-in person is an ACTIVE member of (archived organizations left
106
+ * out), through the canonical membership door (`mbr_for_user`, paged) and the organizations'
107
+ * own rows. Throws when either read fails; [] when there are none. Ordered by name.
108
+ */
109
+ declare function readMemberships(db: OrganizationsDb, userId: string): Promise<OrganizationMembership[]>;
110
+ interface ChoicesRow {
111
+ last_active_organization_id: string | null;
112
+ startup_organization_id: string | null;
113
+ }
114
+ /** The two columns as the ladder's input. A person with no preferences row has neither. */
115
+ declare function toAccountOrganizationChoices(row: Partial<ChoicesRow> | null | undefined): AccountOrganizationChoices;
116
+ /**
117
+ * The account's last active and start-up organizations. 🚨 ONLY THE LADDER READS THESE: they
118
+ * choose what the shell opens to and nothing else. Throws when the read fails.
119
+ */
120
+ declare function readAccountOrganizationChoices(db: OrganizationsDb, userId: string): Promise<AccountOrganizationChoices>;
121
+ /** Save where the person now works, for their next load on any device. Only a click calls this. Throws on refusal. */
122
+ declare function writeLastActiveOrganization(db: OrganizationsDb, organizationId: string): Promise<void>;
123
+ /** Set (or clear, with null) the Start-up organization setting. Throws on refusal. */
124
+ declare function writeStartupOrganization(db: OrganizationsDb, organizationId: string | null): Promise<void>;
125
+ /** The favourites store's entity type (`platform.user_entity_state`). */
126
+ declare const ORGANIZATION_ENTITY_TYPE = "organization";
127
+ /**
128
+ * The person's favourite organizations: the platform's per-person flag store
129
+ * (`public.ues_list`, kind favorite), the same store agent and model favourites use. Throws on
130
+ * failure.
131
+ */
132
+ declare function readOrganizationFavourites(db: OrganizationsDb): Promise<string[]>;
133
+ /** Star (true) or unstar (false) one organization. `ues_set` refuses one the person cannot open. Throws on refusal. */
134
+ declare function setOrganizationFavourite(db: OrganizationsDb, organizationId: string, favourite: boolean): Promise<void>;
135
+ /**
136
+ * What the AI Matrx server's refusal of a request's organization means for the ladder
137
+ * (`matrx_connect` AuthMiddleware._organization_admission_words via `organization_free.py`):
138
+ * - 400 with `detail.organization_admission` "denied" (not a member), "unknown" (no such
139
+ * organization) or "archived" → the organization is gone for this person: re-run the ladder;
140
+ * - 503 with "unverifiable" (the lookup failed; Retry-After 5) → keep it and retry;
141
+ * - anything else → not an organization refusal.
142
+ */
143
+ type OrganizationRefusal = "lapsed" | "retry" | null;
144
+ declare function classifyOrganizationRefusal(status: number, body: unknown): OrganizationRefusal;
145
+
146
+ /**
147
+ * THE ACTIVE ORGANIZATION — one per tab / running app, set once when the app loads, never none
148
+ * while the person has a membership (Arman, 2026-10-07; organizations STATE rules 10–15).
149
+ *
150
+ * const active = createActiveOrganization({ client: supabase });
151
+ * await active.load(); // the ladder: held → last active → start-up → first
152
+ * const orgId = await active.ensureOrgId(); // HELD until the ladder answers; never a guess
153
+ * await active.switchTo(otherId); // a person's click: saves last active for next load
154
+ *
155
+ * HELD-REQUEST SEMANTICS. A write or server call that needs the organization asks
156
+ * `ensureOrgId()`: while the ladder is still resolving the call WAITS (it is held, never sent
157
+ * without an organization and never sent with a guessed one); once resolved it gets the one
158
+ * active organization; with zero memberships or a failed read it is REFUSED with a named error.
159
+ * Acting on a record passes that record's own organization (`ensureOrgId(record.organization_id)`),
160
+ * which wins and never switches the active one.
161
+ *
162
+ * 🚨 NEVER A LIST FILTER. Reads show everything the person can see across all their
163
+ * organizations; nothing here narrows a read. The active organization is only where new things
164
+ * are saved and which organization a server call runs in.
165
+ *
166
+ * The held id is identity-keyed: a different person signing in on the same device never inherits
167
+ * the last person's organization, and a sign-out resets the store.
168
+ */
169
+
170
+ /** Where this tab / app remembers its organization between reloads (identity-keyed). */
171
+ interface HeldOrganizationStore {
172
+ read(userId: string): string | null | Promise<string | null>;
173
+ write(userId: string, organizationId: string): void | Promise<void>;
174
+ clear(): void | Promise<void>;
175
+ }
176
+ type ActiveOrganizationStatus = "idle" | "resolving" | "ready" | "none" | "error";
177
+ interface ActiveOrganizationSnapshot {
178
+ readonly status: ActiveOrganizationStatus;
179
+ readonly userId: string | null;
180
+ readonly organizationId: string | null;
181
+ readonly organization: OrganizationMembership | null;
182
+ readonly memberships: readonly OrganizationMembership[];
183
+ /** Which ladder rung answered (or "switched" after a person's click). */
184
+ readonly rung: OrganizationRung | "switched" | null;
185
+ readonly error: string | null;
186
+ }
187
+ declare class NoActiveOrganizationError extends Error {
188
+ readonly reason: "signed-out" | "no-memberships" | "unreadable";
189
+ constructor(reason: "signed-out" | "no-memberships" | "unreadable", detail?: string);
190
+ }
191
+ declare class NotAMemberError extends Error {
192
+ readonly organizationId: string;
193
+ constructor(organizationId: string);
194
+ }
195
+ interface ActiveOrganization {
196
+ snapshot(): ActiveOrganizationSnapshot;
197
+ subscribe(listener: () => void): () => void;
198
+ /** Run the ladder (again). Resolves with the settled snapshot; never throws. */
199
+ load(): Promise<ActiveOrganizationSnapshot>;
200
+ /** The active organization id now, or null while resolving / with none. Sync (api's getter). */
201
+ current(): string | null;
202
+ /** THE HELD REQUEST: the record's own organization, else the active one once resolved. */
203
+ ensureOrgId(recordOrganizationId?: string | null): Promise<string>;
204
+ /** A person's click: switch, remember for this tab, save as last active for the next load. */
205
+ switchTo(organizationId: string): Promise<void>;
206
+ /** Feed a server refusal in: a lapsed organization re-runs the ladder. Returns the class. */
207
+ handleRefusal(status: number, body: unknown): Promise<"lapsed" | "retry" | null>;
208
+ dispose(): void;
209
+ }
210
+ interface CreateActiveOrganizationOptions {
211
+ /** The person's Supabase client (any supabase-js client). */
212
+ readonly client: OrganizationsDb & AuthClient;
213
+ /** Where this tab/app holds its choice. Default: browser `sessionStorage` (per tab), else memory. */
214
+ readonly held?: HeldOrganizationStore | undefined;
215
+ /** An extra first rung (the web's `?org=` link). Kept only if it is a current membership. */
216
+ readonly linked?: (() => string | null | undefined) | undefined;
217
+ /** Load as soon as it is created (default true). */
218
+ readonly autoLoad?: boolean | undefined;
219
+ }
220
+ /** A held store over a `Storage` (browser `sessionStorage` = per tab), identity-keyed. */
221
+ declare function storageHeldOrganization(storage: Pick<Storage, "getItem" | "setItem" | "removeItem">, key?: string): HeldOrganizationStore;
222
+ /** Held in memory: the running app's own choice (desktop, scripts, tests). */
223
+ declare function memoryHeldOrganization(): HeldOrganizationStore;
224
+ declare function createActiveOrganization(options: CreateActiveOrganizationOptions): ActiveOrganization;
225
+
226
+ export { type AccountOrganizationChoices, type ActiveOrganization, type ActiveOrganizationSnapshot, type ActiveOrganizationStatus, type CreateActiveOrganizationOptions, type HeldOrganizationStore, type LadderInput, NoActiveOrganizationError, NotAMemberError, ORGANIZATION_ENTITY_TYPE, type OrganizationMembership, type OrganizationRefusal, type OrganizationRung, type OrganizationsDb, type ResolvedOrganization, classifyOrganizationRefusal, createActiveOrganization, memoryHeldOrganization, oldestMembership, orderOrganizations, organizationMatches, readAccountOrganizationChoices, readMemberships, readOrganizationFavourites, resolveActiveOrganization, resolveActiveOrganizationLazily, setOrganizationFavourite, storageHeldOrganization, toAccountOrganizationChoices, writeLastActiveOrganization, writeStartupOrganization };