@kubun/store-credential 0.14.1 → 0.15.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.
- package/lib/api.d.ts +175 -2
- package/lib/api.js +261 -2
- package/lib/index.d.ts +2 -2
- package/lib/migrations.js +37 -0
- package/lib/tables.d.ts +99 -0
- package/package.json +5 -5
package/lib/api.d.ts
CHANGED
|
@@ -1,6 +1,54 @@
|
|
|
1
1
|
import type { Adapter } from '@kubun/db-adapter';
|
|
2
2
|
import type { Kysely } from 'kysely';
|
|
3
|
-
import type { CredentialEntry, CredentialEntryWithProvenance, CredentialFactorDescriptor, CredentialKey, CredentialKeyBranch, CredentialKeyState, CredentialOp, CredentialOpSubjectKind, CredentialTables, CredentialWrapping, CredentialWrappingWithBranch } from './tables.js';
|
|
3
|
+
import type { CredentialEntry, CredentialEntryWithProvenance, CredentialFactorDescriptor, CredentialKey, CredentialKeyBranch, CredentialKeyState, CredentialManifestKey, CredentialOp, CredentialOpSubjectKind, CredentialTables, CredentialWrapping, CredentialWrappingWithBranch } from './tables.js';
|
|
4
|
+
/**
|
|
5
|
+
* The persisted half of a credential manifest — a structural subset of the
|
|
6
|
+
* plugin's signed `CredentialManifestClaim`, so a verified claim can pass
|
|
7
|
+
* straight to {@link CredentialStoreAPI.putManifest} without mapping. This
|
|
8
|
+
* package neither signs nor verifies; the raw token rides alongside as `token`.
|
|
9
|
+
*/
|
|
10
|
+
export type CredentialManifestRecord = {
|
|
11
|
+
ownerDID: string;
|
|
12
|
+
recipientDID: string;
|
|
13
|
+
grantorDID: string;
|
|
14
|
+
epoch: number;
|
|
15
|
+
sequence: number;
|
|
16
|
+
keys: Array<CredentialManifestKey>;
|
|
17
|
+
};
|
|
18
|
+
export type ListManifestsParams = {
|
|
19
|
+
ownerDID: string;
|
|
20
|
+
recipientDID: string;
|
|
21
|
+
};
|
|
22
|
+
/** One stored manifest: its persisted record, the signed token that authored it, and delegation tokens. */
|
|
23
|
+
export type StoredCredentialManifest = {
|
|
24
|
+
record: CredentialManifestRecord;
|
|
25
|
+
token: string;
|
|
26
|
+
delegationTokens: Array<string>;
|
|
27
|
+
};
|
|
28
|
+
export type PutWrappingAuthorityParams = {
|
|
29
|
+
wrappingID: string;
|
|
30
|
+
envelope: string;
|
|
31
|
+
refreshSeq: number;
|
|
32
|
+
};
|
|
33
|
+
export type WrappingAuthority = {
|
|
34
|
+
envelope: string;
|
|
35
|
+
refreshSeq: number;
|
|
36
|
+
};
|
|
37
|
+
export type NextGrantorSequenceParams = {
|
|
38
|
+
ownerDID: string;
|
|
39
|
+
recipientDID: string;
|
|
40
|
+
grantorDID: string;
|
|
41
|
+
};
|
|
42
|
+
export type NextProvisioningEpochParams = {
|
|
43
|
+
ownerDID: string;
|
|
44
|
+
recipientDID: string;
|
|
45
|
+
grantorDID: string;
|
|
46
|
+
minEpoch: number;
|
|
47
|
+
};
|
|
48
|
+
export type ListDistributableKeysParams = {
|
|
49
|
+
ownerDID: string;
|
|
50
|
+
grantorDID: string;
|
|
51
|
+
};
|
|
4
52
|
/**
|
|
5
53
|
* The signed op that produced a row's current state, as the store holds it.
|
|
6
54
|
*
|
|
@@ -77,6 +125,13 @@ export type HeldCredentialSubject = {
|
|
|
77
125
|
* query context passes it today: it holds a viewer's DID and no signing key.
|
|
78
126
|
*/
|
|
79
127
|
export type CredentialStoreAPI = {
|
|
128
|
+
/**
|
|
129
|
+
* Run `fn` against a `CredentialStoreAPI` scoped to one atomic transaction.
|
|
130
|
+
* Nesting-safe: if `this` is already bound to an open Kysely transaction,
|
|
131
|
+
* `fn` runs inline against `this` instead of opening a second one (Kysely
|
|
132
|
+
* has no nested transactions), so callers can invoke this unconditionally.
|
|
133
|
+
*/
|
|
134
|
+
transaction<R>(fn: (store: CredentialStoreAPI) => Promise<R>): Promise<R>;
|
|
80
135
|
putKey(params: PutCredentialKeyParams, op: CredentialOpInput | null): Promise<void>;
|
|
81
136
|
getKey(keyID: string): Promise<CredentialKey | undefined>;
|
|
82
137
|
listKeys(ownerDID: string): Promise<Array<CredentialKey>>;
|
|
@@ -94,7 +149,17 @@ export type CredentialStoreAPI = {
|
|
|
94
149
|
branchID: string;
|
|
95
150
|
}): Promise<void>;
|
|
96
151
|
listKeyBranches(keyID: string, keyVersion: number): Promise<Array<CredentialKeyBranch>>;
|
|
97
|
-
|
|
152
|
+
/**
|
|
153
|
+
* `authority`, when present, upserts the wrapping's grantor-authority envelope
|
|
154
|
+
* in the SAME transaction as the wrapping row and its op, so a crash can't
|
|
155
|
+
* leave a held wrapping this device can't later prove was authorized. Omitted
|
|
156
|
+
* for an owner-signed wrapping. Gated strictly-greater on `refreshSeq`, same
|
|
157
|
+
* as {@link CredentialStoreAPI.putWrappingAuthority}.
|
|
158
|
+
*/
|
|
159
|
+
putWrapping(params: PutCredentialWrappingParams, op: CredentialOpInput | null, authority?: {
|
|
160
|
+
envelope: string;
|
|
161
|
+
refreshSeq: number;
|
|
162
|
+
}): Promise<void>;
|
|
98
163
|
getWrapping(wrappingID: string): Promise<CredentialWrapping | undefined>;
|
|
99
164
|
/** Every wrapping for a key, or only those at one version. */
|
|
100
165
|
listWrappings(keyID: string, keyVersion?: number): Promise<Array<CredentialWrapping>>;
|
|
@@ -172,5 +237,113 @@ export type CredentialStoreAPI = {
|
|
|
172
237
|
* namespace.
|
|
173
238
|
*/
|
|
174
239
|
listAliasKeys(namespace: string): Promise<Array<string>>;
|
|
240
|
+
/** Store a wrapping's grantor-authority envelope; gated strictly-greater on `refreshSeq` (stale/replayed is a silent no-op). */
|
|
241
|
+
putWrappingAuthority(params: PutWrappingAuthorityParams): Promise<void>;
|
|
242
|
+
/** The stored envelope and its `refreshSeq`, or `undefined` when unset. */
|
|
243
|
+
getWrappingAuthority(wrappingID: string): Promise<WrappingAuthority | undefined>;
|
|
244
|
+
/**
|
|
245
|
+
* Mint the next `refreshSeq` for one `(ownerDID, recipientDID, grantorDID)`
|
|
246
|
+
* triple: 1 on the first call, strictly increasing thereafter. Persisted, so
|
|
247
|
+
* a restart never reissues a number already handed out.
|
|
248
|
+
*/
|
|
249
|
+
nextGrantorSequence(params: NextGrantorSequenceParams): Promise<number>;
|
|
250
|
+
/**
|
|
251
|
+
* Allocate the next `epoch` for one `(ownerDID, recipientDID, grantorDID)`
|
|
252
|
+
* triple: returns `max(stored, minEpoch) + 1` on each call, so the value is
|
|
253
|
+
* strictly increasing but a floor can jump it forward. Persisted; never
|
|
254
|
+
* reissues an epoch already handed out. Triple is keyed on normalized DIDs,
|
|
255
|
+
* so a `did:peer:4` short/long form converge on one row.
|
|
256
|
+
*/
|
|
257
|
+
nextProvisioningEpoch(params: NextProvisioningEpochParams): Promise<number>;
|
|
258
|
+
/**
|
|
259
|
+
* Store a grantor's manifest, gated lexicographically on `(epoch, sequence)`
|
|
260
|
+
* per `(ownerDID, recipientDID, grantorDID)` triple — higher epoch always
|
|
261
|
+
* wins, sequence breaks ties within an epoch; a stale/replayed value is a
|
|
262
|
+
* silent no-op. Triple keyed on normalized DIDs. `token` is the raw signed
|
|
263
|
+
* manifest; `delegationTokens` are optional wrappings that authorized it.
|
|
264
|
+
*/
|
|
265
|
+
putManifest(record: CredentialManifestRecord, token: string, delegationTokens?: Array<string>): Promise<void>;
|
|
266
|
+
/**
|
|
267
|
+
* Every grantor's manifest for one `(ownerDID, recipientDID)`, one row per
|
|
268
|
+
* grantor. Arguments are normalized before query, matching
|
|
269
|
+
* {@link CredentialStoreAPI.putManifest}'s normalize-on-write.
|
|
270
|
+
*/
|
|
271
|
+
listManifests(params: ListManifestsParams): Promise<Array<StoredCredentialManifest>>;
|
|
272
|
+
/**
|
|
273
|
+
* Take the write lock on the manifest row so a held-epoch read later in the
|
|
274
|
+
* SAME transaction can't race a concurrent {@link CredentialStoreAPI.putManifest}.
|
|
275
|
+
* A value-preserving UPDATE, not `FOR UPDATE` (SQLite rejects that syntax) —
|
|
276
|
+
* it blocks a concurrent Postgres writer the same way; SQLite is already
|
|
277
|
+
* single-writer-serialized. No-op if the row doesn't exist yet; that
|
|
278
|
+
* first-write race is benign (held epoch 0).
|
|
279
|
+
*/
|
|
280
|
+
lockManifestRow(params: {
|
|
281
|
+
ownerDID: string;
|
|
282
|
+
recipientDID: string;
|
|
283
|
+
grantorDID: string;
|
|
284
|
+
}): Promise<void>;
|
|
285
|
+
/**
|
|
286
|
+
* Every active owner key for which a wrapping exists at the key's current
|
|
287
|
+
* version addressed (after normalization) to the grantor. One snapshot read;
|
|
288
|
+
* in-memory DID normalization. Deduped by keyID.
|
|
289
|
+
*/
|
|
290
|
+
listDistributableKeys(params: ListDistributableKeysParams): Promise<Array<{
|
|
291
|
+
keyID: string;
|
|
292
|
+
version: number;
|
|
293
|
+
}>>;
|
|
294
|
+
/**
|
|
295
|
+
* Begin tracking a provisioning attempt, idempotent on the id. Ensures an
|
|
296
|
+
* expectation floor row exists (at 0) if none yet. The attempt counts toward
|
|
297
|
+
* outstanding and blocks readiness until cleared.
|
|
298
|
+
*/
|
|
299
|
+
beginProvisioningAttempt(params: {
|
|
300
|
+
ownerDID: string;
|
|
301
|
+
grantorDID: string;
|
|
302
|
+
attemptId: string;
|
|
303
|
+
}): Promise<void>;
|
|
304
|
+
/**
|
|
305
|
+
* Record a provisioning attempt's outcome, atomically. If `epoch > heldEpoch`,
|
|
306
|
+
* clears the attempt row and raises the expectation floor to
|
|
307
|
+
* `max(stored, epoch)`; otherwise leaves the attempt row and returns retry.
|
|
308
|
+
*/
|
|
309
|
+
recordProvisioningAttempt(params: {
|
|
310
|
+
ownerDID: string;
|
|
311
|
+
grantorDID: string;
|
|
312
|
+
attemptId: string;
|
|
313
|
+
epoch: number;
|
|
314
|
+
heldEpoch: number;
|
|
315
|
+
}): Promise<{
|
|
316
|
+
status: 'recorded';
|
|
317
|
+
} | {
|
|
318
|
+
status: 'retry';
|
|
319
|
+
minEpoch: number;
|
|
320
|
+
}>;
|
|
321
|
+
/**
|
|
322
|
+
* Abandon a provisioning attempt. Deletes ONLY the attempt row, never touches
|
|
323
|
+
* the expectation floor.
|
|
324
|
+
*/
|
|
325
|
+
abandonProvisioningAttempt(params: {
|
|
326
|
+
ownerDID: string;
|
|
327
|
+
grantorDID: string;
|
|
328
|
+
attemptId: string;
|
|
329
|
+
}): Promise<void>;
|
|
330
|
+
/**
|
|
331
|
+
* List provisioning expectations for one owner: one row per (owner, grantor)
|
|
332
|
+
* pair currently held. Returns the expectation floor and count of outstanding
|
|
333
|
+
* attempts. Normalizes ownerDID on read.
|
|
334
|
+
*/
|
|
335
|
+
listProvisioningExpectations(params: {
|
|
336
|
+
ownerDID: string;
|
|
337
|
+
}): Promise<Array<{
|
|
338
|
+
grantorDID: string;
|
|
339
|
+
epochFloor: number;
|
|
340
|
+
outstanding: number;
|
|
341
|
+
}>>;
|
|
342
|
+
/**
|
|
343
|
+
* Every distinct owner DID with a provisioning expectation row, normalized
|
|
344
|
+
* and de-duped — the catalog-independent enumeration
|
|
345
|
+
* `listProvisioningExpectations` can't give on its own (it's per-owner).
|
|
346
|
+
*/
|
|
347
|
+
listProvisioningExpectationOwners(): Promise<Array<string>>;
|
|
175
348
|
};
|
|
176
349
|
export declare function createCredentialStore(db: Kysely<CredentialTables>, adapter: Adapter): CredentialStoreAPI;
|
package/lib/api.js
CHANGED
|
@@ -16,6 +16,21 @@ import { normalizeDID } from '@kokuin/token';
|
|
|
16
16
|
await write(tx);
|
|
17
17
|
});
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* The strictly-greater-`refreshSeq` upsert shared by {@link CredentialStoreAPI.putWrappingAuthority}
|
|
21
|
+
* and `putWrapping`'s own-transaction variant — one predicate, so the two entry
|
|
22
|
+
* points cannot drift on what "newer" means.
|
|
23
|
+
*/ async function upsertWrappingAuthority(db, params) {
|
|
24
|
+
const values = {
|
|
25
|
+
wrapping_id: params.wrappingID,
|
|
26
|
+
authority_token: params.envelope,
|
|
27
|
+
refresh_seq: params.refreshSeq
|
|
28
|
+
};
|
|
29
|
+
await db.insertInto('kubun_credential_wrapping_authority').values(values).onConflict((oc)=>oc.column('wrapping_id').doUpdateSet({
|
|
30
|
+
authority_token: values.authority_token,
|
|
31
|
+
refresh_seq: values.refresh_seq
|
|
32
|
+
}).where('kubun_credential_wrapping_authority.refresh_seq', '<', values.refresh_seq)).execute();
|
|
33
|
+
}
|
|
19
34
|
const OP_REJECTED = Symbol('credential op rejected');
|
|
20
35
|
async function writeOp(params) {
|
|
21
36
|
const { db, subjectKind, subjectID, op, deleted, allowKeyMove = false } = params;
|
|
@@ -153,7 +168,13 @@ export function createCredentialStore(db, adapter) {
|
|
|
153
168
|
allowKeyMove: allowMove
|
|
154
169
|
});
|
|
155
170
|
}
|
|
156
|
-
|
|
171
|
+
const api = {
|
|
172
|
+
async transaction (fn) {
|
|
173
|
+
if (db.isTransaction) {
|
|
174
|
+
return await fn(api);
|
|
175
|
+
}
|
|
176
|
+
return await db.transaction().execute(async (tx)=>await fn(createCredentialStore(tx, adapter)));
|
|
177
|
+
},
|
|
157
178
|
async putKey (params, op) {
|
|
158
179
|
await writeSubject({
|
|
159
180
|
subjectKind: 'key',
|
|
@@ -241,7 +262,7 @@ export function createCredentialStore(db, adapter) {
|
|
|
241
262
|
async listKeyBranches (keyID, keyVersion) {
|
|
242
263
|
return await db.selectFrom('kubun_credential_key_branches').selectAll().where('key_id', '=', keyID).where('key_version', '=', keyVersion).orderBy('branch_id', 'asc').execute();
|
|
243
264
|
},
|
|
244
|
-
async putWrapping (params, op) {
|
|
265
|
+
async putWrapping (params, op, authority) {
|
|
245
266
|
const values = {
|
|
246
267
|
wrapping_id: params.wrappingID,
|
|
247
268
|
key_id: params.keyID,
|
|
@@ -271,6 +292,15 @@ export function createCredentialStore(db, adapter) {
|
|
|
271
292
|
if ((result.numInsertedOrUpdatedRows ?? 0n) === 0n) {
|
|
272
293
|
throw new Error(`Credential wrapping ${params.wrappingID} belongs to another key`);
|
|
273
294
|
}
|
|
295
|
+
// Runs only after `writeOp` wins its LWW race, so a superseded
|
|
296
|
+
// wrapping never persists an envelope it didn't actually establish.
|
|
297
|
+
if (authority != null) {
|
|
298
|
+
await upsertWrappingAuthority(tx, {
|
|
299
|
+
wrappingID: params.wrappingID,
|
|
300
|
+
envelope: authority.envelope,
|
|
301
|
+
refreshSeq: authority.refreshSeq
|
|
302
|
+
});
|
|
303
|
+
}
|
|
274
304
|
}
|
|
275
305
|
});
|
|
276
306
|
},
|
|
@@ -432,8 +462,237 @@ export function createCredentialStore(db, adapter) {
|
|
|
432
462
|
async listAliasKeys (namespace) {
|
|
433
463
|
const rows = await db.selectFrom('kubun_credential_aliases').select('alias_key').distinct().where('namespace', '=', namespace).execute();
|
|
434
464
|
return rows.map((row)=>row.alias_key);
|
|
465
|
+
},
|
|
466
|
+
async putWrappingAuthority (params) {
|
|
467
|
+
await upsertWrappingAuthority(db, params);
|
|
468
|
+
},
|
|
469
|
+
async getWrappingAuthority (wrappingID) {
|
|
470
|
+
const row = await db.selectFrom('kubun_credential_wrapping_authority').select([
|
|
471
|
+
'authority_token',
|
|
472
|
+
'refresh_seq'
|
|
473
|
+
]).where('wrapping_id', '=', wrappingID).executeTakeFirst();
|
|
474
|
+
if (row == null) return undefined;
|
|
475
|
+
return {
|
|
476
|
+
envelope: row.authority_token,
|
|
477
|
+
refreshSeq: row.refresh_seq
|
|
478
|
+
};
|
|
479
|
+
},
|
|
480
|
+
async nextGrantorSequence (params) {
|
|
481
|
+
// Atomic INSERT ... ON CONFLICT DO UPDATE increments in place, so two
|
|
482
|
+
// concurrent callers can't observe the same seq. Normalize the tuple
|
|
483
|
+
// HERE (not left to callers) so a did:peer:4 recipient's short/long
|
|
484
|
+
// forms converge on one counter instead of splitting into two.
|
|
485
|
+
const row = await db.insertInto('kubun_credential_grantor_counters').values({
|
|
486
|
+
owner_did: normalizeDID(params.ownerDID),
|
|
487
|
+
recipient_did: normalizeDID(params.recipientDID),
|
|
488
|
+
grantor_did: normalizeDID(params.grantorDID),
|
|
489
|
+
seq: 1
|
|
490
|
+
}).onConflict((oc)=>oc.columns([
|
|
491
|
+
'owner_did',
|
|
492
|
+
'recipient_did',
|
|
493
|
+
'grantor_did'
|
|
494
|
+
]).doUpdateSet((eb)=>({
|
|
495
|
+
seq: eb('kubun_credential_grantor_counters.seq', '+', 1)
|
|
496
|
+
}))).returning('seq').executeTakeFirstOrThrow();
|
|
497
|
+
return row.seq;
|
|
498
|
+
},
|
|
499
|
+
async nextProvisioningEpoch (params) {
|
|
500
|
+
// Atomic upsert to max(stored, minEpoch) + 1, guaranteeing monotonicity.
|
|
501
|
+
// Normalize the tuple HERE, same reasoning as `nextGrantorSequence`.
|
|
502
|
+
const ownerDid = normalizeDID(params.ownerDID);
|
|
503
|
+
const recipientDid = normalizeDID(params.recipientDID);
|
|
504
|
+
const grantorDid = normalizeDID(params.grantorDID);
|
|
505
|
+
const floor = Math.max(0, params.minEpoch);
|
|
506
|
+
const row = await db.insertInto('kubun_credential_provisioning_epochs').values({
|
|
507
|
+
owner_did: ownerDid,
|
|
508
|
+
recipient_did: recipientDid,
|
|
509
|
+
grantor_did: grantorDid,
|
|
510
|
+
epoch: floor + 1
|
|
511
|
+
}).onConflict((oc)=>oc.columns([
|
|
512
|
+
'owner_did',
|
|
513
|
+
'recipient_did',
|
|
514
|
+
'grantor_did'
|
|
515
|
+
]).doUpdateSet((eb)=>({
|
|
516
|
+
// max(stored, floor) + 1, expressed without a portable GREATEST:
|
|
517
|
+
epoch: eb.case().when('kubun_credential_provisioning_epochs.epoch', '<', floor).then(floor + 1).else(eb('kubun_credential_provisioning_epochs.epoch', '+', 1)).end()
|
|
518
|
+
}))).returning('epoch').executeTakeFirstOrThrow();
|
|
519
|
+
return row.epoch;
|
|
520
|
+
},
|
|
521
|
+
async putManifest (record, token, delegationTokens) {
|
|
522
|
+
const values = {
|
|
523
|
+
owner_did: normalizeDID(record.ownerDID),
|
|
524
|
+
recipient_did: normalizeDID(record.recipientDID),
|
|
525
|
+
grantor_did: normalizeDID(record.grantorDID),
|
|
526
|
+
epoch: record.epoch,
|
|
527
|
+
sequence: record.sequence,
|
|
528
|
+
keys_json: adapter.encodeJSON(record.keys),
|
|
529
|
+
manifest_token: token,
|
|
530
|
+
delegation_tokens: adapter.encodeJSON(delegationTokens ?? [])
|
|
531
|
+
};
|
|
532
|
+
await db.insertInto('kubun_credential_manifests').values(values).onConflict((oc)=>oc.columns([
|
|
533
|
+
'owner_did',
|
|
534
|
+
'recipient_did',
|
|
535
|
+
'grantor_did'
|
|
536
|
+
]).doUpdateSet({
|
|
537
|
+
epoch: values.epoch,
|
|
538
|
+
sequence: values.sequence,
|
|
539
|
+
keys_json: values.keys_json,
|
|
540
|
+
manifest_token: values.manifest_token,
|
|
541
|
+
delegation_tokens: values.delegation_tokens
|
|
542
|
+
})// Lexicographic: higher epoch always wins; within an epoch,
|
|
543
|
+
// strictly-greater sequence. A stale/replayed lower value is a silent no-op.
|
|
544
|
+
.where((eb)=>eb.or([
|
|
545
|
+
eb('kubun_credential_manifests.epoch', '<', values.epoch),
|
|
546
|
+
eb.and([
|
|
547
|
+
eb('kubun_credential_manifests.epoch', '=', values.epoch),
|
|
548
|
+
eb('kubun_credential_manifests.sequence', '<', values.sequence)
|
|
549
|
+
])
|
|
550
|
+
]))).execute();
|
|
551
|
+
},
|
|
552
|
+
async lockManifestRow (params) {
|
|
553
|
+
const owner = normalizeDID(params.ownerDID);
|
|
554
|
+
const recipient = normalizeDID(params.recipientDID);
|
|
555
|
+
const grantor = normalizeDID(params.grantorDID);
|
|
556
|
+
await writeAtomically(db, async (tx)=>{
|
|
557
|
+
await tx.updateTable('kubun_credential_manifests').set((eb)=>({
|
|
558
|
+
epoch: eb.ref('kubun_credential_manifests.epoch')
|
|
559
|
+
})).where('owner_did', '=', owner).where('recipient_did', '=', recipient).where('grantor_did', '=', grantor).execute();
|
|
560
|
+
});
|
|
561
|
+
},
|
|
562
|
+
async listManifests (params) {
|
|
563
|
+
const ownerDID = normalizeDID(params.ownerDID);
|
|
564
|
+
const recipientDID = normalizeDID(params.recipientDID);
|
|
565
|
+
const rows = await db.selectFrom('kubun_credential_manifests').select([
|
|
566
|
+
'owner_did',
|
|
567
|
+
'recipient_did',
|
|
568
|
+
'grantor_did',
|
|
569
|
+
'epoch',
|
|
570
|
+
'sequence',
|
|
571
|
+
'keys_json',
|
|
572
|
+
'manifest_token',
|
|
573
|
+
'delegation_tokens'
|
|
574
|
+
]).where('owner_did', '=', ownerDID).where('recipient_did', '=', recipientDID).orderBy('grantor_did', 'asc').execute();
|
|
575
|
+
return rows.map((row)=>({
|
|
576
|
+
record: {
|
|
577
|
+
ownerDID: row.owner_did,
|
|
578
|
+
recipientDID: row.recipient_did,
|
|
579
|
+
grantorDID: row.grantor_did,
|
|
580
|
+
epoch: row.epoch,
|
|
581
|
+
sequence: row.sequence,
|
|
582
|
+
keys: row.keys_json
|
|
583
|
+
},
|
|
584
|
+
token: row.manifest_token,
|
|
585
|
+
delegationTokens: row.delegation_tokens
|
|
586
|
+
}));
|
|
587
|
+
},
|
|
588
|
+
async listDistributableKeys (params) {
|
|
589
|
+
const owner = normalizeDID(params.ownerDID);
|
|
590
|
+
const grantor = normalizeDID(params.grantorDID);
|
|
591
|
+
// One snapshot: active owner keys joined to wrappings at the key's current version.
|
|
592
|
+
const rows = await db.selectFrom('kubun_credential_keys as k').innerJoin('kubun_credential_wrappings as w', (join)=>join.onRef('w.key_id', '=', 'k.key_id').onRef('w.key_version', '=', 'k.version')).where('k.state', '=', 'active').select([
|
|
593
|
+
'k.key_id as keyID',
|
|
594
|
+
'k.version as version',
|
|
595
|
+
'k.owner_did as ownerDID',
|
|
596
|
+
'w.recipient_did as recipientDID'
|
|
597
|
+
]).execute();
|
|
598
|
+
const seen = new Set();
|
|
599
|
+
const out = [];
|
|
600
|
+
for (const row of rows){
|
|
601
|
+
if (normalizeDID(row.ownerDID) !== owner) continue;
|
|
602
|
+
if (row.recipientDID == null || normalizeDID(row.recipientDID) !== grantor) continue;
|
|
603
|
+
if (seen.has(row.keyID)) continue;
|
|
604
|
+
seen.add(row.keyID);
|
|
605
|
+
out.push({
|
|
606
|
+
keyID: row.keyID,
|
|
607
|
+
version: row.version
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
return out;
|
|
611
|
+
},
|
|
612
|
+
async beginProvisioningAttempt (params) {
|
|
613
|
+
const ownerDid = normalizeDID(params.ownerDID);
|
|
614
|
+
const grantorDid = normalizeDID(params.grantorDID);
|
|
615
|
+
await writeAtomically(db, async (tx)=>{
|
|
616
|
+
// Ensure the expectation floor row exists, idempotent at floor 0.
|
|
617
|
+
await tx.insertInto('kubun_credential_provisioning_expectations').values({
|
|
618
|
+
owner_did: ownerDid,
|
|
619
|
+
grantor_did: grantorDid,
|
|
620
|
+
epoch_floor: 0
|
|
621
|
+
}).onConflict((oc)=>oc.doNothing()).execute();
|
|
622
|
+
// Insert the outstanding attempt row, idempotent on the id.
|
|
623
|
+
await tx.insertInto('kubun_credential_provisioning_attempts').values({
|
|
624
|
+
owner_did: ownerDid,
|
|
625
|
+
grantor_did: grantorDid,
|
|
626
|
+
attempt_id: params.attemptId
|
|
627
|
+
}).onConflict((oc)=>oc.doNothing()).execute();
|
|
628
|
+
});
|
|
629
|
+
},
|
|
630
|
+
async recordProvisioningAttempt (params) {
|
|
631
|
+
const ownerDid = normalizeDID(params.ownerDID);
|
|
632
|
+
const grantorDid = normalizeDID(params.grantorDID);
|
|
633
|
+
// Single atomic operation: if epoch > heldEpoch, delete attempt and raise floor.
|
|
634
|
+
let result = {
|
|
635
|
+
status: 'retry',
|
|
636
|
+
minEpoch: params.heldEpoch
|
|
637
|
+
};
|
|
638
|
+
await writeAtomically(db, async (tx)=>{
|
|
639
|
+
if (params.epoch > params.heldEpoch) {
|
|
640
|
+
// Phantom-attempt guard: if the id names no live attempt, move
|
|
641
|
+
// nothing — raising the floor for it would wedge readiness forever.
|
|
642
|
+
const existing = await tx.selectFrom('kubun_credential_provisioning_attempts').select('attempt_id').where('owner_did', '=', ownerDid).where('grantor_did', '=', grantorDid).where('attempt_id', '=', params.attemptId).executeTakeFirst();
|
|
643
|
+
if (existing == null) {
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
// Delete the attempt row.
|
|
647
|
+
await tx.deleteFrom('kubun_credential_provisioning_attempts').where('owner_did', '=', ownerDid).where('grantor_did', '=', grantorDid).where('attempt_id', '=', params.attemptId).execute();
|
|
648
|
+
// Raise the floor to max(stored, epoch).
|
|
649
|
+
await tx.updateTable('kubun_credential_provisioning_expectations').set((eb)=>({
|
|
650
|
+
epoch_floor: eb.case().when('epoch_floor', '<', params.epoch).then(params.epoch).else(eb.ref('epoch_floor')).end()
|
|
651
|
+
})).where('owner_did', '=', ownerDid).where('grantor_did', '=', grantorDid).execute();
|
|
652
|
+
result = {
|
|
653
|
+
status: 'recorded'
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
});
|
|
657
|
+
return result;
|
|
658
|
+
},
|
|
659
|
+
async abandonProvisioningAttempt (params) {
|
|
660
|
+
const ownerDid = normalizeDID(params.ownerDID);
|
|
661
|
+
const grantorDid = normalizeDID(params.grantorDID);
|
|
662
|
+
// Delete ONLY the attempt row, never the floor.
|
|
663
|
+
await db.deleteFrom('kubun_credential_provisioning_attempts').where('owner_did', '=', ownerDid).where('grantor_did', '=', grantorDid).where('attempt_id', '=', params.attemptId).execute();
|
|
664
|
+
},
|
|
665
|
+
async listProvisioningExpectations (params) {
|
|
666
|
+
const ownerDid = normalizeDID(params.ownerDID);
|
|
667
|
+
// Left join expectations to outstanding attempts, grouped by (owner, grantor).
|
|
668
|
+
// Return one row per (owner, grantor) with floor and outstanding count.
|
|
669
|
+
const rows = await db.selectFrom('kubun_credential_provisioning_expectations as e').leftJoin('kubun_credential_provisioning_attempts as a', (join)=>join.onRef('a.owner_did', '=', 'e.owner_did').onRef('a.grantor_did', '=', 'e.grantor_did')).select((eb)=>[
|
|
670
|
+
'e.grantor_did as grantor_did',
|
|
671
|
+
'e.epoch_floor as epoch_floor',
|
|
672
|
+
eb.fn.count('a.attempt_id').as('outstanding')
|
|
673
|
+
]).where('e.owner_did', '=', ownerDid).groupBy([
|
|
674
|
+
'e.owner_did',
|
|
675
|
+
'e.grantor_did',
|
|
676
|
+
'e.epoch_floor'
|
|
677
|
+
]).orderBy('e.grantor_did', 'asc').execute();
|
|
678
|
+
return rows.map((row)=>({
|
|
679
|
+
grantorDID: row.grantor_did,
|
|
680
|
+
epochFloor: row.epoch_floor,
|
|
681
|
+
outstanding: Number(row.outstanding)
|
|
682
|
+
}));
|
|
683
|
+
},
|
|
684
|
+
async listProvisioningExpectationOwners () {
|
|
685
|
+
const rows = await db.selectFrom('kubun_credential_provisioning_expectations').select('owner_did').distinct().execute();
|
|
686
|
+
const owners = new Set();
|
|
687
|
+
for (const row of rows){
|
|
688
|
+
owners.add(normalizeDID(row.owner_did));
|
|
689
|
+
}
|
|
690
|
+
return [
|
|
691
|
+
...owners
|
|
692
|
+
];
|
|
435
693
|
}
|
|
436
694
|
};
|
|
695
|
+
return api;
|
|
437
696
|
}
|
|
438
697
|
/**
|
|
439
698
|
* The ops leaf scan in stamp order — every one, or one key's. Two devices must
|
package/lib/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { StoreProvider } from '@kubun/db';
|
|
2
2
|
import type { CredentialStoreAPI } from './api.js';
|
|
3
|
-
export type { CredentialOpInput, CredentialStoreAPI, CredentialTombstoneInput, HeldCredentialSubject, PutCredentialEntryParams, PutCredentialKeyParams, PutCredentialWrappingParams, } from './api.js';
|
|
3
|
+
export type { CredentialManifestRecord, CredentialOpInput, CredentialStoreAPI, CredentialTombstoneInput, HeldCredentialSubject, ListManifestsParams, NextGrantorSequenceParams, PutCredentialEntryParams, PutCredentialKeyParams, PutCredentialWrappingParams, PutWrappingAuthorityParams, StoredCredentialManifest, WrappingAuthority, } from './api.js';
|
|
4
4
|
export { createCredentialStore } from './api.js';
|
|
5
5
|
export { credentialStoreDefinition } from './definition.js';
|
|
6
6
|
export declare const CREDENTIAL_STORE: "credential";
|
|
7
7
|
export declare function getCredentialStore(provider: StoreProvider): Promise<CredentialStoreAPI>;
|
|
8
|
-
export type { CredentialAlias, CredentialEntry, CredentialEntryTable, CredentialEntryWithProvenance, CredentialFactorDescriptor, CredentialKey, CredentialKeyBranch, CredentialKeyBranchTable, CredentialKeyState, CredentialKeyTable, CredentialOp, CredentialOpSubjectKind, CredentialOpTable, CredentialTables, CredentialWrapping, CredentialWrappingTable, CredentialWrappingWithBranch, InsertCredentialAlias, InsertCredentialEntry, InsertCredentialKey, InsertCredentialOp, InsertCredentialWrapping, KubunCredentialAliasTable, } from './tables.js';
|
|
8
|
+
export type { CredentialAlias, CredentialEntry, CredentialEntryTable, CredentialEntryWithProvenance, CredentialFactorDescriptor, CredentialGrantorCounter, CredentialGrantorCounterTable, CredentialKey, CredentialKeyBranch, CredentialKeyBranchTable, CredentialKeyState, CredentialKeyTable, CredentialManifest, CredentialManifestKey, CredentialManifestTable, CredentialOp, CredentialOpSubjectKind, CredentialOpTable, CredentialTables, CredentialWrapping, CredentialWrappingAuthority, CredentialWrappingAuthorityTable, CredentialWrappingTable, CredentialWrappingWithBranch, InsertCredentialAlias, InsertCredentialEntry, InsertCredentialGrantorCounter, InsertCredentialKey, InsertCredentialManifest, InsertCredentialOp, InsertCredentialWrapping, InsertCredentialWrappingAuthority, KubunCredentialAliasTable, } from './tables.js';
|
package/lib/migrations.js
CHANGED
|
@@ -49,8 +49,45 @@ export function getCredentialMigrations(ctx) {
|
|
|
49
49
|
'namespace',
|
|
50
50
|
'alias_key'
|
|
51
51
|
]).execute();
|
|
52
|
+
// Grantor-authority envelope for a wrapping; `refresh_seq` gates every upsert strictly-greater.
|
|
53
|
+
await db.schema.createTable('kubun_credential_wrapping_authority').ifNotExists().addColumn('wrapping_id', t.text, (col)=>col.notNull().primaryKey()).addColumn('authority_token', t.text, (col)=>col.notNull()).addColumn('refresh_seq', 'integer', (col)=>col.notNull()).execute();
|
|
54
|
+
// Persisted counter minting strictly-increasing refreshSeq, one per (owner, recipient, grantor) triple.
|
|
55
|
+
await db.schema.createTable('kubun_credential_grantor_counters').ifNotExists().addColumn('owner_did', t.text, (col)=>col.notNull()).addColumn('recipient_did', t.text, (col)=>col.notNull()).addColumn('grantor_did', t.text, (col)=>col.notNull()).addColumn('seq', 'integer', (col)=>col.notNull()).addPrimaryKeyConstraint('pk_kubun_credential_grantor_counters', [
|
|
56
|
+
'owner_did',
|
|
57
|
+
'recipient_did',
|
|
58
|
+
'grantor_did'
|
|
59
|
+
]).execute();
|
|
60
|
+
// Persisted counter minting strictly-increasing provisioning epoch, respecting a minEpoch floor.
|
|
61
|
+
await db.schema.createTable('kubun_credential_provisioning_epochs').ifNotExists().addColumn('owner_did', t.text, (col)=>col.notNull()).addColumn('recipient_did', t.text, (col)=>col.notNull()).addColumn('grantor_did', t.text, (col)=>col.notNull()).addColumn('epoch', 'integer', (col)=>col.notNull()).addPrimaryKeyConstraint('pk_kubun_credential_provisioning_epochs', [
|
|
62
|
+
'owner_did',
|
|
63
|
+
'recipient_did',
|
|
64
|
+
'grantor_did'
|
|
65
|
+
]).execute();
|
|
66
|
+
// Provisioning expectation floor per (owner, grantor) pair; normalized DIDs.
|
|
67
|
+
await db.schema.createTable('kubun_credential_provisioning_expectations').ifNotExists().addColumn('owner_did', t.text, (col)=>col.notNull()).addColumn('grantor_did', t.text, (col)=>col.notNull()).addColumn('epoch_floor', 'integer', (col)=>col.notNull().defaultTo(0)).addPrimaryKeyConstraint('pk_kubun_credential_provisioning_expectations', [
|
|
68
|
+
'owner_did',
|
|
69
|
+
'grantor_did'
|
|
70
|
+
]).execute();
|
|
71
|
+
// Outstanding provisioning attempts; row exists while in flight, deleted on completion.
|
|
72
|
+
await db.schema.createTable('kubun_credential_provisioning_attempts').ifNotExists().addColumn('owner_did', t.text, (col)=>col.notNull()).addColumn('grantor_did', t.text, (col)=>col.notNull()).addColumn('attempt_id', t.text, (col)=>col.notNull()).addPrimaryKeyConstraint('pk_kubun_credential_provisioning_attempts', [
|
|
73
|
+
'owner_did',
|
|
74
|
+
'grantor_did',
|
|
75
|
+
'attempt_id'
|
|
76
|
+
]).execute();
|
|
77
|
+
// Grantor-recorded manifests, keyed on the NORMALIZED triple; `(epoch, sequence)` gate every upsert lexicographically.
|
|
78
|
+
await db.schema.createTable('kubun_credential_manifests').ifNotExists().addColumn('owner_did', t.text, (col)=>col.notNull()).addColumn('recipient_did', t.text, (col)=>col.notNull()).addColumn('grantor_did', t.text, (col)=>col.notNull()).addColumn('epoch', 'integer', (col)=>col.notNull()).addColumn('sequence', 'integer', (col)=>col.notNull()).addColumn('keys_json', t.json, (col)=>col.notNull()).addColumn('manifest_token', t.text, (col)=>col.notNull()).addColumn('delegation_tokens', t.json, (col)=>col.notNull()).addPrimaryKeyConstraint('pk_kubun_credential_manifests', [
|
|
79
|
+
'owner_did',
|
|
80
|
+
'recipient_did',
|
|
81
|
+
'grantor_did'
|
|
82
|
+
]).execute();
|
|
52
83
|
},
|
|
53
84
|
async down (db) {
|
|
85
|
+
await db.schema.dropTable('kubun_credential_manifests').ifExists().execute();
|
|
86
|
+
await db.schema.dropTable('kubun_credential_provisioning_attempts').ifExists().execute();
|
|
87
|
+
await db.schema.dropTable('kubun_credential_provisioning_expectations').ifExists().execute();
|
|
88
|
+
await db.schema.dropTable('kubun_credential_provisioning_epochs').ifExists().execute();
|
|
89
|
+
await db.schema.dropTable('kubun_credential_grantor_counters').ifExists().execute();
|
|
90
|
+
await db.schema.dropTable('kubun_credential_wrapping_authority').ifExists().execute();
|
|
54
91
|
await db.schema.dropTable('kubun_credential_aliases').ifExists().execute();
|
|
55
92
|
await db.schema.dropTable('kubun_credential_ops').ifExists().execute();
|
|
56
93
|
await db.schema.dropTable('kubun_credential_entries').ifExists().execute();
|
package/lib/tables.d.ts
CHANGED
|
@@ -166,6 +166,99 @@ export type KubunCredentialAliasTable = {
|
|
|
166
166
|
};
|
|
167
167
|
export type CredentialAlias = Selectable<KubunCredentialAliasTable>;
|
|
168
168
|
export type InsertCredentialAlias = Insertable<KubunCredentialAliasTable>;
|
|
169
|
+
/**
|
|
170
|
+
* The grantor-authority envelope backing one wrapping — a signed token proving
|
|
171
|
+
* whoever minted the wrapping held the right to grant it, carried beside it
|
|
172
|
+
* rather than folded into its content-addressed digest. `refresh_seq` gates
|
|
173
|
+
* every `put` strictly-greater, so a stale/replayed envelope is a silent no-op.
|
|
174
|
+
*/
|
|
175
|
+
export type CredentialWrappingAuthorityTable = {
|
|
176
|
+
wrapping_id: string;
|
|
177
|
+
authority_token: string;
|
|
178
|
+
refresh_seq: number;
|
|
179
|
+
};
|
|
180
|
+
export type CredentialWrappingAuthority = Selectable<CredentialWrappingAuthorityTable>;
|
|
181
|
+
export type InsertCredentialWrappingAuthority = Insertable<CredentialWrappingAuthorityTable>;
|
|
182
|
+
/**
|
|
183
|
+
* Monotonic counter minting the strictly-increasing `refreshSeq`, one per
|
|
184
|
+
* `(ownerDID, recipientDID, grantorDID)` triple. Persisted so a restart never
|
|
185
|
+
* reissues a sequence number already handed out.
|
|
186
|
+
*/
|
|
187
|
+
export type CredentialGrantorCounterTable = {
|
|
188
|
+
owner_did: string;
|
|
189
|
+
recipient_did: string;
|
|
190
|
+
grantor_did: string;
|
|
191
|
+
seq: number;
|
|
192
|
+
};
|
|
193
|
+
export type CredentialGrantorCounter = Selectable<CredentialGrantorCounterTable>;
|
|
194
|
+
export type InsertCredentialGrantorCounter = Insertable<CredentialGrantorCounterTable>;
|
|
195
|
+
/**
|
|
196
|
+
* Monotonic epoch counter minting `nextProvisioningEpoch`, one per
|
|
197
|
+
* `(ownerDID, recipientDID, grantorDID)` triple, respecting a `minEpoch` floor
|
|
198
|
+
* for post-rollback recovery. Persisted so a restart never reissues an epoch.
|
|
199
|
+
*/
|
|
200
|
+
export type CredentialProvisioningEpochTable = {
|
|
201
|
+
owner_did: string;
|
|
202
|
+
recipient_did: string;
|
|
203
|
+
grantor_did: string;
|
|
204
|
+
epoch: number;
|
|
205
|
+
};
|
|
206
|
+
export type CredentialProvisioningEpoch = Selectable<CredentialProvisioningEpochTable>;
|
|
207
|
+
export type InsertCredentialProvisioningEpoch = Insertable<CredentialProvisioningEpochTable>;
|
|
208
|
+
/**
|
|
209
|
+
* The expectation floor for provisioning a credential manifest: a monotonic
|
|
210
|
+
* epoch counter gating when a provisioning attempt counts as complete.
|
|
211
|
+
* Readiness fails closed while an outstanding attempt row exists.
|
|
212
|
+
*/
|
|
213
|
+
export type CredentialProvisioningExpectationTable = {
|
|
214
|
+
owner_did: string;
|
|
215
|
+
grantor_did: string;
|
|
216
|
+
epoch_floor: number;
|
|
217
|
+
};
|
|
218
|
+
export type CredentialProvisioningExpectation = Selectable<CredentialProvisioningExpectationTable>;
|
|
219
|
+
export type InsertCredentialProvisioningExpectation = Insertable<CredentialProvisioningExpectationTable>;
|
|
220
|
+
/**
|
|
221
|
+
* An outstanding provisioning attempt. Success deletes the row; retry failure
|
|
222
|
+
* keeps it; abandon deletes it without touching the expectation floor.
|
|
223
|
+
*/
|
|
224
|
+
export type CredentialProvisioningAttemptTable = {
|
|
225
|
+
owner_did: string;
|
|
226
|
+
grantor_did: string;
|
|
227
|
+
attempt_id: string;
|
|
228
|
+
};
|
|
229
|
+
export type CredentialProvisioningAttempt = Selectable<CredentialProvisioningAttemptTable>;
|
|
230
|
+
export type InsertCredentialProvisioningAttempt = Insertable<CredentialProvisioningAttemptTable>;
|
|
231
|
+
/** One key a grantor recorded as granted, as it rides a manifest's `keys_json`. */
|
|
232
|
+
export type CredentialManifestKey = {
|
|
233
|
+
keyID: string;
|
|
234
|
+
version: number;
|
|
235
|
+
};
|
|
236
|
+
/**
|
|
237
|
+
* A grantor's advisory record of the key set it granted one recipient under one
|
|
238
|
+
* owner — the projection a recipient checks its own store against to prove
|
|
239
|
+
* completeness (the readiness union). Carries no key material, only ids/versions
|
|
240
|
+
* and the signed token that authenticated them.
|
|
241
|
+
*
|
|
242
|
+
* `(epoch, sequence)` gates every `put` lexicographically: a stale/replayed
|
|
243
|
+
* value is a silent no-op. `issuedAt` is a drift bound applied on admission,
|
|
244
|
+
* not persisted here.
|
|
245
|
+
*
|
|
246
|
+
* Triple columns are stored NORMALIZED (`normalizeDID`), so a `did:peer:4`
|
|
247
|
+
* short/long form converges on one row — matching the grantor's write to the
|
|
248
|
+
* recipient's readiness read.
|
|
249
|
+
*/
|
|
250
|
+
export type CredentialManifestTable = {
|
|
251
|
+
owner_did: string;
|
|
252
|
+
recipient_did: string;
|
|
253
|
+
grantor_did: string;
|
|
254
|
+
epoch: number;
|
|
255
|
+
sequence: number;
|
|
256
|
+
keys_json: JSONArrayColumn<CredentialManifestKey>;
|
|
257
|
+
manifest_token: string;
|
|
258
|
+
delegation_tokens: JSONArrayColumn<string>;
|
|
259
|
+
};
|
|
260
|
+
export type CredentialManifest = Selectable<CredentialManifestTable>;
|
|
261
|
+
export type InsertCredentialManifest = Insertable<CredentialManifestTable>;
|
|
169
262
|
export type CredentialTables = {
|
|
170
263
|
kubun_credential_keys: CredentialKeyTable;
|
|
171
264
|
kubun_credential_key_branches: CredentialKeyBranchTable;
|
|
@@ -173,5 +266,11 @@ export type CredentialTables = {
|
|
|
173
266
|
kubun_credential_entries: CredentialEntryTable;
|
|
174
267
|
kubun_credential_ops: CredentialOpTable;
|
|
175
268
|
kubun_credential_aliases: KubunCredentialAliasTable;
|
|
269
|
+
kubun_credential_wrapping_authority: CredentialWrappingAuthorityTable;
|
|
270
|
+
kubun_credential_grantor_counters: CredentialGrantorCounterTable;
|
|
271
|
+
kubun_credential_provisioning_epochs: CredentialProvisioningEpochTable;
|
|
272
|
+
kubun_credential_provisioning_expectations: CredentialProvisioningExpectationTable;
|
|
273
|
+
kubun_credential_provisioning_attempts: CredentialProvisioningAttemptTable;
|
|
274
|
+
kubun_credential_manifests: CredentialManifestTable;
|
|
176
275
|
};
|
|
177
276
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubun/store-credential",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"keywords": [],
|
|
5
5
|
"license": "see LICENSE.md",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -16,13 +16,13 @@
|
|
|
16
16
|
],
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@kokuin/token": "^0.5.0",
|
|
19
|
-
"@kubun/db": "^0.
|
|
20
|
-
"@kubun/db-adapter": "^0.
|
|
19
|
+
"@kubun/db": "^0.15.0",
|
|
20
|
+
"@kubun/db-adapter": "^0.15.0",
|
|
21
21
|
"kysely": "^0.29.5"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"@kubun/db-better-sqlite": "^0.
|
|
25
|
-
"@kubun/db-postgres": "^0.
|
|
24
|
+
"@kubun/db-better-sqlite": "^0.15.0",
|
|
25
|
+
"@kubun/db-postgres": "^0.15.0",
|
|
26
26
|
"@testcontainers/postgresql": "^12.1.0"
|
|
27
27
|
},
|
|
28
28
|
"publishConfig": {
|