@ai-matrx/data 0.14.2 → 0.15.0

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/dist/db.d.cts CHANGED
@@ -523,4 +523,42 @@ declare function encodeActiveOrgSelection(selection: ActiveOrgSelection): string
523
523
  declare function decodeActiveOrgSelection(raw: string | null | undefined): ActiveOrgSelection | null;
524
524
  declare function createActiveOrgCookie(args: CreateActiveOrgCookieArgs): ActiveOrgCookie;
525
525
 
526
- export { type ActiveOrgCookie, type ActiveOrgCookieOptions, type ActiveOrgSelection, type AuthRetryableResult, type CreateActiveOrgCookieArgs, type CreateSessionRetryOptions, DEFAULT_ACTIVE_ORG_COOKIE_MAX_AGE, DEFAULT_ACTIVE_ORG_COOKIE_NAME, type GuardedUpdateArgs, type GuardedUpdateResult, IncompleteReadError, type JsonObject, type MergeJsonColumnArgs, type MergeJsonColumnResult, type PagedReadQuery, type PagedReadResult, type ReadAllRowsOptions, type ReadAllRowsRestOptions, type SessionSource, SessionUnavailableError, type VersionedRow, asJsonObject, createActiveOrgCookie, createSessionRetry, decodeActiveOrgSelection, encodeActiveOrgSelection, guardedUpdate, isMissingSessionError, mergeJsonColumn, readAllRows, readAllRowsRest, tryReadAllRows, tryReadAllRowsRest };
526
+ /**
527
+ * OAuth-aware transport for a Supabase auth-js client.
528
+ *
529
+ * Supabase's ordinary GoTrue refresh endpoint is `/auth/v1/token`, while a
530
+ * session minted for a registered OAuth client must refresh at
531
+ * `/auth/v1/oauth/token` and include that client id. auth-js owns session
532
+ * storage, locking, rotation and notifications; this module changes only the
533
+ * one outbound refresh request after proving that its refresh token belongs to
534
+ * an allowlisted OAuth client. It never calls `auth.getSession()`.
535
+ */
536
+ interface OAuthRefreshStorage {
537
+ getItem(key: string): string | null | Promise<string | null>;
538
+ }
539
+ interface CreateOAuthRefreshFetchArgs {
540
+ /** The exact Supabase project origin, e.g. `https://db.matrxserver.com`. */
541
+ supabaseUrl: string;
542
+ /** The same auth storage auth-js uses. */
543
+ storage: OAuthRefreshStorage;
544
+ /** The auth-js storage key containing the persisted session JSON. */
545
+ storageKey: string;
546
+ /** Public OAuth client ids this host is intentionally allowed to refresh. */
547
+ oauthClientIds: readonly string[];
548
+ /** Injectable only for tests and non-browser hosts. */
549
+ fetch?: typeof globalThis.fetch;
550
+ }
551
+ interface JwtClaims {
552
+ sub?: unknown;
553
+ client_id?: unknown;
554
+ exp?: unknown;
555
+ }
556
+ /** Decode routing metadata only. The server remains the authentication authority. */
557
+ declare function decodeOAuthRefreshClaims(token: string): JwtClaims | null;
558
+ /**
559
+ * Returns a fetch implementation suitable for `createClient(..., { global:
560
+ * { fetch } })`. Normal sessions and unrelated requests pass through exactly.
561
+ */
562
+ declare function createOAuthRefreshFetch(args: CreateOAuthRefreshFetchArgs): typeof globalThis.fetch;
563
+
564
+ export { type ActiveOrgCookie, type ActiveOrgCookieOptions, type ActiveOrgSelection, type AuthRetryableResult, type CreateActiveOrgCookieArgs, type CreateOAuthRefreshFetchArgs, type CreateSessionRetryOptions, DEFAULT_ACTIVE_ORG_COOKIE_MAX_AGE, DEFAULT_ACTIVE_ORG_COOKIE_NAME, type GuardedUpdateArgs, type GuardedUpdateResult, IncompleteReadError, type JsonObject, type MergeJsonColumnArgs, type MergeJsonColumnResult, type OAuthRefreshStorage, type PagedReadQuery, type PagedReadResult, type ReadAllRowsOptions, type ReadAllRowsRestOptions, type SessionSource, SessionUnavailableError, type VersionedRow, asJsonObject, createActiveOrgCookie, createOAuthRefreshFetch, createSessionRetry, decodeActiveOrgSelection, decodeOAuthRefreshClaims, encodeActiveOrgSelection, guardedUpdate, isMissingSessionError, mergeJsonColumn, readAllRows, readAllRowsRest, tryReadAllRows, tryReadAllRowsRest };
package/dist/db.d.ts CHANGED
@@ -523,4 +523,42 @@ declare function encodeActiveOrgSelection(selection: ActiveOrgSelection): string
523
523
  declare function decodeActiveOrgSelection(raw: string | null | undefined): ActiveOrgSelection | null;
