@ai-matrx/auth 0.2.1 → 0.2.3
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 +32 -0
- package/dist/active-organization-CIvSdboO.d.cts +594 -0
- package/dist/active-organization-CIvSdboO.d.ts +594 -0
- package/dist/guest.cjs +213 -0
- package/dist/guest.d.cts +189 -0
- package/dist/guest.d.ts +189 -0
- package/dist/guest.js +192 -0
- package/dist/index.cjs +514 -112
- package/dist/index.d.cts +161 -5
- package/dist/index.d.ts +161 -5
- package/dist/index.js +515 -113
- package/dist/matrx-oauth-_Ji8X5Wm.d.cts +264 -0
- package/dist/matrx-oauth-_Ji8X5Wm.d.ts +264 -0
- package/dist/matrx-oauth.cjs +77 -10
- package/dist/matrx-oauth.d.cts +1 -181
- package/dist/matrx-oauth.d.ts +1 -181
- package/dist/matrx-oauth.js +77 -10
- package/dist/organizations.cjs +5 -3
- package/dist/organizations.d.cts +3 -475
- package/dist/organizations.d.ts +3 -475
- package/dist/organizations.js +5 -3
- package/dist/react.d.cts +2 -0
- package/dist/react.d.ts +2 -0
- package/package.json +12 -2
- package/dist/session-DdQiStp6.d.cts +0 -112
- package/dist/session-DdQiStp6.d.ts +0 -112
- package/dist/storage-Dqnye4KF.d.cts +0 -51
- package/dist/storage-Dqnye4KF.d.ts +0 -51
package/dist/organizations.d.ts
CHANGED
|
@@ -1,477 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
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
|
-
* Organization-question plumbing that is LOGIC, not store binding (moved from the website's
|
|
148
|
-
* `orgBootstrapGate`, `organizationRefusal`, `childOrganization`, `adminOrganization`).
|
|
149
|
-
* Pure: every host input is injected; no Redux, no Next, no window.
|
|
150
|
-
*/
|
|
151
|
-
/** How long a write may wait for the first answer before it is refused anyway. */
|
|
152
|
-
declare const ORGANIZATION_RESOLUTION_TIMEOUT_MS = 10000;
|
|
153
|
-
interface OrganizationBootstrapGate {
|
|
154
|
-
isResolved(): boolean;
|
|
155
|
-
/** The organization question has been answered (with an organization or without). Idempotent. */
|
|
156
|
-
markResolved(): void;
|
|
157
|
-
/** Wait for the answer or the timeout, whichever first. Always resolves; never rejects. */
|
|
158
|
-
whenResolved(timeoutMs?: number): Promise<void>;
|
|
159
|
-
/** Tests and in-place identity swaps only. */
|
|
160
|
-
reset(): void;
|
|
161
|
-
}
|
|
162
|
-
/** "Nobody has looked yet" is a race, not a fact: a held write waits on this, bounded. */
|
|
163
|
-
declare function createOrganizationBootstrapGate(defaultTimeoutMs?: number): OrganizationBootstrapGate;
|
|
164
|
-
/** True when an error body is the server's "you are not a member of the organization you carried". */
|
|
165
|
-
declare function isNotAMemberRefusal(body: unknown): boolean;
|
|
166
|
-
declare const NOT_A_MEMBER_RECHECK_GAP_MS = 30000;
|
|
167
|
-
interface NotAMemberRefusalNotice {
|
|
168
|
-
/** True when a recheck was started (at most one per gap, so a burst reads memberships once). */
|
|
169
|
-
notice(body: unknown, now?: number): boolean;
|
|
170
|
-
reset(): void;
|
|
171
|
-
}
|
|
172
|
-
declare function createNotAMemberRefusalNotice(recheck: () => void, gapMs?: number): NotAMemberRefusalNotice;
|
|
173
|
-
type ParentOrganization = string | {
|
|
174
|
-
organization_id?: string | null;
|
|
175
|
-
} | null | undefined;
|
|
176
|
-
/**
|
|
177
|
-
* A record created as part of an existing parent lives in the PARENT's organization; the explicit
|
|
178
|
-
* one applies only when the parent names none. A disagreement is announced (`onDisagree`,
|
|
179
|
-
* default console.warn), never obeyed.
|
|
180
|
-
*/
|
|
181
|
-
declare function resolveChildOrganizationId(parent: ParentOrganization, explicit?: string | null, label?: string, onDisagree?: (message: string) => void): string | null;
|
|
182
|
-
/** What the host knows about the admin lane. */
|
|
183
|
-
interface AdminSeatInput {
|
|
184
|
-
readonly laneOpen: boolean;
|
|
185
|
-
readonly organizationId?: string | null;
|
|
186
|
-
readonly organizationName?: string | null;
|
|
187
|
-
}
|
|
188
|
-
interface AdminSeat {
|
|
189
|
-
readonly id: string;
|
|
190
|
-
readonly name: string | null;
|
|
191
|
-
}
|
|
192
|
-
/** The admin organization in force right now, or null (off the lane, or none set). */
|
|
193
|
-
declare function resolveAdminSeat(input: AdminSeatInput | null | undefined): AdminSeat | null;
|
|
194
|
-
/** True when the admin seat differs from the person's own active organization (scope/project/task never ride across). */
|
|
195
|
-
declare function adminSeatReplacesUserContext(input: AdminSeatInput | null | undefined, personOrganizationId: string | null | undefined): boolean;
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* The query key, platform-wide. The server stamps this on every deep link it
|
|
199
|
-
* emits; `features/admin/users/components/OrganizationsAdminClient.tsx` has
|
|
200
|
-
* read it since before this module existed. One spelling, in one place.
|
|
201
|
-
*/
|
|
202
|
-
declare const LINK_ORGANIZATION_QUERY_KEY = "org";
|
|
203
|
-
type LinkOrganizationParam =
|
|
204
|
-
/** No link said anything about an organization. The ladder is untouched. */
|
|
205
|
-
{
|
|
206
|
-
kind: "absent";
|
|
207
|
-
}
|
|
208
|
-
/** Something was said and it is neither a uuid nor an address. Refused in words, never used. */
|
|
209
|
-
| {
|
|
210
|
-
kind: "malformed";
|
|
211
|
-
raw: string;
|
|
212
|
-
}
|
|
213
|
-
/** A well-formed organization id. Still has to survive the membership check. */
|
|
214
|
-
| {
|
|
215
|
-
kind: "named";
|
|
216
|
-
organizationId: string;
|
|
217
|
-
}
|
|
218
|
-
/** An organization's address (slug). Survives only by matching a membership's slug. */
|
|
219
|
-
| {
|
|
220
|
-
kind: "addressed";
|
|
221
|
-
slug: string;
|
|
222
|
-
};
|
|
223
|
-
/**
|
|
224
|
-
* Classify one bare `org=` value. The ONE rule for what a value may be, shared
|
|
225
|
-
* by the URL reader below and the boot resolver's bare-value path.
|
|
226
|
-
*/
|
|
227
|
-
declare function classifyLinkOrganizationValue(raw: string): LinkOrganizationParam;
|
|
228
|
-
/**
|
|
229
|
-
* Read the key out of a query string, a `URLSearchParams`, or a full URL.
|
|
230
|
-
* Never throws: a URL this cannot parse is `absent`, because a crash on
|
|
231
|
-
* arrival is a worse answer than the ladder's own.
|
|
232
|
-
*/
|
|
233
|
-
declare function readLinkOrganizationParam(source: string | URLSearchParams | null | undefined): LinkOrganizationParam;
|
|
234
|
-
interface LinkOrganizationMembership {
|
|
235
|
-
id: string;
|
|
236
|
-
name: string;
|
|
237
|
-
/** The organization's address, so an `addressed` link can be matched. */
|
|
238
|
-
slug?: string | null;
|
|
239
|
-
}
|
|
240
|
-
interface LinkOrganizationDecisionInput {
|
|
241
|
-
param: LinkOrganizationParam;
|
|
242
|
-
/** The person's LIVE memberships. The only authority on what they may open. */
|
|
243
|
-
memberships: ReadonlyArray<LinkOrganizationMembership>;
|
|
244
|
-
/** The organization they are working in right now, or null. */
|
|
245
|
-
currentOrganizationId: string | null;
|
|
246
|
-
currentOrganizationName?: string | null;
|
|
247
|
-
/**
|
|
248
|
-
* The knob `userPreferences.organization.switchOnLinkPrompt` — "switch
|
|
249
|
-
* organization when a link asks". DEFAULT ON, so an omitted value is `true`.
|
|
250
|
-
*/
|
|
251
|
-
switchWhenALinkAsks?: boolean;
|
|
252
|
-
/** The account they are signed in as, for the refusal sentence. */
|
|
253
|
-
signedInAs?: string | null;
|
|
254
|
-
/**
|
|
255
|
-
* The link's organization is one this person is not a member of but IS let into
|
|
256
|
-
* by a share they accepted (`lib/organizations/linkOrganizationAdmission.ts`).
|
|
257
|
-
* A share admits and never makes a member, so the answer is `admitted`: nobody
|
|
258
|
-
* is moved, nothing is refused, and the page opens it as that organization's
|
|
259
|
-
* guest. Omitted, the answer is exactly what it was before (VERIFIER-15 H4).
|
|
260
|
-
*/
|
|
261
|
-
admittedByAShare?: boolean;
|
|
262
|
-
/** Replace any of the default English sentences (a localising host). */
|
|
263
|
-
messages?: Partial<LinkOrganizationMessages>;
|
|
264
|
-
}
|
|
265
|
-
interface LinkOrganizationMessages {
|
|
266
|
-
notAMember(signedInAs?: string | null): string;
|
|
267
|
-
malformed(raw: string): string;
|
|
268
|
-
switched(toName: string, fromName: string | null): string;
|
|
269
|
-
offer(toName: string, fromName: string | null): string;
|
|
270
|
-
offerAction(toName: string): string;
|
|
271
|
-
}
|
|
272
|
-
type LinkOrganizationDecision =
|
|
273
|
-
/** Nothing to do. No toast, no change. */
|
|
274
|
-
{
|
|
275
|
-
kind: "no-link";
|
|
276
|
-
}
|
|
277
|
-
/** The link names the organization they are already in. Silent no-op. */
|
|
278
|
-
| {
|
|
279
|
-
kind: "already-current";
|
|
280
|
-
organizationId: string;
|
|
281
|
-
}
|
|
282
|
-
/** Use this organization for this session. `announcement` non-null = they moved. */
|
|
283
|
-
| {
|
|
284
|
-
kind: "honoured";
|
|
285
|
-
organizationId: string;
|
|
286
|
-
organizationName: string;
|
|
287
|
-
announcement: string | null;
|
|
288
|
-
}
|
|
289
|
-
/** The knob is off and this would be a real switch. Offer it as a click. */
|
|
290
|
-
| {
|
|
291
|
-
kind: "offered";
|
|
292
|
-
organizationId: string;
|
|
293
|
-
organizationName: string;
|
|
294
|
-
message: string;
|
|
295
|
-
actionLabel: string;
|
|
296
|
-
}
|
|
297
|
-
/**
|
|
298
|
-
* Not a member, but let in by a share they accepted. Silent: nobody is moved
|
|
299
|
-
* (a share never makes a member) and nothing was refused (the page opens it).
|
|
300
|
-
*/
|
|
301
|
-
| {
|
|
302
|
-
kind: "admitted";
|
|
303
|
-
organizationId: string;
|
|
304
|
-
}
|
|
305
|
-
/** Malformed, or not theirs. Changes nothing; says so. */
|
|
306
|
-
| {
|
|
307
|
-
kind: "refused";
|
|
308
|
-
reason: "malformed" | "not-a-member";
|
|
309
|
-
message: string;
|
|
310
|
-
};
|
|
311
|
-
/** What a person reads when the link's organization is not theirs to open. */
|
|
312
|
-
declare function linkOrganizationNotAMemberMessage(signedInAs?: string | null): string;
|
|
313
|
-
/** What a person reads when `org=` is not an organization id at all. */
|
|
314
|
-
declare function linkOrganizationMalformedMessage(raw: string): string;
|
|
315
|
-
/** What a person reads when a link actually moved them. Law 4: never silent. */
|
|
316
|
-
declare function linkOrganizationSwitchAnnouncement(toName: string, fromName: string | null): string;
|
|
317
|
-
/** What a person reads with the knob turned off and a link asking to move them. */
|
|
318
|
-
declare function linkOrganizationOfferMessage(toName: string, fromName: string | null): string;
|
|
319
|
-
/**
|
|
320
|
-
* The decision. One function, every branch, no side effects.
|
|
321
|
-
*
|
|
322
|
-
* 🚨 WHY THE KNOB GOVERNS ONLY A REAL SWITCH. The knob protects the
|
|
323
|
-
* organization a person is WORKING IN from being yanked out from under them by
|
|
324
|
-
* a link someone else composed. With `currentOrganizationId === null` there is
|
|
325
|
-
* nothing to protect and nothing to switch away from: the alternative to
|
|
326
|
-
* honouring the link is the picker, which is the very dead end this module was
|
|
327
|
-
* built to end. So the knob turns a SWITCH into an offer; it never turns an
|
|
328
|
-
* arrival into a refusal. This is deliberate and it is not a hidden default —
|
|
329
|
-
* with no link present, a null selection stays null exactly as before.
|
|
330
|
-
*/
|
|
331
|
-
declare function decideLinkOrganization(input: LinkOrganizationDecisionInput): LinkOrganizationDecision;
|
|
332
|
-
interface ResolveWithLinkInput<T extends {
|
|
333
|
-
id: string;
|
|
334
|
-
name: string;
|
|
335
|
-
slug?: string | null;
|
|
336
|
-
joinedAt?: string;
|
|
337
|
-
}> {
|
|
338
|
-
readonly memberships: readonly T[];
|
|
339
|
-
/** This tab's held organization (rung 1; a refresh keeps it). */
|
|
340
|
-
readonly held?: string | null;
|
|
341
|
-
readonly readAccount: () => Promise<AccountOrganizationChoices>;
|
|
342
|
-
readonly first?: () => Promise<T | null>;
|
|
343
|
-
/** The raw `?org=` value, a query string, a URL or URLSearchParams. Omitted = no link. */
|
|
344
|
-
readonly link?: string | URLSearchParams | null;
|
|
345
|
-
readonly switchWhenALinkAsks?: boolean | undefined;
|
|
346
|
-
readonly signedInAs?: string | null | undefined;
|
|
347
|
-
readonly messages?: Partial<LinkOrganizationMessages> | undefined;
|
|
348
|
-
/** Asked only when the link already failed the membership check (a share admits, never joins). */
|
|
349
|
-
readonly admittedByAShare?: ((organizationId: string) => Promise<boolean>) | undefined;
|
|
350
|
-
}
|
|
351
|
-
interface ResolvedWithLink<T> {
|
|
352
|
-
readonly organization: T;
|
|
353
|
-
readonly rung: OrganizationRung | "link";
|
|
354
|
-
/** What the link meant, when it said anything — the host SAYS it (refused / announced / offered). */
|
|
355
|
-
readonly link: LinkOrganizationDecision;
|
|
356
|
-
}
|
|
357
|
-
/**
|
|
358
|
-
* The ONE load ladder with the link folded in: held → last active → start-up → first, then the
|
|
359
|
-
* link's own organization wins ONLY when it is a current membership. Every other link outcome
|
|
360
|
-
* (refused, offered, already-current, admitted) leaves the ladder's answer standing. Null when
|
|
361
|
-
* the person has no memberships. A failed read throws through.
|
|
362
|
-
*/
|
|
363
|
-
declare function resolveOrganizationWithLink<T extends {
|
|
364
|
-
id: string;
|
|
365
|
-
name: string;
|
|
366
|
-
slug?: string | null;
|
|
367
|
-
joinedAt?: string;
|
|
368
|
-
}>(input: ResolveWithLinkInput<T>): Promise<ResolvedWithLink<T> | null>;
|
|
369
|
-
|
|
370
|
-
/**
|
|
371
|
-
* THE ACTIVE ORGANIZATION — one per tab / running app, set once when the app loads, never none
|
|
372
|
-
* while the person has a membership (Arman, 2026-10-07; organizations STATE rules 10–15).
|
|
373
|
-
*
|
|
374
|
-
* const active = createActiveOrganization({ client: supabase });
|
|
375
|
-
* await active.load(); // the ladder: held → last active → start-up → first
|
|
376
|
-
* const orgId = await active.ensureOrgId(); // HELD until the ladder answers; never a guess
|
|
377
|
-
* await active.switchTo(otherId); // a person's click: saves last active for next load
|
|
378
|
-
*
|
|
379
|
-
* HELD-REQUEST SEMANTICS. A write or server call that needs the organization asks
|
|
380
|
-
* `ensureOrgId()`: while the ladder is still resolving the call WAITS (it is held, never sent
|
|
381
|
-
* without an organization and never sent with a guessed one); once resolved it gets the one
|
|
382
|
-
* active organization; with zero memberships or a failed read it is REFUSED with a named error.
|
|
383
|
-
* Acting on a record passes that record's own organization (`ensureOrgId(record.organization_id)`),
|
|
384
|
-
* which wins and never switches the active one.
|
|
385
|
-
*
|
|
386
|
-
* 🚨 NEVER A LIST FILTER. Reads show everything the person can see across all their
|
|
387
|
-
* organizations; nothing here narrows a read. The active organization is only where new things
|
|
388
|
-
* are saved and which organization a server call runs in.
|
|
389
|
-
*
|
|
390
|
-
* The held id is identity-keyed: a different person signing in on the same device never inherits
|
|
391
|
-
* the last person's organization, and a sign-out resets the store.
|
|
392
|
-
*/
|
|
393
|
-
|
|
394
|
-
/** Where this tab / app remembers its organization between reloads (identity-keyed). */
|
|
395
|
-
interface HeldOrganizationStore {
|
|
396
|
-
read(userId: string): string | null | Promise<string | null>;
|
|
397
|
-
write(userId: string, organizationId: string): void | Promise<void>;
|
|
398
|
-
clear(): void | Promise<void>;
|
|
399
|
-
}
|
|
400
|
-
type ActiveOrganizationStatus = "idle" | "resolving" | "ready" | "none" | "error";
|
|
401
|
-
interface ActiveOrganizationSnapshot {
|
|
402
|
-
readonly status: ActiveOrganizationStatus;
|
|
403
|
-
readonly userId: string | null;
|
|
404
|
-
readonly organizationId: string | null;
|
|
405
|
-
readonly organization: OrganizationMembership | null;
|
|
406
|
-
readonly memberships: readonly OrganizationMembership[];
|
|
407
|
-
/** Which ladder rung answered (or "switched" after a person's click). */
|
|
408
|
-
readonly rung: OrganizationRung | "switched" | "link" | null;
|
|
409
|
-
readonly error: string | null;
|
|
410
|
-
}
|
|
411
|
-
declare class NoActiveOrganizationError extends Error {
|
|
412
|
-
readonly reason: "signed-out" | "no-memberships" | "unreadable";
|
|
413
|
-
constructor(reason: "signed-out" | "no-memberships" | "unreadable", detail?: string);
|
|
414
|
-
}
|
|
415
|
-
declare class NotAMemberError extends Error {
|
|
416
|
-
readonly organizationId: string;
|
|
417
|
-
constructor(organizationId: string);
|
|
418
|
-
}
|
|
419
|
-
interface ActiveOrganization {
|
|
420
|
-
snapshot(): ActiveOrganizationSnapshot;
|
|
421
|
-
subscribe(listener: () => void): () => void;
|
|
422
|
-
/** Run the ladder (again). Resolves with the settled snapshot; never throws. */
|
|
423
|
-
load(): Promise<ActiveOrganizationSnapshot>;
|
|
424
|
-
/** The active organization id now, or null while resolving / with none. Sync (api's getter). */
|
|
425
|
-
current(): string | null;
|
|
426
|
-
/** THE HELD REQUEST: the record's own organization, else the active one once resolved. */
|
|
427
|
-
ensureOrgId(recordOrganizationId?: string | null): Promise<string>;
|
|
428
|
-
/** A person's click: switch, remember for this tab, save as last active for the next load. */
|
|
429
|
-
switchTo(organizationId: string): Promise<void>;
|
|
430
|
-
/** Feed a server refusal in: a lapsed organization re-runs the ladder. Returns the class. */
|
|
431
|
-
handleRefusal(status: number, body: unknown): Promise<"lapsed" | "retry" | null>;
|
|
432
|
-
dispose(): void;
|
|
433
|
-
}
|
|
434
|
-
interface CreateActiveOrganizationOptions {
|
|
435
|
-
/** The person's Supabase client (any supabase-js client). */
|
|
436
|
-
readonly client: OrganizationsDb & AuthClient;
|
|
437
|
-
/** Where this tab/app holds its choice. Default: browser `sessionStorage` (per tab), else memory. */
|
|
438
|
-
readonly held?: HeldOrganizationStore | undefined;
|
|
439
|
-
/** An extra first rung (the web's `?org=` link). Kept only if it is a current membership. */
|
|
440
|
-
readonly linked?: (() => string | null | undefined) | undefined;
|
|
441
|
-
/**
|
|
442
|
-
* The link's own organization (`?org=` uuid or address) as a first-class rung: honoured only when
|
|
443
|
-
* it is a current membership, otherwise the ladder stands and `onDecision` receives the refusal /
|
|
444
|
-
* offer / announcement for the host to SAY. Supersedes `linked` when both are given.
|
|
445
|
-
*/
|
|
446
|
-
readonly link?: {
|
|
447
|
-
readonly source: () => string | URLSearchParams | null | undefined;
|
|
448
|
-
readonly switchWhenALinkAsks?: (() => boolean | undefined) | undefined;
|
|
449
|
-
readonly signedInAs?: (() => string | null | undefined) | undefined;
|
|
450
|
-
readonly messages?: Partial<LinkOrganizationMessages> | undefined;
|
|
451
|
-
/** A share admits without making a member (asked only after a not-a-member verdict). */
|
|
452
|
-
readonly admittedByAShare?: ((organizationId: string) => Promise<boolean>) | undefined;
|
|
453
|
-
readonly onDecision?: ((decision: LinkOrganizationDecision) => void) | undefined;
|
|
454
|
-
} | undefined;
|
|
455
|
-
/**
|
|
456
|
-
* THE ADMIN SEAT: while the host's admin lane is open, the admin's own organization IS the active
|
|
457
|
-
* one for every reader (`current()`, `ensureOrgId()`); off the lane the person's own is untouched.
|
|
458
|
-
* Ignored unless the seat is a current membership. The snapshot always keeps the person's own.
|
|
459
|
-
*/
|
|
460
|
-
readonly adminSeat?: (() => AdminSeatInput | null | undefined) | undefined;
|
|
461
|
-
/** Marked resolved whenever the ladder settles (organization or none). */
|
|
462
|
-
readonly bootstrapGate?: OrganizationBootstrapGate | undefined;
|
|
463
|
-
/** Minimum gap between ladder re-runs caused by a not-a-member refusal. Default 30 s. */
|
|
464
|
-
readonly refusalRecheckGapMs?: number | undefined;
|
|
465
|
-
/** Load as soon as it is created (default true). */
|
|
466
|
-
readonly autoLoad?: boolean | undefined;
|
|
467
|
-
}
|
|
468
|
-
/** A held store over a `Storage` (browser `sessionStorage` = per tab), identity-keyed. */
|
|
469
|
-
declare function storageHeldOrganization(storage: Pick<Storage, "getItem" | "setItem" | "removeItem">, key?: string): HeldOrganizationStore & {
|
|
470
|
-
read(userId: string): string | null;
|
|
471
|
-
};
|
|
472
|
-
/** Held in memory: the running app's own choice (desktop, scripts, tests). */
|
|
473
|
-
declare function memoryHeldOrganization(): HeldOrganizationStore;
|
|
474
|
-
declare function createActiveOrganization(options: CreateActiveOrganizationOptions): ActiveOrganization;
|
|
1
|
+
export { h as AccountOrganizationChoices, j as ActiveOrganization, k as ActiveOrganizationSnapshot, l as ActiveOrganizationStatus, m as AdminSeat, n as AdminSeatInput, p as ArchiveFilter, C as CreateActiveOrganizationOptions, H as HeldOrganizationStore, L as LINK_ORGANIZATION_QUERY_KEY, q as LadderInput, t as LinkOrganizationDecision, v as LinkOrganizationDecisionInput, w as LinkOrganizationMembership, x as LinkOrganizationMessages, y as LinkOrganizationParam, N as NOT_A_MEMBER_RECHECK_GAP_MS, z as NoActiveOrganizationError, B as NotAMemberError, D as NotAMemberRefusalNotice, O as ORGANIZATION_ENTITY_TYPE, E as ORGANIZATION_RESOLUTION_TIMEOUT_MS, F as OrganizationBootstrapGate, G as OrganizationMembership, I as OrganizationRefusal, J as OrganizationRung, K as OrganizationsDb, P as ParentOrganization, R as ReadMembershipsOptions, M as ResolveWithLinkInput, Q as ResolvedOrganization, T as ResolvedWithLink, V as adminSeatReplacesUserContext, W as classifyLinkOrganizationValue, X as classifyOrganizationRefusal, Y as createActiveOrganization, Z as createNotAMemberRefusalNotice, _ as createOrganizationBootstrapGate, $ as decideLinkOrganization, a0 as isNotAMemberRefusal, a1 as linkOrganizationMalformedMessage, a2 as linkOrganizationNotAMemberMessage, a3 as linkOrganizationOfferMessage, a4 as linkOrganizationSwitchAnnouncement, a5 as memoryHeldOrganization, a6 as oldestMembership, a7 as orderOrganizations, a8 as organizationMatches, a9 as readAccountOrganizationChoices, aa as readLinkOrganizationParam, ab as readMemberships, ac as readOrganizationFavourites, ad as resolveActiveOrganization, ae as resolveActiveOrganizationLazily, af as resolveAdminSeat, ag as resolveChildOrganizationId, ah as resolveOrganizationWithLink, ai as setOrganizationFavourite, aj as storageHeldOrganization, ak as toAccountOrganizationChoices, al as writeLastActiveOrganization, am as writeStartupOrganization } from './active-organization-CIvSdboO.js';
|
|
2
|
+
import '@supabase/supabase-js';
|
|
475
3
|
|
|
476
4
|
/**
|
|
477
5
|
* "This request needs an organization and names none" — the ONE recogniser, the server's throwable
|
|
@@ -579,4 +107,4 @@ declare function organizationRequiredEnvelope(error: OrganizationRequiredServerE
|
|
|
579
107
|
/** The standard `Response` (status 400) a route answers the refusal with — no framework needed. */
|
|
580
108
|
declare function organizationRequiredResponse(error: OrganizationRequiredServerError): Response;
|
|
581
109
|
|
|
582
|
-
export {
|
|
110
|
+
export { MEMBERSHIPS_URL, type MembershipReadClient, ORGANIZATION_CONTEXT_REQUIRED_CODE, ORGANIZATION_REQUIRED_CODE, ORGANIZATION_REQUIRED_WIRE_CODE, type OrganizationHoldDetails, type OrganizationHoldSetOn, type OrganizationMembershipSummary, type OrganizationRequiredEnvelope, OrganizationRequiredServerError, type OrganizationRequiredWireMembership, extractOrganizationHoldMemberships, isOrganizationRequiredEnvelope, isOrganizationRequiredError, isOrganizationRequiredServerError, organizationRequired, organizationRequiredEnvelope, organizationRequiredResponse, readCallerMemberships };
|
package/dist/organizations.js
CHANGED
|
@@ -45,7 +45,8 @@ function organizationMatches(org, query) {
|
|
|
45
45
|
}
|
|
46
46
|
var MEMBERSHIP_PAGE = 500;
|
|
47
47
|
var ID_CHUNK = 100;
|
|
48
|
-
async function readMemberships(db, userId) {
|
|
48
|
+
async function readMemberships(db, userId, options = {}) {
|
|
49
|
+
const filter = options.archived ?? "active";
|
|
49
50
|
const memberships = [];
|
|
50
51
|
for (let from = 0; ; from += MEMBERSHIP_PAGE) {
|
|
51
52
|
const { data, error } = await db.rpc("mbr_for_user", { p_container_type: "organization" }).order("id", { ascending: true }).range(from, from + MEMBERSHIP_PAGE - 1);
|
|
@@ -68,7 +69,7 @@ async function readMemberships(db, userId) {
|
|
|
68
69
|
if (error) throw new Error(`Your organizations could not be read: ${error.message}`);
|
|
69
70
|
rows.push(...data ?? []);
|
|
70
71
|
}
|
|
71
|
-
return rows.filter((r) => !r.archived_at).map((r) => {
|
|
72
|
+
return rows.filter((r) => filter === "all" ? true : filter === "archived" ? !!r.archived_at : !r.archived_at).map((r) => {
|
|
72
73
|
const m = active.get(r.id);
|
|
73
74
|
const settings = r.settings && typeof r.settings === "object" ? r.settings : {};
|
|
74
75
|
return {
|
|
@@ -80,7 +81,8 @@ async function readMemberships(db, userId) {
|
|
|
80
81
|
role: m?.role ?? "member",
|
|
81
82
|
isOwn: !!r.created_by && r.created_by === userId,
|
|
82
83
|
isTestFixture: settings["test_fixture"] === true,
|
|
83
|
-
joinedAt: m?.created_at ?? ""
|
|
84
|
+
joinedAt: m?.created_at ?? "",
|
|
85
|
+
archivedAt: r.archived_at ?? null
|
|
84
86
|
};
|
|
85
87
|
}).sort((a, b) => a.name.localeCompare(b.name));
|
|
86
88
|
}
|
package/dist/react.d.cts
CHANGED
|
@@ -61,6 +61,8 @@ interface OrganizationMembership {
|
|
|
61
61
|
readonly isTestFixture: boolean;
|
|
62
62
|
/** When the membership began (ISO): the oldest is rung 4. */
|
|
63
63
|
readonly joinedAt: string;
|
|
64
|
+
/** When the organization was archived (ISO), or null. Present on every row `readMemberships` returns. */
|
|
65
|
+
readonly archivedAt?: string | null;
|
|
64
66
|
}
|
|
65
67
|
type OrganizationRung = "held" | "last-active" | "startup" | "first";
|
|
66
68
|
|
package/dist/react.d.ts
CHANGED
|
@@ -61,6 +61,8 @@ interface OrganizationMembership {
|
|
|
61
61
|
readonly isTestFixture: boolean;
|
|
62
62
|
/** When the membership began (ISO): the oldest is rung 4. */
|
|
63
63
|
readonly joinedAt: string;
|
|
64
|
+
/** When the organization was archived (ISO), or null. Present on every row `readMemberships` returns. */
|
|
65
|
+
readonly archivedAt?: string | null;
|
|
64
66
|
}
|
|
65
67
|
type OrganizationRung = "held" | "last-active" | "startup" | "first";
|
|
66
68
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-matrx/auth",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "AI Matrx sign-in, session and organizations for every app: sign in/out, session storage and refresh per platform (browser, Next.js, extension, desktop, Node), the current person, their organizations and favourites, the one active organization (held requests, never a list filter), and the credentials @ai-matrx/data and @ai-matrx/api read.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -132,6 +132,16 @@
|
|
|
132
132
|
"default": "./dist/matrx-oauth.cjs"
|
|
133
133
|
}
|
|
134
134
|
},
|
|
135
|
+
"./guest": {
|
|
136
|
+
"import": {
|
|
137
|
+
"types": "./dist/guest.d.ts",
|
|
138
|
+
"default": "./dist/guest.js"
|
|
139
|
+
},
|
|
140
|
+
"require": {
|
|
141
|
+
"types": "./dist/guest.d.cts",
|
|
142
|
+
"default": "./dist/guest.cjs"
|
|
143
|
+
}
|
|
144
|
+
},
|
|
135
145
|
"./package.json": "./package.json"
|
|
136
146
|
},
|
|
137
147
|
"devDependencies": {
|
|
@@ -201,5 +211,5 @@
|
|
|
201
211
|
"test": "vitest run",
|
|
202
212
|
"verify:tarball": "node ./scripts/verify-tarball.mjs"
|
|
203
213
|
},
|
|
204
|
-
"gitHead": "
|
|
214
|
+
"gitHead": "2d910d0280828c62bf8f52534665eba32abe02ad"
|
|
205
215
|
}
|