@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 CHANGED
@@ -1,3 +1,25 @@
1
- # Temporary Holding Version
1
+ # @kokuin/store-controller
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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,2 @@
1
+ export { createControllerStore } from './api.js';
2
+ export { CONTROLLER_STORE, controllerStoreDefinition, getControllerStore } from './definition.js';
@@ -0,0 +1,2 @@
1
+ import type { Migration, MigrationContext } from '@hozon/db';
2
+ export declare function getControllerMigrations(ctx: MigrationContext): Record<string, Migration>;
@@ -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
+ }
@@ -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.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
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
  }