@cortexkit/common-auth 0.1.3 → 0.2.1

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 (47) hide show
  1. package/dist/quota/codec.d.ts +11 -0
  2. package/dist/quota/codec.js +12 -0
  3. package/dist/quota/index.d.ts +8 -0
  4. package/dist/quota/index.js +4 -0
  5. package/dist/quota/map.d.ts +63 -0
  6. package/dist/quota/map.js +105 -0
  7. package/dist/quota/merge.d.ts +48 -0
  8. package/dist/quota/merge.js +162 -0
  9. package/dist/quota/projection.d.ts +57 -0
  10. package/dist/quota/projection.js +127 -0
  11. package/dist/routing/admission.d.ts +93 -0
  12. package/dist/routing/admission.js +140 -0
  13. package/dist/routing/index.d.ts +8 -0
  14. package/dist/routing/index.js +4 -0
  15. package/dist/routing/ordered.d.ts +47 -0
  16. package/dist/routing/ordered.js +58 -0
  17. package/dist/routing/pins.d.ts +22 -0
  18. package/dist/routing/pins.js +34 -0
  19. package/dist/routing/sticky.d.ts +118 -0
  20. package/dist/routing/sticky.js +310 -0
  21. package/dist/store/attribution.d.ts +17 -0
  22. package/dist/store/attribution.js +46 -0
  23. package/dist/store/errors.d.ts +52 -0
  24. package/dist/store/errors.js +38 -0
  25. package/dist/store/hooks.d.ts +12 -0
  26. package/dist/store/hooks.js +34 -0
  27. package/dist/store/identity.d.ts +23 -0
  28. package/dist/store/identity.js +53 -0
  29. package/dist/store/index.d.ts +16 -0
  30. package/dist/store/index.js +5 -0
  31. package/dist/store/mutate.d.ts +124 -0
  32. package/dist/store/mutate.js +339 -0
  33. package/dist/store/pool.d.ts +97 -0
  34. package/dist/store/pool.js +101 -0
  35. package/dist/store/pull.d.ts +36 -0
  36. package/dist/store/pull.js +129 -0
  37. package/dist/store/refresh-lock.d.ts +63 -0
  38. package/dist/store/refresh-lock.js +125 -0
  39. package/dist/store/refresh.d.ts +48 -0
  40. package/dist/store/refresh.js +169 -0
  41. package/dist/store/rows.d.ts +53 -0
  42. package/dist/store/rows.js +250 -0
  43. package/dist/store/runtime.d.ts +28 -0
  44. package/dist/store/runtime.js +45 -0
  45. package/dist/store/schema.d.ts +133 -0
  46. package/dist/store/schema.js +323 -0
  47. package/package.json +13 -1
