@kubun/plugin-connector 0.14.0 → 0.14.2

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.
@@ -14,7 +14,7 @@ import { MAX_LOGGED_ERRORS } from './processor.js';
14
14
  * definitions (two "boots" over one DB) do not share iterators — which is what
15
15
  * makes crash-recovery re-open the provider from the checkpoint.
16
16
  */ export function createConnectorSyncWorkflow(params) {
17
- const { registry, credentialProvider, stateStore, syncEventEmitter, stores, mutateDocuments, logger } = params;
17
+ const { registry, credentialProvider, stateStore, syncEventEmitter, stores, mutateDocuments, logger, emitAuthRequired } = params;
18
18
  const sessions = new Map();
19
19
  const sessionKey = (state)=>`${state.connectorName}:${state.ownerDID}`;
20
20
  const effectiveBoundary = params.boundary ?? {
@@ -64,6 +64,15 @@ import { MAX_LOGGED_ERRORS } from './processor.js';
64
64
  lastSyncedAt: new Date().toISOString(),
65
65
  failedSample: []
66
66
  };
67
+ // Only a genuinely absent credential asks for (re)authorization; an absent
68
+ // server provider stays a silent configuration outcome. Both still end
69
+ // offline (the event is additive, not a change to the offline outcome).
70
+ if (providerName != null && credential == null) {
71
+ emitAuthRequired?.({
72
+ provider: providerName,
73
+ ownerDID
74
+ });
75
+ }
67
76
  return {
68
77
  status: 'end',
69
78
  state: offlineState,
@@ -126,6 +135,20 @@ import { MAX_LOGGED_ERRORS } from './processor.js';
126
135
  }
127
136
  const providerName = connector.auth.provider !== 'device' ? connector.auth.provider : null;
128
137
  const credential = providerName ? await credentialProvider.get(providerName, state.ownerDID) : null;
138
+ // A crash-recovered batch whose credential has since been removed cannot
139
+ // resume: signal (re)authorization and end offline rather than opening the
140
+ // provider with an empty credential (which would fail mid-sync ⇒ error).
141
+ if (providerName != null && credential == null) {
142
+ emitAuthRequired?.({
143
+ provider: providerName,
144
+ ownerDID: state.ownerDID
145
+ });
146
+ return {
147
+ status: 'end',
148
+ state,
149
+ outcome: 'offline'
150
+ };
151
+ }
129
152
  if (connector.serverProvider == null) {
130
153
  throw new Error(`Connector "${state.connectorName}" has no server provider`);
131
154
  }
@@ -258,7 +281,8 @@ import { MAX_LOGGED_ERRORS } from './processor.js';
258
281
  start,
259
282
  batch,
260
283
  finalize
261
- }
284
+ },
285
+ managed: true
262
286
  }
263
287
  };
264
288
  }
@@ -1,8 +1,11 @@
1
- import type { CredentialProvider } from '@kubun/credential-types';
1
+ import type { Credential, CredentialProvider } from '@kubun/credential-types';
2
2
  import { HLC } from '@kubun/hlc';
3
3
  import { getDelegationStore } from '@kubun/store-delegation';
4
4
  import type { ConnectorRegistry } from './registry.js';
5
5
  import type { ConnectorWriteGrant } from './schema.js';
