@syncello/auth 3.6.1 → 4.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/README.md +193 -0
- package/dist/{chunk-OZ26T3ZA.js → chunk-W35EDOHW.js} +21 -3
- package/dist/chunk-W35EDOHW.js.map +1 -0
- package/dist/cli/index.js +27 -18
- package/dist/index.cjs +1055 -885
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +185 -41
- package/dist/index.d.ts +185 -41
- package/dist/index.js +1053 -911
- package/dist/index.js.map +1 -1
- package/dist/schema/index.cjs +20 -2
- package/dist/schema/index.cjs.map +1 -1
- package/dist/schema/index.d.cts +62 -2
- package/dist/schema/index.d.ts +62 -2
- package/dist/schema/index.js +1 -1
- package/package.json +2 -1
- package/dist/chunk-OZ26T3ZA.js.map +0 -1
package/README.md
CHANGED
|
@@ -14,6 +14,7 @@ Authentication library for Hono + Drizzle + Cloudflare Workers.
|
|
|
14
14
|
- Password reset
|
|
15
15
|
- OAuth support (Google, Microsoft)
|
|
16
16
|
- Mobile PKCE support
|
|
17
|
+
- Passwordless sign-in (emailed link and code)
|
|
17
18
|
|
|
18
19
|
## Installation
|
|
19
20
|
|
|
@@ -157,6 +158,145 @@ const sessionId = await auth.utils.createSession(db, userId);
|
|
|
157
158
|
await auth.utils.deleteSession(db, sessionId);
|
|
158
159
|
```
|
|
159
160
|
|
|
161
|
+
## Passwordless Sign-in
|
|
162
|
+
|
|
163
|
+
An emailed sign-in that carries both a link and a 6-digit code. The link works once, from any
|
|
164
|
+
browser. The code works only in the browser that asked for it, and only alongside that browser's
|
|
165
|
+
secret. Secrets are stored hashed, expire after 10 minutes, and asking again replaces the open
|
|
166
|
+
challenge for that email.
|
|
167
|
+
|
|
168
|
+
The module is table-injected and independent of `createAuth`, so it can sign in any kind of
|
|
169
|
+
account: `subjectId` is your own account's id, and the column has no foreign key.
|
|
170
|
+
`syncello-auth sync` generates the table as `passwordlessChallenges`; to use another table, give it
|
|
171
|
+
these columns:
|
|
172
|
+
|
|
173
|
+
| Column | Type | Notes |
|
|
174
|
+
| ----------------- | -------------------------- | --------------------------------------- |
|
|
175
|
+
| `id` | `uuid` primary key | |
|
|
176
|
+
| `email` | `varchar(255)` not null | lowercased |
|
|
177
|
+
| `subject_id` | `uuid` | your account id; null for a sign-up |
|
|
178
|
+
| `link_token_hash` | `varchar(128)` not null | unique |
|
|
179
|
+
| `code_hash` | `varchar(128)` not null | |
|
|
180
|
+
| `browser_hash` | `varchar(128)` not null | |
|
|
181
|
+
| `attempts` | `integer` not null, 0 | failed code tries |
|
|
182
|
+
| `payload` | `jsonb` | handed back on success |
|
|
183
|
+
| `expires_at` | `bigint` (ms) not null | |
|
|
184
|
+
| `consumed_at` | `bigint` (ms) | |
|
|
185
|
+
| `created_at` | `bigint` (ms) not null | indexed with `email` |
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
import {
|
|
189
|
+
createPasswordlessChallenge,
|
|
190
|
+
verifyPasswordlessLink,
|
|
191
|
+
verifyPasswordlessCode,
|
|
192
|
+
recentPasswordlessFailures,
|
|
193
|
+
passwordlessEmail,
|
|
194
|
+
cleanupExpiredPasswordlessChallenges,
|
|
195
|
+
} from '@syncello/auth';
|
|
196
|
+
|
|
197
|
+
const tables = { passwordlessChallenges };
|
|
198
|
+
|
|
199
|
+
// Start: email the link and code, keep the browser secret in an HttpOnly cookie.
|
|
200
|
+
const { linkToken, code, browserSecret } = await createPasswordlessChallenge(db, tables, {
|
|
201
|
+
email,
|
|
202
|
+
subjectId: account?.id ?? null, // null for a sign-up
|
|
203
|
+
payload: { firstName, phone }, // optional JSON, returned on success (see below)
|
|
204
|
+
ttlMs: 10 * 60 * 1000, // the default
|
|
205
|
+
});
|
|
206
|
+
const mail = passwordlessEmail({
|
|
207
|
+
appName: 'My App',
|
|
208
|
+
appUrl: 'https://example.com', // logo and footer links; the link must be on this host
|
|
209
|
+
linkUrl: `https://www.example.com/sign-in/confirm#t=${linkToken}`,
|
|
210
|
+
code,
|
|
211
|
+
ttlMs: 10 * 60 * 1000, // the default; the email states it in minutes
|
|
212
|
+
});
|
|
213
|
+
await send(email, mail.subject, mail.html, mail.text);
|
|
214
|
+
setCookie(c, 'sign_in_browser', browserSecret, { httpOnly: true, secure: true, sameSite: 'Lax', maxAge: 600 });
|
|
215
|
+
|
|
216
|
+
// The link, from any browser.
|
|
217
|
+
const viaLink = await verifyPasswordlessLink(db, tables, { linkToken });
|
|
218
|
+
|
|
219
|
+
// The code, only with the cookie of the browser that started it.
|
|
220
|
+
if ((await recentPasswordlessFailures(db, tables, { email, windowMs: 60 * 60 * 1000 })) >= 10) {
|
|
221
|
+
// refuse: too many wrong codes for this email
|
|
222
|
+
}
|
|
223
|
+
const viaCode = await verifyPasswordlessCode(db, tables, {
|
|
224
|
+
email,
|
|
225
|
+
code: submitted,
|
|
226
|
+
browserSecret: getCookie(c, 'sign_in_browser') ?? '',
|
|
227
|
+
maxAttempts: 5, // the default
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// Either answers { ok: true, email, subjectId, payload }
|
|
231
|
+
// or { ok: false, reason: 'invalid' | 'expired' } (link) / 'invalid' | 'expired' | 'locked' (code).
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
- **`payload` is chosen by whoever asked for the email, not by the email's owner.** Anyone can
|
|
235
|
+
start a sign-in for any address, so a success proves only that the owner opened the email. Keep
|
|
236
|
+
`payload` to details the requester may set for themselves (sign-up fields such as a name). Never
|
|
237
|
+
put an invitation, a role, a grant or an unchecked redirect in it; look those up server-side
|
|
238
|
+
after sign-in, and check any return path against your own allow-list.
|
|
239
|
+
- A success consumes every open challenge for the email, so the other half of the email stops
|
|
240
|
+
working too.
|
|
241
|
+
- Starting a challenge closes the email's open ones, then inserts the new one; the two writes are
|
|
242
|
+
not one transaction, since the injected `db` may be a driver without them (such as Neon HTTP).
|
|
243
|
+
Two requests at the same instant can leave two challenges open. That is harmless: the code is
|
|
244
|
+
checked against the newest, each link works once, and either success consumes both. Rate-limit
|
|
245
|
+
starting a sign-in per email anyway.
|
|
246
|
+
- A wrong code counts one attempt; at `maxAttempts` the challenge is `locked`, even for the right
|
|
247
|
+
code. A wrong browser secret is answered `invalid` without counting, so a stranger who knows
|
|
248
|
+
the address cannot lock its owner out or learn whether a challenge is expired or locked.
|
|
249
|
+
- `recentPasswordlessFailures` sums attempts across all of an email's challenges in the window,
|
|
250
|
+
so asking for a new code never resets the count. Failures are dated by when their challenge was
|
|
251
|
+
created, so one can leave the window up to one TTL early. The count is keyed by email only:
|
|
252
|
+
pair it with a limit per browser or IP, or a Turnstile check, on starting and on verifying.
|
|
253
|
+
- `passwordlessEmail` puts the code in the subject ("123456 is your sign-in code") and the body,
|
|
254
|
+
and states the lifetime from `ttlMs`. It throws unless `linkUrl` is http(s) on `appUrl`'s host
|
|
255
|
+
or a subdomain of it, or on `localhost` / `127.0.0.1` for local development. A `#` fragment is kept, which keeps the token out of server logs and
|
|
256
|
+
`Referer` headers.
|
|
257
|
+
- Run `cleanupExpiredPasswordlessChallenges(db, tables)` from a scheduled job: it deletes
|
|
258
|
+
challenges that expired more than a day ago.
|
|
259
|
+
|
|
260
|
+
## Invitation-only Sign-up
|
|
261
|
+
|
|
262
|
+
By default anyone can create an account (`signup: 'open'`). Set `signup: 'invitation'` to refuse
|
|
263
|
+
every new account that does not come from an invitation your `invitations.verify` hook accepts
|
|
264
|
+
for that email. `createAuth` throws if `invitations` is missing.
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
const auth = createAuth({
|
|
268
|
+
db,
|
|
269
|
+
schema,
|
|
270
|
+
getEnv: (c) => c.env,
|
|
271
|
+
signup: 'invitation',
|
|
272
|
+
invitations: {
|
|
273
|
+
// Answer the invited email for a live token, or null.
|
|
274
|
+
verify: async ({ token, email, db }) => findLiveInvitation(db, token, email),
|
|
275
|
+
// Link the new user: create the membership, mark the invitation accepted.
|
|
276
|
+
onAccepted: async ({ token, user, db }) => acceptInvitation(db, token, user),
|
|
277
|
+
},
|
|
278
|
+
});
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
| Path | Without a valid invitation | With one |
|
|
282
|
+
| --- | --- | --- |
|
|
283
|
+
| `POST /signup` (password) | `403` Problem JSON, "Sign-up is by invitation only" (or "This invitation is invalid, expired, or for a different email address" when a token was given) | verified account, no verification email, `onAccepted` |
|
|
284
|
+
| OAuth callback, new email (web and mobile) | nothing created; redirect to `${APP_URL}/login?error=invitation_required` | account created, `onAccepted`, usual login code |
|
|
285
|
+
| OAuth callback, existing user | signs in as usual | signs in as usual |
|
|
286
|
+
|
|
287
|
+
Pass the token to `GET /oauth/{provider}/authorize?invitationToken=…` so the callback can check
|
|
288
|
+
it. Refusals are logged as `signup_refused_no_invitation` or
|
|
289
|
+
`oauth_signup_refused_no_invitation` security events.
|
|
290
|
+
|
|
291
|
+
In either mode, a user the OAuth callback creates from a verified invitation gets `onAccepted`.
|
|
292
|
+
It is non-fatal: a failure is logged and the user is still signed in. The login code still
|
|
293
|
+
carries `invitationToken`, so make `onAccepted` and any accept-on-login of your own safe to run
|
|
294
|
+
twice.
|
|
295
|
+
|
|
296
|
+
A `verify` hook that throws is logged and treated as "not invited": invitation mode refuses, an
|
|
297
|
+
OAuth sign-up in open mode goes ahead unlinked, and a password signup with the token answers
|
|
298
|
+
`400`.
|
|
299
|
+
|
|
160
300
|
## Direct Imports
|
|
161
301
|
|
|
162
302
|
For advanced usage, you can import utilities directly:
|
|
@@ -177,6 +317,59 @@ import {
|
|
|
177
317
|
} from '@syncello/auth';
|
|
178
318
|
```
|
|
179
319
|
|
|
320
|
+
## Release Notes
|
|
321
|
+
|
|
322
|
+
### 4.1.0
|
|
323
|
+
|
|
324
|
+
- `emailBranding.marketingUrl`: the site the email footer's Privacy Policy (`/privacy`) and Get
|
|
325
|
+
in Touch (`/contact`) links point at. Without it the footer has no links; they used to point
|
|
326
|
+
at the app, where those pages don't exist.
|
|
327
|
+
- Emails state each link's or code's real lifetime from `AUTH_DEFAULTS`: the password reset
|
|
328
|
+
email said 1 hour for a 15-minute token, and the verification email 48 hours for a 24-hour
|
|
329
|
+
one. `AUTH_DEFAULTS` gains `EMAIL_VERIFICATION_TTL_HOURS`, `EMAIL_CHANGE_TTL_HOURS` and
|
|
330
|
+
`TWO_FACTOR_EMAIL_CODE_TTL_MINUTES`, which the token code now reads too (values unchanged).
|
|
331
|
+
|
|
332
|
+
### 4.0.0
|
|
333
|
+
|
|
334
|
+
**Breaking:** `verifyWebhookSignature` takes all three svix headers. Svix signs
|
|
335
|
+
`<id>.<timestamp>.<body>` and refuses a stale timestamp, but 3.x passed an empty id and
|
|
336
|
+
timestamp, so every genuine Resend delivery was refused.
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
// 3.x
|
|
340
|
+
verifyWebhookSignature(payload, c.req.header('svix-signature'), secret);
|
|
341
|
+
|
|
342
|
+
// 4.0.0
|
|
343
|
+
const headers: SvixHeaders = {
|
|
344
|
+
'svix-id': c.req.header('svix-id') ?? '',
|
|
345
|
+
'svix-timestamp': c.req.header('svix-timestamp') ?? '',
|
|
346
|
+
'svix-signature': c.req.header('svix-signature') ?? '',
|
|
347
|
+
};
|
|
348
|
+
verifyWebhookSignature(payload, headers, secret);
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
**Breaking:** `POST /signup` no longer accepts `oauthToken`. Nothing has written the stored
|
|
352
|
+
OAuth signup state it read since 3.5.0, when the OAuth callback began creating first-time OAuth
|
|
353
|
+
users itself, so the path was unreachable.
|
|
354
|
+
|
|
355
|
+
- Passwordless sign-in (see above), with a `passwordlessChallenges` table and a `jsonb` column
|
|
356
|
+
type in the generated schema (schema version `2026.10.1`). Run `syncello-auth sync` to add the
|
|
357
|
+
table.
|
|
358
|
+
- `signup: 'open' | 'invitation'` on `createAuth` (see Invitation-only Sign-up). The default,
|
|
359
|
+
`'open'`, keeps today's behaviour, with two changes: the OAuth callback now calls
|
|
360
|
+
`invitations.verify` for a new user whose OAuth state carries an `invitationToken`, and
|
|
361
|
+
`onAccepted` when it verifies; and password signup checks an invitation token before looking
|
|
362
|
+
up the email, so a bad token on a registered email answers `400` rather than `409`.
|
|
363
|
+
- The session's sliding window moves at most every 15 minutes
|
|
364
|
+
(`SESSION_REFRESH_INTERVAL_MINUTES`), on the cookie and Bearer paths and in `validateSession`,
|
|
365
|
+
instead of writing the session row on every authenticated request.
|
|
366
|
+
- Emails name the app in the footer (`appName`, HTML-escaped) instead of "App", and
|
|
367
|
+
`EmailBranding.headingColor` sets the heading colour (default `#1e1b4b`, unchanged).
|
|
368
|
+
- `syncello-auth sync` now detects breaking changes it previously missed. Its parser read only the
|
|
369
|
+
first column or two of each table, so removed columns, type changes, shortened `varchar`s and
|
|
370
|
+
new `NOT NULL`s further down went unreported. Expect sync to flag such changes in a schema that
|
|
371
|
+
has drifted.
|
|
372
|
+
|
|
180
373
|
## License
|
|
181
374
|
|
|
182
375
|
UNLICENSED - Syncello Internal Use Only
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/schema/definitions.ts
|
|
2
|
-
var SCHEMA_VERSION = "2026.
|
|
2
|
+
var SCHEMA_VERSION = "2026.10.1";
|
|
3
3
|
var enums = {
|
|
4
4
|
identifierType: {
|
|
5
5
|
name: "identifier_type_enum",
|
|
@@ -237,6 +237,23 @@ var tables = {
|
|
|
237
237
|
{ name: "oauth_accounts_user_provider_idx", columns: ["userId", "provider"], unique: true },
|
|
238
238
|
{ name: "oauth_accounts_provider_user_idx", columns: ["provider", "providerUserId"], unique: true }
|
|
239
239
|
]
|
|
240
|
+
},
|
|
241
|
+
passwordlessChallenges: {
|
|
242
|
+
columns: {
|
|
243
|
+
id: { type: "uuid", primaryKey: true, default: "gen_random_uuid()" },
|
|
244
|
+
email: { type: "varchar", length: 255, notNull: true },
|
|
245
|
+
// The id of whatever account signs in, so no foreign key; null for a sign-up.
|
|
246
|
+
subjectId: { type: "uuid" },
|
|
247
|
+
linkTokenHash: { type: "varchar", length: 128, notNull: true, unique: true },
|
|
248
|
+
codeHash: { type: "varchar", length: 128, notNull: true },
|
|
249
|
+
browserHash: { type: "varchar", length: 128, notNull: true },
|
|
250
|
+
attempts: { type: "integer", notNull: true, default: 0 },
|
|
251
|
+
payload: { type: "jsonb" },
|
|
252
|
+
expiresAt: { type: "bigint", mode: "number", notNull: true },
|
|
253
|
+
consumedAt: { type: "bigint", mode: "number" },
|
|
254
|
+
createdAt: { type: "bigint", mode: "number", notNull: true }
|
|
255
|
+
},
|
|
256
|
+
indexes: [{ name: "idx_passwordless_email_created", columns: ["email", "createdAt"] }]
|
|
240
257
|
}
|
|
241
258
|
};
|
|
242
259
|
var tableNames = {
|
|
@@ -251,7 +268,8 @@ var tableNames = {
|
|
|
251
268
|
emailEvents: "email_events",
|
|
252
269
|
failedLoginAttempts: "failed_login_attempts",
|
|
253
270
|
securityAuditLog: "security_audit_log",
|
|
254
|
-
oauthAccounts: "oauth_accounts"
|
|
271
|
+
oauthAccounts: "oauth_accounts",
|
|
272
|
+
passwordlessChallenges: "passwordless_challenges"
|
|
255
273
|
};
|
|
256
274
|
|
|
257
275
|
export {
|
|
@@ -260,4 +278,4 @@ export {
|
|
|
260
278
|
tables,
|
|
261
279
|
tableNames
|
|
262
280
|
};
|
|
263
|
-
//# sourceMappingURL=chunk-
|
|
281
|
+
//# sourceMappingURL=chunk-W35EDOHW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/schema/definitions.ts"],"sourcesContent":["/**\n * @syncello/auth Schema Definitions\n * Plain TypeScript objects for CLI-based Drizzle code generation\n */\n\nexport const SCHEMA_VERSION = '2026.10.1';\n\n// ====================================\n// TYPE DEFINITIONS\n// ====================================\n\nexport type ColumnType =\n\t| 'uuid'\n\t| 'varchar'\n\t| 'text'\n\t| 'boolean'\n\t| 'integer'\n\t| 'bigint'\n\t| 'timestamp'\n\t| 'json'\n\t| 'jsonb'\n\t| 'serial';\n\nexport type ColumnDefinition = {\n\ttype: ColumnType | string; // string allows enum references\n\tlength?: number;\n\tprimaryKey?: boolean;\n\tnotNull?: boolean;\n\tunique?: boolean;\n\tdefault?: string | number | boolean | null;\n\treferences?: {\n\t\ttable: string;\n\t\tcolumn: string;\n\t\tonDelete?: 'cascade' | 'set null' | 'restrict' | 'no action';\n\t};\n\tmode?: 'number'; // for bigint\n\twithTimezone?: boolean; // for timestamp\n};\n\nexport type IndexDefinition = {\n\tname: string;\n\tcolumns: string[];\n\tunique?: boolean;\n};\n\nexport type TableDefinition = {\n\tcolumns: Record<string, ColumnDefinition>;\n\tindexes?: IndexDefinition[];\n};\n\nexport type EnumDefinition = {\n\tname: string;\n\tvalues: readonly string[];\n};\n\n// ====================================\n// ENUMS\n// ====================================\n\nexport const enums = {\n\tidentifierType: {\n\t\tname: 'identifier_type_enum',\n\t\tvalues: ['email', 'ip'] as const,\n\t},\n\ttwoFactorMethod: {\n\t\tname: 'two_factor_method',\n\t\tvalues: ['totp', 'email'] as const,\n\t},\n\tdeviceType: {\n\t\tname: 'device_type',\n\t\tvalues: ['desktop', 'mobile', 'tablet'] as const,\n\t},\n} as const satisfies Record<string, EnumDefinition>;\n\n// ====================================\n// TABLE DEFINITIONS\n// ====================================\n\nexport const tables = {\n\tusers: {\n\t\tcolumns: {\n\t\t\tid: { type: 'uuid', primaryKey: true, default: 'gen_random_uuid()' },\n\t\t\temail: { type: 'varchar', length: 255, notNull: true, unique: true },\n\t\t\tname: { type: 'text' },\n\t\t\thashedPassword: { type: 'varchar', length: 255 },\n\t\t\tpepperKid: { type: 'varchar', length: 10, default: 'v1' },\n\t\t\temailVerified: { type: 'boolean', default: false },\n\t\t\tisAdmin: { type: 'boolean', default: false, notNull: true },\n\t\t\tsessionVersion: { type: 'integer', default: 1 },\n\t\t\tlockedUntil: { type: 'bigint', mode: 'number' },\n\t\t\tfailedLoginCount: { type: 'integer', default: 0 },\n\t\t\temailBounced: { type: 'boolean', default: false, notNull: true },\n\t\t\temailBouncedAt: { type: 'timestamp', withTimezone: true },\n\t\t\temailComplained: { type: 'boolean', default: false, notNull: true },\n\t\t\temailComplainedAt: { type: 'timestamp', withTimezone: true },\n\t\t\tactivatedAt: { type: 'timestamp', withTimezone: true },\n\t\t\tdeletedAt: { type: 'timestamp', withTimezone: true },\n\t\t\tscheduledPurgeAt: { type: 'timestamp', withTimezone: true },\n\t\t\tcreatedAt: { type: 'timestamp', withTimezone: true, default: 'now()' },\n\t\t\tupdatedAt: { type: 'timestamp', withTimezone: true, default: 'now()' },\n\t\t},\n\t\tindexes: [{ name: 'idx_users_email', columns: ['email'] }],\n\t},\n\n\tsessions: {\n\t\tcolumns: {\n\t\t\tid: { type: 'varchar', length: 128, primaryKey: true },\n\t\t\tuserId: {\n\t\t\t\ttype: 'uuid',\n\t\t\t\tnotNull: true,\n\t\t\t\treferences: { table: 'users', column: 'id', onDelete: 'cascade' },\n\t\t\t},\n\t\t\texpiresAt: { type: 'bigint', mode: 'number', notNull: true },\n\t\t\tcreatedAt: { type: 'bigint', mode: 'number', notNull: true },\n\t\t\tlastActiveAt: { type: 'bigint', mode: 'number' },\n\t\t\tfingerprint: { type: 'varchar', length: 64 },\n\t\t\tipAddress: { type: 'varchar', length: 45 },\n\t\t},\n\t\tindexes: [\n\t\t\t{ name: 'idx_sessions_expires', columns: ['expiresAt'] },\n\t\t\t{ name: 'idx_sessions_user', columns: ['userId'] },\n\t\t],\n\t},\n\n\tuser2faMethods: {\n\t\tcolumns: {\n\t\t\tid: { type: 'uuid', primaryKey: true, default: 'gen_random_uuid()' },\n\t\t\tuserId: {\n\t\t\t\ttype: 'uuid',\n\t\t\t\tnotNull: true,\n\t\t\t\treferences: { table: 'users', column: 'id', onDelete: 'cascade' },\n\t\t\t},\n\t\t\tmethod: { type: 'two_factor_method', notNull: true },\n\t\t\ttotpSecret: { type: 'varchar', length: 256 },\n\t\t\tlastTotpCounter: { type: 'bigint', mode: 'number' },\n\t\t\tisPrimary: { type: 'boolean', default: false, notNull: true },\n\t\t\tverifiedAt: { type: 'bigint', mode: 'number', notNull: true },\n\t\t\tcreatedAt: { type: 'bigint', mode: 'number', notNull: true },\n\t\t},\n\t\tindexes: [\n\t\t\t{ name: 'user_2fa_methods_user_method_idx', columns: ['userId', 'method'], unique: true },\n\t\t\t{ name: 'user_2fa_methods_user_idx', columns: ['userId'] },\n\t\t],\n\t},\n\n\tuserBackupCodes: {\n\t\tcolumns: {\n\t\t\tid: { type: 'uuid', primaryKey: true, default: 'gen_random_uuid()' },\n\t\t\tuserId: {\n\t\t\t\ttype: 'uuid',\n\t\t\t\tnotNull: true,\n\t\t\t\treferences: { table: 'users', column: 'id', onDelete: 'cascade' },\n\t\t\t},\n\t\t\tcodeHash: { type: 'varchar', length: 255, notNull: true },\n\t\t\tusedAt: { type: 'bigint', mode: 'number' },\n\t\t\tcreatedAt: { type: 'bigint', mode: 'number', notNull: true },\n\t\t},\n\t\tindexes: [{ name: 'user_backup_codes_user_idx', columns: ['userId'] }],\n\t},\n\n\tuserTrustedDevices: {\n\t\tcolumns: {\n\t\t\tid: { type: 'uuid', primaryKey: true, default: 'gen_random_uuid()' },\n\t\t\tuserId: {\n\t\t\t\ttype: 'uuid',\n\t\t\t\tnotNull: true,\n\t\t\t\treferences: { table: 'users', column: 'id', onDelete: 'cascade' },\n\t\t\t},\n\t\t\ttokenHash: { type: 'varchar', length: 128, notNull: true },\n\t\t\tdeviceName: { type: 'varchar', length: 255, notNull: true },\n\t\t\tdeviceType: { type: 'device_type', notNull: true, default: 'desktop' },\n\t\t\tipAddress: { type: 'varchar', length: 45, notNull: true },\n\t\t\texpiresAt: { type: 'bigint', mode: 'number', notNull: true },\n\t\t\tlastUsedAt: { type: 'bigint', mode: 'number', notNull: true },\n\t\t\tcreatedAt: { type: 'bigint', mode: 'number', notNull: true },\n\t\t},\n\t\tindexes: [\n\t\t\t{ name: 'user_trusted_devices_user_idx', columns: ['userId'] },\n\t\t\t{ name: 'user_trusted_devices_token_idx', columns: ['tokenHash'] },\n\t\t\t{ name: 'user_trusted_devices_expires_idx', columns: ['expiresAt'] },\n\t\t],\n\t},\n\n\temailVerificationTokens: {\n\t\tcolumns: {\n\t\t\ttokenHash: { type: 'varchar', length: 128, primaryKey: true },\n\t\t\tuserId: {\n\t\t\t\ttype: 'uuid',\n\t\t\t\tnotNull: true,\n\t\t\t\treferences: { table: 'users', column: 'id', onDelete: 'cascade' },\n\t\t\t},\n\t\t\temail: { type: 'varchar', length: 255, notNull: true },\n\t\t\texpiresAt: { type: 'bigint', mode: 'number', notNull: true },\n\t\t\tcreatedAt: { type: 'bigint', mode: 'number', notNull: true },\n\t\t},\n\t\tindexes: [\n\t\t\t{ name: 'idx_email_verification_user', columns: ['userId'] },\n\t\t\t{ name: 'idx_email_verification_expires', columns: ['expiresAt'] },\n\t\t],\n\t},\n\n\tpasswordResetTokens: {\n\t\tcolumns: {\n\t\t\ttokenHash: { type: 'varchar', length: 128, primaryKey: true },\n\t\t\tuserId: {\n\t\t\t\ttype: 'uuid',\n\t\t\t\tnotNull: true,\n\t\t\t\treferences: { table: 'users', column: 'id', onDelete: 'cascade' },\n\t\t\t},\n\t\t\texpiresAt: { type: 'bigint', mode: 'number', notNull: true },\n\t\t\tused: { type: 'boolean', default: false },\n\t\t\tcreatedAt: { type: 'bigint', mode: 'number', notNull: true },\n\t\t},\n\t\tindexes: [\n\t\t\t{ name: 'idx_password_reset_user', columns: ['userId'] },\n\t\t\t{ name: 'idx_password_reset_expires', columns: ['expiresAt'] },\n\t\t],\n\t},\n\n\temailChangeTokens: {\n\t\tcolumns: {\n\t\t\tid: { type: 'uuid', primaryKey: true, default: 'gen_random_uuid()' },\n\t\t\tuserId: {\n\t\t\t\ttype: 'uuid',\n\t\t\t\tnotNull: true,\n\t\t\t\treferences: { table: 'users', column: 'id', onDelete: 'cascade' },\n\t\t\t},\n\t\t\tnewEmail: { type: 'varchar', length: 255, notNull: true },\n\t\t\ttokenHash: { type: 'varchar', length: 128, notNull: true },\n\t\t\tcancelTokenHash: { type: 'varchar', length: 128, notNull: true },\n\t\t\texpiresAt: { type: 'bigint', mode: 'number', notNull: true },\n\t\t\tcreatedAt: { type: 'bigint', mode: 'number', notNull: true },\n\t\t},\n\t\tindexes: [\n\t\t\t{ name: 'idx_email_change_tokens_user', columns: ['userId'], unique: true },\n\t\t\t{ name: 'idx_email_change_tokens_token', columns: ['tokenHash'] },\n\t\t\t{ name: 'idx_email_change_tokens_cancel', columns: ['cancelTokenHash'] },\n\t\t\t{ name: 'idx_email_change_tokens_expires', columns: ['expiresAt'] },\n\t\t],\n\t},\n\n\temailEvents: {\n\t\tcolumns: {\n\t\t\tid: { type: 'uuid', primaryKey: true, default: 'gen_random_uuid()' },\n\t\t\temailId: { type: 'text', notNull: true },\n\t\t\tuserId: {\n\t\t\t\ttype: 'uuid',\n\t\t\t\treferences: { table: 'users', column: 'id', onDelete: 'cascade' },\n\t\t\t},\n\t\t\teventType: { type: 'text', notNull: true },\n\t\t\temailAddress: { type: 'text', notNull: true },\n\t\t\tmetadata: { type: 'json' },\n\t\t\tcreatedAt: { type: 'timestamp', withTimezone: true, default: 'now()', notNull: true },\n\t\t},\n\t\tindexes: [\n\t\t\t{ name: 'email_events_email_address_idx', columns: ['emailAddress'] },\n\t\t\t{ name: 'email_events_user_id_idx', columns: ['userId'] },\n\t\t\t{ name: 'email_events_email_id_event_type_idx', columns: ['emailId', 'eventType'], unique: true },\n\t\t],\n\t},\n\n\tfailedLoginAttempts: {\n\t\tcolumns: {\n\t\t\tid: { type: 'serial', primaryKey: true },\n\t\t\tidentifier: { type: 'varchar', length: 255, notNull: true },\n\t\t\tidentifierType: { type: 'identifier_type_enum', notNull: true },\n\t\t\tattemptedAt: { type: 'bigint', mode: 'number', notNull: true },\n\t\t},\n\t\tindexes: [\n\t\t\t{ name: 'idx_failed_attempts_identifier', columns: ['identifier', 'attemptedAt'] },\n\t\t\t{ name: 'idx_failed_attempts_time', columns: ['attemptedAt'] },\n\t\t],\n\t},\n\n\tsecurityAuditLog: {\n\t\tcolumns: {\n\t\t\tid: { type: 'serial', primaryKey: true },\n\t\t\tuserId: {\n\t\t\t\ttype: 'uuid',\n\t\t\t\treferences: { table: 'users', column: 'id', onDelete: 'set null' },\n\t\t\t},\n\t\t\teventType: { type: 'varchar', length: 50, notNull: true },\n\t\t\teventData: { type: 'json' },\n\t\t\tipAddress: { type: 'varchar', length: 45 },\n\t\t\tuserAgent: { type: 'text' },\n\t\t\tsuccess: { type: 'boolean', notNull: true },\n\t\t\tcreatedAt: { type: 'timestamp', withTimezone: true, default: 'now()' },\n\t\t},\n\t\tindexes: [\n\t\t\t{ name: 'idx_audit_user', columns: ['userId'] },\n\t\t\t{ name: 'idx_audit_event', columns: ['eventType'] },\n\t\t\t{ name: 'idx_audit_created', columns: ['createdAt'] },\n\t\t],\n\t},\n\n\toauthAccounts: {\n\t\tcolumns: {\n\t\t\tid: { type: 'uuid', primaryKey: true, default: 'gen_random_uuid()' },\n\t\t\tuserId: {\n\t\t\t\ttype: 'uuid',\n\t\t\t\tnotNull: true,\n\t\t\t\treferences: { table: 'users', column: 'id', onDelete: 'cascade' },\n\t\t\t},\n\t\t\tprovider: { type: 'varchar', length: 50, notNull: true },\n\t\t\tproviderUserId: { type: 'varchar', length: 255, notNull: true },\n\t\t\temail: { type: 'varchar', length: 255, notNull: true },\n\t\t\tcreatedAt: { type: 'timestamp', withTimezone: true, default: 'now()', notNull: true },\n\t\t},\n\t\tindexes: [\n\t\t\t{ name: 'oauth_accounts_user_provider_idx', columns: ['userId', 'provider'], unique: true },\n\t\t\t{ name: 'oauth_accounts_provider_user_idx', columns: ['provider', 'providerUserId'], unique: true },\n\t\t],\n\t},\n\n\tpasswordlessChallenges: {\n\t\tcolumns: {\n\t\t\tid: { type: 'uuid', primaryKey: true, default: 'gen_random_uuid()' },\n\t\t\temail: { type: 'varchar', length: 255, notNull: true },\n\t\t\t// The id of whatever account signs in, so no foreign key; null for a sign-up.\n\t\t\tsubjectId: { type: 'uuid' },\n\t\t\tlinkTokenHash: { type: 'varchar', length: 128, notNull: true, unique: true },\n\t\t\tcodeHash: { type: 'varchar', length: 128, notNull: true },\n\t\t\tbrowserHash: { type: 'varchar', length: 128, notNull: true },\n\t\t\tattempts: { type: 'integer', notNull: true, default: 0 },\n\t\t\tpayload: { type: 'jsonb' },\n\t\t\texpiresAt: { type: 'bigint', mode: 'number', notNull: true },\n\t\t\tconsumedAt: { type: 'bigint', mode: 'number' },\n\t\t\tcreatedAt: { type: 'bigint', mode: 'number', notNull: true },\n\t\t},\n\t\tindexes: [{ name: 'idx_passwordless_email_created', columns: ['email', 'createdAt'] }],\n\t},\n} as const satisfies Record<string, TableDefinition>;\n\n// ====================================\n// TABLE NAME MAPPING\n// ====================================\n\nexport const tableNames = {\n\tusers: 'users',\n\tsessions: 'sessions',\n\tuser2faMethods: 'user_2fa_methods',\n\tuserBackupCodes: 'user_backup_codes',\n\tuserTrustedDevices: 'user_trusted_devices',\n\temailVerificationTokens: 'email_verification_tokens',\n\tpasswordResetTokens: 'password_reset_tokens',\n\temailChangeTokens: 'email_change_tokens',\n\temailEvents: 'email_events',\n\tfailedLoginAttempts: 'failed_login_attempts',\n\tsecurityAuditLog: 'security_audit_log',\n\toauthAccounts: 'oauth_accounts',\n\tpasswordlessChallenges: 'passwordless_challenges',\n} as const;\n\nexport type TableName = keyof typeof tables;\nexport type EnumName = keyof typeof enums;\n"],"mappings":";AAKO,IAAM,iBAAiB;AAsDvB,IAAM,QAAQ;AAAA,EACpB,gBAAgB;AAAA,IACf,MAAM;AAAA,IACN,QAAQ,CAAC,SAAS,IAAI;AAAA,EACvB;AAAA,EACA,iBAAiB;AAAA,IAChB,MAAM;AAAA,IACN,QAAQ,CAAC,QAAQ,OAAO;AAAA,EACzB;AAAA,EACA,YAAY;AAAA,IACX,MAAM;AAAA,IACN,QAAQ,CAAC,WAAW,UAAU,QAAQ;AAAA,EACvC;AACD;AAMO,IAAM,SAAS;AAAA,EACrB,OAAO;AAAA,IACN,SAAS;AAAA,MACR,IAAI,EAAE,MAAM,QAAQ,YAAY,MAAM,SAAS,oBAAoB;AAAA,MACnE,OAAO,EAAE,MAAM,WAAW,QAAQ,KAAK,SAAS,MAAM,QAAQ,KAAK;AAAA,MACnE,MAAM,EAAE,MAAM,OAAO;AAAA,MACrB,gBAAgB,EAAE,MAAM,WAAW,QAAQ,IAAI;AAAA,MAC/C,WAAW,EAAE,MAAM,WAAW,QAAQ,IAAI,SAAS,KAAK;AAAA,MACxD,eAAe,EAAE,MAAM,WAAW,SAAS,MAAM;AAAA,MACjD,SAAS,EAAE,MAAM,WAAW,SAAS,OAAO,SAAS,KAAK;AAAA,MAC1D,gBAAgB,EAAE,MAAM,WAAW,SAAS,EAAE;AAAA,MAC9C,aAAa,EAAE,MAAM,UAAU,MAAM,SAAS;AAAA,MAC9C,kBAAkB,EAAE,MAAM,WAAW,SAAS,EAAE;AAAA,MAChD,cAAc,EAAE,MAAM,WAAW,SAAS,OAAO,SAAS,KAAK;AAAA,MAC/D,gBAAgB,EAAE,MAAM,aAAa,cAAc,KAAK;AAAA,MACxD,iBAAiB,EAAE,MAAM,WAAW,SAAS,OAAO,SAAS,KAAK;AAAA,MAClE,mBAAmB,EAAE,MAAM,aAAa,cAAc,KAAK;AAAA,MAC3D,aAAa,EAAE,MAAM,aAAa,cAAc,KAAK;AAAA,MACrD,WAAW,EAAE,MAAM,aAAa,cAAc,KAAK;AAAA,MACnD,kBAAkB,EAAE,MAAM,aAAa,cAAc,KAAK;AAAA,MAC1D,WAAW,EAAE,MAAM,aAAa,cAAc,MAAM,SAAS,QAAQ;AAAA,MACrE,WAAW,EAAE,MAAM,aAAa,cAAc,MAAM,SAAS,QAAQ;AAAA,IACtE;AAAA,IACA,SAAS,CAAC,EAAE,MAAM,mBAAmB,SAAS,CAAC,OAAO,EAAE,CAAC;AAAA,EAC1D;AAAA,EAEA,UAAU;AAAA,IACT,SAAS;AAAA,MACR,IAAI,EAAE,MAAM,WAAW,QAAQ,KAAK,YAAY,KAAK;AAAA,MACrD,QAAQ;AAAA,QACP,MAAM;AAAA,QACN,SAAS;AAAA,QACT,YAAY,EAAE,OAAO,SAAS,QAAQ,MAAM,UAAU,UAAU;AAAA,MACjE;AAAA,MACA,WAAW,EAAE,MAAM,UAAU,MAAM,UAAU,SAAS,KAAK;AAAA,MAC3D,WAAW,EAAE,MAAM,UAAU,MAAM,UAAU,SAAS,KAAK;AAAA,MAC3D,cAAc,EAAE,MAAM,UAAU,MAAM,SAAS;AAAA,MAC/C,aAAa,EAAE,MAAM,WAAW,QAAQ,GAAG;AAAA,MAC3C,WAAW,EAAE,MAAM,WAAW,QAAQ,GAAG;AAAA,IAC1C;AAAA,IACA,SAAS;AAAA,MACR,EAAE,MAAM,wBAAwB,SAAS,CAAC,WAAW,EAAE;AAAA,MACvD,EAAE,MAAM,qBAAqB,SAAS,CAAC,QAAQ,EAAE;AAAA,IAClD;AAAA,EACD;AAAA,EAEA,gBAAgB;AAAA,IACf,SAAS;AAAA,MACR,IAAI,EAAE,MAAM,QAAQ,YAAY,MAAM,SAAS,oBAAoB;AAAA,MACnE,QAAQ;AAAA,QACP,MAAM;AAAA,QACN,SAAS;AAAA,QACT,YAAY,EAAE,OAAO,SAAS,QAAQ,MAAM,UAAU,UAAU;AAAA,MACjE;AAAA,MACA,QAAQ,EAAE,MAAM,qBAAqB,SAAS,KAAK;AAAA,MACnD,YAAY,EAAE,MAAM,WAAW,QAAQ,IAAI;AAAA,MAC3C,iBAAiB,EAAE,MAAM,UAAU,MAAM,SAAS;AAAA,MAClD,WAAW,EAAE,MAAM,WAAW,SAAS,OAAO,SAAS,KAAK;AAAA,MAC5D,YAAY,EAAE,MAAM,UAAU,MAAM,UAAU,SAAS,KAAK;AAAA,MAC5D,WAAW,EAAE,MAAM,UAAU,MAAM,UAAU,SAAS,KAAK;AAAA,IAC5D;AAAA,IACA,SAAS;AAAA,MACR,EAAE,MAAM,oCAAoC,SAAS,CAAC,UAAU,QAAQ,GAAG,QAAQ,KAAK;AAAA,MACxF,EAAE,MAAM,6BAA6B,SAAS,CAAC,QAAQ,EAAE;AAAA,IAC1D;AAAA,EACD;AAAA,EAEA,iBAAiB;AAAA,IAChB,SAAS;AAAA,MACR,IAAI,EAAE,MAAM,QAAQ,YAAY,MAAM,SAAS,oBAAoB;AAAA,MACnE,QAAQ;AAAA,QACP,MAAM;AAAA,QACN,SAAS;AAAA,QACT,YAAY,EAAE,OAAO,SAAS,QAAQ,MAAM,UAAU,UAAU;AAAA,MACjE;AAAA,MACA,UAAU,EAAE,MAAM,WAAW,QAAQ,KAAK,SAAS,KAAK;AAAA,MACxD,QAAQ,EAAE,MAAM,UAAU,MAAM,SAAS;AAAA,MACzC,WAAW,EAAE,MAAM,UAAU,MAAM,UAAU,SAAS,KAAK;AAAA,IAC5D;AAAA,IACA,SAAS,CAAC,EAAE,MAAM,8BAA8B,SAAS,CAAC,QAAQ,EAAE,CAAC;AAAA,EACtE;AAAA,EAEA,oBAAoB;AAAA,IACnB,SAAS;AAAA,MACR,IAAI,EAAE,MAAM,QAAQ,YAAY,MAAM,SAAS,oBAAoB;AAAA,MACnE,QAAQ;AAAA,QACP,MAAM;AAAA,QACN,SAAS;AAAA,QACT,YAAY,EAAE,OAAO,SAAS,QAAQ,MAAM,UAAU,UAAU;AAAA,MACjE;AAAA,MACA,WAAW,EAAE,MAAM,WAAW,QAAQ,KAAK,SAAS,KAAK;AAAA,MACzD,YAAY,EAAE,MAAM,WAAW,QAAQ,KAAK,SAAS,KAAK;AAAA,MAC1D,YAAY,EAAE,MAAM,eAAe,SAAS,MAAM,SAAS,UAAU;AAAA,MACrE,WAAW,EAAE,MAAM,WAAW,QAAQ,IAAI,SAAS,KAAK;AAAA,MACxD,WAAW,EAAE,MAAM,UAAU,MAAM,UAAU,SAAS,KAAK;AAAA,MAC3D,YAAY,EAAE,MAAM,UAAU,MAAM,UAAU,SAAS,KAAK;AAAA,MAC5D,WAAW,EAAE,MAAM,UAAU,MAAM,UAAU,SAAS,KAAK;AAAA,IAC5D;AAAA,IACA,SAAS;AAAA,MACR,EAAE,MAAM,iCAAiC,SAAS,CAAC,QAAQ,EAAE;AAAA,MAC7D,EAAE,MAAM,kCAAkC,SAAS,CAAC,WAAW,EAAE;AAAA,MACjE,EAAE,MAAM,oCAAoC,SAAS,CAAC,WAAW,EAAE;AAAA,IACpE;AAAA,EACD;AAAA,EAEA,yBAAyB;AAAA,IACxB,SAAS;AAAA,MACR,WAAW,EAAE,MAAM,WAAW,QAAQ,KAAK,YAAY,KAAK;AAAA,MAC5D,QAAQ;AAAA,QACP,MAAM;AAAA,QACN,SAAS;AAAA,QACT,YAAY,EAAE,OAAO,SAAS,QAAQ,MAAM,UAAU,UAAU;AAAA,MACjE;AAAA,MACA,OAAO,EAAE,MAAM,WAAW,QAAQ,KAAK,SAAS,KAAK;AAAA,MACrD,WAAW,EAAE,MAAM,UAAU,MAAM,UAAU,SAAS,KAAK;AAAA,MAC3D,WAAW,EAAE,MAAM,UAAU,MAAM,UAAU,SAAS,KAAK;AAAA,IAC5D;AAAA,IACA,SAAS;AAAA,MACR,EAAE,MAAM,+BAA+B,SAAS,CAAC,QAAQ,EAAE;AAAA,MAC3D,EAAE,MAAM,kCAAkC,SAAS,CAAC,WAAW,EAAE;AAAA,IAClE;AAAA,EACD;AAAA,EAEA,qBAAqB;AAAA,IACpB,SAAS;AAAA,MACR,WAAW,EAAE,MAAM,WAAW,QAAQ,KAAK,YAAY,KAAK;AAAA,MAC5D,QAAQ;AAAA,QACP,MAAM;AAAA,QACN,SAAS;AAAA,QACT,YAAY,EAAE,OAAO,SAAS,QAAQ,MAAM,UAAU,UAAU;AAAA,MACjE;AAAA,MACA,WAAW,EAAE,MAAM,UAAU,MAAM,UAAU,SAAS,KAAK;AAAA,MAC3D,MAAM,EAAE,MAAM,WAAW,SAAS,MAAM;AAAA,MACxC,WAAW,EAAE,MAAM,UAAU,MAAM,UAAU,SAAS,KAAK;AAAA,IAC5D;AAAA,IACA,SAAS;AAAA,MACR,EAAE,MAAM,2BAA2B,SAAS,CAAC,QAAQ,EAAE;AAAA,MACvD,EAAE,MAAM,8BAA8B,SAAS,CAAC,WAAW,EAAE;AAAA,IAC9D;AAAA,EACD;AAAA,EAEA,mBAAmB;AAAA,IAClB,SAAS;AAAA,MACR,IAAI,EAAE,MAAM,QAAQ,YAAY,MAAM,SAAS,oBAAoB;AAAA,MACnE,QAAQ;AAAA,QACP,MAAM;AAAA,QACN,SAAS;AAAA,QACT,YAAY,EAAE,OAAO,SAAS,QAAQ,MAAM,UAAU,UAAU;AAAA,MACjE;AAAA,MACA,UAAU,EAAE,MAAM,WAAW,QAAQ,KAAK,SAAS,KAAK;AAAA,MACxD,WAAW,EAAE,MAAM,WAAW,QAAQ,KAAK,SAAS,KAAK;AAAA,MACzD,iBAAiB,EAAE,MAAM,WAAW,QAAQ,KAAK,SAAS,KAAK;AAAA,MAC/D,WAAW,EAAE,MAAM,UAAU,MAAM,UAAU,SAAS,KAAK;AAAA,MAC3D,WAAW,EAAE,MAAM,UAAU,MAAM,UAAU,SAAS,KAAK;AAAA,IAC5D;AAAA,IACA,SAAS;AAAA,MACR,EAAE,MAAM,gCAAgC,SAAS,CAAC,QAAQ,GAAG,QAAQ,KAAK;AAAA,MAC1E,EAAE,MAAM,iCAAiC,SAAS,CAAC,WAAW,EAAE;AAAA,MAChE,EAAE,MAAM,kCAAkC,SAAS,CAAC,iBAAiB,EAAE;AAAA,MACvE,EAAE,MAAM,mCAAmC,SAAS,CAAC,WAAW,EAAE;AAAA,IACnE;AAAA,EACD;AAAA,EAEA,aAAa;AAAA,IACZ,SAAS;AAAA,MACR,IAAI,EAAE,MAAM,QAAQ,YAAY,MAAM,SAAS,oBAAoB;AAAA,MACnE,SAAS,EAAE,MAAM,QAAQ,SAAS,KAAK;AAAA,MACvC,QAAQ;AAAA,QACP,MAAM;AAAA,QACN,YAAY,EAAE,OAAO,SAAS,QAAQ,MAAM,UAAU,UAAU;AAAA,MACjE;AAAA,MACA,WAAW,EAAE,MAAM,QAAQ,SAAS,KAAK;AAAA,MACzC,cAAc,EAAE,MAAM,QAAQ,SAAS,KAAK;AAAA,MAC5C,UAAU,EAAE,MAAM,OAAO;AAAA,MACzB,WAAW,EAAE,MAAM,aAAa,cAAc,MAAM,SAAS,SAAS,SAAS,KAAK;AAAA,IACrF;AAAA,IACA,SAAS;AAAA,MACR,EAAE,MAAM,kCAAkC,SAAS,CAAC,cAAc,EAAE;AAAA,MACpE,EAAE,MAAM,4BAA4B,SAAS,CAAC,QAAQ,EAAE;AAAA,MACxD,EAAE,MAAM,wCAAwC,SAAS,CAAC,WAAW,WAAW,GAAG,QAAQ,KAAK;AAAA,IACjG;AAAA,EACD;AAAA,EAEA,qBAAqB;AAAA,IACpB,SAAS;AAAA,MACR,IAAI,EAAE,MAAM,UAAU,YAAY,KAAK;AAAA,MACvC,YAAY,EAAE,MAAM,WAAW,QAAQ,KAAK,SAAS,KAAK;AAAA,MAC1D,gBAAgB,EAAE,MAAM,wBAAwB,SAAS,KAAK;AAAA,MAC9D,aAAa,EAAE,MAAM,UAAU,MAAM,UAAU,SAAS,KAAK;AAAA,IAC9D;AAAA,IACA,SAAS;AAAA,MACR,EAAE,MAAM,kCAAkC,SAAS,CAAC,cAAc,aAAa,EAAE;AAAA,MACjF,EAAE,MAAM,4BAA4B,SAAS,CAAC,aAAa,EAAE;AAAA,IAC9D;AAAA,EACD;AAAA,EAEA,kBAAkB;AAAA,IACjB,SAAS;AAAA,MACR,IAAI,EAAE,MAAM,UAAU,YAAY,KAAK;AAAA,MACvC,QAAQ;AAAA,QACP,MAAM;AAAA,QACN,YAAY,EAAE,OAAO,SAAS,QAAQ,MAAM,UAAU,WAAW;AAAA,MAClE;AAAA,MACA,WAAW,EAAE,MAAM,WAAW,QAAQ,IAAI,SAAS,KAAK;AAAA,MACxD,WAAW,EAAE,MAAM,OAAO;AAAA,MAC1B,WAAW,EAAE,MAAM,WAAW,QAAQ,GAAG;AAAA,MACzC,WAAW,EAAE,MAAM,OAAO;AAAA,MAC1B,SAAS,EAAE,MAAM,WAAW,SAAS,KAAK;AAAA,MAC1C,WAAW,EAAE,MAAM,aAAa,cAAc,MAAM,SAAS,QAAQ;AAAA,IACtE;AAAA,IACA,SAAS;AAAA,MACR,EAAE,MAAM,kBAAkB,SAAS,CAAC,QAAQ,EAAE;AAAA,MAC9C,EAAE,MAAM,mBAAmB,SAAS,CAAC,WAAW,EAAE;AAAA,MAClD,EAAE,MAAM,qBAAqB,SAAS,CAAC,WAAW,EAAE;AAAA,IACrD;AAAA,EACD;AAAA,EAEA,eAAe;AAAA,IACd,SAAS;AAAA,MACR,IAAI,EAAE,MAAM,QAAQ,YAAY,MAAM,SAAS,oBAAoB;AAAA,MACnE,QAAQ;AAAA,QACP,MAAM;AAAA,QACN,SAAS;AAAA,QACT,YAAY,EAAE,OAAO,SAAS,QAAQ,MAAM,UAAU,UAAU;AAAA,MACjE;AAAA,MACA,UAAU,EAAE,MAAM,WAAW,QAAQ,IAAI,SAAS,KAAK;AAAA,MACvD,gBAAgB,EAAE,MAAM,WAAW,QAAQ,KAAK,SAAS,KAAK;AAAA,MAC9D,OAAO,EAAE,MAAM,WAAW,QAAQ,KAAK,SAAS,KAAK;AAAA,MACrD,WAAW,EAAE,MAAM,aAAa,cAAc,MAAM,SAAS,SAAS,SAAS,KAAK;AAAA,IACrF;AAAA,IACA,SAAS;AAAA,MACR,EAAE,MAAM,oCAAoC,SAAS,CAAC,UAAU,UAAU,GAAG,QAAQ,KAAK;AAAA,MAC1F,EAAE,MAAM,oCAAoC,SAAS,CAAC,YAAY,gBAAgB,GAAG,QAAQ,KAAK;AAAA,IACnG;AAAA,EACD;AAAA,EAEA,wBAAwB;AAAA,IACvB,SAAS;AAAA,MACR,IAAI,EAAE,MAAM,QAAQ,YAAY,MAAM,SAAS,oBAAoB;AAAA,MACnE,OAAO,EAAE,MAAM,WAAW,QAAQ,KAAK,SAAS,KAAK;AAAA;AAAA,MAErD,WAAW,EAAE,MAAM,OAAO;AAAA,MAC1B,eAAe,EAAE,MAAM,WAAW,QAAQ,KAAK,SAAS,MAAM,QAAQ,KAAK;AAAA,MAC3E,UAAU,EAAE,MAAM,WAAW,QAAQ,KAAK,SAAS,KAAK;AAAA,MACxD,aAAa,EAAE,MAAM,WAAW,QAAQ,KAAK,SAAS,KAAK;AAAA,MAC3D,UAAU,EAAE,MAAM,WAAW,SAAS,MAAM,SAAS,EAAE;AAAA,MACvD,SAAS,EAAE,MAAM,QAAQ;AAAA,MACzB,WAAW,EAAE,MAAM,UAAU,MAAM,UAAU,SAAS,KAAK;AAAA,MAC3D,YAAY,EAAE,MAAM,UAAU,MAAM,SAAS;AAAA,MAC7C,WAAW,EAAE,MAAM,UAAU,MAAM,UAAU,SAAS,KAAK;AAAA,IAC5D;AAAA,IACA,SAAS,CAAC,EAAE,MAAM,kCAAkC,SAAS,CAAC,SAAS,WAAW,EAAE,CAAC;AAAA,EACtF;AACD;AAMO,IAAM,aAAa;AAAA,EACzB,OAAO;AAAA,EACP,UAAU;AAAA,EACV,gBAAgB;AAAA,EAChB,iBAAiB;AAAA,EACjB,oBAAoB;AAAA,EACpB,yBAAyB;AAAA,EACzB,qBAAqB;AAAA,EACrB,mBAAmB;AAAA,EACnB,aAAa;AAAA,EACb,qBAAqB;AAAA,EACrB,kBAAkB;AAAA,EAClB,eAAe;AAAA,EACf,wBAAwB;AACzB;","names":[]}
|
package/dist/cli/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import * as fs3 from "fs";
|
|
|
8
8
|
import * as path2 from "path";
|
|
9
9
|
|
|
10
10
|
// src/schema/definitions.ts
|
|
11
|
-
var SCHEMA_VERSION = "2026.
|
|
11
|
+
var SCHEMA_VERSION = "2026.10.1";
|
|
12
12
|
var enums = {
|
|
13
13
|
identifierType: {
|
|
14
14
|
name: "identifier_type_enum",
|
|
@@ -246,6 +246,23 @@ var tables = {
|
|
|
246
246
|
{ name: "oauth_accounts_user_provider_idx", columns: ["userId", "provider"], unique: true },
|
|
247
247
|
{ name: "oauth_accounts_provider_user_idx", columns: ["provider", "providerUserId"], unique: true }
|
|
248
248
|
]
|
|
249
|
+
},
|
|
250
|
+
passwordlessChallenges: {
|
|
251
|
+
columns: {
|
|
252
|
+
id: { type: "uuid", primaryKey: true, default: "gen_random_uuid()" },
|
|
253
|
+
email: { type: "varchar", length: 255, notNull: true },
|
|
254
|
+
// The id of whatever account signs in, so no foreign key; null for a sign-up.
|
|
255
|
+
subjectId: { type: "uuid" },
|
|
256
|
+
linkTokenHash: { type: "varchar", length: 128, notNull: true, unique: true },
|
|
257
|
+
codeHash: { type: "varchar", length: 128, notNull: true },
|
|
258
|
+
browserHash: { type: "varchar", length: 128, notNull: true },
|
|
259
|
+
attempts: { type: "integer", notNull: true, default: 0 },
|
|
260
|
+
payload: { type: "jsonb" },
|
|
261
|
+
expiresAt: { type: "bigint", mode: "number", notNull: true },
|
|
262
|
+
consumedAt: { type: "bigint", mode: "number" },
|
|
263
|
+
createdAt: { type: "bigint", mode: "number", notNull: true }
|
|
264
|
+
},
|
|
265
|
+
indexes: [{ name: "idx_passwordless_email_created", columns: ["email", "createdAt"] }]
|
|
249
266
|
}
|
|
250
267
|
};
|
|
251
268
|
var tableNames = {
|
|
@@ -260,7 +277,8 @@ var tableNames = {
|
|
|
260
277
|
emailEvents: "email_events",
|
|
261
278
|
failedLoginAttempts: "failed_login_attempts",
|
|
262
279
|
securityAuditLog: "security_audit_log",
|
|
263
|
-
oauthAccounts: "oauth_accounts"
|
|
280
|
+
oauthAccounts: "oauth_accounts",
|
|
281
|
+
passwordlessChallenges: "passwordless_challenges"
|
|
264
282
|
};
|
|
265
283
|
|
|
266
284
|
// src/schema/generator.ts
|
|
@@ -312,6 +330,9 @@ function generateColumnCode(name, col) {
|
|
|
312
330
|
case "json":
|
|
313
331
|
parts.push(`json('${snakeName}')`);
|
|
314
332
|
break;
|
|
333
|
+
case "jsonb":
|
|
334
|
+
parts.push(`jsonb('${snakeName}')`);
|
|
335
|
+
break;
|
|
315
336
|
case "serial":
|
|
316
337
|
parts.push(`serial('${snakeName}')`);
|
|
317
338
|
break;
|
|
@@ -461,6 +482,7 @@ function generateSchemaFile() {
|
|
|
461
482
|
lines.push(" bigint,");
|
|
462
483
|
lines.push(" timestamp,");
|
|
463
484
|
lines.push(" json,");
|
|
485
|
+
lines.push(" jsonb,");
|
|
464
486
|
lines.push(" serial,");
|
|
465
487
|
lines.push(" index,");
|
|
466
488
|
lines.push(" uniqueIndex,");
|
|
@@ -753,7 +775,7 @@ function parseGeneratedSchema(content) {
|
|
|
753
775
|
const values = valuesStr.match(/'([^']+)'/g)?.map((v) => v.replace(/'/g, "")) || [];
|
|
754
776
|
result.enums[enumName] = { values };
|
|
755
777
|
}
|
|
756
|
-
const tableRegex = /export const (\w+) = pgTable\(\s*'(\w+)',\s*\{([^}]
|
|
778
|
+
const tableRegex = /export const (\w+) = pgTable\(\s*'(\w+)',\s*\{([^{}]*(?:\{[^{}]*\}[^{}]*)*)\}/gs;
|
|
757
779
|
let tableMatch;
|
|
758
780
|
while ((tableMatch = tableRegex.exec(content)) !== null) {
|
|
759
781
|
const tableName = tableMatch[2];
|
|
@@ -766,7 +788,7 @@ function parseColumns(block) {
|
|
|
766
788
|
const columns = {};
|
|
767
789
|
const lines = block.split("\n");
|
|
768
790
|
for (const line of lines) {
|
|
769
|
-
const colMatch = line.match(/(\w+):\s*(uuid|varchar|text|boolean|integer|bigint|timestamp|json|serial)\(/);
|
|
791
|
+
const colMatch = line.match(/(\w+):\s*(uuid|varchar|text|boolean|integer|bigint|timestamp|jsonb|json|serial)\(/);
|
|
770
792
|
if (colMatch) {
|
|
771
793
|
const colName = colMatch[1];
|
|
772
794
|
const colType = colMatch[2];
|
|
@@ -886,20 +908,7 @@ function detectBreakingChanges(oldSchemaContent) {
|
|
|
886
908
|
};
|
|
887
909
|
}
|
|
888
910
|
function getTableNameMapping() {
|
|
889
|
-
return
|
|
890
|
-
users: "users",
|
|
891
|
-
sessions: "sessions",
|
|
892
|
-
user_2fa_methods: "user2faMethods",
|
|
893
|
-
user_backup_codes: "userBackupCodes",
|
|
894
|
-
user_trusted_devices: "userTrustedDevices",
|
|
895
|
-
email_verification_tokens: "emailVerificationTokens",
|
|
896
|
-
password_reset_tokens: "passwordResetTokens",
|
|
897
|
-
email_change_tokens: "emailChangeTokens",
|
|
898
|
-
email_events: "emailEvents",
|
|
899
|
-
failed_login_attempts: "failedLoginAttempts",
|
|
900
|
-
security_audit_log: "securityAuditLog",
|
|
901
|
-
oauth_accounts: "oauthAccounts"
|
|
902
|
-
};
|
|
911
|
+
return Object.fromEntries(Object.entries(tableNames).map(([jsName, dbName]) => [dbName, jsName]));
|
|
903
912
|
}
|
|
904
913
|
function getNewTableByName(dbTableName) {
|
|
905
914
|
const mapping = getTableNameMapping();
|