@@ -0,0 +1,250 @@
1
+ import { assertNotInsideHook } from './hooks.js';
2
+ import { DUPLICATE_IDENTITY_REASON, disableIdentityDuplicates, disableIn, recordIdentityIn, } from './identity.js';
3
+ import { runOperation, withTransaction } from './mutate.js';
4
+ import { readRow, refusal, rowLockSpec, unknownRow, } from './runtime.js';
5
+ import { credentialProblem, fingerprintOf, idProblem, isRecord, rosterRowFor, rotationStamp, rowLockKey, stateFieldsFor, storedCredential, } from './schema.js';
6
+ /** Fields of a state entry that belong to the credential it replaces. */
7
+ const CREDENTIAL_STATE_FIELDS = [
8
+ 'access',
9
+ 'refresh',
10
+ 'expires',
11
+ 'lastRefreshedAt',
12
+ 'apiKey',
13
+ ];
14
+ /**
15
+ * Writes a rotated credential into the state file (one write). A rotation is
16
+ * the same lineage: no epoch bump, no identity or quota change.
17
+ */
18
+ export async function rotateIn(rt, tx, id, credential, extra = {}) {
19
+ const prior = tx.stateAccount(id);
20
+ const priorStamp = typeof prior?.lastRefreshedAt === 'number'
21
+ ? prior.lastRefreshedAt
22
+ : undefined;
23
+ const stamp = credential.type === 'oauth'
24
+ ? (extra.stamp ?? rotationStamp(priorStamp, rt.ctx.now()))
25
+ : undefined;
26
+ const kept = { ...(prior ?? {}) };
27
+ for (const field of CREDENTIAL_STATE_FIELDS)
28
+ delete kept[field];
29
+ if (extra.clearErrors) {
30
+ delete kept.lastRefreshError;
31
+ delete kept.lastQuotaRefreshError;
32
+ delete kept.quota;
33
+ }
34
+ const raw = tx.rosterRow(id);
35
+ const stored = credential.type === 'api' && isRecord(raw)
36
+ ? storedCredential({
37
+ ...credential,
38
+ baseURL: String(raw.baseURL ?? credential.baseURL),
39
+ ...(raw.authHeader === 'x-api-key' ||
40
+ raw.authHeader === 'authorization-bearer'
41
+ ? { authHeader: raw.authHeader }
42
+ : {}),
43
+ }, stamp)
44
+ : storedCredential(credential, stamp);
45
+ tx.setStateAccount(id, { ...kept, ...stateFieldsFor(credential, stamp) });
46
+ await tx.commitState(stored);
47
+ return stored;
48
+ }
49
+ function checkInput(operation, id, credential) {
50
+ const idIssue = operation === 'add' ? idProblem(id) : undefined;
51
+ if (idIssue)
52
+ throw refusal(operation, id, 'invalid-input', idIssue);
53
+ const credentialIssue = credentialProblem(credential);
54
+ if (credentialIssue)
55
+ throw refusal(operation, id, 'invalid-input', credentialIssue);
56
+ }
57
+ function requireUsableRow(operation, id, row, credential) {
58
+ if (!row)
59
+ throw unknownRow(operation, id);
60
+ if (row.invalid)
61
+ throw refusal(operation, id, 'invalid-row', `row ${id} failed validation`);
62
+ if (credential && credential.type !== row.type)
63
+ throw refusal(operation, id, 'type-mismatch', `row ${id} holds a ${row.type} credential`);
64
+ return row;
65
+ }
66
+ /** Keying changed between the unlocked read and the locked one: retry. */
67
+ function keyChanged(operation, id) {
68
+ return refusal(operation, id, 'row-key-changed', `row ${id}'s wire identity changed while its lock was being taken`, true);
69
+ }
70
+ export async function addRow(rt, input, options = {}) {
71
+ assertNotInsideHook('add');
72
+ const { ctx } = rt;
73
+ const { id, credential, identity, label } = input;
74
+ const result = await runOperation(ctx, 'add', id, options.onFailure, async (locks, progress) => {
75
+ checkInput('add', id, credential);
76
+ if (ctx.removedIds.has(id))
77
+ throw refusal('add', id, 'id-removed', `id ${id} was removed from the roster in this process and is not reused`);
78
+ await locks.acquire(rowLockSpec(rt, { id, identity }));
79
+ if (credential.type === 'oauth')
80
+ await locks.acquire(options.providerLock ?? rt.providerLock);
81
+ return withTransaction(ctx, locks, progress, { operation: 'add', rowId: id }, async (tx) => {
82
+ const rows = tx.rows();
83
+ const fingerprint = fingerprintOf(credential);
84
+ const same = rows.find((row) => row.invalid === undefined && row.fingerprint === fingerprint);
85
+ if (same) {
86
+ const stored = await rotateIn(rt, tx, same.id, credential);
87
+ return { id: same.id, outcome: 'rotated', credential: stored };
88
+ }
89
+ const existing = rows.find((row) => row.id === id);
90
+ if (existing) {
91
+ if (existing.invalid)
92
+ throw refusal('add', id, 'invalid-row', `row ${id} is invalid`);
93
+ if (existing.credential)
94
+ throw refusal('add', id, 'id-exists', `row ${id} already holds a credential`);
95
+ if (existing.type !== credential.type)
96
+ throw refusal('add', id, 'type-mismatch', `row ${id} is a ${existing.type} row`);
97
+ // An earlier add wrote this row's config and stopped before the
98
+ // state write; writing the credential now completes it at epoch 1.
99
+ if (!existing.hasEntry) {
100
+ tx.setEntry(id, {
101
+ credentialEpoch: 1,
102
+ needsFirstReading: credential.type === 'oauth',
103
+ });
104
+ await tx.commitConfig();
105
+ }
106
+ const stored = await rotateIn(rt, tx, id, credential, {
107
+ clearErrors: true,
108
+ });
109
+ return { id, outcome: 'completed', credential: stored };
110
+ }
111
+ tx.roster().push(rosterRowFor({
112
+ id,
113
+ credential,
114
+ ...(identity !== undefined ? { identity } : {}),
115
+ ...(label !== undefined ? { label } : {}),
116
+ addedAt: ctx.now(),
117
+ }));
118
+ tx.setEntry(id, {
119
+ credentialEpoch: 1,
120
+ needsFirstReading: credential.type === 'oauth',
121
+ });
122
+ let outcome = 'added';
123
+ if (identity !== undefined && credential.type === 'oauth') {
124
+ // Same account, different credential: kept on disk, disabled.
125
+ const holder = rows.find((row) => row.invalid === undefined &&
126
+ row.type === 'oauth' &&
127
+ row.enabled &&
128
+ row.identity === identity);
129
+ if (holder) {
130
+ disableIn(tx, id, DUPLICATE_IDENTITY_REASON);
131
+ outcome = 'added-disabled';
132
+ }
133
+ }
134
+ await tx.commitConfig();
135
+ const stored = await rotateIn(rt, tx, id, credential);
136
+ return { id, outcome, credential: stored };
137
+ });
138
+ });
139
+ if (credential.type === 'oauth' &&
140
+ (result.outcome === 'added' || result.outcome === 'completed'))
141
+ rt.firePull(result.id, 'add');
142
+ return result;
143
+ }
144
+ export async function replaceRow(rt, id, credential, input = {}, options = {}) {
145
+ assertNotInsideHook('replace');
146
+ const { ctx } = rt;
147
+ const result = await runOperation(ctx, 'replace', id, options.onFailure, async (locks, progress) => {
148
+ checkInput('replace', id, credential);
149
+ const { row: seen } = await readRow(rt, 'replace', id);
150
+ await locks.acquire(rowLockSpec(rt, seen));
151
+ if (credential.type === 'oauth')
152
+ await locks.acquire(options.providerLock ?? rt.providerLock);
153
+ return withTransaction(ctx, locks, progress, { operation: 'replace', rowId: id }, async (tx) => {
154
+ const row = requireUsableRow('replace', id, tx.row(id), credential);
155
+ if (rowLockKey(row) !== rowLockKey(seen))
156
+ throw keyChanged('replace', id);
157
+ const entry = tx.entry(id) ?? {};
158
+ const priorEpoch = typeof entry.credentialEpoch === 'number'
159
+ ? entry.credentialEpoch
160
+ : 1;
161
+ const credentialEpoch = priorEpoch + 1;
162
+ const nextEntry = {
163
+ ...entry,
164
+ credentialEpoch,
165
+ needsFirstReading: true,
166
+ };
167
+ delete nextEntry.quota;
168
+ tx.setEntry(id, nextEntry);
169
+ const raw = tx.rosterRow(id);
170
+ if (input.identity !== undefined)
171
+ raw.accountId = input.identity;
172
+ else
173
+ delete raw.accountId;
174
+ if (credential.type === 'api') {
175
+ raw.baseURL = credential.baseURL.trim();
176
+ raw.authHeader = credential.authHeader ?? 'authorization-bearer';
177
+ }
178
+ if (input.identity !== undefined)
179
+ disableIdentityDuplicates(tx, input.identity);
180
+ await tx.commitConfig();
181
+ const stored = await rotateIn(rt, tx, id, credential, {
182
+ clearErrors: true,
183
+ });
184
+ return { id, credential: stored, credentialEpoch };
185
+ });
186
+ });
187
+ if (credential.type === 'oauth')
188
+ rt.firePull(id, 'replace');
189
+ return result;
190
+ }
191
+ export async function rotateRow(rt, id, credential, input = {}, options = {}) {
192
+ assertNotInsideHook('rotate');
193
+ const { ctx } = rt;
194
+ return runOperation(ctx, 'rotate', id, options.onFailure, async (locks, progress) => {
195
+ checkInput('rotate', id, credential);
196
+ const { row: seen } = await readRow(rt, 'rotate', id);
197
+ await locks.acquire(rowLockSpec(rt, seen));
198
+ if (input.identity !== undefined && credential.type === 'oauth')
199
+ await locks.acquire(options.providerLock ?? rt.providerLock);
200
+ return withTransaction(ctx, locks, progress, { operation: 'rotate', rowId: id }, async (tx) => {
201
+ const row = requireUsableRow('rotate', id, tx.row(id), credential);
202
+ if (rowLockKey(row) !== rowLockKey(seen))
203
+ throw keyChanged('rotate', id);
204
+ const stored = await rotateIn(rt, tx, id, credential);
205
+ let configChanged = false;
206
+ if (!row.hasEntry) {
207
+ tx.setEntry(id, {
208
+ credentialEpoch: 1,
209
+ needsFirstReading: row.type === 'oauth',
210
+ });
211
+ configChanged = true;
212
+ }
213
+ if (input.identity !== undefined && row.identity !== input.identity) {
214
+ recordIdentityIn(tx, id, input.identity);
215
+ configChanged = true;
216
+ }
217
+ if (configChanged)
218
+ await tx.commitConfig();
219
+ return { id, credential: stored };
220
+ });
221
+ });
222
+ }
223
+ export async function disableRow(rt, id, reason, options = {}) {
224
+ assertNotInsideHook('disable');
225
+ return runOperation(rt.ctx, 'disable', id, options.onFailure, async (locks, progress) => withTransaction(rt.ctx, locks, progress, { operation: 'disable', rowId: id }, async (tx) => {
226
+ if (!tx.rosterRow(id))
227
+ throw unknownRow('disable', id);
228
+ disableIn(tx, id, reason);
229
+ await tx.commitConfig();
230
+ return { id };
231
+ }));
232
+ }
233
+ export async function recordRowIdentity(rt, id, identity, options = {}) {
234
+ assertNotInsideHook('recordIdentity');
235
+ return runOperation(rt.ctx, 'recordIdentity', id, options.onFailure, async (locks, progress) => {
236
+ if (typeof identity !== 'string' || identity.length === 0)
237
+ throw refusal('recordIdentity', id, 'invalid-input', 'identity must be non-empty');
238
+ const { row: seen } = await readRow(rt, 'recordIdentity', id);
239
+ await locks.acquire(rowLockSpec(rt, seen));
240
+ await locks.acquire(options.providerLock ?? rt.providerLock);
241
+ return withTransaction(rt.ctx, locks, progress, { operation: 'recordIdentity', rowId: id }, async (tx) => {
242
+ const row = requireUsableRow('recordIdentity', id, tx.row(id));
243
+ if (rowLockKey(row) !== rowLockKey(seen))
244
+ throw keyChanged('recordIdentity', id);
245
+ const disabled = recordIdentityIn(tx, id, identity);
246
+ await tx.commitConfig();
247
+ return { id, disabled };
248
+ });
249
+ });
250
+ }
@@ -0,0 +1,28 @@
1
+ import { type PoolOperation, PoolOperationError } from './errors.js';
2
+ import { type ReadResult, type StoreContext } from './mutate.js';
3
+ import type { PoolLockOptions, PoolLockSpec } from './refresh-lock.js';
4
+ import { type PoolRow } from './schema.js';
5
+ /** Where a pull was fired from; `load` fires at most once per process per row. */
6
+ export type PullReason = 'load' | 'add' | 'replace' | 'admission';
7
+ /** What every operation module shares. */
8
+ export interface StoreRuntime {
9
+ ctx: StoreContext;
10
+ providerLock: PoolLockSpec;
11
+ rowLockOptions: Partial<PoolLockOptions>;
12
+ firePull(id: string, reason: PullReason): void;
13
+ }
14
+ /**
15
+ * The row lock's (name, path): keyed by the row's recorded wire identity when
16
+ * known, else its local id, beside the state file. The key is prefixed and
17
+ * URL-encoded so it can never name a store lock or leave the directory.
18
+ */
19
+ export declare function rowLockSpec(rt: StoreRuntime, row: Pick<PoolRow, 'id' | 'identity'>): PoolLockSpec;
20
+ /** An unlocked read that must find a ready pool holding the row. */
21
+ export declare function readRow(rt: StoreRuntime, operation: PoolOperation, id: string): Promise<{
22
+ result: Extract<ReadResult, {
23
+ status: 'ready';
24
+ }>;
25
+ row: PoolRow;
26
+ }>;
27
+ export declare function unknownRow(operation: PoolOperation, id: string): PoolOperationError;
28
+ export declare function refusal(operation: PoolOperation, id: string, kind: PoolOperationError['kind'], message: string, retryable?: boolean): PoolOperationError;
@@ -0,0 +1,45 @@
1
+ import { PoolOperationError } from './errors.js';
2
+ import { notReadyError, readPool, } from './mutate.js';
3
+ import { rowLockKey } from './schema.js';
4
+ /**
5
+ * The row lock's (name, path): keyed by the row's recorded wire identity when
6
+ * known, else its local id, beside the state file. The key is prefixed and
7
+ * URL-encoded so it can never name a store lock or leave the directory.
8
+ */
9
+ export function rowLockSpec(rt, row) {
10
+ return {
11
+ ...rt.rowLockOptions,
12
+ name: `row-${encodeURIComponent(rowLockKey(row))}`,
13
+ path: rt.ctx.statePath,
14
+ };
15
+ }
16
+ /** An unlocked read that must find a ready pool holding the row. */
17
+ export async function readRow(rt, operation, id) {
18
+ const result = await readPool(rt.ctx);
19
+ if (result.status !== 'ready')
20
+ throw notReadyError(result, operation, id);
21
+ const row = result.rows.find((candidate) => candidate.id === id);
22
+ if (!row)
23
+ throw unknownRow(operation, id);
24
+ return { result, row };
25
+ }
26
+ export function unknownRow(operation, id) {
27
+ return new PoolOperationError({
28
+ operation,
29
+ rowId: id,
30
+ phase: operation === 'pull' ? 'pull' : 'before-first-write',
31
+ retryable: false,
32
+ kind: 'unknown-row',
33
+ message: `no row ${id} in the pool`,
34
+ });
35
+ }
36
+ export function refusal(operation, id, kind, message, retryable = false) {
37
+ return new PoolOperationError({
38
+ operation,
39
+ rowId: id,
40
+ phase: operation === 'pull' ? 'pull' : 'before-first-write',
41
+ retryable,
42
+ kind,
43
+ message,
44
+ });
45
+ }
@@ -0,0 +1,133 @@
1
+ /** Top-level key of the config file that holds everything the pool adds. */
2
+ export declare const POOL_KEY = "commonAuthPool";
3
+ /** The pool schema this library reads and writes. */
4
+ export declare const POOL_SCHEMA_VERSION = 1;
5
+ /** Property of `commonAuthPool` holding the per-row entries, keyed by local id. */
6
+ export declare const POOL_ROWS_KEY = "rows";
7
+ /** The `version` older readers of the same files expect at the top level. */
8
+ export declare const LEGACY_STORE_VERSION = 1;
9
+ /**
10
+ * How far ahead of the clock a stored `lastRefreshedAt` may sit and still be
11
+ * trusted. Older readers of the state file apply the same bound, and a stamp
12
+ * strictly above it counts as absent when they compare two copies of a token.
13
+ */
14
+ export declare const REFRESH_STAMP_TOLERANCE_MS: number;
15
+ export type OAuthCredential = {
16
+ type: 'oauth';
17
+ access?: string;
18
+ refresh: string;
19
+ expires?: number;
20
+ };
21
+ export type ApiKeyCredential = {
22
+ type: 'api';
23
+ apiKey: string;
24
+ baseURL: string;
25
+ authHeader?: 'authorization-bearer' | 'x-api-key';
26
+ };
27
+ export type PoolCredential = OAuthCredential | ApiKeyCredential;
28
+ /** A credential as stored: an OAuth credential also carries its refresh stamp. */
29
+ export type StoredCredential = (OAuthCredential & {
30
+ lastRefreshedAt?: number;
31
+ }) | ApiKeyCredential;
32
+ /** Quota codec supplied by `/quota`: the store never interprets the map itself. */
33
+ export interface QuotaCodec {
34
+ validate(value: unknown): boolean;
35
+ merge(stored: unknown | undefined, observation: unknown): unknown;
36
+ }
37
+ /** One row of the pool as loaded. */
38
+ export interface PoolRow {
39
+ id: string;
40
+ type: 'oauth' | 'api';
41
+ label?: string;
42
+ enabled: boolean;
43
+ addedAt?: number;
44
+ /** Recorded wire identity (the roster row's `accountId`), when known. */
45
+ identity?: string;
46
+ /** Undefined when the state file holds no usable credential for the row. */
47
+ credential?: StoredCredential;
48
+ /** Stable hash of the credential's secret material; never persisted. */
49
+ fingerprint?: string;
50
+ /** Undefined when the row has no per-row entry yet. */
51
+ credentialEpoch?: number;
52
+ needsFirstReading: boolean;
53
+ disabledReason?: string;
54
+ /** The opaque quota map, as validated by the codec. */
55
+ quota?: unknown;
56
+ hasEntry: boolean;
57
+ /** A row that may be refreshed, pulled for, or admitted. */
58
+ candidate: boolean;
59
+ /** Set when the roster row or the per-row entry failed validation. */
60
+ invalid?: 'roster' | 'entry';
61
+ }
62
+ export type ConfigClassification = {
63
+ status: 'ready';
64
+ exists: boolean;
65
+ config: Record<string, unknown>;
66
+ } | {
67
+ status: 'pending-migration';
68
+ config: Record<string, unknown>;
69
+ } | {
70
+ status: 'error';
71
+ reason: string;
72
+ };
73
+ export type StateClassification = {
74
+ status: 'ready';
75
+ exists: boolean;
76
+ state: Record<string, unknown>;
77
+ } | {
78
+ status: 'error';
79
+ reason: string;
80
+ };
81
+ export declare function isRecord(value: unknown): value is Record<string, unknown>;
82
+ /**
83
+ * Ids are stored exactly as given. Older readers trim ids, so an id with
84
+ * surrounding whitespace would be renamed by them; such ids, empty ids and
85
+ * prototype keys are refused instead of being rewritten.
86
+ */
87
+ export declare function idProblem(id: unknown): string | undefined;
88
+ /** Same URL rule the older readers apply to an API-key row's `baseURL`. */
89
+ export declare function isValidBaseURL(value: unknown): boolean;
90
+ export declare function fingerprintOf(credential: PoolCredential | StoredCredential): string;
91
+ /** A parsed file, or the reason it could not be parsed. */
92
+ export type FileRead = {
93
+ exists: false;
94
+ } | {
95
+ exists: true;
96
+ value: unknown;
97
+ } | {
98
+ exists: true;
99
+ parseError: unknown;
100
+ };
101
+ export declare function classifyConfig(read: FileRead): ConfigClassification;
102
+ export declare function classifyState(read: FileRead): StateClassification;
103
+ export declare function rosterOf(config: Record<string, unknown>): unknown[];
104
+ export declare function entriesOf(config: Record<string, unknown>): Record<string, unknown>;
105
+ /**
106
+ * Builds the rows of a ready pool. A roster row the older readers would
107
+ * reject, a duplicate id, or a malformed per-row entry makes that one row
108
+ * invalid (never a candidate) and blocks nothing else.
109
+ */
110
+ export declare function buildRows(config: Record<string, unknown>, state: Record<string, unknown>, codec: QuotaCodec): PoolRow[];
111
+ /** The row-lock key: recorded wire identity when known, else the local id. */
112
+ export declare function rowLockKey(row: Pick<PoolRow, 'id' | 'identity'>): string;
113
+ /**
114
+ * The refresh stamp a write persists: the clock, raised to one past a prior
115
+ * stamp that is still trusted, so a stale copy of the previous token can never
116
+ * compare newer than the rotation. A prior stamp beyond the trust bound says
117
+ * nothing and is ignored.
118
+ */
119
+ export declare function rotationStamp(prior: number | undefined, now: number): number;
120
+ /** True when a rotation stamped now would itself be past the trust bound. */
121
+ export declare function rotationStampUntrusted(prior: number | undefined, now: number): boolean;
122
+ /** The legacy-shaped roster row for a new row. */
123
+ export declare function rosterRowFor(input: {
124
+ id: string;
125
+ credential: PoolCredential;
126
+ identity?: string;
127
+ label?: string;
128
+ addedAt: number;
129
+ }): Record<string, unknown>;
130
+ /** The state-file fields a credential occupies, in the older readers' names. */
131
+ export declare function stateFieldsFor(credential: PoolCredential, lastRefreshedAt: number | undefined): Record<string, unknown>;
132
+ export declare function storedCredential(credential: PoolCredential, lastRefreshedAt: number | undefined): StoredCredential;
133
+ export declare function credentialProblem(credential: unknown): string | undefined;