@kokuin/store-controller 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.
- package/README.md +24 -2
- package/lib/api.d.ts +9 -0
- package/lib/api.js +40 -0
- package/lib/definition.d.ts +6 -0
- package/lib/definition.js +11 -0
- package/lib/index.d.ts +4 -0
- package/lib/index.js +2 -0
- package/lib/migrations.d.ts +2 -0
- package/lib/migrations.js +18 -0
- package/lib/tables.d.ts +16 -0
- package/lib/tables.js +1 -0
- package/package.json +44 -3
package/README.md
CHANGED
|
@@ -1,3 +1,25 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @kokuin/store-controller
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Hozon-backed store for `did:kokuin:` controller key event logs.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install @kokuin/store-controller
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { HozonDB } from '@hozon/db'
|
|
15
|
+
import { controllerStoreDefinition, getControllerStore } from '@kokuin/store-controller'
|
|
16
|
+
|
|
17
|
+
const db = new HozonDB({ adapter }) // an @hozon/node-sqlite or @hozon/postgres adapter
|
|
18
|
+
db.register(controllerStoreDefinition)
|
|
19
|
+
const logs = await getControllerStore(db)
|
|
20
|
+
|
|
21
|
+
await logs.set(did, log) // after the log has folded
|
|
22
|
+
const stored = await logs.get(did)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
See [docs/reference/stores.md](../../docs/reference/stores.md).
|
package/lib/api.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Adapter } from '@hozon/adapter';
|
|
2
|
+
import type { Kysely } from '@hozon/db';
|
|
3
|
+
import type { LogStore } from '@kokuin/controller';
|
|
4
|
+
import type { ControllerStoreTables } from './tables.js';
|
|
5
|
+
export type ControllerStoreAPI = LogStore & {
|
|
6
|
+
/** Last explicit store write, including a locally seeded controller log. */
|
|
7
|
+
getObservedAt(did: string): Promise<Date | undefined>;
|
|
8
|
+
};
|
|
9
|
+
export declare function createControllerStore(db: Kysely<ControllerStoreTables>, adapter: Adapter): ControllerStoreAPI;
|
package/lib/api.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// ParseJSONResultsPlugin usually parses the JSON column back to an object, but a
|
|
2
|
+
// value can still arrive as a string (adapter/driver dependent). Handle both.
|
|
3
|
+
function decodeLog(value) {
|
|
4
|
+
return typeof value === 'string' ? JSON.parse(value) : value;
|
|
5
|
+
}
|
|
6
|
+
export function createControllerStore(db, adapter) {
|
|
7
|
+
const api = {
|
|
8
|
+
async get (did) {
|
|
9
|
+
const row = await db.selectFrom('controller_logs').select('log').where('did', '=', did).executeTakeFirst();
|
|
10
|
+
return row == null ? undefined : decodeLog(row.log);
|
|
11
|
+
},
|
|
12
|
+
async getObservedAt (did) {
|
|
13
|
+
const row = await db.selectFrom('controller_logs').select([
|
|
14
|
+
'created_at',
|
|
15
|
+
'updated_at'
|
|
16
|
+
]).where('did', '=', did).executeTakeFirst();
|
|
17
|
+
if (row == null) return undefined;
|
|
18
|
+
return adapter.decodeTimestamp(row.updated_at ?? row.created_at);
|
|
19
|
+
},
|
|
20
|
+
// Plain last-writer-wins: `set` is called only after the log has folded, so
|
|
21
|
+
// arbitration lives in the resolver, never here. See `@kokuin/controller`.
|
|
22
|
+
// A caller ingesting an UNTRUSTED log (e.g. one pulled from a peer) must run
|
|
23
|
+
// it through the resolver's `authoritativeStates`/`history` guard first — this
|
|
24
|
+
// store will not refuse a truncated or forked log on its own, so a direct
|
|
25
|
+
// `set` of peer bytes would bypass the anti-truncation guarantee.
|
|
26
|
+
async set (did, log) {
|
|
27
|
+
const encoded = adapter.encodeJSON(log);
|
|
28
|
+
const observedAt = adapter.encodeTimestamp(new Date());
|
|
29
|
+
await db.insertInto('controller_logs').values({
|
|
30
|
+
did,
|
|
31
|
+
log: encoded,
|
|
32
|
+
updated_at: observedAt
|
|
33
|
+
}).onConflict((oc)=>oc.column('did').doUpdateSet((eb)=>({
|
|
34
|
+
log: eb.ref('excluded.log'),
|
|
35
|
+
updated_at: observedAt
|
|
36
|
+
}))).execute();
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
return api;
|
|
40
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { StoreDefinition, StoreProvider } from '@hozon/db';
|
|
2
|
+
import type { ControllerStoreAPI } from './api.js';
|
|
3
|
+
import type { ControllerStoreTables } from './tables.js';
|
|
4
|
+
export declare const CONTROLLER_STORE: "controller";
|
|
5
|
+
export declare const controllerStoreDefinition: StoreDefinition<ControllerStoreTables, ControllerStoreAPI>;
|
|
6
|
+
export declare function getControllerStore(provider: StoreProvider): Promise<ControllerStoreAPI>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createControllerStore } from './api.js';
|
|
2
|
+
import { getControllerMigrations } from './migrations.js';
|
|
3
|
+
export const CONTROLLER_STORE = 'controller';
|
|
4
|
+
export const controllerStoreDefinition = {
|
|
5
|
+
name: CONTROLLER_STORE,
|
|
6
|
+
migrations: getControllerMigrations,
|
|
7
|
+
createAPI: createControllerStore
|
|
8
|
+
};
|
|
9
|
+
export async function getControllerStore(provider) {
|
|
10
|
+
return provider.getStore(CONTROLLER_STORE);
|
|
11
|
+
}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type { ControllerStoreAPI } from './api.js';
|
|
2
|
+
export { createControllerStore } from './api.js';
|
|
3
|
+
export { CONTROLLER_STORE, controllerStoreDefinition, getControllerStore, } from './definition.js';
|
|
4
|
+
export type { ControllerLogRow, ControllerLogTable, ControllerStoreTables, InsertControllerLog, } from './tables.js';
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function getControllerMigrations(ctx) {
|
|
2
|
+
const t = ctx.types;
|
|
3
|
+
const now = ctx.functions.now;
|
|
4
|
+
const init = {
|
|
5
|
+
async up (db) {
|
|
6
|
+
// One row per controller DID; `log` holds the signed event array verbatim.
|
|
7
|
+
await db.schema.createTable('controller_logs').ifNotExists().addColumn('did', t.text, (col)=>col.notNull()).addColumn('log', t.json, (col)=>col.notNull()).addColumn('created_at', t.timestamp, (col)=>col.defaultTo(now).notNull()).addColumn('updated_at', t.timestamp).addPrimaryKeyConstraint(`${ctx.tablePrefix}_controller_logs_pkey`, [
|
|
8
|
+
'did'
|
|
9
|
+
]).execute();
|
|
10
|
+
},
|
|
11
|
+
async down (db) {
|
|
12
|
+
await db.schema.dropTable('controller_logs').ifExists().execute();
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
return {
|
|
16
|
+
'0-init': init
|
|
17
|
+
};
|
|
18
|
+
}
|
package/lib/tables.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { CreatedAtColumn, UpdatedAtColumn } from '@hozon/adapter';
|
|
2
|
+
import type { ColumnType, Insertable, Selectable } from '@hozon/db';
|
|
3
|
+
import type { SignedEvent } from '@kokuin/controller';
|
|
4
|
+
type JSONValueColumn<T> = ColumnType<T, T, T>;
|
|
5
|
+
export type ControllerLogTable = {
|
|
6
|
+
did: string;
|
|
7
|
+
log: JSONValueColumn<Array<SignedEvent>>;
|
|
8
|
+
created_at: CreatedAtColumn;
|
|
9
|
+
updated_at: UpdatedAtColumn;
|
|
10
|
+
};
|
|
11
|
+
export type ControllerLogRow = Selectable<ControllerLogTable>;
|
|
12
|
+
export type InsertControllerLog = Insertable<ControllerLogTable>;
|
|
13
|
+
export type ControllerStoreTables = {
|
|
14
|
+
controller_logs: ControllerLogTable;
|
|
15
|
+
};
|
|
16
|
+
export {};
|
package/lib/tables.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/package.json
CHANGED
|
@@ -1,6 +1,47 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kokuin/store-controller",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Hozon-backed controller log store for did:kokuin",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"did",
|
|
7
|
+
"controller",
|
|
8
|
+
"store"
|
|
9
|
+
],
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/TairuFramework/kokuin",
|
|
13
|
+
"directory": "packages/store-controller"
|
|
14
|
+
},
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"sideEffects": false,
|
|
17
|
+
"type": "module",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": "./lib/index.js"
|
|
20
|
+
},
|
|
21
|
+
"main": "lib/index.js",
|
|
22
|
+
"types": "lib/index.d.ts",
|
|
23
|
+
"files": [
|
|
24
|
+
"lib/*"
|
|
25
|
+
],
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@hozon/adapter": "^0.1.0",
|
|
28
|
+
"@hozon/db": "^0.2.2",
|
|
29
|
+
"@kokuin/controller": "^0.1.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@hozon/node-sqlite": "^0.1.0",
|
|
33
|
+
"@types/node": "^26.6.4"
|
|
34
|
+
},
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "pnpm run build:clean && pnpm run build:js && pnpm run build:types",
|
|
40
|
+
"build:clean": "del-cli lib",
|
|
41
|
+
"build:js": "del-cli 'lib/**/*.js' && swc src -d ./lib --config-file ../../node_modules/@kigu/dev/swc.json --strip-leading-paths",
|
|
42
|
+
"build:types": "del-cli 'lib/**/*.d.ts' 'lib/**/*.d.ts.map' && tsc --emitDeclarationOnly --skipLibCheck",
|
|
43
|
+
"test": "pnpm run test:types && pnpm run test:unit",
|
|
44
|
+
"test:types": "tsc --noEmit --skipLibCheck -p tsconfig.test.json",
|
|
45
|
+
"test:unit": "vitest run"
|
|
46
|
+
}
|
|
6
47
|
}
|