better-supabase 0.0.0 → 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.
Files changed (154) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +88 -1
  3. package/bin/better-supabase.js +3 -0
  4. package/dist/casing-da0uRTqt.js +14 -0
  5. package/dist/cli/bin.d.ts +1 -0
  6. package/dist/cli/bin.js +15 -0
  7. package/dist/cli/index.d.ts +226 -0
  8. package/dist/cli/index.js +3 -0
  9. package/dist/cli-CoByWYja.js +6472 -0
  10. package/dist/client/index.d.ts +2 -0
  11. package/dist/client/index.js +112 -0
  12. package/dist/compiler-DL18ukPe.d.ts +14 -0
  13. package/dist/config/index.d.ts +3 -0
  14. package/dist/config/index.js +3 -0
  15. package/dist/config-D-aR2ZoN.js +276 -0
  16. package/dist/define-CTfWw-SS.d.ts +662 -0
  17. package/dist/define-QGkq04oe.js +1068 -0
  18. package/dist/edge/index.d.ts +43 -0
  19. package/dist/edge/index.js +103 -0
  20. package/dist/entitlements-BBg61DQZ.d.ts +20 -0
  21. package/dist/entitlements-BC_khwRy.js +17 -0
  22. package/dist/env/index.d.ts +2 -0
  23. package/dist/env/index.js +2 -0
  24. package/dist/env-BEToab0I.js +204 -0
  25. package/dist/errors-Bl3rkOzo.js +196 -0
  26. package/dist/events/index.d.ts +2 -0
  27. package/dist/events/index.js +2 -0
  28. package/dist/events-2OJsVG_d.d.ts +84 -0
  29. package/dist/events-BuzTgn2D.js +135 -0
  30. package/dist/executor-Bazhgsgq.d.ts +195 -0
  31. package/dist/executor-Bje4suQF.d.ts +25 -0
  32. package/dist/executor-Ceq60hPM.js +89 -0
  33. package/dist/hono/index.d.ts +45 -0
  34. package/dist/hono/index.js +70 -0
  35. package/dist/index-BD6wKthp.d.ts +597 -0
  36. package/dist/index-DQVvHzjW.d.ts +74 -0
  37. package/dist/index-DRid0qIv.d.ts +76 -0
  38. package/dist/index-Dup-nSe9.d.ts +117 -0
  39. package/dist/index-Dy6kuF0P.d.ts +71 -0
  40. package/dist/index-Hf8PP9sP.d.ts +116 -0
  41. package/dist/index-JXHBuS53.d.ts +144 -0
  42. package/dist/index-Nxo3TJcm.d.ts +208 -0
  43. package/dist/index-Sw43J8HG.d.ts +62 -0
  44. package/dist/index-cEeweTeM.d.ts +38 -0
  45. package/dist/index.d.ts +110 -0
  46. package/dist/index.js +42 -0
  47. package/dist/invalidate-DbxuL2jL.js +18 -0
  48. package/dist/jobs/index.d.ts +173 -0
  49. package/dist/jobs/index.js +477 -0
  50. package/dist/json-schema-BbwO2mZg.js +181 -0
  51. package/dist/lint/index.d.ts +75 -0
  52. package/dist/lint/index.js +167 -0
  53. package/dist/list/index.d.ts +2 -0
  54. package/dist/list/index.js +410 -0
  55. package/dist/live-6b9i72Ux.d.ts +180 -0
  56. package/dist/live-D8rV1Ldw.js +168 -0
  57. package/dist/mcp/index.d.ts +105 -0
  58. package/dist/mcp/index.js +330 -0
  59. package/dist/mfa-CpogP66z.js +521 -0
  60. package/dist/mfa-TVia554c.d.ts +44 -0
  61. package/dist/next/image/index.d.ts +34 -0
  62. package/dist/next/image/index.js +40 -0
  63. package/dist/next/index.d.ts +210 -0
  64. package/dist/next/index.js +426 -0
  65. package/dist/openapi/index.d.ts +2 -0
  66. package/dist/openapi/index.js +332 -0
  67. package/dist/orpc/index.d.ts +46 -0
  68. package/dist/orpc/index.js +50 -0
  69. package/dist/otel/index.d.ts +40 -0
  70. package/dist/otel/index.js +168 -0
  71. package/dist/path-Cv__-OTj.d.ts +17 -0
  72. package/dist/plugin-BqR6wKMB.js +23 -0
  73. package/dist/plugin-HvGqCurB.d.ts +118 -0
  74. package/dist/plugins/actor/index.d.ts +13 -0
  75. package/dist/plugins/actor/index.js +43 -0
  76. package/dist/plugins/rules/index.d.ts +88 -0
  77. package/dist/plugins/rules/index.js +174 -0
  78. package/dist/plugins/soft-delete/index.d.ts +38 -0
  79. package/dist/plugins/soft-delete/index.js +69 -0
  80. package/dist/plugins/tenant/index.d.ts +43 -0
  81. package/dist/plugins/tenant/index.js +73 -0
  82. package/dist/plugins/timestamps/index.d.ts +9 -0
  83. package/dist/plugins/timestamps/index.js +37 -0
  84. package/dist/plugins/validation/index.d.ts +24 -0
  85. package/dist/plugins/validation/index.js +59 -0
  86. package/dist/pool-BFeqzwQS.d.ts +37 -0
  87. package/dist/postgres/index.d.ts +28 -0
  88. package/dist/postgres/index.js +79 -0
  89. package/dist/postgrest-Dm-vhY6R.d.ts +43 -0
  90. package/dist/problem-DPu6Fh-t.js +97 -0
  91. package/dist/problem-DlgB0lYl.d.ts +47 -0
  92. package/dist/query/index.d.ts +2 -0
  93. package/dist/query/index.js +3 -0
  94. package/dist/query-ChvDbsDE.js +186 -0
  95. package/dist/react/index.d.ts +7 -0
  96. package/dist/react/index.js +5 -0
  97. package/dist/react/server.d.ts +13 -0
  98. package/dist/react/server.js +25 -0
  99. package/dist/react/session.d.ts +2 -0
  100. package/dist/react/session.js +3 -0
  101. package/dist/react-CrOvrnIP.js +241 -0
  102. package/dist/read-set-DhX2d04c.js +216 -0
  103. package/dist/realtime/index.d.ts +3 -0
  104. package/dist/realtime/index.js +253 -0
  105. package/dist/resolve-DW8h9NPm.d.ts +155 -0
  106. package/dist/resource-CYiu6Ygi.js +175 -0
  107. package/dist/resource-SkPTRJDD.d.ts +65 -0
  108. package/dist/respond-4-dhoV7x.js +380 -0
  109. package/dist/respond-PoTpy5DT.d.ts +169 -0
  110. package/dist/result-CTkCZ6JA.d.ts +198 -0
  111. package/dist/result-DWXatkd6.js +103 -0
  112. package/dist/scope-CDHcS7dE.js +90 -0
  113. package/dist/seed-CzecODcJ.js +101 -0
  114. package/dist/server/index.d.ts +42 -0
  115. package/dist/server/index.js +68 -0
  116. package/dist/session-C3osL21k.d.ts +21 -0
  117. package/dist/session-CD-3orO_.js +19 -0
  118. package/dist/shared-_6-uPL7E.js +57 -0
  119. package/dist/simplify-oHiDKRVQ.js +665 -0
  120. package/dist/spec-pins-CJPVghHy.d.ts +20 -0
  121. package/dist/spec-pins-t6sPaHvl.js +20 -0
  122. package/dist/sql-M8l3fnS0.js +260 -0
  123. package/dist/ssr/index.d.ts +5 -0
  124. package/dist/ssr/index.js +4 -0
  125. package/dist/standard-CyhRzctb.d.ts +7 -0
  126. package/dist/standard-FUi3Z3-7.js +21 -0
  127. package/dist/stats-3iy8u3_P.js +566 -0
  128. package/dist/storage/index.d.ts +4 -0
  129. package/dist/storage/index.js +2 -0
  130. package/dist/storage-BWXVUXGk.js +461 -0
  131. package/dist/tables-rZxRxNPW.js +59 -0
  132. package/dist/template-BeOGnynM.js +93 -0
  133. package/dist/template-mbvlysOo.d.ts +6 -0
  134. package/dist/testing/index.d.ts +294 -0
  135. package/dist/testing/index.js +751 -0
  136. package/dist/types-6cYK9pbJ.js +29 -0
  137. package/dist/types-C2fslP1z.d.ts +195 -0
  138. package/dist/version-Cqq1m3UY.js +4 -0
  139. package/dist/view-B0ltUmTi.d.ts +43 -0
  140. package/dist/view-B6F9QbG-.js +35 -0
  141. package/dist/webhooks/index.d.ts +152 -0
  142. package/dist/webhooks/index.js +2 -0
  143. package/dist/webhooks-DbMTJJX2.js +139 -0
  144. package/package.json +246 -17
  145. package/schemas/config-v1.json +328 -0
  146. package/schemas/doctor-report-v1.json +105 -0
  147. package/schemas/snapshot-v2.json +1002 -0
  148. package/skills/better-supabase/SKILL.md +84 -0
  149. package/skills/better-supabase/references/plugins.md +56 -0
  150. package/skills/better-supabase/references/troubleshooting.md +39 -0
  151. package/skills/better-supabase-api/SKILL.md +67 -0
  152. package/skills/better-supabase-api/references/adapters.md +108 -0
  153. package/skills/better-supabase-testing/SKILL.md +65 -0
  154. package/index.js +0 -1
