@mdbase-dev/connect 0.1.0-beta.63 → 0.1.0-beta.65
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/dist/browser/integrity.json +1 -1
- package/dist/browser/mdbase-connect.min.js +3 -3
- package/dist/browser/mdbase-connect.min.js.sha384 +1 -1
- package/dist/collection-client.d.ts +1 -0
- package/dist/collection-client.js +14 -51
- package/dist/connection.js +3 -2
- package/dist/index.d.ts +1 -1
- package/dist/operation-types.d.ts +22 -1
- package/dist/outcomes.d.ts +2 -2
- package/dist/outcomes.js +3 -0
- package/dist/query-pagination.d.ts +6 -0
- package/dist/query-pagination.js +98 -0
- package/dist/request-coordinator.d.ts +29 -0
- package/dist/request-coordinator.js +212 -0
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
sha384-
|
|
1
|
+
sha384-Sac9rFWvetnHV52bEp32Rc1ljfJn2zHX+iQ7rHGKCRRAsiaeaofsdMp2IskpKr4N
|
|
@@ -60,5 +60,6 @@ export declare class MdbaseCollectionClient<Frontmatter extends JsonObject = Jso
|
|
|
60
60
|
}, options?: ConnectRequestOptions): Promise<ConnectOutcome<MdbaseTimerReconciliation, CollectionMutationProblemCode>>;
|
|
61
61
|
watch(options?: WatchOptions): AsyncGenerator<ConnectOutcome<CollectionChange, CollectionChangesProblemCode>>;
|
|
62
62
|
private rawOperation;
|
|
63
|
+
private releaseQueryCursor;
|
|
63
64
|
private envelopeOperation;
|
|
64
65
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { abortableDelay } from "./async.js";
|
|
2
|
+
import { coordinatedQueryPages } from "./query-pagination.js";
|
|
2
3
|
import { MdbaseConnectError, connectError, connectProblem, operationProblem } from "./errors.js";
|
|
3
4
|
import { COLLECTION_CHANGES_PROBLEM_CODES, COLLECTION_DESCRIPTION_PROBLEM_CODES, COLLECTION_MUTATION_PROBLEM_CODES, COLLECTION_QUERY_PROBLEM_CODES, COLLECTION_READ_PROBLEM_CODES, COLLECTION_TYPE_PROBLEM_CODES, ALL_CONNECT_PROBLEM_CODES, captureConnectOutcome, connectFailure, connectSuccess } from "./outcomes.js";
|
|
4
5
|
import { createRequestBudget, DEFAULT_REQUEST_TIMEOUT_MS, requestAbortReason } from "./request-budget.js";
|
|
@@ -39,56 +40,14 @@ export class MdbaseCollectionClient {
|
|
|
39
40
|
meta: {
|
|
40
41
|
totalCount: meta.total_count,
|
|
41
42
|
hasMore: meta.has_more,
|
|
43
|
+
...(meta.cursor ? { cursor: meta.cursor } : {}),
|
|
42
44
|
...(meta.snapshot ? { snapshot: meta.snapshot } : {})
|
|
43
45
|
}
|
|
44
46
|
} : {})
|
|
45
47
|
}));
|
|
46
48
|
}
|
|
47
49
|
async *queryPages(input = {}, options = {}) {
|
|
48
|
-
|
|
49
|
-
let offset = nonNegativeInteger(requestedOffset, 0);
|
|
50
|
-
const firstPageSize = positiveInteger(options.firstPageSize ?? requestedLimit, 200);
|
|
51
|
-
const pageSize = positiveInteger(options.pageSize ?? requestedLimit, 1_000);
|
|
52
|
-
let snapshot = requestedSnapshot;
|
|
53
|
-
let loaded = 0;
|
|
54
|
-
let pageNumber = 0;
|
|
55
|
-
while (!options.signal?.aborted) {
|
|
56
|
-
const queried = await this.query({
|
|
57
|
-
...criteria,
|
|
58
|
-
offset,
|
|
59
|
-
limit: pageNumber === 0 ? firstPageSize : pageSize,
|
|
60
|
-
...(snapshot ? { snapshot } : {})
|
|
61
|
-
}, { signal: options.signal, timeoutMs: options.pageTimeoutMs });
|
|
62
|
-
if (!queried.ok) {
|
|
63
|
-
yield queried;
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
const result = queried.value;
|
|
67
|
-
const returnedSnapshot = result.meta?.snapshot;
|
|
68
|
-
if (snapshot && returnedSnapshot && snapshot !== returnedSnapshot) {
|
|
69
|
-
yield connectFailure(connectProblem("query_snapshot_changed", "The collection query snapshot changed while paging. Refresh the query before continuing."));
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
if (!snapshot && returnedSnapshot)
|
|
73
|
-
snapshot = returnedSnapshot;
|
|
74
|
-
loaded += result.results.length;
|
|
75
|
-
const complete = !result.meta?.hasMore || result.results.length === 0;
|
|
76
|
-
const page = {
|
|
77
|
-
results: result.results,
|
|
78
|
-
...(result.meta ? { meta: result.meta } : {}),
|
|
79
|
-
page: pageNumber,
|
|
80
|
-
offset,
|
|
81
|
-
loaded,
|
|
82
|
-
complete,
|
|
83
|
-
...(snapshot ? { snapshot } : {})
|
|
84
|
-
};
|
|
85
|
-
options.onProgress?.(page);
|
|
86
|
-
yield connectSuccess(page, queried.diagnostics);
|
|
87
|
-
if (complete)
|
|
88
|
-
return;
|
|
89
|
-
offset += result.results.length;
|
|
90
|
-
pageNumber += 1;
|
|
91
|
-
}
|
|
50
|
+
yield* coordinatedQueryPages((pageInput, pageOptions) => this.query(pageInput, pageOptions), (cursor) => this.releaseQueryCursor(cursor), input, options);
|
|
92
51
|
}
|
|
93
52
|
async queryAll(input = {}, options = {}) {
|
|
94
53
|
const budget = createRequestBudget(options, this.requestTimeoutMs);
|
|
@@ -101,6 +60,7 @@ export class MdbaseCollectionClient {
|
|
|
101
60
|
pageSize: options.pageSize,
|
|
102
61
|
signal: budget.signal,
|
|
103
62
|
pageTimeoutMs: null,
|
|
63
|
+
coordination: options.coordination,
|
|
104
64
|
onProgress: options.onProgress
|
|
105
65
|
})) {
|
|
106
66
|
if (!outcome.ok)
|
|
@@ -115,7 +75,6 @@ export class MdbaseCollectionClient {
|
|
|
115
75
|
return connectSuccess({
|
|
116
76
|
results,
|
|
117
77
|
meta: {
|
|
118
|
-
...(finalPage?.meta ?? {}),
|
|
119
78
|
totalCount: finalPage?.meta?.totalCount ?? results.length,
|
|
120
79
|
hasMore: finalPage ? !finalPage.complete : false,
|
|
121
80
|
...(finalPage?.snapshot ? { snapshot: finalPage.snapshot } : {})
|
|
@@ -317,6 +276,14 @@ export class MdbaseCollectionClient {
|
|
|
317
276
|
rawOperation(operation, input, allowedCodes, options) {
|
|
318
277
|
return captureConnectOutcome(() => this.transport.operation(operation, input, options), allowedCodes);
|
|
319
278
|
}
|
|
279
|
+
async releaseQueryCursor(cursor) {
|
|
280
|
+
try {
|
|
281
|
+
await this.envelopeOperation("query", { release_cursor: cursor }, COLLECTION_QUERY_PROBLEM_CODES, { timeoutMs: 2_000, coordination: { coalesce: false } });
|
|
282
|
+
}
|
|
283
|
+
catch {
|
|
284
|
+
// Lease expiry is bounded by the authority; cleanup never masks query results.
|
|
285
|
+
}
|
|
286
|
+
}
|
|
320
287
|
async envelopeOperation(operation, input, allowedCodes, options) {
|
|
321
288
|
const transported = await captureConnectOutcome(() => this.transport.operation(operation, input, options), allowedCodes);
|
|
322
289
|
if (!transported.ok)
|
|
@@ -371,6 +338,8 @@ function wireQueryInput(input) {
|
|
|
371
338
|
...(input.summaries ? { summaries: input.summaries } : {}),
|
|
372
339
|
...(input.limit === undefined ? {} : { limit: input.limit }),
|
|
373
340
|
...(input.offset === undefined ? {} : { offset: input.offset }),
|
|
341
|
+
...(input.pagination ? { pagination: input.pagination } : {}),
|
|
342
|
+
...(input.cursor ? { cursor: input.cursor } : {}),
|
|
374
343
|
...(input.snapshot ? { snapshot: input.snapshot } : {}),
|
|
375
344
|
...(input.includeBody === undefined ? {} : { include_body: input.includeBody }),
|
|
376
345
|
...(input.frontmatterMode ? { frontmatter_mode: input.frontmatterMode } : {}),
|
|
@@ -707,9 +676,3 @@ function wireCollectionDescription(value) {
|
|
|
707
676
|
...(value.configuration ? { configuration: value.configuration } : {})
|
|
708
677
|
};
|
|
709
678
|
}
|
|
710
|
-
function nonNegativeInteger(value, fallback) {
|
|
711
|
-
return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.floor(value)) : fallback;
|
|
712
|
-
}
|
|
713
|
-
function positiveInteger(value, fallback) {
|
|
714
|
-
return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
|
|
715
|
-
}
|
package/dist/connection.js
CHANGED
|
@@ -7,6 +7,7 @@ import { DEFAULT_OPERATIONS } from "./internal-types.js";
|
|
|
7
7
|
import { assertDeletePreview, assertRenamePreview, deleteEstimate, isCancellation, renameEstimate, throwIfCancelled, uniqueOperations } from "./operation-helpers.js";
|
|
8
8
|
import { AUTHORIZATION_PROBLEM_CODES, COLLECTION_CHANGES_PROBLEM_CODES, COLLECTION_MUTATION_PROBLEM_CODES, DIRECT_ACCESS_PROBLEM_CODES, NOTIFICATION_PROBLEM_CODES, REGISTRATION_PROBLEM_CODES, captureConnectOutcome, connectFailure, connectSuccess } from "./outcomes.js";
|
|
9
9
|
import { withCooperativeRequestBudget, withRequestBudget } from "./request-budget.js";
|
|
10
|
+
import { CollectionRequestCoordinator } from "./request-coordinator.js";
|
|
10
11
|
export class MdbaseConnection {
|
|
11
12
|
internals;
|
|
12
13
|
collectionId;
|
|
@@ -35,9 +36,9 @@ export class MdbaseConnection {
|
|
|
35
36
|
}, {
|
|
36
37
|
downloadPart: (session, partIndex, expectedLength, signal) => this.transport.files.downloadHostedPart(session, partIndex, expectedLength, signal)
|
|
37
38
|
}, internals.timeouts);
|
|
38
|
-
this.collectionClient = new MdbaseCollectionClient({
|
|
39
|
+
this.collectionClient = new MdbaseCollectionClient(new CollectionRequestCoordinator({
|
|
39
40
|
operation: (operation, input, requestOptions) => this.transport.performOperation(operation, input, requestOptions)
|
|
40
|
-
}, internals.timeouts.requestMs);
|
|
41
|
+
}, internals.timeouts.requestMs), internals.timeouts.requestMs);
|
|
41
42
|
this.notifications = new ConnectionNotifications({
|
|
42
43
|
serverUrl: internals.serverUrl,
|
|
43
44
|
storage: internals.storage,
|
package/dist/index.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export type { MdbaseFileSource, CollectionFileDescriptor, MdbaseFileDeleteReceip
|
|
|
15
15
|
export { parseMdbasePushPayload, parseMdbaseNativeNotificationData, showMdbasePushNotification } from "./notifications.js";
|
|
16
16
|
export type { MdbaseNotificationRegistrationOptions, MdbaseNotificationRegistration, MdbaseNativeNotificationRegistrationOptions, MdbaseNativeNotificationRegistration, MdbaseNativeNotificationData, MdbasePushPayload } from "./notifications.js";
|
|
17
17
|
export type { ConnectSuccess, ConnectFailure, ConnectOutcome, CommonOperationProblemCode, RegistrationProblemCode, AuthorizationProblemCode, NotificationProblemCode, DirectAccessProblemCode, SessionProblemCode, CollectionSetupProblemCode, CollectionDescriptionProblemCode, CollectionReadProblemCode, CollectionQueryProblemCode, CollectionMutationProblemCode, CollectionChangesProblemCode, CollectionTypeProblemCode } from "./outcomes.js";
|
|
18
|
-
export type { MdbaseDesiredTimer, MdbaseTimer, MdbaseTimerList, MdbaseTimerReconciliation, ReadInput, DataContractSelector, CollectionFileMetadata, DataContractViewIdentity, QueryRecord, RecordDocument, QueryInput, QueryProjection, QuerySelectionExpression, QueryOrder, QuerySummary, QueryResult, QueryPagesOptions, QueryAllOptions, QueryPage, ConnectRequestOptions, MutationEstimate, MutationProgressState, MutationProgress, MutationProgressOptions, RenameProgressOptions, DeleteProgressOptions, PendingMutationSummary, PendingMutation, CreateInput, UpdateInput, DeleteInput, DeleteResult, DeletePreflightResult, RenameInput, RenameResult, RenamePreflightResult, ReadTypeInput, CreateTypeInput, UpdateTypeInput, ReadViewSourceInput, CreateViewSourceInput, UpdateViewSourceInput, DeleteViewSourceInput, DeleteViewSourceResult, ExecuteViewInput, SavedViewPresentation, SavedViewSource, SavedViewProperty, SavedNamedView, SavedViewDocument, SavedViewList, SavedViewSourceDocument, SavedViewExecution, ContractSetupChoice, TypePackResourceDiff, TypePackReceipt, TypePackAssessment, TypePackApplyResult, ConfigurationContributionValue, ConfigurationRequirement, ConfigurationProvision, ApplicationCollectionSetupRequirements, ApplicationCollectionSetupProvisions, ConfigurationSetupConflict, ConfigurationSetupAssessment, CollectionSetupAssessment, CollectionSetupReceipt, CollectionSetupApplyResult, AssessTypePackInput, ApplyTypePackInput, AssessCollectionSetupInput, ApplyCollectionSetupInput, ChangesInput, WatchOptions, WatchRetryOptions, WatchStatus, WatchInput, MdbaseWatchSubscription, CollectionChange, CollectionChangesPage, CollectionDescription, CollectionContractDescriptor, CollectionContractImplementationDescriptor, CollectionTypeDescriptor } from "./operation-types.js";
|
|
18
|
+
export type { MdbaseDesiredTimer, MdbaseTimer, MdbaseTimerList, MdbaseTimerReconciliation, ReadInput, DataContractSelector, CollectionFileMetadata, DataContractViewIdentity, QueryRecord, RecordDocument, QueryInput, QueryProjection, QuerySelectionExpression, QueryOrder, QuerySummary, QueryResult, QueryPagesOptions, QueryAllOptions, QueryPage, ConnectRequestOptions, MutationEstimate, MutationProgressState, MutationProgress, MutationProgressOptions, RequestCoordinationOptions, RenameProgressOptions, DeleteProgressOptions, PendingMutationSummary, PendingMutation, CreateInput, UpdateInput, DeleteInput, DeleteResult, DeletePreflightResult, RenameInput, RenameResult, RenamePreflightResult, ReadTypeInput, CreateTypeInput, UpdateTypeInput, ReadViewSourceInput, CreateViewSourceInput, UpdateViewSourceInput, DeleteViewSourceInput, DeleteViewSourceResult, ExecuteViewInput, SavedViewPresentation, SavedViewSource, SavedViewProperty, SavedNamedView, SavedViewDocument, SavedViewList, SavedViewSourceDocument, SavedViewExecution, ContractSetupChoice, TypePackResourceDiff, TypePackReceipt, TypePackAssessment, TypePackApplyResult, ConfigurationContributionValue, ConfigurationRequirement, ConfigurationProvision, ApplicationCollectionSetupRequirements, ApplicationCollectionSetupProvisions, ConfigurationSetupConflict, ConfigurationSetupAssessment, CollectionSetupAssessment, CollectionSetupReceipt, CollectionSetupApplyResult, AssessTypePackInput, ApplyTypePackInput, AssessCollectionSetupInput, ApplyCollectionSetupInput, ChangesInput, WatchOptions, WatchRetryOptions, WatchStatus, WatchInput, MdbaseWatchSubscription, CollectionChange, CollectionChangesPage, CollectionDescription, CollectionContractDescriptor, CollectionContractImplementationDescriptor, CollectionTypeDescriptor } from "./operation-types.js";
|
|
19
19
|
export { externalStore } from "./external-store.js";
|
|
20
20
|
export type { MdbaseExternalStore } from "./external-store.js";
|
|
21
21
|
export { MdbaseBrowserSelection, MdbaseMemorySelection } from "./selection.js";
|
|
@@ -158,7 +158,11 @@ export interface QueryInput {
|
|
|
158
158
|
summaries?: QuerySummary[];
|
|
159
159
|
limit?: number;
|
|
160
160
|
offset?: number;
|
|
161
|
-
/**
|
|
161
|
+
/** Request generation-pinned cursor pagination from a supporting authority. */
|
|
162
|
+
pagination?: "cursor";
|
|
163
|
+
/** Opaque, single-use token returned for the next generation-pinned page. */
|
|
164
|
+
cursor?: string;
|
|
165
|
+
/** Legacy stable-query token used by older authorities. */
|
|
162
166
|
snapshot?: string;
|
|
163
167
|
includeBody?: boolean;
|
|
164
168
|
frontmatterMode?: "effective" | "persisted" | "both";
|
|
@@ -170,6 +174,7 @@ export interface QueryResult<Record extends JsonObject = JsonObject> {
|
|
|
170
174
|
meta?: {
|
|
171
175
|
totalCount: number;
|
|
172
176
|
hasMore: boolean;
|
|
177
|
+
cursor?: string;
|
|
173
178
|
snapshot?: string;
|
|
174
179
|
};
|
|
175
180
|
}
|
|
@@ -179,6 +184,7 @@ export interface QueryPagesOptions<Record extends JsonObject = JsonObject> {
|
|
|
179
184
|
signal?: AbortSignal;
|
|
180
185
|
/** Independent budget for each page requested by this caller-driven iterator. */
|
|
181
186
|
pageTimeoutMs?: number | null;
|
|
187
|
+
coordination?: RequestCoordinationOptions;
|
|
182
188
|
onProgress?: (page: QueryPage<Record>) => void;
|
|
183
189
|
}
|
|
184
190
|
export interface QueryAllOptions<Record extends JsonObject = JsonObject> extends ConnectRequestOptions {
|
|
@@ -193,12 +199,27 @@ export interface QueryPage<Record extends JsonObject = JsonObject> {
|
|
|
193
199
|
offset: number;
|
|
194
200
|
loaded: number;
|
|
195
201
|
complete: boolean;
|
|
202
|
+
/** Opaque token for the next page while this iterator remains open. */
|
|
203
|
+
cursor?: string;
|
|
196
204
|
snapshot?: string;
|
|
197
205
|
}
|
|
198
206
|
export interface ConnectRequestOptions {
|
|
199
207
|
signal?: AbortSignal;
|
|
200
208
|
/** Relative request budget. `null` deliberately disables the SDK default. */
|
|
201
209
|
timeoutMs?: number | null;
|
|
210
|
+
/** Optional coordination for replaceable reads on this selected connection. */
|
|
211
|
+
coordination?: RequestCoordinationOptions;
|
|
212
|
+
}
|
|
213
|
+
export interface RequestCoordinationOptions {
|
|
214
|
+
/**
|
|
215
|
+
* Stable UI/request family. Required when `latestWins` is enabled; a newer
|
|
216
|
+
* request in the same family cancels the older read or query.
|
|
217
|
+
*/
|
|
218
|
+
family?: string;
|
|
219
|
+
/** Explicitly cancel older work in this family. Mutations never allow this. */
|
|
220
|
+
latestWins?: boolean;
|
|
221
|
+
/** Disable automatic coalescing of identical signal-free reads. */
|
|
222
|
+
coalesce?: boolean;
|
|
202
223
|
}
|
|
203
224
|
export interface MutationEstimate {
|
|
204
225
|
/** Records whose links may be affected, excluding the record being mutated. */
|
package/dist/outcomes.d.ts
CHANGED
|
@@ -29,14 +29,14 @@ export declare const SESSION_PROBLEM_CODES: readonly ["discovery_failed", "inval
|
|
|
29
29
|
export type CollectionSetupProblemCode = "collection_configuration_invalid" | "collection_invalid" | "collection_type_registry_invalid" | "collection_version_unsupported";
|
|
30
30
|
export type CollectionDescriptionProblemCode = CommonOperationProblemCode | CollectionSetupProblemCode;
|
|
31
31
|
export type CollectionReadProblemCode = CollectionDescriptionProblemCode | "invalid_path" | "operation_invalid";
|
|
32
|
-
export type CollectionQueryProblemCode = CollectionReadProblemCode | "query_snapshot_changed" | "sandbox_unsupported";
|
|
32
|
+
export type CollectionQueryProblemCode = CollectionReadProblemCode | "cursor_capacity_exhausted" | "generation_expired" | "invalid_read_cursor" | "query_snapshot_changed" | "sandbox_unsupported";
|
|
33
33
|
export type CollectionMutationProblemCode = CollectionReadProblemCode | "concurrent_modification" | "invalid_preflight" | "pending_mutation_unresolved";
|
|
34
34
|
export type CollectionChangesProblemCode = CollectionDescriptionProblemCode | "change_cursor_reset";
|
|
35
35
|
export type CollectionTypeProblemCode = CollectionMutationProblemCode | "type_pack_provision_failed";
|
|
36
36
|
export declare const COLLECTION_SETUP_PROBLEM_CODES: readonly ["collection_configuration_invalid", "collection_invalid", "collection_type_registry_invalid", "collection_version_unsupported"];
|
|
37
37
|
export declare const COLLECTION_DESCRIPTION_PROBLEM_CODES: readonly ["access_denied", "access_paused", "authorization_binding_incompatible", "authorization_expired", "capability_contract_incompatible", "collection_access_denied", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "direct_operation_rejected", "durable_mutation_unsupported", "operation_outcome_unknown", "encryption_required", "encrypted_relay_rejected", "hosted_provider_unavailable", "insufficient_access", "invalid_encrypted_response", "invalid_operation_response", "invalid_request", "missing_grant_key", "mutation_recovery_expired", "mutation_request_conflict", "not_authorized", "operation_cancelled", "operation_failed", "rate_limited", "relay_authorization_expired", "relay_unavailable", "temporarily_unavailable", "timeout", "transport_protocol_incompatible", "unsupported_operation", "collection_configuration_invalid", "collection_invalid", "collection_type_registry_invalid", "collection_version_unsupported"];
|
|
38
38
|
export declare const COLLECTION_READ_PROBLEM_CODES: readonly ["access_denied", "access_paused", "authorization_binding_incompatible", "authorization_expired", "capability_contract_incompatible", "collection_access_denied", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "direct_operation_rejected", "durable_mutation_unsupported", "operation_outcome_unknown", "encryption_required", "encrypted_relay_rejected", "hosted_provider_unavailable", "insufficient_access", "invalid_encrypted_response", "invalid_operation_response", "invalid_request", "missing_grant_key", "mutation_recovery_expired", "mutation_request_conflict", "not_authorized", "operation_cancelled", "operation_failed", "rate_limited", "relay_authorization_expired", "relay_unavailable", "temporarily_unavailable", "timeout", "transport_protocol_incompatible", "unsupported_operation", "collection_configuration_invalid", "collection_invalid", "collection_type_registry_invalid", "collection_version_unsupported", "invalid_path", "operation_invalid"];
|
|
39
|
-
export declare const COLLECTION_QUERY_PROBLEM_CODES: readonly ["access_denied", "access_paused", "authorization_binding_incompatible", "authorization_expired", "capability_contract_incompatible", "collection_access_denied", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "direct_operation_rejected", "durable_mutation_unsupported", "operation_outcome_unknown", "encryption_required", "encrypted_relay_rejected", "hosted_provider_unavailable", "insufficient_access", "invalid_encrypted_response", "invalid_operation_response", "invalid_request", "missing_grant_key", "mutation_recovery_expired", "mutation_request_conflict", "not_authorized", "operation_cancelled", "operation_failed", "rate_limited", "relay_authorization_expired", "relay_unavailable", "temporarily_unavailable", "timeout", "transport_protocol_incompatible", "unsupported_operation", "collection_configuration_invalid", "collection_invalid", "collection_type_registry_invalid", "collection_version_unsupported", "invalid_path", "operation_invalid", "query_snapshot_changed", "sandbox_unsupported"];
|
|
39
|
+
export declare const COLLECTION_QUERY_PROBLEM_CODES: readonly ["access_denied", "access_paused", "authorization_binding_incompatible", "authorization_expired", "capability_contract_incompatible", "collection_access_denied", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "direct_operation_rejected", "durable_mutation_unsupported", "operation_outcome_unknown", "encryption_required", "encrypted_relay_rejected", "hosted_provider_unavailable", "insufficient_access", "invalid_encrypted_response", "invalid_operation_response", "invalid_request", "missing_grant_key", "mutation_recovery_expired", "mutation_request_conflict", "not_authorized", "operation_cancelled", "operation_failed", "rate_limited", "relay_authorization_expired", "relay_unavailable", "temporarily_unavailable", "timeout", "transport_protocol_incompatible", "unsupported_operation", "collection_configuration_invalid", "collection_invalid", "collection_type_registry_invalid", "collection_version_unsupported", "invalid_path", "operation_invalid", "cursor_capacity_exhausted", "generation_expired", "invalid_read_cursor", "query_snapshot_changed", "sandbox_unsupported"];
|
|
40
40
|
export declare const COLLECTION_MUTATION_PROBLEM_CODES: readonly ["access_denied", "access_paused", "authorization_binding_incompatible", "authorization_expired", "capability_contract_incompatible", "collection_access_denied", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "direct_operation_rejected", "durable_mutation_unsupported", "operation_outcome_unknown", "encryption_required", "encrypted_relay_rejected", "hosted_provider_unavailable", "insufficient_access", "invalid_encrypted_response", "invalid_operation_response", "invalid_request", "missing_grant_key", "mutation_recovery_expired", "mutation_request_conflict", "not_authorized", "operation_cancelled", "operation_failed", "rate_limited", "relay_authorization_expired", "relay_unavailable", "temporarily_unavailable", "timeout", "transport_protocol_incompatible", "unsupported_operation", "collection_configuration_invalid", "collection_invalid", "collection_type_registry_invalid", "collection_version_unsupported", "invalid_path", "operation_invalid", "concurrent_modification", "invalid_preflight", "pending_mutation_unresolved"];
|
|
41
41
|
export declare const COLLECTION_CHANGES_PROBLEM_CODES: readonly ["access_denied", "access_paused", "authorization_binding_incompatible", "authorization_expired", "capability_contract_incompatible", "collection_access_denied", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "direct_operation_rejected", "durable_mutation_unsupported", "operation_outcome_unknown", "encryption_required", "encrypted_relay_rejected", "hosted_provider_unavailable", "insufficient_access", "invalid_encrypted_response", "invalid_operation_response", "invalid_request", "missing_grant_key", "mutation_recovery_expired", "mutation_request_conflict", "not_authorized", "operation_cancelled", "operation_failed", "rate_limited", "relay_authorization_expired", "relay_unavailable", "temporarily_unavailable", "timeout", "transport_protocol_incompatible", "unsupported_operation", "collection_configuration_invalid", "collection_invalid", "collection_type_registry_invalid", "collection_version_unsupported", "change_cursor_reset"];
|
|
42
42
|
export declare const COLLECTION_TYPE_PROBLEM_CODES: readonly ["access_denied", "access_paused", "authorization_binding_incompatible", "authorization_expired", "capability_contract_incompatible", "collection_access_denied", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "direct_operation_rejected", "durable_mutation_unsupported", "operation_outcome_unknown", "encryption_required", "encrypted_relay_rejected", "hosted_provider_unavailable", "insufficient_access", "invalid_encrypted_response", "invalid_operation_response", "invalid_request", "missing_grant_key", "mutation_recovery_expired", "mutation_request_conflict", "not_authorized", "operation_cancelled", "operation_failed", "rate_limited", "relay_authorization_expired", "relay_unavailable", "temporarily_unavailable", "timeout", "transport_protocol_incompatible", "unsupported_operation", "collection_configuration_invalid", "collection_invalid", "collection_type_registry_invalid", "collection_version_unsupported", "invalid_path", "operation_invalid", "concurrent_modification", "invalid_preflight", "pending_mutation_unresolved", "type_pack_provision_failed"];
|
package/dist/outcomes.js
CHANGED
|
@@ -127,6 +127,9 @@ export const COLLECTION_READ_PROBLEM_CODES = [
|
|
|
127
127
|
];
|
|
128
128
|
export const COLLECTION_QUERY_PROBLEM_CODES = [
|
|
129
129
|
...COLLECTION_READ_PROBLEM_CODES,
|
|
130
|
+
"cursor_capacity_exhausted",
|
|
131
|
+
"generation_expired",
|
|
132
|
+
"invalid_read_cursor",
|
|
130
133
|
"query_snapshot_changed",
|
|
131
134
|
"sandbox_unsupported"
|
|
132
135
|
];
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { JsonObject } from "@mdbase-dev/connect-protocol";
|
|
2
|
+
import { type CollectionQueryProblemCode, type ConnectOutcome } from "./outcomes.js";
|
|
3
|
+
import type { ConnectRequestOptions, QueryInput, QueryPage, QueryPagesOptions, QueryResult } from "./operation-types.js";
|
|
4
|
+
type QueryOperation<Frontmatter extends JsonObject> = (input: QueryInput, options?: ConnectRequestOptions) => Promise<ConnectOutcome<QueryResult<Frontmatter>, CollectionQueryProblemCode>>;
|
|
5
|
+
export declare function coordinatedQueryPages<Frontmatter extends JsonObject>(query: QueryOperation<Frontmatter>, releaseQueryCursor: (cursor: string) => Promise<void>, input?: QueryInput, options?: QueryPagesOptions<Frontmatter>): AsyncGenerator<ConnectOutcome<QueryPage<Frontmatter>, CollectionQueryProblemCode>>;
|
|
6
|
+
export {};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { connectProblem } from "./errors.js";
|
|
2
|
+
import { connectFailure, connectSuccess } from "./outcomes.js";
|
|
3
|
+
export async function* coordinatedQueryPages(query, releaseQueryCursor, input = {}, options = {}) {
|
|
4
|
+
const { offset: requestedOffset, limit: requestedLimit, cursor: requestedCursor, pagination: requestedPagination, snapshot: requestedSnapshot, ...criteria } = input;
|
|
5
|
+
let offset = nonNegativeInteger(requestedOffset, 0);
|
|
6
|
+
const firstPageSize = positiveInteger(options.firstPageSize ?? requestedLimit, 200);
|
|
7
|
+
const pageSize = positiveInteger(options.pageSize ?? requestedLimit, 1_000);
|
|
8
|
+
let cursor = requestedCursor;
|
|
9
|
+
let cursorMode = requestedCursor !== undefined;
|
|
10
|
+
let cursorToRelease = requestedCursor;
|
|
11
|
+
let snapshot = requestedSnapshot;
|
|
12
|
+
let loaded = 0;
|
|
13
|
+
let pageNumber = 0;
|
|
14
|
+
try {
|
|
15
|
+
while (!options.signal?.aborted) {
|
|
16
|
+
const pageCursor = cursor;
|
|
17
|
+
const queried = await query({
|
|
18
|
+
...criteria,
|
|
19
|
+
limit: pageNumber === 0 ? firstPageSize : pageSize,
|
|
20
|
+
...(cursorMode
|
|
21
|
+
? (pageCursor ? { cursor: pageCursor } : { pagination: "cursor" })
|
|
22
|
+
: {
|
|
23
|
+
offset,
|
|
24
|
+
...(snapshot ? { snapshot } : {}),
|
|
25
|
+
...(!snapshot && requestedPagination === "cursor"
|
|
26
|
+
? { pagination: "cursor" }
|
|
27
|
+
: {})
|
|
28
|
+
}),
|
|
29
|
+
...(!cursorMode && !snapshot && requestedPagination === undefined
|
|
30
|
+
? { pagination: "cursor" }
|
|
31
|
+
: {})
|
|
32
|
+
}, {
|
|
33
|
+
signal: options.signal,
|
|
34
|
+
timeoutMs: options.pageTimeoutMs,
|
|
35
|
+
coordination: { ...options.coordination, coalesce: false }
|
|
36
|
+
});
|
|
37
|
+
if (!queried.ok) {
|
|
38
|
+
yield queried;
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const result = queried.value;
|
|
42
|
+
const returnedCursor = result.meta?.cursor;
|
|
43
|
+
if (returnedCursor) {
|
|
44
|
+
cursorMode = true;
|
|
45
|
+
cursor = returnedCursor;
|
|
46
|
+
cursorToRelease = returnedCursor;
|
|
47
|
+
}
|
|
48
|
+
else if (cursorMode) {
|
|
49
|
+
cursor = undefined;
|
|
50
|
+
cursorToRelease = pageCursor;
|
|
51
|
+
}
|
|
52
|
+
if (cursorMode && result.meta?.hasMore && !returnedCursor) {
|
|
53
|
+
yield connectFailure(connectProblem("invalid_operation_response", "The collection authority omitted the cursor required for the next query page."));
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const returnedSnapshot = result.meta?.snapshot;
|
|
57
|
+
if (!cursorMode && snapshot && returnedSnapshot && snapshot !== returnedSnapshot) {
|
|
58
|
+
yield connectFailure(connectProblem("query_snapshot_changed", "The collection query snapshot changed while paging. Refresh the query before continuing."));
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (!cursorMode && !snapshot && returnedSnapshot)
|
|
62
|
+
snapshot = returnedSnapshot;
|
|
63
|
+
loaded += result.results.length;
|
|
64
|
+
const complete = !result.meta?.hasMore || result.results.length === 0;
|
|
65
|
+
const page = {
|
|
66
|
+
results: result.results,
|
|
67
|
+
...(result.meta ? { meta: result.meta } : {}),
|
|
68
|
+
page: pageNumber,
|
|
69
|
+
offset,
|
|
70
|
+
loaded,
|
|
71
|
+
complete,
|
|
72
|
+
...(returnedCursor ? { cursor: returnedCursor } : {}),
|
|
73
|
+
...(!cursorMode && snapshot ? { snapshot } : {})
|
|
74
|
+
};
|
|
75
|
+
options.onProgress?.(page);
|
|
76
|
+
yield connectSuccess(page, queried.diagnostics);
|
|
77
|
+
if (complete)
|
|
78
|
+
return;
|
|
79
|
+
offset += result.results.length;
|
|
80
|
+
pageNumber += 1;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
if (cursorMode && cursorToRelease) {
|
|
85
|
+
await releaseQueryCursor(cursorToRelease);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function nonNegativeInteger(value, fallback) {
|
|
90
|
+
return typeof value === "number" && Number.isFinite(value)
|
|
91
|
+
? Math.max(0, Math.floor(value))
|
|
92
|
+
: fallback;
|
|
93
|
+
}
|
|
94
|
+
function positiveInteger(value, fallback) {
|
|
95
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0
|
|
96
|
+
? Math.floor(value)
|
|
97
|
+
: fallback;
|
|
98
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type CollectionOperation } from "@mdbase-dev/connect-protocol";
|
|
2
|
+
import type { ConnectRequestOptions, MdbaseCollectionTransport } from "./operation-types.js";
|
|
3
|
+
interface CoordinatorLimits {
|
|
4
|
+
foregroundCapacity?: number;
|
|
5
|
+
foregroundQueueCapacity?: number;
|
|
6
|
+
mutationQueueCapacity?: number;
|
|
7
|
+
}
|
|
8
|
+
/** One bounded request owner for a selected connection. */
|
|
9
|
+
export declare class CollectionRequestCoordinator implements MdbaseCollectionTransport {
|
|
10
|
+
private readonly transport;
|
|
11
|
+
private readonly requestTimeoutMs;
|
|
12
|
+
private readonly foregroundCapacity;
|
|
13
|
+
private readonly foregroundQueueCapacity;
|
|
14
|
+
private readonly mutationQueueCapacity;
|
|
15
|
+
private readonly foregroundQueue;
|
|
16
|
+
private readonly mutationQueue;
|
|
17
|
+
private readonly identicalReads;
|
|
18
|
+
private readonly latestFamilies;
|
|
19
|
+
private activeForeground;
|
|
20
|
+
private mutationActive;
|
|
21
|
+
constructor(transport: MdbaseCollectionTransport, requestTimeoutMs: number | null, limits?: CoordinatorLimits);
|
|
22
|
+
operation<Result>(operation: CollectionOperation, input: unknown, options?: ConnectRequestOptions): Promise<Result>;
|
|
23
|
+
private schedule;
|
|
24
|
+
private replaceLatest;
|
|
25
|
+
private releaseLatest;
|
|
26
|
+
private pump;
|
|
27
|
+
private start;
|
|
28
|
+
}
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { isMutatingOperation } from "@mdbase-dev/connect-protocol";
|
|
2
|
+
import { connectError } from "./errors.js";
|
|
3
|
+
import { requestAbortReason, requestOptionsWithinBudget, withCooperativeRequestBudget } from "./request-budget.js";
|
|
4
|
+
const DEFAULT_FOREGROUND_CAPACITY = 4;
|
|
5
|
+
const DEFAULT_QUEUE_CAPACITY = 32;
|
|
6
|
+
/** One bounded request owner for a selected connection. */
|
|
7
|
+
export class CollectionRequestCoordinator {
|
|
8
|
+
transport;
|
|
9
|
+
requestTimeoutMs;
|
|
10
|
+
foregroundCapacity;
|
|
11
|
+
foregroundQueueCapacity;
|
|
12
|
+
mutationQueueCapacity;
|
|
13
|
+
foregroundQueue = [];
|
|
14
|
+
mutationQueue = [];
|
|
15
|
+
identicalReads = new Map();
|
|
16
|
+
latestFamilies = new Map();
|
|
17
|
+
activeForeground = 0;
|
|
18
|
+
mutationActive = false;
|
|
19
|
+
constructor(transport, requestTimeoutMs, limits = {}) {
|
|
20
|
+
this.transport = transport;
|
|
21
|
+
this.requestTimeoutMs = requestTimeoutMs;
|
|
22
|
+
this.foregroundCapacity = positiveCapacity(limits.foregroundCapacity, DEFAULT_FOREGROUND_CAPACITY);
|
|
23
|
+
this.foregroundQueueCapacity = positiveCapacity(limits.foregroundQueueCapacity, DEFAULT_QUEUE_CAPACITY);
|
|
24
|
+
this.mutationQueueCapacity = positiveCapacity(limits.mutationQueueCapacity, DEFAULT_QUEUE_CAPACITY);
|
|
25
|
+
}
|
|
26
|
+
operation(operation, input, options = {}) {
|
|
27
|
+
const read = !isMutatingOperation(operation, input);
|
|
28
|
+
const coordination = validatedCoordination(options.coordination, read);
|
|
29
|
+
const coalesceKey = read
|
|
30
|
+
&& coordination.coalesce !== false
|
|
31
|
+
&& !coordination.latestWins
|
|
32
|
+
&& options.signal === undefined
|
|
33
|
+
? stableRequestKey(operation, input, options.timeoutMs)
|
|
34
|
+
: undefined;
|
|
35
|
+
if (coalesceKey) {
|
|
36
|
+
const existing = this.identicalReads.get(coalesceKey);
|
|
37
|
+
if (existing)
|
|
38
|
+
return existing;
|
|
39
|
+
}
|
|
40
|
+
const request = withCooperativeRequestBudget(options, this.requestTimeoutMs, (budget) => this.schedule(operation, input, options, coordination, read, budget));
|
|
41
|
+
if (coalesceKey) {
|
|
42
|
+
this.identicalReads.set(coalesceKey, request);
|
|
43
|
+
void request.finally(() => {
|
|
44
|
+
if (this.identicalReads.get(coalesceKey) === request) {
|
|
45
|
+
this.identicalReads.delete(coalesceKey);
|
|
46
|
+
}
|
|
47
|
+
}).catch(() => undefined);
|
|
48
|
+
}
|
|
49
|
+
return request;
|
|
50
|
+
}
|
|
51
|
+
schedule(operation, input, options, coordination, read, budget) {
|
|
52
|
+
const replacement = coordination.latestWins
|
|
53
|
+
? this.replaceLatest(coordination.family)
|
|
54
|
+
: undefined;
|
|
55
|
+
const combined = combineSignals(budget.signal, replacement?.signal);
|
|
56
|
+
const { coordination: _coordination, ...transportOptions } = options;
|
|
57
|
+
const withinBudget = requestOptionsWithinBudget({ ...transportOptions, timeoutMs: null }, budget);
|
|
58
|
+
const run = () => this.transport.operation(operation, input, {
|
|
59
|
+
...withinBudget,
|
|
60
|
+
signal: combined.signal
|
|
61
|
+
});
|
|
62
|
+
const queue = read ? this.foregroundQueue : this.mutationQueue;
|
|
63
|
+
const capacity = read ? this.foregroundQueueCapacity : this.mutationQueueCapacity;
|
|
64
|
+
if (queue.length >= capacity) {
|
|
65
|
+
combined.dispose();
|
|
66
|
+
this.releaseLatest(coordination.family, replacement);
|
|
67
|
+
return Promise.reject(connectError("connector_busy", "The selected connection request queue is full.", { operationOutcome: "not_sent", status: 503 }));
|
|
68
|
+
}
|
|
69
|
+
return new Promise((resolve, reject) => {
|
|
70
|
+
const queued = {
|
|
71
|
+
budget,
|
|
72
|
+
run,
|
|
73
|
+
resolve: (result) => resolve(result),
|
|
74
|
+
reject,
|
|
75
|
+
started: false,
|
|
76
|
+
settled: false
|
|
77
|
+
};
|
|
78
|
+
const abortQueued = () => {
|
|
79
|
+
if (queued.started || queued.settled)
|
|
80
|
+
return;
|
|
81
|
+
queued.settled = true;
|
|
82
|
+
queued.reject(requestAbortReason(combined.signal));
|
|
83
|
+
combined.dispose();
|
|
84
|
+
this.releaseLatest(coordination.family, replacement);
|
|
85
|
+
};
|
|
86
|
+
combined.signal.addEventListener("abort", abortQueued, { once: true });
|
|
87
|
+
const originalRun = queued.run;
|
|
88
|
+
queued.run = async () => {
|
|
89
|
+
combined.signal.removeEventListener("abort", abortQueued);
|
|
90
|
+
try {
|
|
91
|
+
if (combined.signal.aborted)
|
|
92
|
+
throw requestAbortReason(combined.signal);
|
|
93
|
+
return await originalRun();
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
combined.dispose();
|
|
97
|
+
this.releaseLatest(coordination.family, replacement);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
queue.push(queued);
|
|
101
|
+
this.pump();
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
replaceLatest(family) {
|
|
105
|
+
const replacement = new AbortController();
|
|
106
|
+
this.latestFamilies.get(family)?.abort(connectError("operation_cancelled", "A newer request replaced this read before it changed the collection.", { operationOutcome: "not_sent" }));
|
|
107
|
+
this.latestFamilies.set(family, replacement);
|
|
108
|
+
return replacement;
|
|
109
|
+
}
|
|
110
|
+
releaseLatest(family, controller) {
|
|
111
|
+
if (family && controller && this.latestFamilies.get(family) === controller) {
|
|
112
|
+
this.latestFamilies.delete(family);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
pump() {
|
|
116
|
+
while (this.activeForeground < this.foregroundCapacity) {
|
|
117
|
+
const request = nextLive(this.foregroundQueue);
|
|
118
|
+
if (!request)
|
|
119
|
+
break;
|
|
120
|
+
this.activeForeground += 1;
|
|
121
|
+
this.start(request, () => {
|
|
122
|
+
this.activeForeground -= 1;
|
|
123
|
+
this.pump();
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
if (!this.mutationActive) {
|
|
127
|
+
const request = nextLive(this.mutationQueue);
|
|
128
|
+
if (request) {
|
|
129
|
+
this.mutationActive = true;
|
|
130
|
+
this.start(request, () => {
|
|
131
|
+
this.mutationActive = false;
|
|
132
|
+
this.pump();
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
start(request, release) {
|
|
138
|
+
request.started = true;
|
|
139
|
+
void request.run().then((result) => {
|
|
140
|
+
if (!request.settled) {
|
|
141
|
+
request.settled = true;
|
|
142
|
+
request.resolve(result);
|
|
143
|
+
}
|
|
144
|
+
}, (error) => {
|
|
145
|
+
if (!request.settled) {
|
|
146
|
+
request.settled = true;
|
|
147
|
+
request.reject(error);
|
|
148
|
+
}
|
|
149
|
+
}).finally(release);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function nextLive(queue) {
|
|
153
|
+
while (queue.length > 0) {
|
|
154
|
+
const request = queue.shift();
|
|
155
|
+
if (!request.settled)
|
|
156
|
+
return request;
|
|
157
|
+
}
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
160
|
+
function validatedCoordination(coordination, read) {
|
|
161
|
+
const resolved = coordination ?? {};
|
|
162
|
+
if (resolved.latestWins && !read) {
|
|
163
|
+
throw new TypeError("latestWins coordination is available only for read operations.");
|
|
164
|
+
}
|
|
165
|
+
if (resolved.latestWins && !resolved.family?.trim()) {
|
|
166
|
+
throw new TypeError("latestWins coordination requires a non-empty family.");
|
|
167
|
+
}
|
|
168
|
+
return resolved;
|
|
169
|
+
}
|
|
170
|
+
function stableRequestKey(operation, input, timeoutMs) {
|
|
171
|
+
return `${operation}:${timeoutMs === undefined ? "default" : String(timeoutMs)}:${stableJson(input)}`;
|
|
172
|
+
}
|
|
173
|
+
function stableJson(value) {
|
|
174
|
+
if (value === null || typeof value !== "object") {
|
|
175
|
+
return JSON.stringify(value) ?? String(value);
|
|
176
|
+
}
|
|
177
|
+
if (Array.isArray(value))
|
|
178
|
+
return `[${value.map(stableJson).join(",")}]`;
|
|
179
|
+
return `{${Object.entries(value)
|
|
180
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
181
|
+
.map(([key, nested]) => `${JSON.stringify(key)}:${stableJson(nested)}`)
|
|
182
|
+
.join(",")}}`;
|
|
183
|
+
}
|
|
184
|
+
function combineSignals(primary, secondary) {
|
|
185
|
+
if (!secondary)
|
|
186
|
+
return { signal: primary, dispose: () => undefined };
|
|
187
|
+
const controller = new AbortController();
|
|
188
|
+
const abortPrimary = () => controller.abort(primary.reason);
|
|
189
|
+
const abortSecondary = () => controller.abort(secondary.reason);
|
|
190
|
+
if (primary.aborted)
|
|
191
|
+
abortPrimary();
|
|
192
|
+
else
|
|
193
|
+
primary.addEventListener("abort", abortPrimary, { once: true });
|
|
194
|
+
if (secondary.aborted)
|
|
195
|
+
abortSecondary();
|
|
196
|
+
else
|
|
197
|
+
secondary.addEventListener("abort", abortSecondary, { once: true });
|
|
198
|
+
return {
|
|
199
|
+
signal: controller.signal,
|
|
200
|
+
dispose: () => {
|
|
201
|
+
primary.removeEventListener("abort", abortPrimary);
|
|
202
|
+
secondary.removeEventListener("abort", abortSecondary);
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
function positiveCapacity(value, fallback) {
|
|
207
|
+
const resolved = value ?? fallback;
|
|
208
|
+
if (!Number.isSafeInteger(resolved) || resolved <= 0) {
|
|
209
|
+
throw new TypeError("Request coordinator capacities must be positive safe integers.");
|
|
210
|
+
}
|
|
211
|
+
return resolved;
|
|
212
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mdbase-dev/connect",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.65",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"README.md"
|
|
34
34
|
],
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@mdbase-dev/connect-protocol": "0.1.0-beta.
|
|
36
|
+
"@mdbase-dev/connect-protocol": "0.1.0-beta.65"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"esbuild": "^0.28.1",
|