@kubun/store-graph 0.17.1 → 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/access-default-records.d.ts +18 -0
- package/lib/access-default-records.js +167 -0
- package/lib/api.d.ts +382 -51
- package/lib/api.js +2397 -186
- package/lib/catalog-merge.d.ts +5 -0
- package/lib/catalog-merge.js +117 -0
- package/lib/catalog-records.d.ts +34 -0
- package/lib/catalog-records.js +527 -0
- package/lib/catalog-scope.d.ts +120 -0
- package/lib/catalog-scope.js +165 -0
- package/lib/catalog-state.d.ts +21 -0
- package/lib/catalog-state.js +122 -0
- package/lib/catalog-types.d.ts +71 -0
- package/lib/catalog-types.js +1 -0
- package/lib/errors.d.ts +15 -0
- package/lib/errors.js +33 -0
- package/lib/identifiers.d.ts +7 -0
- package/lib/identifiers.js +27 -0
- package/lib/index.d.ts +11 -4
- package/lib/index.js +7 -2
- package/lib/migrations.d.ts +8 -10
- package/lib/migrations.js +111 -18
- package/lib/query-builder.js +1 -1
- package/lib/tables.d.ts +194 -39
- package/package.json +14 -11
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { MethodRegistry } from '@kokuin/token';
|
|
2
|
+
import type { StoreProvider } from '@kubun/db';
|
|
3
|
+
import { type Adapter } from '@kubun/db-adapter';
|
|
4
|
+
import { type RecordStoreAdapter } from '@kubun/records';
|
|
5
|
+
import { type Kysely } from 'kysely';
|
|
6
|
+
import type { GraphTables } from './tables.js';
|
|
7
|
+
export declare class GraphAccessDefaultRecordTypeError extends Error {
|
|
8
|
+
constructor(type: string);
|
|
9
|
+
}
|
|
10
|
+
export declare class GraphAccessDefaultRecordError extends Error {
|
|
11
|
+
constructor(message: string);
|
|
12
|
+
}
|
|
13
|
+
export type GraphAccessDefaultRecordMethods = {
|
|
14
|
+
accessDefaultRecords(methods?: MethodRegistry, ownerDID?: string): RecordStoreAdapter;
|
|
15
|
+
};
|
|
16
|
+
export declare function createGraphAccessDefaultRecordMethods(db: Kysely<GraphTables>, adapter: Adapter): GraphAccessDefaultRecordMethods;
|
|
17
|
+
export declare function accessDefaultRecordProvider(adapter: RecordStoreAdapter): StoreProvider;
|
|
18
|
+
export declare function graphAccessDefaultRecordAdapter(provider: StoreProvider, methods?: MethodRegistry, ownerDID?: string): Promise<RecordStoreAdapter>;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { AbstractPostgresAdapter, chunked } from '@kubun/db-adapter';
|
|
2
|
+
import { compareRecordDecisionHLC, recordDecisionFromVerifiedCopies, recordKeyText, recordPayloadMatches, verifySignedRecord } from '@kubun/records';
|
|
3
|
+
import { sql } from 'kysely';
|
|
4
|
+
export class GraphAccessDefaultRecordTypeError extends Error {
|
|
5
|
+
constructor(type){
|
|
6
|
+
super(`Unsupported access default record type: ${type}`);
|
|
7
|
+
this.name = 'GraphAccessDefaultRecordTypeError';
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export class GraphAccessDefaultRecordError extends Error {
|
|
11
|
+
constructor(message){
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = 'GraphAccessDefaultRecordError';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function requireType(type) {
|
|
17
|
+
if (type !== 'access.default') throw new GraphAccessDefaultRecordTypeError(type);
|
|
18
|
+
}
|
|
19
|
+
async function decisionFromRow(row, methods) {
|
|
20
|
+
const copy = {
|
|
21
|
+
type: row.type,
|
|
22
|
+
owner: row.owner,
|
|
23
|
+
id: row.id,
|
|
24
|
+
hlc: row.hlc,
|
|
25
|
+
opHash: row.op_hash,
|
|
26
|
+
opJWT: row.op_jwt,
|
|
27
|
+
payload: row.payload === null ? null : Uint8Array.from(row.payload)
|
|
28
|
+
};
|
|
29
|
+
const op = await verifySignedRecord(copy, methods);
|
|
30
|
+
if (op == null || !recordPayloadMatches(copy, op.claim)) return null;
|
|
31
|
+
return recordDecisionFromVerifiedCopies({
|
|
32
|
+
type: row.type,
|
|
33
|
+
owner: row.owner,
|
|
34
|
+
id: row.id
|
|
35
|
+
}, [
|
|
36
|
+
{
|
|
37
|
+
copy,
|
|
38
|
+
op
|
|
39
|
+
}
|
|
40
|
+
], 'hlc');
|
|
41
|
+
}
|
|
42
|
+
export function createGraphAccessDefaultRecordMethods(db, adapter) {
|
|
43
|
+
return {
|
|
44
|
+
accessDefaultRecords (methods, ownerDID) {
|
|
45
|
+
const records = {
|
|
46
|
+
isTransaction: db.isTransaction,
|
|
47
|
+
async transaction (fn) {
|
|
48
|
+
if (db.isTransaction) return await fn(records);
|
|
49
|
+
return await db.transaction().execute(async (tx)=>fn(createGraphAccessDefaultRecordMethods(tx, adapter).accessDefaultRecords(methods, ownerDID)));
|
|
50
|
+
},
|
|
51
|
+
async get (key) {
|
|
52
|
+
requireType(key.type);
|
|
53
|
+
const row = await db.selectFrom('kubun_graph_access_default_records').selectAll().where('type', '=', key.type).where('owner', '=', key.owner).where('id', '=', key.id).executeTakeFirst();
|
|
54
|
+
return row == null ? null : await decisionFromRow(row, methods);
|
|
55
|
+
},
|
|
56
|
+
async getMany (keys) {
|
|
57
|
+
for (const key of keys)requireType(key.type);
|
|
58
|
+
const wanted = new Set(keys.map(recordKeyText));
|
|
59
|
+
const decisions = new Map();
|
|
60
|
+
for (const chunk of chunked(keys.map((key)=>key.id))){
|
|
61
|
+
const rows = await db.selectFrom('kubun_graph_access_default_records').selectAll().where('type', '=', 'access.default').where('id', 'in', chunk).execute();
|
|
62
|
+
for (const row of rows){
|
|
63
|
+
const text = recordKeyText(row);
|
|
64
|
+
if (!wanted.has(text)) continue;
|
|
65
|
+
const decision = await decisionFromRow(row, methods);
|
|
66
|
+
if (decision !== null) decisions.set(text, decision);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return decisions;
|
|
70
|
+
},
|
|
71
|
+
async list (types) {
|
|
72
|
+
types.forEach(requireType);
|
|
73
|
+
if (types.length === 0) return [];
|
|
74
|
+
let query = db.selectFrom('kubun_graph_access_default_records').select([
|
|
75
|
+
'owner',
|
|
76
|
+
'id'
|
|
77
|
+
]).where('type', '=', 'access.default');
|
|
78
|
+
if (ownerDID != null) query = query.where('owner', '=', ownerDID);
|
|
79
|
+
const rows = await query.orderBy('owner').orderBy('id').execute();
|
|
80
|
+
const decisions = await Promise.all(rows.map(async (row)=>records.get({
|
|
81
|
+
type: 'access.default',
|
|
82
|
+
...row
|
|
83
|
+
})));
|
|
84
|
+
return decisions.filter((decision)=>decision !== null);
|
|
85
|
+
},
|
|
86
|
+
async putCopy (record) {
|
|
87
|
+
requireType(record.type);
|
|
88
|
+
const op = await verifySignedRecord(record, methods);
|
|
89
|
+
if (op === null || !recordPayloadMatches(record, op.claim)) throw new GraphAccessDefaultRecordError('Invalid signed access default record');
|
|
90
|
+
if (!db.isTransaction) return await records.transaction(async (tx)=>tx.putCopy(record));
|
|
91
|
+
await records.lock?.([
|
|
92
|
+
{
|
|
93
|
+
type: record.type,
|
|
94
|
+
key: JSON.stringify([
|
|
95
|
+
record.owner,
|
|
96
|
+
record.id
|
|
97
|
+
])
|
|
98
|
+
}
|
|
99
|
+
]);
|
|
100
|
+
const stored = await records.get(record);
|
|
101
|
+
const incoming = recordDecisionFromVerifiedCopies({
|
|
102
|
+
type: record.type,
|
|
103
|
+
owner: record.owner,
|
|
104
|
+
id: record.id
|
|
105
|
+
}, [
|
|
106
|
+
{
|
|
107
|
+
copy: record,
|
|
108
|
+
op
|
|
109
|
+
}
|
|
110
|
+
], 'hlc');
|
|
111
|
+
if (incoming === null) throw new GraphAccessDefaultRecordError('Invalid access default decision');
|
|
112
|
+
const order = stored === null ? 1 : compareRecordDecisionHLC(incoming.decisionHLC, stored.decisionHLC);
|
|
113
|
+
if (stored !== null && (order < 0 || order === 0 && (incoming.contentDigest < stored.contentDigest || incoming.contentDigest === stored.contentDigest && record.opHash >= (stored.copies[0]?.opHash ?? record.opHash)))) return;
|
|
114
|
+
await db.insertInto('kubun_graph_access_default_records').values({
|
|
115
|
+
type: record.type,
|
|
116
|
+
owner: record.owner,
|
|
117
|
+
id: record.id,
|
|
118
|
+
hlc: record.hlc,
|
|
119
|
+
op_hash: record.opHash,
|
|
120
|
+
op_jwt: record.opJWT,
|
|
121
|
+
payload: record.payload === null ? null : adapter.encodeBinary(record.payload)
|
|
122
|
+
}).onConflict((oc)=>oc.columns([
|
|
123
|
+
'type',
|
|
124
|
+
'owner',
|
|
125
|
+
'id'
|
|
126
|
+
]).doUpdateSet({
|
|
127
|
+
hlc: record.hlc,
|
|
128
|
+
op_hash: record.opHash,
|
|
129
|
+
op_jwt: record.opJWT,
|
|
130
|
+
payload: record.payload === null ? null : adapter.encodeBinary(record.payload)
|
|
131
|
+
})).execute();
|
|
132
|
+
},
|
|
133
|
+
async pruneCopy (key, signerDID) {
|
|
134
|
+
requireType(key.type);
|
|
135
|
+
// One authenticated signer owns each slot; never prune its sole durable decision.
|
|
136
|
+
void signerDID;
|
|
137
|
+
},
|
|
138
|
+
async lock (keys) {
|
|
139
|
+
for (const { type } of keys)requireType(type);
|
|
140
|
+
if (!db.isTransaction) throw new GraphAccessDefaultRecordError('Access default locks require a transaction');
|
|
141
|
+
if (!(adapter instanceof AbstractPostgresAdapter)) return;
|
|
142
|
+
for (const key of [
|
|
143
|
+
...new Set(keys.map(({ key })=>key))
|
|
144
|
+
].sort())await sql`select pg_advisory_xact_lock(hashtextextended(${`access.default:${key}`}, 0))`.execute(db);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
return records;
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
const providers = new WeakMap();
|
|
152
|
+
export function accessDefaultRecordProvider(adapter) {
|
|
153
|
+
const provider = providers.get(adapter.adapterForType?.('access.default') ?? adapter);
|
|
154
|
+
if (provider == null) throw new GraphAccessDefaultRecordError('Access default requires its graph record adapter');
|
|
155
|
+
return provider;
|
|
156
|
+
}
|
|
157
|
+
export async function graphAccessDefaultRecordAdapter(provider, methods, ownerDID) {
|
|
158
|
+
const graph = await provider.getStore('graph');
|
|
159
|
+
const base = graph.accessDefaultRecords(methods, ownerDID);
|
|
160
|
+
const records = {
|
|
161
|
+
...base,
|
|
162
|
+
isTransaction: provider.isTransactionBound,
|
|
163
|
+
transaction: async (fn)=>provider.withTransaction(async (tx)=>fn(await graphAccessDefaultRecordAdapter(tx, methods, ownerDID)))
|
|
164
|
+
};
|
|
165
|
+
providers.set(records, provider);
|
|
166
|
+
return records;
|
|
167
|
+
}
|