@kokuin/store-vault 0.0.0-stage → 0.1.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.
Files changed (48) hide show
  1. package/README.md +126 -2
  2. package/lib/api.d.ts +5 -0
  3. package/lib/api.js +504 -0
  4. package/lib/batch.d.ts +89 -0
  5. package/lib/batch.js +159 -0
  6. package/lib/coordinator-context.d.ts +71 -0
  7. package/lib/coordinator-context.js +410 -0
  8. package/lib/coordinator-entries.d.ts +20 -0
  9. package/lib/coordinator-entries.js +258 -0
  10. package/lib/coordinator-open.d.ts +9 -0
  11. package/lib/coordinator-open.js +141 -0
  12. package/lib/coordinator-records.d.ts +27 -0
  13. package/lib/coordinator-records.js +132 -0
  14. package/lib/coordinator-rotate.d.ts +9 -0
  15. package/lib/coordinator-rotate.js +436 -0
  16. package/lib/coordinator-types.d.ts +139 -0
  17. package/lib/coordinator-types.js +1 -0
  18. package/lib/coordinator.d.ts +2 -0
  19. package/lib/coordinator.js +459 -0
  20. package/lib/definition.d.ts +6 -0
  21. package/lib/definition.js +12 -0
  22. package/lib/index.d.ts +12 -0
  23. package/lib/index.js +7 -0
  24. package/lib/migrations.d.ts +2 -0
  25. package/lib/migrations.js +52 -0
  26. package/lib/record-context.d.ts +24 -0
  27. package/lib/record-context.js +34 -0
  28. package/lib/record-definition.d.ts +7 -0
  29. package/lib/record-definition.js +93 -0
  30. package/lib/record-tables.d.ts +47 -0
  31. package/lib/record-tables.js +1 -0
  32. package/lib/records-adapter.d.ts +3 -0
  33. package/lib/records-adapter.js +197 -0
  34. package/lib/records-ports.d.ts +4 -0
  35. package/lib/records-ports.js +72 -0
  36. package/lib/records-projection.d.ts +3 -0
  37. package/lib/records-projection.js +105 -0
  38. package/lib/records-rows.d.ts +54 -0
  39. package/lib/records-rows.js +155 -0
  40. package/lib/records.d.ts +59 -0
  41. package/lib/records.js +233 -0
  42. package/lib/settle.d.ts +12 -0
  43. package/lib/settle.js +18 -0
  44. package/lib/tables.d.ts +42 -0
  45. package/lib/tables.js +1 -0
  46. package/lib/types.d.ts +74 -0
  47. package/lib/types.js +1 -0
  48. package/package.json +45 -3
