@aooth/auth 0.1.18 → 0.1.20

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.
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_clock = require("./clock-Bl-H3eqE.cjs");
3
3
  const require_payload = require("./payload-BJjvj8AH.cjs");
4
- const require_auth_code_store = require("./auth-code-store-B_m51OSB.cjs");
4
+ const require_dynamic_client_store = require("./dynamic-client-store-DAStgv2j.cjs");
5
5
  let node_crypto = require("node:crypto");
6
6
  //#region src/atscript-db/authz-stores.ts
7
7
  /**
@@ -9,7 +9,7 @@ let node_crypto = require("node:crypto");
9
9
  * fresh opaque `handle`; `get` rejects (and lazily evicts) a past-expiry row;
10
10
  * `delete` drops it once consumed. PK is `handle`, so reads/deletes are O(1).
11
11
  */
12
- var PendingAuthorizationStoreAtscriptDb = class extends require_auth_code_store.PendingAuthorizationStore {
12
+ var PendingAuthorizationStoreAtscriptDb = class extends require_dynamic_client_store.PendingAuthorizationStore {
13
13
  table;
14
14
  clock;
15
15
  ttlMs;
@@ -32,7 +32,9 @@ var PendingAuthorizationStoreAtscriptDb = class extends require_auth_code_store.
32
32
  createdAt: now,
33
33
  expiresAt,
34
34
  ...rec.clientId !== void 0 && { clientId: rec.clientId },
35
+ ...rec.clientName !== void 0 && { clientName: rec.clientName },
35
36
  ...rec.clientState !== void 0 && { clientState: rec.clientState },
37
+ ...rec.resource !== void 0 && { resource: rec.resource },
36
38
  ...rec.scope !== void 0 && { scope: rec.scope },
37
39
  ...rec.nonce !== void 0 && { nonce: rec.nonce },
38
40
  ...rec.idToken !== void 0 && { idToken: rec.idToken },
@@ -69,7 +71,9 @@ function rowToPending(row) {
69
71
  expiresAt: row.expiresAt
70
72
  };
71
73
  if (row.clientId != null) out.clientId = row.clientId;
74
+ if (row.clientName != null) out.clientName = row.clientName;
72
75
  if (row.clientState != null) out.clientState = row.clientState;
76
+ if (row.resource != null) out.resource = row.resource;
73
77
  if (row.scope != null) out.scope = row.scope;
74
78
  if (row.nonce != null) out.nonce = row.nonce;
75
79
  if (row.idToken != null) out.idToken = row.idToken;
@@ -86,7 +90,7 @@ function rowToPending(row) {
86
90
  * back-button replay loses the race and gets `null`. (The delete is the claim,
87
91
  * so no version column is needed — `deleteOne(PK)` is atomic per the DB engine.)
88
92
  */
89
- var AuthCodeStoreAtscriptDb = class extends require_auth_code_store.AuthCodeStore {
93
+ var AuthCodeStoreAtscriptDb = class extends require_dynamic_client_store.AuthCodeStore {
90
94
  table;
91
95
  clock;
92
96
  ttlMs;
@@ -107,6 +111,7 @@ var AuthCodeStoreAtscriptDb = class extends require_auth_code_store.AuthCodeStor
107
111
  expiresAt: this.clock.now() + this.ttlMs,
108
112
  ...rec.clientId !== void 0 && { clientId: rec.clientId },
109
113
  ...rec.scope !== void 0 && { scope: rec.scope },
114
+ ...rec.resource !== void 0 && { resource: rec.resource },
110
115
  ...rec.nonce !== void 0 && { nonce: rec.nonce },
111
116
  ...rec.idToken !== void 0 && { idToken: rec.idToken },
112
117
  ...rec.accessToken !== void 0 && { accessToken: rec.accessToken },
@@ -134,12 +139,83 @@ function rowToAuthCode(row) {
134
139
  };
135
140
  if (row.clientId != null) out.clientId = row.clientId;
136
141
  if (row.scope != null) out.scope = row.scope;
142
+ if (row.resource != null) out.resource = row.resource;
137
143
  if (row.nonce != null) out.nonce = row.nonce;
138
144
  if (row.idToken != null) out.idToken = row.idToken;
139
145
  if (row.accessToken != null) out.accessToken = row.accessToken;
140
146
  if (row.audience != null) out.audience = row.audience;
141
147
  return out;
142
148
  }
149
+ /**
150
+ * Durable {@link DynamicClientStore}. Long-lived rows (a connector caches its
151
+ * `client_id` across grants). `touch` is the portable findOne + replaceOne
152
+ * (same trick as `CredentialStoreAtscriptDb.touch` — no engine-specific patch
153
+ * op); `deleteUnusedBefore` is one mass `deleteMany` over
154
+ * `createdAt < cutoff && lastUsedAt unset` — the never-used GC.
155
+ */
156
+ var DynamicClientStoreAtscriptDb = class extends require_dynamic_client_store.DynamicClientStore {
157
+ table;
158
+ clock;
159
+ constructor(opts) {
160
+ super();
161
+ this.table = opts.table;
162
+ this.clock = opts.clock ?? require_clock.defaultClock;
163
+ }
164
+ async create(rec) {
165
+ const row = {
166
+ clientId: (0, node_crypto.randomUUID)(),
167
+ redirectUris: JSON.stringify(rec.redirectUris),
168
+ tokenEndpointAuthMethod: rec.tokenEndpointAuthMethod,
169
+ grantTypes: JSON.stringify(rec.grantTypes),
170
+ responseTypes: JSON.stringify(rec.responseTypes),
171
+ createdAt: this.clock.now(),
172
+ ...rec.clientName !== void 0 && { clientName: rec.clientName },
173
+ ...rec.scope !== void 0 && { scope: rec.scope }
174
+ };
175
+ await this.table.insertOne(row);
176
+ return rowToDynamicClient(row);
177
+ }
178
+ async get(clientId) {
179
+ const row = await this.table.findOne({ filter: { clientId } });
180
+ return row ? rowToDynamicClient(row) : null;
181
+ }
182
+ async delete(clientId) {
183
+ const { deletedCount } = await this.table.deleteOne(clientId);
184
+ return deletedCount > 0;
185
+ }
186
+ async count() {
187
+ return this.table.count({});
188
+ }
189
+ async touch(clientId, at) {
190
+ const row = await this.table.findOne({ filter: { clientId } });
191
+ if (!row) return;
192
+ await this.table.replaceOne({
193
+ ...row,
194
+ lastUsedAt: at
195
+ });
196
+ }
197
+ async deleteUnusedBefore(cutoff) {
198
+ const { deletedCount } = await this.table.deleteMany({
199
+ createdAt: { $lt: cutoff },
200
+ lastUsedAt: { $exists: false }
201
+ });
202
+ return deletedCount;
203
+ }
204
+ };
205
+ function rowToDynamicClient(row) {
206
+ const out = {
207
+ clientId: row.clientId,
208
+ redirectUris: JSON.parse(row.redirectUris),
209
+ tokenEndpointAuthMethod: row.tokenEndpointAuthMethod,
210
+ grantTypes: JSON.parse(row.grantTypes),
211
+ responseTypes: JSON.parse(row.responseTypes),
212
+ createdAt: row.createdAt
213
+ };
214
+ if (row.clientName != null) out.clientName = row.clientName;
215
+ if (row.scope != null) out.scope = row.scope;
216
+ if (row.lastUsedAt != null) out.lastUsedAt = row.lastUsedAt;
217
+ return out;
218
+ }
143
219
  //#endregion
144
220
  //#region src/atscript-db/index.ts
145
221
  /**
@@ -157,13 +233,15 @@ function rowToAuthCode(row) {
157
233
  */
158
234
  var CredentialStoreAtscriptDb = class {
159
235
  table;
236
+ metadataField;
160
237
  constructor(opts) {
161
238
  this.table = opts.table;
239
+ this.metadataField = opts.metadataField;
162
240
  }
163
241
  async persist(state, ttl) {
164
242
  const token = (0, node_crypto.randomUUID)();
165
243
  const expiresAt = typeof ttl === "number" ? Date.now() + ttl : state.expiresAt;
166
- await this.table.insertOne(stateToRow(state, token, expiresAt));
244
+ await this.table.insertOne(stateToRow(state, token, expiresAt, this.metadataField));
167
245
  return token;
168
246
  }
169
247
  async retrieve(token) {
@@ -173,7 +251,7 @@ var CredentialStoreAtscriptDb = class {
173
251
  await this.table.deleteOne(token).catch(() => {});
174
252
  return null;
175
253
  }
176
- return rowToState(row);
254
+ return rowToState(row, this.metadataField);
177
255
  }
178
256
  async consume(token) {
179
257
  const state = await this.retrieve(token);
@@ -187,7 +265,7 @@ var CredentialStoreAtscriptDb = class {
187
265
  await this.revoke(token);
188
266
  return token;
189
267
  }
190
- await this.table.replaceOne(stateToRow(state, token, state.expiresAt));
268
+ await this.table.replaceOne(stateToRow(state, token, state.expiresAt, this.metadataField));
191
269
  return token;
192
270
  }
193
271
  async revoke(token) {
@@ -216,7 +294,7 @@ var CredentialStoreAtscriptDb = class {
216
294
  continue;
217
295
  }
218
296
  out.push({
219
- ...rowToState(row),
297
+ ...rowToState(row, this.metadataField),
220
298
  token: row.token
221
299
  });
222
300
  }
@@ -229,30 +307,45 @@ var CredentialStoreAtscriptDb = class {
229
307
  * (so an envelope field wins a name clash), then the fixed envelope fields and
230
308
  * the resolved `token` + `expiresAt`. Shared by `persist` and `update`, which
231
309
  * differ only in the `expiresAt` they resolve.
310
+ *
311
+ * The envelope's `metadata` is written to the consumer's `metadataField`
312
+ * column (a dynamic key the static row type can't carry) — only when the
313
+ * field is configured; otherwise metadata is silently not persisted.
232
314
  */
233
- function stateToRow(state, token, expiresAt) {
234
- return {
315
+ function stateToRow(state, token, expiresAt, metadataField) {
316
+ const row = {
235
317
  ...require_payload.credentialPayloadOf(state),
236
318
  token,
237
319
  userId: state.userId,
238
320
  issuedAt: state.issuedAt,
239
321
  expiresAt,
240
322
  kind: state.kind,
241
- metadata: state.metadata,
242
323
  parentCredentialId: state.parentCredentialId,
243
324
  rotatedAt: state.rotatedAt,
244
325
  sessionId: state.sessionId,
245
326
  lastSeenAt: state.lastSeenAt
246
327
  };
328
+ if (metadataField !== void 0 && state.metadata !== void 0) row[metadataField] = state.metadata;
329
+ return row;
247
330
  }
248
- function rowToState(row) {
331
+ /**
332
+ * Inverse of {@link stateToRow}. The consumer's `metadataField` column maps
333
+ * back onto the envelope's `metadata` — and is excluded from the extracted
334
+ * payload (it is envelope data riding under a consumer-chosen name, not a
335
+ * typed payload field).
336
+ */
337
+ function rowToState(row, metadataField) {
249
338
  const state = {
250
339
  ...require_payload.credentialPayloadOf(row),
251
340
  userId: row.userId,
252
341
  issuedAt: row.issuedAt,
253
342
  expiresAt: row.expiresAt
254
343
  };
255
- if (row.metadata !== void 0) state.metadata = row.metadata;
344
+ if (metadataField !== void 0) {
345
+ delete state[metadataField];
346
+ const metadata = row[metadataField];
347
+ if (metadata !== void 0) state.metadata = metadata;
348
+ }
256
349
  if (row.kind === "access" || row.kind === "refresh") state.kind = row.kind;
257
350
  if (row.parentCredentialId !== void 0) state.parentCredentialId = row.parentCredentialId;
258
351
  if (row.rotatedAt !== void 0) state.rotatedAt = row.rotatedAt;
@@ -263,4 +356,5 @@ function rowToState(row) {
263
356
  //#endregion
264
357
  exports.AuthCodeStoreAtscriptDb = AuthCodeStoreAtscriptDb;
265
358
  exports.CredentialStoreAtscriptDb = CredentialStoreAtscriptDb;
359
+ exports.DynamicClientStoreAtscriptDb = DynamicClientStoreAtscriptDb;
266
360
  exports.PendingAuthorizationStoreAtscriptDb = PendingAuthorizationStoreAtscriptDb;
@@ -1,6 +1,6 @@
1
1
  import { a as CredentialState, t as CredentialStore } from "./store-BG6m6oSJ.cjs";
2
2
  import { t as Clock } from "./clock-BjXa0LXb.cjs";
3
- import { a as NewAuthCode, c as PendingAuthorization, l as PendingAuthorizationStore, n as AuthCodeStore, s as NewPendingAuthorization, t as AuthCode } from "./auth-code-store-BZ88d_tq.cjs";
3
+ import { a as NewDynamicClient, f as NewPendingAuthorization, m as PendingAuthorizationStore, n as DynamicClientStore, o as AuthCode, p as PendingAuthorization, s as AuthCodeStore, t as DynamicClient, u as NewAuthCode } from "./dynamic-client-store-DzqdUwnw.cjs";
4
4
 
5
5
  //#region src/atscript-db/authz-stores.d.ts
6
6
  /**
@@ -22,7 +22,9 @@ interface PendingAuthorizationRow {
22
22
  redirectUri: string;
23
23
  codeChallenge: string;
24
24
  clientId?: string;
25
+ clientName?: string;
25
26
  clientState?: string;
27
+ resource?: string;
26
28
  scope?: string;
27
29
  nonce?: string;
28
30
  idToken?: boolean;
@@ -84,6 +86,7 @@ interface AuthCodeRow {
84
86
  redirectUri: string;
85
87
  clientId?: string;
86
88
  scope?: string;
89
+ resource?: string;
87
90
  nonce?: string;
88
91
  idToken?: boolean;
89
92
  accessToken?: boolean;
@@ -130,6 +133,71 @@ declare class AuthCodeStoreAtscriptDb extends AuthCodeStore {
130
133
  }>;
131
134
  consume(code: string): Promise<AuthCode | null>;
132
135
  }
136
+ /**
137
+ * Persisted row — mirrors `AoothDynamicClient` (`dynamic-client.as`). The
138
+ * three array fields are JSON-STRING columns (same opaque-string pattern as
139
+ * `tokenPolicy` above): a `string[]` column would need engine-specific array
140
+ * support, and all matching happens in `DynamicClientPolicy` after parse.
141
+ */
142
+ interface DynamicClientRow {
143
+ clientId: string;
144
+ clientName?: string;
145
+ /** `JSON.stringify(string[])`. */
146
+ redirectUris: string;
147
+ tokenEndpointAuthMethod: string;
148
+ /** `JSON.stringify(string[])`. */
149
+ grantTypes: string;
150
+ /** `JSON.stringify(string[])`. */
151
+ responseTypes: string;
152
+ scope?: string;
153
+ createdAt: number;
154
+ lastUsedAt?: number;
155
+ }
156
+ /** Structural surface of `AtscriptDbTable` for the dynamic-client adapter. */
157
+ interface DynamicClientTable {
158
+ insertOne(row: DynamicClientRow): Promise<{
159
+ insertedId: unknown;
160
+ }>;
161
+ findOne(query: {
162
+ filter: Record<string, unknown>;
163
+ }): Promise<DynamicClientRow | null>;
164
+ count(query: {
165
+ filter?: Record<string, unknown>;
166
+ }): Promise<number>;
167
+ replaceOne(row: DynamicClientRow): Promise<{
168
+ matchedCount: number;
169
+ modifiedCount: number;
170
+ }>;
171
+ deleteOne(idOrPk: unknown): Promise<{
172
+ deletedCount: number;
173
+ }>;
174
+ deleteMany(filter: Record<string, unknown>): Promise<{
175
+ deletedCount: number;
176
+ }>;
177
+ }
178
+ interface DynamicClientStoreAtscriptDbOptions {
179
+ table: DynamicClientTable;
180
+ /** Injectable clock for deterministic `createdAt`. Defaults to {@link defaultClock}. */
181
+ clock?: Clock;
182
+ }
183
+ /**
184
+ * Durable {@link DynamicClientStore}. Long-lived rows (a connector caches its
185
+ * `client_id` across grants). `touch` is the portable findOne + replaceOne
186
+ * (same trick as `CredentialStoreAtscriptDb.touch` — no engine-specific patch
187
+ * op); `deleteUnusedBefore` is one mass `deleteMany` over
188
+ * `createdAt < cutoff && lastUsedAt unset` — the never-used GC.
189
+ */
190
+ declare class DynamicClientStoreAtscriptDb extends DynamicClientStore {
191
+ private readonly table;
192
+ private readonly clock;
193
+ constructor(opts: DynamicClientStoreAtscriptDbOptions);
194
+ create(rec: NewDynamicClient): Promise<DynamicClient>;
195
+ get(clientId: string): Promise<DynamicClient | null>;
196
+ delete(clientId: string): Promise<boolean>;
197
+ count(): Promise<number>;
198
+ touch(clientId: string, at: number): Promise<void>;
199
+ deleteUnusedBefore(cutoff: number): Promise<number>;
200
+ }
133
201
  //#endregion
134
202
  //#region src/atscript-db/index.d.ts
135
203
  /**
@@ -142,6 +210,11 @@ declare class AuthCodeStoreAtscriptDb extends AuthCodeStore {
142
210
  * The consumer's typed payload `TPayload` (the root fields they add to their
143
211
  * `extends AoothAuthCredential` model) is intersected flat — those become real
144
212
  * typed columns, replacing the dropped free-form `claims` blob.
213
+ *
214
+ * There is NO `metadata` envelope column: credential metadata is
215
+ * consumer-declared (a fully-typed `@db.json` field on the extending model,
216
+ * marked `@aooth.auth.metadata`) and mapped dynamically through the
217
+ * `metadataField` option below.
145
218
  */
146
219
  type AuthCredentialRow<TPayload extends object = object> = {
147
220
  token: string;
@@ -149,12 +222,6 @@ type AuthCredentialRow<TPayload extends object = object> = {
149
222
  issuedAt: number;
150
223
  expiresAt: number;
151
224
  kind?: string;
152
- metadata?: {
153
- ip?: string;
154
- userAgent?: string;
155
- fingerprint?: string;
156
- label?: string;
157
- };
158
225
  parentCredentialId?: string;
159
226
  rotatedAt?: number;
160
227
  sessionId?: string;
@@ -190,6 +257,15 @@ interface AuthCredentialTable<TPayload extends object = object> {
190
257
  }
191
258
  interface CredentialStoreAtscriptDbOptions<TPayload extends object> {
192
259
  table: AuthCredentialTable<TPayload>;
260
+ /**
261
+ * Name of the consumer's `@aooth.auth.metadata`-annotated column — the
262
+ * fully-typed `@db.json` field declared on their `extends AoothAuthCredential`
263
+ * model that persists the envelope's `metadata`. Resolved at boot by
264
+ * `getAoothCredentialMetadataSpec` (`@aooth/arbac-moost/atscript`) and
265
+ * threaded here as plain config — same pattern as `UserStore.handleFields`.
266
+ * Absent → metadata is not persisted/read by this store.
267
+ */
268
+ metadataField?: string;
193
269
  }
194
270
  /**
195
271
  * atscript-db-backed `CredentialStore`.
@@ -206,6 +282,7 @@ interface CredentialStoreAtscriptDbOptions<TPayload extends object> {
206
282
  */
207
283
  declare class CredentialStoreAtscriptDb<TPayload extends object = object> implements CredentialStore<TPayload> {
208
284
  private readonly table;
285
+ private readonly metadataField;
209
286
  constructor(opts: CredentialStoreAtscriptDbOptions<TPayload>);
210
287
  persist(state: CredentialState & TPayload, ttl?: number): Promise<string>;
211
288
  retrieve(token: string): Promise<(CredentialState & TPayload) | null>;
@@ -219,4 +296,4 @@ declare class CredentialStoreAtscriptDb<TPayload extends object = object> implem
219
296
  }>>;
220
297
  }
221
298
  //#endregion
222
- export { AuthCodeRow, AuthCodeStoreAtscriptDb, AuthCodeStoreAtscriptDbOptions, AuthCodeTable, AuthCredentialRow, AuthCredentialTable, CredentialStoreAtscriptDb, PendingAuthorizationRow, PendingAuthorizationStoreAtscriptDb, PendingAuthorizationStoreAtscriptDbOptions, PendingAuthorizationTable };
299
+ export { AuthCodeRow, AuthCodeStoreAtscriptDb, AuthCodeStoreAtscriptDbOptions, AuthCodeTable, AuthCredentialRow, AuthCredentialTable, CredentialStoreAtscriptDb, DynamicClientRow, DynamicClientStoreAtscriptDb, DynamicClientStoreAtscriptDbOptions, DynamicClientTable, PendingAuthorizationRow, PendingAuthorizationStoreAtscriptDb, PendingAuthorizationStoreAtscriptDbOptions, PendingAuthorizationTable };
@@ -1,6 +1,6 @@
1
1
  import { a as CredentialState, t as CredentialStore } from "./store-BG6m6oSJ.mjs";
2
2
  import { t as Clock } from "./clock-BjXa0LXb.mjs";
3
- import { a as NewAuthCode, c as PendingAuthorization, l as PendingAuthorizationStore, n as AuthCodeStore, s as NewPendingAuthorization, t as AuthCode } from "./auth-code-store-Om5MXNUT.mjs";
3
+ import { a as NewDynamicClient, f as NewPendingAuthorization, m as PendingAuthorizationStore, n as DynamicClientStore, o as AuthCode, p as PendingAuthorization, s as AuthCodeStore, t as DynamicClient, u as NewAuthCode } from "./dynamic-client-store-oHEsQaJg.mjs";
4
4
 
5
5
  //#region src/atscript-db/authz-stores.d.ts
6
6
  /**
@@ -22,7 +22,9 @@ interface PendingAuthorizationRow {
22
22
  redirectUri: string;
23
23
  codeChallenge: string;
24
24
  clientId?: string;
25
+ clientName?: string;
25
26
  clientState?: string;
27
+ resource?: string;
26
28
  scope?: string;
27
29
  nonce?: string;
28
30
  idToken?: boolean;
@@ -84,6 +86,7 @@ interface AuthCodeRow {
84
86
  redirectUri: string;
85
87
  clientId?: string;
86
88
  scope?: string;
89
+ resource?: string;
87
90
  nonce?: string;
88
91
  idToken?: boolean;
89
92
  accessToken?: boolean;
@@ -130,6 +133,71 @@ declare class AuthCodeStoreAtscriptDb extends AuthCodeStore {
130
133
  }>;
131
134
  consume(code: string): Promise<AuthCode | null>;
132
135
  }
136
+ /**
137
+ * Persisted row — mirrors `AoothDynamicClient` (`dynamic-client.as`). The
138
+ * three array fields are JSON-STRING columns (same opaque-string pattern as
139
+ * `tokenPolicy` above): a `string[]` column would need engine-specific array
140
+ * support, and all matching happens in `DynamicClientPolicy` after parse.
141
+ */
142
+ interface DynamicClientRow {
143
+ clientId: string;
144
+ clientName?: string;
145
+ /** `JSON.stringify(string[])`. */
146
+ redirectUris: string;
147
+ tokenEndpointAuthMethod: string;
148
+ /** `JSON.stringify(string[])`. */
149
+ grantTypes: string;
150
+ /** `JSON.stringify(string[])`. */
151
+ responseTypes: string;
152
+ scope?: string;
153
+ createdAt: number;
154
+ lastUsedAt?: number;
155
+ }
156
+ /** Structural surface of `AtscriptDbTable` for the dynamic-client adapter. */
157
+ interface DynamicClientTable {
158
+ insertOne(row: DynamicClientRow): Promise<{
159
+ insertedId: unknown;
160
+ }>;
161
+ findOne(query: {
162
+ filter: Record<string, unknown>;
163
+ }): Promise<DynamicClientRow | null>;
164
+ count(query: {
165
+ filter?: Record<string, unknown>;
166
+ }): Promise<number>;
167
+ replaceOne(row: DynamicClientRow): Promise<{
168
+ matchedCount: number;
169
+ modifiedCount: number;
170
+ }>;
171
+ deleteOne(idOrPk: unknown): Promise<{
172
+ deletedCount: number;
173
+ }>;
174
+ deleteMany(filter: Record<string, unknown>): Promise<{
175
+ deletedCount: number;
176
+ }>;
177
+ }
178
+ interface DynamicClientStoreAtscriptDbOptions {
179
+ table: DynamicClientTable;
180
+ /** Injectable clock for deterministic `createdAt`. Defaults to {@link defaultClock}. */
181
+ clock?: Clock;
182
+ }
183
+ /**
184
+ * Durable {@link DynamicClientStore}. Long-lived rows (a connector caches its
185
+ * `client_id` across grants). `touch` is the portable findOne + replaceOne
186
+ * (same trick as `CredentialStoreAtscriptDb.touch` — no engine-specific patch
187
+ * op); `deleteUnusedBefore` is one mass `deleteMany` over
188
+ * `createdAt < cutoff && lastUsedAt unset` — the never-used GC.
189
+ */
190
+ declare class DynamicClientStoreAtscriptDb extends DynamicClientStore {
191
+ private readonly table;
192
+ private readonly clock;
193
+ constructor(opts: DynamicClientStoreAtscriptDbOptions);
194
+ create(rec: NewDynamicClient): Promise<DynamicClient>;
195
+ get(clientId: string): Promise<DynamicClient | null>;
196
+ delete(clientId: string): Promise<boolean>;
197
+ count(): Promise<number>;
198
+ touch(clientId: string, at: number): Promise<void>;
199
+ deleteUnusedBefore(cutoff: number): Promise<number>;
200
+ }
133
201
  //#endregion
134
202
  //#region src/atscript-db/index.d.ts
135
203
  /**
@@ -142,6 +210,11 @@ declare class AuthCodeStoreAtscriptDb extends AuthCodeStore {
142
210
  * The consumer's typed payload `TPayload` (the root fields they add to their
143
211
  * `extends AoothAuthCredential` model) is intersected flat — those become real
144
212
  * typed columns, replacing the dropped free-form `claims` blob.
213
+ *
214
+ * There is NO `metadata` envelope column: credential metadata is
215
+ * consumer-declared (a fully-typed `@db.json` field on the extending model,
216
+ * marked `@aooth.auth.metadata`) and mapped dynamically through the
217
+ * `metadataField` option below.
145
218
  */
146
219
  type AuthCredentialRow<TPayload extends object = object> = {
147
220
  token: string;
@@ -149,12 +222,6 @@ type AuthCredentialRow<TPayload extends object = object> = {
149
222
  issuedAt: number;
150
223
  expiresAt: number;
151
224
  kind?: string;
152
- metadata?: {
153
- ip?: string;
154
- userAgent?: string;
155
- fingerprint?: string;
156
- label?: string;
157
- };
158
225
  parentCredentialId?: string;
159
226
  rotatedAt?: number;
160
227
  sessionId?: string;
@@ -190,6 +257,15 @@ interface AuthCredentialTable<TPayload extends object = object> {
190
257
  }
191
258
  interface CredentialStoreAtscriptDbOptions<TPayload extends object> {
192
259
  table: AuthCredentialTable<TPayload>;
260
+ /**
261
+ * Name of the consumer's `@aooth.auth.metadata`-annotated column — the
262
+ * fully-typed `@db.json` field declared on their `extends AoothAuthCredential`
263
+ * model that persists the envelope's `metadata`. Resolved at boot by
264
+ * `getAoothCredentialMetadataSpec` (`@aooth/arbac-moost/atscript`) and
265
+ * threaded here as plain config — same pattern as `UserStore.handleFields`.
266
+ * Absent → metadata is not persisted/read by this store.
267
+ */
268
+ metadataField?: string;
193
269
  }
194
270
  /**
195
271
  * atscript-db-backed `CredentialStore`.
@@ -206,6 +282,7 @@ interface CredentialStoreAtscriptDbOptions<TPayload extends object> {
206
282
  */
207
283
  declare class CredentialStoreAtscriptDb<TPayload extends object = object> implements CredentialStore<TPayload> {
208
284
  private readonly table;
285
+ private readonly metadataField;
209
286
  constructor(opts: CredentialStoreAtscriptDbOptions<TPayload>);
210
287
  persist(state: CredentialState & TPayload, ttl?: number): Promise<string>;
211
288
  retrieve(token: string): Promise<(CredentialState & TPayload) | null>;
@@ -219,4 +296,4 @@ declare class CredentialStoreAtscriptDb<TPayload extends object = object> implem
219
296
  }>>;
220
297
  }
221
298
  //#endregion
222
- export { AuthCodeRow, AuthCodeStoreAtscriptDb, AuthCodeStoreAtscriptDbOptions, AuthCodeTable, AuthCredentialRow, AuthCredentialTable, CredentialStoreAtscriptDb, PendingAuthorizationRow, PendingAuthorizationStoreAtscriptDb, PendingAuthorizationStoreAtscriptDbOptions, PendingAuthorizationTable };
299
+ export { AuthCodeRow, AuthCodeStoreAtscriptDb, AuthCodeStoreAtscriptDbOptions, AuthCodeTable, AuthCredentialRow, AuthCredentialTable, CredentialStoreAtscriptDb, DynamicClientRow, DynamicClientStoreAtscriptDb, DynamicClientStoreAtscriptDbOptions, DynamicClientTable, PendingAuthorizationRow, PendingAuthorizationStoreAtscriptDb, PendingAuthorizationStoreAtscriptDbOptions, PendingAuthorizationTable };