@withwiz/toolkit 0.8.0 → 0.9.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 (43) hide show
  1. package/README.ko.md +16 -25
  2. package/README.md +16 -25
  3. package/dist/chunk-2JUFL4KX.js +41 -0
  4. package/dist/chunk-6A5JHOBB.js +134 -0
  5. package/dist/chunk-BTYH4MHU.js +118 -0
  6. package/dist/chunk-E4TKORMQ.js +59 -0
  7. package/dist/{chunk-OII5BXCZ.js → chunk-U2TGXJ3A.js} +1 -1
  8. package/dist/chunk-VQJY52YF.js +139 -0
  9. package/dist/{chunk-NLV7IEYM.js → chunk-WSHRBK6Y.js} +1 -1
  10. package/dist/core/auth/adapters/sql/dialect.d.ts +21 -0
  11. package/dist/core/auth/adapters/sql/dialect.js +11 -0
  12. package/dist/core/auth/adapters/sql/email-token-repository.d.ts +17 -0
  13. package/dist/core/auth/adapters/sql/email-token-repository.js +9 -0
  14. package/dist/core/auth/adapters/sql/index.d.ts +7 -0
  15. package/dist/core/auth/adapters/sql/index.js +27 -0
  16. package/dist/core/auth/adapters/sql/oauth-account-repository.d.ts +18 -0
  17. package/dist/core/auth/adapters/sql/oauth-account-repository.js +9 -0
  18. package/dist/core/auth/adapters/sql/types.d.ts +73 -0
  19. package/dist/core/auth/adapters/sql/types.js +7 -0
  20. package/dist/core/auth/adapters/sql/user-repository.d.ts +20 -0
  21. package/dist/core/auth/adapters/sql/user-repository.js +9 -0
  22. package/dist/core/auth/index.js +9 -9
  23. package/dist/core/auth/oauth/index.js +7 -7
  24. package/dist/core/auth/oauth/providers/index.js +6 -6
  25. package/dist/core/auth/services/index.js +4 -4
  26. package/dist/core/constants/index.js +6 -6
  27. package/dist/core/geolocation/index.js +4 -4
  28. package/dist/core/geolocation/providers/index.js +4 -4
  29. package/dist/core/system/health-check.js +2 -2
  30. package/dist/core/system/index.js +5 -5
  31. package/dist/core/utils/index.js +8 -8
  32. package/dist/initialize.js +3 -3
  33. package/dist/next/auth-handlers/index.js +19 -19
  34. package/dist/next/auth-handlers/oauth-authorize.handler.js +4 -4
  35. package/dist/next/auth-handlers/oauth-callback.handler.js +5 -5
  36. package/dist/next/error/index.js +3 -3
  37. package/dist/next/middleware/index.js +1 -1
  38. package/dist/next/middleware/wrappers.js +1 -1
  39. package/dist/next/utils/index.js +14 -14
  40. package/package.json +3 -2
  41. package/dist/{chunk-SXBDP3CK.js → chunk-4EXZ2SDV.js} +3 -3
  42. package/dist/{chunk-VDRLJU6C.js → chunk-E2HYIONP.js} +3 -3
  43. package/dist/{chunk-ER6LSA4M.js → chunk-P7WHMKN5.js} +6 -6
