@fragno-dev/auth 0.0.14 → 0.0.16

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 (36) hide show
  1. package/README.md +196 -9
  2. package/dist/browser/client/react.d.ts +1194 -64
  3. package/dist/browser/client/react.d.ts.map +1 -1
  4. package/dist/browser/client/react.js +1 -1
  5. package/dist/browser/client/react.js.map +1 -1
  6. package/dist/browser/client/solid.d.ts +1446 -64
  7. package/dist/browser/client/solid.d.ts.map +1 -1
  8. package/dist/browser/client/solid.js +1 -1
  9. package/dist/browser/client/solid.js.map +1 -1
  10. package/dist/browser/client/svelte.d.ts +1194 -64
  11. package/dist/browser/client/svelte.d.ts.map +1 -1
  12. package/dist/browser/client/svelte.js +1 -1
  13. package/dist/browser/client/svelte.js.map +1 -1
  14. package/dist/browser/client/vanilla.d.ts +1194 -64
  15. package/dist/browser/client/vanilla.d.ts.map +1 -1
  16. package/dist/browser/client/vanilla.js +1 -1
  17. package/dist/browser/client/vanilla.js.map +1 -1
  18. package/dist/browser/client/vue.d.ts +1150 -20
  19. package/dist/browser/client/vue.d.ts.map +1 -1
  20. package/dist/browser/client/vue.js +1 -1
  21. package/dist/browser/client/vue.js.map +1 -1
  22. package/dist/browser/index-m_5zsra2.d.ts +7141 -0
  23. package/dist/browser/index-m_5zsra2.d.ts.map +1 -0
  24. package/dist/browser/index.d.ts +2 -600
  25. package/dist/browser/index.js +2 -2
  26. package/dist/browser/src-Ck4bl2NH.js +1892 -0
  27. package/dist/browser/src-Ck4bl2NH.js.map +1 -0
  28. package/dist/node/index.d.ts +6806 -265
  29. package/dist/node/index.d.ts.map +1 -1
  30. package/dist/node/index.js +5532 -266
  31. package/dist/node/index.js.map +1 -1
  32. package/dist/tsconfig.tsbuildinfo +1 -1
  33. package/package.json +20 -39
  34. package/dist/browser/index.d.ts.map +0 -1
  35. package/dist/browser/src-DNrh9CQq.js +0 -184
  36. package/dist/browser/src-DNrh9CQq.js.map +0 -1
