@kubun/store-connector 0.12.1 → 0.14.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/api.d.ts +4 -5
- package/lib/api.js +7 -64
- package/lib/index.d.ts +1 -1
- package/lib/migrations.js +5 -7
- package/lib/tables.d.ts +12 -7
- package/package.json +7 -7
package/lib/api.d.ts
CHANGED
|
@@ -19,18 +19,17 @@ export type PendingAuthRecord = {
|
|
|
19
19
|
codeVerifier: string;
|
|
20
20
|
provider: string;
|
|
21
21
|
ownerDID: string;
|
|
22
|
+
/** The owner's DID in a form that can be encrypted to. See the table type. */
|
|
23
|
+
ownerWrappableDID?: string | null;
|
|
24
|
+
/** A recovery recipient's DID in a form that can be encrypted to. See the table type. */
|
|
25
|
+
controllerWrappableDID?: string | null;
|
|
22
26
|
createdAt: number;
|
|
23
27
|
};
|
|
24
28
|
export type ConnectorStoreAPI = {
|
|
25
|
-
getCredential(providerName: string, ownerDID: string): Promise<string | null>;
|
|
26
|
-
setCredential(providerName: string, ownerDID: string, credential: string): Promise<void>;
|
|
27
|
-
deleteCredential(providerName: string, ownerDID: string): Promise<void>;
|
|
28
29
|
getSyncState(connectorName: string, ownerDID: string): Promise<SyncState | null>;
|
|
29
30
|
setSyncState(connectorName: string, ownerDID: string, state: SyncState): Promise<void>;
|
|
30
31
|
deleteSyncState(connectorName: string, ownerDID: string): Promise<void>;
|
|
31
32
|
listSyncStatesForConnector(connectorName: string): Promise<Array<SyncStateEntry>>;
|
|
32
|
-
claimSync(connectorName: string, ownerDID: string, leaseMs: number): Promise<boolean>;
|
|
33
|
-
renewSync(connectorName: string, ownerDID: string, leaseMs: number): Promise<void>;
|
|
34
33
|
createPendingAuth(record: PendingAuthRecord): Promise<void>;
|
|
35
34
|
consumePendingAuth(state: string): Promise<PendingAuthRecord | null>;
|
|
36
35
|
deleteExpiredPendingAuth(cutoffMs: number): Promise<void>;
|
package/lib/api.js
CHANGED
|
@@ -20,27 +20,6 @@ function rowToSyncState(row) {
|
|
|
20
20
|
// --- Factory function ---
|
|
21
21
|
export function createConnectorStore(db, _adapter) {
|
|
22
22
|
return {
|
|
23
|
-
// --- Credential operations ---
|
|
24
|
-
async getCredential (providerName, ownerDID) {
|
|
25
|
-
const row = await db.selectFrom('kubun_connector_credentials').select('credential').where('provider_name', '=', providerName).where('owner_did', '=', ownerDID).executeTakeFirst();
|
|
26
|
-
if (row == null) return null;
|
|
27
|
-
return row.credential;
|
|
28
|
-
},
|
|
29
|
-
async setCredential (providerName, ownerDID, credential) {
|
|
30
|
-
await db.insertInto('kubun_connector_credentials').values({
|
|
31
|
-
provider_name: providerName,
|
|
32
|
-
owner_did: ownerDID,
|
|
33
|
-
credential
|
|
34
|
-
}).onConflict((oc)=>oc.columns([
|
|
35
|
-
'provider_name',
|
|
36
|
-
'owner_did'
|
|
37
|
-
]).doUpdateSet({
|
|
38
|
-
credential
|
|
39
|
-
})).execute();
|
|
40
|
-
},
|
|
41
|
-
async deleteCredential (providerName, ownerDID) {
|
|
42
|
-
await db.deleteFrom('kubun_connector_credentials').where('provider_name', '=', providerName).where('owner_did', '=', ownerDID).execute();
|
|
43
|
-
},
|
|
44
23
|
// --- Sync state operations ---
|
|
45
24
|
async getSyncState (connectorName, ownerDID) {
|
|
46
25
|
const row = await db.selectFrom('kubun_connector_sync_state').selectAll().where('connector_name', '=', connectorName).where('owner_did', '=', ownerDID).executeTakeFirst();
|
|
@@ -48,10 +27,9 @@ export function createConnectorStore(db, _adapter) {
|
|
|
48
27
|
return rowToSyncState(row);
|
|
49
28
|
},
|
|
50
29
|
async setSyncState (connectorName, ownerDID, state) {
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
// sync worker survives progress updates.
|
|
30
|
+
// The `lease_expires_at` column is dead (the lease moved to the workflow
|
|
31
|
+
// engine); a fresh INSERT sets it null and a conflicting update leaves it
|
|
32
|
+
// untouched.
|
|
55
33
|
await db.insertInto('kubun_connector_sync_state').values({
|
|
56
34
|
connector_name: connectorName,
|
|
57
35
|
owner_did: ownerDID,
|
|
@@ -72,45 +50,6 @@ export function createConnectorStore(db, _adapter) {
|
|
|
72
50
|
error: state.error ?? null
|
|
73
51
|
})).execute();
|
|
74
52
|
},
|
|
75
|
-
async claimSync (connectorName, ownerDID, leaseMs) {
|
|
76
|
-
const now = Date.now();
|
|
77
|
-
const leaseExpiresAt = now + leaseMs;
|
|
78
|
-
// Single atomic upsert. INSERT wins when no row exists. On conflict the
|
|
79
|
-
// DO UPDATE only fires when its WHERE holds: the existing lease is not
|
|
80
|
-
// 'syncing', is unset, or has expired. The guard columns are
|
|
81
|
-
// table-qualified because a bare 'status'/'lease_expires_at' is ambiguous
|
|
82
|
-
// on Postgres (both the target row and the excluded row are in scope).
|
|
83
|
-
// A live lease fails the WHERE, so no row is returned.
|
|
84
|
-
const result = await db.insertInto('kubun_connector_sync_state').values({
|
|
85
|
-
connector_name: connectorName,
|
|
86
|
-
owner_did: ownerDID,
|
|
87
|
-
checkpoint: null,
|
|
88
|
-
last_synced_at: new Date(now).toISOString(),
|
|
89
|
-
entity_count: 0,
|
|
90
|
-
status: 'syncing',
|
|
91
|
-
error: null,
|
|
92
|
-
lease_expires_at: leaseExpiresAt
|
|
93
|
-
}).onConflict((oc)=>oc.columns([
|
|
94
|
-
'connector_name',
|
|
95
|
-
'owner_did'
|
|
96
|
-
]).doUpdateSet({
|
|
97
|
-
status: 'syncing',
|
|
98
|
-
lease_expires_at: leaseExpiresAt
|
|
99
|
-
}).where((eb)=>eb.or([
|
|
100
|
-
eb('kubun_connector_sync_state.status', '!=', 'syncing'),
|
|
101
|
-
eb('kubun_connector_sync_state.lease_expires_at', 'is', null),
|
|
102
|
-
eb('kubun_connector_sync_state.lease_expires_at', '<', now)
|
|
103
|
-
]))).returningAll().executeTakeFirst();
|
|
104
|
-
return result != null;
|
|
105
|
-
},
|
|
106
|
-
async renewSync (connectorName, ownerDID, leaseMs) {
|
|
107
|
-
const now = Date.now();
|
|
108
|
-
// Extend the lease only while the row is still actively syncing. A plain
|
|
109
|
-
// UPDATE (no excluded row in scope) needs no table-qualified columns.
|
|
110
|
-
await db.updateTable('kubun_connector_sync_state').set({
|
|
111
|
-
lease_expires_at: now + leaseMs
|
|
112
|
-
}).where('connector_name', '=', connectorName).where('owner_did', '=', ownerDID).where('status', '=', 'syncing').execute();
|
|
113
|
-
},
|
|
114
53
|
async deleteSyncState (connectorName, ownerDID) {
|
|
115
54
|
await db.deleteFrom('kubun_connector_sync_state').where('connector_name', '=', connectorName).where('owner_did', '=', ownerDID).execute();
|
|
116
55
|
},
|
|
@@ -129,6 +68,8 @@ export function createConnectorStore(db, _adapter) {
|
|
|
129
68
|
code_verifier: record.codeVerifier,
|
|
130
69
|
provider: record.provider,
|
|
131
70
|
owner_did: record.ownerDID,
|
|
71
|
+
owner_wrappable_did: record.ownerWrappableDID ?? null,
|
|
72
|
+
controller_wrappable_did: record.controllerWrappableDID ?? null,
|
|
132
73
|
created_at: record.createdAt
|
|
133
74
|
}).execute();
|
|
134
75
|
},
|
|
@@ -142,6 +83,8 @@ export function createConnectorStore(db, _adapter) {
|
|
|
142
83
|
codeVerifier: row.code_verifier,
|
|
143
84
|
provider: row.provider,
|
|
144
85
|
ownerDID: row.owner_did,
|
|
86
|
+
ownerWrappableDID: row.owner_wrappable_did,
|
|
87
|
+
controllerWrappableDID: row.controller_wrappable_did,
|
|
145
88
|
createdAt: row.created_at
|
|
146
89
|
};
|
|
147
90
|
},
|
package/lib/index.d.ts
CHANGED
|
@@ -5,4 +5,4 @@ export { createConnectorStore } from './api.js';
|
|
|
5
5
|
export { connectorStoreDefinition } from './definition.js';
|
|
6
6
|
export declare const CONNECTOR_STORE: "connector";
|
|
7
7
|
export declare function getConnectorStore(provider: StoreProvider): Promise<ConnectorStoreAPI>;
|
|
8
|
-
export type {
|
|
8
|
+
export type { ConnectorSyncState, ConnectorSyncStateTable, ConnectorTables, } from './tables.js';
|
package/lib/migrations.js
CHANGED
|
@@ -11,20 +11,18 @@ export function getConnectorMigrations(ctx) {
|
|
|
11
11
|
'connector_name',
|
|
12
12
|
'owner_did'
|
|
13
13
|
]).execute();
|
|
14
|
-
// Connector credentials
|
|
15
|
-
await db.schema.createTable('kubun_connector_credentials').ifNotExists().addColumn('provider_name', t.text, (col)=>col.notNull()).addColumn('owner_did', t.text, (col)=>col.notNull()).addColumn('credential', t.text, (col)=>col.notNull()).addPrimaryKeyConstraint('kubun_connector_credentials_pkey', [
|
|
16
|
-
'provider_name',
|
|
17
|
-
'owner_did'
|
|
18
|
-
]).execute();
|
|
19
14
|
// Pending OAuth authorizations: state + PKCE verifier persisted between the
|
|
20
15
|
// authorize redirect and the callback. State is single-use, consumed on read.
|
|
21
|
-
await db.schema.createTable('kubun_connector_pending_auth').ifNotExists().addColumn('state', t.text, (col)=>col.notNull().primaryKey()).addColumn('code_verifier', t.text, (col)=>col.notNull()).addColumn('provider', t.text, (col)=>col.notNull()).addColumn('owner_did', t.text, (col)=>col.notNull())//
|
|
16
|
+
await db.schema.createTable('kubun_connector_pending_auth').ifNotExists().addColumn('state', t.text, (col)=>col.notNull().primaryKey()).addColumn('code_verifier', t.text, (col)=>col.notNull()).addColumn('provider', t.text, (col)=>col.notNull()).addColumn('owner_did', t.text, (col)=>col.notNull())// The wrappable spelling of owner_did, kept beside it rather than
|
|
17
|
+
// replacing it: owner_did is the join key everywhere and is normalized.
|
|
18
|
+
.addColumn('owner_wrappable_did', t.text)// The wrappable DID of the user's controller, carried from authorize to
|
|
19
|
+
// callback so the mint can add the controller recovery wrap.
|
|
20
|
+
.addColumn('controller_wrappable_did', t.text)// ms-epoch (Date.now) overflows int32 on Postgres; bigint holds it and
|
|
22
21
|
// the adapter's int8 parser reads it back as a JS number.
|
|
23
22
|
.addColumn('created_at', t.bigint, (col)=>col.notNull()).execute();
|
|
24
23
|
},
|
|
25
24
|
async down (db) {
|
|
26
25
|
await db.schema.dropTable('kubun_connector_pending_auth').ifExists().execute();
|
|
27
|
-
await db.schema.dropTable('kubun_connector_credentials').ifExists().execute();
|
|
28
26
|
await db.schema.dropTable('kubun_connector_sync_state').ifExists().execute();
|
|
29
27
|
}
|
|
30
28
|
};
|
package/lib/tables.d.ts
CHANGED
|
@@ -10,22 +10,27 @@ export type ConnectorSyncStateTable = {
|
|
|
10
10
|
lease_expires_at: number | null;
|
|
11
11
|
};
|
|
12
12
|
export type ConnectorSyncState = Selectable<ConnectorSyncStateTable>;
|
|
13
|
-
export type ConnectorCredentialTable = {
|
|
14
|
-
provider_name: string;
|
|
15
|
-
owner_did: string;
|
|
16
|
-
credential: string;
|
|
17
|
-
};
|
|
18
|
-
export type ConnectorCredential = Selectable<ConnectorCredentialTable>;
|
|
19
13
|
export type ConnectorPendingAuthTable = {
|
|
20
14
|
state: string;
|
|
21
15
|
code_verifier: string;
|
|
22
16
|
provider: string;
|
|
23
17
|
owner_did: string;
|
|
18
|
+
/**
|
|
19
|
+
* The owner's DID in a form that can be encrypted to, carried from the
|
|
20
|
+
* authorize call to the callback that mints the key. Null when the caller did
|
|
21
|
+
* not supply one, which leaves the credential creatable only if `owner_did`
|
|
22
|
+
* happens to be wrappable on its own.
|
|
23
|
+
*/
|
|
24
|
+
owner_wrappable_did: string | null;
|
|
25
|
+
/**
|
|
26
|
+
* A recovery recipient's DID in a form that can be encrypted to, carried the
|
|
27
|
+
* same way as `owner_wrappable_did`. Null when the caller supplied none.
|
|
28
|
+
*/
|
|
29
|
+
controller_wrappable_did: string | null;
|
|
24
30
|
created_at: number;
|
|
25
31
|
};
|
|
26
32
|
export type ConnectorPendingAuth = Selectable<ConnectorPendingAuthTable>;
|
|
27
33
|
export type ConnectorTables = {
|
|
28
34
|
kubun_connector_sync_state: ConnectorSyncStateTable;
|
|
29
|
-
kubun_connector_credentials: ConnectorCredentialTable;
|
|
30
35
|
kubun_connector_pending_auth: ConnectorPendingAuthTable;
|
|
31
36
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubun/store-connector",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"keywords": [],
|
|
5
5
|
"license": "see LICENSE.md",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -15,14 +15,14 @@
|
|
|
15
15
|
"LICENSE.md"
|
|
16
16
|
],
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"
|
|
19
|
-
"@kubun/db": "^0.
|
|
20
|
-
"
|
|
18
|
+
"@kubun/db": "^0.14.0",
|
|
19
|
+
"@kubun/db-adapter": "^0.14.0",
|
|
20
|
+
"kysely": "^0.29.5"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"@
|
|
24
|
-
"@kubun/db-
|
|
25
|
-
"@
|
|
23
|
+
"@kubun/db-better-sqlite": "^0.14.0",
|
|
24
|
+
"@kubun/db-postgres": "^0.14.0",
|
|
25
|
+
"@testcontainers/postgresql": "^12.1.0"
|
|
26
26
|
},
|
|
27
27
|
"publishConfig": {
|
|
28
28
|
"access": "public"
|