6
+ type CredentialFacade = CredentialProvider & {
7
+ peek?(providerName: string, ownerDID: string): Promise<Credential | null>;
8
+ };
6
9
  export type WriteGrantContextParams = {
7
10
  registry: ConnectorRegistry;
8
11
  /** The server identity a viewer delegates its write authority to (`aud`). */
@@ -10,8 +13,12 @@ export type WriteGrantContextParams = {
10
13
  /** The calling viewer (raw, unnormalized) — the grantor of every cap. */
11
14
  viewerDID: string;
12
15
  stores: Parameters<typeof getDelegationStore>[0];
13
- credentialProvider: CredentialProvider;
16
+ credentialProvider: CredentialFacade;
14
17
  hlc: HLC;
18
+ onCredentialRemoved?: (params: {
19
+ provider: string;
20
+ ownerDID: string;
21
+ }) => void;
15
22
  };
16
23
  export type WriteGrantContext = {
17
24
  grantWriteCapability(args: {
@@ -33,3 +40,4 @@ export type WriteGrantContext = {
33
40
  * stored `resource` string handled here is only the revoke/list selector.
34
41
  */
35
42
  export declare function createWriteGrantContext(params: WriteGrantContextParams): WriteGrantContext;
43
+ export {};
@@ -240,8 +240,17 @@ async function revokeCapsSubsetOf(delegationStore, grantor, audience, allowed) {
240
240
  }
241
241
  const delegationStore = await getDelegationStore(stores);
242
242
  const removed = await revokeCapsSubsetOf(delegationStore, viewerDID, audienceDID, providerURNs);
243
- const credentialExisted = await credentialProvider.get(provider, viewerDID) != null;
243
+ // Test existence with the non-refreshing peek (falling back to `get` only
244
+ // when a caller supplies no peek) so disconnecting an expiring credential
245
+ // does not trigger a transparent refresh right before the delete.
246
+ const credentialExisted = (credentialProvider.peek != null ? await credentialProvider.peek(provider, viewerDID) : await credentialProvider.get(provider, viewerDID)) != null;
244
247
  await credentialProvider.delete(provider, viewerDID);
248
+ if (credentialExisted) {
249
+ params.onCredentialRemoved?.({
250
+ provider,
251
+ ownerDID: viewerDID
252
+ });
253
+ }
245
254
  return removed > 0 || credentialExisted;
246
255
  },
247
256
  connectorWriteGrants: async ()=>{
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubun/plugin-connector",
3
- "version": "0.14.0",
3
+ "version": "0.14.2",
4
4
  "license": "see LICENSE.md",
5
5
  "sideEffects": false,
6
6
  "type": "module",
@@ -18,23 +18,23 @@
18
18
  "@kokuin/jwe": "^0.1.0",
19
19
  "@kokuin/token": "^0.5.0",
20
20
  "@kubun/connector": "^0.14.0",
21
- "@kubun/credential": "^0.14.0",
21
+ "@kubun/credential": "^0.14.2",
22
22
  "@kubun/credential-types": "^0.14.0",
23
23
  "@kubun/db": "^0.14.0",
24
- "@kubun/engine": "^0.14.0",
25
- "@kubun/graphql": "^0.14.0",
24
+ "@kubun/engine": "^0.14.1",
25
+ "@kubun/graphql": "^0.14.1",
26
26
  "@kubun/hlc": "^0.14.0",
27
27
  "@kubun/id": "^0.14.0",
28
28
  "@kubun/logger": "^0.14.0",
29
29
  "@kubun/plugin-blob-api": "^0.14.0",
30
- "@kubun/plugin-workflow-api": "^0.14.0",
31
- "@kubun/protocol": "^0.14.0",
30
+ "@kubun/plugin-workflow-api": "^0.14.1",
31
+ "@kubun/protocol": "^0.14.1",
32
32
  "@kubun/service-credential-api": "^0.14.0",
33
33
  "@kubun/store-connector": "^0.14.0",
34
34
  "@kubun/store-credential": "^0.14.0",
35
35
  "@kubun/store-delegation": "^0.14.0",
36
- "@kubun/store-graph": "^0.14.0",
37
- "@noble/hashes": "^2.3.0",
36
+ "@kubun/store-graph": "^0.14.1",
37
+ "@noble/hashes": "^2.4.0",
38
38
  "@sozai/async": "^0.2.1",
39
39
  "@sozai/codec": "^0.4.0",
40
40
  "@sozai/event": "^0.1.3",
@@ -56,11 +56,11 @@
56
56
  "@kubun/plugin-credential": "^0.14.0",
57
57
  "@kubun/plugin-service-client": "^0.14.0",
58
58
  "@kubun/plugin-service-server": "^0.14.0",
59
- "@kubun/plugin-workflow": "^0.14.0",
59
+ "@kubun/plugin-workflow": "^0.14.1",
60
60
  "@kubun/service-controller-log-api": "^0.14.0",
61
61
  "@kubun/store-blob": "^0.14.0",
62
62
  "@kubun/store-controller": "^0.14.0",
63
- "@kubun/store-workflow": "^0.14.0",
63
+ "@kubun/store-workflow": "^0.14.1",
64
64
  "@kubun/test-utils": "^0.13.0",
65
65
  "@testcontainers/postgresql": "^12.1.0",
66
66
  "get-port": "^7.2.0"