@@ -0,0 +1,1892 @@
1
+ import { createClientBuilder } from "@fragno-dev/core/client";
2
+ import { defineFragment, defineRoute, defineRoutes } from "@fragno-dev/core";
3
+ import { atom, computed, onMount } from "nanostores";
4
+ import { z } from "zod";
5
+ import { decodeCursor } from "@fragno-dev/db/cursor";
6
+ import { column, idColumn, referenceColumn, schema } from "@fragno-dev/db/schema";
7
+
8
+ //#region src/client/default-organization.ts
9
+ const DEFAULT_ORGANIZATION_STORAGE_KEY = "fragno-auth.default-organization-id";
10
+ const DEFAULT_ORGANIZATION_CHANGE_EVENT = "fragno-auth:default-organization-change";
11
+ const NO_ORGANIZATIONS_ERROR_MESSAGE = "Authenticated users must always have at least one organization.";
12
+ function getStorage(storage) {
13
+ if (storage != null) return storage;
14
+ if (typeof window === "undefined") return null;
15
+ try {
16
+ return window.localStorage;
17
+ } catch {
18
+ return null;
19
+ }
20
+ }
21
+ function getWindow(win) {
22
+ if (win != null) return win;
23
+ return typeof window !== "undefined" ? window : null;
24
+ }
25
+ function toNonEmptyString(value) {
26
+ const trimmed = value?.trim();
27
+ return trimmed && trimmed.length > 0 ? trimmed : null;
28
+ }
29
+ function readCurrentDefaultOrganizationId(storage) {
30
+ const s = getStorage(storage);
31
+ if (!s) return null;
32
+ return toNonEmptyString(s.getItem(getDefaultOrganizationStorageKey()));
33
+ }
34
+ function getDefaultOrganizationStorageKey(_accountId) {
35
+ return DEFAULT_ORGANIZATION_STORAGE_KEY;
36
+ }
37
+ function getDefaultOrganizationChangeEventName(_accountId) {
38
+ return DEFAULT_ORGANIZATION_CHANGE_EVENT;
39
+ }
40
+ function readDefaultOrganizationId(_accountId, storage) {
41
+ const s = getStorage(storage);
42
+ if (!s) return null;
43
+ try {
44
+ return readCurrentDefaultOrganizationId(s);
45
+ } catch {
46
+ return null;
47
+ }
48
+ }
49
+ function writeDefaultOrganizationId(_accountId, organizationId, storage, win) {
50
+ const trimmed = toNonEmptyString(organizationId);
51
+ if (!trimmed) return clearDefaultOrganizationId(_accountId, storage, win);
52
+ const s = getStorage(storage);
53
+ const storageKey = getDefaultOrganizationStorageKey();
54
+ const eventName = getDefaultOrganizationChangeEventName();
55
+ if (!s) return false;
56
+ if (readCurrentDefaultOrganizationId(s) === trimmed) return false;
57
+ try {
58
+ s.setItem(storageKey, trimmed);
59
+ } catch {
60
+ return false;
61
+ }
62
+ getWindow(win)?.dispatchEvent(new Event(eventName));
63
+ return true;
64
+ }
65
+ function clearDefaultOrganizationId(_accountId, storage, win) {
66
+ const s = getStorage(storage);
67
+ const storageKey = getDefaultOrganizationStorageKey();
68
+ const eventName = getDefaultOrganizationChangeEventName();
69
+ if (!s) return false;
70
+ if (!readCurrentDefaultOrganizationId(s)) return false;
71
+ try {
72
+ s.removeItem(storageKey);
73
+ } catch {
74
+ return false;
75
+ }
76
+ getWindow(win)?.dispatchEvent(new Event(eventName));
77
+ return true;
78
+ }
79
+ function subscribeToDefaultOrganizationPreference(_accountId, onChange, options) {
80
+ const win = getWindow(options?.windowObject);
81
+ const storageKey = getDefaultOrganizationStorageKey();
82
+ const eventName = getDefaultOrganizationChangeEventName();
83
+ if (!win) return () => {};
84
+ const handleChange = () => onChange();
85
+ const handleStorage = (e) => {
86
+ if (typeof StorageEvent === "undefined") return onChange();
87
+ if (e instanceof StorageEvent && (!e.key || e.key === storageKey)) onChange();
88
+ };
89
+ win.addEventListener(eventName, handleChange);
90
+ win.addEventListener("storage", handleStorage);
91
+ return () => {
92
+ win.removeEventListener(eventName, handleChange);
93
+ win.removeEventListener("storage", handleStorage);
94
+ };
95
+ }
96
+ function findOrganizationEntry(me, organizationId) {
97
+ const id = toNonEmptyString(organizationId);
98
+ if (!id) return null;
99
+ return me.organizations.find((e) => e.organization.id === id) ?? null;
100
+ }
101
+ function toResolution(entry, status, storedId) {
102
+ return {
103
+ status,
104
+ storedOrganizationId: storedId,
105
+ resolvedOrganizationId: entry.organization.id,
106
+ entry,
107
+ organization: entry.organization,
108
+ member: entry.member
109
+ };
110
+ }
111
+ function resolveDefaultOrganization(me, storedOrganizationId) {
112
+ if (me.organizations.length === 0) throw new Error(NO_ORGANIZATIONS_ERROR_MESSAGE);
113
+ const stored = findOrganizationEntry(me, storedOrganizationId);
114
+ if (stored) return toResolution(stored, "reused", storedOrganizationId);
115
+ const active = findOrganizationEntry(me, me.activeOrganization?.organization.id ?? null);
116
+ const fallback = active ?? me.organizations[0];
117
+ return toResolution(fallback, storedOrganizationId ? "repaired" : "initialized", storedOrganizationId);
118
+ }
119
+ function syncDefaultOrganizationPreference(_accountId, me, storage, win) {
120
+ const resolution = resolveDefaultOrganization(me, readDefaultOrganizationId(_accountId, storage));
121
+ if (resolution.status !== "reused") writeDefaultOrganizationId(_accountId, resolution.resolvedOrganizationId, storage, win);
122
+ return resolution;
123
+ }
124
+ function setDefaultOrganizationForMe(_accountId, me, organizationId, storage, win) {
125
+ const entry = findOrganizationEntry(me, organizationId);
126
+ if (!entry) throw new Error("The selected organization is no longer available for this account.");
127
+ writeDefaultOrganizationId(_accountId, entry.organization.id, storage, win);
128
+ return resolveDefaultOrganization(me, entry.organization.id);
129
+ }
130
+ function createDefaultOrganizationPreferenceState(options) {
131
+ const { meStore, readMe, storage, windowObject } = options;
132
+ const storageVersion = atom(0);
133
+ const refresh = () => storageVersion.set(storageVersion.get() + 1);
134
+ const getCurrentStorageKey = () => getDefaultOrganizationStorageKey();
135
+ const storedOrganizationId = computed(storageVersion, () => readDefaultOrganizationId(null, storage));
136
+ const write = (id) => {
137
+ const ok = writeDefaultOrganizationId(null, id, storage, windowObject);
138
+ if (ok) refresh();
139
+ return ok;
140
+ };
141
+ const clear = () => {
142
+ const ok = clearDefaultOrganizationId(null, storage, windowObject);
143
+ if (ok) refresh();
144
+ return ok;
145
+ };
146
+ const syncForMe = (me) => {
147
+ const resolution$1 = syncDefaultOrganizationPreference(null, me, storage, windowObject);
148
+ refresh();
149
+ return resolution$1;
150
+ };
151
+ const setForMe = (me, id) => {
152
+ const entry = findOrganizationEntry(me, id);
153
+ if (!entry) throw new Error("The selected organization is no longer available for this account.");
154
+ const resolution$1 = setDefaultOrganizationForMe(null, me, id, storage, windowObject);
155
+ refresh();
156
+ return resolution$1;
157
+ };
158
+ onMount(storedOrganizationId, () => subscribeToDefaultOrganizationPreference(null, refresh, { windowObject }));
159
+ const resolution = computed([meStore, storedOrganizationId], (meState, orgId) => {
160
+ if (!meState.data) return null;
161
+ return resolveDefaultOrganization(meState.data, orgId);
162
+ });
163
+ onMount(resolution, () => resolution.listen((next) => {
164
+ if (!next || next.status === "reused") return;
165
+ if (writeDefaultOrganizationId(null, next.resolvedOrganizationId, storage, windowObject)) refresh();
166
+ }));
167
+ const effectiveMe = computed([meStore, resolution], (meState) => meState.data ?? null);
168
+ return {
169
+ me: async (params) => {
170
+ const me = await readMe(params);
171
+ if (getWindow(windowObject)) syncForMe(me);
172
+ return me;
173
+ },
174
+ defaultOrganization: {
175
+ get storageKey() {
176
+ return getCurrentStorageKey();
177
+ },
178
+ read: () => readDefaultOrganizationId(null, storage),
179
+ write,
180
+ clear,
181
+ resolve: resolveDefaultOrganization,
182
+ sync: syncForMe,
183
+ setForMe
184
+ },
185
+ store: {
186
+ get storageKey() {
187
+ return getCurrentStorageKey();
188
+ },
189
+ storedOrganizationId,
190
+ resolution,
191
+ status: computed(resolution, (r) => r?.status ?? null),
192
+ defaultOrganizationId: computed(resolution, (r) => r?.resolvedOrganizationId ?? null),
193
+ defaultOrganization: computed(resolution, (r) => r?.entry ?? null),
194
+ me: effectiveMe,
195
+ loading: computed(meStore, (s) => s.loading),
196
+ error: computed(meStore, (s) => s.error),
197
+ readDefaultOrganizationId: () => readDefaultOrganizationId(null, storage),
198
+ writeDefaultOrganizationId: write,
199
+ clearDefaultOrganizationId: clear,
200
+ syncPreference: () => {
201
+ const me = effectiveMe.get();
202
+ return me ? syncForMe(me) : null;
203
+ },
204
+ setDefaultOrganization: (id) => {
205
+ const me = effectiveMe.get();
206
+ if (!me) throw new Error("Cannot set a default organization without an authenticated user.");
207
+ return setForMe(me, id);
208
+ }
209
+ }
210
+ };
211
+ }
212
+
213
+ //#endregion
214
+ //#region src/oauth/routes.ts
215
+ const oauthRoutesFactory = defineRoutes().create(({ services, config }) => {
216
+ return [defineRoute({
217
+ method: "GET",
218
+ path: "/oauth/:provider/authorize",
219
+ queryParameters: [
220
+ "redirectUri",
221
+ "returnTo",
222
+ "link",
223
+ "sessionId",
224
+ "scope",
225
+ "loginHint",
226
+ "session"
227
+ ],
228
+ outputSchema: z.object({ url: z.string() }),
229
+ errorCodes: [
230
+ "oauth_disabled",
231
+ "provider_not_found",
232
+ "missing_redirect_uri",
233
+ "redirect_uri_mismatch",
234
+ "invalid_input",
235
+ "session_invalid"
236
+ ],
237
+ handler: () => {}
238
+ }), defineRoute({
239
+ method: "GET",
240
+ path: "/oauth/:provider/callback",
241
+ queryParameters: [
242
+ "code",
243
+ "state",
244
+ "requestSignUp"
245
+ ],
246
+ outputSchema: z.object({
247
+ sessionId: z.string(),
248
+ userId: z.string(),
249
+ email: z.string(),
250
+ role: z.enum(["user", "admin"]),
251
+ returnTo: z.string().nullable()
252
+ }),
253
+ errorCodes: [
254
+ "oauth_disabled",
255
+ "provider_not_found",
256
+ "missing_redirect_uri",
257
+ "invalid_code",
258
+ "invalid_state",
259
+ "email_required",
260
+ "signup_disabled",
261
+ "signup_required",
262
+ "user_banned"
263
+ ],
264
+ handler: () => {}
265
+ })];
266
+ });
267
+
268
+ //#endregion
269
+ //#region src/utils/cookie.ts
270
+ /**
271
+ * Cookie utilities for session management
272
+ */
273
+ const COOKIE_NAME = "sessionid";
274
+ /**
275
+ * Parse cookies from a Cookie header string
276
+ */
277
+ function parseCookies(cookieHeader) {
278
+ if (!cookieHeader) return {};
279
+ const cookies = {};
280
+ const pairs = cookieHeader.split(";");
281
+ for (const pair of pairs) {
282
+ const [key, ...valueParts] = pair.split("=");
283
+ const trimmedKey = key?.trim();
284
+ const value = valueParts.join("=").trim();
285
+ if (trimmedKey) cookies[trimmedKey] = decodeURIComponent(value);
286
+ }
287
+ return cookies;
288
+ }
289
+ /**
290
+ * Extract session ID from headers, checking cookies first, then query/body
291
+ */
292
+ function extractSessionId(headers, queryParam, bodySessionId) {
293
+ const cookieHeader = headers.get("Cookie");
294
+ const cookies = parseCookies(cookieHeader);
295
+ const sessionIdFromCookie = cookies[COOKIE_NAME];
296
+ if (sessionIdFromCookie) return sessionIdFromCookie;
297
+ if (queryParam) return queryParam;
298
+ if (bodySessionId) return bodySessionId;
299
+ return null;
300
+ }
301
+
302
+ //#endregion
303
+ //#region src/organization/schemas.ts
304
+ const organizationSchema = z.object({
305
+ id: z.string(),
306
+ name: z.string(),
307
+ slug: z.string(),
308
+ logoUrl: z.string().nullable(),
309
+ metadata: z.record(z.string(), z.unknown()).nullable(),
310
+ createdBy: z.string(),
311
+ createdAt: z.string(),
312
+ updatedAt: z.string(),
313
+ deletedAt: z.string().nullable()
314
+ });
315
+ const memberSchema = z.object({
316
+ id: z.string(),
317
+ organizationId: z.string(),
318
+ userId: z.string(),
319
+ roles: z.array(z.string()),
320
+ createdAt: z.string(),
321
+ updatedAt: z.string()
322
+ });
323
+ const invitationSchema = z.object({
324
+ id: z.string(),
325
+ organizationId: z.string(),
326
+ email: z.string(),
327
+ roles: z.array(z.string()),
328
+ status: z.enum([
329
+ "pending",
330
+ "accepted",
331
+ "rejected",
332
+ "canceled",
333
+ "expired"
334
+ ]),
335
+ token: z.string(),
336
+ inviterId: z.string(),
337
+ expiresAt: z.string(),
338
+ createdAt: z.string(),
339
+ respondedAt: z.string().nullable()
340
+ });
341
+ const invitationSummarySchema = invitationSchema.omit({ token: true });
342
+
343
+ //#endregion
344
+ //#region src/organization/serializers.ts
345
+ const normalizeMetadata = (value) => {
346
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
347
+ return value;
348
+ };
349
+ function serializeOrganization(organization) {
350
+ return {
351
+ id: organization.id,
352
+ name: organization.name,
353
+ slug: organization.slug,
354
+ logoUrl: organization.logoUrl ?? null,
355
+ metadata: normalizeMetadata(organization.metadata),
356
+ createdBy: organization.createdBy,
357
+ createdAt: organization.createdAt.toISOString(),
358
+ updatedAt: organization.updatedAt.toISOString(),
359
+ deletedAt: organization.deletedAt ? organization.deletedAt.toISOString() : null
360
+ };
361
+ }
362
+ function serializeMember(member) {
363
+ return {
364
+ id: member.id,
365
+ organizationId: member.organizationId,
366
+ userId: member.userId,
367
+ roles: member.roles,
368
+ createdAt: member.createdAt.toISOString(),
369
+ updatedAt: member.updatedAt.toISOString()
370
+ };
371
+ }
372
+ function serializeInvitation(invitation) {
373
+ return {
374
+ id: invitation.id,
375
+ organizationId: invitation.organizationId,
376
+ email: invitation.email,
377
+ roles: invitation.roles,
378
+ status: invitation.status,
379
+ token: invitation.token,
380
+ inviterId: invitation.inviterId,
381
+ expiresAt: invitation.expiresAt.toISOString(),
382
+ createdAt: invitation.createdAt.toISOString(),
383
+ respondedAt: invitation.respondedAt ? invitation.respondedAt.toISOString() : null
384
+ };
385
+ }
386
+
387
+ //#endregion
388
+ //#region src/organization/routes.ts
389
+ const createOrganizationInputSchema = z.object({
390
+ name: z.string().min(1).max(120),
391
+ slug: z.string().min(1),
392
+ logoUrl: z.string().nullable().optional(),
393
+ metadata: z.record(z.string(), z.unknown()).nullable().optional()
394
+ });
395
+ const updateOrganizationInputSchema = z.object({
396
+ name: z.string().min(1).max(120).optional(),
397
+ slug: z.string().min(1).optional(),
398
+ logoUrl: z.string().nullable().optional(),
399
+ metadata: z.record(z.string(), z.unknown()).nullable().optional()
400
+ }).refine((value) => Object.keys(value).length > 0, { message: "At least one field must be provided" });
401
+ const pageQuerySchema = z.object({ pageSize: z.coerce.number().int().min(1).max(100).default(20) });
402
+ const parseCursor = (cursorParam) => {
403
+ if (!cursorParam) return void 0;
404
+ try {
405
+ return decodeCursor(cursorParam);
406
+ } catch {
407
+ return void 0;
408
+ }
409
+ };
410
+ const organizationRoutesFactory = defineRoutes().create(({ config, services }) => {
411
+ const defineOrganizationRoute = (route) => defineRoute({
412
+ ...route,
413
+ errorCodes: route.errorCodes ? Array.from(new Set([...route.errorCodes, "feature_disabled"])) : ["feature_disabled"],
414
+ handler: () => {}
415
+ });
416
+ return [
417
+ defineOrganizationRoute({
418
+ method: "POST",
419
+ path: "/organizations",
420
+ inputSchema: createOrganizationInputSchema,
421
+ outputSchema: z.object({
422
+ organization: organizationSchema,
423
+ member: memberSchema
424
+ }),
425
+ errorCodes: [
426
+ "invalid_input",
427
+ "organization_slug_taken",
428
+ "permission_denied",
429
+ "limit_reached",
430
+ "session_invalid"
431
+ ],
432
+ handler: async function({ input, headers, query }, { json, error }) {
433
+ const sessionId = extractSessionId(headers, query.get("sessionId"));
434
+ if (!sessionId) return error({
435
+ message: "Session ID required",
436
+ code: "session_invalid"
437
+ }, 400);
438
+ let body = null;
439
+ let inputError = null;
440
+ try {
441
+ body = await input.valid();
442
+ } catch (err) {
443
+ inputError = err;
444
+ }
445
+ const [result] = await this.handlerTx().withServiceCalls(() => [services.createOrganizationWithSession({
446
+ sessionId,
447
+ input: body,
448
+ inputError
449
+ })]).execute();
450
+ if (!result.ok) {
451
+ if (result.code === "session_invalid") return error({
452
+ message: "Invalid session",
453
+ code: "session_invalid"
454
+ }, 401);
455
+ if (result.code === "input_invalid") {
456
+ if (inputError) throw inputError;
457
+ return error({
458
+ message: "Invalid input",
459
+ code: "invalid_input"
460
+ }, 400);
461
+ }
462
+ if (result.code === "organization_slug_taken") return error({
463
+ message: "Organization slug taken",
464
+ code: "organization_slug_taken"
465
+ }, 400);
466
+ if (result.code === "invalid_slug") return error({
467
+ message: "Invalid input",
468
+ code: "invalid_input"
469
+ }, 400);
470
+ if (result.code === "limit_reached") return error({
471
+ message: "Limit reached",
472
+ code: "limit_reached"
473
+ }, 400);
474
+ return error({
475
+ message: "Permission denied",
476
+ code: "permission_denied"
477
+ }, 403);
478
+ }
479
+ return json({
480
+ organization: serializeOrganization(result.organization),
481
+ member: serializeMember(result.member)
482
+ });
483
+ }
484
+ }),
485
+ defineOrganizationRoute({
486
+ method: "GET",
487
+ path: "/organizations",
488
+ queryParameters: [
489
+ "pageSize",
490
+ "cursor",
491
+ "sessionId"
492
+ ],
493
+ outputSchema: z.object({
494
+ organizations: z.array(z.object({
495
+ organization: organizationSchema,
496
+ member: memberSchema
497
+ })),
498
+ cursor: z.string().optional(),
499
+ hasNextPage: z.boolean()
500
+ }),
501
+ errorCodes: ["invalid_input", "session_invalid"],
502
+ handler: async function({ query, headers }, { json, error }) {
503
+ const parsed = pageQuerySchema.safeParse(Object.fromEntries(query.entries()));
504
+ if (!parsed.success) return error({
505
+ message: "Invalid query parameters",
506
+ code: "invalid_input"
507
+ }, 400);
508
+ const sessionId = extractSessionId(headers, query.get("sessionId"));
509
+ if (!sessionId) return error({
510
+ message: "Session ID required",
511
+ code: "session_invalid"
512
+ }, 400);
513
+ const rawCursor = parseCursor(query.get("cursor"));
514
+ const cursor = rawCursor && (rawCursor.indexName === "_primary" || rawCursor.indexName === "primary") ? rawCursor : void 0;
515
+ const [result] = await this.handlerTx().withServiceCalls(() => [services.getOrganizationsForSession({
516
+ sessionId,
517
+ pageSize: parsed.data.pageSize,
518
+ cursor
519
+ })]).execute();
520
+ if (!result.ok) return error({
521
+ message: "Invalid session",
522
+ code: "session_invalid"
523
+ }, 401);
524
+ return json({
525
+ organizations: result.organizations.map((entry) => ({
526
+ organization: serializeOrganization(entry.organization),
527
+ member: serializeMember(entry.member)
528
+ })),
529
+ cursor: result.cursor,
530
+ hasNextPage: result.hasNextPage
531
+ });
532
+ }
533
+ }),
534
+ defineOrganizationRoute({
535
+ method: "GET",
536
+ path: "/organizations/active",
537
+ queryParameters: ["sessionId"],
538
+ outputSchema: z.object({
539
+ organization: organizationSchema,
540
+ member: memberSchema
541
+ }).nullable(),
542
+ errorCodes: ["session_invalid"],
543
+ handler: async function({ headers, query }, { json, error }) {
544
+ const sessionId = extractSessionId(headers, query.get("sessionId"));
545
+ if (!sessionId) return error({
546
+ message: "Session ID required",
547
+ code: "session_invalid"
548
+ }, 400);
549
+ const [result] = await this.handlerTx().withServiceCalls(() => [services.getActiveOrganizationForSession({ sessionId })]).execute();
550
+ if (!result.ok) return error({
551
+ message: "Invalid session",
552
+ code: "session_invalid"
553
+ }, 401);
554
+ if (!result.data) return json(null);
555
+ return json({
556
+ organization: serializeOrganization(result.data.organization),
557
+ member: serializeMember(result.data.member)
558
+ });
559
+ }
560
+ }),
561
+ defineOrganizationRoute({
562
+ method: "POST",
563
+ path: "/organizations/active",
564
+ queryParameters: ["sessionId"],
565
+ inputSchema: z.object({ organizationId: z.string() }),
566
+ outputSchema: z.object({
567
+ organization: organizationSchema,
568
+ member: memberSchema
569
+ }),
570
+ errorCodes: [
571
+ "organization_not_found",
572
+ "membership_not_found",
573
+ "session_invalid"
574
+ ],
575
+ handler: async function({ input, headers, query }, { json, error }) {
576
+ const sessionId = extractSessionId(headers, query.get("sessionId"));
577
+ if (!sessionId) return error({
578
+ message: "Session ID required",
579
+ code: "session_invalid"
580
+ }, 400);
581
+ const body = await input.valid();
582
+ const [result] = await this.handlerTx().withServiceCalls(() => [services.setActiveOrganizationForSession({
583
+ sessionId,
584
+ organizationId: body.organizationId
585
+ })]).execute();
586
+ if (!result.ok) {
587
+ if (result.code === "organization_not_found") return error({
588
+ message: "Organization not found",
589
+ code: "organization_not_found"
590
+ }, 404);
591
+ if (result.code === "membership_not_found") return error({
592
+ message: "Membership not found",
593
+ code: "membership_not_found"
594
+ }, 404);
595
+ return error({
596
+ message: "Invalid session",
597
+ code: "session_invalid"
598
+ }, 401);
599
+ }
600
+ return json({
601
+ organization: serializeOrganization(result.organization),
602
+ member: serializeMember(result.member)
603
+ });
604
+ }
605
+ }),
606
+ defineOrganizationRoute({
607
+ method: "GET",
608
+ path: "/organizations/invitations",
609
+ queryParameters: ["sessionId"],
610
+ outputSchema: z.object({ invitations: z.array(z.object({
611
+ invitation: invitationSchema,
612
+ organization: organizationSchema
613
+ })) }),
614
+ errorCodes: ["session_invalid"],
615
+ handler: async function({ headers, query }, { json, error }) {
616
+ const sessionId = extractSessionId(headers, query.get("sessionId"));
617
+ if (!sessionId) return error({
618
+ message: "Session ID required",
619
+ code: "session_invalid"
620
+ }, 400);
621
+ const [result] = await this.handlerTx().withServiceCalls(() => [services.listInvitationsForSession({ sessionId })]).execute();
622
+ if (!result.ok) return error({
623
+ message: "Invalid session",
624
+ code: "session_invalid"
625
+ }, 401);
626
+ return json({ invitations: result.invitations.map((entry) => ({
627
+ invitation: serializeInvitation(entry.invitation),
628
+ organization: serializeOrganization(entry.organization)
629
+ })) });
630
+ }
631
+ }),
632
+ defineOrganizationRoute({
633
+ method: "PATCH",
634
+ path: "/organizations/invitations/:invitationId",
635
+ queryParameters: ["sessionId"],
636
+ inputSchema: z.object({
637
+ action: z.enum([
638
+ "accept",
639
+ "reject",
640
+ "cancel"
641
+ ]),
642
+ token: z.string().optional()
643
+ }),
644
+ outputSchema: z.object({ invitation: invitationSchema }),
645
+ errorCodes: [
646
+ "invitation_not_found",
647
+ "invitation_expired",
648
+ "permission_denied",
649
+ "invalid_token",
650
+ "limit_reached",
651
+ "session_invalid"
652
+ ],
653
+ handler: async function({ input, pathParams, headers, query }, { json, error }) {
654
+ const sessionId = extractSessionId(headers, query.get("sessionId"));
655
+ if (!sessionId) return error({
656
+ message: "Session ID required",
657
+ code: "session_invalid"
658
+ }, 400);
659
+ const body = await input.valid();
660
+ if ((body.action === "accept" || body.action === "reject") && !body.token) return error({
661
+ message: "Invalid token",
662
+ code: "invalid_token"
663
+ }, 400);
664
+ const [result] = await this.handlerTx().withServiceCalls(() => [services.respondToInvitationWithSession({
665
+ sessionId,
666
+ invitationId: pathParams.invitationId,
667
+ action: body.action,
668
+ token: body.token
669
+ })]).execute();
670
+ if (!result.ok) {
671
+ if (result.code === "invalid_token") return error({
672
+ message: "Invalid token",
673
+ code: "invalid_token"
674
+ }, 400);
675
+ if (result.code === "invitation_expired") return error({
676
+ message: "Invitation expired",
677
+ code: "invitation_expired"
678
+ }, 400);
679
+ if (result.code === "limit_reached") return error({
680
+ message: "Limit reached",
681
+ code: "limit_reached"
682
+ }, 400);
683
+ if (result.code === "permission_denied") return error({
684
+ message: "Permission denied",
685
+ code: "permission_denied"
686
+ }, 403);
687
+ if (result.code === "session_invalid") return error({
688
+ message: "Invalid session",
689
+ code: "session_invalid"
690
+ }, 401);
691
+ return error({
692
+ message: "Invitation not found",
693
+ code: "invitation_not_found"
694
+ }, 404);
695
+ }
696
+ return json({ invitation: serializeInvitation(result.invitation) });
697
+ }
698
+ }),
699
+ defineOrganizationRoute({
700
+ method: "GET",
701
+ path: "/organizations/:organizationId",
702
+ queryParameters: ["sessionId"],
703
+ outputSchema: z.object({
704
+ organization: organizationSchema,
705
+ member: memberSchema
706
+ }),
707
+ errorCodes: [
708
+ "organization_not_found",
709
+ "permission_denied",
710
+ "session_invalid"
711
+ ],
712
+ handler: async function({ pathParams, headers, query }, { json, error }) {
713
+ const sessionId = extractSessionId(headers, query.get("sessionId"));
714
+ if (!sessionId) return error({
715
+ message: "Session ID required",
716
+ code: "session_invalid"
717
+ }, 400);
718
+ const [result] = await this.handlerTx().withServiceCalls(() => [services.getOrganizationForSession({
719
+ sessionId,
720
+ organizationId: pathParams.organizationId
721
+ })]).execute();
722
+ if (!result.ok) {
723
+ if (result.code === "organization_not_found") return error({
724
+ message: "Organization not found",
725
+ code: "organization_not_found"
726
+ }, 404);
727
+ if (result.code === "permission_denied") return error({
728
+ message: "Permission denied",
729
+ code: "permission_denied"
730
+ }, 403);
731
+ return error({
732
+ message: "Invalid session",
733
+ code: "session_invalid"
734
+ }, 401);
735
+ }
736
+ return json({
737
+ organization: serializeOrganization(result.organization),
738
+ member: serializeMember(result.member)
739
+ });
740
+ }
741
+ }),
742
+ defineOrganizationRoute({
743
+ method: "PATCH",
744
+ path: "/organizations/:organizationId",
745
+ queryParameters: ["sessionId"],
746
+ inputSchema: updateOrganizationInputSchema,
747
+ outputSchema: z.object({ organization: organizationSchema }),
748
+ errorCodes: [
749
+ "invalid_input",
750
+ "organization_not_found",
751
+ "organization_slug_taken",
752
+ "permission_denied",
753
+ "session_invalid"
754
+ ],
755
+ handler: async function({ input, pathParams, headers, query }, { json, error }) {
756
+ const sessionId = extractSessionId(headers, query.get("sessionId"));
757
+ if (!sessionId) return error({
758
+ message: "Session ID required",
759
+ code: "session_invalid"
760
+ }, 400);
761
+ const body = await input.valid();
762
+ const [result] = await this.handlerTx().withServiceCalls(() => [services.updateOrganizationWithSession({
763
+ sessionId,
764
+ organizationId: pathParams.organizationId,
765
+ patch: body
766
+ })]).execute();
767
+ if (!result.ok) {
768
+ if (result.code === "session_invalid") return error({
769
+ message: "Invalid session",
770
+ code: "session_invalid"
771
+ }, 401);
772
+ if (result.code === "organization_not_found") return error({
773
+ message: "Organization not found",
774
+ code: "organization_not_found"
775
+ }, 404);
776
+ if (result.code === "organization_slug_taken") return error({
777
+ message: "Organization slug taken",
778
+ code: "organization_slug_taken"
779
+ }, 400);
780
+ if (result.code === "permission_denied") return error({
781
+ message: "Permission denied",
782
+ code: "permission_denied"
783
+ }, 403);
784
+ return error({
785
+ message: "Invalid input",
786
+ code: "invalid_input"
787
+ }, 400);
788
+ }
789
+ return json({ organization: serializeOrganization(result.organization) });
790
+ }
791
+ }),
792
+ defineOrganizationRoute({
793
+ method: "DELETE",
794
+ path: "/organizations/:organizationId",
795
+ queryParameters: ["sessionId"],
796
+ outputSchema: z.object({ success: z.boolean() }),
797
+ errorCodes: [
798
+ "organization_not_found",
799
+ "permission_denied",
800
+ "session_invalid"
801
+ ],
802
+ handler: async function({ pathParams, headers, query }, { json, error }) {
803
+ const sessionId = extractSessionId(headers, query.get("sessionId"));
804
+ if (!sessionId) return error({
805
+ message: "Session ID required",
806
+ code: "session_invalid"
807
+ }, 400);
808
+ const [result] = await this.handlerTx().withServiceCalls(() => [services.deleteOrganizationWithSession({
809
+ sessionId,
810
+ organizationId: pathParams.organizationId
811
+ })]).execute();
812
+ if (!result.ok) {
813
+ if (result.code === "session_invalid") return error({
814
+ message: "Invalid session",
815
+ code: "session_invalid"
816
+ }, 401);
817
+ if (result.code === "organization_not_found") return error({
818
+ message: "Organization not found",
819
+ code: "organization_not_found"
820
+ }, 404);
821
+ return error({
822
+ message: "Permission denied",
823
+ code: "permission_denied"
824
+ }, 403);
825
+ }
826
+ return json({ success: true });
827
+ }
828
+ }),
829
+ defineOrganizationRoute({
830
+ method: "GET",
831
+ path: "/organizations/:organizationId/members",
832
+ queryParameters: [
833
+ "pageSize",
834
+ "cursor",
835
+ "sessionId"
836
+ ],
837
+ outputSchema: z.object({
838
+ members: z.array(memberSchema),
839
+ cursor: z.string().optional(),
840
+ hasNextPage: z.boolean()
841
+ }),
842
+ errorCodes: [
843
+ "invalid_input",
844
+ "organization_not_found",
845
+ "permission_denied",
846
+ "session_invalid"
847
+ ],
848
+ handler: async function({ pathParams, query, headers }, { json, error }) {
849
+ const parsed = pageQuerySchema.safeParse(Object.fromEntries(query.entries()));
850
+ if (!parsed.success) return error({
851
+ message: "Invalid query parameters",
852
+ code: "invalid_input"
853
+ }, 400);
854
+ const sessionId = extractSessionId(headers, query.get("sessionId"));
855
+ if (!sessionId) return error({
856
+ message: "Session ID required",
857
+ code: "session_invalid"
858
+ }, 400);
859
+ const cursor = parseCursor(query.get("cursor"));
860
+ const [result] = await this.handlerTx().withServiceCalls(() => [services.listOrganizationMembersWithSession({
861
+ sessionId,
862
+ organizationId: pathParams.organizationId,
863
+ pageSize: parsed.data.pageSize,
864
+ cursor
865
+ })]).execute();
866
+ if (!result.ok) {
867
+ if (result.code === "session_invalid") return error({
868
+ message: "Invalid session",
869
+ code: "session_invalid"
870
+ }, 401);
871
+ if (result.code === "organization_not_found") return error({
872
+ message: "Organization not found",
873
+ code: "organization_not_found"
874
+ }, 404);
875
+ return error({
876
+ message: "Permission denied",
877
+ code: "permission_denied"
878
+ }, 403);
879
+ }
880
+ return json({
881
+ members: result.members.map((member) => serializeMember(member)),
882
+ cursor: result.cursor,
883
+ hasNextPage: result.hasNextPage
884
+ });
885
+ }
886
+ }),
887
+ defineOrganizationRoute({
888
+ method: "POST",
889
+ path: "/organizations/:organizationId/members",
890
+ queryParameters: ["sessionId"],
891
+ inputSchema: z.object({
892
+ userId: z.string(),
893
+ roles: z.array(z.string()).optional()
894
+ }),
895
+ outputSchema: z.object({ member: memberSchema }),
896
+ errorCodes: [
897
+ "organization_not_found",
898
+ "permission_denied",
899
+ "member_already_exists",
900
+ "limit_reached",
901
+ "session_invalid"
902
+ ],
903
+ handler: async function({ input, pathParams, headers, query }, { json, error }) {
904
+ const sessionId = extractSessionId(headers, query.get("sessionId"));
905
+ if (!sessionId) return error({
906
+ message: "Session ID required",
907
+ code: "session_invalid"
908
+ }, 400);
909
+ const body = await input.valid();
910
+ const [result] = await this.handlerTx().withServiceCalls(() => [services.createOrganizationMemberWithSession({
911
+ sessionId,
912
+ organizationId: pathParams.organizationId,
913
+ userId: body.userId,
914
+ roles: body.roles
915
+ })]).execute();
916
+ if (!result.ok) {
917
+ if (result.code === "session_invalid") return error({
918
+ message: "Invalid session",
919
+ code: "session_invalid"
920
+ }, 401);
921
+ if (result.code === "organization_not_found") return error({
922
+ message: "Organization not found",
923
+ code: "organization_not_found"
924
+ }, 404);
925
+ if (result.code === "member_already_exists") return error({
926
+ message: "Member already exists",
927
+ code: "member_already_exists"
928
+ }, 400);
929
+ if (result.code === "limit_reached") return error({
930
+ message: "Limit reached",
931
+ code: "limit_reached"
932
+ }, 400);
933
+ return error({
934
+ message: "Permission denied",
935
+ code: "permission_denied"
936
+ }, 403);
937
+ }
938
+ return json({ member: serializeMember(result.member) });
939
+ }
940
+ }),
941
+ defineOrganizationRoute({
942
+ method: "PATCH",
943
+ path: "/organizations/:organizationId/members/:memberId",
944
+ queryParameters: ["sessionId"],
945
+ inputSchema: z.object({ roles: z.array(z.string()).min(1) }),
946
+ outputSchema: z.object({ member: memberSchema }),
947
+ errorCodes: [
948
+ "member_not_found",
949
+ "permission_denied",
950
+ "last_owner",
951
+ "session_invalid"
952
+ ],
953
+ handler: async function({ input, pathParams, headers, query }, { json, error }) {
954
+ const sessionId = extractSessionId(headers, query.get("sessionId"));
955
+ if (!sessionId) return error({
956
+ message: "Session ID required",
957
+ code: "session_invalid"
958
+ }, 400);
959
+ const body = await input.valid();
960
+ const [result] = await this.handlerTx().withServiceCalls(() => [services.updateOrganizationMemberRolesWithSession({
961
+ sessionId,
962
+ organizationId: pathParams.organizationId,
963
+ memberId: pathParams.memberId,
964
+ roles: body.roles
965
+ })]).execute();
966
+ if (!result.ok) {
967
+ if (result.code === "session_invalid") return error({
968
+ message: "Invalid session",
969
+ code: "session_invalid"
970
+ }, 401);
971
+ if (result.code === "member_not_found") return error({
972
+ message: "Member not found",
973
+ code: "member_not_found"
974
+ }, 404);
975
+ if (result.code === "last_owner") return error({
976
+ message: "Last owner",
977
+ code: "last_owner"
978
+ }, 400);
979
+ return error({
980
+ message: "Permission denied",
981
+ code: "permission_denied"
982
+ }, 403);
983
+ }
984
+ return json({ member: serializeMember(result.member) });
985
+ }
986
+ }),
987
+ defineOrganizationRoute({
988
+ method: "DELETE",
989
+ path: "/organizations/:organizationId/members/:memberId",
990
+ queryParameters: ["sessionId"],
991
+ outputSchema: z.object({ success: z.boolean() }),
992
+ errorCodes: [
993
+ "member_not_found",
994
+ "permission_denied",
995
+ "last_owner",
996
+ "session_invalid"
997
+ ],
998
+ handler: async function({ pathParams, headers, query }, { json, error }) {
999
+ const sessionId = extractSessionId(headers, query.get("sessionId"));
1000
+ if (!sessionId) return error({
1001
+ message: "Session ID required",
1002
+ code: "session_invalid"
1003
+ }, 400);
1004
+ const [result] = await this.handlerTx().withServiceCalls(() => [services.deleteOrganizationMemberWithSession({
1005
+ sessionId,
1006
+ organizationId: pathParams.organizationId,
1007
+ memberId: pathParams.memberId
1008
+ })]).execute();
1009
+ if (!result.ok) {
1010
+ if (result.code === "session_invalid") return error({
1011
+ message: "Invalid session",
1012
+ code: "session_invalid"
1013
+ }, 401);
1014
+ if (result.code === "member_not_found") return error({
1015
+ message: "Member not found",
1016
+ code: "member_not_found"
1017
+ }, 404);
1018
+ if (result.code === "last_owner") return error({
1019
+ message: "Last owner",
1020
+ code: "last_owner"
1021
+ }, 400);
1022
+ return error({
1023
+ message: "Permission denied",
1024
+ code: "permission_denied"
1025
+ }, 403);
1026
+ }
1027
+ return json({ success: true });
1028
+ }
1029
+ }),
1030
+ defineOrganizationRoute({
1031
+ method: "GET",
1032
+ path: "/organizations/:organizationId/invitations",
1033
+ queryParameters: ["sessionId"],
1034
+ outputSchema: z.object({ invitations: z.array(invitationSchema) }),
1035
+ errorCodes: [
1036
+ "organization_not_found",
1037
+ "permission_denied",
1038
+ "session_invalid"
1039
+ ],
1040
+ handler: async function({ pathParams, headers, query }, { json, error }) {
1041
+ const sessionId = extractSessionId(headers, query.get("sessionId"));
1042
+ if (!sessionId) return error({
1043
+ message: "Session ID required",
1044
+ code: "session_invalid"
1045
+ }, 400);
1046
+ const [result] = await this.handlerTx().withServiceCalls(() => [services.listOrganizationInvitationsWithSession({
1047
+ sessionId,
1048
+ organizationId: pathParams.organizationId
1049
+ })]).execute();
1050
+ if (!result.ok) {
1051
+ if (result.code === "session_invalid") return error({
1052
+ message: "Invalid session",
1053
+ code: "session_invalid"
1054
+ }, 401);
1055
+ if (result.code === "organization_not_found") return error({
1056
+ message: "Organization not found",
1057
+ code: "organization_not_found"
1058
+ }, 404);
1059
+ return error({
1060
+ message: "Permission denied",
1061
+ code: "permission_denied"
1062
+ }, 403);
1063
+ }
1064
+ return json({ invitations: result.invitations.map(serializeInvitation) });
1065
+ }
1066
+ }),
1067
+ defineOrganizationRoute({
1068
+ method: "POST",
1069
+ path: "/organizations/:organizationId/invitations",
1070
+ queryParameters: ["sessionId"],
1071
+ inputSchema: z.object({
1072
+ email: z.email(),
1073
+ roles: z.array(z.string()).optional()
1074
+ }),
1075
+ outputSchema: z.object({ invitation: invitationSchema }),
1076
+ errorCodes: [
1077
+ "organization_not_found",
1078
+ "permission_denied",
1079
+ "limit_reached",
1080
+ "session_invalid"
1081
+ ],
1082
+ handler: async function({ input, pathParams, headers, query }, { json, error }) {
1083
+ const sessionId = extractSessionId(headers, query.get("sessionId"));
1084
+ if (!sessionId) return error({
1085
+ message: "Session ID required",
1086
+ code: "session_invalid"
1087
+ }, 400);
1088
+ const body = await input.valid();
1089
+ const [result] = await this.handlerTx().withServiceCalls(() => [services.createOrganizationInvitationWithSession({
1090
+ sessionId,
1091
+ organizationId: pathParams.organizationId,
1092
+ email: body.email,
1093
+ roles: body.roles
1094
+ })]).execute();
1095
+ if (!result.ok) {
1096
+ if (result.code === "session_invalid") return error({
1097
+ message: "Invalid session",
1098
+ code: "session_invalid"
1099
+ }, 401);
1100
+ if (result.code === "organization_not_found") return error({
1101
+ message: "Organization not found",
1102
+ code: "organization_not_found"
1103
+ }, 404);
1104
+ if (result.code === "limit_reached") return error({
1105
+ message: "Limit reached",
1106
+ code: "limit_reached"
1107
+ }, 400);
1108
+ return error({
1109
+ message: "Permission denied",
1110
+ code: "permission_denied"
1111
+ }, 403);
1112
+ }
1113
+ return json({ invitation: serializeInvitation(result.invitation) });
1114
+ }
1115
+ })
1116
+ ];
1117
+ });
1118
+
1119
+ //#endregion
1120
+ //#region src/schema.ts
1121
+ const authSchema = schema("auth", (s) => {
1122
+ return s.addTable("user", (t) => {
1123
+ return t.addColumn("id", idColumn()).addColumn("email", column("string")).addColumn("passwordHash", column("string")).addColumn("role", column("string").defaultTo("user")).addColumn("createdAt", column("timestamp").defaultTo((b) => b.now())).createIndex("idx_user_email", ["email"]).createIndex("idx_user_id", ["id"], { unique: true });
1124
+ }).addTable("session", (t) => {
1125
+ return t.addColumn("id", idColumn()).addColumn("userId", referenceColumn()).addColumn("expiresAt", column("timestamp")).addColumn("createdAt", column("timestamp").defaultTo((b) => b.now())).createIndex("idx_session_user", ["userId"]);
1126
+ }).alterTable("user", (t) => {
1127
+ return t.addColumn("bannedAt", column("timestamp").nullable()).createIndex("idx_user_createdAt", ["createdAt"]);
1128
+ }).alterTable("session", (t) => {
1129
+ return t.addColumn("activeOrganizationId", referenceColumn().nullable());
1130
+ }).addTable("organization", (t) => {
1131
+ return t.addColumn("id", idColumn()).addColumn("name", column("string")).addColumn("slug", column("string")).addColumn("logoUrl", column("string").nullable()).addColumn("metadata", column("json").nullable()).addColumn("createdBy", referenceColumn()).addColumn("createdAt", column("timestamp").defaultTo((b) => b.now())).addColumn("updatedAt", column("timestamp").defaultTo((b) => b.now())).addColumn("deletedAt", column("timestamp").nullable()).createIndex("idx_organization_slug", ["slug"], { unique: true }).createIndex("idx_organization_createdBy", ["createdBy"]);
1132
+ }).addTable("organizationMember", (t) => {
1133
+ return t.addColumn("id", idColumn()).addColumn("organizationId", referenceColumn()).addColumn("userId", referenceColumn()).addColumn("createdAt", column("timestamp").defaultTo((b) => b.now())).addColumn("updatedAt", column("timestamp").defaultTo((b) => b.now())).createIndex("idx_org_member_org_user", ["organizationId", "userId"], { unique: true }).createIndex("idx_org_member_user", ["userId"]).createIndex("idx_org_member_org", ["organizationId"]);
1134
+ }).addTable("organizationMemberRole", (t) => {
1135
+ return t.addColumn("id", idColumn()).addColumn("memberId", referenceColumn()).addColumn("role", column("string")).addColumn("createdAt", column("timestamp").defaultTo((b) => b.now())).createIndex("idx_org_member_role_member_role", ["memberId", "role"], { unique: true }).createIndex("idx_org_member_role_member", ["memberId"]).createIndex("idx_org_member_role_role", ["role"]);
1136
+ }).addTable("organizationInvitation", (t) => {
1137
+ return t.addColumn("id", idColumn()).addColumn("organizationId", referenceColumn()).addColumn("email", column("string")).addColumn("roles", column("json")).addColumn("status", column("string")).addColumn("token", column("string")).addColumn("inviterId", referenceColumn()).addColumn("expiresAt", column("timestamp")).addColumn("createdAt", column("timestamp").defaultTo((b) => b.now())).addColumn("respondedAt", column("timestamp").nullable()).createIndex("idx_org_invitation_token", ["token"], { unique: true }).createIndex("idx_org_invitation_org_status", ["organizationId", "status"]).createIndex("idx_org_invitation_email", ["email"]).createIndex("idx_org_invitation_email_status", ["email", "status"]);
1138
+ }).addReference("sessionOwner", {
1139
+ from: {
1140
+ table: "session",
1141
+ column: "userId"
1142
+ },
1143
+ to: {
1144
+ table: "user",
1145
+ column: "id"
1146
+ },
1147
+ type: "one"
1148
+ }).addReference("sessionActiveOrganization", {
1149
+ from: {
1150
+ table: "session",
1151
+ column: "activeOrganizationId"
1152
+ },
1153
+ to: {
1154
+ table: "organization",
1155
+ column: "id"
1156
+ },
1157
+ type: "one"
1158
+ }).addReference("organizationCreator", {
1159
+ from: {
1160
+ table: "organization",
1161
+ column: "createdBy"
1162
+ },
1163
+ to: {
1164
+ table: "user",
1165
+ column: "id"
1166
+ },
1167
+ type: "one"
1168
+ }).addReference("organizationMemberOrganization", {
1169
+ from: {
1170
+ table: "organizationMember",
1171
+ column: "organizationId"
1172
+ },
1173
+ to: {
1174
+ table: "organization",
1175
+ column: "id"
1176
+ },
1177
+ type: "one"
1178
+ }).addReference("organizationMembers", {
1179
+ from: {
1180
+ table: "organization",
1181
+ column: "id"
1182
+ },
1183
+ to: {
1184
+ table: "organizationMember",
1185
+ column: "organizationId"
1186
+ },
1187
+ type: "many",
1188
+ foreignKey: false
1189
+ }).addReference("organizationMemberUser", {
1190
+ from: {
1191
+ table: "organizationMember",
1192
+ column: "userId"
1193
+ },
1194
+ to: {
1195
+ table: "user",
1196
+ column: "id"
1197
+ },
1198
+ type: "one"
1199
+ }).addReference("organizationMemberRoleMember", {
1200
+ from: {
1201
+ table: "organizationMemberRole",
1202
+ column: "memberId"
1203
+ },
1204
+ to: {
1205
+ table: "organizationMember",
1206
+ column: "id"
1207
+ },
1208
+ type: "one"
1209
+ }).addReference("organizationInvitationOrganization", {
1210
+ from: {
1211
+ table: "organizationInvitation",
1212
+ column: "organizationId"
1213
+ },
1214
+ to: {
1215
+ table: "organization",
1216
+ column: "id"
1217
+ },
1218
+ type: "one"
1219
+ }).addReference("organizationInvitationInviter", {
1220
+ from: {
1221
+ table: "organizationInvitation",
1222
+ column: "inviterId"
1223
+ },
1224
+ to: {
1225
+ table: "user",
1226
+ column: "id"
1227
+ },
1228
+ type: "one"
1229
+ }).addTable("oauthAccount", (t) => {
1230
+ return t.addColumn("id", idColumn()).addColumn("userId", referenceColumn()).addColumn("provider", column("string")).addColumn("providerAccountId", column("string")).addColumn("email", column("string").nullable()).addColumn("emailVerified", column("bool").defaultTo(false)).addColumn("image", column("string").nullable()).addColumn("accessToken", column("string").nullable()).addColumn("refreshToken", column("string").nullable()).addColumn("idToken", column("string").nullable()).addColumn("tokenType", column("string").nullable()).addColumn("tokenExpiresAt", column("timestamp").nullable()).addColumn("scopes", column("json").nullable()).addColumn("rawProfile", column("json").nullable()).addColumn("createdAt", column("timestamp").defaultTo((b) => b.now())).addColumn("updatedAt", column("timestamp").defaultTo((b) => b.now())).createIndex("idx_oauth_account_provider_account", ["provider", "providerAccountId"], { unique: true }).createIndex("idx_oauth_account_user", ["userId"]).createIndex("idx_oauth_account_provider", ["provider"]);
1231
+ }).addTable("oauthState", (t) => {
1232
+ return t.addColumn("id", idColumn()).addColumn("provider", column("string")).addColumn("state", column("string")).addColumn("codeVerifier", column("string").nullable()).addColumn("redirectUri", column("string").nullable()).addColumn("returnTo", column("string").nullable()).addColumn("linkUserId", referenceColumn().nullable()).addColumn("createdAt", column("timestamp").defaultTo((b) => b.now())).addColumn("expiresAt", column("timestamp")).createIndex("idx_oauth_state_state", ["state"], { unique: true }).createIndex("idx_oauth_state_provider", ["provider"]).createIndex("idx_oauth_state_expiresAt", ["expiresAt"]);
1233
+ }).addReference("oauthAccountUser", {
1234
+ from: {
1235
+ table: "oauthAccount",
1236
+ column: "userId"
1237
+ },
1238
+ to: {
1239
+ table: "user",
1240
+ column: "id"
1241
+ },
1242
+ type: "one"
1243
+ }).addReference("oauthStateLinkUser", {
1244
+ from: {
1245
+ table: "oauthState",
1246
+ column: "linkUserId"
1247
+ },
1248
+ to: {
1249
+ table: "user",
1250
+ column: "id"
1251
+ },
1252
+ type: "one"
1253
+ }).alterTable("user", (t) => {
1254
+ return t.alterColumn("passwordHash").nullable();
1255
+ }).addReference("sessionOrganizationMembers", {
1256
+ type: "many",
1257
+ from: {
1258
+ table: "session",
1259
+ column: "userId"
1260
+ },
1261
+ to: {
1262
+ table: "organizationMember",
1263
+ column: "userId"
1264
+ },
1265
+ foreignKey: false
1266
+ }).addReference("sessionMembers", {
1267
+ type: "many",
1268
+ from: {
1269
+ table: "session",
1270
+ column: "userId"
1271
+ },
1272
+ to: {
1273
+ table: "organizationMember",
1274
+ column: "userId"
1275
+ },
1276
+ foreignKey: false
1277
+ }).addReference("organizationMemberRoles", {
1278
+ type: "many",
1279
+ from: {
1280
+ table: "organizationMember",
1281
+ column: "id"
1282
+ },
1283
+ to: {
1284
+ table: "organizationMemberRole",
1285
+ column: "memberId"
1286
+ },
1287
+ foreignKey: false
1288
+ }).addReference("roles", {
1289
+ type: "many",
1290
+ from: {
1291
+ table: "organizationMember",
1292
+ column: "id"
1293
+ },
1294
+ to: {
1295
+ table: "organizationMemberRole",
1296
+ column: "memberId"
1297
+ },
1298
+ foreignKey: false
1299
+ }).addReference("organization", {
1300
+ type: "one",
1301
+ from: {
1302
+ table: "organizationMember",
1303
+ column: "organizationId"
1304
+ },
1305
+ to: {
1306
+ table: "organization",
1307
+ column: "id"
1308
+ },
1309
+ foreignKey: false
1310
+ }).addReference("userOrganizationInvitations", {
1311
+ type: "many",
1312
+ from: {
1313
+ table: "user",
1314
+ column: "email"
1315
+ },
1316
+ to: {
1317
+ table: "organizationInvitation",
1318
+ column: "email"
1319
+ },
1320
+ foreignKey: false
1321
+ }).addReference("invitations", {
1322
+ type: "many",
1323
+ from: {
1324
+ table: "user",
1325
+ column: "email"
1326
+ },
1327
+ to: {
1328
+ table: "organizationInvitation",
1329
+ column: "email"
1330
+ },
1331
+ foreignKey: false
1332
+ }).addReference("organization", {
1333
+ type: "one",
1334
+ from: {
1335
+ table: "organizationInvitation",
1336
+ column: "organizationId"
1337
+ },
1338
+ to: {
1339
+ table: "organization",
1340
+ column: "id"
1341
+ },
1342
+ foreignKey: false
1343
+ }).alterTable("session", (t) => {
1344
+ return t.createIndex("idx_session_id_expiresAt", ["id", "expiresAt"]);
1345
+ }).addReference("userOrganizationMembers", {
1346
+ type: "many",
1347
+ from: {
1348
+ table: "user",
1349
+ column: "id"
1350
+ },
1351
+ to: {
1352
+ table: "organizationMember",
1353
+ column: "userId"
1354
+ },
1355
+ foreignKey: false
1356
+ }).alterTable("oauthState", (t) => {
1357
+ return t.addColumn("sessionSeed", column("json").nullable());
1358
+ });
1359
+ });
1360
+
1361
+ //#endregion
1362
+ //#region src/session/session-seed.ts
1363
+ const sessionSeedSchema = z.object({ activeOrganizationId: z.string().trim().min(1).optional() }).strict();
1364
+ function normalizeSessionSeed(session) {
1365
+ const parsed = sessionSeedSchema.safeParse(session ?? {});
1366
+ if (!parsed.success) return null;
1367
+ return parsed.data.activeOrganizationId ? parsed.data : null;
1368
+ }
1369
+ function serializeSessionSeedForQuery(session) {
1370
+ const normalized = normalizeSessionSeed(session);
1371
+ return normalized ? JSON.stringify(normalized) : void 0;
1372
+ }
1373
+
1374
+ //#endregion
1375
+ //#region src/session/session.ts
1376
+ const sessionRoutesFactory = defineRoutes().create(({ services, config }) => {
1377
+ return [defineRoute({
1378
+ method: "POST",
1379
+ path: "/sign-out",
1380
+ inputSchema: z.object({ sessionId: z.string().optional() }).optional(),
1381
+ outputSchema: z.object({ success: z.boolean() }),
1382
+ errorCodes: ["session_not_found"],
1383
+ handler: () => {}
1384
+ }), defineRoute({
1385
+ method: "GET",
1386
+ path: "/me",
1387
+ queryParameters: ["sessionId"],
1388
+ outputSchema: z.object({
1389
+ user: z.object({
1390
+ id: z.string(),
1391
+ email: z.string(),
1392
+ role: z.enum(["user", "admin"])
1393
+ }),
1394
+ organizations: z.array(z.object({
1395
+ organization: organizationSchema,
1396
+ member: memberSchema
1397
+ })),
1398
+ activeOrganization: z.object({
1399
+ organization: organizationSchema,
1400
+ member: memberSchema
1401
+ }).nullable(),
1402
+ invitations: z.array(z.object({
1403
+ invitation: invitationSummarySchema,
1404
+ organization: organizationSchema
1405
+ }))
1406
+ }),
1407
+ errorCodes: ["session_invalid"],
1408
+ handler: () => {}
1409
+ })];
1410
+ });
1411
+
1412
+ //#endregion
1413
+ //#region src/user/user-actions.ts
1414
+ const userActionsRoutesFactory = defineRoutes().create(({ services, config }) => {
1415
+ return [
1416
+ defineRoute({
1417
+ method: "PATCH",
1418
+ path: "/users/:userId/role",
1419
+ inputSchema: z.object({ role: z.enum(["user", "admin"]) }),
1420
+ outputSchema: z.object({ success: z.boolean() }),
1421
+ errorCodes: [
1422
+ "invalid_input",
1423
+ "session_invalid",
1424
+ "permission_denied"
1425
+ ],
1426
+ handler: () => {}
1427
+ }),
1428
+ defineRoute({
1429
+ method: "POST",
1430
+ path: "/sign-up",
1431
+ inputSchema: z.object({
1432
+ email: z.email(),
1433
+ password: z.string().min(8).max(100)
1434
+ }),
1435
+ outputSchema: z.object({
1436
+ sessionId: z.string(),
1437
+ userId: z.string(),
1438
+ email: z.string(),
1439
+ role: z.enum(["user", "admin"])
1440
+ }),
1441
+ errorCodes: [
1442
+ "email_already_exists",
1443
+ "invalid_input",
1444
+ "email_password_disabled"
1445
+ ],
1446
+ handler: () => {}
1447
+ }),
1448
+ defineRoute({
1449
+ method: "POST",
1450
+ path: "/sign-in",
1451
+ inputSchema: z.object({
1452
+ email: z.email(),
1453
+ password: z.string().min(8).max(100),
1454
+ session: sessionSeedSchema.optional()
1455
+ }),
1456
+ outputSchema: z.object({
1457
+ sessionId: z.string(),
1458
+ userId: z.string(),
1459
+ email: z.string(),
1460
+ role: z.enum(["user", "admin"])
1461
+ }),
1462
+ errorCodes: [
1463
+ "invalid_credentials",
1464
+ "user_banned",
1465
+ "email_password_disabled"
1466
+ ],
1467
+ handler: () => {}
1468
+ }),
1469
+ defineRoute({
1470
+ method: "POST",
1471
+ path: "/change-password",
1472
+ inputSchema: z.object({ newPassword: z.string().min(8).max(100) }),
1473
+ outputSchema: z.object({ success: z.boolean() }),
1474
+ errorCodes: ["session_invalid"],
1475
+ handler: () => {}
1476
+ })
1477
+ ];
1478
+ });
1479
+
1480
+ //#endregion
1481
+ //#region src/user/user-overview.ts
1482
+ const sortBySchema = z.enum(["email", "createdAt"]);
1483
+ const userOverviewRoutesFactory = defineRoutes().create(({ services }) => {
1484
+ return [defineRoute({
1485
+ method: "GET",
1486
+ path: "/users",
1487
+ queryParameters: [
1488
+ "search",
1489
+ "sortBy",
1490
+ "sortOrder",
1491
+ "pageSize",
1492
+ "cursor"
1493
+ ],
1494
+ outputSchema: z.object({
1495
+ users: z.array(z.object({
1496
+ id: z.string(),
1497
+ email: z.string(),
1498
+ role: z.enum(["user", "admin"]),
1499
+ createdAt: z.string()
1500
+ })),
1501
+ cursor: z.string().optional(),
1502
+ hasNextPage: z.boolean(),
1503
+ sortBy: sortBySchema
1504
+ }),
1505
+ errorCodes: ["invalid_input"],
1506
+ handler: () => {}
1507
+ })];
1508
+ });
1509
+
1510
+ //#endregion
1511
+ //#region src/oauth/utils.ts
1512
+ const DEFAULT_STATE_TTL_MS = 10 * 60 * 1e3;
1513
+ const createAuthorizationURL = (params) => {
1514
+ const url = new URL(params.authorizationEndpoint);
1515
+ url.searchParams.set("response_type", "code");
1516
+ url.searchParams.set("client_id", params.clientId);
1517
+ url.searchParams.set("redirect_uri", params.redirectURI);
1518
+ url.searchParams.set("state", params.state);
1519
+ if (params.scopes && params.scopes.length > 0) url.searchParams.set("scope", params.scopes.join(" "));
1520
+ if (params.prompt) url.searchParams.set("prompt", params.prompt);
1521
+ if (params.loginHint) url.searchParams.set("login_hint", params.loginHint);
1522
+ if (params.codeChallenge) {
1523
+ url.searchParams.set("code_challenge", params.codeChallenge);
1524
+ url.searchParams.set("code_challenge_method", params.codeChallengeMethod ?? "S256");
1525
+ }
1526
+ if (params.extraParams) {
1527
+ for (const [key, value] of Object.entries(params.extraParams)) if (typeof value === "string" && value.length > 0) url.searchParams.set(key, value);
1528
+ }
1529
+ return url;
1530
+ };
1531
+ const parseScopes = (scopeValue) => {
1532
+ if (typeof scopeValue !== "string") return void 0;
1533
+ const scopes = scopeValue.split(/[,\s]+/).map((scope) => scope.trim()).filter(Boolean);
1534
+ return scopes.length > 0 ? scopes : void 0;
1535
+ };
1536
+ const readNumber = (value) => {
1537
+ if (typeof value === "number") return Number.isFinite(value) ? value : void 0;
1538
+ if (typeof value === "string") {
1539
+ const parsed = Number(value);
1540
+ return Number.isFinite(parsed) ? parsed : void 0;
1541
+ }
1542
+ return void 0;
1543
+ };
1544
+ const normalizeOAuthTokens = (response) => {
1545
+ const accessToken = typeof response["access_token"] === "string" ? response["access_token"] : void 0;
1546
+ const refreshToken = typeof response["refresh_token"] === "string" ? response["refresh_token"] : void 0;
1547
+ const tokenType = typeof response["token_type"] === "string" ? response["token_type"] : void 0;
1548
+ const idToken = typeof response["id_token"] === "string" ? response["id_token"] : void 0;
1549
+ const expiresIn = readNumber(response["expires_in"]);
1550
+ const accessTokenExpiresAt = expiresIn ? new Date(Date.now() + expiresIn * 1e3) : void 0;
1551
+ return {
1552
+ accessToken,
1553
+ refreshToken,
1554
+ tokenType,
1555
+ idToken,
1556
+ accessTokenExpiresAt,
1557
+ scopes: parseScopes(response["scope"]),
1558
+ raw: response
1559
+ };
1560
+ };
1561
+ const parseOAuthTokenResponse = async (response) => {
1562
+ const contentType = response.headers.get("content-type") ?? "";
1563
+ const status = response.status;
1564
+ let data;
1565
+ try {
1566
+ if (contentType.includes("application/json")) data = await response.json();
1567
+ else {
1568
+ const text = await response.text();
1569
+ data = Object.fromEntries(new URLSearchParams(text));
1570
+ }
1571
+ } catch (err) {
1572
+ return {
1573
+ ok: false,
1574
+ status,
1575
+ error: err instanceof Error ? err.message : "Invalid token response"
1576
+ };
1577
+ }
1578
+ if (!response.ok) {
1579
+ const message = typeof data.error_description === "string" ? data.error_description : typeof data.error === "string" ? data.error : `Token request failed with status ${status}`;
1580
+ return {
1581
+ ok: false,
1582
+ status,
1583
+ error: message,
1584
+ data
1585
+ };
1586
+ }
1587
+ if (!data || typeof data !== "object") return {
1588
+ ok: false,
1589
+ status,
1590
+ error: "Token response was empty"
1591
+ };
1592
+ return {
1593
+ ok: true,
1594
+ data
1595
+ };
1596
+ };
1597
+
1598
+ //#endregion
1599
+ //#region src/oauth/providers/github/github.ts
1600
+ const defaultScopes = ["read:user", "user:email"];
1601
+ const defaultUserAgent = "fragno-auth";
1602
+ const tokenEndpoint = "https://github.com/login/oauth/access_token";
1603
+ const profileEndpoint = "https://api.github.com/user";
1604
+ const emailsEndpoint = "https://api.github.com/user/emails";
1605
+ const createGithubOAuthClient = (options) => {
1606
+ const fetcher = options?.fetcher ?? fetch;
1607
+ const userAgent = options?.userAgent ?? defaultUserAgent;
1608
+ return {
1609
+ exchangeCode: async ({ code, redirectURI, clientId, clientSecret, codeVerifier }) => {
1610
+ const body = new URLSearchParams({
1611
+ client_id: clientId,
1612
+ client_secret: clientSecret,
1613
+ code,
1614
+ redirect_uri: redirectURI
1615
+ });
1616
+ if (codeVerifier) body.set("code_verifier", codeVerifier);
1617
+ const response = await fetcher(tokenEndpoint, {
1618
+ method: "POST",
1619
+ headers: {
1620
+ accept: "application/json",
1621
+ "content-type": "application/x-www-form-urlencoded"
1622
+ },
1623
+ body
1624
+ });
1625
+ const parsed = await parseOAuthTokenResponse(response);
1626
+ if (!parsed.ok) return null;
1627
+ return normalizeOAuthTokens(parsed.data);
1628
+ },
1629
+ fetchProfile: async (accessToken) => {
1630
+ const response = await fetcher(profileEndpoint, { headers: {
1631
+ "User-Agent": userAgent,
1632
+ authorization: `Bearer ${accessToken}`
1633
+ } });
1634
+ if (!response.ok) return null;
1635
+ return await response.json();
1636
+ },
1637
+ fetchEmails: async (accessToken) => {
1638
+ const response = await fetcher(emailsEndpoint, { headers: {
1639
+ "User-Agent": userAgent,
1640
+ authorization: `Bearer ${accessToken}`
1641
+ } });
1642
+ if (!response.ok) return [];
1643
+ return await response.json();
1644
+ }
1645
+ };
1646
+ };
1647
+ const github = (options) => {
1648
+ const client = options.client ?? createGithubOAuthClient();
1649
+ const authorizationEndpoint = options.authorizationEndpoint ?? "https://github.com/login/oauth/authorize";
1650
+ return {
1651
+ id: "github",
1652
+ name: "GitHub",
1653
+ options,
1654
+ createAuthorizationURL({ state, scopes, loginHint, redirectURI }) {
1655
+ const resolvedScopes = options.disableDefaultScope ? [] : [...defaultScopes];
1656
+ if (options.scope) resolvedScopes.push(...options.scope);
1657
+ if (scopes) resolvedScopes.push(...scopes);
1658
+ return createAuthorizationURL({
1659
+ authorizationEndpoint,
1660
+ clientId: options.clientId,
1661
+ redirectURI,
1662
+ state,
1663
+ scopes: resolvedScopes,
1664
+ prompt: options.prompt,
1665
+ loginHint: loginHint ?? void 0,
1666
+ extraParams: loginHint ? { login: loginHint } : void 0
1667
+ });
1668
+ },
1669
+ validateAuthorizationCode: async ({ code, redirectURI, codeVerifier }) => {
1670
+ return client.exchangeCode({
1671
+ code,
1672
+ redirectURI,
1673
+ clientId: options.clientId,
1674
+ clientSecret: options.clientSecret,
1675
+ codeVerifier
1676
+ });
1677
+ },
1678
+ refreshAccessToken: options.refreshAccessToken,
1679
+ async getUserInfo(token) {
1680
+ if (options.getUserInfo) return options.getUserInfo(token);
1681
+ if (!token.accessToken) return null;
1682
+ const profile = await client.fetchProfile(token.accessToken);
1683
+ if (!profile) return null;
1684
+ const emails = await client.fetchEmails(token.accessToken);
1685
+ if (!profile.email && emails.length > 0) profile.email = (emails.find((entry) => entry.primary) ?? emails[0])?.email;
1686
+ const emailVerified = emails.find((entry) => entry.email === profile.email)?.verified ?? false;
1687
+ const mapped = await options.mapProfileToUser?.(profile);
1688
+ return {
1689
+ user: {
1690
+ id: profile.id,
1691
+ name: profile.name || profile.login,
1692
+ email: profile.email ?? null,
1693
+ image: profile.avatar_url,
1694
+ emailVerified,
1695
+ ...mapped
1696
+ },
1697
+ data: profile
1698
+ };
1699
+ }
1700
+ };
1701
+ };
1702
+
1703
+ //#endregion
1704
+ //#region src/index.ts
1705
+ const authFragmentDefinition = defineFragment("auth").extend((x) => x).providesBaseService(() => {}).build();
1706
+ function createAuthFragment(config = {}, fragnoConfig) {
1707
+ return {};
1708
+ }
1709
+ function createAuthFragmentClients(fragnoConfig) {
1710
+ const config = { ...fragnoConfig };
1711
+ const b = createClientBuilder(authFragmentDefinition, config, [
1712
+ userActionsRoutesFactory,
1713
+ sessionRoutesFactory,
1714
+ userOverviewRoutesFactory,
1715
+ organizationRoutesFactory,
1716
+ oauthRoutesFactory
1717
+ ], {
1718
+ type: "options",
1719
+ options: { credentials: "include" }
1720
+ });
1721
+ const useMe = b.createHook("/me");
1722
+ const useSignUp = b.createMutator("POST", "/sign-up");
1723
+ const useSignIn = b.createMutator("POST", "/sign-in");
1724
+ const useSignOut = b.createMutator("POST", "/sign-out", (invalidate) => {
1725
+ invalidate("GET", "/me", {});
1726
+ invalidate("GET", "/users", {});
1727
+ });
1728
+ const useUsers = b.createHook("/users");
1729
+ const useUpdateUserRole = b.createMutator("PATCH", "/users/:userId/role", (invalidate) => {
1730
+ invalidate("GET", "/users", {});
1731
+ invalidate("GET", "/me", {});
1732
+ });
1733
+ const useChangePassword = b.createMutator("POST", "/change-password");
1734
+ const useOrganizations = b.createHook("/organizations");
1735
+ const useOrganization = b.createHook("/organizations/:organizationId");
1736
+ const useCreateOrganization = b.createMutator("POST", "/organizations", (invalidate) => {
1737
+ invalidate("GET", "/organizations", {});
1738
+ invalidate("GET", "/organizations/active", {});
1739
+ invalidate("GET", "/me", {});
1740
+ });
1741
+ const useUpdateOrganization = b.createMutator("PATCH", "/organizations/:organizationId", (invalidate, params) => {
1742
+ const organizationId = params.pathParams.organizationId;
1743
+ if (organizationId) {
1744
+ invalidate("GET", "/organizations/:organizationId", { pathParams: { organizationId } });
1745
+ invalidate("GET", "/organizations/:organizationId/members", { pathParams: { organizationId } });
1746
+ invalidate("GET", "/organizations/:organizationId/invitations", { pathParams: { organizationId } });
1747
+ }
1748
+ invalidate("GET", "/organizations", {});
1749
+ invalidate("GET", "/organizations/active", {});
1750
+ invalidate("GET", "/me", {});
1751
+ });
1752
+ const useDeleteOrganization = b.createMutator("DELETE", "/organizations/:organizationId", (invalidate, params) => {
1753
+ const organizationId = params.pathParams.organizationId;
1754
+ if (organizationId) {
1755
+ invalidate("GET", "/organizations/:organizationId", { pathParams: { organizationId } });
1756
+ invalidate("GET", "/organizations/:organizationId/members", { pathParams: { organizationId } });
1757
+ invalidate("GET", "/organizations/:organizationId/invitations", { pathParams: { organizationId } });
1758
+ }
1759
+ invalidate("GET", "/organizations", {});
1760
+ invalidate("GET", "/organizations/active", {});
1761
+ invalidate("GET", "/me", {});
1762
+ });
1763
+ const useActiveOrganization = b.createHook("/organizations/active");
1764
+ const useSetActiveOrganization = b.createMutator("POST", "/organizations/active", (invalidate) => {
1765
+ invalidate("GET", "/organizations/active", {});
1766
+ invalidate("GET", "/me", {});
1767
+ });
1768
+ const useOrganizationMembers = b.createHook("/organizations/:organizationId/members");
1769
+ const useAddOrganizationMember = b.createMutator("POST", "/organizations/:organizationId/members", (invalidate, params) => {
1770
+ const organizationId = params.pathParams.organizationId;
1771
+ if (!organizationId) return;
1772
+ invalidate("GET", "/organizations/:organizationId/members", { pathParams: { organizationId } });
1773
+ invalidate("GET", "/organizations/:organizationId", { pathParams: { organizationId } });
1774
+ invalidate("GET", "/organizations", {});
1775
+ invalidate("GET", "/me", {});
1776
+ });
1777
+ const useUpdateOrganizationMemberRoles = b.createMutator("PATCH", "/organizations/:organizationId/members/:memberId", (invalidate, params) => {
1778
+ const organizationId = params.pathParams.organizationId;
1779
+ if (!organizationId) return;
1780
+ invalidate("GET", "/organizations/:organizationId/members", { pathParams: { organizationId } });
1781
+ invalidate("GET", "/organizations/:organizationId", { pathParams: { organizationId } });
1782
+ invalidate("GET", "/organizations", {});
1783
+ invalidate("GET", "/me", {});
1784
+ });
1785
+ const useRemoveOrganizationMember = b.createMutator("DELETE", "/organizations/:organizationId/members/:memberId", (invalidate, params) => {
1786
+ const organizationId = params.pathParams.organizationId;
1787
+ if (!organizationId) return;
1788
+ invalidate("GET", "/organizations/:organizationId/members", { pathParams: { organizationId } });
1789
+ invalidate("GET", "/organizations/:organizationId", { pathParams: { organizationId } });
1790
+ invalidate("GET", "/organizations", {});
1791
+ invalidate("GET", "/me", {});
1792
+ });
1793
+ const useOrganizationInvitations = b.createHook("/organizations/:organizationId/invitations");
1794
+ const useInviteOrganizationMember = b.createMutator("POST", "/organizations/:organizationId/invitations", (invalidate, params) => {
1795
+ const organizationId = params.pathParams.organizationId;
1796
+ if (!organizationId) return;
1797
+ invalidate("GET", "/organizations/:organizationId/invitations", { pathParams: { organizationId } });
1798
+ });
1799
+ const useRespondOrganizationInvitation = b.createMutator("PATCH", "/organizations/invitations/:invitationId", (invalidate) => {
1800
+ invalidate("GET", "/organizations/invitations", {});
1801
+ invalidate("GET", "/organizations", {});
1802
+ invalidate("GET", "/organizations/active", {});
1803
+ invalidate("GET", "/me", {});
1804
+ });
1805
+ const useUserInvitations = b.createHook("/organizations/invitations");
1806
+ const useOAuthAuthorize = b.createHook("/oauth/:provider/authorize");
1807
+ const useOAuthCallback = b.createHook("/oauth/:provider/callback");
1808
+ const readRawMe = async (params) => {
1809
+ if (params?.sessionId) return useMe.query({ query: { sessionId: params.sessionId } });
1810
+ return useMe.query();
1811
+ };
1812
+ const defaultOrganizationPreference = createDefaultOrganizationPreferenceState({
1813
+ meStore: useMe.store(),
1814
+ readMe: readRawMe,
1815
+ getAccountId: (me) => me.user.id
1816
+ });
1817
+ return {
1818
+ useSignUp,
1819
+ useSignIn,
1820
+ useSignOut,
1821
+ useMe,
1822
+ useDefaultOrganizationPreference: b.createStore(defaultOrganizationPreference.store),
1823
+ useUsers,
1824
+ useUpdateUserRole,
1825
+ useChangePassword,
1826
+ useOrganizations,
1827
+ useOrganization,
1828
+ useCreateOrganization,
1829
+ useUpdateOrganization,
1830
+ useDeleteOrganization,
1831
+ useActiveOrganization,
1832
+ useSetActiveOrganization,
1833
+ useOrganizationMembers,
1834
+ useAddOrganizationMember,
1835
+ useUpdateOrganizationMemberRoles,
1836
+ useRemoveOrganizationMember,
1837
+ useOrganizationInvitations,
1838
+ useInviteOrganizationMember,
1839
+ useRespondOrganizationInvitation,
1840
+ useUserInvitations,
1841
+ useOAuthAuthorize,
1842
+ useOAuthCallback,
1843
+ signIn: { email: async ({ email, password, session, rememberMe: _rememberMe }) => {
1844
+ return useSignIn.mutateQuery({ body: {
1845
+ email,
1846
+ password,
1847
+ session
1848
+ } });
1849
+ } },
1850
+ signUp: { email: async ({ email, password }) => {
1851
+ return useSignUp.mutateQuery({ body: {
1852
+ email,
1853
+ password
1854
+ } });
1855
+ } },
1856
+ signOut: (params) => {
1857
+ return useSignOut.mutateQuery({ body: params?.sessionId ? { sessionId: params.sessionId } : {} });
1858
+ },
1859
+ me: defaultOrganizationPreference.me,
1860
+ defaultOrganization: defaultOrganizationPreference.defaultOrganization,
1861
+ oauth: {
1862
+ getAuthorizationUrl: async (params) => {
1863
+ return useOAuthAuthorize.query({
1864
+ path: { provider: params.provider },
1865
+ query: {
1866
+ redirectUri: params.redirectUri,
1867
+ returnTo: params.returnTo,
1868
+ link: params.link ? "true" : void 0,
1869
+ sessionId: params.sessionId,
1870
+ session: serializeSessionSeedForQuery(params.session),
1871
+ scope: params.scope,
1872
+ loginHint: params.loginHint
1873
+ }
1874
+ });
1875
+ },
1876
+ callback: async (params) => {
1877
+ return useOAuthCallback.query({
1878
+ path: { provider: params.provider },
1879
+ query: {
1880
+ code: params.code,
1881
+ state: params.state,
1882
+ requestSignUp: params.requestSignUp ? "true" : void 0
1883
+ }
1884
+ });
1885
+ }
1886
+ }
1887
+ };
1888
+ }
1889
+
1890
+ //#endregion
1891
+ export { DEFAULT_ORGANIZATION_CHANGE_EVENT, DEFAULT_ORGANIZATION_STORAGE_KEY, NO_ORGANIZATIONS_ERROR_MESSAGE, authFragmentDefinition, clearDefaultOrganizationId, createAuthFragment, createAuthFragmentClients, createGithubOAuthClient, findOrganizationEntry, getDefaultOrganizationChangeEventName, getDefaultOrganizationStorageKey, github, readDefaultOrganizationId, resolveDefaultOrganization, setDefaultOrganizationForMe, subscribeToDefaultOrganizationPreference, syncDefaultOrganizationPreference, writeDefaultOrganizationId };
1892
+ //# sourceMappingURL=src-Ck4bl2NH.js.map