@@ -0,0 +1,29 @@
1
+ //#region src/ir/types.ts
2
+ const and = (...items) => {
3
+ const present = items.filter((item) => item !== void 0);
4
+ if (present.length === 0) return void 0;
5
+ if (present.length === 1) return present[0];
6
+ return {
7
+ kind: "and",
8
+ items: present
9
+ };
10
+ };
11
+ const or = (...items) => {
12
+ if (items.length === 1 && items[0]) return items[0];
13
+ return {
14
+ kind: "or",
15
+ items
16
+ };
17
+ };
18
+ const not = (item) => ({
19
+ kind: "not",
20
+ item
21
+ });
22
+ const column = (name, op, value) => ({
23
+ kind: "column",
24
+ column: name,
25
+ op,
26
+ value
27
+ });
28
+ //#endregion
29
+ export { or as i, column as n, not as r, and as t };
@@ -0,0 +1,195 @@
1
+ //#region src/schema/types.d.ts
2
+ type Casing = "snake" | "camel";
3
+ /** Runtime metadata for one column. Keyed by the app (cased) column name. */
4
+ interface ColumnMeta {
5
+ /** Database column name. */
6
+ readonly db: string;
7
+ /** Postgres type name (`uuid`, `text`, `timestamptz`, `note_kind`). */
8
+ readonly type: string;
9
+ readonly nullable: boolean;
10
+ readonly hasDefault: boolean;
11
+ readonly generated?: boolean;
12
+ /** Identity column; `'always'` columns can't be written. */
13
+ readonly identity?: "always" | "by default";
14
+ /** `false` when inserts can't set this column (generated, identity always, view). */
15
+ readonly insertable?: false;
16
+ /** `false` when updates can't set this column. */
17
+ readonly updatable?: false;
18
+ readonly array?: boolean;
19
+ readonly json?: boolean;
20
+ /** Allowed values for enum columns and CHECK-constraint unions. */
21
+ readonly enum?: readonly string[];
22
+ /** How values are decoded from the wire (see the `codecs` config). */
23
+ readonly codec?: Codec;
24
+ /** Listed in `config.sensitive`; the `noSensitiveSelect` rule guards reads of it. */
25
+ readonly sensitive?: true;
26
+ /** Bucket id of the objects this text column points to (`config.storagePaths`). */
27
+ readonly storage?: string;
28
+ }
29
+ /**
30
+ * Runtime representation for a column: `date` decodes timestamps to `Date`,
31
+ * `bigint` and `string` read the exact text of `int8`/`numeric` values.
32
+ */
33
+ type Codec = "date" | "bigint" | "string";
34
+ interface RelationMeta {
35
+ /** App key of the target table. */
36
+ readonly table: string;
37
+ readonly kind: "one" | "many";
38
+ readonly nullable: boolean;
39
+ /** Foreign key constraint name, used as the PostgREST embed hint. */
40
+ readonly foreignKey: string;
41
+ /** App column names on the source table. */
42
+ readonly columns: readonly string[];
43
+ /** App column names on the target table. */
44
+ readonly references: readonly string[];
45
+ /** `forward`: this table holds the FK. `reverse`: the target holds it. */
46
+ readonly direction: "forward" | "reverse";
47
+ /** What deleting the referenced row does to the referencing rows, when it changes them. */
48
+ readonly onDelete?: "cascade" | "set null" | "set default";
49
+ }
50
+ interface TableFlags {
51
+ /** App column holding the soft-delete timestamp. */
52
+ readonly softDelete?: string;
53
+ readonly timestamps?: {
54
+ readonly createdAt?: string;
55
+ readonly updatedAt?: string;
56
+ };
57
+ /** App column holding the tenant id. */
58
+ readonly tenant?: string;
59
+ readonly actor?: {
60
+ readonly createdBy?: string;
61
+ readonly updatedBy?: string;
62
+ };
63
+ /** App column used for optimistic concurrency, usually `updatedAt`. */
64
+ readonly version?: string;
65
+ }
66
+ interface TableMeta {
67
+ /** App key of the table (`customerTags` in camel casing). */
68
+ readonly key: string;
69
+ /** Database table name. */
70
+ readonly name: string;
71
+ readonly schema: string;
72
+ readonly kind: "table" | "view";
73
+ readonly columns: Readonly<Record<string, ColumnMeta>>;
74
+ /** App column names. */
75
+ readonly primaryKey: readonly string[];
76
+ /** Constraint name to app column names. */
77
+ readonly uniqueKeys: Readonly<Record<string, readonly string[]>>;
78
+ readonly relations: Readonly<Record<string, RelationMeta>>;
79
+ readonly flags: TableFlags;
80
+ }
81
+ interface FunctionMeta {
82
+ readonly name: string;
83
+ readonly schema: string;
84
+ readonly args: readonly {
85
+ readonly name: string;
86
+ readonly type: string;
87
+ }[];
88
+ readonly returns: string;
89
+ readonly returnsSet: boolean;
90
+ readonly volatility: "immutable" | "stable" | "volatile";
91
+ }
92
+ interface SchemaMeta {
93
+ readonly version: 1;
94
+ readonly casing: Casing;
95
+ readonly tables: Readonly<Record<string, TableMeta>>;
96
+ readonly enums: Readonly<Record<string, readonly string[]>>;
97
+ readonly functions: Readonly<Record<string, FunctionMeta>>;
98
+ readonly buckets?: Readonly<Record<string, BucketMeta>>;
99
+ readonly topics?: Readonly<Record<string, string>>;
100
+ /** Tables that broadcast change signals (`config.realtime.tables`), keyed by app key. */
101
+ readonly realtime?: Readonly<Record<string, RealtimeTableMeta>>;
102
+ }
103
+ interface RealtimeTableMeta {
104
+ /** App column whose value scopes the broadcast topic. */
105
+ readonly tenant?: string;
106
+ }
107
+ interface BucketMeta {
108
+ readonly id: string;
109
+ readonly public: boolean;
110
+ readonly path: string;
111
+ readonly policy?: "tenant" | "owner" | "public" | "none";
112
+ readonly fileSizeLimit?: string;
113
+ readonly allowedMimeTypes?: readonly string[];
114
+ }
115
+ interface RelationShape {
116
+ readonly table: string;
117
+ readonly kind: "one" | "many";
118
+ readonly nullable: boolean;
119
+ }
120
+ interface FlagsShape {
121
+ readonly softDelete?: string;
122
+ readonly timestamps?: true;
123
+ readonly tenant?: string;
124
+ readonly actor?: true;
125
+ readonly version?: string;
126
+ }
127
+ interface ModelShape {
128
+ readonly Row: object;
129
+ readonly Insert: object;
130
+ readonly Update: object;
131
+ readonly Relations: object;
132
+ readonly PrimaryKey: PropertyKey;
133
+ readonly UniqueKeys: object;
134
+ /** CHECK constraint names. */
135
+ readonly Checks?: string;
136
+ /** Foreign key constraint names on this table. */
137
+ readonly ForeignKeys?: string;
138
+ readonly Flags: FlagsShape;
139
+ }
140
+ type AnyModels = {
141
+ readonly [table: string]: ModelShape;
142
+ };
143
+ type FunctionShape = {
144
+ readonly Args: object;
145
+ readonly Returns: unknown;
146
+ };
147
+ type AnyFunctions = {
148
+ readonly [name: string]: FunctionShape;
149
+ };
150
+ /**
151
+ * Carries the model types next to the runtime metadata. Created by
152
+ * `defineSchema` in the generated module.
153
+ */
154
+ interface Schema<M extends AnyModels = AnyModels, D = unknown, F extends AnyFunctions = AnyFunctions> {
155
+ readonly meta: SchemaMeta;
156
+ /** Phantom. Never present at runtime. */
157
+ readonly "~types"?: {
158
+ readonly models: M;
159
+ readonly database: D;
160
+ readonly functions: F;
161
+ };
162
+ }
163
+ type ModelsOf<S> = S extends Schema<infer M> ? M : never;
164
+ type DatabaseOf<S> = S extends Schema<AnyModels, infer D> ? D : never;
165
+ type FunctionsOf<S> = S extends Schema<AnyModels, unknown, infer F> ? F : never;
166
+ type TableKey<M extends AnyModels> = Extract<keyof M, string>;
167
+ type Row<M extends AnyModels, T extends keyof M> = M[T]["Row"];
168
+ type Insert<M extends AnyModels, T extends keyof M> = M[T]["Insert"];
169
+ type Update<M extends AnyModels, T extends keyof M> = M[T]["Update"];
170
+ type Relations<M extends AnyModels, T extends keyof M> = M[T]["Relations"];
171
+ type Flags<M extends AnyModels, T extends keyof M> = M[T]["Flags"];
172
+ type PrimaryKeyColumn<M extends AnyModels, T extends keyof M> = Extract<M[T]["PrimaryKey"], keyof Row<M, T>>;
173
+ type UniqueKeyName<M extends AnyModels, T extends keyof M> = Extract<keyof M[T]["UniqueKeys"], string>;
174
+ type UniqueKeyColumns<M extends AnyModels, T extends keyof M, K extends keyof M[T]["UniqueKeys"]> = Extract<M[T]["UniqueKeys"][K] extends readonly (infer C)[] ? C : never, keyof Row<M, T>>;
175
+ /**
176
+ * Values for exactly one unique key: the primary key columns, or the columns
177
+ * of one named unique constraint or unique index.
178
+ */
179
+ type UniqueWhere<M extends AnyModels, T extends keyof M> = { readonly [C in PrimaryKeyColumn<M, T>]: Row<M, T>[C]; } | { [K in keyof M[T]["UniqueKeys"]]: { readonly [C in UniqueKeyColumns<M, T, K>]: Row<M, T>[C]; }; }[keyof M[T]["UniqueKeys"]];
180
+ /** Every unique constraint name in the schema, for `isConflict`. */
181
+ type UniqueConstraint<M extends AnyModels> = { [T in keyof M]: Extract<keyof M[T]["UniqueKeys"], string>; }[keyof M];
182
+ /** Every CHECK constraint name in the schema, for `isCheck`. */
183
+ type CheckConstraint<M extends AnyModels> = { [T in keyof M]: M[T] extends {
184
+ readonly Checks: infer C extends string;
185
+ } ? C : never; }[keyof M];
186
+ /** Every foreign key constraint name in the schema, for `isForeignKey`. */
187
+ type ForeignKeyConstraint<M extends AnyModels> = { [T in keyof M]: M[T] extends {
188
+ readonly ForeignKeys: infer C extends string;
189
+ } ? C : never; }[keyof M];
190
+ type IsUnion<T, U = T> = T extends unknown ? [U] extends [T] ? false : true : never;
191
+ /** A single value for single-column keys, an object for composite keys. */
192
+ type PrimaryKeyValue<M extends AnyModels, T extends keyof M> = true extends IsUnion<PrimaryKeyColumn<M, T>> ? { readonly [K in PrimaryKeyColumn<M, T>]: Row<M, T>[K]; } : Row<M, T>[PrimaryKeyColumn<M, T>];
193
+ type Simplify<T> = { [K in keyof T]: T[K]; } & {};
194
+ //#endregion
195
+ export { TableMeta as A, Relations as C, Simplify as D, SchemaMeta as E, UniqueKeyName as M, UniqueWhere as N, TableFlags as O, Update as P, RelationShape as S, Schema as T, ModelsOf as _, CheckConstraint as a, RealtimeTableMeta as b, DatabaseOf as c, ForeignKeyConstraint as d, FunctionMeta as f, ModelShape as g, Insert as h, Casing as i, UniqueConstraint as j, TableKey as k, Flags as l, FunctionsOf as m, AnyModels as n, Codec as o, FunctionShape as p, BucketMeta as r, ColumnMeta as s, AnyFunctions as t, FlagsShape as u, PrimaryKeyColumn as v, Row as w, RelationMeta as x, PrimaryKeyValue as y };
@@ -0,0 +1,4 @@
1
+ //#region src/cli/version.ts
2
+ const VERSION = "0.0.0";
3
+ //#endregion
4
+ export { VERSION as t };
@@ -0,0 +1,43 @@
1
+ import { p as DbError } from "./result-CTkCZ6JA.js";
2
+ import { n as AmrEntry, s as Impersonator, t as Aal } from "./mfa-TVia554c.js";
3
+ import { i as InvalidReason, r as AuthState } from "./resolve-DW8h9NPm.js";
4
+ import { JWTClaims, UserClaims } from "@supabase/server";
5
+ //#region src/auth/view.d.ts
6
+ /**
7
+ * The verified caller as plain data: no token, no clients. Safe to return
8
+ * from a `'use cache: private'` function and to pass to Client Components.
9
+ */
10
+ type AuthSession<C = unknown> = {
11
+ readonly kind: "user";
12
+ readonly user: UserClaims;
13
+ /**
14
+ * The verified JWT payload, including custom access token hook claims,
15
+ * typed and validated by `sb.claims(schema)`.
16
+ */
17
+ readonly claims: JWTClaims & C;
18
+ /** Seconds since epoch, from the token's `exp`. */
19
+ readonly expiresAt: number | null;
20
+ /** `aal2` once the user verified a second factor in this session. */
21
+ readonly aal: Aal;
22
+ /** How the user signed in (`password`, `totp`, `sso/saml`, ...). */
23
+ readonly amr: readonly AmrEntry[];
24
+ /** Set when an admin acts as this user (the `act` claim), for a banner. */
25
+ readonly impersonator?: Impersonator;
26
+ } | {
27
+ readonly kind: "service";
28
+ readonly keyName: string;
29
+ } | {
30
+ readonly kind: "anon";
31
+ readonly reason: AnonReason;
32
+ } | {
33
+ readonly kind: "invalid";
34
+ readonly reason: InvalidReason;
35
+ readonly error: DbError;
36
+ };
37
+ type AnonReason = Extract<AuthState, {
38
+ kind: "anon";
39
+ }>["reason"];
40
+ /** Drops the token from an `AuthState`, leaving only serializable fields. */
41
+ declare function toSession<C>(auth: AuthState<C>): AuthSession<C>;
42
+ //#endregion
43
+ export { toSession as n, AuthSession as t };
@@ -0,0 +1,35 @@
1
+ import { g as impersonatorOf, n as amrOf, t as aalOf } from "./mfa-CpogP66z.js";
2
+ //#region src/auth/view.ts
3
+ /** Drops the token from an `AuthState`, leaving only serializable fields. */
4
+ function toSession(auth) {
5
+ switch (auth.kind) {
6
+ case "user": {
7
+ const impersonator = impersonatorOf(auth.claims);
8
+ return {
9
+ kind: "user",
10
+ user: auth.user,
11
+ claims: auth.claims,
12
+ expiresAt: auth.expiresAt,
13
+ aal: aalOf(auth.claims),
14
+ amr: amrOf(auth.claims),
15
+ ...impersonator ? { impersonator } : {}
16
+ };
17
+ }
18
+ case "service": return {
19
+ kind: "service",
20
+ keyName: auth.keyName
21
+ };
22
+ case "anon": return {
23
+ kind: "anon",
24
+ reason: auth.reason
25
+ };
26
+ case "invalid": return {
27
+ kind: "invalid",
28
+ reason: auth.reason,
29
+ error: auth.error
30
+ };
31
+ default: return auth;
32
+ }
33
+ }
34
+ //#endregion
35
+ export { toSession as t };
@@ -0,0 +1,152 @@
1
+ import { k as TableKey, n as AnyModels, t as AnyFunctions, w as Row } from "../types-C2fslP1z.js";
2
+ import { o as Result } from "../result-CTkCZ6JA.js";
3
+ import { t as BetterSupabase } from "../define-CTfWw-SS.js";
4
+ //#region src/webhooks/index.d.ts
5
+ export interface VerifiedWebhook<T = unknown> {
6
+ readonly id: string;
7
+ readonly timestamp: Date;
8
+ readonly payload: T;
9
+ readonly body: string;
10
+ }
11
+ export interface VerifyOptions {
12
+ /** Allowed clock skew in seconds. Defaults to 300. */
13
+ readonly tolerance?: number;
14
+ readonly now?: () => number;
15
+ }
16
+ export type WebhookInput = Request | {
17
+ readonly headers: Headers | Readonly<Record<string, string>>;
18
+ readonly body: string;
19
+ };
20
+ /** Constant-time string comparison. */
21
+ export declare function timingSafeEqual(a: string, b: string): boolean;
22
+ /** Headers for a signed Standard Webhooks request. */
23
+ export declare function signWebhook(secret: string, message: {
24
+ readonly id: string;
25
+ readonly body: string;
26
+ readonly timestamp?: Date;
27
+ }): Promise<Record<string, string>>;
28
+ /**
29
+ * Verifies a Standard Webhooks signature (`webhook-id`, `webhook-timestamp`,
30
+ * `webhook-signature`) and parses the JSON body. Pass several secrets while
31
+ * rotating.
32
+ */
33
+ export declare function verifyWebhook<T = unknown>(input: WebhookInput, secrets: string | readonly string[], options?: VerifyOptions): Promise<Result<VerifiedWebhook<T>>>;
34
+ /** Checks a shared-secret header (database webhooks), in constant time. */
35
+ export declare function verifySharedSecret(request: Request, secret: string, headerName?: string): boolean;
36
+ interface HookUser {
37
+ readonly id: string;
38
+ readonly email?: string;
39
+ readonly phone?: string;
40
+ readonly app_metadata?: Readonly<Record<string, unknown>>;
41
+ readonly user_metadata?: Readonly<Record<string, unknown>>;
42
+ readonly [key: string]: unknown;
43
+ }
44
+ export interface AuthHookError {
45
+ readonly error: {
46
+ readonly http_code: number;
47
+ readonly message: string;
48
+ };
49
+ }
50
+ export interface AuthHooks {
51
+ custom_access_token: {
52
+ input: {
53
+ readonly user_id: string;
54
+ readonly claims: Readonly<Record<string, unknown>> & {
55
+ readonly sub: string;
56
+ readonly role: string;
57
+ };
58
+ readonly authentication_method: string;
59
+ };
60
+ output: {
61
+ readonly claims: Readonly<Record<string, unknown>>;
62
+ };
63
+ };
64
+ send_email: {
65
+ input: {
66
+ readonly user: HookUser;
67
+ readonly email_data: {
68
+ readonly token: string;
69
+ readonly token_hash: string;
70
+ readonly redirect_to: string;
71
+ readonly email_action_type: string;
72
+ readonly site_url: string;
73
+ readonly token_new: string;
74
+ readonly token_hash_new: string;
75
+ };
76
+ };
77
+ output: Record<never, never>;
78
+ };
79
+ send_sms: {
80
+ input: {
81
+ readonly user: HookUser;
82
+ readonly sms: {
83
+ readonly otp: string;
84
+ };
85
+ };
86
+ output: Record<never, never>;
87
+ };
88
+ mfa_verification_attempt: {
89
+ input: {
90
+ readonly factor_id: string;
91
+ readonly factor_type: string;
92
+ readonly user_id: string;
93
+ readonly valid: boolean;
94
+ };
95
+ output: {
96
+ readonly decision: "continue" | "reject";
97
+ readonly message?: string;
98
+ };
99
+ };
100
+ password_verification_attempt: {
101
+ input: {
102
+ readonly user_id: string;
103
+ readonly valid: boolean;
104
+ };
105
+ output: {
106
+ readonly decision: "continue" | "reject";
107
+ readonly message?: string;
108
+ readonly should_logout_user?: boolean;
109
+ };
110
+ };
111
+ before_user_created: {
112
+ input: {
113
+ readonly metadata: Readonly<Record<string, unknown>>;
114
+ readonly user: HookUser;
115
+ };
116
+ output: Record<never, never>;
117
+ };
118
+ }
119
+ export type AuthHookKind = keyof AuthHooks;
120
+ /** Rejects a hook with a status and message Supabase Auth passes to the client. */
121
+ export declare function hookError(status: number, message: string): AuthHookError;
122
+ /**
123
+ * A `(request) => Response` handler for a Supabase Auth hook: verifies the
124
+ * Standard Webhooks signature, types the payload and serializes the answer.
125
+ *
126
+ * ```ts
127
+ * export const POST = authHook('custom_access_token', env.AUTH_HOOK_SECRET, async ({ claims, user_id }) => ({
128
+ * claims: { ...claims, org_id: await orgFor(user_id) },
129
+ * }));
130
+ * ```
131
+ */
132
+ export declare function authHook<K extends AuthHookKind>(_kind: K, secret: string | readonly string[], handler: (payload: AuthHooks[K]["input"], webhook: VerifiedWebhook<AuthHooks[K]["input"]>) => AuthHooks[K]["output"] | AuthHookError | Promise<AuthHooks[K]["output"] | AuthHookError>, options?: VerifyOptions): (request: Request) => Promise<Response>;
133
+ export interface DatabaseWebhookPayload {
134
+ readonly type: "INSERT" | "UPDATE" | "DELETE";
135
+ readonly table: string;
136
+ readonly schema: string;
137
+ readonly record: Readonly<Record<string, unknown>> | null;
138
+ readonly old_record: Readonly<Record<string, unknown>> | null;
139
+ }
140
+ export interface DatabaseChange<R> {
141
+ readonly type: "INSERT" | "UPDATE" | "DELETE";
142
+ readonly table: string;
143
+ readonly record: R | null;
144
+ readonly oldRecord: Partial<R> | null;
145
+ }
146
+ export declare function isDatabaseWebhook(value: unknown): value is DatabaseWebhookPayload;
147
+ /**
148
+ * Reads a database webhook payload for `table` as an app-cased change.
149
+ * Returns `null` for other tables or payloads that aren't database webhooks.
150
+ */
151
+ export declare function databaseChange<M extends AnyModels, D, F extends AnyFunctions, E, T extends TableKey<M>>(sb: BetterSupabase<M, D, F, E>, table: T, payload: unknown): DatabaseChange<Row<M, T>> | null;
152
+ //#endregion
@@ -0,0 +1,2 @@
1
+ import { a as signWebhook, c as verifyWebhook, i as isDatabaseWebhook, n as databaseChange, o as timingSafeEqual, r as hookError, s as verifySharedSecret, t as authHook } from "../webhooks-DbMTJJX2.js";
2
+ export { authHook, databaseChange, hookError, isDatabaseWebhook, signWebhook, timingSafeEqual, verifySharedSecret, verifyWebhook };
@@ -0,0 +1,139 @@
1
+ import { n as dbError } from "./errors-Bl3rkOzo.js";
2
+ import { i as ok, n as err } from "./result-DWXatkd6.js";
3
+ import { s as toApp } from "./shared-_6-uPL7E.js";
4
+ //#region src/webhooks/index.ts
5
+ const encoder = new TextEncoder();
6
+ /** Accepts `whsec_…`, `v1,whsec_…` (Supabase Auth hooks) or raw base64. */
7
+ function secretBytes(secret) {
8
+ const raw = secret.replace(/^v1,/, "").replace(/^whsec_/, "");
9
+ try {
10
+ return Uint8Array.from(atob(raw), (char) => char.charCodeAt(0));
11
+ } catch {
12
+ throw new TypeError("Webhook secret is not valid base64");
13
+ }
14
+ }
15
+ async function signature(secret, content) {
16
+ const key = await crypto.subtle.importKey("raw", secretBytes(secret), {
17
+ name: "HMAC",
18
+ hash: "SHA-256"
19
+ }, false, ["sign"]);
20
+ const mac = new Uint8Array(await crypto.subtle.sign("HMAC", key, encoder.encode(content)));
21
+ let binary = "";
22
+ for (const byte of mac) binary += String.fromCharCode(byte);
23
+ return btoa(binary);
24
+ }
25
+ /** Constant-time string comparison. */
26
+ function timingSafeEqual(a, b) {
27
+ const left = encoder.encode(a);
28
+ const right = encoder.encode(b);
29
+ let diff = left.length ^ right.length;
30
+ for (let index = 0; index < Math.max(left.length, right.length); index++) diff |= (left[index] ?? 0) ^ (right[index] ?? 0);
31
+ return diff === 0;
32
+ }
33
+ function header(headers, name) {
34
+ if (headers instanceof Headers) return headers.get(name);
35
+ return Object.entries(headers).find(([key]) => key.toLowerCase() === name)?.[1] ?? null;
36
+ }
37
+ const unauthorized = (code, message) => dbError("unauthorized", message, { code });
38
+ /** Headers for a signed Standard Webhooks request. */
39
+ async function signWebhook(secret, message) {
40
+ const timestamp = String(Math.floor((message.timestamp ?? /* @__PURE__ */ new Date()).getTime() / 1e3));
41
+ return {
42
+ "webhook-id": message.id,
43
+ "webhook-timestamp": timestamp,
44
+ "webhook-signature": `v1,${await signature(secret, `${message.id}.${timestamp}.${message.body}`)}`
45
+ };
46
+ }
47
+ /**
48
+ * Verifies a Standard Webhooks signature (`webhook-id`, `webhook-timestamp`,
49
+ * `webhook-signature`) and parses the JSON body. Pass several secrets while
50
+ * rotating.
51
+ */
52
+ async function verifyWebhook(input, secrets, options = {}) {
53
+ const headers = input.headers;
54
+ const body = input instanceof Request ? await input.text() : input.body;
55
+ const id = header(headers, "webhook-id");
56
+ const timestamp = header(headers, "webhook-timestamp");
57
+ const signatures = header(headers, "webhook-signature");
58
+ if (!id || !timestamp || !signatures) return err(unauthorized("WEBHOOK_MISSING_HEADERS", "Missing webhook signature headers"));
59
+ const seconds = Number(timestamp);
60
+ if (!/^\d+$/.test(timestamp)) return err(unauthorized("WEBHOOK_INVALID_TIMESTAMP", "Invalid webhook timestamp"));
61
+ const now = (options.now?.() ?? Date.now()) / 1e3;
62
+ const tolerance = options.tolerance ?? 300;
63
+ if (seconds < now - tolerance) return err(unauthorized("WEBHOOK_TIMESTAMP_TOO_OLD", "Webhook timestamp is too old"));
64
+ if (seconds > now + tolerance) return err(unauthorized("WEBHOOK_TIMESTAMP_TOO_NEW", "Webhook timestamp is in the future"));
65
+ const content = `${id}.${timestamp}.${body}`;
66
+ const offered = signatures.split(" ").map((entry) => entry.split(",")).filter(([version, value]) => version === "v1" && value).map(([, value]) => value);
67
+ let valid = false;
68
+ for (const secret of typeof secrets === "string" ? [secrets] : secrets) {
69
+ const expected = await signature(secret, content);
70
+ for (const candidate of offered) if (timingSafeEqual(candidate, expected)) valid = true;
71
+ }
72
+ if (!valid) return err(unauthorized("WEBHOOK_INVALID_SIGNATURE", "Invalid webhook signature"));
73
+ try {
74
+ return ok({
75
+ id,
76
+ timestamp: /* @__PURE__ */ new Date(seconds * 1e3),
77
+ payload: JSON.parse(body),
78
+ body
79
+ });
80
+ } catch {
81
+ return err(dbError("invalid_request", "Webhook body is not JSON"));
82
+ }
83
+ }
84
+ /** Checks a shared-secret header (database webhooks), in constant time. */
85
+ function verifySharedSecret(request, secret, headerName = "authorization") {
86
+ return timingSafeEqual((request.headers.get(headerName) ?? "").replace(/^Bearer\s+/i, ""), secret);
87
+ }
88
+ /** Rejects a hook with a status and message Supabase Auth passes to the client. */
89
+ function hookError(status, message) {
90
+ return { error: {
91
+ http_code: status,
92
+ message
93
+ } };
94
+ }
95
+ /**
96
+ * A `(request) => Response` handler for a Supabase Auth hook: verifies the
97
+ * Standard Webhooks signature, types the payload and serializes the answer.
98
+ *
99
+ * ```ts
100
+ * export const POST = authHook('custom_access_token', env.AUTH_HOOK_SECRET, async ({ claims, user_id }) => ({
101
+ * claims: { ...claims, org_id: await orgFor(user_id) },
102
+ * }));
103
+ * ```
104
+ */
105
+ function authHook(_kind, secret, handler, options) {
106
+ return async (request) => {
107
+ const verified = await verifyWebhook(request, secret, options);
108
+ if (!verified.ok) return Response.json(hookError(verified.error.status, verified.error.message), { status: verified.error.status });
109
+ try {
110
+ const answer = await handler(verified.data.payload, verified.data);
111
+ const status = "error" in answer ? answer.error.http_code : 200;
112
+ return Response.json(answer, { status });
113
+ } catch (cause) {
114
+ console.error("better-supabase: auth hook failed", cause);
115
+ return Response.json(hookError(500, "Hook failed"), { status: 500 });
116
+ }
117
+ };
118
+ }
119
+ function isDatabaseWebhook(value) {
120
+ if (typeof value !== "object" || value === null) return false;
121
+ const payload = value;
122
+ return (payload["type"] === "INSERT" || payload["type"] === "UPDATE" || payload["type"] === "DELETE") && typeof payload["table"] === "string" && typeof payload["schema"] === "string";
123
+ }
124
+ /**
125
+ * Reads a database webhook payload for `table` as an app-cased change.
126
+ * Returns `null` for other tables or payloads that aren't database webhooks.
127
+ */
128
+ function databaseChange(sb, table, payload) {
129
+ const meta = sb.meta.tables[table];
130
+ if (!meta || !isDatabaseWebhook(payload) || payload.table !== meta.name || payload.schema !== meta.schema) return null;
131
+ return {
132
+ type: payload.type,
133
+ table,
134
+ record: payload.record ? toApp(meta, payload.record) : null,
135
+ oldRecord: payload.old_record ? toApp(meta, payload.old_record) : null
136
+ };
137
+ }
138
+ //#endregion
139
+ export { signWebhook as a, verifyWebhook as c, isDatabaseWebhook as i, databaseChange as n, timingSafeEqual as o, hookError as r, verifySharedSecret as s, authHook as t };