@nxgt/janus-drizzle 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.
@@ -0,0 +1,324 @@
1
+ # Troubleshooting `@nxgt/janus-drizzle`
2
+
3
+ Each entry is headed by the text you see: a compiler error, a message, or an
4
+ error `code`. Search this page for the words of your message.
5
+
6
+ This adapter **defines no error class**. Every error it throws is one of
7
+ `@nxgt/janus`'s: `StoreFailure`, `StoreConflict` or `NotFoundError`. So the
8
+ codes, and the response each one deserves, are those of the core. The errors
9
+ the core raises itself (`CREDENTIALS_INVALID`, `TOKEN_EXPIRED`,
10
+ `PERMISSION_DEPTH`, …) are in
11
+ [`@nxgt/janus`'s troubleshooting](https://github.com/softistx/nxgt-janus/blob/develop/packages/janus/docs/troubleshooting.md).
12
+
13
+ How PostgreSQL's errors become the port's:
14
+
15
+ | What PostgreSQL reports | What you get |
16
+ | --- | --- |
17
+ | A login another user of the type holds | `LOGIN_TAKEN`, carrying `login` and `userType` |
18
+ | A row with this id already there | Nothing: the insert was a retry, and returns what is stored — or `NOT_FOUND` if the user was deleted between the two |
19
+ | A unique violation on any other constraint | `STORE_FAILED` |
20
+ | A check or foreign key refusing a row | `STORE_FAILED` |
21
+ | Anything else | `STORE_FAILED`, with the driver's error as `cause` (see below) |
22
+
23
+ ## Index
24
+
25
+ **Install and types**
26
+ - [`TS2834: Relative import paths need explicit file extensions …`](#ts2834-relative-import-paths-need-explicit-file-extensions-in-ecmascript-imports-when---moduleresolution-is-node16-or-nodenext)
27
+ - [`TS2345: Argument of type '…' is not assignable to parameter of type 'PgDatabase'.`](#ts2345-argument-of-type--is-not-assignable-to-parameter-of-type-pgdatabase)
28
+ - [`TS2322: Type 'string' is not assignable to type 'PgSchema<string>'.`](#ts2322-type-string-is-not-assignable-to-type-pgschemastring)
29
+ - [`TS2353: Object literal may only specify known properties, and 'schema' does not exist in type 'DrizzleAdapterOptions<…>'.`](#ts2353-object-literal-may-only-specify-known-properties-and-schema-does-not-exist-in-type-drizzleadapteroptions)
30
+ - [`TS2559: Type '{ users: …; … }' has no properties in common with type 'DrizzleAdapterOptions<…>'.`](#ts2559-type--users----has-no-properties-in-common-with-type-drizzleadapteroptions)
31
+ - [`error instanceof StoreFailure` is `false` for an outage](#error-instanceof-storefailure-is-false-for-an-outage)
32
+
33
+ **Setup**
34
+ - [`STORE_FAILED` caused by `relation "users" does not exist`](#store_failed-caused-by-relation-users-does-not-exist)
35
+ - [`STORE_FAILED` caused by `column "type" does not exist`](#store_failed-caused-by-column-type-does-not-exist)
36
+ - [`schema "janus" does not exist` while migrating](#schema-janus-does-not-exist-while-migrating)
37
+ - [`syntax error at or near "NULLS"` while migrating](#syntax-error-at-or-near-nulls-while-migrating)
38
+ - [The migration drops Janus's tables](#the-migration-drops-januss-tables)
39
+
40
+ **Runtime**
41
+ - [`STORE_FAILED`: `<slot>.<operation>: the store could not answer`](#store_failed-slotoperation-the-store-could-not-answer)
42
+ - [`LOGIN_TAKEN`: `<operation>: the login is taken by another <type>`](#login_taken-operation-the-login-is-taken-by-another-type)
43
+ - [`NOT_FOUND` / `VERSION_CONFLICT`: `updateUser: …`](#not_found--version_conflict-updateuser-)
44
+ - [The `sessions` table keeps growing](#the-sessions-table-keeps-growing)
45
+ - [`NOT_FOUND`: `insertUser: the user was deleted meanwhile`](#not_found-insertuser-the-user-was-deleted-meanwhile)
46
+
47
+ ---
48
+
49
+ ## Install and types
50
+
51
+ ### `TS2834: Relative import paths need explicit file extensions in ECMAScript imports when '--moduleResolution' is 'node16' or 'nodenext'.`
52
+
53
+ With `skipLibCheck: true`, the same cause shows up instead as
54
+ `TS2305: Module '"@nxgt/janus-drizzle"' has no exported member '<name>'.`
55
+
56
+ **When:** you type-check with `"moduleResolution": "node16"` or `"nodenext"`.
57
+
58
+ **Why:** the declarations import without extensions, the way Bun and every
59
+ bundler resolve them. `nodenext` is not supported, as with `@nxgt/janus`.
60
+
61
+ **Fix:** use the bundler resolution.
62
+
63
+ ```json
64
+ { "compilerOptions": { "module": "preserve", "moduleResolution": "bundler" } }
65
+ ```
66
+
67
+ ### `TS2345: Argument of type '…' is not assignable to parameter of type 'PgDatabase'.`
68
+
69
+ `'…'` is `'string'` for a connection string, or the client's type.
70
+
71
+ **When:** you pass `createDrizzleAdapter` a connection string, the driver's own
72
+ client (a `pg` `Pool`, a `postgres()` sql, a `PGlite`), or a Drizzle instance
73
+ over SQLite.
74
+
75
+ **Why:** the adapter takes a Drizzle PostgreSQL instance and connects to
76
+ nothing itself.
77
+
78
+ **Fix:** wrap the client with Drizzle's PostgreSQL `drizzle()` for its driver.
79
+
80
+ ```ts
81
+ import { drizzle } from 'drizzle-orm/node-postgres';
82
+
83
+ const postgres = createDrizzleAdapter(
84
+ drizzle(process.env.DATABASE_URL ?? 'postgres://localhost:5432/app'),
85
+ );
86
+ ```
87
+
88
+ ### `TS2322: Type 'string' is not assignable to type 'PgSchema<string>'.`
89
+
90
+ **When:** `defineJanusTables({ schema: 'janus' })`.
91
+
92
+ **Why:** `schema` is the `pgSchema(…)` itself, which your schema file must
93
+ export anyway: drizzle-kit writes `CREATE SCHEMA` only for an exported one.
94
+
95
+ **Fix:**
96
+
97
+ ```ts
98
+ export const janus = pgSchema('janus');
99
+ export const janusTables = defineJanusTables({ schema: janus });
100
+ ```
101
+
102
+ ### `TS2353: Object literal may only specify known properties, and 'schema' does not exist in type 'DrizzleAdapterOptions<…>'.`
103
+
104
+ **When:** `createDrizzleAdapter(db, { schema: janus })`, or the same given to
105
+ `createDrizzleStores` or `createDrizzleRelations`.
106
+
107
+ **Why:** the factories take the tables themselves, not how to build them, so
108
+ the stores query exactly the tables your migration created.
109
+
110
+ **Fix:** pass the object your schema file exports.
111
+
112
+ ```ts
113
+ createDrizzleAdapter(db, { tables: janusTables });
114
+ ```
115
+
116
+ ### `TS2559: Type '{ users: …; … }' has no properties in common with type 'DrizzleAdapterOptions<…>'.`
117
+
118
+ **When:** `createDrizzleAdapter(db, janusTables)`: the tables given bare.
119
+
120
+ **Why:** the second argument is options; the tables are one of them.
121
+
122
+ **Fix:**
123
+
124
+ ```ts
125
+ createDrizzleAdapter(db, { tables: janusTables });
126
+ ```
127
+
128
+ ### `error instanceof StoreFailure` is `false` for an outage
129
+
130
+ **When:** an outage rejects with an error whose `code` is `'STORE_FAILED'`,
131
+ yet `instanceof StoreFailure` is `false` in your code.
132
+
133
+ **Why:** two copies of `@nxgt/janus` are installed: the one you import, and
134
+ another under this adapter. `@nxgt/janus` is a peer so that there is one copy.
135
+
136
+ **Fix:** keep one copy. `bun pm ls | grep @nxgt/janus` should print one
137
+ version. Align the version you depend on with the adapter's peer range.
138
+
139
+ ---
140
+
141
+ ## Setup
142
+
143
+ ### `STORE_FAILED` caused by `relation "users" does not exist`
144
+
145
+ The code is `42P01`: `sqlState` on the `DataError` in `cause`, or `errno`
146
+ under it over Bun's `SQL`. It can name `logins`, `sessions`, `tokens` or
147
+ `relations` just as well, or `janus.users` and the like.
148
+
149
+ **When:** the first call that reaches the database: a sign-up, a sign-in, a
150
+ `can()`.
151
+
152
+ **Why:** one of two things.
153
+ - Your migrations never created the tables. The core never manages a schema,
154
+ and neither do the stores.
155
+ - They did, in a PostgreSQL schema, and the factory was not given
156
+ `{ tables }`: it queries `users` in the connection's `search_path`, not
157
+ `janus.users`.
158
+ Or the Drizzle instance is connected to your application's database while
159
+ the tables are in Janus's own.
160
+
161
+ **Fix:** export the five tables from a drizzle-kit schema file, then generate
162
+ and apply a migration.
163
+
164
+ ```ts
165
+ // src/janus/schema.ts
166
+ import { defineJanusTables } from '@nxgt/janus-drizzle';
167
+
168
+ export const { users, logins, sessions, tokens, relations } = defineJanusTables();
169
+ ```
170
+
171
+ ```sh
172
+ # With a database of its own, name its config; with a schema of its own, it is your application's.
173
+ bunx drizzle-kit generate --config drizzle.janus.config.ts
174
+ bunx drizzle-kit migrate --config drizzle.janus.config.ts
175
+ ```
176
+
177
+ drizzle-kit reads top-level table exports only: exporting the object
178
+ `defineJanusTables()` returns, whole, creates nothing.
179
+
180
+ With a PostgreSQL schema, pass the adapter the tables built in it:
181
+
182
+ ```ts
183
+ createDrizzleAdapter(db, { tables: janusTables }); // from the schema file
184
+ ```
185
+
186
+ ### `STORE_FAILED` caused by `column "type" does not exist`
187
+
188
+ Also `column "type" of relation "users" does not exist`, or another column of
189
+ Janus's tables. The code is `42703`.
190
+
191
+ **When:** the tables are in a PostgreSQL schema of their own, and your
192
+ application has a `users` (or `sessions`, …) table of its own in `public`.
193
+
194
+ **Why:** the factory was not given `{ tables }`, so it queries `public.users`:
195
+ your table, not Janus's. Reads fail on its columns. **A delete can succeed**,
196
+ and delete from your table.
197
+
198
+ **Fix:** pass the tables your schema file exports, and check your own tables
199
+ for rows deleted meanwhile.
200
+
201
+ ```ts
202
+ createDrizzleAdapter(db, { tables: janusTables });
203
+ ```
204
+
205
+ ### `schema "janus" does not exist` while migrating
206
+
207
+ The code is `3F000`. `janus` is the name your `pgSchema(…)` gives.
208
+
209
+ **When:** applying the migration that creates `"janus"."users"`.
210
+
211
+ **Why:** the schema file passes `pgSchema('janus')` to `defineJanusTables`
212
+ without exporting it. drizzle-kit writes `CREATE SCHEMA` only for a schema it
213
+ finds exported, so the migration creates tables in a schema that does not
214
+ exist.
215
+
216
+ **Fix:** export it, and generate the migration again.
217
+
218
+ ```ts
219
+ export const janus = pgSchema('janus');
220
+ export const janusTables = defineJanusTables({ schema: janus });
221
+ export const { users, logins, sessions, tokens, relations } = janusTables;
222
+ ```
223
+
224
+ ### `syntax error at or near "NULLS"` while migrating
225
+
226
+ **When:** applying the migration that creates `relations`.
227
+
228
+ **Why:** the tuple constraint is `UNIQUE NULLS NOT DISTINCT`, which PostgreSQL
229
+ accepts from version 15. Before that, two rows for one entity (whose
230
+ `subject_relation` is `null`) would not collide, and a tuple could be stored
231
+ twice.
232
+
233
+ **Fix:** run PostgreSQL 15 or later. An application that only authenticates
234
+ can leave `relations` out of its schema file, and then never passes
235
+ `createDrizzleRelations`.
236
+
237
+ ### The migration drops Janus's tables
238
+
239
+ **When:** `drizzle-kit generate` writes `DROP TABLE "users"` (or another of
240
+ the five) into a migration.
241
+
242
+ **Why:** the schema no longer exports that table: an export was removed, or
243
+ the schema file moved out of the drizzle-kit config's `schema`. drizzle-kit
244
+ drops what the schema no longer names.
245
+
246
+ **Fix:** delete that migration before applying it, and export all five tables
247
+ again. Applied, it deletes every user.
248
+
249
+ ---
250
+
251
+ ## Runtime
252
+
253
+ ### `STORE_FAILED`: `<slot>.<operation>: the store could not answer`
254
+
255
+ **When:** any call, for as long as the database cannot answer: a refused
256
+ connection, a timeout, a failover, a missing table, a permission denied.
257
+
258
+ **Why:** the adapter never turns a failure into an absence. An outage is not
259
+ "no such user".
260
+
261
+ **Fix:** answer 503 and let the client retry; log `cause` for the details.
262
+
263
+ ```ts
264
+ import { StoreFailure } from '@nxgt/janus';
265
+
266
+ if (error instanceof StoreFailure) {
267
+ console.error(error.slot, error.operation, error.cause);
268
+ }
269
+ ```
270
+
271
+ What `cause` is depends on the driver. Over `node-postgres`, postgres.js and
272
+ PGlite it is `@nxgt/drizzle`'s `DataError`, whose `sqlState` is PostgreSQL's
273
+ code — `42P01`, `57P01`. Over Bun's `SQL` it is Drizzle's `DrizzleQueryError`:
274
+ Bun reports the code as `errno`, which `@nxgt/drizzle` does not read yet, so
275
+ the code is `error.cause.cause.errno`.
276
+
277
+ `slot` is `users`, `sessions`, `tokens` or `relations`; `operation` is the port
278
+ method.
279
+
280
+ ### `LOGIN_TAKEN`: `<operation>: the login is taken by another <type>`
281
+
282
+ **When:** a sign-up, or an update that changes a login, with a login another
283
+ user of the same type holds.
284
+
285
+ **Why:** a login is unique per user type, by the `logins` table's primary key. The
286
+ same e-mail may hold a patient and a staff user.
287
+
288
+ **Fix:** tell the user the login is taken. `error.login` and `error.userType`
289
+ name it; the message does not, since it never carries a value.
290
+ `@nxgt/janus-hono` answers it 409.
291
+
292
+ ### `NOT_FOUND` / `VERSION_CONFLICT`: `updateUser: …`
293
+
294
+ **When:** `update`, `setActive`, `setPassword` or `changePassword` races another
295
+ write or a deletion.
296
+
297
+ **Why:** every user write is conditional on the version just read. Nothing was
298
+ written.
299
+
300
+ **Fix:** read the user again and retry, or tell the user it changed meanwhile.
301
+
302
+ ### The `sessions` table keeps growing
303
+
304
+ **When:** rows whose `expires_at` has passed are never deleted.
305
+
306
+ **Why:** PostgreSQL has no TTL. The core refuses a lapsed session on every
307
+ read, but deleting it is `auth.collectExpired()`'s job.
308
+
309
+ **Fix:** schedule it.
310
+
311
+ ```ts
312
+ const collected = await auth.collectExpired(); // hourly
313
+ ```
314
+
315
+ ### `NOT_FOUND`: `insertUser: the user was deleted meanwhile`
316
+
317
+ **When:** a sign-up or `create` is retried after a timeout, and the user its
318
+ first attempt stored was deleted before the retry read it back.
319
+
320
+ **Why:** the retry found a row with its id already there, which makes it a
321
+ retry, and then found no user to return. Nothing was written by the retry.
322
+
323
+ **Fix:** treat it as the deletion it raced: the user is gone. Sign up again
324
+ if that is what the caller wants.
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "@nxgt/janus-drizzle",
3
+ "version": "0.1.0",
4
+ "description": "The PostgreSQL adapter for @nxgt/janus on Drizzle and @nxgt/drizzle: users, sessions, one-time tokens and permission tuples",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "files": [
10
+ "dist",
11
+ "docs",
12
+ "README.md",
13
+ "package.json",
14
+ "LICENSE"
15
+ ],
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "import": "./dist/index.js",
20
+ "default": "./dist/index.js"
21
+ },
22
+ "./package.json": "./package.json"
23
+ },
24
+ "keywords": [
25
+ "identity",
26
+ "authentication",
27
+ "postgresql",
28
+ "drizzle",
29
+ "janus",
30
+ "typescript"
31
+ ],
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/softistx/nxgt-janus.git",
35
+ "directory": "packages/janus-drizzle"
36
+ },
37
+ "publishConfig": {
38
+ "registry": "https://registry.npmjs.org",
39
+ "access": "public"
40
+ },
41
+ "scripts": {
42
+ "build": "bun run ../../build.ts",
43
+ "test": "bun test src",
44
+ "typecheck": "tsc --noEmit"
45
+ },
46
+ "nxgt": {
47
+ "entrypoints": [
48
+ "src/index.ts"
49
+ ]
50
+ },
51
+ "devDependencies": {
52
+ "@electric-sql/pglite": "0.5.8",
53
+ "@nxgt/drizzle": "0.6.1",
54
+ "@nxgt/janus": "^0.2.2",
55
+ "@types/bun": "^1.4.2",
56
+ "@types/pg": "^8.23.1",
57
+ "drizzle-kit": "1.0.0-rc.4",
58
+ "drizzle-orm": "1.0.0-rc.4",
59
+ "pg": "^8",
60
+ "postgres": "^3",
61
+ "zod": "^4.6.5"
62
+ },
63
+ "peerDependencies": {
64
+ "@nxgt/drizzle": ">=0.6.1 <1",
65
+ "@nxgt/janus": "^0.2.2",
66
+ "drizzle-orm": ">=1.0.0-rc.4 <2",
67
+ "typescript": "^6.0.3"
68
+ }
69
+ }