524
524
  declare function createActiveOrgCookie(args: CreateActiveOrgCookieArgs): ActiveOrgCookie;
525
525
 
526
- export { type ActiveOrgCookie, type ActiveOrgCookieOptions, type ActiveOrgSelection, type AuthRetryableResult, type CreateActiveOrgCookieArgs, type CreateSessionRetryOptions, DEFAULT_ACTIVE_ORG_COOKIE_MAX_AGE, DEFAULT_ACTIVE_ORG_COOKIE_NAME, type GuardedUpdateArgs, type GuardedUpdateResult, IncompleteReadError, type JsonObject, type MergeJsonColumnArgs, type MergeJsonColumnResult, type PagedReadQuery, type PagedReadResult, type ReadAllRowsOptions, type ReadAllRowsRestOptions, type SessionSource, SessionUnavailableError, type VersionedRow, asJsonObject, createActiveOrgCookie, createSessionRetry, decodeActiveOrgSelection, encodeActiveOrgSelection, guardedUpdate, isMissingSessionError, mergeJsonColumn, readAllRows, readAllRowsRest, tryReadAllRows, tryReadAllRowsRest };
526
+ /**
527
+ * OAuth-aware transport for a Supabase auth-js client.
528
+ *
529
+ * Supabase's ordinary GoTrue refresh endpoint is `/auth/v1/token`, while a
530
+ * session minted for a registered OAuth client must refresh at
531
+ * `/auth/v1/oauth/token` and include that client id. auth-js owns session
532
+ * storage, locking, rotation and notifications; this module changes only the
533
+ * one outbound refresh request after proving that its refresh token belongs to
534
+ * an allowlisted OAuth client. It never calls `auth.getSession()`.
535
+ */
536
+ interface OAuthRefreshStorage {
537
+ getItem(key: string): string | null | Promise<string | null>;
538
+ }
539
+ interface CreateOAuthRefreshFetchArgs {
540
+ /** The exact Supabase project origin, e.g. `https://db.matrxserver.com`. */
541
+ supabaseUrl: string;
542
+ /** The same auth storage auth-js uses. */
543
+ storage: OAuthRefreshStorage;
544
+ /** The auth-js storage key containing the persisted session JSON. */
545
+ storageKey: string;
546
+ /** Public OAuth client ids this host is intentionally allowed to refresh. */
547
+ oauthClientIds: readonly string[];
548
+ /** Injectable only for tests and non-browser hosts. */
549
+ fetch?: typeof globalThis.fetch;
550
+ }
551
+ interface JwtClaims {
552
+ sub?: unknown;
553
+ client_id?: unknown;
554
+ exp?: unknown;
555
+ }
556
+ /** Decode routing metadata only. The server remains the authentication authority. */
557
+ declare function decodeOAuthRefreshClaims(token: string): JwtClaims | null;
558
+ /**
559
+ * Returns a fetch implementation suitable for `createClient(..., { global:
560
+ * { fetch } })`. Normal sessions and unrelated requests pass through exactly.
561
+ */
562
+ declare function createOAuthRefreshFetch(args: CreateOAuthRefreshFetchArgs): typeof globalThis.fetch;
563
+
564
+ export { type ActiveOrgCookie, type ActiveOrgCookieOptions, type ActiveOrgSelection, type AuthRetryableResult, type CreateActiveOrgCookieArgs, type CreateOAuthRefreshFetchArgs, type CreateSessionRetryOptions, DEFAULT_ACTIVE_ORG_COOKIE_MAX_AGE, DEFAULT_ACTIVE_ORG_COOKIE_NAME, type GuardedUpdateArgs, type GuardedUpdateResult, IncompleteReadError, type JsonObject, type MergeJsonColumnArgs, type MergeJsonColumnResult, type OAuthRefreshStorage, type PagedReadQuery, type PagedReadResult, type ReadAllRowsOptions, type ReadAllRowsRestOptions, type SessionSource, SessionUnavailableError, type VersionedRow, asJsonObject, createActiveOrgCookie, createOAuthRefreshFetch, createSessionRetry, decodeActiveOrgSelection, decodeOAuthRefreshClaims, encodeActiveOrgSelection, guardedUpdate, isMissingSessionError, mergeJsonColumn, readAllRows, readAllRowsRest, tryReadAllRows, tryReadAllRowsRest };
package/dist/db.js CHANGED
@@ -475,6 +475,159 @@ function createActiveOrgCookie(args) {
475
475
 
476
476
  // src/db/realtime.ts
477
477
  import { uniqueChannelTopic } from "@ai-matrx/realtime";
478
+
479
+ // src/db/oauth-refresh-transport.ts
480
+ function object(value) {
481
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
482
+ }
483
+ function text(value) {
484
+ return typeof value === "string" && value.length > 0 ? value : null;
485
+ }
486
+ function decodeOAuthRefreshClaims(token) {
487
+ const payload = token.split(".")[1];
488
+ if (!payload) return null;
489
+ try {
490
+ const normalized = payload.replace(/-/g, "+").replace(/_/g, "/");
491
+ const padded = normalized + "=".repeat((4 - normalized.length % 4) % 4);
492
+ const json = typeof atob === "function" ? atob(padded) : Buffer.from(padded, "base64").toString("utf8");
493
+ return object(JSON.parse(json)) ?? null;
494
+ } catch {
495
+ return null;
496
+ }
497
+ }
498
+ function isOrdinaryRefreshRequest(input, init, origin) {
499
+ const request = input instanceof Request ? input : null;
500
+ const url = new URL(request?.url ?? String(input), origin);
501
+ const method = (init?.method ?? request?.method ?? "GET").toUpperCase();
502
+ return method === "POST" && url.origin === origin && url.pathname === "/auth/v1/token" && url.searchParams.get("grant_type") === "refresh_token";
503
+ }
504
+ async function refreshTokenFrom(input, init) {
505
+ let body = init?.body;
506
+ if (body === void 0 && input instanceof Request) body = await input.clone().text();
507
+ if (body instanceof URLSearchParams) return body.get("refresh_token");
508
+ if (typeof body !== "string") return null;
509
+ try {
510
+ return text(object(JSON.parse(body))?.refresh_token);
511
+ } catch {
512
+ return new URLSearchParams(body).get("refresh_token");
513
+ }
514
+ }
515
+ function rewrittenInit(input, init, headers, body) {
516
+ const request = input instanceof Request ? input : null;
517
+ const copied = {
518
+ // init takes precedence, just as native fetch does. Copying Request fields
519
+ // preserves the public Fetch contract after replacing its body.
520
+ method: "POST",
521
+ headers,
522
+ body
523
+ };
524
+ const inherited = {
525
+ credentials: init?.credentials ?? request?.credentials,
526
+ cache: init?.cache ?? request?.cache,
527
+ redirect: init?.redirect ?? request?.redirect,
528
+ referrer: init?.referrer ?? request?.referrer,
529
+ referrerPolicy: init?.referrerPolicy ?? request?.referrerPolicy,
530
+ integrity: init?.integrity ?? request?.integrity,
531
+ keepalive: init?.keepalive ?? request?.keepalive,
532
+ signal: init?.signal ?? request?.signal
533
+ };
534
+ for (const [key, value] of Object.entries(inherited)) {
535
+ if (value !== void 0) Object.assign(copied, { [key]: value });
536
+ }
537
+ return copied;
538
+ }
539
+ async function storedSession(storage, key) {
540
+ const raw = await storage.getItem(key);
541
+ if (!raw) return null;
542
+ try {
543
+ return object(JSON.parse(raw));
544
+ } catch {
545
+ return null;
546
+ }
547
+ }
548
+ function sameSubject(previousAccessToken, refreshedAccessToken, clientId) {
549
+ const previous = decodeOAuthRefreshClaims(previousAccessToken);
550
+ const refreshed = decodeOAuthRefreshClaims(refreshedAccessToken);
551
+ return text(previous?.sub) !== null && previous?.sub === refreshed?.sub && previous?.client_id === clientId && refreshed?.client_id === clientId;
552
+ }
553
+ function retryableRefusal(message) {
554
+ return new Response(JSON.stringify({ error: "temporarily_unavailable", error_description: message }), {
555
+ status: 503,
556
+ headers: { "content-type": "application/json", "retry-after": "1" }
557
+ });
558
+ }
559
+ async function normalizeOAuthResponse(response, session, clientId) {
560
+ if (!response.ok) return response;
561
+ const previousAccessToken = text(session.access_token);
562
+ const previousUser = session.user;
563
+ const previousSubject = previousAccessToken ? text(decodeOAuthRefreshClaims(previousAccessToken)?.sub) : null;
564
+ if (!previousAccessToken || !previousSubject || text(object(previousUser)?.id) !== previousSubject) {
565
+ return retryableRefusal("Stored OAuth identity does not match its credential. Restore the account connection.");
566
+ }
567
+ let payload = null;
568
+ try {
569
+ payload = object(await response.clone().json());
570
+ } catch {
571
+ return retryableRefusal("OAuth refresh returned an invalid response.");
572
+ }
573
+ const refreshedAccessToken = text(payload?.access_token);
574
+ if (!payload || !refreshedAccessToken || !sameSubject(previousAccessToken, refreshedAccessToken, clientId)) {
575
+ return retryableRefusal("OAuth refresh returned a credential for an unexpected identity or client.");
576
+ }
577
+ if (payload.user !== void 0 && text(object(payload.user)?.id) !== previousSubject) {
578
+ return retryableRefusal("OAuth refresh returned a user that does not match its credential.");
579
+ }
580
+ if (payload.user === void 0) payload.user = previousUser;
581
+ if (text(payload.refresh_token) === null) payload.refresh_token = session.refresh_token;
582
+ const headers = new Headers(response.headers);
583
+ headers.set("content-type", "application/json");
584
+ return new Response(JSON.stringify(payload), {
585
+ status: response.status,
586
+ statusText: response.statusText,
587
+ headers
588
+ });
589
+ }
590
+ async function replacementAfterRejectedRefresh(response, storage, storageKey, previous, clientId) {
591
+ if (response.status !== 400 && response.status !== 401) return response;
592
+ const replacement = await storedSession(storage, storageKey);
593
+ const previousRefresh = text(previous.refresh_token);
594
+ const replacementRefresh = text(replacement?.refresh_token);
595
+ const previousAccess = text(previous.access_token);
596
+ const replacementAccess = text(replacement?.access_token);
597
+ if (!replacement || !previousRefresh || !replacementRefresh || replacementRefresh === previousRefresh || !previousAccess || !replacementAccess || !sameSubject(previousAccess, replacementAccess, clientId) || replacement.user === void 0) return response;
598
+ return new Response(JSON.stringify(replacement), {
599
+ status: 200,
600
+ headers: { "content-type": "application/json" }
601
+ });
602
+ }
603
+ function createOAuthRefreshFetch(args) {
604
+ const origin = new URL(args.supabaseUrl).origin;
605
+ const upstream = args.fetch ?? globalThis.fetch;
606
+ const allowed = new Set(args.oauthClientIds);
607
+ return async (input, init) => {
608
+ if (!isOrdinaryRefreshRequest(input, init, origin)) return upstream(input, init);
609
+ const refreshToken = await refreshTokenFrom(input, init);
610
+ const session = await storedSession(args.storage, args.storageKey);
611
+ const storedRefreshToken = text(session?.refresh_token);
612
+ const accessToken = text(session?.access_token);
613
+ const clientId = accessToken ? text(decodeOAuthRefreshClaims(accessToken)?.client_id) : null;
614
+ if (!session || !clientId) {
615
+ return upstream(input, init);
616
+ }
617
+ if (!allowed.has(clientId)) return retryableRefusal("This OAuth client is not allowed to refresh in this host.");
618
+ if (!refreshToken || refreshToken !== storedRefreshToken) {
619
+ return retryableRefusal("OAuth refresh token no longer matches the stored session; resolve the session again.");
620
+ }
621
+ const request = input instanceof Request ? input : null;
622
+ const headers = new Headers(init?.headers ?? request?.headers);
623
+ headers.set("content-type", "application/x-www-form-urlencoded");
624
+ const body = new URLSearchParams({ grant_type: "refresh_token", refresh_token: refreshToken, client_id: clientId });
625
+ const response = await upstream(`${origin}/auth/v1/oauth/token`, rewrittenInit(input, init, headers, body));
626
+ const adopted = await replacementAfterRejectedRefresh(response, args.storage, args.storageKey, session, clientId);
627
+ const responseSession = adopted === response ? session : await storedSession(args.storage, args.storageKey) ?? session;
628
+ return normalizeOAuthResponse(adopted, responseSession, clientId);
629
+ };
630
+ }
478
631
  export {
479
632
  DEFAULT_ACTIVE_ORG_COOKIE_MAX_AGE,
480
633
  DEFAULT_ACTIVE_ORG_COOKIE_NAME,
@@ -484,8 +637,10 @@ export {
484
637
  asJsonObject,
485
638
  createActiveOrgCookie,
486
639
  createAuthCookie,
640
+ createOAuthRefreshFetch,
487
641
  createSessionRetry,
488
642
  decodeActiveOrgSelection,
643
+ decodeOAuthRefreshClaims,
489
644
  encodeActiveOrgSelection,
490
645
  guardedUpdate,
491
646
  isMissingSessionError,