@kubun/store-credential 0.17.4 → 0.18.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/advisory-locks.d.ts +5 -0
- package/lib/advisory-locks.js +12 -0
- package/lib/api-aliases.d.ts +3 -0
- package/lib/api-aliases.js +58 -0
- package/lib/api-entries.d.ts +3 -0
- package/lib/api-entries.js +157 -0
- package/lib/api-keys.d.ts +3 -0
- package/lib/api-keys.js +249 -0
- package/lib/api-provisioning.d.ts +3 -0
- package/lib/api-provisioning.js +227 -0
- package/lib/api-records.d.ts +3 -0
- package/lib/api-records.js +74 -0
- package/lib/api-types.d.ts +412 -0
- package/lib/api-types.js +14 -0
- package/lib/api.d.ts +5 -448
- package/lib/api.js +24 -977
- package/lib/definition.js +3 -1
- package/lib/index.d.ts +3 -2
- package/lib/index.js +1 -0
- package/lib/migrations.js +62 -36
- package/lib/records.d.ts +206 -0
- package/lib/records.js +905 -0
- package/lib/store-context.d.ts +37 -0
- package/lib/store-context.js +139 -0
- package/lib/tables.d.ts +88 -64
- package/package.json +11 -8
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type Adapter } from '@kubun/db-adapter';
|
|
2
|
+
import { type Kysely } from 'kysely';
|
|
3
|
+
import type { CredentialStoreAPI } from './api-types.js';
|
|
4
|
+
import type { VerifiedRecordCopies } from './records.js';
|
|
5
|
+
import type { CredentialTables } from './tables.js';
|
|
6
|
+
export type StoreContext = {
|
|
7
|
+
db: Kysely<CredentialTables>;
|
|
8
|
+
adapter: Adapter;
|
|
9
|
+
advisoryIDForKey?: (keyID: string) => bigint | Promise<bigint>;
|
|
10
|
+
api: () => CredentialStoreAPI;
|
|
11
|
+
verifiedCopies: VerifiedRecordCopies;
|
|
12
|
+
oneOwner: <T extends {
|
|
13
|
+
owner_did: string;
|
|
14
|
+
}>(rows: Array<T>, address: string) => Array<T>;
|
|
15
|
+
ownerForKey: (keyID: string, ownerDID?: string) => Promise<string>;
|
|
16
|
+
lockKeyRowInTransaction: (tx: Kysely<CredentialTables>, keyID: string) => Promise<void>;
|
|
17
|
+
lockKeyRowsInTransaction: (tx: Kysely<CredentialTables>, keyIDs: ReadonlyArray<string>) => Promise<void>;
|
|
18
|
+
writeSubject: (params: {
|
|
19
|
+
keyID: string;
|
|
20
|
+
keyIDs?: Array<string>;
|
|
21
|
+
write: (tx: Kysely<CredentialTables>) => Promise<void>;
|
|
22
|
+
accept?: (tx: Kysely<CredentialTables>) => Promise<boolean>;
|
|
23
|
+
}) => Promise<void>;
|
|
24
|
+
deleteSubject: (params: {
|
|
25
|
+
keyID: string;
|
|
26
|
+
remove: (tx: Kysely<CredentialTables>) => Promise<void>;
|
|
27
|
+
}) => Promise<void>;
|
|
28
|
+
writeAmbient: (innerDb: Kysely<CredentialTables>, write: (tx: Kysely<CredentialTables>) => Promise<void>) => Promise<void>;
|
|
29
|
+
gatedIfAmbient: <T>(fn: () => Promise<T>) => Promise<T>;
|
|
30
|
+
};
|
|
31
|
+
export type StoreContextParams = {
|
|
32
|
+
db: Kysely<CredentialTables>;
|
|
33
|
+
adapter: Adapter;
|
|
34
|
+
advisoryIDForKey: ((keyID: string) => bigint | Promise<bigint>) | undefined;
|
|
35
|
+
getAPI: () => CredentialStoreAPI;
|
|
36
|
+
};
|
|
37
|
+
export declare function createStoreContext(params: StoreContextParams): StoreContext;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { AbstractPostgresAdapter } from '@kubun/db-adapter';
|
|
2
|
+
import { sql } from 'kysely';
|
|
3
|
+
import { orderAdvisoryLockIDs } from './advisory-locks.js';
|
|
4
|
+
/**
|
|
5
|
+
* Run `write` as one atomic unit.
|
|
6
|
+
*
|
|
7
|
+
* A row and the op that speaks for it must land together or not at all: a crash
|
|
8
|
+
* between a delete and its tombstone loses the deletion, and one between a put
|
|
9
|
+
* and its op leaves a row nothing authenticates. Kysely rejects a nested
|
|
10
|
+
* `transaction()`, and an ambient one already provides the atomicity, so inside
|
|
11
|
+
* a caller's transaction the statements run inline.
|
|
12
|
+
*/ async function writeAtomically(db, write) {
|
|
13
|
+
if (db.isTransaction) {
|
|
14
|
+
await write(db);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
await db.transaction().execute(async (tx)=>{
|
|
18
|
+
await write(tx);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
export function createStoreContext(params) {
|
|
22
|
+
const { db, adapter, advisoryIDForKey, getAPI } = params;
|
|
23
|
+
const verifiedCopies = new Map();
|
|
24
|
+
function oneOwner(rows, address) {
|
|
25
|
+
if (new Set(rows.map((row)=>row.owner_did)).size > 1) {
|
|
26
|
+
throw new Error(`Credential ${address} needs an owner`);
|
|
27
|
+
}
|
|
28
|
+
return rows;
|
|
29
|
+
}
|
|
30
|
+
async function ownerForKey(keyID, ownerDID) {
|
|
31
|
+
if (ownerDID !== undefined) return ownerDID;
|
|
32
|
+
const owners = new Set();
|
|
33
|
+
for (const table of [
|
|
34
|
+
'kubun_credential_keys',
|
|
35
|
+
'kubun_credential_key_branches',
|
|
36
|
+
'kubun_credential_wrappings',
|
|
37
|
+
'kubun_credential_entries',
|
|
38
|
+
'kubun_credential_entry_candidates',
|
|
39
|
+
'kubun_credential_revocations',
|
|
40
|
+
'kubun_credential_key_owner_delegation'
|
|
41
|
+
]){
|
|
42
|
+
const rows = await db.selectFrom(table).select('owner_did').where('key_id', '=', keyID).execute();
|
|
43
|
+
for (const row of rows)owners.add(row.owner_did);
|
|
44
|
+
if (owners.size > 1) throw new Error(`Credential key ${keyID} needs an owner`);
|
|
45
|
+
}
|
|
46
|
+
// Legacy local rows can precede a key row; they live in the empty local namespace.
|
|
47
|
+
return owners.values().next().value ?? '';
|
|
48
|
+
}
|
|
49
|
+
async function lockKeyRowsInTransaction(tx, keyIDs) {
|
|
50
|
+
if (!tx.isTransaction) throw new Error('lockKeyRows requires a store transaction');
|
|
51
|
+
const keys = [
|
|
52
|
+
...new Set(keyIDs)
|
|
53
|
+
];
|
|
54
|
+
if (keys.length === 0) return;
|
|
55
|
+
if (adapter instanceof AbstractPostgresAdapter) {
|
|
56
|
+
// A row lock cannot protect a key before its first insert. The first 64
|
|
57
|
+
// MD5 bits give every transaction the same lock for present and absent keys.
|
|
58
|
+
// Hash every key before locking so colliding IDs are deduplicated and all
|
|
59
|
+
// transactions acquire their IDs in the same signed numeric order.
|
|
60
|
+
const keyedIDs = [];
|
|
61
|
+
for (const keyID of keys){
|
|
62
|
+
const advisoryID = advisoryIDForKey == null ? BigInt((await sql`SELECT (('x' || md5(${`credential-key:${keyID}`}::text))::bit(64)::bigint)::text AS lock_id`.execute(tx)).rows[0]?.lock_id ?? '') : await advisoryIDForKey(keyID);
|
|
63
|
+
keyedIDs.push({
|
|
64
|
+
keyID,
|
|
65
|
+
advisoryID
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
for (const advisoryID of orderAdvisoryLockIDs(keyedIDs)){
|
|
69
|
+
await sql`SELECT pg_advisory_xact_lock(${advisoryID.toString()}::bigint)`.execute(tx);
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
// SQLite's deferred BEGIN does not reserve its single writer until a
|
|
73
|
+
// write. Acquire it before the first read, matching Postgres ordering.
|
|
74
|
+
await tx.updateTable('kubun_credential_keys').set((eb)=>({
|
|
75
|
+
version: eb.ref('kubun_credential_keys.version')
|
|
76
|
+
})).where('key_id', 'in', keys).executeTakeFirst();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async function lockKeyRowInTransaction(tx, keyID) {
|
|
80
|
+
await lockKeyRowsInTransaction(tx, [
|
|
81
|
+
keyID
|
|
82
|
+
]);
|
|
83
|
+
}
|
|
84
|
+
async function writeSubject(params) {
|
|
85
|
+
await writeAmbient(db, async (tx)=>{
|
|
86
|
+
await lockKeyRowsInTransaction(tx, params.keyIDs ?? [
|
|
87
|
+
params.keyID
|
|
88
|
+
]);
|
|
89
|
+
if (params.accept == null || await params.accept(tx)) await params.write(tx);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
async function deleteSubject(params) {
|
|
93
|
+
await writeAmbient(db, async (tx)=>{
|
|
94
|
+
await lockKeyRowInTransaction(tx, params.keyID);
|
|
95
|
+
await params.remove(tx);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
// One async gate serializes EVERY write that joins a caller-supplied ambient
|
|
99
|
+
// transaction.
|
|
100
|
+
// then holds the connection exclusively for its
|
|
101
|
+
// whole open → rollback/release window, so a ROLLBACK TO SAVEPOINT can never undo
|
|
102
|
+
// another concurrent write's committed work — a savepoint rollback reverts ALL work
|
|
103
|
+
// since the savepoint, not just this operation's statements. getStates resolves its
|
|
104
|
+
// connectors with Promise.all over one shared request tx, the source of that
|
|
105
|
+
// concurrency. The gate covers not only the savepoint sections but every ambient
|
|
106
|
+
// write (writeAmbient, and the raw alias writers via gatedIfAmbient), so no ungated
|
|
107
|
+
// statement can land inside a savepoint window. Reads are never gated. Owned
|
|
108
|
+
// transactions (db.isTransaction === false) open their own connection and are
|
|
109
|
+
// serialized by the driver, so they skip the gate. The gate is per api instance:
|
|
110
|
+
// `db.isTransaction` is fixed per instance, so a fresh transaction builds a fresh
|
|
111
|
+
// store with its own gate.
|
|
112
|
+
let ambientWriteChain = Promise.resolve();
|
|
113
|
+
const withAmbientWrite = (fn)=>{
|
|
114
|
+
const run = ambientWriteChain.then(fn, fn);
|
|
115
|
+
ambientWriteChain = run.then(()=>undefined, ()=>undefined);
|
|
116
|
+
return run;
|
|
117
|
+
};
|
|
118
|
+
// Gate a single raw ambient statement; run it directly when this store owns no
|
|
119
|
+
// ambient transaction (nothing else can interleave a savepoint there).
|
|
120
|
+
const gatedIfAmbient = (fn)=>db.isTransaction ? withAmbientWrite(fn) : fn();
|
|
121
|
+
// writeAtomically, but the inline (ambient) branch runs under the gate so it cannot
|
|
122
|
+
// interleave a concurrent savepoint section. The owned branch opens its own tx.
|
|
123
|
+
const writeAmbient = (innerDb, write)=>innerDb.isTransaction ? withAmbientWrite(()=>writeAtomically(innerDb, write)) : writeAtomically(innerDb, write);
|
|
124
|
+
return {
|
|
125
|
+
db,
|
|
126
|
+
adapter,
|
|
127
|
+
advisoryIDForKey,
|
|
128
|
+
api: getAPI,
|
|
129
|
+
verifiedCopies,
|
|
130
|
+
oneOwner,
|
|
131
|
+
ownerForKey,
|
|
132
|
+
lockKeyRowInTransaction,
|
|
133
|
+
lockKeyRowsInTransaction,
|
|
134
|
+
writeSubject,
|
|
135
|
+
deleteSubject,
|
|
136
|
+
writeAmbient,
|
|
137
|
+
gatedIfAmbient
|
|
138
|
+
};
|
|
139
|
+
}
|
package/lib/tables.d.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import type { CreatedAtColumn } from '@kubun/db-adapter';
|
|
2
2
|
import type { ColumnType, Insertable, Selectable } from 'kysely';
|
|
3
3
|
type JSONArrayColumn<T> = ColumnType<Array<T>, Array<T> | undefined, Array<T>>;
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* The content-key branch a wrapping or entry joins: its introducing key op's
|
|
6
|
+
* `${hlc}:${opHash}` label, taken from the op that wrote the row. Empty when no op
|
|
7
|
+
* named one, which readers treat as branch-agnostic.
|
|
8
|
+
*/
|
|
9
|
+
type BranchColumn = ColumnType<string, string | undefined, string>;
|
|
5
10
|
/**
|
|
6
11
|
* Whether a key is the one new entries encrypt under.
|
|
7
12
|
*
|
|
@@ -35,6 +40,7 @@ export type CredentialKey = Selectable<CredentialKeyTable>;
|
|
|
35
40
|
export type InsertCredentialKey = Insertable<CredentialKeyTable>;
|
|
36
41
|
/** An authenticated key-op retained as append-only branch provenance. */
|
|
37
42
|
export type CredentialKeyBranchTable = {
|
|
43
|
+
owner_did: string;
|
|
38
44
|
key_id: string;
|
|
39
45
|
key_version: number;
|
|
40
46
|
op_hash: string;
|
|
@@ -64,6 +70,7 @@ export type CredentialKeyBranch = Selectable<CredentialKeyBranchTable>;
|
|
|
64
70
|
* regardless.
|
|
65
71
|
*/
|
|
66
72
|
export type CredentialWrappingTable = {
|
|
73
|
+
owner_did: string;
|
|
67
74
|
wrapping_id: string;
|
|
68
75
|
key_id: string;
|
|
69
76
|
key_version: number;
|
|
@@ -71,6 +78,7 @@ export type CredentialWrappingTable = {
|
|
|
71
78
|
iv: Uint8Array;
|
|
72
79
|
wrapped_key: Uint8Array;
|
|
73
80
|
recipient_did: string | null;
|
|
81
|
+
branch_id: BranchColumn;
|
|
74
82
|
created_at: CreatedAtColumn;
|
|
75
83
|
};
|
|
76
84
|
export type CredentialWrapping = Selectable<CredentialWrappingTable>;
|
|
@@ -92,12 +100,14 @@ export type CredentialWrappingWithBranch = CredentialWrapping & {
|
|
|
92
100
|
* readable, and the version is what selects the wrapping that opens them.
|
|
93
101
|
*/
|
|
94
102
|
export type CredentialEntryTable = {
|
|
103
|
+
owner_did: string;
|
|
95
104
|
entry_id: string;
|
|
96
105
|
key_id: string;
|
|
97
106
|
key_version: number;
|
|
98
107
|
iv: Uint8Array;
|
|
99
108
|
ciphertext: Uint8Array;
|
|
100
109
|
hlc: string;
|
|
110
|
+
branch_id: BranchColumn;
|
|
101
111
|
created_at: CreatedAtColumn;
|
|
102
112
|
};
|
|
103
113
|
export type CredentialEntry = Selectable<CredentialEntryTable>;
|
|
@@ -105,67 +115,6 @@ export type InsertCredentialEntry = Insertable<CredentialEntryTable>;
|
|
|
105
115
|
export type CredentialEntryWithProvenance = CredentialEntry & {
|
|
106
116
|
author_did: string | null;
|
|
107
117
|
};
|
|
108
|
-
/** Which of the three tables a signed op speaks for. */
|
|
109
|
-
export type CredentialOpSubjectKind = 'key' | 'wrapping' | 'entry';
|
|
110
|
-
/**
|
|
111
|
-
* The latest signed op for one key, wrapping or entry — replaced on update, not
|
|
112
|
-
* appended. There is no op history: a credential op is a whole-row replacement,
|
|
113
|
-
* and a log would retain the wrapped key and the ciphertext that a rotation
|
|
114
|
-
* exists to destroy.
|
|
115
|
-
*
|
|
116
|
-
* One table for all three subjects because a merkle tree wants its leaves from
|
|
117
|
-
* one scan rather than a union, and because a deletion has to travel: a lane
|
|
118
|
-
* that only adds cannot express absence, so a delete leaves a row here with
|
|
119
|
-
* `deleted` set and removes nothing else.
|
|
120
|
-
*
|
|
121
|
-
* `deleted` is an integer 0/1 rather than a boolean so it reads back identically
|
|
122
|
-
* on SQLite and Postgres, matching the other flags in this stack.
|
|
123
|
-
*
|
|
124
|
-
* `hlc` is the op author's stamp, not the local write time. For an entry it is
|
|
125
|
-
* the same string the entry row carries — the two must agree, and a test pins
|
|
126
|
-
* that.
|
|
127
|
-
*/
|
|
128
|
-
export type CredentialOpTable = {
|
|
129
|
-
subject_kind: string;
|
|
130
|
-
subject_id: string;
|
|
131
|
-
/** So a whole vault can be served, swept or dropped together. */
|
|
132
|
-
key_id: string;
|
|
133
|
-
/** The op's verified issuer, normalized — this one IS compared. */
|
|
134
|
-
author_did: string;
|
|
135
|
-
hlc: string;
|
|
136
|
-
op_jwt: string;
|
|
137
|
-
/** Digest of `op_jwt`, as the graph mutation log addresses its mutations. */
|
|
138
|
-
op_hash: string;
|
|
139
|
-
/**
|
|
140
|
-
* Which content-key branch this op belongs to, `${hlc}:${opHash}` of the
|
|
141
|
-
* key-op that introduced the branch. A keyless party partitions and ranks
|
|
142
|
-
* branches by this without holding a wrapping. Empty for an op minted outside
|
|
143
|
-
* a rotation/create, which does not yet resolve the branch it joins.
|
|
144
|
-
*/
|
|
145
|
-
branch_id: string;
|
|
146
|
-
deleted: DefaultedNumberColumn;
|
|
147
|
-
created_at: CreatedAtColumn;
|
|
148
|
-
};
|
|
149
|
-
export type CredentialOp = Selectable<CredentialOpTable>;
|
|
150
|
-
export type InsertCredentialOp = Insertable<CredentialOpTable>;
|
|
151
|
-
/**
|
|
152
|
-
* A local pointer from an opaque `(namespace, key)` to the entry id it
|
|
153
|
-
* currently names. Not an op subject: it carries no `hlc` and never appears in
|
|
154
|
-
* `kubun_credential_ops` or the merkle scan, because it is a per-device index
|
|
155
|
-
* rather than a replicated fact — each device that resolves the same
|
|
156
|
-
* `(namespace, key)` points it at the entry it converges on locally.
|
|
157
|
-
*
|
|
158
|
-
* The store does not interpret `namespace`, `alias_key` or `entry_id` — they
|
|
159
|
-
* are stored and returned verbatim, whatever a caller passes.
|
|
160
|
-
*/
|
|
161
|
-
export type KubunCredentialAliasTable = {
|
|
162
|
-
namespace: string;
|
|
163
|
-
alias_key: string;
|
|
164
|
-
entry_id: string;
|
|
165
|
-
created_at: CreatedAtColumn;
|
|
166
|
-
};
|
|
167
|
-
export type CredentialAlias = Selectable<KubunCredentialAliasTable>;
|
|
168
|
-
export type InsertCredentialAlias = Insertable<KubunCredentialAliasTable>;
|
|
169
118
|
/**
|
|
170
119
|
* The grantor-authority envelope backing one wrapping — a signed token proving
|
|
171
120
|
* whoever minted the wrapping held the right to grant it, carried beside it
|
|
@@ -173,6 +122,7 @@ export type InsertCredentialAlias = Insertable<KubunCredentialAliasTable>;
|
|
|
173
122
|
* every `put` strictly-greater, so a stale/replayed envelope is a silent no-op.
|
|
174
123
|
*/
|
|
175
124
|
export type CredentialWrappingAuthorityTable = {
|
|
125
|
+
owner_did: string;
|
|
176
126
|
wrapping_id: string;
|
|
177
127
|
authority_token: string;
|
|
178
128
|
refresh_seq: number;
|
|
@@ -267,6 +217,7 @@ export type InsertCredentialManifest = Insertable<CredentialManifestTable>;
|
|
|
267
217
|
* shape as {@link CredentialWrappingAuthorityTable}.
|
|
268
218
|
*/
|
|
269
219
|
export type CredentialKeyOwnerDelegationTable = {
|
|
220
|
+
owner_did: string;
|
|
270
221
|
key_id: string;
|
|
271
222
|
delegation_tokens: JSONArrayColumn<string>;
|
|
272
223
|
refresh_seq: number;
|
|
@@ -274,13 +225,82 @@ export type CredentialKeyOwnerDelegationTable = {
|
|
|
274
225
|
};
|
|
275
226
|
export type CredentialKeyOwnerDelegation = Selectable<CredentialKeyOwnerDelegationTable>;
|
|
276
227
|
export type InsertCredentialKeyOwnerDelegation = Insertable<CredentialKeyOwnerDelegationTable>;
|
|
228
|
+
type JSONObjectColumn = ColumnType<Record<string, unknown>, Record<string, unknown> | string, Record<string, unknown> | string>;
|
|
229
|
+
/**
|
|
230
|
+
* A signed copy of the selected owner-qualified credential decision, addressed by
|
|
231
|
+
* `(type, owner, id, op_hash)`, tombstones included. A deleted record stays with no payload, so an
|
|
232
|
+
* older write cannot come back.
|
|
233
|
+
*
|
|
234
|
+
* The indexed tables (keys, key branches, wrappings, wrapping authority, entries, manifests,
|
|
235
|
+
* revocations) are projections of the live rows here, written in the same transaction. A
|
|
236
|
+
* row the legacy verbs wrote into an indexed table has no row here: it is local state that
|
|
237
|
+
* no record speaks for, and the record lane never lists it.
|
|
238
|
+
*
|
|
239
|
+
* `owner`, `hlc`, `payload_digest`, `deleted` and `meta` repeat fields of the claim that
|
|
240
|
+
* `op_jwt` signs, so the table can be indexed without verifying a token. `payload` is the
|
|
241
|
+
* exact byte string the claim's digest covers. `deleted` is an integer 0/1 so it reads back
|
|
242
|
+
* identically on SQLite and Postgres.
|
|
243
|
+
*/
|
|
244
|
+
export type CredentialRecordTable = {
|
|
245
|
+
type: string;
|
|
246
|
+
id: string;
|
|
247
|
+
owner: string;
|
|
248
|
+
hlc: string;
|
|
249
|
+
op_jwt: string;
|
|
250
|
+
op_hash: string;
|
|
251
|
+
payload_digest: string | null;
|
|
252
|
+
payload: Uint8Array | null;
|
|
253
|
+
deleted: number;
|
|
254
|
+
meta: JSONObjectColumn;
|
|
255
|
+
created_at: CreatedAtColumn;
|
|
256
|
+
};
|
|
257
|
+
export type CredentialRecordRow = Selectable<CredentialRecordTable>;
|
|
258
|
+
/** Signed entry operations retained per branch for fork comparison and repair. */
|
|
259
|
+
export type CredentialEntryCandidateTable = {
|
|
260
|
+
owner_did: string;
|
|
261
|
+
key_id: string;
|
|
262
|
+
key_version: number;
|
|
263
|
+
branch_id: string;
|
|
264
|
+
entry_id: string;
|
|
265
|
+
owner: string;
|
|
266
|
+
hlc: string;
|
|
267
|
+
op_jwt: string;
|
|
268
|
+
op_hash: string;
|
|
269
|
+
payload: Uint8Array | null;
|
|
270
|
+
created_at: CreatedAtColumn;
|
|
271
|
+
};
|
|
272
|
+
/**
|
|
273
|
+
* A recipient revoked from a key, projected from a `credential.revocation` record. Keyed by
|
|
274
|
+
* `(key_id, recipient_did)` with the recipient normalized. Only a revocation record writes
|
|
275
|
+
* here: removing a wrapping, as rotation cleanup does, never touches it, which is what lets
|
|
276
|
+
* a revocation be told apart from cleanup after replicas merge.
|
|
277
|
+
*/
|
|
278
|
+
export type CredentialRevocationTable = {
|
|
279
|
+
key_id: string;
|
|
280
|
+
recipient_did: string;
|
|
281
|
+
owner_did: string;
|
|
282
|
+
created_at: CreatedAtColumn;
|
|
283
|
+
};
|
|
284
|
+
export type CredentialRevocation = Selectable<CredentialRevocationTable>;
|
|
285
|
+
/**
|
|
286
|
+
* A local pointer from `(provider, owner, device)` to the entry holding that device's own
|
|
287
|
+
* grant. Like the two-key alias it is a per-device index, not a replicated fact, and the
|
|
288
|
+
* store stores and returns its values verbatim: a caller normalizes the DIDs.
|
|
289
|
+
*/
|
|
290
|
+
export type CredentialDeviceAliasTable = {
|
|
291
|
+
provider_name: string;
|
|
292
|
+
owner_did: string;
|
|
293
|
+
device_did: string;
|
|
294
|
+
entry_id: string;
|
|
295
|
+
entry_owner_did: string;
|
|
296
|
+
created_at: CreatedAtColumn;
|
|
297
|
+
};
|
|
298
|
+
export type CredentialDeviceAlias = Selectable<CredentialDeviceAliasTable>;
|
|
277
299
|
export type CredentialTables = {
|
|
278
300
|
kubun_credential_keys: CredentialKeyTable;
|
|
279
301
|
kubun_credential_key_branches: CredentialKeyBranchTable;
|
|
280
302
|
kubun_credential_wrappings: CredentialWrappingTable;
|
|
281
303
|
kubun_credential_entries: CredentialEntryTable;
|
|
282
|
-
kubun_credential_ops: CredentialOpTable;
|
|
283
|
-
kubun_credential_aliases: KubunCredentialAliasTable;
|
|
284
304
|
kubun_credential_wrapping_authority: CredentialWrappingAuthorityTable;
|
|
285
305
|
kubun_credential_grantor_counters: CredentialGrantorCounterTable;
|
|
286
306
|
kubun_credential_provisioning_epochs: CredentialProvisioningEpochTable;
|
|
@@ -288,5 +308,9 @@ export type CredentialTables = {
|
|
|
288
308
|
kubun_credential_provisioning_attempts: CredentialProvisioningAttemptTable;
|
|
289
309
|
kubun_credential_manifests: CredentialManifestTable;
|
|
290
310
|
kubun_credential_key_owner_delegation: CredentialKeyOwnerDelegationTable;
|
|
311
|
+
kubun_credential_records: CredentialRecordTable;
|
|
312
|
+
kubun_credential_entry_candidates: CredentialEntryCandidateTable;
|
|
313
|
+
kubun_credential_revocations: CredentialRevocationTable;
|
|
314
|
+
kubun_credential_device_aliases: CredentialDeviceAliasTable;
|
|
291
315
|
};
|
|
292
316
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubun/store-credential",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"keywords": [],
|
|
5
5
|
"license": "see LICENSE.md",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -15,15 +15,18 @@
|
|
|
15
15
|
"LICENSE.md"
|
|
16
16
|
],
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@kokuin/token": "^0.5.
|
|
19
|
-
"@kubun/db": "^0.
|
|
20
|
-
"@kubun/db-adapter": "^0.
|
|
21
|
-
"
|
|
18
|
+
"@kokuin/token": "^0.5.1",
|
|
19
|
+
"@kubun/db": "^0.18.0",
|
|
20
|
+
"@kubun/db-adapter": "^0.18.0",
|
|
21
|
+
"@kubun/records": "^0.18.0",
|
|
22
|
+
"@sozai/codec": "^0.4.3",
|
|
23
|
+
"kysely": "^0.29.7"
|
|
22
24
|
},
|
|
23
25
|
"devDependencies": {
|
|
24
|
-
"@kubun/db-better-sqlite": "^0.
|
|
25
|
-
"@kubun/db-postgres": "^0.
|
|
26
|
-
"@
|
|
26
|
+
"@kubun/db-better-sqlite": "^0.18.0",
|
|
27
|
+
"@kubun/db-postgres": "^0.18.0",
|
|
28
|
+
"@kubun/hlc": "^0.18.0",
|
|
29
|
+
"@testcontainers/postgresql": "^12.2.1"
|
|
27
30
|
},
|
|
28
31
|
"publishConfig": {
|
|
29
32
|
"access": "public"
|