rei-kit 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"app-error-DF9cijE0.js","names":[],"sources":["../src/utils/app-error.ts"],"sourcesContent":["/** Error categories the UI can branch on, independent of Postgres or Supabase. */\nexport type AppErrorKind = 'conflict' | 'not-found' | 'network' | 'unknown'\n\n/**\n * A normalised error thrown by the data layer.\n *\n * Extends `Error` so it can be thrown, caught and logged like any other error,\n * and keeps the original in `cause` for debugging.\n *\n * @example\n * ```ts\n * try {\n * await createHabit(input)\n * } catch (e) {\n * const err = toAppError(e)\n * if (err.kind === 'conflict') return // already exists, not a real failure\n * showToast(err.message)\n * }\n * ```\n */\nexport class AppError extends Error {\n readonly kind: AppErrorKind\n\n constructor(kind: AppErrorKind, message: string, options?: ErrorOptions) {\n super(message, options)\n this.name = 'AppError'\n this.kind = kind\n }\n}\n\n/**\n * Normalises anything thrown by Supabase into an {@link AppError}.\n *\n * Idempotent: an `AppError` is returned as-is, so wrapping twice is safe.\n *\n * @param error - Anything caught from the data layer.\n * @returns An `AppError` with a user-facing message and a `kind` to branch on.\n */\n/**\n * Turns a backend-specific error into an `AppError`, or returns `null` to let\n * the next mapper try.\n */\nexport type ErrorMapper = (error: unknown) => AppError | null\n\nconst mappers: ErrorMapper[] = []\n\n/**\n * Teaches `toAppError` about a backend it does not import.\n *\n * The core has no database dependency; `rei-kit/supabase` registers the\n * Postgrest mapping when it is imported, so an app that never touches Supabase\n * never downloads the code that knows about it.\n *\n * @example\n * ```ts\n * registerErrorMapper((error) =>\n * isPrismaConflict(error) ? new AppError('conflict', 'Already exists.') : null,\n * )\n * ```\n */\nexport function registerErrorMapper(mapper: ErrorMapper): void {\n mappers.push(mapper)\n}\n\n/**\n * Normalises anything thrown by the data layer.\n *\n * @param error - Whatever was caught.\n * @returns An `AppError`, never a rethrow.\n */\nexport function toAppError(error: unknown): AppError {\n if (error instanceof AppError) return error\n\n for (const map of mappers) {\n const mapped = map(error)\n if (mapped) return mapped\n }\n\n // A failed fetch surfaces as a TypeError, which is the only reliable signal\n // the browser gives that the request never left.\n if (error instanceof TypeError) {\n return new AppError('network', 'Could not reach the server.', { cause: error })\n }\n\n return new AppError('unknown', 'Something went wrong.', { cause: error })\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAoBA,IAAa,WAAb,cAA8B,MAAM;CAClC;CAEA,YAAY,MAAoB,SAAiB,SAAwB;EACvE,MAAM,SAAS,OAAO;EACtB,KAAK,OAAO;EACZ,KAAK,OAAO;CACd;AACF;AAgBA,IAAM,UAAyB,CAAC;;;;;;;;;;;;;;;AAgBhC,SAAgB,oBAAoB,QAA2B;CAC7D,QAAQ,KAAK,MAAM;AACrB;;;;;;;AAQA,SAAgB,WAAW,OAA0B;CACnD,IAAI,iBAAiB,UAAU,OAAO;CAEtC,KAAK,MAAM,OAAO,SAAS;EACzB,MAAM,SAAS,IAAI,KAAK;EACxB,IAAI,QAAQ,OAAO;CACrB;CAIA,IAAI,iBAAiB,WACnB,OAAO,IAAI,SAAS,WAAW,+BAA+B,EAAE,OAAO,MAAM,CAAC;CAGhF,OAAO,IAAI,SAAS,WAAW,yBAAyB,EAAE,OAAO,MAAM,CAAC;AAC1E"}
1
+ {"version":3,"file":"app-error-DF9cijE0.js","names":[],"sources":["../src/utils/app-error.ts"],"sourcesContent":["/**\n * Error categories the UI can branch on, independent of Postgres or Supabase.\n *\n * `denied` is the one that is not a fault: the request was understood, well\n * formed and refused. A screen that treats it as a failure tells the reader\n * something is broken and sends them to support, when what they need is to\n * sign in again or to be told the thing is not theirs.\n */\nexport type AppErrorKind = 'conflict' | 'not-found' | 'network' | 'denied' | 'unknown'\n\n/**\n * A normalised error thrown by the data layer.\n *\n * Extends `Error` so it can be thrown, caught and logged like any other error,\n * and keeps the original in `cause` for debugging.\n *\n * @example\n * ```ts\n * try {\n * await createHabit(input)\n * } catch (e) {\n * const err = toAppError(e)\n * if (err.kind === 'conflict') return // already exists, not a real failure\n * showToast(err.message)\n * }\n * ```\n */\nexport class AppError extends Error {\n readonly kind: AppErrorKind\n\n constructor(kind: AppErrorKind, message: string, options?: ErrorOptions) {\n super(message, options)\n this.name = 'AppError'\n this.kind = kind\n }\n}\n\n/**\n * Normalises anything thrown by Supabase into an {@link AppError}.\n *\n * Idempotent: an `AppError` is returned as-is, so wrapping twice is safe.\n *\n * @param error - Anything caught from the data layer.\n * @returns An `AppError` with a user-facing message and a `kind` to branch on.\n */\n/**\n * Turns a backend-specific error into an `AppError`, or returns `null` to let\n * the next mapper try.\n */\nexport type ErrorMapper = (error: unknown) => AppError | null\n\nconst mappers: ErrorMapper[] = []\n\n/**\n * Teaches `toAppError` about a backend it does not import.\n *\n * The core has no database dependency; `rei-kit/supabase` registers the\n * Postgrest mapping when it is imported, so an app that never touches Supabase\n * never downloads the code that knows about it.\n *\n * @example\n * ```ts\n * registerErrorMapper((error) =>\n * isPrismaConflict(error) ? new AppError('conflict', 'Already exists.') : null,\n * )\n * ```\n */\nexport function registerErrorMapper(mapper: ErrorMapper): void {\n mappers.push(mapper)\n}\n\n/**\n * Normalises anything thrown by the data layer.\n *\n * @param error - Whatever was caught.\n * @returns An `AppError`, never a rethrow.\n */\nexport function toAppError(error: unknown): AppError {\n if (error instanceof AppError) return error\n\n for (const map of mappers) {\n const mapped = map(error)\n if (mapped) return mapped\n }\n\n // A failed fetch surfaces as a TypeError, which is the only reliable signal\n // the browser gives that the request never left.\n if (error instanceof TypeError) {\n return new AppError('network', 'Could not reach the server.', { cause: error })\n }\n\n return new AppError('unknown', 'Something went wrong.', { cause: error })\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AA2BA,IAAa,WAAb,cAA8B,MAAM;CAClC;CAEA,YAAY,MAAoB,SAAiB,SAAwB;EACvE,MAAM,SAAS,OAAO;EACtB,KAAK,OAAO;EACZ,KAAK,OAAO;CACd;AACF;AAgBA,IAAM,UAAyB,CAAC;;;;;;;;;;;;;;;AAgBhC,SAAgB,oBAAoB,QAA2B;CAC7D,QAAQ,KAAK,MAAM;AACrB;;;;;;;AAQA,SAAgB,WAAW,OAA0B;CACnD,IAAI,iBAAiB,UAAU,OAAO;CAEtC,KAAK,MAAM,OAAO,SAAS;EACzB,MAAM,SAAS,IAAI,KAAK;EACxB,IAAI,QAAQ,OAAO;CACrB;CAIA,IAAI,iBAAiB,WACnB,OAAO,IAAI,SAAS,WAAW,+BAA+B,EAAE,OAAO,MAAM,CAAC;CAGhF,OAAO,IAAI,SAAS,WAAW,yBAAyB,EAAE,OAAO,MAAM,CAAC;AAC1E"}
package/dist/supabase.js CHANGED
@@ -88,10 +88,36 @@ function createSupabaseClient(url, anonKey) {
88
88
  * Registered on import rather than exported as a step to remember: importing
89
89
  * this module is already the decision to use Supabase.
90
90
  */
91
+ /**
92
+ * A PostgREST failure, however it reached us.
93
+ *
94
+ * `instanceof PostgrestError` is the obvious test and it is not enough. The
95
+ * error returned in `{ data, error }` is a plain object — supabase-js builds
96
+ * the class only on the paths that throw — and even where it does construct
97
+ * one, a project holding two copies of `@supabase/postgrest-js` gets two
98
+ * different classes and an `instanceof` that is false against a genuine error.
99
+ *
100
+ * The consequence was silent and total: every database failure fell past this
101
+ * mapper into the generic branch, so `permission denied for table enrollments`
102
+ * — a message naming the table and the missing grant — reached the screen as
103
+ * "Something went wrong." The information was there the whole time.
104
+ *
105
+ * So: the class where it holds, and the shape where it does not. `code` and
106
+ * `message` are what PostgREST always sends; `details` and `hint` are always
107
+ * present as keys, null when empty, which is what separates this from any
108
+ * other object carrying a `code`.
109
+ */
110
+ function isPostgrestError(error) {
111
+ if (error instanceof PostgrestError) return true;
112
+ if (typeof error !== "object" || error === null) return false;
113
+ const candidate = error;
114
+ return typeof candidate["message"] === "string" && typeof candidate["code"] === "string" && "details" in candidate && "hint" in candidate;
115
+ }
91
116
  registerErrorMapper((error) => {
92
- if (!(error instanceof PostgrestError)) return null;
117
+ if (!isPostgrestError(error)) return null;
93
118
  if (error.code === "23505") return new AppError("conflict", "That already exists.", { cause: error });
94
119
  if (error.code === "PGRST116") return new AppError("not-found", "That could not be found.", { cause: error });
120
+ if (error.code === "42501" || error.code === "PGRST301" || error.code === "PGRST302") return new AppError("denied", "You are not allowed to do that.", { cause: error });
95
121
  return new AppError("unknown", error.message, { cause: error });
96
122
  });
97
123
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"supabase.js","names":[],"sources":["../src/supabase/index.ts"],"sourcesContent":["import { createClient } from '@supabase/supabase-js'\nimport { PostgrestError } from '@supabase/supabase-js'\nimport type { SupabaseClient, SupportedStorage } from '@supabase/supabase-js'\n\nimport { AppError, registerErrorMapper } from '../utils/app-error'\n\n/**\n * The optional Supabase entry.\n *\n * Behind its own export so an app that never touches Supabase downloads none of\n * it — importing this module is what opts in, including to the error mapping\n * registered at the bottom.\n */\n\nconst REMEMBER_KEY = 'rei-remember'\n\n/**\n * Records whether the next session should outlive the tab.\n *\n * Call before signing in: the SDK writes the session as soon as the request\n * succeeds, and this decides where it lands.\n */\nexport function setRememberMe(remember: boolean): void {\n try {\n localStorage.setItem(REMEMBER_KEY, String(remember))\n } catch {\n // Storage blocked; the session will simply not persist.\n }\n}\n\nfunction activeStore(): Storage {\n try {\n return localStorage.getItem(REMEMBER_KEY) === 'false' ? sessionStorage : localStorage\n } catch {\n return sessionStorage\n }\n}\n\n/**\n * Session storage that follows the \"remember me\" choice.\n *\n * Supabase issues a short-lived access token plus a long-lived refresh token.\n * Where the refresh token is kept decides how long a login survives:\n * `localStorage` outlives the browser, `sessionStorage` dies with the tab. On a\n * shared machine that difference is the whole point, so the choice switches the\n * store rather than the token lifetime.\n *\n * Removal clears both, so signing out cannot leave a copy behind.\n */\nconst rememberAwareStorage: SupportedStorage = {\n getItem: (key) => {\n try {\n return activeStore().getItem(key)\n } catch {\n return null\n }\n },\n setItem: (key, value) => {\n try {\n activeStore().setItem(key, value)\n } catch {\n // Storage blocked.\n }\n },\n removeItem: (key) => {\n try {\n localStorage.removeItem(key)\n sessionStorage.removeItem(key)\n } catch {\n // Storage blocked.\n }\n },\n}\n\n/**\n * Builds a typed Supabase client with the remember-me storage wired in.\n *\n * A factory, not a module singleton reading `import.meta.env`: a package cannot\n * know what an app calls its environment variables, and a second app would have\n * different ones.\n *\n * @param url - Project URL. Public; it is the API endpoint.\n * @param anonKey - Anon key. Also public — row-level security is the boundary,\n * not the key.\n *\n * @example\n * ```ts\n * export const supabase = createSupabaseClient<Database>(\n * import.meta.env.VITE_SUPABASE_URL,\n * import.meta.env.VITE_SUPABASE_ANON_KEY,\n * )\n * ```\n */\nexport function createSupabaseClient<Database>(\n url: string,\n anonKey: string,\n): SupabaseClient<Database> {\n if (!url) throw new Error('createSupabaseClient: the project URL is missing.')\n if (!anonKey) throw new Error('createSupabaseClient: the anon key is missing.')\n\n return createClient<Database>(url, anonKey, { auth: { storage: rememberAwareStorage } })\n}\n\n/**\n * Teaches `toAppError` to read Postgres.\n *\n * Registered on import rather than exported as a step to remember: importing\n * this module is already the decision to use Supabase.\n */\nregisterErrorMapper((error) => {\n if (!(error instanceof PostgrestError)) return null\n\n // 23505 is unique_violation — a second row where the schema allows one. It is\n // a normal outcome of a double tap, not a failure worth an error screen.\n if (error.code === '23505') {\n return new AppError('conflict', 'That already exists.', { cause: error })\n }\n\n if (error.code === 'PGRST116') {\n return new AppError('not-found', 'That could not be found.', { cause: error })\n }\n\n return new AppError('unknown', error.message, { cause: error })\n})\n"],"mappings":";;;;;;;;;;AAcA,IAAM,eAAe;;;;;;;AAQrB,SAAgB,cAAc,UAAyB;CACrD,IAAI;EACF,aAAa,QAAQ,cAAc,OAAO,QAAQ,CAAC;CACrD,QAAQ,CAER;AACF;AAEA,SAAS,cAAuB;CAC9B,IAAI;EACF,OAAO,aAAa,QAAQ,YAAY,MAAM,UAAU,iBAAiB;CAC3E,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;;;;;;AAaA,IAAM,uBAAyC;CAC7C,UAAU,QAAQ;EAChB,IAAI;GACF,OAAO,YAAY,CAAC,CAAC,QAAQ,GAAG;EAClC,QAAQ;GACN,OAAO;EACT;CACF;CACA,UAAU,KAAK,UAAU;EACvB,IAAI;GACF,YAAY,CAAC,CAAC,QAAQ,KAAK,KAAK;EAClC,QAAQ,CAER;CACF;CACA,aAAa,QAAQ;EACnB,IAAI;GACF,aAAa,WAAW,GAAG;GAC3B,eAAe,WAAW,GAAG;EAC/B,QAAQ,CAER;CACF;AACF;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,qBACd,KACA,SAC0B;CAC1B,IAAI,CAAC,KAAK,MAAM,IAAI,MAAM,mDAAmD;CAC7E,IAAI,CAAC,SAAS,MAAM,IAAI,MAAM,gDAAgD;CAE9E,OAAO,aAAuB,KAAK,SAAS,EAAE,MAAM,EAAE,SAAS,qBAAqB,EAAE,CAAC;AACzF;;;;;;;AAQA,qBAAqB,UAAU;CAC7B,IAAI,EAAE,iBAAiB,iBAAiB,OAAO;CAI/C,IAAI,MAAM,SAAS,SACjB,OAAO,IAAI,SAAS,YAAY,wBAAwB,EAAE,OAAO,MAAM,CAAC;CAG1E,IAAI,MAAM,SAAS,YACjB,OAAO,IAAI,SAAS,aAAa,4BAA4B,EAAE,OAAO,MAAM,CAAC;CAG/E,OAAO,IAAI,SAAS,WAAW,MAAM,SAAS,EAAE,OAAO,MAAM,CAAC;AAChE,CAAC"}
1
+ {"version":3,"file":"supabase.js","names":[],"sources":["../src/supabase/index.ts"],"sourcesContent":["import { createClient } from '@supabase/supabase-js'\nimport { PostgrestError } from '@supabase/supabase-js'\nimport type { SupabaseClient, SupportedStorage } from '@supabase/supabase-js'\n\nimport { AppError, registerErrorMapper } from '../utils/app-error'\n\n/**\n * The optional Supabase entry.\n *\n * Behind its own export so an app that never touches Supabase downloads none of\n * it — importing this module is what opts in, including to the error mapping\n * registered at the bottom.\n */\n\nconst REMEMBER_KEY = 'rei-remember'\n\n/**\n * Records whether the next session should outlive the tab.\n *\n * Call before signing in: the SDK writes the session as soon as the request\n * succeeds, and this decides where it lands.\n */\nexport function setRememberMe(remember: boolean): void {\n try {\n localStorage.setItem(REMEMBER_KEY, String(remember))\n } catch {\n // Storage blocked; the session will simply not persist.\n }\n}\n\nfunction activeStore(): Storage {\n try {\n return localStorage.getItem(REMEMBER_KEY) === 'false' ? sessionStorage : localStorage\n } catch {\n return sessionStorage\n }\n}\n\n/**\n * Session storage that follows the \"remember me\" choice.\n *\n * Supabase issues a short-lived access token plus a long-lived refresh token.\n * Where the refresh token is kept decides how long a login survives:\n * `localStorage` outlives the browser, `sessionStorage` dies with the tab. On a\n * shared machine that difference is the whole point, so the choice switches the\n * store rather than the token lifetime.\n *\n * Removal clears both, so signing out cannot leave a copy behind.\n */\nconst rememberAwareStorage: SupportedStorage = {\n getItem: (key) => {\n try {\n return activeStore().getItem(key)\n } catch {\n return null\n }\n },\n setItem: (key, value) => {\n try {\n activeStore().setItem(key, value)\n } catch {\n // Storage blocked.\n }\n },\n removeItem: (key) => {\n try {\n localStorage.removeItem(key)\n sessionStorage.removeItem(key)\n } catch {\n // Storage blocked.\n }\n },\n}\n\n/**\n * Builds a typed Supabase client with the remember-me storage wired in.\n *\n * A factory, not a module singleton reading `import.meta.env`: a package cannot\n * know what an app calls its environment variables, and a second app would have\n * different ones.\n *\n * @param url - Project URL. Public; it is the API endpoint.\n * @param anonKey - Anon key. Also public — row-level security is the boundary,\n * not the key.\n *\n * @example\n * ```ts\n * export const supabase = createSupabaseClient<Database>(\n * import.meta.env.VITE_SUPABASE_URL,\n * import.meta.env.VITE_SUPABASE_ANON_KEY,\n * )\n * ```\n */\nexport function createSupabaseClient<Database>(\n url: string,\n anonKey: string,\n): SupabaseClient<Database> {\n if (!url) throw new Error('createSupabaseClient: the project URL is missing.')\n if (!anonKey) throw new Error('createSupabaseClient: the anon key is missing.')\n\n return createClient<Database>(url, anonKey, { auth: { storage: rememberAwareStorage } })\n}\n\n/**\n * Teaches `toAppError` to read Postgres.\n *\n * Registered on import rather than exported as a step to remember: importing\n * this module is already the decision to use Supabase.\n */\n/**\n * A PostgREST failure, however it reached us.\n *\n * `instanceof PostgrestError` is the obvious test and it is not enough. The\n * error returned in `{ data, error }` is a plain object — supabase-js builds\n * the class only on the paths that throw — and even where it does construct\n * one, a project holding two copies of `@supabase/postgrest-js` gets two\n * different classes and an `instanceof` that is false against a genuine error.\n *\n * The consequence was silent and total: every database failure fell past this\n * mapper into the generic branch, so `permission denied for table enrollments`\n * — a message naming the table and the missing grant — reached the screen as\n * \"Something went wrong.\" The information was there the whole time.\n *\n * So: the class where it holds, and the shape where it does not. `code` and\n * `message` are what PostgREST always sends; `details` and `hint` are always\n * present as keys, null when empty, which is what separates this from any\n * other object carrying a `code`.\n */\nfunction isPostgrestError(error: unknown): error is PostgrestError {\n if (error instanceof PostgrestError) return true\n if (typeof error !== 'object' || error === null) return false\n\n const candidate = error as Record<string, unknown>\n\n return (\n typeof candidate['message'] === 'string' &&\n typeof candidate['code'] === 'string' &&\n 'details' in candidate &&\n 'hint' in candidate\n )\n}\n\nregisterErrorMapper((error) => {\n if (!isPostgrestError(error)) return null\n\n // 23505 is unique_violation — a second row where the schema allows one. It is\n // a normal outcome of a double tap, not a failure worth an error screen.\n if (error.code === '23505') {\n return new AppError('conflict', 'That already exists.', { cause: error })\n }\n\n if (error.code === 'PGRST116') {\n return new AppError('not-found', 'That could not be found.', { cause: error })\n }\n\n // 42501 is insufficient_privilege and PGRST301/302 are an absent or expired\n // token. All three mean the same thing to a reader — you are not allowed to\n // do this — and none of them mean the app is broken, which is what a generic\n // failure message implies and what sends somebody to the error log.\n if (error.code === '42501' || error.code === 'PGRST301' || error.code === 'PGRST302') {\n return new AppError('denied', 'You are not allowed to do that.', { cause: error })\n }\n\n return new AppError('unknown', error.message, { cause: error })\n})\n"],"mappings":";;;;;;;;;;AAcA,IAAM,eAAe;;;;;;;AAQrB,SAAgB,cAAc,UAAyB;CACrD,IAAI;EACF,aAAa,QAAQ,cAAc,OAAO,QAAQ,CAAC;CACrD,QAAQ,CAER;AACF;AAEA,SAAS,cAAuB;CAC9B,IAAI;EACF,OAAO,aAAa,QAAQ,YAAY,MAAM,UAAU,iBAAiB;CAC3E,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;;;;;;AAaA,IAAM,uBAAyC;CAC7C,UAAU,QAAQ;EAChB,IAAI;GACF,OAAO,YAAY,CAAC,CAAC,QAAQ,GAAG;EAClC,QAAQ;GACN,OAAO;EACT;CACF;CACA,UAAU,KAAK,UAAU;EACvB,IAAI;GACF,YAAY,CAAC,CAAC,QAAQ,KAAK,KAAK;EAClC,QAAQ,CAER;CACF;CACA,aAAa,QAAQ;EACnB,IAAI;GACF,aAAa,WAAW,GAAG;GAC3B,eAAe,WAAW,GAAG;EAC/B,QAAQ,CAER;CACF;AACF;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,qBACd,KACA,SAC0B;CAC1B,IAAI,CAAC,KAAK,MAAM,IAAI,MAAM,mDAAmD;CAC7E,IAAI,CAAC,SAAS,MAAM,IAAI,MAAM,gDAAgD;CAE9E,OAAO,aAAuB,KAAK,SAAS,EAAE,MAAM,EAAE,SAAS,qBAAqB,EAAE,CAAC;AACzF;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA,SAAS,iBAAiB,OAAyC;CACjE,IAAI,iBAAiB,gBAAgB,OAAO;CAC5C,IAAI,OAAO,UAAU,YAAY,UAAU,MAAM,OAAO;CAExD,MAAM,YAAY;CAElB,OACE,OAAO,UAAU,eAAe,YAChC,OAAO,UAAU,YAAY,YAC7B,aAAa,aACb,UAAU;AAEd;AAEA,qBAAqB,UAAU;CAC7B,IAAI,CAAC,iBAAiB,KAAK,GAAG,OAAO;CAIrC,IAAI,MAAM,SAAS,SACjB,OAAO,IAAI,SAAS,YAAY,wBAAwB,EAAE,OAAO,MAAM,CAAC;CAG1E,IAAI,MAAM,SAAS,YACjB,OAAO,IAAI,SAAS,aAAa,4BAA4B,EAAE,OAAO,MAAM,CAAC;CAO/E,IAAI,MAAM,SAAS,WAAW,MAAM,SAAS,cAAc,MAAM,SAAS,YACxE,OAAO,IAAI,SAAS,UAAU,mCAAmC,EAAE,OAAO,MAAM,CAAC;CAGnF,OAAO,IAAI,SAAS,WAAW,MAAM,SAAS,EAAE,OAAO,MAAM,CAAC;AAChE,CAAC"}
@@ -1,5 +1,12 @@
1
- /** Error categories the UI can branch on, independent of Postgres or Supabase. */
2
- export type AppErrorKind = 'conflict' | 'not-found' | 'network' | 'unknown';
1
+ /**
2
+ * Error categories the UI can branch on, independent of Postgres or Supabase.
3
+ *
4
+ * `denied` is the one that is not a fault: the request was understood, well
5
+ * formed and refused. A screen that treats it as a failure tells the reader
6
+ * something is broken and sends them to support, when what they need is to
7
+ * sign in again or to be told the thing is not theirs.
8
+ */
9
+ export type AppErrorKind = 'conflict' | 'not-found' | 'network' | 'denied' | 'unknown';
3
10
  /**
4
11
  * A normalised error thrown by the data layer.
5
12
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rei-kit",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Vue 3 and Tailwind 4 design system and shared runtime. Extracted from Hibi.",
5
5
  "license": "MIT",
6
6
  "author": "Ramazan Doğan",