package/lib/batch.d.ts ADDED
@@ -0,0 +1,89 @@
1
+ import type { Insertable, Kysely } from '@hozon/db';
2
+ import type { VaultKeyBranchTable, VaultRecordsTables } from './record-tables.js';
3
+ import type { VaultEntryTable, VaultStoreTables, VaultWrappingTable } from './tables.js';
4
+ /**
5
+ * Bound parameters allowed in one statement. Well under SQLite's oldest default limit (999) and
6
+ * Postgres's (65535), and the cap the store's own reads keep to.
7
+ */
8
+ export declare const PARAMETER_BUDGET = 500;
9
+ /** Splits `items` so no statement binds more than `PARAMETER_BUDGET` parameters. */
10
+ export declare function statementChunks<Item>(items: Iterable<Item>, parametersPerItem: number): Array<Array<Item>>;
11
+ export type WrappingValues = Insertable<Omit<VaultWrappingTable, 'mode'>>;
12
+ export type EntryValues = Insertable<Omit<VaultEntryTable, 'mode'>>;
13
+ export type WrappingKey = {
14
+ owner_did: string;
15
+ wrapping_id: string;
16
+ };
17
+ export type EntryKey = {
18
+ owner_did: string;
19
+ entry_id: string;
20
+ };
21
+ export type DecisionKey = {
22
+ type: string;
23
+ owner: string;
24
+ id: string;
25
+ };
26
+ /** Parameters each row binds. */
27
+ export declare const WRAPPING_COLUMNS = 10;
28
+ export declare const ENTRY_COLUMNS = 11;
29
+ export declare const DECISION_COLUMNS = 6;
30
+ export declare const KEY_BRANCH_COLUMNS = 6;
31
+ /**
32
+ * One multi-row upsert. A row whose stored wrapping belongs to another key is left alone and
33
+ * not counted, as the single-row upsert does.
34
+ */
35
+ export declare function upsertWrappings(db: Kysely<VaultStoreTables>, rows: Array<WrappingValues>): import("kysely").InsertQueryBuilder<VaultStoreTables, "vault_wrappings", import("kysely").InsertResult>;
36
+ /** As `upsertWrappings`, for entries. */
37
+ export declare function upsertEntries(db: Kysely<VaultStoreTables>, rows: Array<EntryValues>): import("kysely").InsertQueryBuilder<VaultStoreTables, "vault_entries", import("kysely").InsertResult>;
38
+ export declare function selectWrappings(db: Kysely<VaultStoreTables>, keys: Array<WrappingKey>): import("@hozon/db").SelectQueryBuilder<VaultStoreTables, "vault_wrappings", {
39
+ mode: "local" | "replicated";
40
+ owner_did: string;
41
+ wrapping_id: string;
42
+ key_id: string;
43
+ key_version: number;
44
+ descriptors: import("@kokuin/vault").FactorDescriptor[];
45
+ descriptor_digest: string;
46
+ iv: Uint8Array<ArrayBufferLike>;
47
+ wrapped_key: Uint8Array<ArrayBufferLike>;
48
+ recipient_did: string | null;
49
+ provenance: string;
50
+ }>;
51
+ export declare function selectEntries(db: Kysely<VaultStoreTables>, keys: Array<EntryKey>): import("@hozon/db").SelectQueryBuilder<VaultStoreTables, "vault_entries", {
52
+ mode: "local" | "replicated";
53
+ owner_did: string;
54
+ key_id: string;
55
+ key_version: number;
56
+ iv: Uint8Array<ArrayBufferLike>;
57
+ provenance: string;
58
+ entry_id: string;
59
+ kind: string;
60
+ label: string;
61
+ attributes: Record<string, string>;
62
+ ciphertext: Uint8Array<ArrayBufferLike>;
63
+ hlc: string;
64
+ }>;
65
+ /** Deletes each wrapping only while it still belongs to the named key. */
66
+ export declare function deleteWrappingRows(db: Kysely<VaultStoreTables>, rows: Array<WrappingKey & {
67
+ key_id: string;
68
+ }>): import("kysely").DeleteQueryBuilder<VaultStoreTables, "vault_wrappings", import("kysely").DeleteResult>;
69
+ /** Deletes each entry only while it still belongs to the named key. */
70
+ export declare function deleteEntryRows(db: Kysely<VaultStoreTables>, rows: Array<EntryKey & {
71
+ key_id: string;
72
+ }>): import("kysely").DeleteQueryBuilder<VaultStoreTables, "vault_entries", import("kysely").DeleteResult>;
73
+ export declare function insertDecisionClaims(db: Kysely<VaultRecordsTables>, keys: Array<DecisionKey>, mode: 'local' | 'replicated'): import("kysely").InsertQueryBuilder<VaultRecordsTables, "vault_decisions", import("kysely").InsertResult>;
74
+ export declare function selectDecisionModes(db: Kysely<VaultRecordsTables>, keys: Array<DecisionKey>): import("@hozon/db").SelectQueryBuilder<VaultRecordsTables, "vault_decisions", {
75
+ mode: "local" | "replicated";
76
+ type: string;
77
+ owner: string;
78
+ id: string;
79
+ }>;
80
+ /**
81
+ * Claims a record key for replicated mode and records its decision, in one statement. A key held
82
+ * in local mode is left alone and counts no row.
83
+ */
84
+ export declare function upsertReplicatedDecision(db: Kysely<VaultRecordsTables>, decision: DecisionKey & {
85
+ content_digest: string;
86
+ decision_hlc: string;
87
+ }): import("kysely").InsertQueryBuilder<VaultRecordsTables, "vault_decisions", import("kysely").InsertResult>;
88
+ /** One multi-row upsert of key-branch projection rows. A record ID moves to its new key. */
89
+ export declare function upsertKeyBranches(db: Kysely<VaultRecordsTables>, rows: Array<VaultKeyBranchTable>): import("kysely").InsertQueryBuilder<VaultRecordsTables, "vault_key_branches", import("kysely").InsertResult>;
package/lib/batch.js ADDED
@@ -0,0 +1,159 @@
1
+ import { chunk } from '@hozon/db';
2
+ /**
3
+ * Bound parameters allowed in one statement. Well under SQLite's oldest default limit (999) and
4
+ * Postgres's (65535), and the cap the store's own reads keep to.
5
+ */ export const PARAMETER_BUDGET = 500;
6
+ /** Splits `items` so no statement binds more than `PARAMETER_BUDGET` parameters. */ export function statementChunks(items, parametersPerItem) {
7
+ return chunk(items, Math.max(1, Math.floor(PARAMETER_BUDGET / parametersPerItem)));
8
+ }
9
+ /** Parameters each row binds. */ export const WRAPPING_COLUMNS = 10;
10
+ export const ENTRY_COLUMNS = 11;
11
+ export const DECISION_COLUMNS = 6;
12
+ export const KEY_BRANCH_COLUMNS = 6;
13
+ /**
14
+ * One multi-row upsert. A row whose stored wrapping belongs to another key is left alone and
15
+ * not counted, as the single-row upsert does.
16
+ */ export function upsertWrappings(db, rows) {
17
+ return db.insertInto('vault_wrappings').values(rows).onConflict((oc)=>{
18
+ return oc.columns([
19
+ 'owner_did',
20
+ 'wrapping_id'
21
+ ]).doUpdateSet((eb)=>{
22
+ return {
23
+ key_id: eb.ref('excluded.key_id'),
24
+ key_version: eb.ref('excluded.key_version'),
25
+ descriptors: eb.ref('excluded.descriptors'),
26
+ descriptor_digest: eb.ref('excluded.descriptor_digest'),
27
+ iv: eb.ref('excluded.iv'),
28
+ wrapped_key: eb.ref('excluded.wrapped_key'),
29
+ recipient_did: eb.ref('excluded.recipient_did'),
30
+ provenance: eb.ref('excluded.provenance')
31
+ };
32
+ }).where((eb)=>eb('vault_wrappings.key_id', '=', eb.ref('excluded.key_id')));
33
+ });
34
+ }
35
+ /** As `upsertWrappings`, for entries. */ export function upsertEntries(db, rows) {
36
+ return db.insertInto('vault_entries').values(rows).onConflict((oc)=>{
37
+ return oc.columns([
38
+ 'owner_did',
39
+ 'entry_id'
40
+ ]).doUpdateSet((eb)=>{
41
+ return {
42
+ key_id: eb.ref('excluded.key_id'),
43
+ key_version: eb.ref('excluded.key_version'),
44
+ kind: eb.ref('excluded.kind'),
45
+ label: eb.ref('excluded.label'),
46
+ attributes: eb.ref('excluded.attributes'),
47
+ iv: eb.ref('excluded.iv'),
48
+ ciphertext: eb.ref('excluded.ciphertext'),
49
+ hlc: eb.ref('excluded.hlc'),
50
+ provenance: eb.ref('excluded.provenance')
51
+ };
52
+ }).where((eb)=>eb('vault_entries.key_id', '=', eb.ref('excluded.key_id')));
53
+ });
54
+ }
55
+ export function selectWrappings(db, keys) {
56
+ return db.selectFrom('vault_wrappings').selectAll().where((eb)=>{
57
+ return eb.or(keys.map((key)=>{
58
+ return eb.and([
59
+ eb('owner_did', '=', key.owner_did),
60
+ eb('wrapping_id', '=', key.wrapping_id)
61
+ ]);
62
+ }));
63
+ });
64
+ }
65
+ export function selectEntries(db, keys) {
66
+ return db.selectFrom('vault_entries').selectAll().where((eb)=>{
67
+ return eb.or(keys.map((key)=>{
68
+ return eb.and([
69
+ eb('owner_did', '=', key.owner_did),
70
+ eb('entry_id', '=', key.entry_id)
71
+ ]);
72
+ }));
73
+ });
74
+ }
75
+ /** Deletes each wrapping only while it still belongs to the named key. */ export function deleteWrappingRows(db, rows) {
76
+ return db.deleteFrom('vault_wrappings').where((eb)=>{
77
+ return eb.or(rows.map((row)=>{
78
+ return eb.and([
79
+ eb('owner_did', '=', row.owner_did),
80
+ eb('wrapping_id', '=', row.wrapping_id),
81
+ eb('key_id', '=', row.key_id)
82
+ ]);
83
+ }));
84
+ });
85
+ }
86
+ /** Deletes each entry only while it still belongs to the named key. */ export function deleteEntryRows(db, rows) {
87
+ return db.deleteFrom('vault_entries').where((eb)=>{
88
+ return eb.or(rows.map((row)=>{
89
+ return eb.and([
90
+ eb('owner_did', '=', row.owner_did),
91
+ eb('entry_id', '=', row.entry_id),
92
+ eb('key_id', '=', row.key_id)
93
+ ]);
94
+ }));
95
+ });
96
+ }
97
+ export function insertDecisionClaims(db, keys, mode) {
98
+ return db.insertInto('vault_decisions').values(keys.map((key)=>{
99
+ return {
100
+ ...key,
101
+ mode,
102
+ content_digest: null,
103
+ decision_hlc: null
104
+ };
105
+ })).onConflict((oc)=>oc.columns([
106
+ 'type',
107
+ 'owner',
108
+ 'id'
109
+ ]).doNothing());
110
+ }
111
+ export function selectDecisionModes(db, keys) {
112
+ return db.selectFrom('vault_decisions').select([
113
+ 'type',
114
+ 'owner',
115
+ 'id',
116
+ 'mode'
117
+ ]).where((eb)=>{
118
+ return eb.or(keys.map((key)=>{
119
+ return eb.and([
120
+ eb('type', '=', key.type),
121
+ eb('owner', '=', key.owner),
122
+ eb('id', '=', key.id)
123
+ ]);
124
+ }));
125
+ });
126
+ }
127
+ /**
128
+ * Claims a record key for replicated mode and records its decision, in one statement. A key held
129
+ * in local mode is left alone and counts no row.
130
+ */ export function upsertReplicatedDecision(db, decision) {
131
+ return db.insertInto('vault_decisions').values({
132
+ ...decision,
133
+ mode: 'replicated'
134
+ }).onConflict((oc)=>{
135
+ return oc.columns([
136
+ 'type',
137
+ 'owner',
138
+ 'id'
139
+ ]).doUpdateSet({
140
+ content_digest: decision.content_digest,
141
+ decision_hlc: decision.decision_hlc
142
+ }).where('vault_decisions.mode', '=', 'replicated');
143
+ });
144
+ }
145
+ /** One multi-row upsert of key-branch projection rows. A record ID moves to its new key. */ export function upsertKeyBranches(db, rows) {
146
+ return db.insertInto('vault_key_branches').values(rows).onConflict((oc)=>{
147
+ return oc.columns([
148
+ 'owner_did',
149
+ 'record_id'
150
+ ]).doUpdateSet((eb)=>{
151
+ return {
152
+ key_id: eb.ref('excluded.key_id'),
153
+ key_version: eb.ref('excluded.key_version'),
154
+ branch_id: eb.ref('excluded.branch_id'),
155
+ op_jwt: eb.ref('excluded.op_jwt')
156
+ };
157
+ });
158
+ });
159
+ }
@@ -0,0 +1,71 @@
1
+ import type { RecordKey, SignedRecord, VerifiedRecordOp } from '@kokuin/records';
2
+ import type { KeyCache, VaultRecordMetas, VaultRecordPayloads, VaultRecordType } from '@kokuin/vault';
3
+ import type { CoordinatorCall, VaultCoordinatorParams } from './coordinator-types.js';
4
+ import type { VaultKeyBranch, VaultRecords, VaultWritePreconditions } from './records.js';
5
+ import type { KeyAddress, KeyRow, VaultStoreAPI, WrappingRow } from './types.js';
6
+ export declare class VaultNotAuthorized extends Error {
7
+ constructor(verb: 'read' | 'administer');
8
+ }
9
+ export declare class VaultNotFound extends Error {
10
+ constructor();
11
+ }
12
+ export declare class VaultRepairPending extends Error {
13
+ constructor();
14
+ }
15
+ export declare class VaultSupersededBranch extends Error {
16
+ constructor();
17
+ }
18
+ export declare class VaultKeyLocked extends Error {
19
+ constructor();
20
+ }
21
+ export type StoredRecordEvidence = {
22
+ record: SignedRecord;
23
+ op: VerifiedRecordOp;
24
+ };
25
+ export type WrappingEvidence = StoredRecordEvidence & {
26
+ expectedDescriptorDigest: string;
27
+ };
28
+ export type KeyBranch = {
29
+ branchID: string;
30
+ branchRecordID: string;
31
+ suite: number;
32
+ };
33
+ export type KeyRecords = {
34
+ records: Array<SignedRecord>;
35
+ branchID: string;
36
+ branchRecordID: string;
37
+ };
38
+ export type CoordinatorContext = {
39
+ params: VaultCoordinatorParams;
40
+ cache: KeyCache;
41
+ cacheID(address: KeyAddress): string;
42
+ records: VaultRecords | undefined;
43
+ recordsFor(store: VaultStoreAPI): VaultRecords;
44
+ authorize(operation: string, verb: 'read' | 'administer', address: {
45
+ ownerDID: string;
46
+ keyID?: string;
47
+ entryID?: string;
48
+ } & CoordinatorCall, store?: VaultStoreAPI): Promise<void>;
49
+ key(address: KeyAddress, store?: VaultStoreAPI): Promise<KeyRow>;
50
+ signed<Type extends VaultRecordType>(type: Type, address: {
51
+ ownerDID: string;
52
+ id: string;
53
+ }, payload: VaultRecordPayloads[Type] | null, meta: VaultRecordMetas[Type], hlc?: string): Promise<SignedRecord>;
54
+ verified(record: SignedRecord): Promise<VerifiedRecordOp>;
55
+ stored(address: RecordKey, store?: VaultStoreAPI): Promise<StoredRecordEvidence>;
56
+ /** One batched read. Throws the first missing or unverifiable record in address order. */
57
+ storedMany(addresses: ReadonlyArray<RecordKey>, store?: VaultStoreAPI): Promise<Array<StoredRecordEvidence>>;
58
+ wrappingEvidence(row: WrappingRow, store?: VaultStoreAPI): Promise<WrappingEvidence | undefined>;
59
+ /** Replicated evidence for each row, in row order, from one batched read. Empty in local mode. */
60
+ wrappingEvidences(rows: ReadonlyArray<WrappingRow>, store?: VaultStoreAPI): Promise<Array<WrappingEvidence>>;
61
+ branches(address: KeyAddress, version: number, store?: VaultStoreAPI): Promise<Array<VaultKeyBranch>>;
62
+ branch(address: KeyAddress, version: number, store?: VaultStoreAPI, specific?: string): Promise<KeyBranch>;
63
+ write(store: VaultStoreAPI, output: Array<SignedRecord>, preconditions: VaultWritePreconditions, call: CoordinatorCall): Promise<void>;
64
+ keyRecords(row: KeyRow, store: VaultStoreAPI, unique?: string, consumer?: unknown): Promise<KeyRecords>;
65
+ locked<Result>(addresses: ReadonlyArray<KeyAddress>, fn: (store: VaultStoreAPI) => Promise<Result>): Promise<Result>;
66
+ read<Result>(fn: (store: VaultStoreAPI) => Promise<Result>): Promise<Result>;
67
+ evict(address: KeyAddress): void;
68
+ evictAll(ownerDID: string): void;
69
+ generation(address: KeyAddress): string;
70
+ };
71
+ export declare function createCoordinatorContext(params: VaultCoordinatorParams): CoordinatorContext;