@marktiderman/genesis-supabase 0.0.0-stage → 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mark Tiderman
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,36 @@
1
- # Temporary Holding Version
1
+ # @marktiderman/genesis-supabase
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Supabase for Genesis apps on web and React Native.
4
+
5
+ ```ts
6
+ import { createGenesisSupabase, must } from "@marktiderman/genesis-supabase";
7
+ import { keychainStorage, refreshWhileActive, wipeSessionOnReinstall } from "@marktiderman/genesis-supabase/native";
8
+ import * as SecureStore from "expo-secure-store";
9
+ import AsyncStorage from "@react-native-async-storage/async-storage";
10
+ import { AppState } from "react-native";
11
+
12
+ const session = keychainStorage(SecureStore);
13
+ await wipeSessionOnReinstall({ session, sessionKey: "sb-<project-ref>-auth-token", installMarker: AsyncStorage });
14
+
15
+ export const supabase = createGenesisSupabase<Database>({ url, publishableKey, storage: session, platform: "native" });
16
+ refreshWhileActive(supabase, AppState);
17
+
18
+ const rows = must(await supabase.from("tasks").select("*"));
19
+ ```
20
+
21
+ - **`createGenesisSupabase`**: a client with PKCE sign-in, a persisted session that refreshes itself, and the storage you pass. `platform: "native"` turns off reading a session from the URL; exchange the OAuth redirect's code yourself.
22
+ - **`keychainStorage(SecureStore)`**: session storage in the Keychain or Keystore. Values are split between characters into chunks under the Keychain's byte limit; a write switches to its new chunks only once they are all written, so a write cut short keeps the previous session, and its leftover chunks are swept on the next write; writes to one key run one at a time.
23
+ - **`refreshWhileActive(client, AppState)`**: refreshes the session only while the app is in front.
24
+ - **`wipeSessionOnReinstall`**: the iOS Keychain outlives an uninstall; this removes a leftover session (and its PKCE verifier) on the first launch after an install.
25
+ - **`must(result)`**: the data, or a thrown `SupabaseQueryError` carrying the database's `code`, `details` and `hint`.
26
+
27
+ Nothing here imports React Native or Expo: you pass those modules in.
28
+
29
+ ## Choices, and why
30
+
31
+ - **Keychain chunks rather than encrypted AsyncStorage.** Supabase's Expo guide shows two options: an AES-encrypted value in AsyncStorage with the key in SecureStore ("LargeSecureStore"), or `expo-sqlite/localStorage`. `keychainStorage` keeps the whole session in the Keychain/Keystore instead, split into chunks under the item size limit, with an atomic switch between generations. That means one native dependency (`expo-secure-store`) rather than three, no key to manage, and a session that can't be read off the device's file system. If you prefer LargeSecureStore, pass it as `storage`; everything else here works the same.
32
+ - **The URL polyfill.** Supabase's React Native guides still import `react-native-url-polyfill/auto` before creating the client. Keep it until your app has tested PKCE sign-in end to end without it: React Native's built-in `URL` has improved, but the docs don't yet say it's enough.
33
+ - **`refreshWhileActive` on native only.** Supabase gates the AppState listener to native platforms; call it from native code (not on web, where the browser already refreshes in the background).
34
+
35
+
36
+ Part of the [Genesis framework](https://github.com/marktiderman/genesis).
@@ -0,0 +1,78 @@
1
+ import { SupabaseClientOptions, SupabaseClient } from '@supabase/supabase-js';
2
+
3
+ /**
4
+ * Supabase for Genesis apps. Platform-neutral: web, React Native and the
5
+ * server import it. Native-only helpers (Keychain storage, foreground
6
+ * refresh, reinstall wipe) are on `@marktiderman/genesis-supabase/native`.
7
+ */
8
+
9
+ /** The async key-value store Supabase auth keeps its session in. */
10
+ interface SessionStorage {
11
+ getItem(key: string): Promise<string | null>;
12
+ setItem(key: string, value: string): Promise<void>;
13
+ removeItem(key: string): Promise<void>;
14
+ }
15
+ /** @stability Beta */
16
+ interface GenesisSupabaseOptions {
17
+ url: string;
18
+ /** The publishable (anon) key. Never the secret key in an app. */
19
+ publishableKey: string;
20
+ /** Where the session lives. Native: `keychainStorage(SecureStore)`. Web: leave out for supabase-js's own default. */
21
+ storage?: SessionStorage;
22
+ /** `"native"` sets the React Native defaults (no session from the URL). Default `"web"`. */
23
+ platform?: "web" | "native";
24
+ /**
25
+ * Read a session from the URL after an OAuth redirect. Default: `true` on
26
+ * web, `false` on native, where the app hands the redirect URL to
27
+ * `exchangeCodeForSession` itself.
28
+ */
29
+ detectSessionInUrl?: boolean;
30
+ /** Anything else for supabase-js, merged last. */
31
+ options?: SupabaseClientOptions<"public">;
32
+ }
33
+ /**
34
+ * A Supabase client with Genesis's defaults: PKCE sign-in (the code flow, not
35
+ * tokens in a redirect URL), a persisted session that refreshes itself, and
36
+ * the session storage you pass.
37
+ *
38
+ * @stability Beta
39
+ */
40
+ declare function createGenesisSupabase<Database = unknown>({ url, publishableKey, storage, platform, detectSessionInUrl, options, }: GenesisSupabaseOptions): SupabaseClient<Database>;
41
+ /** What a Supabase or PostgREST error carries. */
42
+ interface PostgrestErrorLike {
43
+ message: string;
44
+ code?: string;
45
+ details?: string | null;
46
+ hint?: string | null;
47
+ }
48
+ /**
49
+ * A failed query, with the database's `code` (`23505` unique violation,
50
+ * `42501` permission denied, `PGRST116` no rows…), `details` and `hint`, so a
51
+ * caller can tell an RLS refusal from a conflict. The original error is the
52
+ * `cause`.
53
+ *
54
+ * @stability Beta
55
+ */
56
+ declare class SupabaseQueryError extends Error {
57
+ name: string;
58
+ readonly code?: string;
59
+ readonly details?: string | null;
60
+ readonly hint?: string | null;
61
+ constructor(error: PostgrestErrorLike);
62
+ }
63
+ /**
64
+ * The data, or a thrown {@link SupabaseQueryError}: for query results where an
65
+ * error should fail the caller (a TanStack Query `queryFn`). The return type
66
+ * keeps `null` when the query can return no row (`.maybeSingle()`).
67
+ *
68
+ * @stability Beta
69
+ */
70
+ declare function must<T>(result: {
71
+ data: T;
72
+ error: null;
73
+ } | {
74
+ data: null;
75
+ error: PostgrestErrorLike;
76
+ }): T;
77
+
78
+ export { type GenesisSupabaseOptions, type PostgrestErrorLike, type SessionStorage, SupabaseQueryError, createGenesisSupabase, must };
package/dist/index.js ADDED
@@ -0,0 +1,44 @@
1
+ import { createClient } from '@supabase/supabase-js';
2
+
3
+ // src/index.ts
4
+ function createGenesisSupabase({
5
+ url,
6
+ publishableKey,
7
+ storage,
8
+ platform = "web",
9
+ detectSessionInUrl = platform === "web",
10
+ options = {}
11
+ }) {
12
+ const client = createClient(url, publishableKey, {
13
+ ...options,
14
+ auth: {
15
+ flowType: "pkce",
16
+ persistSession: true,
17
+ autoRefreshToken: true,
18
+ detectSessionInUrl,
19
+ ...storage ? { storage } : {},
20
+ ...options.auth
21
+ }
22
+ });
23
+ return client;
24
+ }
25
+ var SupabaseQueryError = class extends Error {
26
+ name = "SupabaseQueryError";
27
+ code;
28
+ details;
29
+ hint;
30
+ constructor(error) {
31
+ super(error.message, { cause: error });
32
+ this.code = error.code;
33
+ this.details = error.details;
34
+ this.hint = error.hint;
35
+ }
36
+ };
37
+ function must(result) {
38
+ if (result.error) throw new SupabaseQueryError(result.error);
39
+ return result.data;
40
+ }
41
+
42
+ export { SupabaseQueryError, createGenesisSupabase, must };
43
+ //# sourceMappingURL=index.js.map
44
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":";;;AAwCO,SAAS,qBAAA,CAA0C;AAAA,EACxD,GAAA;AAAA,EACA,cAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA,GAAW,KAAA;AAAA,EACX,qBAAqB,QAAA,KAAa,KAAA;AAAA,EAClC,UAAU;AACZ,CAAA,EAAqD;AAGnD,EAAA,MAAM,MAAA,GAAS,YAAA,CAAa,GAAA,EAAK,cAAA,EAAgB;AAAA,IAC/C,GAAG,OAAA;AAAA,IACH,IAAA,EAAM;AAAA,MACJ,QAAA,EAAU,MAAA;AAAA,MACV,cAAA,EAAgB,IAAA;AAAA,MAChB,gBAAA,EAAkB,IAAA;AAAA,MAClB,kBAAA;AAAA,MACA,GAAI,OAAA,GAAU,EAAE,OAAA,KAAY,EAAC;AAAA,MAC7B,GAAG,OAAA,CAAQ;AAAA;AACb,GACD,CAAA;AACD,EAAA,OAAO,MAAA;AACT;AAkBO,IAAM,kBAAA,GAAN,cAAiC,KAAA,CAAM;AAAA,EACnC,IAAA,GAAO,oBAAA;AAAA,EACP,IAAA;AAAA,EACA,OAAA;AAAA,EACA,IAAA;AAAA,EACT,YAAY,KAAA,EAA2B;AACrC,IAAA,KAAA,CAAM,KAAA,CAAM,OAAA,EAAS,EAAE,KAAA,EAAO,OAAO,CAAA;AACrC,IAAA,IAAA,CAAK,OAAO,KAAA,CAAM,IAAA;AAClB,IAAA,IAAA,CAAK,UAAU,KAAA,CAAM,OAAA;AACrB,IAAA,IAAA,CAAK,OAAO,KAAA,CAAM,IAAA;AAAA,EACpB;AACF;AASO,SAAS,KAAQ,MAAA,EAAiF;AACvG,EAAA,IAAI,OAAO,KAAA,EAAO,MAAM,IAAI,kBAAA,CAAmB,OAAO,KAAK,CAAA;AAC3D,EAAA,OAAO,MAAA,CAAO,IAAA;AAChB","file":"index.js","sourcesContent":["/**\n * Supabase for Genesis apps. Platform-neutral: web, React Native and the\n * server import it. Native-only helpers (Keychain storage, foreground\n * refresh, reinstall wipe) are on `@marktiderman/genesis-supabase/native`.\n */\nimport { createClient, type SupabaseClient, type SupabaseClientOptions } from \"@supabase/supabase-js\";\n\n/** The async key-value store Supabase auth keeps its session in. */\nexport interface SessionStorage {\n getItem(key: string): Promise<string | null>;\n setItem(key: string, value: string): Promise<void>;\n removeItem(key: string): Promise<void>;\n}\n\n/** @stability Beta */\nexport interface GenesisSupabaseOptions {\n url: string;\n /** The publishable (anon) key. Never the secret key in an app. */\n publishableKey: string;\n /** Where the session lives. Native: `keychainStorage(SecureStore)`. Web: leave out for supabase-js's own default. */\n storage?: SessionStorage;\n /** `\"native\"` sets the React Native defaults (no session from the URL). Default `\"web\"`. */\n platform?: \"web\" | \"native\";\n /**\n * Read a session from the URL after an OAuth redirect. Default: `true` on\n * web, `false` on native, where the app hands the redirect URL to\n * `exchangeCodeForSession` itself.\n */\n detectSessionInUrl?: boolean;\n /** Anything else for supabase-js, merged last. */\n options?: SupabaseClientOptions<\"public\">;\n}\n\n/**\n * A Supabase client with Genesis's defaults: PKCE sign-in (the code flow, not\n * tokens in a redirect URL), a persisted session that refreshes itself, and\n * the session storage you pass.\n *\n * @stability Beta\n */\nexport function createGenesisSupabase<Database = unknown>({\n url,\n publishableKey,\n storage,\n platform = \"web\",\n detectSessionInUrl = platform === \"web\",\n options = {},\n}: GenesisSupabaseOptions): SupabaseClient<Database> {\n // supabase-js's generics over the schema don't survive being forwarded, so the\n // options are built untyped and the client is typed by the caller's Database.\n const client = createClient(url, publishableKey, {\n ...options,\n auth: {\n flowType: \"pkce\",\n persistSession: true,\n autoRefreshToken: true,\n detectSessionInUrl,\n ...(storage ? { storage } : {}),\n ...options.auth,\n },\n });\n return client as unknown as SupabaseClient<Database>;\n}\n\n/** What a Supabase or PostgREST error carries. */\nexport interface PostgrestErrorLike {\n message: string;\n code?: string;\n details?: string | null;\n hint?: string | null;\n}\n\n/**\n * A failed query, with the database's `code` (`23505` unique violation,\n * `42501` permission denied, `PGRST116` no rows…), `details` and `hint`, so a\n * caller can tell an RLS refusal from a conflict. The original error is the\n * `cause`.\n *\n * @stability Beta\n */\nexport class SupabaseQueryError extends Error {\n override name = \"SupabaseQueryError\";\n readonly code?: string;\n readonly details?: string | null;\n readonly hint?: string | null;\n constructor(error: PostgrestErrorLike) {\n super(error.message, { cause: error });\n this.code = error.code;\n this.details = error.details;\n this.hint = error.hint;\n }\n}\n\n/**\n * The data, or a thrown {@link SupabaseQueryError}: for query results where an\n * error should fail the caller (a TanStack Query `queryFn`). The return type\n * keeps `null` when the query can return no row (`.maybeSingle()`).\n *\n * @stability Beta\n */\nexport function must<T>(result: { data: T; error: null } | { data: null; error: PostgrestErrorLike }): T {\n if (result.error) throw new SupabaseQueryError(result.error);\n return result.data as T;\n}\n"]}
@@ -0,0 +1,84 @@
1
+ import { SessionStorage } from './index.js';
2
+ import '@supabase/supabase-js';
3
+
4
+ /**
5
+ * React Native helpers for a Supabase session. Nothing here imports React
6
+ * Native or Expo: pass in `expo-secure-store`, `AppState` and a plain store,
7
+ * so the package stays light and the helpers are easy to test.
8
+ */
9
+
10
+ /** The parts of `expo-secure-store` the Keychain storage uses. */
11
+ interface SecureStoreLike {
12
+ getItemAsync(key: string, options?: Record<string, unknown>): Promise<string | null>;
13
+ setItemAsync(key: string, value: string, options?: Record<string, unknown>): Promise<void>;
14
+ deleteItemAsync(key: string, options?: Record<string, unknown>): Promise<void>;
15
+ /** `SecureStore.AFTER_FIRST_UNLOCK`, when available. */
16
+ AFTER_FIRST_UNLOCK?: unknown;
17
+ }
18
+ /** @stability Beta */
19
+ interface KeychainStorageOptions {
20
+ /** UTF-8 bytes per Keychain item. A Keychain item should stay under about 2 KB; a Supabase session is larger. Default 1800. */
21
+ chunkSize?: number;
22
+ /** Extra options for every SecureStore call. Default: readable after first unlock, so a background refresh works. */
23
+ secureStoreOptions?: Record<string, unknown>;
24
+ }
25
+ /**
26
+ * Session storage in the Keychain (iOS) or Keystore (Android).
27
+ *
28
+ * A value is split into numbered chunks. Each write goes to a new generation of
29
+ * chunks, and one manifest item (`<key>.n` = `generation:count`) switches to it
30
+ * only once every chunk is written, so a write cut short leaves the previous
31
+ * session whole. Chunks split between characters (never inside one) and
32
+ * stay under `chunkSize` UTF-8 bytes. A pending marker (`<key>.p`) names a
33
+ * generation while it is being written, so chunks left by a write that was
34
+ * cut short or crashed are swept on the next write or remove. Writes to the
35
+ * same key run one at a time, so two token refreshes can't interleave.
36
+ *
37
+ * @stability Beta
38
+ */
39
+ declare function keychainStorage(secureStore: SecureStoreLike, options?: KeychainStorageOptions): SessionStorage;
40
+ /** The parts of a Supabase client's auth the refresh helper uses. */
41
+ interface AutoRefreshAuth {
42
+ auth: {
43
+ startAutoRefresh(): Promise<void> | void;
44
+ stopAutoRefresh(): Promise<void> | void;
45
+ };
46
+ }
47
+ /** The parts of React Native's `AppState` the refresh helper uses. */
48
+ interface AppStateLike {
49
+ currentState?: string | null;
50
+ addEventListener(type: "change", listener: (state: string) => void): {
51
+ remove(): void;
52
+ };
53
+ }
54
+ /**
55
+ * Refresh the session only while the app is in front, as Supabase recommends
56
+ * for React Native. Returns the unsubscribe.
57
+ *
58
+ * @stability Beta
59
+ */
60
+ declare function refreshWhileActive(client: AutoRefreshAuth, appState: AppStateLike): () => void;
61
+ /** @stability Beta */
62
+ interface WipeOnReinstallOptions {
63
+ /** The session storage (usually `keychainStorage`). */
64
+ session: SessionStorage;
65
+ /** The session's storage key. Supabase's default is `sb-<project ref>-auth-token`. */
66
+ sessionKey: string;
67
+ /** A store that an uninstall deletes, e.g. AsyncStorage. The Keychain survives an uninstall; this is how we notice one. */
68
+ installMarker: SessionStorage;
69
+ /** The marker's key. Default `genesis.installed`. */
70
+ markerKey?: string;
71
+ }
72
+ /**
73
+ * On iOS the Keychain outlives an uninstall, so a reinstalled app would sign
74
+ * the last person back in. Call this before creating the client: on the first
75
+ * launch after an install it removes any session left in the Keychain, then
76
+ * sets a marker so later launches keep the session. Returns whether it wiped.
77
+ *
78
+ * @stability Beta
79
+ */
80
+ declare function wipeSessionOnReinstall({ session, sessionKey, installMarker, markerKey }: WipeOnReinstallOptions): Promise<boolean>;
81
+ /** Split text into pieces of at most `maxBytes` UTF-8 bytes, never inside a character. */
82
+ declare function splitUtf8(text: string, maxBytes: number): string[];
83
+
84
+ export { type AppStateLike, type AutoRefreshAuth, type KeychainStorageOptions, type SecureStoreLike, type WipeOnReinstallOptions, keychainStorage, refreshWhileActive, splitUtf8, wipeSessionOnReinstall };
package/dist/native.js ADDED
@@ -0,0 +1,111 @@
1
+ // src/native.ts
2
+ function keychainStorage(secureStore, options = {}) {
3
+ const size = options.chunkSize ?? 1800;
4
+ const opts = options.secureStoreOptions ?? (secureStore.AFTER_FIRST_UNLOCK !== void 0 ? { keychainAccessible: secureStore.AFTER_FIRST_UNLOCK } : {});
5
+ const chunkKey = (key, gen, i) => `${key}.${gen}.${i}`;
6
+ let counter = 0;
7
+ const queues = /* @__PURE__ */ new Map();
8
+ const serial = (key, task) => {
9
+ const run = (queues.get(key) ?? Promise.resolve()).catch(() => void 0).then(task);
10
+ queues.set(key, run);
11
+ return run;
12
+ };
13
+ const parse = (raw) => {
14
+ const [gen, count] = (raw ?? "").split(":");
15
+ return gen && Number(count) > 0 ? { gen, count: Number(count) } : null;
16
+ };
17
+ const manifest = async (key) => parse(await secureStore.getItemAsync(`${key}.n`, opts));
18
+ const pending = async (key) => parse(await secureStore.getItemAsync(`${key}.p`, opts));
19
+ const drop = (key, gen, count) => Promise.all(Array.from({ length: count }, (_, i) => secureStore.deleteItemAsync(chunkKey(key, gen, i), opts)));
20
+ const sweepPending = async (key, current) => {
21
+ const p = await pending(key);
22
+ if (!p) return;
23
+ if (p.gen !== current) await drop(key, p.gen, p.count);
24
+ await secureStore.deleteItemAsync(`${key}.p`, opts);
25
+ };
26
+ return {
27
+ getItem(key) {
28
+ return serial(key, async () => {
29
+ const m = await manifest(key);
30
+ if (!m) return null;
31
+ const parts = await Promise.all(Array.from({ length: m.count }, (_, i) => secureStore.getItemAsync(chunkKey(key, m.gen, i), opts)));
32
+ return parts.some((p) => p === null) ? null : parts.join("");
33
+ });
34
+ },
35
+ setItem(key, value) {
36
+ return serial(key, async () => {
37
+ const before = await manifest(key);
38
+ await sweepPending(key, before?.gen);
39
+ const gen = `${Date.now().toString(36)}${(counter++).toString(36)}`;
40
+ const parts = splitUtf8(value, size);
41
+ await secureStore.setItemAsync(`${key}.p`, `${gen}:${parts.length}`, opts);
42
+ const written = await Promise.allSettled(parts.map((p, i) => secureStore.setItemAsync(chunkKey(key, gen, i), p, opts)));
43
+ const failed = written.find((r) => r.status === "rejected");
44
+ if (failed) throw failed.reason;
45
+ await secureStore.setItemAsync(`${key}.n`, `${gen}:${parts.length}`, opts);
46
+ await secureStore.deleteItemAsync(`${key}.p`, opts);
47
+ if (before) await drop(key, before.gen, before.count);
48
+ });
49
+ },
50
+ removeItem(key) {
51
+ return serial(key, async () => {
52
+ const m = await manifest(key);
53
+ await sweepPending(key, m?.gen);
54
+ if (m) await drop(key, m.gen, m.count);
55
+ await secureStore.deleteItemAsync(`${key}.n`, opts);
56
+ });
57
+ }
58
+ };
59
+ }
60
+ function refreshWhileActive(client, appState) {
61
+ const apply = (state) => {
62
+ if (state === "active") void client.auth.startAutoRefresh();
63
+ else void client.auth.stopAutoRefresh();
64
+ };
65
+ apply(appState.currentState ?? "active");
66
+ const sub = appState.addEventListener("change", apply);
67
+ return () => {
68
+ sub.remove();
69
+ void client.auth.stopAutoRefresh();
70
+ };
71
+ }
72
+ async function wipeSessionOnReinstall({ session, sessionKey, installMarker, markerKey = "genesis.installed" }) {
73
+ if (await installMarker.getItem(markerKey) !== null) return false;
74
+ const indexKey = `${sessionKey}-flows-code-verifier`;
75
+ for (const flowId of parseFlowIndex(await session.getItem(indexKey))) await session.removeItem(`${sessionKey}-flow-${flowId}-code-verifier`);
76
+ for (const key of [sessionKey, `${sessionKey}-code-verifier`, indexKey, `${sessionKey}-user`]) await session.removeItem(key);
77
+ await installMarker.setItem(markerKey, "1");
78
+ return true;
79
+ }
80
+ var utf8Length = (codePoint) => codePoint < 128 ? 1 : codePoint < 2048 ? 2 : codePoint < 65536 ? 3 : 4;
81
+ function splitUtf8(text, maxBytes) {
82
+ if (text === "") return [""];
83
+ const parts = [];
84
+ let current = "";
85
+ let bytes = 0;
86
+ for (const ch of text) {
87
+ const n = utf8Length(ch.codePointAt(0));
88
+ if (bytes + n > maxBytes && current) {
89
+ parts.push(current);
90
+ current = "";
91
+ bytes = 0;
92
+ }
93
+ current += ch;
94
+ bytes += n;
95
+ }
96
+ parts.push(current);
97
+ return parts;
98
+ }
99
+ function parseFlowIndex(raw) {
100
+ if (!raw) return [];
101
+ try {
102
+ const ids = JSON.parse(raw);
103
+ return Array.isArray(ids) ? ids.filter((id) => typeof id === "string" && /^[\w-]{1,128}$/.test(id)) : [];
104
+ } catch {
105
+ return [];
106
+ }
107
+ }
108
+
109
+ export { keychainStorage, refreshWhileActive, splitUtf8, wipeSessionOnReinstall };
110
+ //# sourceMappingURL=native.js.map
111
+ //# sourceMappingURL=native.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/native.ts"],"names":[],"mappings":"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","file":"native.js","sourcesContent":["/**\n * React Native helpers for a Supabase session. Nothing here imports React\n * Native or Expo: pass in `expo-secure-store`, `AppState` and a plain store,\n * so the package stays light and the helpers are easy to test.\n */\nimport type { SessionStorage } from \"./index\";\n\n/** The parts of `expo-secure-store` the Keychain storage uses. */\nexport interface SecureStoreLike {\n getItemAsync(key: string, options?: Record<string, unknown>): Promise<string | null>;\n setItemAsync(key: string, value: string, options?: Record<string, unknown>): Promise<void>;\n deleteItemAsync(key: string, options?: Record<string, unknown>): Promise<void>;\n /** `SecureStore.AFTER_FIRST_UNLOCK`, when available. */\n AFTER_FIRST_UNLOCK?: unknown;\n}\n\n/** @stability Beta */\nexport interface KeychainStorageOptions {\n /** UTF-8 bytes per Keychain item. A Keychain item should stay under about 2 KB; a Supabase session is larger. Default 1800. */\n chunkSize?: number;\n /** Extra options for every SecureStore call. Default: readable after first unlock, so a background refresh works. */\n secureStoreOptions?: Record<string, unknown>;\n}\n\n/**\n * Session storage in the Keychain (iOS) or Keystore (Android).\n *\n * A value is split into numbered chunks. Each write goes to a new generation of\n * chunks, and one manifest item (`<key>.n` = `generation:count`) switches to it\n * only once every chunk is written, so a write cut short leaves the previous\n * session whole. Chunks split between characters (never inside one) and\n * stay under `chunkSize` UTF-8 bytes. A pending marker (`<key>.p`) names a\n * generation while it is being written, so chunks left by a write that was\n * cut short or crashed are swept on the next write or remove. Writes to the\n * same key run one at a time, so two token refreshes can't interleave.\n *\n * @stability Beta\n */\nexport function keychainStorage(secureStore: SecureStoreLike, options: KeychainStorageOptions = {}): SessionStorage {\n const size = options.chunkSize ?? 1800;\n const opts = options.secureStoreOptions ?? (secureStore.AFTER_FIRST_UNLOCK !== undefined ? { keychainAccessible: secureStore.AFTER_FIRST_UNLOCK } : {});\n const chunkKey = (key: string, gen: string, i: number) => `${key}.${gen}.${i}`;\n let counter = 0;\n const queues = new Map<string, Promise<unknown>>();\n\n /** Run `task` after every earlier write to `key` has settled. */\n const serial = <T>(key: string, task: () => Promise<T>): Promise<T> => {\n const run = (queues.get(key) ?? Promise.resolve()).catch(() => undefined).then(task);\n queues.set(key, run);\n return run;\n };\n\n const parse = (raw: string | null) => {\n const [gen, count] = (raw ?? \"\").split(\":\");\n return gen && Number(count) > 0 ? { gen, count: Number(count) } : null;\n };\n const manifest = async (key: string) => parse(await secureStore.getItemAsync(`${key}.n`, opts));\n const pending = async (key: string) => parse(await secureStore.getItemAsync(`${key}.p`, opts));\n const drop = (key: string, gen: string, count: number) =>\n Promise.all(Array.from({ length: count }, (_, i) => secureStore.deleteItemAsync(chunkKey(key, gen, i), opts)));\n\n /** Remove chunks of a write that never completed (unless they are the current generation). */\n const sweepPending = async (key: string, current: string | undefined) => {\n const p = await pending(key);\n if (!p) return;\n if (p.gen !== current) await drop(key, p.gen, p.count);\n await secureStore.deleteItemAsync(`${key}.p`, opts);\n };\n\n return {\n getItem(key) {\n // Reads queue with writes, so a write can't swap the manifest and drop\n // the old chunks halfway through a read.\n return serial(key, async () => {\n const m = await manifest(key);\n if (!m) return null;\n const parts = await Promise.all(Array.from({ length: m.count }, (_, i) => secureStore.getItemAsync(chunkKey(key, m.gen, i), opts)));\n return parts.some((p) => p === null) ? null : parts.join(\"\");\n });\n },\n setItem(key, value) {\n return serial(key, async () => {\n const before = await manifest(key);\n await sweepPending(key, before?.gen);\n const gen = `${Date.now().toString(36)}${(counter++).toString(36)}`;\n const parts = splitUtf8(value, size);\n await secureStore.setItemAsync(`${key}.p`, `${gen}:${parts.length}`, opts);\n // Let every chunk write settle before failing, so a late write can't land\n // after the next operation has swept this generation.\n const written = await Promise.allSettled(parts.map((p, i) => secureStore.setItemAsync(chunkKey(key, gen, i), p, opts)));\n const failed = written.find((r): r is PromiseRejectedResult => r.status === \"rejected\");\n if (failed) throw failed.reason;\n await secureStore.setItemAsync(`${key}.n`, `${gen}:${parts.length}`, opts);\n await secureStore.deleteItemAsync(`${key}.p`, opts);\n if (before) await drop(key, before.gen, before.count);\n });\n },\n removeItem(key) {\n return serial(key, async () => {\n const m = await manifest(key);\n await sweepPending(key, m?.gen);\n // Chunks first: the manifest is what finds them, so it goes last.\n if (m) await drop(key, m.gen, m.count);\n await secureStore.deleteItemAsync(`${key}.n`, opts);\n });\n },\n };\n}\n\n/** The parts of a Supabase client's auth the refresh helper uses. */\nexport interface AutoRefreshAuth {\n auth: { startAutoRefresh(): Promise<void> | void; stopAutoRefresh(): Promise<void> | void };\n}\n\n/** The parts of React Native's `AppState` the refresh helper uses. */\nexport interface AppStateLike {\n currentState?: string | null;\n addEventListener(type: \"change\", listener: (state: string) => void): { remove(): void };\n}\n\n/**\n * Refresh the session only while the app is in front, as Supabase recommends\n * for React Native. Returns the unsubscribe.\n *\n * @stability Beta\n */\nexport function refreshWhileActive(client: AutoRefreshAuth, appState: AppStateLike): () => void {\n const apply = (state: string | null | undefined) => {\n if (state === \"active\") void client.auth.startAutoRefresh();\n else void client.auth.stopAutoRefresh();\n };\n apply(appState.currentState ?? \"active\");\n const sub = appState.addEventListener(\"change\", apply);\n return () => {\n sub.remove();\n void client.auth.stopAutoRefresh();\n };\n}\n\n/** @stability Beta */\nexport interface WipeOnReinstallOptions {\n /** The session storage (usually `keychainStorage`). */\n session: SessionStorage;\n /** The session's storage key. Supabase's default is `sb-<project ref>-auth-token`. */\n sessionKey: string;\n /** A store that an uninstall deletes, e.g. AsyncStorage. The Keychain survives an uninstall; this is how we notice one. */\n installMarker: SessionStorage;\n /** The marker's key. Default `genesis.installed`. */\n markerKey?: string;\n}\n\n/**\n * On iOS the Keychain outlives an uninstall, so a reinstalled app would sign\n * the last person back in. Call this before creating the client: on the first\n * launch after an install it removes any session left in the Keychain, then\n * sets a marker so later launches keep the session. Returns whether it wiped.\n *\n * @stability Beta\n */\nexport async function wipeSessionOnReinstall({ session, sessionKey, installMarker, markerKey = \"genesis.installed\" }: WipeOnReinstallOptions): Promise<boolean> {\n if ((await installMarker.getItem(markerKey)) !== null) return false;\n // The session, plus what supabase-js keeps beside it: the PKCE verifier (the\n // single legacy slot, each indexed flow's slot and the index) and, with\n // userStorage, the user.\n const indexKey = `${sessionKey}-flows-code-verifier`;\n for (const flowId of parseFlowIndex(await session.getItem(indexKey))) await session.removeItem(`${sessionKey}-flow-${flowId}-code-verifier`);\n for (const key of [sessionKey, `${sessionKey}-code-verifier`, indexKey, `${sessionKey}-user`]) await session.removeItem(key);\n await installMarker.setItem(markerKey, \"1\");\n return true;\n}\n\n/** UTF-8 byte length of one code point. */\nconst utf8Length = (codePoint: number) => (codePoint < 0x80 ? 1 : codePoint < 0x800 ? 2 : codePoint < 0x10000 ? 3 : 4);\n\n/** Split text into pieces of at most `maxBytes` UTF-8 bytes, never inside a character. */\nexport function splitUtf8(text: string, maxBytes: number): string[] {\n if (text === \"\") return [\"\"];\n const parts: string[] = [];\n let current = \"\";\n let bytes = 0;\n for (const ch of text) {\n const n = utf8Length(ch.codePointAt(0)!);\n if (bytes + n > maxBytes && current) {\n parts.push(current);\n current = \"\";\n bytes = 0;\n }\n current += ch;\n bytes += n;\n }\n parts.push(current);\n return parts;\n}\n\n/** supabase-js's PKCE flow index: a JSON array of flow ids. Anything else is ignored. */\nfunction parseFlowIndex(raw: string | null): string[] {\n if (!raw) return [];\n try {\n const ids: unknown = JSON.parse(raw);\n return Array.isArray(ids) ? ids.filter((id): id is string => typeof id === \"string\" && /^[\\w-]{1,128}$/.test(id)) : [];\n } catch {\n return [];\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,51 @@
1
1
  {
2
2
  "name": "@marktiderman/genesis-supabase",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "description": "Supabase for Genesis apps on web and React Native: a client with PKCE and safe defaults, Keychain session storage that survives interrupted writes, refresh only while the app is in front, and a result helper. Part of the Genesis framework.",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/marktiderman/genesis.git",
11
+ "directory": "packages/supabase"
12
+ },
13
+ "license": "MIT",
14
+ "publishConfig": {
15
+ "access": "public",
16
+ "provenance": false
17
+ },
18
+ "main": "./dist/index.js",
19
+ "module": "./dist/index.js",
20
+ "types": "./dist/index.d.ts",
21
+ "exports": {
22
+ "./package.json": "./package.json",
23
+ ".": {
24
+ "types": "./dist/index.d.ts",
25
+ "import": "./dist/index.js"
26
+ },
27
+ "./native": {
28
+ "types": "./dist/native.d.ts",
29
+ "import": "./dist/native.js"
30
+ }
31
+ },
32
+ "files": [
33
+ "dist",
34
+ "README.md"
35
+ ],
36
+ "peerDependencies": {
37
+ "@supabase/supabase-js": "^2.118.0"
38
+ },
39
+ "devDependencies": {
40
+ "@supabase/supabase-js": "^2.118.0",
41
+ "tsup": "^8.3.0",
42
+ "typescript": "^5.9.3",
43
+ "vitest": "^4.1.12"
44
+ },
45
+ "scripts": {
46
+ "build": "tsup && node ../../scripts/check-exports-resolve.mjs",
47
+ "clean": "rm -rf dist",
48
+ "test": "vitest run",
49
+ "typecheck": "tsc --noEmit"
50
+ }
6
51
  }