package/README.ko.md CHANGED
@@ -7,13 +7,12 @@ Shared utility library for [withwiz](https://github.com/greeun) projects — 인
7
7
  ## Features
8
8
 
9
9
  - **Composition root** — 단일 `initialize()`로 모든 티어 설정을 한 번에 조립
10
- - **Framework tiers** — `core` / `next` / `react` / `prisma` 4개 티어로 의존성 분리 (0.7+)
10
+ - **Framework tiers** — `core` / `next` / `prisma` 3개 티어로 의존성 분리 (0.8+)
11
11
  - **Auth** — JWT, password hashing, OAuth helpers (Google / GitHub / Kakao / Microsoft / Meta), state-cookie CSRF 바인딩, Prisma adapter
12
12
  - **Cache** — Redis / in-memory / hybrid / noop 백엔드, factory, invalidation, defaults
13
13
  - **Constants** — Error codes, messages, pagination, security constants
14
- - **Error** — Typed `AppError`, framework-aware error handler / display (core · next · react)
14
+ - **Error** — Typed `AppError`, framework-aware error handler (core · next)
15
15
  - **Geolocation** — GeoIP lookup, batch processing, provider factory
16
- - **Hooks** — `useDataTable`, `useDebounce`, `useExitIntent`, `useTimezone`
17
16
  - **Logger** — Winston-based structured logger with daily rotation
18
17
  - **Middleware** — Auth, rate-limiting, CORS, security middleware wrappers (Next.js)
19
18
  - **Storage** — Cloudflare R2 (AWS S3-compatible) storage
@@ -38,15 +37,17 @@ yarn add @withwiz/toolkit
38
37
 
39
38
  ```bash
40
39
  # next 티어를 쓸 때
41
- npm install next
42
-
43
- # react 티어를 쓸 때
44
- npm install react react-dom
40
+ # (React 도 필요 — next/error/ErrorBoundary 가 React 컴포넌트)
41
+ npm install next react react-dom
45
42
 
46
43
  # core 티어만 쓰는 백엔드 / CLI
47
44
  # (별도 peer 설치 불필요)
48
45
  ```
49
46
 
47
+ > **React 컴포넌트 분리 (0.8).** `react` 티어(UI 컴포넌트, hooks, `error-display`)는
48
+ > 독립 패키지 [`@withwiz/ui`](https://github.com/greeun) 로 추출되었습니다.
49
+ > `@withwiz/toolkit/react/*` → `@withwiz/ui/react/*` 로 import 경로를 치환하세요.
50
+
50
51
  ## Quick start
51
52
 
52
53
  ```ts
@@ -87,13 +88,6 @@ const geo = await getGeoLocation('8.8.8.8')
87
88
  // { country: 'US', city: 'Mountain View', ... }
88
89
  ```
89
90
 
90
- ```tsx
91
- // Hooks (React)
92
- import { useDebounce } from '@withwiz/toolkit/react/hooks/useDebounce'
93
-
94
- const debouncedQuery = useDebounce(query, 300)
95
- ```
96
-
97
91
  ```ts
98
92
  // Utils
99
93
  import { sanitizeInput } from '@withwiz/toolkit/core/utils/sanitizer'
@@ -132,9 +126,10 @@ createAuthHandlers({ ...options, tokenDelivery: 'header' });
132
126
 
133
127
  ## Module reference
134
128
 
135
- 0.7부터 모든 subpath는 프레임워크 의존성에 따라 4개 티어로 분리됩니다.
129
+ 0.7부터 모든 subpath는 프레임워크 의존성에 따라 티어로 분리됩니다
130
+ (0.8부터 `core` / `next` / `prisma` 3개 — `react` 티어는 `@withwiz/ui` 로 이동).
136
131
  자세한 티어 모델·규칙·마이그레이션 매핑은 [`docs/FRAMEWORK_TIERS.md`](docs/FRAMEWORK_TIERS.md)
137
- 및 [`CHANGELOG.md`](CHANGELOG.md) 의 0.7.0 항목을 참조하세요.
132
+ 및 [`CHANGELOG.md`](CHANGELOG.md) 의 0.7.0 / 0.8.0 항목을 참조하세요.
138
133
 
139
134
  ### Composition root
140
135
 
@@ -179,14 +174,11 @@ createAuthHandlers({ ...options, tokenDelivery: 'header' });
179
174
  | `/next/error` | `error-handler` (NextResponse), `LocaleDetector`, `ErrorBoundary` |
180
175
  | `/next/utils` | `api-helpers`, `cors`, `csv-export`, `error-processor` |
181
176
 
182
- ### `react` — React 의존
177
+ ### `react` — `@withwiz/ui` 로 이동 (0.8)
183
178
 
184
- | Subpath | Description |
185
- |---|---|
186
- | `/react/components/ui/*` | Button, Table, Badge, DataTable, ... |
187
- | `/react/hooks/{useDataTable,useDebounce,useExitIntent,useTimezone}` | React hooks |
188
- | `/react/error` | `error-display` (sonner toast) |
189
- | `/react/utils/{client-utils,qr-code}` | Browser-context utilities |
179
+ React 티어(UI 컴포넌트, hooks, `error-display`, 브라우저 컨텍스트 utils)는 독립 패키지
180
+ [`@withwiz/ui`](https://github.com/greeun) 로 추출되었습니다.
181
+ import 경로 치환: `@withwiz/toolkit/react/*` `@withwiz/ui/react/*`.
190
182
 
191
183
  ### `prisma` — Prisma 의존
192
184
 
@@ -205,8 +197,7 @@ createAuthHandlers({ ...options, tokenDelivery: 'header' });
205
197
  사용하는 티어가 요구하는 peer만 설치하세요:
206
198
 
207
199
  - `core` 티어만 쓰는 백엔드 / CLI: peer 불필요
208
- - `next` 티어: Next.js >= 15
209
- - `react` 티어: React >= 18, React-DOM >= 18
200
+ - `next` 티어: Next.js >= 15, 그리고 React >= 18 / React-DOM >= 18 (`next/error/ErrorBoundary` 가 React 컴포넌트)
210
201
  - `prisma` 티어: Prisma 호환 클라이언트 (덕 타이핑)
211
202
  - Prisma 어댑터의 `EmailTokenRepository` 등 일부 모듈: `date-fns >= 3` (optional)
212
203
  - 이메일 전송: `nodemailer >= 6` (optional)
package/README.md CHANGED
@@ -7,13 +7,12 @@ Shared utility library for [withwiz](https://github.com/greeun) projects — a c
7
7
  ## Features
8
8
 
9
9
  - **Composition root** — assemble every tier's configuration at once with a single `initialize()`
10
- - **Framework tiers** — dependencies split across four tiers: `core` / `next` / `react` / `prisma` (0.7+)
10
+ - **Framework tiers** — dependencies split across three tiers: `core` / `next` / `prisma` (0.8+)
11
11
  - **Auth** — JWT, password hashing, OAuth helpers (Google / GitHub / Kakao / Microsoft / Meta), state-cookie CSRF binding, Prisma adapter
12
12
  - **Cache** — Redis / in-memory / hybrid / noop backends, factory, invalidation, defaults
13
13
  - **Constants** — Error codes, messages, pagination, security constants
14
- - **Error** — Typed `AppError`, framework-aware error handler / display (core · next · react)
14
+ - **Error** — Typed `AppError`, framework-aware error handler (core · next)
15
15
  - **Geolocation** — GeoIP lookup, batch processing, provider factory
16
- - **Hooks** — `useDataTable`, `useDebounce`, `useExitIntent`, `useTimezone`
17
16
  - **Logger** — Winston-based structured logger with daily rotation
18
17
  - **Middleware** — Auth, rate-limiting, CORS, security middleware wrappers (Next.js)
19
18
  - **Storage** — Cloudflare R2 (AWS S3-compatible) storage
@@ -38,15 +37,17 @@ yarn add @withwiz/toolkit
38
37
 
39
38
  ```bash
40
39
  # When using the next tier
41
- npm install next
42
-
43
- # When using the react tier
44
- npm install react react-dom
40
+ # (React is also needed — next/error/ErrorBoundary is a React component)
41
+ npm install next react react-dom
45
42
 
46
43
  # Backend / CLI using only the core tier
47
44
  # (no extra peers required)
48
45
  ```
49
46
 
47
+ > **React components moved out (0.8).** The `react` tier (UI components, hooks,
48
+ > `error-display`) was extracted to [`@withwiz/ui`](https://github.com/greeun).
49
+ > Migrate by replacing `@withwiz/toolkit/react/*` → `@withwiz/ui/react/*`.
50
+
50
51
  ## Quick start
51
52
 
52
53
  ```ts
@@ -87,13 +88,6 @@ const geo = await getGeoLocation('8.8.8.8')
87
88
  // { country: 'US', city: 'Mountain View', ... }
88
89
  ```
89
90
 
90
- ```tsx
91
- // Hooks (React)
92
- import { useDebounce } from '@withwiz/toolkit/react/hooks/useDebounce'
93
-
94
- const debouncedQuery = useDebounce(query, 300)
95
- ```
96
-
97
91
  ```ts
98
92
  // Utils
99
93
  import { sanitizeInput } from '@withwiz/toolkit/core/utils/sanitizer'
@@ -132,9 +126,10 @@ createAuthHandlers({ ...options, tokenDelivery: 'header' });
132
126
 
133
127
  ## Module reference
134
128
 
135
- Since 0.7, every subpath is split into four tiers based on its framework dependencies.
129
+ Since 0.7, every subpath is split into tiers based on its framework dependencies
130
+ (`core` / `next` / `prisma` since 0.8 — the `react` tier moved to `@withwiz/ui`).
136
131
  For the detailed tier model, rules, and migration mapping, see [`docs/FRAMEWORK_TIERS.md`](docs/FRAMEWORK_TIERS.md)
137
- and the 0.7.0 entry in [`CHANGELOG.md`](CHANGELOG.md).
132
+ and the 0.7.0 / 0.8.0 entries in [`CHANGELOG.md`](CHANGELOG.md).
138
133
 
139
134
  ### Composition root
140
135
 
@@ -179,14 +174,11 @@ and the 0.7.0 entry in [`CHANGELOG.md`](CHANGELOG.md).
179
174
  | `/next/error` | `error-handler` (NextResponse), `LocaleDetector`, `ErrorBoundary` |
180
175
  | `/next/utils` | `api-helpers`, `cors`, `csv-export`, `error-processor` |
181
176
 
182
- ### `react` — depends on React
177
+ ### `react` — moved to `@withwiz/ui` (0.8)
183
178
 
184
- | Subpath | Description |
185
- |---|---|
186
- | `/react/components/ui/*` | Button, Table, Badge, DataTable, ... |
187
- | `/react/hooks/{useDataTable,useDebounce,useExitIntent,useTimezone}` | React hooks |
188
- | `/react/error` | `error-display` (sonner toast) |
189
- | `/react/utils/{client-utils,qr-code}` | Browser-context utilities |
179
+ The React tier (UI components, hooks, `error-display`, browser-context utils) was
180
+ extracted to the standalone [`@withwiz/ui`](https://github.com/greeun) package.
181
+ Migrate imports: `@withwiz/toolkit/react/*` `@withwiz/ui/react/*`.
190
182
 
191
183
  ### `prisma` — depends on Prisma
192
184
 
@@ -205,8 +197,7 @@ and the 0.7.0 entry in [`CHANGELOG.md`](CHANGELOG.md).
205
197
  Install only the peers required by the tiers you use:
206
198
 
207
199
  - Backend / CLI using only the `core` tier: no peers required
208
- - `next` tier: Next.js >= 15
209
- - `react` tier: React >= 18, React-DOM >= 18
200
+ - `next` tier: Next.js >= 15, plus React >= 18 / React-DOM >= 18 (`next/error/ErrorBoundary` is a React component)
210
201
  - `prisma` tier: a Prisma-compatible client (duck-typed)
211
202
  - Some modules such as the Prisma adapter's `EmailTokenRepository`: `date-fns >= 3` (optional)
212
203
  - Email delivery: `nodemailer >= 6` (optional)
@@ -0,0 +1,41 @@
1
+ // src/core/auth/adapters/sql/dialect.ts
2
+ var POSTGRES = {
3
+ placeholder: (index) => `$${index}`,
4
+ quoteId: (id) => `"${id}"`
5
+ };
6
+ var MYSQL = {
7
+ placeholder: () => "?",
8
+ quoteId: (id) => `\`${id}\``
9
+ };
10
+ function getDialect(dialect) {
11
+ switch (dialect) {
12
+ case "postgres":
13
+ return POSTGRES;
14
+ case "mysql":
15
+ return MYSQL;
16
+ default:
17
+ throw new Error(`Unsupported SQL dialect: ${dialect}`);
18
+ }
19
+ }
20
+ var ParamBuilder = class {
21
+ constructor(dialect) {
22
+ this.dialect = dialect;
23
+ this._params = [];
24
+ }
25
+ add(value) {
26
+ this._params.push(value);
27
+ return this.dialect.placeholder(this._params.length);
28
+ }
29
+ get params() {
30
+ return this._params;
31
+ }
32
+ };
33
+ function columnList(columns, dialect) {
34
+ return columns.map((c) => dialect.quoteId(c)).join(", ");
35
+ }
36
+
37
+ export {
38
+ getDialect,
39
+ ParamBuilder,
40
+ columnList
41
+ };
@@ -0,0 +1,134 @@
1
+ import {
2
+ ParamBuilder,
3
+ columnList,
4
+ getDialect
5
+ } from "./chunk-2JUFL4KX.js";
6
+ import {
7
+ resolveConfig
8
+ } from "./chunk-E4TKORMQ.js";
9
+
10
+ // src/core/auth/adapters/sql/user-repository.ts
11
+ import { randomUUID } from "crypto";
12
+ var SqlUserRepository = class {
13
+ constructor(exec, config) {
14
+ this.exec = exec;
15
+ this.config = resolveConfig(config);
16
+ this.dialect = getDialect(this.config.dialect);
17
+ }
18
+ get table() {
19
+ return this.dialect.quoteId(this.config.tables.user);
20
+ }
21
+ get cols() {
22
+ return this.config.userColumns;
23
+ }
24
+ /** password 제외 SELECT/매핑 대상 컬럼. */
25
+ selectColumns() {
26
+ const c = this.cols;
27
+ return [c.id, c.email, c.name, c.role, c.image, c.emailVerified, c.isActive, c.createdAt, c.updatedAt];
28
+ }
29
+ async findById(id) {
30
+ const pb = new ParamBuilder(this.dialect);
31
+ const sql = `SELECT ${columnList(this.selectColumns(), this.dialect)} FROM ${this.table} WHERE ${this.dialect.quoteId(this.cols.id)} = ${pb.add(id)}`;
32
+ const rows = await this.exec.query(sql, pb.params);
33
+ return rows[0] ? this.mapToBaseUser(rows[0]) : null;
34
+ }
35
+ async findByEmail(email) {
36
+ var _a;
37
+ const pb = new ParamBuilder(this.dialect);
38
+ const cols = [...this.selectColumns(), this.cols.password];
39
+ const sql = `SELECT ${columnList(cols, this.dialect)} FROM ${this.table} WHERE ${this.dialect.quoteId(this.cols.email)} = ${pb.add(email)}`;
40
+ const rows = await this.exec.query(sql, pb.params);
41
+ if (!rows[0]) return null;
42
+ const baseUser = this.mapToBaseUser(rows[0]);
43
+ return Object.assign(baseUser, { password: (_a = rows[0][this.cols.password]) != null ? _a : null });
44
+ }
45
+ async create(data) {
46
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
47
+ const c = this.cols;
48
+ const id = randomUUID();
49
+ const now = /* @__PURE__ */ new Date();
50
+ const pb = new ParamBuilder(this.dialect);
51
+ const entries = [
52
+ [c.id, id],
53
+ [c.email, data.email],
54
+ [c.name, (_a = data.name) != null ? _a : null],
55
+ [c.password, (_b = data.password) != null ? _b : null],
56
+ [c.role, (_c = data.role) != null ? _c : null],
57
+ [c.emailVerified, (_d = data.emailVerified) != null ? _d : null],
58
+ [c.image, (_e = data.image) != null ? _e : null],
59
+ [c.isActive, true],
60
+ [c.createdAt, now],
61
+ [c.updatedAt, now]
62
+ ];
63
+ const colSql = entries.map(([col]) => this.dialect.quoteId(col)).join(", ");
64
+ const valSql = entries.map(([, val]) => pb.add(val)).join(", ");
65
+ const sql = `INSERT INTO ${this.table} (${colSql}) VALUES (${valSql})`;
66
+ await this.exec.query(sql, pb.params);
67
+ return {
68
+ id,
69
+ email: data.email,
70
+ name: (_f = data.name) != null ? _f : null,
71
+ role: (_g = data.role) != null ? _g : void 0,
72
+ emailVerified: (_h = data.emailVerified) != null ? _h : null,
73
+ image: (_i = data.image) != null ? _i : null,
74
+ isActive: true,
75
+ createdAt: now,
76
+ updatedAt: now
77
+ };
78
+ }
79
+ async update(id, data) {
80
+ const c = this.cols;
81
+ const pb = new ParamBuilder(this.dialect);
82
+ const sets = [];
83
+ const assign = (col, val) => {
84
+ sets.push(`${this.dialect.quoteId(col)} = ${pb.add(val)}`);
85
+ };
86
+ if (data.email !== void 0) assign(c.email, data.email);
87
+ if (data.name !== void 0) assign(c.name, data.name);
88
+ if (data.isActive !== void 0) assign(c.isActive, data.isActive);
89
+ if (data.password !== void 0) assign(c.password, data.password);
90
+ if (data.role !== void 0) assign(c.role, data.role);
91
+ if (data.emailVerified !== void 0) assign(c.emailVerified, data.emailVerified);
92
+ if (data.image !== void 0) assign(c.image, data.image);
93
+ assign(c.updatedAt, /* @__PURE__ */ new Date());
94
+ const sql = `UPDATE ${this.table} SET ${sets.join(", ")} WHERE ${this.dialect.quoteId(c.id)} = ${pb.add(id)}`;
95
+ await this.exec.query(sql, pb.params);
96
+ const updated = await this.findById(id);
97
+ if (!updated) throw new Error(`User not found after update: ${id}`);
98
+ return updated;
99
+ }
100
+ async delete(id) {
101
+ const pb = new ParamBuilder(this.dialect);
102
+ const sql = `DELETE FROM ${this.table} WHERE ${this.dialect.quoteId(this.cols.id)} = ${pb.add(id)}`;
103
+ await this.exec.query(sql, pb.params);
104
+ }
105
+ async updateLastLoginAt(id) {
106
+ const pb = new ParamBuilder(this.dialect);
107
+ const sql = `UPDATE ${this.table} SET ${this.dialect.quoteId(this.cols.lastLoginAt)} = ${pb.add(/* @__PURE__ */ new Date())} WHERE ${this.dialect.quoteId(this.cols.id)} = ${pb.add(id)}`;
108
+ await this.exec.query(sql, pb.params);
109
+ }
110
+ async verifyEmail(email) {
111
+ const pb = new ParamBuilder(this.dialect);
112
+ const sql = `UPDATE ${this.table} SET ${this.dialect.quoteId(this.cols.emailVerified)} = ${pb.add(/* @__PURE__ */ new Date())} WHERE ${this.dialect.quoteId(this.cols.email)} = ${pb.add(email)}`;
113
+ await this.exec.query(sql, pb.params);
114
+ }
115
+ mapToBaseUser(row) {
116
+ var _a, _b, _c;
117
+ const c = this.cols;
118
+ return {
119
+ id: row[c.id],
120
+ email: row[c.email],
121
+ name: (_a = row[c.name]) != null ? _a : null,
122
+ role: row[c.role],
123
+ emailVerified: (_b = row[c.emailVerified]) != null ? _b : null,
124
+ isActive: row[c.isActive],
125
+ image: (_c = row[c.image]) != null ? _c : null,
126
+ createdAt: row[c.createdAt],
127
+ updatedAt: row[c.updatedAt]
128
+ };
129
+ }
130
+ };
131
+
132
+ export {
133
+ SqlUserRepository
134
+ };
@@ -0,0 +1,118 @@
1
+ import {
2
+ ParamBuilder,
3
+ columnList,
4
+ getDialect
5
+ } from "./chunk-2JUFL4KX.js";
6
+ import {
7
+ resolveConfig
8
+ } from "./chunk-E4TKORMQ.js";
9
+
10
+ // src/core/auth/adapters/sql/email-token-repository.ts
11
+ import { randomUUID } from "crypto";
12
+ var SqlEmailTokenRepository = class {
13
+ constructor(exec, config) {
14
+ this.exec = exec;
15
+ this.config = resolveConfig(config);
16
+ this.dialect = getDialect(this.config.dialect);
17
+ }
18
+ get cols() {
19
+ return this.config.emailTokenColumns;
20
+ }
21
+ tableName(type) {
22
+ switch (type) {
23
+ case "EMAIL_VERIFICATION":
24
+ return this.config.tables.emailVerification;
25
+ case "PASSWORD_RESET":
26
+ return this.config.tables.passwordReset;
27
+ case "MAGIC_LINK":
28
+ return this.config.tables.magicLink;
29
+ default:
30
+ throw new Error(`Unsupported token type: ${type}`);
31
+ }
32
+ }
33
+ quotedTable(type) {
34
+ return this.dialect.quoteId(this.tableName(type));
35
+ }
36
+ selectColumns(includeUsed) {
37
+ const c = this.cols;
38
+ const base = [c.id, c.email, c.token, c.expires, c.createdAt];
39
+ return includeUsed ? [...base, c.used] : base;
40
+ }
41
+ async create(email, token, type, expiresAt) {
42
+ const c = this.cols;
43
+ const id = randomUUID();
44
+ const now = /* @__PURE__ */ new Date();
45
+ const isMagic = type === "MAGIC_LINK";
46
+ const pb = new ParamBuilder(this.dialect);
47
+ const entries = [
48
+ [c.id, id],
49
+ [c.email, email],
50
+ [c.token, token],
51
+ [c.expires, expiresAt],
52
+ [c.createdAt, now]
53
+ ];
54
+ if (isMagic) entries.push([c.used, false]);
55
+ const colSql = entries.map(([col]) => this.dialect.quoteId(col)).join(", ");
56
+ const valSql = entries.map(([, val]) => pb.add(val)).join(", ");
57
+ const sql = `INSERT INTO ${this.quotedTable(type)} (${colSql}) VALUES (${valSql})`;
58
+ await this.exec.query(sql, pb.params);
59
+ return {
60
+ id,
61
+ email,
62
+ token,
63
+ type,
64
+ expires: expiresAt,
65
+ used: false,
66
+ createdAt: now
67
+ };
68
+ }
69
+ async findByEmailAndToken(email, token, type) {
70
+ const c = this.cols;
71
+ const isMagic = type === "MAGIC_LINK";
72
+ const pb = new ParamBuilder(this.dialect);
73
+ const sql = `SELECT ${columnList(this.selectColumns(isMagic), this.dialect)} FROM ${this.quotedTable(type)} WHERE ${this.dialect.quoteId(c.email)} = ${pb.add(email)} AND ${this.dialect.quoteId(c.token)} = ${pb.add(token)}`;
74
+ const rows = await this.exec.query(sql, pb.params);
75
+ const row = rows[0];
76
+ if (!row) return null;
77
+ return {
78
+ id: row[c.id],
79
+ email: row[c.email],
80
+ token: row[c.token],
81
+ type,
82
+ expires: row[c.expires],
83
+ used: isMagic ? Boolean(row[c.used]) : false,
84
+ createdAt: row[c.createdAt]
85
+ };
86
+ }
87
+ async delete(email, token, type) {
88
+ const c = this.cols;
89
+ const pb = new ParamBuilder(this.dialect);
90
+ const sql = `DELETE FROM ${this.quotedTable(type)} WHERE ${this.dialect.quoteId(c.email)} = ${pb.add(email)} AND ${this.dialect.quoteId(c.token)} = ${pb.add(token)}`;
91
+ await this.exec.query(sql, pb.params);
92
+ }
93
+ async deleteExpired() {
94
+ const c = this.cols;
95
+ const tables = [
96
+ this.config.tables.emailVerification,
97
+ this.config.tables.passwordReset,
98
+ this.config.tables.magicLink
99
+ ];
100
+ await Promise.all(
101
+ tables.map((t) => {
102
+ const pb = new ParamBuilder(this.dialect);
103
+ const sql = `DELETE FROM ${this.dialect.quoteId(t)} WHERE ${this.dialect.quoteId(c.expires)} < ${pb.add(/* @__PURE__ */ new Date())}`;
104
+ return this.exec.query(sql, pb.params);
105
+ })
106
+ );
107
+ }
108
+ async markAsUsed(id) {
109
+ const c = this.cols;
110
+ const pb = new ParamBuilder(this.dialect);
111
+ const sql = `UPDATE ${this.dialect.quoteId(this.config.tables.magicLink)} SET ${this.dialect.quoteId(c.used)} = ${pb.add(true)} WHERE ${this.dialect.quoteId(c.id)} = ${pb.add(id)}`;
112
+ await this.exec.query(sql, pb.params);
113
+ }
114
+ };
115
+
116
+ export {
117
+ SqlEmailTokenRepository
118
+ };
@@ -0,0 +1,59 @@
1
+ import {
2
+ __spreadValues
3
+ } from "./chunk-N3ETBM74.js";
4
+
5
+ // src/core/auth/adapters/sql/types.ts
6
+ var DEFAULT_TABLES = {
7
+ user: "users",
8
+ oauthAccount: "accounts",
9
+ emailVerification: "email_verification_tokens",
10
+ passwordReset: "password_reset_tokens",
11
+ magicLink: "magic_link_tokens"
12
+ };
13
+ var DEFAULT_USER_COLUMNS = {
14
+ id: "id",
15
+ email: "email",
16
+ name: "name",
17
+ password: "password",
18
+ role: "role",
19
+ image: "image",
20
+ emailVerified: "email_verified",
21
+ isActive: "is_active",
22
+ createdAt: "created_at",
23
+ updatedAt: "updated_at",
24
+ lastLoginAt: "last_login_at"
25
+ };
26
+ var DEFAULT_OAUTH_COLUMNS = {
27
+ id: "id",
28
+ userId: "user_id",
29
+ provider: "provider",
30
+ providerAccountId: "provider_account_id",
31
+ accessToken: "access_token",
32
+ refreshToken: "refresh_token",
33
+ expiresAt: "expires_at",
34
+ tokenType: "token_type",
35
+ scope: "scope",
36
+ createdAt: "created_at",
37
+ updatedAt: "updated_at"
38
+ };
39
+ var DEFAULT_EMAIL_TOKEN_COLUMNS = {
40
+ id: "id",
41
+ email: "email",
42
+ token: "token",
43
+ expires: "expires",
44
+ used: "used",
45
+ createdAt: "created_at"
46
+ };
47
+ function resolveConfig(config) {
48
+ return {
49
+ dialect: config.dialect,
50
+ tables: __spreadValues(__spreadValues({}, DEFAULT_TABLES), config.tables),
51
+ userColumns: __spreadValues(__spreadValues({}, DEFAULT_USER_COLUMNS), config.userColumns),
52
+ oauthColumns: __spreadValues(__spreadValues({}, DEFAULT_OAUTH_COLUMNS), config.oauthColumns),
53
+ emailTokenColumns: __spreadValues(__spreadValues({}, DEFAULT_EMAIL_TOKEN_COLUMNS), config.emailTokenColumns)
54
+ };
55
+ }
56
+
57
+ export {
58
+ resolveConfig
59
+ };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  OAuthManager
3
- } from "./chunk-ER6LSA4M.js";
3
+ } from "./chunk-P7WHMKN5.js";
4
4
  import {
5
5
  generateOAuthState,
6
6
  setOAuthStateCookie
@@ -0,0 +1,139 @@
1
+ import {
2
+ ParamBuilder,
3
+ columnList,
4
+ getDialect
5
+ } from "./chunk-2JUFL4KX.js";
6
+ import {
7
+ resolveConfig
8
+ } from "./chunk-E4TKORMQ.js";
9
+
10
+ // src/core/auth/adapters/sql/oauth-account-repository.ts
11
+ import { randomUUID } from "crypto";
12
+ var SqlOAuthAccountRepository = class {
13
+ constructor(exec, config) {
14
+ this.exec = exec;
15
+ this.config = resolveConfig(config);
16
+ this.dialect = getDialect(this.config.dialect);
17
+ }
18
+ get table() {
19
+ return this.dialect.quoteId(this.config.tables.oauthAccount);
20
+ }
21
+ get cols() {
22
+ return this.config.oauthColumns;
23
+ }
24
+ selectColumns() {
25
+ const c = this.cols;
26
+ return [
27
+ c.id,
28
+ c.userId,
29
+ c.provider,
30
+ c.providerAccountId,
31
+ c.accessToken,
32
+ c.refreshToken,
33
+ c.expiresAt,
34
+ c.tokenType,
35
+ c.scope,
36
+ c.createdAt,
37
+ c.updatedAt
38
+ ];
39
+ }
40
+ async findByProvider(provider, providerAccountId) {
41
+ const c = this.cols;
42
+ const pb = new ParamBuilder(this.dialect);
43
+ const sql = `SELECT ${columnList(this.selectColumns(), this.dialect)} FROM ${this.table} WHERE ${this.dialect.quoteId(c.provider)} = ${pb.add(provider)} AND ${this.dialect.quoteId(c.providerAccountId)} = ${pb.add(providerAccountId)}`;
44
+ const rows = await this.exec.query(sql, pb.params);
45
+ return rows[0] ? this.mapToOAuthAccount(rows[0]) : null;
46
+ }
47
+ async findByUserId(userId) {
48
+ const pb = new ParamBuilder(this.dialect);
49
+ const sql = `SELECT ${columnList(this.selectColumns(), this.dialect)} FROM ${this.table} WHERE ${this.dialect.quoteId(this.cols.userId)} = ${pb.add(userId)}`;
50
+ const rows = await this.exec.query(sql, pb.params);
51
+ return rows.map((r) => this.mapToOAuthAccount(r));
52
+ }
53
+ async create(data) {
54
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
55
+ const c = this.cols;
56
+ const id = randomUUID();
57
+ const now = /* @__PURE__ */ new Date();
58
+ const pb = new ParamBuilder(this.dialect);
59
+ const entries = [
60
+ [c.id, id],
61
+ [c.userId, data.userId],
62
+ [c.provider, data.provider],
63
+ [c.providerAccountId, data.providerAccountId],
64
+ [c.accessToken, (_a = data.accessToken) != null ? _a : null],
65
+ [c.refreshToken, (_b = data.refreshToken) != null ? _b : null],
66
+ [c.expiresAt, (_c = data.expiresAt) != null ? _c : null],
67
+ [c.tokenType, (_d = data.tokenType) != null ? _d : null],
68
+ [c.scope, (_e = data.scope) != null ? _e : null],
69
+ [c.createdAt, now],
70
+ [c.updatedAt, now]
71
+ ];
72
+ const colSql = entries.map(([col]) => this.dialect.quoteId(col)).join(", ");
73
+ const valSql = entries.map(([, val]) => pb.add(val)).join(", ");
74
+ const sql = `INSERT INTO ${this.table} (${colSql}) VALUES (${valSql})`;
75
+ await this.exec.query(sql, pb.params);
76
+ return {
77
+ id,
78
+ userId: data.userId,
79
+ provider: data.provider,
80
+ providerAccountId: data.providerAccountId,
81
+ accessToken: (_f = data.accessToken) != null ? _f : null,
82
+ refreshToken: (_g = data.refreshToken) != null ? _g : null,
83
+ expiresAt: (_h = data.expiresAt) != null ? _h : null,
84
+ tokenType: (_i = data.tokenType) != null ? _i : null,
85
+ scope: (_j = data.scope) != null ? _j : null,
86
+ createdAt: now,
87
+ updatedAt: now
88
+ };
89
+ }
90
+ async update(id, data) {
91
+ const c = this.cols;
92
+ const pb = new ParamBuilder(this.dialect);
93
+ const sets = [];
94
+ const assign = (col, val) => sets.push(`${this.dialect.quoteId(col)} = ${pb.add(val)}`);
95
+ if (data.accessToken !== void 0) assign(c.accessToken, data.accessToken);
96
+ if (data.refreshToken !== void 0) assign(c.refreshToken, data.refreshToken);
97
+ if (data.expiresAt !== void 0) assign(c.expiresAt, data.expiresAt);
98
+ if (data.tokenType !== void 0) assign(c.tokenType, data.tokenType);
99
+ if (data.scope !== void 0) assign(c.scope, data.scope);
100
+ assign(c.updatedAt, /* @__PURE__ */ new Date());
101
+ const sql = `UPDATE ${this.table} SET ${sets.join(", ")} WHERE ${this.dialect.quoteId(c.id)} = ${pb.add(id)}`;
102
+ await this.exec.query(sql, pb.params);
103
+ const updated = await this.selectById(id);
104
+ if (!updated) throw new Error(`OAuth account not found after update: ${id}`);
105
+ return updated;
106
+ }
107
+ async delete(id) {
108
+ const pb = new ParamBuilder(this.dialect);
109
+ const sql = `DELETE FROM ${this.table} WHERE ${this.dialect.quoteId(this.cols.id)} = ${pb.add(id)}`;
110
+ await this.exec.query(sql, pb.params);
111
+ }
112
+ async selectById(id) {
113
+ const pb = new ParamBuilder(this.dialect);
114
+ const sql = `SELECT ${columnList(this.selectColumns(), this.dialect)} FROM ${this.table} WHERE ${this.dialect.quoteId(this.cols.id)} = ${pb.add(id)}`;
115
+ const rows = await this.exec.query(sql, pb.params);
116
+ return rows[0] ? this.mapToOAuthAccount(rows[0]) : null;
117
+ }
118
+ mapToOAuthAccount(row) {
119
+ var _a, _b, _c, _d, _e, _f;
120
+ const c = this.cols;
121
+ return {
122
+ id: row[c.id],
123
+ userId: row[c.userId],
124
+ provider: row[c.provider],
125
+ providerAccountId: row[c.providerAccountId],
126
+ accessToken: (_a = row[c.accessToken]) != null ? _a : null,
127
+ refreshToken: (_b = row[c.refreshToken]) != null ? _b : null,
128
+ expiresAt: (_c = row[c.expiresAt]) != null ? _c : null,
129
+ tokenType: (_d = row[c.tokenType]) != null ? _d : null,
130
+ scope: (_e = row[c.scope]) != null ? _e : null,
131
+ createdAt: row[c.createdAt],
132
+ updatedAt: (_f = row[c.updatedAt]) != null ? _f : row[c.createdAt]
133
+ };
134
+ }
135
+ };
136
+
137
+ export {
138
+ SqlOAuthAccountRepository
139
+ };