@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 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
- // Progress write only: the lease column is owned by claimSync/renewSync,
52
- // never by a plain set. A fresh INSERT carries no lease (null); on
53
- // conflict the lease is deliberately excluded so a live lease held by the
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 { ConnectorCredential, ConnectorCredentialTable, ConnectorSyncState, ConnectorSyncStateTable, ConnectorTables, } from './tables.js';
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())// ms-epoch (Date.now) overflows int32 on Postgres; bigint holds it and
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.12.1",
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
- "kysely": "^0.29.5",
19
- "@kubun/db": "^0.12.1",
20
- "@kubun/db-adapter": "^0.13.0"
18
+ "@kubun/db": "^0.14.0",
19
+ "@kubun/db-adapter": "^0.14.0",
20
+ "kysely": "^0.29.5"
21
21
  },
22
22
  "devDependencies": {
23
- "@testcontainers/postgresql": "^12.1.0",
24
- "@kubun/db-better-sqlite": "^0.12.1",
25
- "@kubun/db-postgres": "^0.12.1"
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"