@kubun/connector 0.17.0 → 0.17.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.
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The capability action a caller presents to execute a connector action
3
+ * (create/update) on a `did:kokuin:` owner's behalf. Gated BEFORE any provider
4
+ * credential I/O by the plugin-connector execution path — see
5
+ * `authorizeAction` in `@kubun/engine`.
6
+ */
7
+ export declare const CONNECTOR_EXECUTE_ACTION = "connector/execute";
8
+ /**
9
+ * Group-anchored resource specificity tiers for a `connector/execute`
10
+ * capability, most-specific first: the exact model, any model of the
11
+ * connector, then any connector in the group. Every tier is anchored to the
12
+ * group — there is no group-agnostic or global (`*`) tier, so a grant can
13
+ * never authorize a connector spend outside the group it was issued in.
14
+ */
15
+ export declare function connectorExecuteResourceTiers(groupID: string, connector: string, model: string): Array<string>;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * The capability action a caller presents to execute a connector action
3
+ * (create/update) on a `did:kokuin:` owner's behalf. Gated BEFORE any provider
4
+ * credential I/O by the plugin-connector execution path — see
5
+ * `authorizeAction` in `@kubun/engine`.
6
+ */ export const CONNECTOR_EXECUTE_ACTION = 'connector/execute';
7
+ /**
8
+ * Group-anchored resource specificity tiers for a `connector/execute`
9
+ * capability, most-specific first: the exact model, any model of the
10
+ * connector, then any connector in the group. Every tier is anchored to the
11
+ * group — there is no group-agnostic or global (`*`) tier, so a grant can
12
+ * never authorize a connector spend outside the group it was issued in.
13
+ */ export function connectorExecuteResourceTiers(groupID, connector, model) {
14
+ return [
15
+ `urn:kubun:connector:${groupID}/${connector}/${model}`,
16
+ `urn:kubun:connector:${groupID}/${connector}/*`,
17
+ `urn:kubun:connector:${groupID}/*`
18
+ ];
19
+ }
@@ -0,0 +1,51 @@
1
+ /** Machine-readable outcome classification for a failed connector action. */
2
+ export type ConnectorErrorKind = 'rejected' | 'unknown';
3
+ export type ConnectorErrorOptions = ErrorOptions & {
4
+ /** A machine-readable failure code. */
5
+ code?: string;
6
+ /** Whether the failure is a definite non-commit or outcome-unknown. */
7
+ kind?: ConnectorErrorKind;
8
+ /** The provider's HTTP status, when the failure came from a response. */
9
+ httpStatus?: number;
10
+ /** Set when the failure came after a provider write went out. */
11
+ sent?: boolean;
12
+ };
13
+ /**
14
+ * A connector failure carrying the fields write-back recovery reads: an
15
+ * optional `code`, the outcome classification (`kind`), the provider HTTP
16
+ * status and whether the write already went out (`sent`). A field left unset
17
+ * reads `undefined`, which recovery treats as unclassified.
18
+ */
19
+ export declare class ConnectorError extends Error {
20
+ #private;
21
+ constructor(message: string, options?: ConnectorErrorOptions);
22
+ get code(): string | undefined;
23
+ get kind(): ConnectorErrorKind | undefined;
24
+ get httpStatus(): number | undefined;
25
+ get sent(): boolean | undefined;
26
+ }
27
+ /**
28
+ * Classify an HTTP failure by whether the write definitely did not commit.
29
+ * A 4xx (except 429 and 412) is a definite non-commit — the request was
30
+ * rejected and a retry will be rejected the same way. A 429 rate-limit, any
31
+ * 5xx, and any transport failure are outcome-unknown: the provider may have
32
+ * accepted the write before the failure surfaced, so a blind retry risks a
33
+ * duplicate. A 412 (If-Match precondition failed) is also outcome-unknown —
34
+ * it means the provider record changed under us, so recovery must reconcile
35
+ * rather than treat it as a terminal rejection.
36
+ */
37
+ export declare function classifyHTTPStatus(status: number): ConnectorErrorKind;
38
+ /**
39
+ * The error a connector throws for a failed provider HTTP call, tagged with the
40
+ * HTTP status and its outcome classification (`kind`) so the caller can tell a
41
+ * definite rejection from an indeterminate failure.
42
+ */
43
+ export declare function connectorHTTPError(message: string, status: number): ConnectorError;
44
+ /**
45
+ * Wraps a failure raised after a handler's provider write already went out
46
+ * (e.g. the read-back that follows a send). The write may have landed, so the
47
+ * failure is marked `sent` — never taken for a request that could not have
48
+ * reached the provider, and so never refunded — and classified `unknown`, never
49
+ * a definite rejection, whatever the later call's own status was.
50
+ */
51
+ export declare function markWriteSent(cause: unknown): ConnectorError;
package/lib/errors.js ADDED
@@ -0,0 +1,72 @@
1
+ /** Machine-readable outcome classification for a failed connector action. */ /**
2
+ * A connector failure carrying the fields write-back recovery reads: an
3
+ * optional `code`, the outcome classification (`kind`), the provider HTTP
4
+ * status and whether the write already went out (`sent`). A field left unset
5
+ * reads `undefined`, which recovery treats as unclassified.
6
+ */ export class ConnectorError extends Error {
7
+ #code;
8
+ #kind;
9
+ #httpStatus;
10
+ #sent;
11
+ constructor(message, options = {}){
12
+ const { code, kind, httpStatus, sent, ...errorOptions } = options;
13
+ super(message, errorOptions);
14
+ this.name = 'ConnectorError';
15
+ this.#code = code;
16
+ this.#kind = kind;
17
+ this.#httpStatus = httpStatus;
18
+ this.#sent = sent;
19
+ }
20
+ get code() {
21
+ return this.#code;
22
+ }
23
+ get kind() {
24
+ return this.#kind;
25
+ }
26
+ get httpStatus() {
27
+ return this.#httpStatus;
28
+ }
29
+ get sent() {
30
+ return this.#sent;
31
+ }
32
+ }
33
+ /**
34
+ * Classify an HTTP failure by whether the write definitely did not commit.
35
+ * A 4xx (except 429 and 412) is a definite non-commit — the request was
36
+ * rejected and a retry will be rejected the same way. A 429 rate-limit, any
37
+ * 5xx, and any transport failure are outcome-unknown: the provider may have
38
+ * accepted the write before the failure surfaced, so a blind retry risks a
39
+ * duplicate. A 412 (If-Match precondition failed) is also outcome-unknown —
40
+ * it means the provider record changed under us, so recovery must reconcile
41
+ * rather than treat it as a terminal rejection.
42
+ */ export function classifyHTTPStatus(status) {
43
+ return status >= 400 && status < 500 && status !== 429 && status !== 412 ? 'rejected' : 'unknown';
44
+ }
45
+ /**
46
+ * The error a connector throws for a failed provider HTTP call, tagged with the
47
+ * HTTP status and its outcome classification (`kind`) so the caller can tell a
48
+ * definite rejection from an indeterminate failure.
49
+ */ export function connectorHTTPError(message, status) {
50
+ return new ConnectorError(message, {
51
+ httpStatus: status,
52
+ kind: classifyHTTPStatus(status)
53
+ });
54
+ }
55
+ /**
56
+ * Wraps a failure raised after a handler's provider write already went out
57
+ * (e.g. the read-back that follows a send). The write may have landed, so the
58
+ * failure is marked `sent` — never taken for a request that could not have
59
+ * reached the provider, and so never refunded — and classified `unknown`, never
60
+ * a definite rejection, whatever the later call's own status was.
61
+ */ export function markWriteSent(cause) {
62
+ const message = cause instanceof Error ? cause.message : String(cause);
63
+ const status = cause?.httpStatus;
64
+ return new ConnectorError(message, {
65
+ cause,
66
+ sent: true,
67
+ kind: 'unknown',
68
+ ...typeof status === 'number' ? {
69
+ httpStatus: status
70
+ } : {}
71
+ });
72
+ }
package/lib/index.d.ts CHANGED
@@ -1,4 +1,6 @@
1
+ export { CONNECTOR_EXECUTE_ACTION, connectorExecuteResourceTiers } from './authority.js';
1
2
  export { clusterModelURNs, resolveModelID } from './cluster-utils.js';
