@korajs/auth 1.0.0-beta.13 → 1.0.0-beta.14

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@korajs/auth",
3
- "version": "1.0.0-beta.13",
3
+ "version": "1.0.0-beta.14",
4
4
  "description": "Offline-first authentication for Kora.js",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -67,7 +67,7 @@
67
67
  "!src/**/*.test.ts"
68
68
  ],
69
69
  "dependencies": {
70
- "@korajs/core": "1.0.0-beta.13"
70
+ "@korajs/core": "1.0.0-beta.14"
71
71
  },
72
72
  "devDependencies": {
73
73
  "@types/better-sqlite3": "^7.6.13",
@@ -78,9 +78,9 @@
78
78
  "typescript": "^5.7.0",
79
79
  "vitest": "^3.0.0",
80
80
  "vue": "^3.5.0",
81
- "@korajs/server": "1.0.0-beta.13",
82
- "@korajs/store": "1.0.0-beta.13",
83
- "@korajs/sync": "1.0.0-beta.13"
81
+ "@korajs/server": "1.0.0-beta.14",
82
+ "@korajs/sync": "1.0.0-beta.14",
83
+ "@korajs/store": "1.0.0-beta.14"
84
84
  },
85
85
  "peerDependencies": {
86
86
  "react": "^18.0.0 || ^19.0.0",
@@ -13,6 +13,18 @@ import { DeviceOwnershipError, DuplicateEmailError } from './user-store'
13
13
  interface PostgresClient {
14
14
  begin<T>(fn: (sql: PostgresClient) => Promise<T>): Promise<T>
15
15
  (template: TemplateStringsArray, ...args: unknown[]): Promise<Record<string, unknown>[]>
16
+ /** postgres-js: close every connection of the pool (optional in this subset). */
17
+ end?(options?: { timeout?: number }): Promise<void>
18
+ }
19
+
20
+ /** Options for {@link PostgresUserStore}. */
21
+ export interface PostgresUserStoreOptions {
22
+ /**
23
+ * Whether {@link PostgresUserStore.close} ends the client's connections. True for
24
+ * a store made by `createPostgresUserStore` (it opened the client); false by
25
+ * default for a client you pass in, which you end yourself.
26
+ */
27
+ ownsClient?: boolean
16
28
  }
17
29
 
18
30
  /**
@@ -33,6 +45,8 @@ interface PostgresClient {
33
45
  * connectionString: 'postgres://user:pass@localhost:5432/mydb',
34
46
  * })
35
47
  * const routes = new BuiltInAuthRoutes({ userStore, tokenManager })
48
+ * // On shutdown (or at the end of a script):
49
+ * await userStore.close()
36
50
  * ```
37
51
  */
38
52
  export class PostgresUserStore implements UserStore {
@@ -41,11 +55,34 @@ export class PostgresUserStore implements UserStore {
41
55
 
42
56
  private revocationStore: PostgresTokenRevocationStore | null = null
43
57
 
44
- constructor(sql: PostgresClient) {
58
+ private readonly ownsClient: boolean
59
+ private closed = false
60
+
61
+ constructor(sql: PostgresClient, options: PostgresUserStoreOptions = {}) {
45
62
  this.sql = sql
63
+ this.ownsClient = options.ownsClient ?? false
46
64
  this.ready = this.ensureTables()
47
65
  }
48
66
 
67
+ /**
68
+ * Release the store's database connections, so a script using it can exit. Waits
69
+ * for table creation to settle first. A client passed to the constructor is left
70
+ * open unless `ownsClient` was set (end it yourself). Idempotent.
71
+ *
72
+ * @example
73
+ * ```typescript
74
+ * const users = await createPostgresUserStore({ connectionString })
75
+ * const device = await users.findDevice(nodeId)
76
+ * await users.close()
77
+ * ```
78
+ */
79
+ async close(): Promise<void> {
80
+ if (this.closed) return
81
+ this.closed = true
82
+ await this.ready.catch(() => {})
83
+ if (this.ownsClient) await this.sql.end?.({ timeout: 5 })
84
+ }
85
+
49
86
  /** Token revocations stored in the same Postgres database as the users. */
50
87
  getTokenRevocationStore(): TokenRevocationStore {
51
88
  if (!this.revocationStore) {
@@ -262,7 +299,7 @@ export async function createPostgresUserStore(options: {
262
299
  }): Promise<PostgresUserStore> {
263
300
  const postgresClient = await loadPostgresDeps()
264
301
  const sql = postgresClient(options.connectionString) as unknown as PostgresClient
265
- return new PostgresUserStore(sql)
302
+ return new PostgresUserStore(sql, { ownsClient: true })
266
303
  }
267
304
 
268
305
  async function loadPostgresDeps(): Promise<(connectionString: string) => unknown> {
@@ -1,4 +1,6 @@
1
1
  import { randomUUID } from 'node:crypto'
2
+ import { mkdirSync } from 'node:fs'
3
+ import { dirname } from 'node:path'
2
4
  import {
3
5
  type SqliteRevocationDatabase,
4
6
  SqliteTokenRevocationStore,
@@ -278,6 +280,7 @@ export async function createSqliteUserStore(options: {
278
280
  filename: string
279
281
  }): Promise<SqliteUserStore> {
280
282
  const Database = await loadBetterSqlite3()
283
+ ensureDatabaseDirectory(options.filename)
281
284
  const db = new Database(options.filename)
282
285
  return new SqliteUserStore(db as unknown as SqliteDatabase)
283
286
  }
@@ -320,3 +323,13 @@ function rowToDevice(row: DeviceRow): AuthDevice {
320
323
  lastSeenAt: row.last_seen_at,
321
324
  }
322
325
  }
326
+
327
+ /**
328
+ * Create the directory a SQLite database file lives in, so a default such as
329
+ * `./.kora/kora-server.db` works on a fresh checkout. In-memory databases and `file:`
330
+ * URIs are left alone.
331
+ */
332
+ function ensureDatabaseDirectory(filename: string): void {
333
+ if (filename === '' || filename === ':memory:' || filename.startsWith('file:')) return
334
+ mkdirSync(dirname(filename), { recursive: true })
335
+ }
@@ -1,4 +1,6 @@
1
1
  import { randomUUID } from 'node:crypto'
2
+ import { mkdirSync } from 'node:fs'
3
+ import { dirname } from 'node:path'
2
4
  import { DuplicateLinkedIdentityError, type LinkedIdentityStore } from './linked-identity-store'
3
5
  import type { LinkedIdentity, OAuthState, OAuthStateStore } from './oauth-types'
4
6
 
@@ -219,6 +221,7 @@ export async function createSqliteOAuthStores(options: { filename: string }): Pr
219
221
  linkedIdentityStore: SqliteLinkedIdentityStore
220
222
  }> {
221
223
  const Database = await loadBetterSqlite3()
224
+ ensureDatabaseDirectory(options.filename)
222
225
  const db = new Database(options.filename) as unknown as SqliteDatabase
223
226
  return {
224
227
  stateStore: new SqliteOAuthStateStore(db),
@@ -270,3 +273,13 @@ function rowToLinkedIdentity(row: LinkedIdentityRow): LinkedIdentity {
270
273
  linkedAt: row.linked_at,
271
274
  }
272
275
  }
276
+
277
+ /**
278
+ * Create the directory a SQLite database file lives in, so a default such as
279
+ * `./.kora/kora-server.db` works on a fresh checkout. In-memory databases and `file:`
280
+ * URIs are left alone.
281
+ */
282
+ function ensureDatabaseDirectory(filename: string): void {
283
+ if (filename === '' || filename === ':memory:' || filename.startsWith('file:')) return
284
+ mkdirSync(dirname(filename), { recursive: true })
285
+ }
package/src/server.ts CHANGED
@@ -103,7 +103,11 @@ export type { UserStore, AuthUser, StoredUser, AuthDevice } from './provider/bui
103
103
  export { SqliteUserStore, createSqliteUserStore } from './provider/built-in/sqlite-user-store'
104
104
 
105
105
  // === PostgreSQL User Store ===
106
- export { PostgresUserStore, createPostgresUserStore } from './provider/built-in/postgres-user-store'
106
+ export {
107
+ PostgresUserStore,
108
+ type PostgresUserStoreOptions,
109
+ createPostgresUserStore,
110
+ } from './provider/built-in/postgres-user-store'
107
111
 
108
112
  // === Provider Adapter ===
109
113
  export { BuiltInProvider, AuthProviderError } from './provider/adapter'