@mdbase-dev/connect 0.1.0-beta.121 → 0.1.0-beta.124
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/README.md +161 -6
- package/dist/application-session.js +51 -27
- package/dist/authority-features.d.ts +29 -0
- package/dist/authority-features.js +84 -0
- package/dist/browser/integrity.json +1 -1
- package/dist/browser/mdbase-connect.min.js +4 -4
- package/dist/browser/mdbase-connect.min.js.sha384 +1 -1
- package/dist/change-events.d.ts +6 -0
- package/dist/change-events.js +95 -0
- package/dist/collection-client.d.ts +13 -3
- package/dist/collection-client.js +98 -140
- package/dist/connection.d.ts +12 -2
- package/dist/connection.js +57 -117
- package/dist/description-cache.d.ts +14 -0
- package/dist/description-cache.js +46 -0
- package/dist/file-crypto.js +10 -6
- package/dist/file-descriptor.d.ts +14 -0
- package/dist/file-descriptor.js +29 -0
- package/dist/file-metadata.d.ts +21 -0
- package/dist/file-metadata.js +112 -0
- package/dist/file-sha256.js +3 -3
- package/dist/files.d.ts +9 -19
- package/dist/files.js +49 -75
- package/dist/index.d.ts +5 -2
- package/dist/index.js +2 -0
- package/dist/local-file-transport.js +18 -16
- package/dist/operation-types.d.ts +155 -14
- package/dist/query-pagination-internals.d.ts +9 -0
- package/dist/query-pagination-internals.js +52 -0
- package/dist/query-pagination.d.ts +7 -3
- package/dist/query-pagination.js +31 -27
- package/dist/query-predicates.d.ts +11 -0
- package/dist/query-predicates.js +15 -0
- package/dist/query-wire.d.ts +61 -0
- package/dist/query-wire.js +110 -0
- package/dist/read-many.d.ts +7 -0
- package/dist/read-many.js +175 -0
- package/dist/record-session.d.ts +3 -2
- package/dist/record-session.js +11 -2
- package/dist/records.d.ts +15 -2
- package/dist/records.js +109 -7
- package/dist/saved-view-pagination.js +20 -20
- package/dist/startup-timing.d.ts +2 -0
- package/dist/startup-timing.js +20 -0
- package/dist/structured-readiness.d.ts +3 -1
- package/dist/structured-readiness.js +21 -1
- package/dist/watch-policy.d.ts +22 -1
- package/dist/watch-policy.js +104 -0
- package/package.json +2 -2
package/dist/files.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import { clientFileDescriptor } from "./file-descriptor.js";
|
|
1
2
|
import { FILE_PROTOCOL_VERSION } from "@mdbase-dev/connect-protocol";
|
|
2
|
-
import {
|
|
3
|
+
import { listFiles, statFile } from "./file-metadata.js";
|
|
4
|
+
import { connectSuccess } from "./outcomes.js";
|
|
3
5
|
import { MdbaseConnectError, connectError } from "./errors.js";
|
|
4
6
|
import { IncrementalSha256 } from "./file-sha256.js";
|
|
5
7
|
import { BinaryPartReader } from "./file-stream-source.js";
|
|
6
|
-
import { MAX_BUFFERED_DOWNLOAD_BYTES, MAX_OBJECT_ATTEMPTS, SHA256_DIGEST, UUID, browserObjectHeaders, chunkLength, hashBlob, mapConcurrent, normalizeFileError, requirePreparedPart, requireTransferSession, requireTransferStatus, retryChunk, sourceBlob, throwIfAborted, uploadBodyBytes, uploadBodyLength, validConcurrency
|
|
7
|
-
import {
|
|
8
|
+
import { MAX_BUFFERED_DOWNLOAD_BYTES, MAX_OBJECT_ATTEMPTS, SHA256_DIGEST, UUID, browserObjectHeaders, chunkLength, hashBlob, mapConcurrent, normalizeFileError, requirePreparedPart, requireTransferSession, requireTransferStatus, retryChunk, sourceBlob, throwIfAborted, uploadBodyBytes, uploadBodyLength, validConcurrency } from "./file-transfer-internals.js";
|
|
9
|
+
import { resolveConnectTimeouts, withCooperativeRequestBudget } from "./request-budget.js";
|
|
8
10
|
/** Ergonomic facade over resumable, authority-specific file delivery. */
|
|
9
11
|
export class MdbaseFileClient {
|
|
10
12
|
capability;
|
|
@@ -12,54 +14,21 @@ export class MdbaseFileClient {
|
|
|
12
14
|
framed;
|
|
13
15
|
hosted;
|
|
14
16
|
timeouts;
|
|
15
|
-
|
|
17
|
+
supportsAuthorityFeature;
|
|
18
|
+
constructor(capability, request, framed, hosted, timeouts = resolveConnectTimeouts(), supportsAuthorityFeature = async () => connectSuccess(false)) {
|
|
16
19
|
this.capability = capability;
|
|
17
20
|
this.request = request;
|
|
18
21
|
this.framed = framed;
|
|
19
22
|
this.hosted = hosted;
|
|
20
23
|
this.timeouts = timeouts;
|
|
24
|
+
this.supportsAuthorityFeature = supportsAuthorityFeature;
|
|
21
25
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
do {
|
|
29
|
-
throwIfAborted(signal);
|
|
30
|
-
const query = new URLSearchParams({
|
|
31
|
-
protocol_version: String(FILE_PROTOCOL_VERSION),
|
|
32
|
-
...(options.folder ? { folder: options.folder } : {}),
|
|
33
|
-
...(after ? { after } : {}),
|
|
34
|
-
...(options.pageSize ? { limit: String(validPageSize(options.pageSize)) } : {})
|
|
35
|
-
});
|
|
36
|
-
let page;
|
|
37
|
-
while (true) {
|
|
38
|
-
try {
|
|
39
|
-
page = await this.request("GET", `?${query.toString()}`, undefined, signal);
|
|
40
|
-
break;
|
|
41
|
-
}
|
|
42
|
-
catch (error) {
|
|
43
|
-
const normalized = normalizeFileError(error);
|
|
44
|
-
if (normalized.code !== "file_index_warming")
|
|
45
|
-
throw normalized;
|
|
46
|
-
options.onIndexing?.();
|
|
47
|
-
await abortableDelay(500, signal);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
if (page.protocol_version !== 1 || page.type !== "files_page" || !Array.isArray(page.files)) {
|
|
51
|
-
throw connectError("invalid_operation_response", "The authority returned an invalid file page.");
|
|
52
|
-
}
|
|
53
|
-
for (const file of page.files) {
|
|
54
|
-
throwIfAborted(signal);
|
|
55
|
-
yield clientFileDescriptor(file);
|
|
56
|
-
}
|
|
57
|
-
after = page.next;
|
|
58
|
-
} while (after);
|
|
59
|
-
}
|
|
60
|
-
finally {
|
|
61
|
-
budget.dispose();
|
|
62
|
-
}
|
|
26
|
+
list(options = {}) {
|
|
27
|
+
return listFiles(this.capability, this.request, options, this.timeouts.fileIndexMs);
|
|
28
|
+
}
|
|
29
|
+
/** Current metadata lookup; requires file list approval, not file byte access. */
|
|
30
|
+
stat(target, options = {}) {
|
|
31
|
+
return statFile(this.capability, this.request, this.supportsAuthorityFeature, options => this.list(options), target, options, this.timeouts.fileIndexMs);
|
|
63
32
|
}
|
|
64
33
|
async upload(path, source, options = {}) {
|
|
65
34
|
return withCooperativeRequestBudget(options, this.timeouts.uploadMs, (budget) => this.uploadWithinBudget(path, source, { ...options, signal: budget.signal, timeoutMs: null }));
|
|
@@ -220,23 +189,9 @@ export class MdbaseFileClient {
|
|
|
220
189
|
}
|
|
221
190
|
}
|
|
222
191
|
async download(file, options = {}) {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
}
|
|
226
|
-
const stream = await this.downloadStream(file, options);
|
|
227
|
-
const chunks = [];
|
|
228
|
-
const reader = stream.getReader();
|
|
229
|
-
while (true) {
|
|
230
|
-
const result = await reader.read();
|
|
231
|
-
if (result.done)
|
|
232
|
-
break;
|
|
233
|
-
chunks.push(result.value);
|
|
234
|
-
}
|
|
235
|
-
return new Blob(chunks.map((chunk) => {
|
|
236
|
-
const copy = new Uint8Array(chunk.byteLength);
|
|
237
|
-
copy.set(chunk);
|
|
238
|
-
return copy.buffer;
|
|
239
|
-
}), { type: file.mediaType ?? "" });
|
|
192
|
+
const bytes = await this.downloadBytes(file, options);
|
|
193
|
+
// downloadBytes owns a full, non-shared ArrayBuffer; Blob snapshots it.
|
|
194
|
+
return new Blob([bytes.buffer], { type: file.mediaType ?? "" });
|
|
240
195
|
}
|
|
241
196
|
/** Download and verify a file with network backpressure and bounded buffering. */
|
|
242
197
|
async downloadStream(file, options = {}) {
|
|
@@ -416,7 +371,38 @@ export class MdbaseFileClient {
|
|
|
416
371
|
});
|
|
417
372
|
}
|
|
418
373
|
async downloadBytes(file, options = {}) {
|
|
419
|
-
|
|
374
|
+
if (file.size > MAX_BUFFERED_DOWNLOAD_BYTES) {
|
|
375
|
+
throw connectError("invalid_request", `download() buffers at most ${MAX_BUFFERED_DOWNLOAD_BYTES} bytes; use downloadStream() for larger files.`);
|
|
376
|
+
}
|
|
377
|
+
const stream = await this.downloadStream(file, options);
|
|
378
|
+
const bytes = new Uint8Array(file.size);
|
|
379
|
+
const reader = stream.getReader();
|
|
380
|
+
let offset = 0;
|
|
381
|
+
try {
|
|
382
|
+
while (true) {
|
|
383
|
+
const result = await reader.read();
|
|
384
|
+
// Reading through EOF is essential: hosted streams verify their digest
|
|
385
|
+
// only after the final range ends, not when the last bytes arrive.
|
|
386
|
+
if (result.done)
|
|
387
|
+
break;
|
|
388
|
+
if (result.value.byteLength > bytes.byteLength - offset) {
|
|
389
|
+
throw connectError("invalid_operation_response", "Downloaded file bytes failed integrity verification.");
|
|
390
|
+
}
|
|
391
|
+
bytes.set(result.value, offset);
|
|
392
|
+
offset += result.value.byteLength;
|
|
393
|
+
}
|
|
394
|
+
if (offset !== bytes.byteLength) {
|
|
395
|
+
throw connectError("invalid_operation_response", "Downloaded file bytes failed integrity verification.");
|
|
396
|
+
}
|
|
397
|
+
return bytes;
|
|
398
|
+
}
|
|
399
|
+
catch (error) {
|
|
400
|
+
await reader.cancel().catch(() => undefined);
|
|
401
|
+
throw error;
|
|
402
|
+
}
|
|
403
|
+
finally {
|
|
404
|
+
reader.releaseLock();
|
|
405
|
+
}
|
|
420
406
|
}
|
|
421
407
|
async move(file, path, options = {}) {
|
|
422
408
|
return withCooperativeRequestBudget(options, this.timeouts.requestMs, (budget) => this.moveWithinBudget(file, path, { ...options, signal: budget.signal, timeoutMs: null }));
|
|
@@ -585,15 +571,3 @@ export class MdbaseFileClient {
|
|
|
585
571
|
await this.request("DELETE", `transfers/${encodeURIComponent(transferId)}`).catch(() => undefined);
|
|
586
572
|
}
|
|
587
573
|
}
|
|
588
|
-
function clientFileDescriptor(file) {
|
|
589
|
-
return {
|
|
590
|
-
fileId: file.file_id,
|
|
591
|
-
path: file.path,
|
|
592
|
-
revision: file.revision,
|
|
593
|
-
contentDigest: file.content_digest,
|
|
594
|
-
size: file.size,
|
|
595
|
-
...(file.media_type ? { mediaType: file.media_type } : {}),
|
|
596
|
-
mediaClass: file.media_class,
|
|
597
|
-
modifiedAt: file.modified_at
|
|
598
|
-
};
|
|
599
|
-
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { normalizeCollectionChange } from "./change-events.js";
|
|
1
2
|
export { MdbaseApplicationSession, MdbaseMemoryVerificationStore } from "./application-session.js";
|
|
2
3
|
export type { MdbaseApplicationSessionOptions, MdbaseApplicationVerificationStore, MdbaseDefinitionUpdate, MdbaseCollectionSetupUpdate, MdbaseApplicationSessionSnapshot } from "./application-session.js";
|
|
3
4
|
export type { MdbaseConnectOptions, MdbaseConnectTimeouts, MdbaseFrontmatter } from "./connect-options.js";
|
|
@@ -18,11 +19,13 @@ export type { MdbaseRecordResolution, MdbaseRecordSessionOptions, MdbaseRecordSe
|
|
|
18
19
|
export { MdbasePeopleClient, PERSON_CONTRACT, sameIdentity } from "./people-client.js";
|
|
19
20
|
export type { CurrentAccount, InvalidPersonRecord, PeopleDirectory, PeopleDirectoryOptions, PersonRecord, PersonResolution } from "./people-client.js";
|
|
20
21
|
export type { AccountIdentity, AccountProfile, CollectionMemberProfile, ApplicationPeopleRequirement, PeoplePermission } from "@mdbase-dev/connect-protocol";
|
|
21
|
-
export type { MdbaseFileSource, CollectionFileDescriptor, MdbaseFileDeleteReceipt, MdbaseFileStreamSource, MdbaseFileProgress, MdbaseFileListOptions, MdbaseFileUploadOptions, MdbaseFileStreamUploadOptions, MdbaseFileDownloadOptions, MdbaseFileMoveOptions, MdbaseFileDeleteOptions } from "./files.js";
|
|
22
|
+
export type { MdbaseFileSource, CollectionFileDescriptor, MdbaseFileDeleteReceipt, MdbaseFileStreamSource, MdbaseFileProgress, MdbaseFileListOptions, MdbaseFileStatTarget, MdbaseFileUploadOptions, MdbaseFileStreamUploadOptions, MdbaseFileDownloadOptions, MdbaseFileMoveOptions, MdbaseFileDeleteOptions } from "./files.js";
|
|
22
23
|
export { parseMdbasePushPayload, parseMdbaseNativeNotificationData, showMdbasePushNotification } from "./notifications.js";
|
|
23
24
|
export type { MdbaseNotificationRegistrationOptions, MdbaseNotificationRegistration, MdbaseNativeNotificationRegistrationOptions, MdbaseNativeNotificationRegistration, MdbaseNativeNotificationData, MdbasePushPayload } from "./notifications.js";
|
|
24
25
|
export type { ConnectSuccess, ConnectFailure, ConnectOutcome, CommonOperationProblemCode, RegistrationProblemCode, AuthorizationProblemCode, NotificationProblemCode, DirectAccessProblemCode, SessionProblemCode, CollectionSetupProblemCode, CollectionDescriptionProblemCode, CollectionReadProblemCode, CollectionQueryProblemCode, CollectionMutationProblemCode, CollectionChangesProblemCode, CollectionTypeProblemCode } from "./outcomes.js";
|
|
25
|
-
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, SavedViewPage, SavedViewPagesOptions, 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";
|
|
26
|
+
export type { MdbaseDesiredTimer, MdbaseTimer, MdbaseTimerList, MdbaseTimerReconciliation, ReadInput, ReadManyOptions, ReadManyRecord, ReadManyEntry, ReadManyBatchError, ReadManyResult, DataContractSelector, CollectionFileMetadata, DataContractViewIdentity, QueryRecord, RecordDocument, QueryInput, QueryMetadataInput, QueryMetadataRecord, QueryMetadataResult, QueryMetadataPage, 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, SavedViewPage, SavedViewPagesOptions, 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, RecordChangeMetadata, DescribeOptions, CollectionChangesPage, CollectionDescription, CollectionContractDescriptor, CollectionContractImplementationDescriptor, CollectionTypeDescriptor } from "./operation-types.js";
|
|
27
|
+
export { linksTo } from "./query-predicates.js";
|
|
28
|
+
export type { LinksToOptions } from "./query-predicates.js";
|
|
26
29
|
export { externalStore } from "./external-store.js";
|
|
27
30
|
export type { MdbaseExternalStore } from "./external-store.js";
|
|
28
31
|
export { MdbaseBrowserSelection, MdbaseMemorySelection } from "./selection.js";
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { normalizeCollectionChange } from "./change-events.js";
|
|
1
2
|
export { MdbaseApplicationSession, MdbaseMemoryVerificationStore } from "./application-session.js";
|
|
2
3
|
export { MdbaseConnect } from "./mdbase-connect.js";
|
|
3
4
|
export { MdbaseConnection } from "./connection.js";
|
|
@@ -8,6 +9,7 @@ export { MdbaseRecords } from "./records.js";
|
|
|
8
9
|
export { MdbaseRecordSession } from "./record-session.js";
|
|
9
10
|
export { MdbasePeopleClient, PERSON_CONTRACT, sameIdentity } from "./people-client.js";
|
|
10
11
|
export { parseMdbasePushPayload, parseMdbaseNativeNotificationData, showMdbasePushNotification } from "./notifications.js";
|
|
12
|
+
export { linksTo } from "./query-predicates.js";
|
|
11
13
|
export { externalStore } from "./external-store.js";
|
|
12
14
|
export { MdbaseBrowserSelection, MdbaseMemorySelection } from "./selection.js";
|
|
13
15
|
export { MDBASE_RECORD_CREATED_CONTRACT, MDBASE_TIMER_FIRED_CONTRACT, operationsForApplicationCapabilities } from "@mdbase-dev/connect-protocol";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { decodeFileFrame, FILE_PROTOCOL_VERSION, FILE_TRANSFER_PROTOCOL_VERSION } from "@mdbase-dev/connect-protocol";
|
|
1
|
+
import { decodeFileFrame, isMutatingOperation, FILE_PROTOCOL_VERSION, FILE_TRANSFER_PROTOCOL_VERSION } from "@mdbase-dev/connect-protocol";
|
|
2
2
|
import { decryptRelayResponse, encryptRelayRequest } from "./crypto.js";
|
|
3
3
|
import { MdbaseConnectError, connectError, serverConnectError } from "./errors.js";
|
|
4
4
|
import { GrantFileTransferCipher } from "./file-crypto.js";
|
|
@@ -51,7 +51,7 @@ export class LocalFileTransport {
|
|
|
51
51
|
if (response)
|
|
52
52
|
this.options.onDirectUnavailable();
|
|
53
53
|
}
|
|
54
|
-
if (triedDirect &&
|
|
54
|
+
if (triedDirect && !isMutatingOperation("file_control", controlInput)) {
|
|
55
55
|
token = token.expiresAt > Date.now() + 30_000
|
|
56
56
|
? token
|
|
57
57
|
: await this.options.refreshAuthorization(signal);
|
|
@@ -81,7 +81,7 @@ export class LocalFileTransport {
|
|
|
81
81
|
return this.controlAttempt(token, method, path, input, signal, freshReadRetried, connectorBusyRetries + 1, leases);
|
|
82
82
|
}
|
|
83
83
|
if (error.code === "fresh_request_required"
|
|
84
|
-
&& method
|
|
84
|
+
&& !isMutatingOperation("file_control", localFileControlInput(method, path, input))
|
|
85
85
|
&& !freshReadRetried) {
|
|
86
86
|
return this.controlAttempt(token, method, path, input, signal, true, connectorBusyRetries, leases);
|
|
87
87
|
}
|
|
@@ -94,7 +94,7 @@ export class LocalFileTransport {
|
|
|
94
94
|
const decrypted = await decryptRelayResponse(this.options.keyStore, token.keyHandle, relayBinding(token), encryptedRequest, encryptedResponse);
|
|
95
95
|
if (!decrypted.ok
|
|
96
96
|
&& decrypted.problem.code === "fresh_request_required"
|
|
97
|
-
&& method
|
|
97
|
+
&& !isMutatingOperation("file_control", localFileControlInput(method, path, input))
|
|
98
98
|
&& !freshReadRetried) {
|
|
99
99
|
return this.controlAttempt(token, method, path, input, signal, true, connectorBusyRetries, leases);
|
|
100
100
|
}
|
|
@@ -407,19 +407,21 @@ function localFileControlInput(method, path, input) {
|
|
|
407
407
|
return transferControl("abort_file_transfer", segments[1]);
|
|
408
408
|
}
|
|
409
409
|
if (method === "POST" && isRecord(input)) {
|
|
410
|
-
const expectedType = path === "
|
|
411
|
-
? "
|
|
412
|
-
: path === "
|
|
413
|
-
? "
|
|
414
|
-
:
|
|
415
|
-
? "
|
|
416
|
-
: segments.length ===
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
: segments.length === 2 && segments[1] === "delete"
|
|
410
|
+
const expectedType = path === "stat"
|
|
411
|
+
? "stat_file"
|
|
412
|
+
: path === "uploads"
|
|
413
|
+
? "open_file_upload"
|
|
414
|
+
: path === "downloads"
|
|
415
|
+
? "open_file_download"
|
|
416
|
+
: segments.length === 3 && segments[0] === "uploads" && segments[2] === "commit"
|
|
417
|
+
? "commit_file_upload"
|
|
418
|
+
: segments.length === 2 && segments[1] === "move"
|
|
420
419
|
&& input.file_id === decodeURIComponent(segments[0])
|
|
421
|
-
? "
|
|
422
|
-
:
|
|
420
|
+
? "move_file"
|
|
421
|
+
: segments.length === 2 && segments[1] === "delete"
|
|
422
|
+
&& input.file_id === decodeURIComponent(segments[0])
|
|
423
|
+
? "delete_file"
|
|
424
|
+
: null;
|
|
423
425
|
if (expectedType && input.type === expectedType)
|
|
424
426
|
return { ...input };
|
|
425
427
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CollectionOperation, ConnectProblem, ContractRequirement, JsonObject, MutationOperationIdentifier, TypePackProvision } from "@mdbase-dev/connect-protocol";
|
|
1
|
+
import type { CollectionChange as WireCollectionChange, CollectionChangesPage as WireCollectionChangesPage, CollectionOperation, ConnectProblem, ContractRequirement, JsonObject, MutationOperationIdentifier, TypePackProvision } from "@mdbase-dev/connect-protocol";
|
|
2
2
|
export interface MdbaseDesiredTimer {
|
|
3
3
|
/** Stable within the timer namespace. */
|
|
4
4
|
id: string;
|
|
@@ -53,6 +53,8 @@ export interface DataContractViewIdentity {
|
|
|
53
53
|
}
|
|
54
54
|
export interface QueryRecord<Frontmatter extends JsonObject = JsonObject> {
|
|
55
55
|
path: string;
|
|
56
|
+
/** Exact-source token when the authority supports query-record-revisions-v1. */
|
|
57
|
+
revision?: string;
|
|
56
58
|
frontmatter?: Frontmatter;
|
|
57
59
|
effectiveFrontmatter?: Frontmatter;
|
|
58
60
|
body?: string;
|
|
@@ -64,6 +66,41 @@ export interface QueryRecord<Frontmatter extends JsonObject = JsonObject> {
|
|
|
64
66
|
values?: JsonObject;
|
|
65
67
|
contract?: DataContractViewIdentity;
|
|
66
68
|
}
|
|
69
|
+
/** Query-shaped projection; qualified document batches supply exact-source revisions. */
|
|
70
|
+
export type ReadManyRecord<Frontmatter extends JsonObject = JsonObject> = QueryRecord<Frontmatter>;
|
|
71
|
+
export interface ReadManyOptions extends ConnectRequestOptions {
|
|
72
|
+
includeBody?: boolean;
|
|
73
|
+
frontmatterMode?: QueryInput["frontmatterMode"];
|
|
74
|
+
/** Restrict matching records to these raw collection types, not a contract view. */
|
|
75
|
+
types?: string[];
|
|
76
|
+
/** Unique paths per batch, default 100; range 1..1,000. Document authorities cap at 100. */
|
|
77
|
+
batchSize?: number;
|
|
78
|
+
/** Independent batches in flight, default 4; range 1..4. Cursor pages stay serial. */
|
|
79
|
+
concurrency?: number;
|
|
80
|
+
}
|
|
81
|
+
export type ReadManyEntry<Frontmatter extends JsonObject = JsonObject> = {
|
|
82
|
+
status: "found";
|
|
83
|
+
path: string;
|
|
84
|
+
record: ReadManyRecord<Frontmatter>;
|
|
85
|
+
} | {
|
|
86
|
+
status: "missing";
|
|
87
|
+
path: string;
|
|
88
|
+
} | {
|
|
89
|
+
status: "error";
|
|
90
|
+
path: string;
|
|
91
|
+
batch: number;
|
|
92
|
+
};
|
|
93
|
+
export interface ReadManyBatchError {
|
|
94
|
+
batch: number;
|
|
95
|
+
paths: string[];
|
|
96
|
+
failure: import("./outcomes.js").ConnectFailure<import("./outcomes.js").CollectionQueryProblemCode>;
|
|
97
|
+
}
|
|
98
|
+
export interface ReadManyResult<Frontmatter extends JsonObject = JsonObject> {
|
|
99
|
+
/** One entry per input path, in input order, including duplicates. */
|
|
100
|
+
results: ReadManyEntry<Frontmatter>[];
|
|
101
|
+
/** Batch failures cover the entire batch; semantic item failures cover only their paths. */
|
|
102
|
+
errors: ReadManyBatchError[];
|
|
103
|
+
}
|
|
67
104
|
export interface RecordDocument<Frontmatter extends JsonObject = JsonObject> {
|
|
68
105
|
path: string;
|
|
69
106
|
revision: string;
|
|
@@ -107,6 +144,8 @@ export interface CollectionContractDescriptor {
|
|
|
107
144
|
implementations: CollectionContractImplementationDescriptor[];
|
|
108
145
|
}
|
|
109
146
|
export interface CollectionDescription {
|
|
147
|
+
/** Immutable authority implementation features; never a permission claim. */
|
|
148
|
+
authorityCapabilities?: readonly string[];
|
|
110
149
|
protocolVersion: 1;
|
|
111
150
|
collectionId: string;
|
|
112
151
|
displayName: string;
|
|
@@ -139,6 +178,7 @@ export interface QuerySummary {
|
|
|
139
178
|
}
|
|
140
179
|
/** Application-facing form of the canonical mdbase v0.3 query schema. */
|
|
141
180
|
export interface QueryInput {
|
|
181
|
+
output?: never;
|
|
142
182
|
/**
|
|
143
183
|
* Queries using a semantic contract view accept only `types`, `timezone`,
|
|
144
184
|
* pagination, `frontmatterMode`, and `contract`; filter normalized fields in
|
|
@@ -172,8 +212,26 @@ export interface QueryInput {
|
|
|
172
212
|
/** Select one exact semantic contract/provider view for this query. */
|
|
173
213
|
contract?: DataContractSelector;
|
|
174
214
|
}
|
|
175
|
-
export
|
|
176
|
-
|
|
215
|
+
export type QueryMetadataInput = Omit<QueryInput, "output" | "includeBody"> & {
|
|
216
|
+
output: "metadata";
|
|
217
|
+
includeBody?: false;
|
|
218
|
+
};
|
|
219
|
+
/** Partial metadata is not a record document; revision and selected values are required. */
|
|
220
|
+
export interface QueryMetadataRecord {
|
|
221
|
+
path: string;
|
|
222
|
+
types: string[];
|
|
223
|
+
revision: string;
|
|
224
|
+
values: JsonObject;
|
|
225
|
+
contract?: DataContractViewIdentity;
|
|
226
|
+
}
|
|
227
|
+
export type QueryMetadataResult = QueryResult<JsonObject, QueryMetadataRecord> & {
|
|
228
|
+
output: "metadata";
|
|
229
|
+
};
|
|
230
|
+
export type QueryMetadataPage = QueryPage<JsonObject, QueryMetadataRecord> & {
|
|
231
|
+
output: "metadata";
|
|
232
|
+
};
|
|
233
|
+
export interface QueryResult<Record extends JsonObject = JsonObject, Row = QueryRecord<Record>> {
|
|
234
|
+
results: Row[];
|
|
177
235
|
meta?: {
|
|
178
236
|
/** Exact total when it is available within the page summary budget. */
|
|
179
237
|
totalCount?: number;
|
|
@@ -188,26 +246,30 @@ export interface QueryResult<Record extends JsonObject = JsonObject> {
|
|
|
188
246
|
snapshot?: string;
|
|
189
247
|
};
|
|
190
248
|
}
|
|
191
|
-
export interface QueryPagesOptions<Record extends JsonObject = JsonObject
|
|
192
|
-
/** Initial page size. Cursor
|
|
249
|
+
export interface QueryPagesOptions<Record extends JsonObject = JsonObject, Row = QueryRecord<Record>> {
|
|
250
|
+
/** Initial page size. Cursor continuations use this pinned size. */
|
|
193
251
|
firstPageSize?: number;
|
|
194
|
-
/**
|
|
252
|
+
/** Initial cursor size (default 200), unless firstPageSize is set. Offset continuations use this size (default 1,000). Authorities may cap it. */
|
|
195
253
|
pageSize?: number;
|
|
254
|
+
/** Total rows to deliver, not a page size. Zero makes no data requests. */
|
|
255
|
+
maxResults?: number;
|
|
196
256
|
signal?: AbortSignal;
|
|
197
257
|
/** Independent budget for each page requested by this caller-driven iterator. */
|
|
198
258
|
pageTimeoutMs?: number | null;
|
|
199
259
|
coordination?: RequestCoordinationOptions;
|
|
200
|
-
onProgress?: (page: QueryPage<Record>) => void;
|
|
260
|
+
onProgress?: (page: QueryPage<Record, Row>) => void;
|
|
201
261
|
}
|
|
202
|
-
export interface QueryAllOptions<Record extends JsonObject = JsonObject
|
|
203
|
-
/** Initial page size. Cursor
|
|
262
|
+
export interface QueryAllOptions<Record extends JsonObject = JsonObject, Row = QueryRecord<Record>> extends ConnectRequestOptions {
|
|
263
|
+
/** Initial page size. Cursor continuations use this pinned size. */
|
|
204
264
|
firstPageSize?: number;
|
|
205
|
-
/**
|
|
265
|
+
/** Initial cursor size (default 200), unless firstPageSize is set. Offset continuations use this size (default 1,000). Authorities may cap it. */
|
|
206
266
|
pageSize?: number;
|
|
207
|
-
|
|
267
|
+
/** Total rows to return, independent of page size. */
|
|
268
|
+
maxResults?: number;
|
|
269
|
+
onProgress?: (page: QueryPage<Record, Row>) => void;
|
|
208
270
|
}
|
|
209
|
-
export interface QueryPage<Record extends JsonObject = JsonObject
|
|
210
|
-
results: QueryResult<Record>["results"];
|
|
271
|
+
export interface QueryPage<Record extends JsonObject = JsonObject, Row = QueryRecord<Record>> {
|
|
272
|
+
results: QueryResult<Record, Row>["results"];
|
|
211
273
|
meta?: QueryResult<Record>["meta"];
|
|
212
274
|
page: number;
|
|
213
275
|
offset: number;
|
|
@@ -404,8 +466,12 @@ export interface ExecuteViewInput {
|
|
|
404
466
|
render?: boolean;
|
|
405
467
|
}
|
|
406
468
|
export interface SavedViewPagesOptions<Frontmatter extends JsonObject = JsonObject> {
|
|
469
|
+
/** Initial page size, pinned for cursor continuations. */
|
|
407
470
|
firstPageSize?: number;
|
|
471
|
+
/** Initial cursor size (default 200), unless firstPageSize is set. Offset continuations use this size (default 1,000). Authorities may cap it. */
|
|
408
472
|
pageSize?: number;
|
|
473
|
+
/** Total rows to deliver, not a page size. Zero makes no data requests. */
|
|
474
|
+
maxResults?: number;
|
|
409
475
|
signal?: AbortSignal;
|
|
410
476
|
/** Independent budget for each page requested by this caller-driven iterator. */
|
|
411
477
|
pageTimeoutMs?: number | null;
|
|
@@ -685,12 +751,87 @@ export interface MdbaseWatchSubscription {
|
|
|
685
751
|
subscribe(listener: (change: CollectionChange) => void, onStatus?: (status: WatchStatus) => void, onProblem?: (problem: ConnectProblem) => void): () => void;
|
|
686
752
|
close(): void;
|
|
687
753
|
}
|
|
688
|
-
export interface
|
|
754
|
+
export interface DescribeOptions extends ConnectRequestOptions {
|
|
755
|
+
/** Bypass the settled description cache (concurrent requests still share a load). */
|
|
756
|
+
fresh?: boolean;
|
|
757
|
+
}
|
|
758
|
+
interface ChangeBase {
|
|
689
759
|
cursor: number;
|
|
760
|
+
/** Original authority event ID. Prefer kind for application logic. */
|
|
690
761
|
type: string;
|
|
691
762
|
occurredAt: string;
|
|
692
763
|
payload: JsonObject;
|
|
764
|
+
/** Unmodified wire event, including authority-specific metadata. */
|
|
765
|
+
raw: WireCollectionChange;
|
|
766
|
+
}
|
|
767
|
+
export interface RecordChangeMetadata {
|
|
768
|
+
revision?: string;
|
|
769
|
+
previousRevision?: string;
|
|
770
|
+
types?: string[];
|
|
771
|
+
previousTypes?: string[];
|
|
772
|
+
/** Authority field identifiers: local JSON Pointers, hosted top-level keys. Preserved, not guessed. */
|
|
773
|
+
changedFields?: string[];
|
|
774
|
+
bodyChanged?: boolean;
|
|
775
|
+
/** Hosted authorities may supply frontmatter; local authorities need not. */
|
|
776
|
+
before?: JsonObject;
|
|
777
|
+
after?: JsonObject;
|
|
778
|
+
}
|
|
779
|
+
interface ResourceChangeBase extends ChangeBase {
|
|
780
|
+
path?: string;
|
|
781
|
+
name?: string;
|
|
782
|
+
revision?: string;
|
|
783
|
+
previousRevision?: string;
|
|
693
784
|
}
|
|
785
|
+
export type CollectionChange = (ChangeBase & RecordChangeMetadata & {
|
|
786
|
+
kind: "record.created";
|
|
787
|
+
path: string;
|
|
788
|
+
}) | (ChangeBase & RecordChangeMetadata & {
|
|
789
|
+
kind: "record.updated";
|
|
790
|
+
path: string;
|
|
791
|
+
}) | (ChangeBase & RecordChangeMetadata & {
|
|
792
|
+
kind: "record.deleted";
|
|
793
|
+
path: string;
|
|
794
|
+
}) | (ChangeBase & RecordChangeMetadata & {
|
|
795
|
+
kind: "record.renamed";
|
|
796
|
+
from: string;
|
|
797
|
+
to: string;
|
|
798
|
+
}) | (ResourceChangeBase & {
|
|
799
|
+
kind: "schema.changed";
|
|
800
|
+
}) | (ResourceChangeBase & {
|
|
801
|
+
kind: "config.changed";
|
|
802
|
+
}) | (ResourceChangeBase & {
|
|
803
|
+
kind: "contract.changed";
|
|
804
|
+
}) | (ResourceChangeBase & {
|
|
805
|
+
kind: "view.changed";
|
|
806
|
+
}) | (ChangeBase & {
|
|
807
|
+
kind: "file.changed";
|
|
808
|
+
path: string;
|
|
809
|
+
revision?: string;
|
|
810
|
+
previousRevision?: string;
|
|
811
|
+
}) | (ChangeBase & {
|
|
812
|
+
kind: "file.put";
|
|
813
|
+
file: import("./file-descriptor.js").CollectionFileDescriptor;
|
|
814
|
+
}) | (ChangeBase & {
|
|
815
|
+
kind: "file.removed";
|
|
816
|
+
fileId: string;
|
|
817
|
+
previousPath?: string;
|
|
818
|
+
revision?: string;
|
|
819
|
+
}) | (ChangeBase & {
|
|
820
|
+
kind: "gap";
|
|
821
|
+
reason?: string;
|
|
822
|
+
}) | (ChangeBase & {
|
|
823
|
+
kind: "unknown";
|
|
824
|
+
reason: "unrecognized_type" | "invalid_payload";
|
|
825
|
+
})
|
|
826
|
+
/** Synthesized from a reset page, not a new authority event ID. Watch still terminates with change_cursor_reset. */
|
|
827
|
+
| {
|
|
828
|
+
kind: "reset";
|
|
829
|
+
cursor: number;
|
|
830
|
+
type: "reset";
|
|
831
|
+
occurredAt: null;
|
|
832
|
+
payload: JsonObject;
|
|
833
|
+
raw: WireCollectionChangesPage;
|
|
834
|
+
};
|
|
694
835
|
export interface CollectionChangesPage {
|
|
695
836
|
events: CollectionChange[];
|
|
696
837
|
cursor: number;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Owns the latest single-use cursor, including while an iterator is paused at yield. */
|
|
2
|
+
export declare function queryCursorLease(release: (cursor: string) => Promise<void>, signal?: AbortSignal, initialCursor?: string): {
|
|
3
|
+
startRequest(): void;
|
|
4
|
+
finishRequest(nextCursor?: string): void;
|
|
5
|
+
dispose(): void;
|
|
6
|
+
};
|
|
7
|
+
export declare function resultCap(value?: number): number;
|
|
8
|
+
export declare function nonNegativeInteger(value: unknown, fallback: number): number;
|
|
9
|
+
export declare function positiveInteger(value: unknown, fallback: number): number;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/** Owns the latest single-use cursor, including while an iterator is paused at yield. */
|
|
2
|
+
export function queryCursorLease(release, signal, initialCursor) {
|
|
3
|
+
let cursor = initialCursor;
|
|
4
|
+
let inFlight = false;
|
|
5
|
+
const close = () => {
|
|
6
|
+
if (!cursor)
|
|
7
|
+
return;
|
|
8
|
+
const owned = cursor;
|
|
9
|
+
cursor = undefined;
|
|
10
|
+
// Collection cleanup has an independent bounded budget, never the data signal.
|
|
11
|
+
void release(owned).catch(() => undefined);
|
|
12
|
+
};
|
|
13
|
+
const abort = () => {
|
|
14
|
+
// A consumed token can rotate while the request settles. Release the returned
|
|
15
|
+
// token instead; a paused iterator has no request and closes immediately.
|
|
16
|
+
if (!inFlight)
|
|
17
|
+
close();
|
|
18
|
+
};
|
|
19
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
20
|
+
return {
|
|
21
|
+
startRequest() { inFlight = true; },
|
|
22
|
+
finishRequest(nextCursor) {
|
|
23
|
+
if (nextCursor)
|
|
24
|
+
cursor = nextCursor;
|
|
25
|
+
inFlight = false;
|
|
26
|
+
if (signal?.aborted)
|
|
27
|
+
close();
|
|
28
|
+
},
|
|
29
|
+
dispose() {
|
|
30
|
+
signal?.removeEventListener("abort", abort);
|
|
31
|
+
close();
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export function resultCap(value) {
|
|
36
|
+
if (value === undefined)
|
|
37
|
+
return Infinity;
|
|
38
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
39
|
+
throw new TypeError("maxResults must be a non-negative safe integer.");
|
|
40
|
+
}
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
export function nonNegativeInteger(value, fallback) {
|
|
44
|
+
return typeof value === "number" && Number.isFinite(value)
|
|
45
|
+
? Math.max(0, Math.floor(value))
|
|
46
|
+
: fallback;
|
|
47
|
+
}
|
|
48
|
+
export function positiveInteger(value, fallback) {
|
|
49
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 1
|
|
50
|
+
? Math.floor(value)
|
|
51
|
+
: fallback;
|
|
52
|
+
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { JsonObject } from "@mdbase-dev/connect-protocol";
|
|
2
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
|
|
5
|
-
|
|
3
|
+
import type { ConnectRequestOptions, QueryInput, QueryMetadataInput, QueryRecord, QueryPage, QueryPagesOptions, QueryResult } from "./operation-types.js";
|
|
4
|
+
type QueryOperation<Frontmatter extends JsonObject, Row> = (input: QueryInput | QueryMetadataInput, options?: ConnectRequestOptions) => Promise<ConnectOutcome<QueryResult<Frontmatter, Row> & {
|
|
5
|
+
output?: "metadata";
|
|
6
|
+
}, CollectionQueryProblemCode>>;
|
|
7
|
+
export declare function coordinatedQueryPages<Frontmatter extends JsonObject, Row = QueryRecord<Frontmatter>>(query: QueryOperation<Frontmatter, Row>, releaseQueryCursor: (cursor: string) => Promise<void>, input?: QueryInput | QueryMetadataInput, options?: QueryPagesOptions<Frontmatter, Row>): AsyncGenerator<ConnectOutcome<QueryPage<Frontmatter, Row> & {
|
|
8
|
+
output?: "metadata";
|
|
9
|
+
}, CollectionQueryProblemCode>>;
|
|
6
10
|
export {};
|