@kubun/plugin-connector 0.12.1 → 0.13.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/action.d.ts +7 -1
- package/lib/action.js +7 -4
- package/lib/api.d.ts +57 -6
- package/lib/api.js +110 -24
- package/lib/credential.d.ts +7 -1
- package/lib/credential.js +25 -3
- package/lib/index.d.ts +1 -3
- package/lib/index.js +138 -23
- package/lib/manager.d.ts +0 -2
- package/lib/manager.js +1 -2
- package/lib/oauth.js +31 -5
- package/lib/schema.d.ts +18 -0
- package/lib/schema.js +3 -1
- package/lib/sync/engine.d.ts +8 -3
- package/lib/sync/engine.js +9 -3
- package/lib/sync/orchestrate.d.ts +8 -1
- package/lib/sync/orchestrate.js +18 -13
- package/lib/sync/state.d.ts +0 -2
- package/lib/sync/state.js +0 -6
- package/lib/sync/workflow.d.ts +59 -0
- package/lib/sync/workflow.js +241 -0
- package/lib/wrappable.d.ts +26 -0
- package/lib/wrappable.js +36 -0
- package/package.json +27 -15
package/lib/action.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CredentialProvider, EntityRecord } from '@kubun/connector';
|
|
1
|
+
import type { CredentialProvider, EntityRecord, WriteAttachment } from '@kubun/connector';
|
|
2
2
|
import type { StoreProvider } from '@kubun/db';
|
|
3
3
|
import type { Logger } from '@kubun/logger';
|
|
4
4
|
import type { ConnectorRegistry } from './registry.js';
|
|
@@ -27,5 +27,11 @@ export type ExecuteActionDeps = {
|
|
|
27
27
|
* rather than opening a nested transaction.
|
|
28
28
|
*/
|
|
29
29
|
writeDocument: SignedDocumentWriter;
|
|
30
|
+
/**
|
|
31
|
+
* Writes attachment bytes to the blob store; absent when no blob plugin is
|
|
32
|
+
* installed. Passed through to the connector's action handler so an action can
|
|
33
|
+
* import binary content (e.g. a Gmail attachment) on demand.
|
|
34
|
+
*/
|
|
35
|
+
writeAttachment?: WriteAttachment;
|
|
30
36
|
};
|
|
31
37
|
export declare function executeAction(params: ExecuteActionParams, deps: ExecuteActionDeps): Promise<ExecuteActionResult>;
|
package/lib/action.js
CHANGED
|
@@ -2,7 +2,7 @@ import { DocumentID, DocumentModelID } from '@kubun/id';
|
|
|
2
2
|
import { getGraphStore } from '@kubun/store-graph';
|
|
3
3
|
import { EntityProcessor } from './sync/processor.js';
|
|
4
4
|
export async function executeAction(params, deps) {
|
|
5
|
-
const { stores, registry, credentialProvider, ownerDID, logger, writeDocument } = deps;
|
|
5
|
+
const { stores, registry, credentialProvider, ownerDID, logger, writeDocument, writeAttachment } = deps;
|
|
6
6
|
const { connector: connectorName, action: actionName, model: modelName, input, sourceID } = params;
|
|
7
7
|
const connector = registry.get(connectorName);
|
|
8
8
|
if (connector == null) {
|
|
@@ -49,21 +49,24 @@ export async function executeAction(params, deps) {
|
|
|
49
49
|
}
|
|
50
50
|
// For updates, merge contextual fields from existing document (e.g., calendarID)
|
|
51
51
|
let mergedInput = input;
|
|
52
|
+
let existingData;
|
|
52
53
|
if (actionName === 'update' && sourceID != null) {
|
|
53
54
|
const uniqueBytes = new TextEncoder().encode(`${connectorName}:${sourceID}`);
|
|
54
55
|
const docID = DocumentID.create(DocumentModelID.fromString(modelID), ownerDID, uniqueBytes);
|
|
55
56
|
const graphStore = await getGraphStore(stores);
|
|
56
57
|
const existingDoc = await graphStore.getDocument(docID);
|
|
57
58
|
if (existingDoc?.data != null) {
|
|
59
|
+
existingData = existingDoc.data;
|
|
58
60
|
mergedInput = {
|
|
59
|
-
...
|
|
61
|
+
...existingData,
|
|
60
62
|
...input
|
|
61
63
|
};
|
|
62
64
|
}
|
|
63
65
|
}
|
|
64
66
|
// Dispatch to provider's action handler
|
|
65
67
|
const handler = connector.actionHandler({
|
|
66
|
-
credential
|
|
68
|
+
credential,
|
|
69
|
+
writeAttachment
|
|
67
70
|
});
|
|
68
71
|
let entity;
|
|
69
72
|
if (actionName === 'create') {
|
|
@@ -72,7 +75,7 @@ export async function executeAction(params, deps) {
|
|
|
72
75
|
if (sourceID == null) {
|
|
73
76
|
throw new Error('sourceID is required for update actions');
|
|
74
77
|
}
|
|
75
|
-
entity = await handler.update(modelName, sourceID, mergedInput);
|
|
78
|
+
entity = await handler.update(modelName, sourceID, mergedInput, existingData);
|
|
76
79
|
} else {
|
|
77
80
|
throw new Error(`Unsupported action: ${actionName}`);
|
|
78
81
|
}
|
package/lib/api.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { type MethodRegistry } from '@kokuin/token';
|
|
2
|
+
import { type CredentialManager } from '@kubun/credential';
|
|
1
3
|
import type { StoreProvider } from '@kubun/db';
|
|
2
|
-
import type {
|
|
4
|
+
import type { Logger } from '@kubun/logger';
|
|
3
5
|
import { type PendingAuthRecord } from '@kubun/store-connector';
|
|
4
6
|
export type SyncStateData = {
|
|
5
7
|
connectorName: string;
|
|
@@ -18,21 +20,70 @@ export type SetSyncStateParams = {
|
|
|
18
20
|
status: string;
|
|
19
21
|
error?: string | null;
|
|
20
22
|
};
|
|
23
|
+
/**
|
|
24
|
+
* Every `ownerDID` here is normalized before it reaches the store.
|
|
25
|
+
*
|
|
26
|
+
* The viewer DID arrives as a mutation's `iss`, which for `did:peer:4` is the
|
|
27
|
+
* long form on first contact with an audience and the short form afterwards. The
|
|
28
|
+
* two `owner_did` columns are equality keys, so an un-normalized one files the
|
|
29
|
+
* same account under two rows: a credential invisible under one spelling and
|
|
30
|
+
* un-revokable under the other, and a sync lease held twice at once.
|
|
31
|
+
*
|
|
32
|
+
* `ownerWrappableDID` is NOT normalized — see {@link createConnectorAPI}.
|
|
33
|
+
*/
|
|
21
34
|
export type ConnectorAPI = {
|
|
22
35
|
getSyncState: (connectorName: string, ownerDID: string) => Promise<SyncStateData | null>;
|
|
23
36
|
setSyncState: (connectorName: string, ownerDID: string, state: SetSyncStateParams) => Promise<SyncStateData>;
|
|
24
37
|
deleteSyncState: (connectorName: string, ownerDID: string) => Promise<void>;
|
|
25
38
|
listSyncStates: (connectorName: string) => Promise<Array<SyncStateData>>;
|
|
26
|
-
claimSync: (connectorName: string, ownerDID: string, leaseMs: number) => Promise<boolean>;
|
|
27
|
-
renewSync: (connectorName: string, ownerDID: string, leaseMs: number) => Promise<void>;
|
|
28
39
|
getCredential: (providerName: string, ownerDID: string) => Promise<string | null>;
|
|
40
|
+
getCredentialProvenance: (providerName: string, ownerDID: string) => Promise<{
|
|
41
|
+
updatedAt: string | null;
|
|
42
|
+
writerDID: string | null;
|
|
43
|
+
} | null>;
|
|
44
|
+
/**
|
|
45
|
+
* Re-encrypt an existing credential. Refuses when none exists, because
|
|
46
|
+
* creating one is a different decision — see `createCredential`.
|
|
47
|
+
*/
|
|
29
48
|
setCredential: (providerName: string, ownerDID: string, credential: string) => Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Mint the key, its two wrappings and the entry, and record the pointer.
|
|
51
|
+
*
|
|
52
|
+
* Split from `setCredential` so the headless refresh path cannot mint a key.
|
|
53
|
+
* A minted key fixes who will ever be able to read it, and a refresh running
|
|
54
|
+
* with no owner in hand would mint one wrapped to the server alone — locking
|
|
55
|
+
* the account owner out of their own credential with nothing failing.
|
|
56
|
+
*/
|
|
57
|
+
createCredential: (params: {
|
|
58
|
+
providerName: string;
|
|
59
|
+
ownerDID: string;
|
|
60
|
+
ownerWrappableDID: string;
|
|
61
|
+
controllerWrappableDID?: string;
|
|
62
|
+
credential: string;
|
|
63
|
+
}) => Promise<void>;
|
|
30
64
|
deleteCredential: (providerName: string, ownerDID: string) => Promise<void>;
|
|
31
65
|
createPendingAuth: (record: PendingAuthRecord) => Promise<void>;
|
|
32
66
|
consumePendingAuth: (state: string) => Promise<PendingAuthRecord | null>;
|
|
33
67
|
deleteExpiredPendingAuth: (cutoffMs: number) => Promise<void>;
|
|
34
68
|
};
|
|
35
|
-
export
|
|
69
|
+
export type CreateConnectorAPIParams = {
|
|
36
70
|
stores: StoreProvider;
|
|
37
|
-
|
|
38
|
-
|
|
71
|
+
logger: Logger;
|
|
72
|
+
/**
|
|
73
|
+
* Takes the `StoreProvider` rather than a manager, because during a mutation
|
|
74
|
+
* that provider is the transaction and a manager built over the base one
|
|
75
|
+
* would read outside it.
|
|
76
|
+
*/
|
|
77
|
+
getCredentialManager: (stores: StoreProvider) => Promise<CredentialManager>;
|
|
78
|
+
/** The engine's own DID, in a form that can be encrypted to. */
|
|
79
|
+
serverWrappableDID: string;
|
|
80
|
+
/**
|
|
81
|
+
* Resolvers for wrapping recipients that carry no key material of their own
|
|
82
|
+
* (`did:kokuin:`), scoped to the request's transaction provider so a resolve
|
|
83
|
+
* reads the mutation's tx, never a second connection. A self-contained
|
|
84
|
+
* recipient resolves with an empty registry, so callers with no controller
|
|
85
|
+
* recipient in play still pass `() => []`.
|
|
86
|
+
*/
|
|
87
|
+
getControllerMethods: (stores: StoreProvider) => MethodRegistry;
|
|
88
|
+
};
|
|
89
|
+
export declare function createConnectorAPI(params: CreateConnectorAPIParams): ConnectorAPI;
|
package/lib/api.js
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
|
+
import { normalizeDID } from '@kokuin/token';
|
|
2
|
+
import { CredentialSupersededBranch } from '@kubun/credential';
|
|
1
3
|
import { getConnectorStore } from '@kubun/store-connector';
|
|
2
|
-
|
|
4
|
+
import { fromUTF, toUTF } from '@sozai/codec';
|
|
3
5
|
export function createConnectorAPI(params) {
|
|
4
6
|
const getStore = ()=>getConnectorStore(params.stores);
|
|
5
|
-
const
|
|
7
|
+
const getCredentials = ()=>params.getCredentialManager(params.stores);
|
|
6
8
|
return {
|
|
7
9
|
async getSyncState (connectorName, ownerDID) {
|
|
8
10
|
const store = await getStore();
|
|
9
|
-
const
|
|
11
|
+
const owner = normalizeDID(ownerDID);
|
|
12
|
+
const state = await store.getSyncState(connectorName, owner);
|
|
10
13
|
if (state == null) return null;
|
|
11
14
|
return {
|
|
12
15
|
connectorName,
|
|
13
|
-
ownerDID,
|
|
16
|
+
ownerDID: owner,
|
|
14
17
|
checkpoint: state.checkpoint != null ? JSON.stringify(state.checkpoint) : null,
|
|
15
18
|
lastSyncedAt: state.lastSyncedAt,
|
|
16
19
|
entityCount: state.entityCount,
|
|
@@ -21,23 +24,24 @@ export function createConnectorAPI(params) {
|
|
|
21
24
|
},
|
|
22
25
|
async setSyncState (connectorName, ownerDID, state) {
|
|
23
26
|
const store = await getStore();
|
|
27
|
+
const owner = normalizeDID(ownerDID);
|
|
24
28
|
const lastSyncedAt = state.lastSyncedAt ?? new Date().toISOString();
|
|
25
29
|
const entityCount = state.entityCount ?? 0;
|
|
26
30
|
const checkpoint = state.checkpoint ?? null;
|
|
27
|
-
await store.setSyncState(connectorName,
|
|
31
|
+
await store.setSyncState(connectorName, owner, {
|
|
28
32
|
checkpoint: checkpoint != null ? JSON.parse(checkpoint) : null,
|
|
29
33
|
lastSyncedAt,
|
|
30
34
|
entityCount,
|
|
31
35
|
status: state.status,
|
|
32
36
|
error: state.error ?? undefined
|
|
33
37
|
});
|
|
34
|
-
const saved = await store.getSyncState(connectorName,
|
|
38
|
+
const saved = await store.getSyncState(connectorName, owner);
|
|
35
39
|
if (saved == null) {
|
|
36
40
|
throw new Error('Failed to set sync state');
|
|
37
41
|
}
|
|
38
42
|
return {
|
|
39
43
|
connectorName,
|
|
40
|
-
ownerDID,
|
|
44
|
+
ownerDID: owner,
|
|
41
45
|
checkpoint: saved.checkpoint != null ? JSON.stringify(saved.checkpoint) : null,
|
|
42
46
|
lastSyncedAt: saved.lastSyncedAt,
|
|
43
47
|
entityCount: saved.entityCount,
|
|
@@ -48,7 +52,7 @@ export function createConnectorAPI(params) {
|
|
|
48
52
|
},
|
|
49
53
|
async deleteSyncState (connectorName, ownerDID) {
|
|
50
54
|
const store = await getStore();
|
|
51
|
-
await store.deleteSyncState(connectorName, ownerDID);
|
|
55
|
+
await store.deleteSyncState(connectorName, normalizeDID(ownerDID));
|
|
52
56
|
},
|
|
53
57
|
async listSyncStates (connectorName) {
|
|
54
58
|
const store = await getStore();
|
|
@@ -64,33 +68,115 @@ export function createConnectorAPI(params) {
|
|
|
64
68
|
leaseExpiresAt: entry.leaseExpiresAt ?? null
|
|
65
69
|
}));
|
|
66
70
|
},
|
|
67
|
-
async
|
|
71
|
+
async getCredential (providerName, ownerDID) {
|
|
68
72
|
const store = await getStore();
|
|
69
|
-
|
|
73
|
+
const entryID = await store.getCredentialEntryID(providerName, normalizeDID(ownerDID));
|
|
74
|
+
if (entryID == null) return null;
|
|
75
|
+
try {
|
|
76
|
+
return toUTF(await (await getCredentials()).readEntry(entryID));
|
|
77
|
+
} catch (error) {
|
|
78
|
+
if (error instanceof CredentialSupersededBranch) {
|
|
79
|
+
params.logger.warn('Credential entry {entryID} for provider {providerName} belongs to a superseded branch', {
|
|
80
|
+
providerName,
|
|
81
|
+
entryID
|
|
82
|
+
});
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
70
87
|
},
|
|
71
|
-
async
|
|
88
|
+
async getCredentialProvenance (providerName, ownerDID) {
|
|
89
|
+
// Reflects the current pointer, not the winning branch — no superseded-branch guard
|
|
90
|
+
// (it never decrypts). Callers must gate on getCredential() != null so a loser-branch
|
|
91
|
+
// pointer's provenance is never surfaced.
|
|
72
92
|
const store = await getStore();
|
|
73
|
-
await store.
|
|
93
|
+
const entryID = await store.getCredentialEntryID(providerName, normalizeDID(ownerDID));
|
|
94
|
+
if (entryID == null) return null;
|
|
95
|
+
return (await getCredentials()).getEntryProvenance(entryID);
|
|
74
96
|
},
|
|
75
|
-
async
|
|
97
|
+
async setCredential (providerName, ownerDID, credential) {
|
|
76
98
|
const store = await getStore();
|
|
77
|
-
const
|
|
78
|
-
if (
|
|
79
|
-
|
|
80
|
-
throw new Error('Cannot decrypt connector credential: no at-rest cipher available');
|
|
99
|
+
const entryID = await store.getCredentialEntryID(providerName, normalizeDID(ownerDID));
|
|
100
|
+
if (entryID == null) {
|
|
101
|
+
throw new Error(`No connector credential to update for provider "${providerName}"; create it through the authorization flow`);
|
|
81
102
|
}
|
|
82
|
-
|
|
103
|
+
// Opens the key through the server's own wrapping and rewrites the entry
|
|
104
|
+
// under it. No key is minted and no wrapping is touched, so a refresh
|
|
105
|
+
// cannot change who can read the credential.
|
|
106
|
+
await (await getCredentials()).updateEntry(entryID, fromUTF(credential));
|
|
83
107
|
},
|
|
84
|
-
async
|
|
85
|
-
|
|
86
|
-
throw new Error('Cannot persist connector credential: no at-rest cipher available');
|
|
87
|
-
}
|
|
108
|
+
async createCredential (input) {
|
|
109
|
+
const { providerName, ownerDID, ownerWrappableDID, credential } = input;
|
|
88
110
|
const store = await getStore();
|
|
89
|
-
|
|
111
|
+
const credentials = await getCredentials();
|
|
112
|
+
// Wrapping to a DID needs only that DID's published key, so an engine that
|
|
113
|
+
// cannot decrypt would mint this key perfectly and then be unable to read
|
|
114
|
+
// it — a failure that surfaces at the first headless sync, far from here.
|
|
115
|
+
// This is where `cipher == null` used to fail closed, and it still does.
|
|
116
|
+
if (!credentials.availableFactors().includes('did')) {
|
|
117
|
+
throw new Error(`Cannot create a connector credential for "${providerName}": this engine's identity cannot decrypt, so it could never read back what it stored`);
|
|
118
|
+
}
|
|
119
|
+
// Two wrappings from the first write, not one plus a later grant: the
|
|
120
|
+
// server has to read this to run sync, and the owner has to be able to
|
|
121
|
+
// rotate and revoke it. Owner is authority, a different question from who
|
|
122
|
+
// can decrypt: when a controller is supplied it is the owner, so a device
|
|
123
|
+
// revoke goes through the controller's capability chain rather than the
|
|
124
|
+
// device self-authorizing; absent one the device owns its own key.
|
|
125
|
+
//
|
|
126
|
+
// The two spellings below are deliberately different questions about the
|
|
127
|
+
// same identity, and NOT interchangeable: `owner_did` is an equality key
|
|
128
|
+
// and so is normalized, while a recipient is encrypted to and must keep
|
|
129
|
+
// its wrappable form. Normalizing a `did:peer:4` recipient hands the short
|
|
130
|
+
// form to `deriveSharedSecret`, which resolves no document and no
|
|
131
|
+
// agreement key — do not fold these together.
|
|
132
|
+
const wrappings = [
|
|
133
|
+
[
|
|
134
|
+
{
|
|
135
|
+
kind: 'did',
|
|
136
|
+
recipientDID: ownerWrappableDID
|
|
137
|
+
}
|
|
138
|
+
],
|
|
139
|
+
[
|
|
140
|
+
{
|
|
141
|
+
kind: 'did',
|
|
142
|
+
recipientDID: params.serverWrappableDID
|
|
143
|
+
}
|
|
144
|
+
]
|
|
145
|
+
];
|
|
146
|
+
// A recovery recipient (e.g. a controller / seed-holder), so the credential
|
|
147
|
+
// survives device loss. Opt-in: absent when the caller supplied none. Kept
|
|
148
|
+
// in its wrappable form for the same reason as the two above — it is
|
|
149
|
+
// encrypted to, not compared against.
|
|
150
|
+
if (input.controllerWrappableDID != null) {
|
|
151
|
+
wrappings.push([
|
|
152
|
+
{
|
|
153
|
+
kind: 'did',
|
|
154
|
+
recipientDID: input.controllerWrappableDID
|
|
155
|
+
}
|
|
156
|
+
]);
|
|
157
|
+
}
|
|
158
|
+
// The controller owns the key when supplied (its canonical DID equals the
|
|
159
|
+
// normalized wrappable form); the account pointer below stays keyed on the
|
|
160
|
+
// device — ownership and account scoping are separate concerns.
|
|
161
|
+
const keyOwnerDID = input.controllerWrappableDID != null ? normalizeDID(input.controllerWrappableDID) : normalizeDID(ownerDID);
|
|
162
|
+
const keyID = await credentials.createKey({
|
|
163
|
+
ownerDID: keyOwnerDID,
|
|
164
|
+
wrappings,
|
|
165
|
+
methods: params.getControllerMethods(params.stores)
|
|
166
|
+
});
|
|
167
|
+
const entryID = await credentials.putEntry(keyID, fromUTF(credential));
|
|
168
|
+
await store.setCredentialEntryID(providerName, normalizeDID(ownerDID), entryID);
|
|
90
169
|
},
|
|
91
170
|
async deleteCredential (providerName, ownerDID) {
|
|
92
171
|
const store = await getStore();
|
|
93
|
-
|
|
172
|
+
const owner = normalizeDID(ownerDID);
|
|
173
|
+
const entryID = await store.getCredentialEntryID(providerName, owner);
|
|
174
|
+
await store.deleteCredentialEntryID(providerName, owner);
|
|
175
|
+
if (entryID != null) {
|
|
176
|
+
// The pointer and the entry go together. Leaving the entry would keep
|
|
177
|
+
// the token readable by anyone who kept its id after a disconnect.
|
|
178
|
+
await (await getCredentials()).deleteEntry(entryID);
|
|
179
|
+
}
|
|
94
180
|
},
|
|
95
181
|
async createPendingAuth (record) {
|
|
96
182
|
const store = await getStore();
|
package/lib/credential.d.ts
CHANGED
|
@@ -11,6 +11,12 @@ export declare class DBCredentialProvider implements CredentialProvider {
|
|
|
11
11
|
#private;
|
|
12
12
|
constructor(params: DBCredentialProviderParams);
|
|
13
13
|
get(providerName: string, ownerDID: string): Promise<Credential | null>;
|
|
14
|
-
set(
|
|
14
|
+
set(params: {
|
|
15
|
+
providerName: string;
|
|
16
|
+
ownerDID: string;
|
|
17
|
+
credential: Credential;
|
|
18
|
+
ownerWrappableDID?: string;
|
|
19
|
+
controllerWrappableDID?: string;
|
|
20
|
+
}): Promise<void>;
|
|
15
21
|
delete(providerName: string, ownerDID: string): Promise<void>;
|
|
16
22
|
}
|
package/lib/credential.js
CHANGED
|
@@ -60,8 +60,26 @@ export class DBCredentialProvider {
|
|
|
60
60
|
}
|
|
61
61
|
return credential;
|
|
62
62
|
}
|
|
63
|
-
async set(
|
|
64
|
-
|
|
63
|
+
async set(params) {
|
|
64
|
+
const { providerName, ownerDID, credential, ownerWrappableDID, controllerWrappableDID } = params;
|
|
65
|
+
const serialized = serializeCredential(credential);
|
|
66
|
+
// Update when one exists, mint only when it does not. Dispatching on
|
|
67
|
+
// presence rather than on the caller keeps the refresh path unable to mint
|
|
68
|
+
// even when it does hold a wrappable DID.
|
|
69
|
+
if (await this.#api.getCredential(providerName, ownerDID) != null) {
|
|
70
|
+
await this.#api.setCredential(providerName, ownerDID, serialized);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (ownerWrappableDID == null) {
|
|
74
|
+
throw new Error(`Cannot create a connector credential for "${providerName}" without the owner's wrappable DID`);
|
|
75
|
+
}
|
|
76
|
+
await this.#api.createCredential({
|
|
77
|
+
providerName,
|
|
78
|
+
ownerDID,
|
|
79
|
+
ownerWrappableDID,
|
|
80
|
+
controllerWrappableDID,
|
|
81
|
+
credential: serialized
|
|
82
|
+
});
|
|
65
83
|
}
|
|
66
84
|
async delete(providerName, ownerDID) {
|
|
67
85
|
await this.#api.deleteCredential(providerName, ownerDID);
|
|
@@ -98,7 +116,11 @@ export class DBCredentialProvider {
|
|
|
98
116
|
accountLabel: credential.accountLabel,
|
|
99
117
|
metadata: credential.metadata
|
|
100
118
|
};
|
|
101
|
-
await this.set(
|
|
119
|
+
await this.set({
|
|
120
|
+
providerName,
|
|
121
|
+
ownerDID,
|
|
122
|
+
credential: refreshed
|
|
123
|
+
});
|
|
102
124
|
return refreshed;
|
|
103
125
|
} catch {
|
|
104
126
|
return null;
|
package/lib/index.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export { type RunSyncParams, SyncEngine, type SyncEngineParams } from './sync/en
|
|
|
12
12
|
export { type OrchestrateSyncParams, type OrchestrateSyncResult, orchestrateSync, type SyncEventEmitter, } from './sync/orchestrate.js';
|
|
13
13
|
export { EntityProcessor, type EntityProcessorParams, type MutateDocuments, type ProcessBatchResult, type SignedDocumentWriter, } from './sync/processor.js';
|
|
14
14
|
export { DBSyncStateStore } from './sync/state.js';
|
|
15
|
+
export { CONNECTOR_SYNC_CONCURRENCY, CONNECTOR_SYNC_WORKFLOW, type ConnectorSyncWorkflowDefinition, type ConnectorSyncWorkflowParams, createConnectorSyncWorkflow, } from './sync/workflow.js';
|
|
15
16
|
export type { ConnectorAPI, SetSyncStateParams, SyncStateData };
|
|
16
17
|
export { DBCredentialProvider, type DBCredentialProviderParams };
|
|
17
18
|
export type ConnectorPluginOptions = {
|
|
@@ -19,9 +20,6 @@ export type ConnectorPluginOptions = {
|
|
|
19
20
|
providers?: Array<OAuthProviderDefinition>;
|
|
20
21
|
defaults?: {
|
|
21
22
|
boundary?: SyncBoundary;
|
|
22
|
-
pollingInterval?: string;
|
|
23
23
|
};
|
|
24
|
-
/** Override the sync-lease TTL in milliseconds (defaults to 5 minutes). */
|
|
25
|
-
syncLeaseTTL?: number;
|
|
26
24
|
};
|
|
27
25
|
export declare function createConnectorPlugin(options: ConnectorPluginOptions): (params: PluginFactoryParams) => KubunPlugin;
|