3
+ export { ConnectorError, type ConnectorErrorKind, type ConnectorErrorOptions, classifyHTTPStatus, connectorHTTPError, markWriteSent, } from './errors.js';
2
4
  export { connectorClusters, connectorModelIDs } from './models/cluster.js';
3
5
  export { externalEntityDefinition } from './models/external-entity.js';
4
- export type { ActionDefinition, ActionHandler, ActionHandlerFactory, ActionResult, ClientDataProvider, ClientProviderFactory, ConnectorAuth, ConnectorDefinition, ConnectorSyncEventPayload, DataProvider, DeviceConnectorAuth, DevicePermission, EntityBatch, EntityRecord, FetchChangesParams, FetchParams, OAuthConnectorAuth, OAuthState, ServerDataProvider, ServerProviderFactory, SyncBoundary, SyncCompleteEvent, SyncErrorEvent, SyncEvent, SyncFailureSample, SyncProgressEvent, SyncState, SyncStateEntry, SyncStateStore, SyncStatus, UserSyncPreferences, WebhookParams, WebhookRegistration, WriteAttachment, } from './types.js';
6
+ export type { ActionDefinition, ActionHandler, ActionHandlerFactory, ActionResult, ActionTargetResolver, ActionTargetStorage, ActionWriteContext, ActionWriteResult, ClientDataProvider, ClientProviderFactory, ConnectorAuth, ConnectorDefinition, ConnectorSyncEventPayload, CreatedSourceIDPredictor, DataProvider, DeadLetter, DeviceConnectorAuth, DevicePermission, EntityBatch, EntityRecord, FetchChangesParams, FetchedAbsentEntity, FetchedEntity, FetchedPresentEntity, FetchParams, IntentCanonicalizer, IntentComparison, JSONValue, LocalAnnotationMerger, OAuthConnectorAuth, OAuthState, PendingOp, ReconcileResult, Recovery, ResolvedActionTarget, ServerDataProvider, ServerProviderFactory, SyncBoundary, SyncCompleteEvent, SyncErrorEvent, SyncEvent, SyncFailureSample, SyncProgressCASCurrent, SyncProgressCASResult, SyncProgressEvent, SyncProgressRecompute, SyncState, SyncStateEntry, SyncStateStore, SyncStatus, UserSyncPreferences, WebhookParams, WebhookRegistration, WriteAttachment, } from './types.js';
package/lib/index.js CHANGED
@@ -1,3 +1,5 @@
1
+ export { CONNECTOR_EXECUTE_ACTION, connectorExecuteResourceTiers } from './authority.js';
1
2
  export { clusterModelURNs, resolveModelID } from './cluster-utils.js';
3
+ export { ConnectorError, classifyHTTPStatus, connectorHTTPError, markWriteSent } from './errors.js';
2
4
  export { connectorClusters, connectorModelIDs } from './models/cluster.js';
3
5
  export { externalEntityDefinition } from './models/external-entity.js';
package/lib/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Credential } from '@kubun/credential-types';
1
+ import type { Credential, ProviderAccount } from '@kubun/credential-types';
2
2
  import type { ClustersRecord } from '@kubun/protocol';
3
3
  import type { JSONSchema } from 'json-schema-typed';
4
4
  export type OAuthState = {
@@ -9,6 +9,37 @@ export type OAuthState = {
9
9
  createdAt: number;
10
10
  };
11
11
  export type SyncStatus = 'idle' | 'syncing' | 'error';
12
+ export type JSONValue = string | number | boolean | null | {
13
+ [key: string]: JSONValue;
14
+ } | Array<JSONValue>;
15
+ export type PendingOp = {
16
+ docID: string;
17
+ kind: 'upsert' | 'delete';
18
+ payload: JSONValue;
19
+ attempts: number;
20
+ firstFailedAt: string;
21
+ lastError: {
22
+ code?: string;
23
+ message: string;
24
+ };
25
+ };
26
+ export type DeadLetter = {
27
+ docID: string | null;
28
+ sourceService: string;
29
+ sourceID: string;
30
+ kind: 'upsert' | 'delete';
31
+ attempts: number;
32
+ lastError: {
33
+ code?: string;
34
+ message: string;
35
+ };
36
+ deadLetteredAt: string;
37
+ reason: 'max-attempts' | 'payload-evicted' | 'unserializable' | 'unaddressable';
38
+ };
39
+ export type Recovery = {
40
+ pending: Array<PendingOp>;
41
+ deadLetters: Array<DeadLetter>;
42
+ };
12
43
  export type SyncState = {
13
44
  checkpoint: unknown;
14
45
  lastSyncedAt: string;
@@ -16,19 +47,33 @@ export type SyncState = {
16
47
  status: SyncStatus;
17
48
  error?: string;
18
49
  leaseExpiresAt?: number | null;
50
+ recovery?: Recovery | null;
51
+ version?: number;
52
+ incarnation?: string;
19
53
  };
20
54
  export type SyncStateEntry = SyncState & {
21
55
  connectorName: string;
22
56
  ownerDID: string;
23
57
  };
58
+ export type SyncProgressCASCurrent = {
59
+ checkpoint: unknown;
60
+ recovery: Recovery | null;
61
+ version: number;
62
+ incarnation: string;
63
+ };
64
+ export type SyncProgressCASResult = {
65
+ checkpoint: unknown;
66
+ recovery: Recovery | null;
67
+ lastSyncedAt: string;
68
+ entityCount: number;
69
+ error?: string | null;
70
+ };
71
+ export type SyncProgressRecompute = (current: SyncProgressCASCurrent | null) => SyncProgressCASResult | Promise<SyncProgressCASResult>;
24
72
  export type SyncStateStore = {
25
73
  get(connectorName: string, ownerDID: string): Promise<SyncState | null>;
26
74
  set(connectorName: string, ownerDID: string, state: SyncState): Promise<void>;
27
- updateSyncProgress(connectorName: string, ownerDID: string, progress: {
28
- checkpoint: unknown;
29
- lastSyncedAt: string;
30
- entityCount: number;
31
- error?: string;
75
+ updateSyncProgress(connectorName: string, ownerDID: string, recompute: SyncProgressRecompute, options?: {
76
+ maxRetries?: number;
32
77
  }): Promise<void>;
33
78
  delete(connectorName: string, ownerDID: string): Promise<void>;
34
79
  listForConnector(connectorName: string): Promise<Array<SyncStateEntry>>;
@@ -48,13 +93,25 @@ export type EntityRecord = Record<string, unknown> & {
48
93
  sourceService: string;
49
94
  sourceID: string;
50
95
  };
96
+ export type FetchedEntity = {
97
+ present: true;
98
+ record: EntityRecord;
99
+ sourceFetchHLC: string;
100
+ } | {
101
+ present: false;
102
+ sourceID: string;
103
+ sourceFetchHLC: string;
104
+ };
105
+ export type FetchedPresentEntity = Extract<FetchedEntity, {
106
+ present: true;
107
+ }>;
108
+ export type FetchedAbsentEntity = Extract<FetchedEntity, {
109
+ present: false;
110
+ }>;
51
111
  export type EntityBatch = {
52
112
  modelID: string;
53
- entities: Array<EntityRecord>;
54
- deleted?: Array<{
55
- sourceService: string;
56
- sourceID: string;
57
- }>;
113
+ entities: Array<FetchedPresentEntity>;
114
+ deleted?: Array<FetchedAbsentEntity>;
58
115
  checkpoint?: unknown;
59
116
  hasMore: boolean;
60
117
  };
@@ -66,9 +123,30 @@ export type FetchParams = {
66
123
  export type FetchChangesParams = FetchParams & {
67
124
  lastSyncedAt?: Date;
68
125
  };
126
+ export type ReconcileResult = (FetchedPresentEntity & {
127
+ providerETag?: string;
128
+ }) | (FetchedAbsentEntity & {
129
+ providerETag?: string;
130
+ });
131
+ /**
132
+ * A source of external entities, driven by an opaque `checkpoint`.
133
+ *
134
+ * **Replay-stability contract (required).** Re-opening `fetchAll`/`fetchChanges`
135
+ * from the same `checkpoint` MUST re-emit the same operations — or a superset —
136
+ * addressed by `sourceService`+`sourceID`; it must never emit a narrower set. The
137
+ * sync engine advances the connector checkpoint before the durable workflow
138
+ * checkpoint, so a crash between the two re-drives `fetchChanges`/`fetchAll` from
139
+ * an older checkpoint; a stable replay makes that re-drive idempotent (imports
140
+ * upsert by unique key) with no dropped entities. Concretely: a time window MUST
141
+ * be an absolute bound carried in the checkpoint, never recomputed from `now` on
142
+ * each call — a `now`-relative window silently narrows on replay. (Server-side
143
+ * ordering may vary between calls: harmless, since ops are keyed by identity.)
144
+ */
69
145
  export type DataProvider = {
70
146
  fetchAll(params: FetchParams): AsyncIterable<EntityBatch>;
71
147
  fetchChanges(params: FetchChangesParams): AsyncIterable<EntityBatch>;
148
+ fetchEntity(model: string, sourceID: string, snapshot: unknown): Promise<FetchedEntity>;
149
+ reconcile(model: string, sourceID: string, snapshot: unknown): Promise<ReconcileResult>;
72
150
  supportedStrategies: Array<'polling' | 'on-demand'>;
73
151
  };
74
152
  export type WebhookParams = {
@@ -107,6 +185,20 @@ export type ActionDefinition = {
107
185
  output?: JSONSchema.Object;
108
186
  entityModel?: string;
109
187
  requiresCredential: boolean;
188
+ recoveryClass: 'reissue-safe' | 'reconcile-only';
189
+ /**
190
+ * The field of the target document holding the provider's conditional-write
191
+ * token (e.g. an ETag) as last fetched. An update pins it on the operation
192
+ * at claim — read from the local document, never re-fetched before the
193
+ * write — and hands it to the handler as `ActionWriteContext.providerETag`.
194
+ */
195
+ providerETagField?: string;
196
+ /**
197
+ * The provider writes only against that token: an update whose target
198
+ * document holds none is refused before the operation is claimed (a sync
199
+ * first provides it), rather than claimed and then dead-lettered.
200
+ */
201
+ providerETagRequired?: boolean;
110
202
  };
111
203
  export type ActionResult = {
112
204
  raw?: Record<string, unknown>;
@@ -119,21 +211,123 @@ export type WriteAttachment = (input: ReadableStream<Uint8Array>, options?: {
119
211
  encrypted?: boolean;
120
212
  keyID?: string;
121
213
  }) => Promise<string>;
214
+ export type ActionWriteResult = FetchedPresentEntity & {
215
+ providerETag?: string;
216
+ localChange?: boolean;
217
+ };
218
+ /** What the write-back layer knows about the operation a write belongs to. */
219
+ export type ActionWriteContext = {
220
+ /**
221
+ * The caller-derived deterministic identity of the intent the write carries
222
+ * (the operation envelope's `deterministicEventID`), the same on every
223
+ * attempt of the operation. A connector whose provider accepts a
224
+ * caller-chosen resource id derives that id from it, so a create re-issued
225
+ * after a lost response collides with its own earlier effect instead of
226
+ * duplicating it.
227
+ */
228
+ deterministicEventID: string;
229
+ /**
230
+ * The provider's conditional-write token (e.g. an ETag) for the state the
231
+ * write is based on, as captured when that state was fetched and pinned on
232
+ * the operation at claim (see `ActionDefinition.providerETagField`). An
233
+ * update writes conditionally on it, so a provider change since then fails
234
+ * the write instead of being overwritten. Absent when unknown.
235
+ */
236
+ providerETag?: string | null;
237
+ };
122
238
  export type ActionHandler = {
123
- create(model: string, params: Record<string, unknown>): Promise<EntityRecord>;
124
- update(model: string, sourceID: string, params: Record<string, unknown>, existing?: Record<string, unknown>): Promise<EntityRecord>;
239
+ create(model: string, params: Record<string, unknown>, context?: ActionWriteContext): Promise<ActionWriteResult>;
240
+ update(model: string, sourceID: string, params: Record<string, unknown>, existing?: Record<string, unknown>, context?: ActionWriteContext): Promise<ActionWriteResult>;
125
241
  };
126
242
  export type ActionHandlerFactory = (config: {
127
243
  credential: Credential;
128
244
  writeAttachment?: WriteAttachment;
245
+ mintFetchHLC: () => string;
129
246
  }) => ActionHandler;
130
247
  export type ServerProviderFactory = (config: {
131
248
  credential: unknown;
132
249
  boundary: SyncBoundary;
250
+ mintFetchHLC: () => string;
133
251
  }) => ServerDataProvider;
134
252
  export type ClientProviderFactory = (config: {
135
253
  boundary: SyncBoundary;
136
254
  }) => ClientDataProvider;
255
+ /** A write's intent and a provider record, in the forms they are compared in. */
256
+ export type IntentComparison = {
257
+ intent: Record<string, unknown>;
258
+ record: Record<string, unknown>;
259
+ };
260
+ /**
261
+ * Brings a write's intent and a provider record into the forms the provider
262
+ * normalizes to, so a record that holds the intent compares equal field by
263
+ * field (e.g. a date-time the provider re-renders in another offset). Only an
264
+ * equivalence the provider guarantees may be folded in: anything looser would
265
+ * confirm an effect that never happened. `null` means the record cannot hold
266
+ * the intent at all.
267
+ */
268
+ export type IntentCanonicalizer = (params: {
269
+ model: string;
270
+ op: 'create' | 'update';
271
+ intent: Record<string, unknown>;
272
+ record: EntityRecord;
273
+ }) => IntentComparison | null;
274
+ /**
275
+ * Carries the device-local annotations of an existing document into an
276
+ * incoming provider record that does not author them — e.g. an imported
277
+ * attachment's `blob`, nested in attachment metadata the provider does author
278
+ * — so a later provider write of the entity keeps them. Only local
279
+ * annotations may be carried over, never a provider-authored value.
280
+ */
281
+ export type LocalAnnotationMerger = (params: {
282
+ model: string;
283
+ incoming: EntityRecord;
284
+ existing: Record<string, unknown>;
285
+ }) => EntityRecord;
286
+ /** Storage-only reads available to an {@link ActionTargetResolver}. */
287
+ export type ActionTargetStorage = {
288
+ /**
289
+ * The acting owner's live documents of one of this connector's models, as
290
+ * synced to this device (document data only). Never a provider call.
291
+ */
292
+ documents(model: string): Promise<Array<Record<string, unknown>>>;
293
+ };
294
+ /** The canonical target of a write and the input it is written with. */
295
+ export type ResolvedActionTarget = {
296
+ /** The canonical provider target the write lands in (pinned on the operation). */
297
+ target: string;
298
+ /** The action input with any alias replaced by what it resolved to. */
299
+ input: Record<string, unknown>;
300
+ };
301
+ /**
302
+ * Derives the canonical target of a write from its own action input, before
303
+ * the operation is claimed and with storage-only access: an alias (a
304
+ * provider's "primary", say) is resolved from locally synced data, never by a
305
+ * provider call. The resolved input is what the operation pins and what the
306
+ * handler writes with. Throws a coded error when the target cannot be
307
+ * resolved locally.
308
+ */
309
+ export type ActionTargetResolver = (params: {
310
+ model: string;
311
+ op: 'create' | 'update';
312
+ input: Record<string, unknown>;
313
+ sourceID?: string;
314
+ /** The provider account of the stored credential the write will spend. */
315
+ providerAccount: ProviderAccount;
316
+ storage: ActionTargetStorage;
317
+ }) => Promise<ResolvedActionTarget>;
318
+ /**
319
+ * Predicts, from a create's resolved input and its operation's deterministic
320
+ * event id alone (no storage, no provider call), the provider id the created
321
+ * entity will be stored under — for a provider whose create inserts under a
322
+ * caller-chosen id. `null` when the provider assigns the id itself, or when
323
+ * the input does not determine it. Every attempt of the operation must end
324
+ * up under the predicted id.
325
+ */
326
+ export type CreatedSourceIDPredictor = (params: {
327
+ model: string;
328
+ input: Record<string, unknown>;
329
+ deterministicEventID: string;
330
+ }) => string | null;
137
331
  export type ConnectorDefinition = {
138
332
  name: string;
139
333
  version: string;
@@ -141,6 +335,27 @@ export type ConnectorDefinition = {
141
335
  syncStrategies: Array<'polling' | 'webhook' | 'on-demand'>;
142
336
  actions?: Array<ActionDefinition>;
143
337
  actionHandler?: ActionHandlerFactory;
338
+ /**
339
+ * How write-back recovery compares an operation's intent against the
340
+ * provider resource that should hold it. Absent, values compare exactly.
341
+ */
342
+ canonicalizeIntent?: IntentCanonicalizer;
343
+ /**
344
+ * Applied to every provider record projected onto an existing document
345
+ * (sync, verification re-pull, an action's write result).
346
+ */
347
+ mergeLocalAnnotations?: LocalAnnotationMerger;
348
+ /**
349
+ * How a write's canonical target is derived from its input. Absent, the
350
+ * caller must name it as a generic `input.target`.
351
+ */
352
+ resolveActionTarget?: ActionTargetResolver;
353
+ /**
354
+ * The provider id a create will be stored under, when the connector chooses
355
+ * it (see {@link CreatedSourceIDPredictor}). Absent, a create's document is
356
+ * known only once the write commits.
357
+ */
358
+ predictCreatedSourceID?: CreatedSourceIDPredictor;
144
359
  serverProvider?: ServerProviderFactory;
145
360
  clientProvider?: ClientProviderFactory;
146
361
  graphql?: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubun/connector",
3
- "version": "0.17.0",
3
+ "version": "0.17.2",
4
4
  "keywords": [],
5
5
  "license": "see LICENSE.md",
6
6
  "sideEffects": false,
@@ -15,8 +15,8 @@
15
15
  "LICENSE.md"
16
16
  ],
17
17
  "dependencies": {
18
- "@kubun/credential-types": "^0.17.0",
19
- "@kubun/protocol": "^0.17.0",
18
+ "@kubun/credential-types": "^0.17.2",
19
+ "@kubun/protocol": "^0.17.1",
20
20
  "@kubun/scalars": "^0.17.0",
21
21
  "json-schema-typed": "^8.0.2"
22
22
  },