@mdbase-dev/connect 0.1.0-beta.32 → 0.1.0-beta.34
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 +37 -15
- package/dist/advanced.d.ts +2 -0
- package/dist/advanced.js +2 -0
- package/dist/application-session.d.ts +31 -11
- package/dist/application-session.js +188 -73
- 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 +9 -6
- package/dist/collection-client.js +522 -77
- package/dist/connect-authorization-helpers.d.ts +3 -0
- package/dist/connect-authorization-helpers.js +13 -0
- package/dist/connect.js +4 -6
- package/dist/connection.d.ts +7 -6
- package/dist/connection.js +103 -89
- package/dist/crypto-entry.d.ts +6 -3
- package/dist/crypto-entry.js +3 -3
- package/dist/file-transfer-internals.d.ts +2 -1
- package/dist/file-transfer-internals.js +38 -0
- package/dist/files.d.ts +20 -2
- package/dist/files.js +43 -19
- package/dist/index.d.ts +24 -16
- package/dist/index.js +10 -14
- package/dist/internal-types.d.ts +1 -0
- package/dist/operation-helpers.js +5 -5
- package/dist/operation-types.d.ts +404 -39
- package/dist/session.d.ts +1 -1
- package/dist/session.js +2 -1
- package/dist/watch-policy.d.ts +8 -0
- package/dist/watch-policy.js +12 -0
- package/package.json +3 -3
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { MdbaseConnectError, connectError } from "./errors.js";
|
|
2
|
+
export function declarationIdFromFamilyIdentity(familyIdentity) {
|
|
3
|
+
const prefix = "bundle:";
|
|
4
|
+
if (!familyIdentity.startsWith(prefix) || familyIdentity.length === prefix.length) {
|
|
5
|
+
throw new Error("The registered application has no valid declaration identity.");
|
|
6
|
+
}
|
|
7
|
+
return familyIdentity.slice(prefix.length);
|
|
8
|
+
}
|
|
9
|
+
export function authorizationAbort(signal, message, cause) {
|
|
10
|
+
if (signal.reason instanceof MdbaseConnectError)
|
|
11
|
+
return signal.reason;
|
|
12
|
+
return connectError("authorization_cancelled", message, { cause });
|
|
13
|
+
}
|
package/dist/connect.js
CHANGED
|
@@ -3,8 +3,9 @@ import { abortableDelay } from "./async.js";
|
|
|
3
3
|
import { randomBase64Url } from "./base64.js";
|
|
4
4
|
import { ApplicationIdentityStoreError, IndexedDbApplicationIdentityStore, MemoryApplicationIdentityStore, applicationIdentity, applicationInstallationId, signApplicationAuthorization } from "./application-identity.js";
|
|
5
5
|
import { MdbaseConnection } from "./connection.js";
|
|
6
|
+
import { authorizationAbort, declarationIdFromFamilyIdentity } from "./connect-authorization-helpers.js";
|
|
6
7
|
import { IndexedDbGrantKeyStore, MemoryGrantKeyStore } from "./crypto.js";
|
|
7
|
-
import {
|
|
8
|
+
import { connectError, serverConnectError } from "./errors.js";
|
|
8
9
|
import { DEFAULT_OPERATIONS } from "./internal-types.js";
|
|
9
10
|
import { uniqueOperations } from "./operation-helpers.js";
|
|
10
11
|
import { resolveConnectTimeouts, withRequestBudget } from "./request-budget.js";
|
|
@@ -197,6 +198,7 @@ export class MdbaseConnectInternals {
|
|
|
197
198
|
protocol_version: APPLICATION_AUTHORIZATION_PROTOCOL_VERSION,
|
|
198
199
|
authorization_id: authorizationId,
|
|
199
200
|
application_id: application.id,
|
|
201
|
+
application_declaration_id: declarationIdFromFamilyIdentity(application.family_identity),
|
|
200
202
|
application_manifest_digest: application.manifest_digest,
|
|
201
203
|
application_installation_id: await applicationInstallationId(installation),
|
|
202
204
|
installation_signing_public_key: installation.signingPublicKey,
|
|
@@ -299,6 +301,7 @@ export class MdbaseConnectInternals {
|
|
|
299
301
|
protocol_version: APPLICATION_AUTHORIZATION_PROTOCOL_VERSION,
|
|
300
302
|
authorization_id: authorizationId,
|
|
301
303
|
application_id: application.id,
|
|
304
|
+
application_declaration_id: declarationIdFromFamilyIdentity(application.family_identity),
|
|
302
305
|
application_manifest_digest: application.manifest_digest,
|
|
303
306
|
application_installation_id: await applicationInstallationId(installation),
|
|
304
307
|
installation_signing_public_key: installation.signingPublicKey,
|
|
@@ -714,8 +717,3 @@ export class MdbaseConnectInternals {
|
|
|
714
717
|
listener(connections);
|
|
715
718
|
}
|
|
716
719
|
}
|
|
717
|
-
function authorizationAbort(signal, message, cause) {
|
|
718
|
-
if (signal.reason instanceof MdbaseConnectError)
|
|
719
|
-
return signal.reason;
|
|
720
|
-
return connectError("authorization_cancelled", message, { cause });
|
|
721
|
-
}
|
package/dist/connection.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { CollectionOperation, CollectionTypeDocument, FileCapability, GrantScope, JsonObject } from "@mdbase-dev/connect-protocol";
|
|
2
2
|
import { type ConnectionTransportInternals } from "./connection-transport.js";
|
|
3
3
|
import type { DirectAccessStatus, MdbaseAuthorizationCapabilities, MdbaseConnectionInfo, MdbaseConnectionRoute, MdbaseSyncConnection } from "./connection-types.js";
|
|
4
4
|
import type { GrantKeyStore } from "./crypto.js";
|
|
5
5
|
import { MdbaseFileClient } from "./files.js";
|
|
6
6
|
import { type Application } from "./internal-types.js";
|
|
7
7
|
import type { MdbaseNativeNotificationRegistration, MdbaseNativeNotificationRegistrationOptions, MdbaseNotificationRegistration, MdbaseNotificationRegistrationOptions } from "./notifications.js";
|
|
8
|
-
import type { ChangesInput, CreateInput, CreateTypeInput, DeleteInput, DeletePreflightResult, DeleteProgressOptions, DeleteResult, MdbaseDesiredTimer, MdbaseTimer, MdbaseTimerList, MdbaseTimerReconciliation, ConnectRequestOptions, PendingMutation, QueryInput, QueryPage, QueryPagesOptions, QueryResult, ReadInput, ReadTypeInput, RenameInput, RenamePreflightResult, RenameProgressOptions, RenameResult, UpdateInput, UpdateTypeInput, MdbaseWatchSubscription, WatchInput } from "./operation-types.js";
|
|
8
|
+
import type { ChangesInput, CollectionChangesPage, CollectionDescription, CollectionSetupApplyResult, CollectionSetupAssessment, ApplyCollectionSetupInput, ApplyTypePackInput, AssessCollectionSetupInput, AssessTypePackInput, CreateInput, CreateTypeInput, CreateViewSourceInput, DeleteInput, DeletePreflightResult, DeleteProgressOptions, DeleteResult, DeleteViewSourceInput, DeleteViewSourceResult, ExecuteViewInput, MdbaseDesiredTimer, MdbaseTimer, MdbaseTimerList, MdbaseTimerReconciliation, ConnectRequestOptions, PendingMutation, QueryAllOptions, QueryInput, QueryPage, QueryPagesOptions, QueryResult, ReadInput, ReadTypeInput, ReadViewSourceInput, RenameInput, RenamePreflightResult, RenameProgressOptions, RenameResult, RecordDocument, SavedViewExecution, SavedViewList, SavedViewSourceDocument, TypePackApplyResult, TypePackAssessment, UpdateInput, UpdateTypeInput, UpdateViewSourceInput, MdbaseWatchSubscription, WatchInput } from "./operation-types.js";
|
|
9
9
|
import { type AuthorizationProblemCode, type CollectionChangesProblemCode, type CollectionDescriptionProblemCode, type CollectionMutationProblemCode, type CollectionQueryProblemCode, type CollectionReadProblemCode, type CollectionTypeProblemCode, type ConnectOutcome, type DirectAccessProblemCode, type NotificationProblemCode, type RegistrationProblemCode } from "./outcomes.js";
|
|
10
10
|
import type { MdbaseDeviceAuthorization } from "./authorization-types.js";
|
|
11
11
|
import { type ResolvedConnectTimeouts } from "./request-budget.js";
|
|
@@ -109,7 +109,7 @@ export declare class MdbaseConnection<Frontmatter extends JsonObject = JsonObjec
|
|
|
109
109
|
read(input: ReadInput, options?: ConnectRequestOptions): Promise<ConnectOutcome<RecordDocument<Frontmatter>, CollectionReadProblemCode>>;
|
|
110
110
|
query(input?: QueryInput, options?: ConnectRequestOptions): Promise<ConnectOutcome<QueryResult<Frontmatter>, CollectionQueryProblemCode>>;
|
|
111
111
|
queryPages(input?: QueryInput, options?: QueryPagesOptions<Frontmatter>): AsyncGenerator<ConnectOutcome<QueryPage<Frontmatter>, CollectionQueryProblemCode>>;
|
|
112
|
-
queryAll(input?: QueryInput, options?:
|
|
112
|
+
queryAll(input?: QueryInput, options?: QueryAllOptions<Frontmatter>): Promise<ConnectOutcome<QueryResult<Frontmatter>, CollectionQueryProblemCode>>;
|
|
113
113
|
listViews(options?: ConnectRequestOptions): Promise<ConnectOutcome<SavedViewList, CollectionReadProblemCode>>;
|
|
114
114
|
executeView(input: ExecuteViewInput, options?: ConnectRequestOptions): Promise<ConnectOutcome<SavedViewExecution<Frontmatter>, CollectionReadProblemCode>>;
|
|
115
115
|
readViewSource(input: ReadViewSourceInput, options?: ConnectRequestOptions): Promise<ConnectOutcome<SavedViewSourceDocument, CollectionReadProblemCode>>;
|
|
@@ -133,10 +133,12 @@ export declare class MdbaseConnection<Frontmatter extends JsonObject = JsonObjec
|
|
|
133
133
|
updateType(input: UpdateTypeInput, options?: ConnectRequestOptions): Promise<ConnectOutcome<CollectionTypeDocument, CollectionTypeProblemCode>>;
|
|
134
134
|
assessTypePack(input: AssessTypePackInput, options?: ConnectRequestOptions): Promise<ConnectOutcome<TypePackAssessment, CollectionTypeProblemCode>>;
|
|
135
135
|
applyTypePack(input: ApplyTypePackInput, options?: ConnectRequestOptions): Promise<ConnectOutcome<TypePackApplyResult, CollectionTypeProblemCode>>;
|
|
136
|
+
assessCollectionSetup(input: AssessCollectionSetupInput, options?: ConnectRequestOptions): Promise<ConnectOutcome<CollectionSetupAssessment, CollectionTypeProblemCode>>;
|
|
137
|
+
applyCollectionSetup(input: ApplyCollectionSetupInput, options?: ConnectRequestOptions): Promise<ConnectOutcome<CollectionSetupApplyResult, CollectionTypeProblemCode>>;
|
|
136
138
|
listTimers(namespace: string, options?: ConnectRequestOptions): Promise<ConnectOutcome<MdbaseTimerList, CollectionReadProblemCode>>;
|
|
137
139
|
putTimer(input: {
|
|
138
140
|
namespace: string;
|
|
139
|
-
|
|
141
|
+
criterionId: string;
|
|
140
142
|
timer: MdbaseDesiredTimer;
|
|
141
143
|
}, options?: ConnectRequestOptions): Promise<ConnectOutcome<MdbaseTimer, CollectionMutationProblemCode>>;
|
|
142
144
|
cancelTimer(input: {
|
|
@@ -150,10 +152,9 @@ export declare class MdbaseConnection<Frontmatter extends JsonObject = JsonObjec
|
|
|
150
152
|
}, CollectionMutationProblemCode>>;
|
|
151
153
|
reconcileTimers(input: {
|
|
152
154
|
namespace: string;
|
|
153
|
-
|
|
155
|
+
criterionId: string;
|
|
154
156
|
timers: MdbaseDesiredTimer[];
|
|
155
157
|
}, options?: ConnectRequestOptions): Promise<ConnectOutcome<MdbaseTimerReconciliation, CollectionMutationProblemCode>>;
|
|
156
158
|
watch(input?: WatchInput, options?: ConnectRequestOptions): Promise<ConnectOutcome<MdbaseWatchSubscription, CollectionChangesProblemCode>>;
|
|
157
|
-
operation<Result>(operation: CollectionOperation, input: unknown, options?: ConnectRequestOptions): Promise<ConnectOutcome<Result>>;
|
|
158
159
|
private emitConnection;
|
|
159
160
|
}
|
package/dist/connection.js
CHANGED
|
@@ -6,7 +6,7 @@ import { MdbaseFileClient } from "./files.js";
|
|
|
6
6
|
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
|
-
import { withRequestBudget } from "./request-budget.js";
|
|
9
|
+
import { withCooperativeRequestBudget, withRequestBudget } from "./request-budget.js";
|
|
10
10
|
export class MdbaseConnection {
|
|
11
11
|
internals;
|
|
12
12
|
collectionId;
|
|
@@ -37,7 +37,7 @@ export class MdbaseConnection {
|
|
|
37
37
|
}, internals.timeouts);
|
|
38
38
|
this.collectionClient = new MdbaseCollectionClient({
|
|
39
39
|
operation: (operation, input, requestOptions) => this.transport.performOperation(operation, input, requestOptions)
|
|
40
|
-
});
|
|
40
|
+
}, internals.timeouts.requestMs);
|
|
41
41
|
this.notifications = new ConnectionNotifications({
|
|
42
42
|
serverUrl: internals.serverUrl,
|
|
43
43
|
storage: internals.storage,
|
|
@@ -267,108 +267,116 @@ export class MdbaseConnection {
|
|
|
267
267
|
return this.collectionClient.preflightRename(input, options);
|
|
268
268
|
}
|
|
269
269
|
async renameWithProgress(input, options = {}) {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
throwIfCancelled(options.signal);
|
|
286
|
-
emit("preflighting", true);
|
|
287
|
-
const previewOutcome = options.preflight
|
|
288
|
-
? connectSuccess(options.preflight)
|
|
289
|
-
: await this.preflightRename(input, { signal: options.signal });
|
|
290
|
-
if (!previewOutcome.ok)
|
|
291
|
-
return previewOutcome;
|
|
292
|
-
const preview = previewOutcome.value;
|
|
270
|
+
return withCooperativeRequestBudget(options, this.internals.timeouts.requestMs, async (budget) => {
|
|
271
|
+
const started = Date.now();
|
|
272
|
+
const resumed = false;
|
|
273
|
+
let estimate;
|
|
274
|
+
const emit = (state, cancellable, completedUnits = 0) => {
|
|
275
|
+
options.onProgress?.({
|
|
276
|
+
operation: "rename",
|
|
277
|
+
state,
|
|
278
|
+
elapsedMs: Date.now() - started,
|
|
279
|
+
cancellable,
|
|
280
|
+
resumed,
|
|
281
|
+
completedUnits,
|
|
282
|
+
...(estimate ? { estimate } : {})
|
|
283
|
+
});
|
|
284
|
+
};
|
|
293
285
|
try {
|
|
294
|
-
|
|
286
|
+
throwIfCancelled(budget.signal);
|
|
287
|
+
emit("preflighting", true);
|
|
288
|
+
const previewOutcome = options.preflight
|
|
289
|
+
? connectSuccess(options.preflight)
|
|
290
|
+
: await this.preflightRename(input, { signal: budget.signal, timeoutMs: null });
|
|
291
|
+
if (!previewOutcome.ok)
|
|
292
|
+
return previewOutcome;
|
|
293
|
+
const preview = previewOutcome.value;
|
|
294
|
+
try {
|
|
295
|
+
assertRenamePreview(input, preview);
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
if (error instanceof MdbaseConnectError) {
|
|
299
|
+
return connectFailure(error.problem);
|
|
300
|
+
}
|
|
301
|
+
throw error;
|
|
302
|
+
}
|
|
303
|
+
estimate = renameEstimate(input, preview);
|
|
304
|
+
emit("ready", true);
|
|
305
|
+
throwIfCancelled(budget.signal);
|
|
306
|
+
const cancellable = this.transport.hasResumableMutationTransport();
|
|
307
|
+
emit("applying", cancellable);
|
|
308
|
+
const result = await this.rename(input, cancellable
|
|
309
|
+
? { signal: budget.signal, timeoutMs: null }
|
|
310
|
+
: { timeoutMs: remainingTimeout(budget.remainingMs()) });
|
|
311
|
+
if (result.ok)
|
|
312
|
+
emit("completed", false, estimate.totalUnits);
|
|
313
|
+
return result;
|
|
295
314
|
}
|
|
296
315
|
catch (error) {
|
|
316
|
+
if (isCancellation(error, budget.signal))
|
|
317
|
+
emit("cancelled", false);
|
|
297
318
|
if (error instanceof MdbaseConnectError) {
|
|
298
319
|
return connectFailure(error.problem);
|
|
299
320
|
}
|
|
300
321
|
throw error;
|
|
301
322
|
}
|
|
302
|
-
|
|
303
|
-
emit("ready", true);
|
|
304
|
-
throwIfCancelled(options.signal);
|
|
305
|
-
const cancellable = this.transport.hasResumableMutationTransport();
|
|
306
|
-
emit("applying", cancellable);
|
|
307
|
-
const result = await this.rename(input, cancellable ? { signal: options.signal } : undefined);
|
|
308
|
-
if (result.ok)
|
|
309
|
-
emit("completed", false, estimate.totalUnits);
|
|
310
|
-
return result;
|
|
311
|
-
}
|
|
312
|
-
catch (error) {
|
|
313
|
-
if (isCancellation(error, options.signal))
|
|
314
|
-
emit("cancelled", false);
|
|
315
|
-
if (error instanceof MdbaseConnectError) {
|
|
316
|
-
return connectFailure(error.problem);
|
|
317
|
-
}
|
|
318
|
-
throw error;
|
|
319
|
-
}
|
|
323
|
+
});
|
|
320
324
|
}
|
|
321
325
|
async deleteWithProgress(input, options = {}) {
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
throwIfCancelled(options.signal);
|
|
338
|
-
emit("preflighting", true);
|
|
339
|
-
const previewOutcome = options.preflight
|
|
340
|
-
? connectSuccess(options.preflight)
|
|
341
|
-
: await this.preflightDelete(input, { signal: options.signal });
|
|
342
|
-
if (!previewOutcome.ok)
|
|
343
|
-
return previewOutcome;
|
|
344
|
-
const preview = previewOutcome.value;
|
|
326
|
+
return withCooperativeRequestBudget(options, this.internals.timeouts.requestMs, async (budget) => {
|
|
327
|
+
const started = Date.now();
|
|
328
|
+
const resumed = false;
|
|
329
|
+
let estimate;
|
|
330
|
+
const emit = (state, cancellable, completedUnits = 0) => {
|
|
331
|
+
options.onProgress?.({
|
|
332
|
+
operation: "delete",
|
|
333
|
+
state,
|
|
334
|
+
elapsedMs: Date.now() - started,
|
|
335
|
+
cancellable,
|
|
336
|
+
resumed,
|
|
337
|
+
completedUnits,
|
|
338
|
+
...(estimate ? { estimate } : {})
|
|
339
|
+
});
|
|
340
|
+
};
|
|
345
341
|
try {
|
|
346
|
-
|
|
342
|
+
throwIfCancelled(budget.signal);
|
|
343
|
+
emit("preflighting", true);
|
|
344
|
+
const previewOutcome = options.preflight
|
|
345
|
+
? connectSuccess(options.preflight)
|
|
346
|
+
: await this.preflightDelete(input, { signal: budget.signal, timeoutMs: null });
|
|
347
|
+
if (!previewOutcome.ok)
|
|
348
|
+
return previewOutcome;
|
|
349
|
+
const preview = previewOutcome.value;
|
|
350
|
+
try {
|
|
351
|
+
assertDeletePreview(input, preview);
|
|
352
|
+
}
|
|
353
|
+
catch (error) {
|
|
354
|
+
if (error instanceof MdbaseConnectError) {
|
|
355
|
+
return connectFailure(error.problem);
|
|
356
|
+
}
|
|
357
|
+
throw error;
|
|
358
|
+
}
|
|
359
|
+
estimate = deleteEstimate(preview);
|
|
360
|
+
emit("ready", true);
|
|
361
|
+
throwIfCancelled(budget.signal);
|
|
362
|
+
const cancellable = this.transport.hasResumableMutationTransport();
|
|
363
|
+
emit("applying", cancellable);
|
|
364
|
+
const result = await this.delete(input, cancellable
|
|
365
|
+
? { signal: budget.signal, timeoutMs: null }
|
|
366
|
+
: { timeoutMs: remainingTimeout(budget.remainingMs()) });
|
|
367
|
+
if (result.ok)
|
|
368
|
+
emit("completed", false, estimate.totalUnits);
|
|
369
|
+
return result;
|
|
347
370
|
}
|
|
348
371
|
catch (error) {
|
|
372
|
+
if (isCancellation(error, budget.signal))
|
|
373
|
+
emit("cancelled", false);
|
|
349
374
|
if (error instanceof MdbaseConnectError) {
|
|
350
375
|
return connectFailure(error.problem);
|
|
351
376
|
}
|
|
352
377
|
throw error;
|
|
353
378
|
}
|
|
354
|
-
|
|
355
|
-
emit("ready", true);
|
|
356
|
-
throwIfCancelled(options.signal);
|
|
357
|
-
const cancellable = this.transport.hasResumableMutationTransport();
|
|
358
|
-
emit("applying", cancellable);
|
|
359
|
-
const result = await this.delete(input, cancellable ? { signal: options.signal } : undefined);
|
|
360
|
-
if (result.ok)
|
|
361
|
-
emit("completed", false, estimate.totalUnits);
|
|
362
|
-
return result;
|
|
363
|
-
}
|
|
364
|
-
catch (error) {
|
|
365
|
-
if (isCancellation(error, options.signal))
|
|
366
|
-
emit("cancelled", false);
|
|
367
|
-
if (error instanceof MdbaseConnectError) {
|
|
368
|
-
return connectFailure(error.problem);
|
|
369
|
-
}
|
|
370
|
-
throw error;
|
|
371
|
-
}
|
|
379
|
+
});
|
|
372
380
|
}
|
|
373
381
|
pendingMutations() {
|
|
374
382
|
return this.transport.pendingMutations().map((summary) => ({
|
|
@@ -413,6 +421,12 @@ export class MdbaseConnection {
|
|
|
413
421
|
applyTypePack(input, options) {
|
|
414
422
|
return this.collectionClient.applyTypePack(input, options);
|
|
415
423
|
}
|
|
424
|
+
assessCollectionSetup(input, options) {
|
|
425
|
+
return this.collectionClient.assessCollectionSetup(input, options);
|
|
426
|
+
}
|
|
427
|
+
applyCollectionSetup(input, options) {
|
|
428
|
+
return this.collectionClient.applyCollectionSetup(input, options);
|
|
429
|
+
}
|
|
416
430
|
listTimers(namespace, options) {
|
|
417
431
|
return this.collectionClient.listTimers(namespace, options);
|
|
418
432
|
}
|
|
@@ -436,9 +450,6 @@ export class MdbaseConnection {
|
|
|
436
450
|
return new CollectionWatchSubscription(this.collectionClient, initial.value.cursor, input, input.cursor === undefined ? [] : initial.value.events, this.internals.timeouts.watchStartMs);
|
|
437
451
|
}), COLLECTION_CHANGES_PROBLEM_CODES);
|
|
438
452
|
}
|
|
439
|
-
operation(operation, input, options) {
|
|
440
|
-
return this.collectionClient.operation(operation, input, options);
|
|
441
|
-
}
|
|
442
453
|
emitConnection() {
|
|
443
454
|
const connection = this.info();
|
|
444
455
|
for (const listener of this.connectionListeners)
|
|
@@ -559,3 +570,6 @@ function syncRequestOptions(options, defaultTimeoutMs) {
|
|
|
559
570
|
timeoutMs: options?.timeoutMs === undefined ? defaultTimeoutMs : options.timeoutMs
|
|
560
571
|
};
|
|
561
572
|
}
|
|
573
|
+
function remainingTimeout(remainingMs) {
|
|
574
|
+
return remainingMs === null ? null : Math.max(1, Math.ceil(remainingMs));
|
|
575
|
+
}
|
package/dist/crypto-entry.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
/** Opt-in cryptographic storage and transport primitives. */
|
|
2
|
-
export
|
|
3
|
-
export
|
|
4
|
-
export
|
|
2
|
+
export { IndexedDbGrantKeyStore, MemoryGrantKeyStore, RelayCryptoError, signAuthorityRequest, authorityProofMessage, encryptRelayRequest, decryptRelayResponse, deriveP256SharedSecret, validateGrantEncryption } from "./crypto.js";
|
|
3
|
+
export type { GrantKeyRecord, GrantKeyStore, RelayBinding, AuthorityProofInput } from "./crypto.js";
|
|
4
|
+
export { FileTransferCryptoError, GrantFileTransferCipher, deriveFileTransferKey } from "./file-crypto.js";
|
|
5
|
+
export type { FileTransferBinding } from "./file-crypto.js";
|
|
6
|
+
export { IndexedDbApplicationIdentityStore, MemoryApplicationIdentityStore, ApplicationIdentityStoreError, applicationInstallationId, signApplicationAuthorization, applicationIdentity } from "./application-identity.js";
|
|
7
|
+
export type { ApplicationIdentity, ApplicationIdentityStore } from "./application-identity.js";
|
package/dist/crypto-entry.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
/** Opt-in cryptographic storage and transport primitives. */
|
|
2
|
-
export
|
|
3
|
-
export
|
|
4
|
-
export
|
|
2
|
+
export { IndexedDbGrantKeyStore, MemoryGrantKeyStore, RelayCryptoError, signAuthorityRequest, authorityProofMessage, encryptRelayRequest, decryptRelayResponse, deriveP256SharedSecret, validateGrantEncryption } from "./crypto.js";
|
|
3
|
+
export { FileTransferCryptoError, GrantFileTransferCipher, deriveFileTransferKey } from "./file-crypto.js";
|
|
4
|
+
export { IndexedDbApplicationIdentityStore, MemoryApplicationIdentityStore, ApplicationIdentityStoreError, applicationInstallationId, signApplicationAuthorization, applicationIdentity } from "./application-identity.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FileTransferSession, PreparedFilePart } from "@mdbase-dev/connect-protocol";
|
|
1
|
+
import type { FileTransferSession, FileTransferStatus, PreparedFilePart } from "@mdbase-dev/connect-protocol";
|
|
2
2
|
import { MdbaseConnectError } from "./errors.js";
|
|
3
3
|
export declare const MAX_OBJECT_ATTEMPTS = 3;
|
|
4
4
|
export declare const MAX_BUFFERED_DOWNLOAD_BYTES: number;
|
|
@@ -13,6 +13,7 @@ export declare function uploadBodyBytes(body: UploadPartBody): Promise<Uint8Arra
|
|
|
13
13
|
export declare function validConcurrency(value?: number): number;
|
|
14
14
|
export declare function validPageSize(value: number): number;
|
|
15
15
|
export declare function requireTransferSession(session: FileTransferSession, transferId: string, direction: "upload" | "download"): void;
|
|
16
|
+
export declare function requireTransferStatus(status: FileTransferStatus, session: FileTransferSession): void;
|
|
16
17
|
export declare function chunkLength(totalSize: number, partSize: number, index: number): number;
|
|
17
18
|
export declare function retryChunk<Result>(work: () => Promise<Result>, signal?: AbortSignal): Promise<Result>;
|
|
18
19
|
export declare function requirePreparedPart(part: PreparedFilePart, transferId: string, partIndex: number, offset: number, contentLength: number, method: "PUT"): void;
|
|
@@ -90,6 +90,44 @@ export function requireTransferSession(session, transferId, direction) {
|
|
|
90
90
|
throw connectError("invalid_operation_response", "The authority returned invalid uploaded part receipts.");
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
|
+
export function requireTransferStatus(status, session) {
|
|
94
|
+
const partSize = session.strategy.kind === "framed_chunks"
|
|
95
|
+
? session.strategy.chunk_size
|
|
96
|
+
: session.strategy.kind === "object_put"
|
|
97
|
+
? Math.max(1, session.total_size)
|
|
98
|
+
: session.strategy.part_size;
|
|
99
|
+
const partCount = session.strategy.kind === "object_put"
|
|
100
|
+
? 1
|
|
101
|
+
: Math.ceil(session.total_size / partSize);
|
|
102
|
+
if (status?.protocol_version !== 1
|
|
103
|
+
|| status.type !== "file_transfer_status"
|
|
104
|
+
|| status.transfer_id !== session.transfer_id
|
|
105
|
+
|| !["open", "committed", "aborted", "expired"].includes(status.state)
|
|
106
|
+
|| !Array.isArray(status.received)
|
|
107
|
+
|| new Set(status.received).size !== status.received.length
|
|
108
|
+
|| status.received.some((index) => !Number.isSafeInteger(index) || index < 0 || index >= partCount)
|
|
109
|
+
|| !Number.isSafeInteger(status.received_bytes)
|
|
110
|
+
|| status.received_bytes < 0
|
|
111
|
+
|| status.received_bytes > session.total_size
|
|
112
|
+
|| status.received.reduce((total, index) => total + chunkLength(session.total_size, partSize, index), 0) !== status.received_bytes) {
|
|
113
|
+
throw connectError("invalid_operation_response", "The authority returned invalid transfer status.");
|
|
114
|
+
}
|
|
115
|
+
const uploadedParts = status.uploaded_parts;
|
|
116
|
+
if (!Array.isArray(uploadedParts)
|
|
117
|
+
|| uploadedParts.some((part, index) => !Number.isSafeInteger(part?.part_number)
|
|
118
|
+
|| part.part_number < 1
|
|
119
|
+
|| part.part_number > partCount
|
|
120
|
+
|| typeof part.etag !== "string"
|
|
121
|
+
|| part.etag.length === 0
|
|
122
|
+
|| part.etag.length > 255
|
|
123
|
+
|| (index > 0 && uploadedParts[index - 1].part_number >= part.part_number))
|
|
124
|
+
|| (session.strategy.kind === "object_multipart"
|
|
125
|
+
? uploadedParts.length !== status.received.length
|
|
126
|
+
|| uploadedParts.some((part, index) => part.part_number - 1 !== status.received[index])
|
|
127
|
+
: uploadedParts.length !== 0)) {
|
|
128
|
+
throw connectError("invalid_operation_response", "The authority returned invalid uploaded part status.");
|
|
129
|
+
}
|
|
130
|
+
}
|
|
93
131
|
function validTransferStrategy(session) {
|
|
94
132
|
if (session.strategy.kind === "framed_chunks") {
|
|
95
133
|
return session.protection === "grant_aead_v1"
|
package/dist/files.d.ts
CHANGED
|
@@ -1,8 +1,26 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { FileCapability, FileTransferSession } from "@mdbase-dev/connect-protocol";
|
|
2
2
|
import { type FileSource } from "./file-transfer-internals.js";
|
|
3
3
|
import type { ConnectRequestOptions } from "./operation-types.js";
|
|
4
4
|
import { type ResolvedConnectTimeouts } from "./request-budget.js";
|
|
5
5
|
export type MdbaseFileSource = FileSource;
|
|
6
|
+
export interface CollectionFileDescriptor {
|
|
7
|
+
fileId: string;
|
|
8
|
+
path: string;
|
|
9
|
+
revision: string;
|
|
10
|
+
contentDigest: `sha256:${string}`;
|
|
11
|
+
size: number;
|
|
12
|
+
mediaType?: string;
|
|
13
|
+
mediaClass: import("@mdbase-dev/connect-protocol").FileMediaClass;
|
|
14
|
+
modifiedAt: string;
|
|
15
|
+
}
|
|
16
|
+
export interface MdbaseFileDeleteReceipt {
|
|
17
|
+
protocolVersion: 1;
|
|
18
|
+
type: "file_deleted";
|
|
19
|
+
mutationId: string;
|
|
20
|
+
fileId: string;
|
|
21
|
+
previousPath: string;
|
|
22
|
+
revision: string;
|
|
23
|
+
}
|
|
6
24
|
export interface MdbaseFileStreamSource {
|
|
7
25
|
/** Exact plaintext byte length. */
|
|
8
26
|
size: number;
|
|
@@ -84,7 +102,7 @@ export declare class MdbaseFileClient {
|
|
|
84
102
|
downloadBytes(file: CollectionFileDescriptor, options?: MdbaseFileDownloadOptions): Promise<Uint8Array>;
|
|
85
103
|
move(file: CollectionFileDescriptor, path: string, options?: MdbaseFileMoveOptions): Promise<CollectionFileDescriptor>;
|
|
86
104
|
private moveWithinBudget;
|
|
87
|
-
delete(file: CollectionFileDescriptor, options?: MdbaseFileDeleteOptions): Promise<
|
|
105
|
+
delete(file: CollectionFileDescriptor, options?: MdbaseFileDeleteOptions): Promise<MdbaseFileDeleteReceipt>;
|
|
88
106
|
private deleteWithinBudget;
|
|
89
107
|
private uploadPart;
|
|
90
108
|
private tryReplayUploadCommit;
|
package/dist/files.js
CHANGED
|
@@ -2,7 +2,7 @@ import { FILE_PROTOCOL_VERSION } from "@mdbase-dev/connect-protocol";
|
|
|
2
2
|
import { MdbaseConnectError, connectError } from "./errors.js";
|
|
3
3
|
import { IncrementalSha256 } from "./file-sha256.js";
|
|
4
4
|
import { BinaryPartReader } from "./file-stream-source.js";
|
|
5
|
-
import { MAX_BUFFERED_DOWNLOAD_BYTES, MAX_OBJECT_ATTEMPTS, SHA256_DIGEST, UUID, browserObjectHeaders, chunkLength, hashBlob, mapConcurrent, normalizeFileError, requirePreparedPart, requireTransferSession, retryChunk, sourceBlob, throwIfAborted, uploadBodyBytes, uploadBodyLength, validConcurrency, validPageSize } from "./file-transfer-internals.js";
|
|
5
|
+
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, validPageSize } from "./file-transfer-internals.js";
|
|
6
6
|
import { createRequestBudget, resolveConnectTimeouts, withCooperativeRequestBudget } from "./request-budget.js";
|
|
7
7
|
/** Ergonomic facade over resumable, authority-specific file delivery. */
|
|
8
8
|
export class MdbaseFileClient {
|
|
@@ -38,7 +38,7 @@ export class MdbaseFileClient {
|
|
|
38
38
|
}
|
|
39
39
|
for (const file of page.files) {
|
|
40
40
|
throwIfAborted(signal);
|
|
41
|
-
yield file;
|
|
41
|
+
yield clientFileDescriptor(file);
|
|
42
42
|
}
|
|
43
43
|
after = page.next;
|
|
44
44
|
} while (after);
|
|
@@ -130,16 +130,21 @@ export class MdbaseFileClient {
|
|
|
130
130
|
const partCount = session.strategy.kind === "object_put"
|
|
131
131
|
? 1
|
|
132
132
|
: Math.ceil(size / partSize);
|
|
133
|
+
const status = await this.request("GET", `transfers/${encodeURIComponent(transferId)}`, undefined, options.signal);
|
|
134
|
+
requireTransferStatus(status, session);
|
|
135
|
+
if (status.state !== "open" && status.state !== "committed") {
|
|
136
|
+
throw connectError("invalid_operation_response", `The authority cannot resume a ${status.state} file transfer.`);
|
|
137
|
+
}
|
|
133
138
|
prepareSource?.(partSize);
|
|
134
|
-
if (!framed &&
|
|
135
|
-
const replay = await this.tryReplayUploadCommit(transferId, session.strategy.kind === "object_multipart" ?
|
|
139
|
+
if (!framed && status.received.length === partCount) {
|
|
140
|
+
const replay = await this.tryReplayUploadCommit(transferId, session.strategy.kind === "object_multipart" ? status.uploaded_parts : undefined, options.signal);
|
|
136
141
|
if (replay) {
|
|
137
142
|
committed = true;
|
|
138
|
-
return replay.file;
|
|
143
|
+
return clientFileDescriptor(replay.file);
|
|
139
144
|
}
|
|
140
145
|
}
|
|
141
|
-
const received = new Set(
|
|
142
|
-
const uploadedParts = new Map(
|
|
146
|
+
const received = new Set(status.received);
|
|
147
|
+
const uploadedParts = new Map(status.uploaded_parts.map((part) => [part.part_number - 1, part]));
|
|
143
148
|
let transferredBytes = [...received].reduce((total, index) => total + chunkLength(size, partSize, index), 0);
|
|
144
149
|
const parts = await mapConcurrent(partCount, concurrency, async (partIndex) => {
|
|
145
150
|
const offset = partIndex * partSize;
|
|
@@ -174,7 +179,7 @@ export class MdbaseFileClient {
|
|
|
174
179
|
throw connectError("invalid_operation_response", "The authority returned an invalid file receipt.");
|
|
175
180
|
}
|
|
176
181
|
committed = true;
|
|
177
|
-
return receipt.file;
|
|
182
|
+
return clientFileDescriptor(receipt.file);
|
|
178
183
|
}
|
|
179
184
|
catch (error) {
|
|
180
185
|
throw normalizeFileError(error);
|
|
@@ -202,7 +207,7 @@ export class MdbaseFileClient {
|
|
|
202
207
|
const copy = new Uint8Array(chunk.byteLength);
|
|
203
208
|
copy.set(chunk);
|
|
204
209
|
return copy.buffer;
|
|
205
|
-
}), { type: file.
|
|
210
|
+
}), { type: file.mediaType ?? "" });
|
|
206
211
|
}
|
|
207
212
|
/** Download and verify a file with network backpressure and bounded buffering. */
|
|
208
213
|
async downloadStream(file, options = {}) {
|
|
@@ -218,7 +223,7 @@ export class MdbaseFileClient {
|
|
|
218
223
|
protocol_version: FILE_PROTOCOL_VERSION,
|
|
219
224
|
type: "open_file_download",
|
|
220
225
|
transfer_id: transferId,
|
|
221
|
-
file_id: file.
|
|
226
|
+
file_id: file.fileId,
|
|
222
227
|
revision: file.revision
|
|
223
228
|
}, startupSignal);
|
|
224
229
|
requireTransferSession(session, transferId, "download");
|
|
@@ -257,7 +262,7 @@ export class MdbaseFileClient {
|
|
|
257
262
|
await this.abort(transferId);
|
|
258
263
|
};
|
|
259
264
|
const verify = () => {
|
|
260
|
-
if (receivedBytes !== file.size || `sha256:${hash.digestHex()}` !== file.
|
|
265
|
+
if (receivedBytes !== file.size || `sha256:${hash.digestHex()}` !== file.contentDigest) {
|
|
261
266
|
throw connectError("invalid_operation_response", "Downloaded file bytes failed integrity verification.");
|
|
262
267
|
}
|
|
263
268
|
};
|
|
@@ -357,11 +362,11 @@ export class MdbaseFileClient {
|
|
|
357
362
|
const mutationId = options.mutationId ?? crypto.randomUUID();
|
|
358
363
|
let receipt;
|
|
359
364
|
try {
|
|
360
|
-
receipt = await this.request("POST", `${encodeURIComponent(file.
|
|
365
|
+
receipt = await this.request("POST", `${encodeURIComponent(file.fileId)}/move`, {
|
|
361
366
|
protocol_version: FILE_PROTOCOL_VERSION,
|
|
362
367
|
type: "move_file",
|
|
363
368
|
mutation_id: mutationId,
|
|
364
|
-
file_id: file.
|
|
369
|
+
file_id: file.fileId,
|
|
365
370
|
if_revision: options.ifRevision ?? file.revision,
|
|
366
371
|
from_path: file.path,
|
|
367
372
|
path,
|
|
@@ -376,11 +381,11 @@ export class MdbaseFileClient {
|
|
|
376
381
|
if (receipt.protocol_version !== 1
|
|
377
382
|
|| receipt.type !== "file_moved"
|
|
378
383
|
|| receipt.mutation_id !== mutationId
|
|
379
|
-
|| receipt.file.file_id !== file.
|
|
384
|
+
|| receipt.file.file_id !== file.fileId
|
|
380
385
|
|| receipt.file.path !== path) {
|
|
381
386
|
throw connectError("invalid_operation_response", "The authority returned an invalid file move receipt.");
|
|
382
387
|
}
|
|
383
|
-
return receipt.file;
|
|
388
|
+
return clientFileDescriptor(receipt.file);
|
|
384
389
|
}
|
|
385
390
|
async delete(file, options = {}) {
|
|
386
391
|
return withCooperativeRequestBudget(options, this.timeouts.requestMs, (budget) => this.deleteWithinBudget(file, { ...options, signal: budget.signal, timeoutMs: null }));
|
|
@@ -390,11 +395,11 @@ export class MdbaseFileClient {
|
|
|
390
395
|
const mutationId = options.mutationId ?? crypto.randomUUID();
|
|
391
396
|
let receipt;
|
|
392
397
|
try {
|
|
393
|
-
receipt = await this.request("POST", `${encodeURIComponent(file.
|
|
398
|
+
receipt = await this.request("POST", `${encodeURIComponent(file.fileId)}/delete`, {
|
|
394
399
|
protocol_version: FILE_PROTOCOL_VERSION,
|
|
395
400
|
type: "delete_file",
|
|
396
401
|
mutation_id: mutationId,
|
|
397
|
-
file_id: file.
|
|
402
|
+
file_id: file.fileId,
|
|
398
403
|
if_revision: options.ifRevision ?? file.revision,
|
|
399
404
|
path: file.path
|
|
400
405
|
}, options.signal);
|
|
@@ -405,11 +410,18 @@ export class MdbaseFileClient {
|
|
|
405
410
|
if (receipt.protocol_version !== 1
|
|
406
411
|
|| receipt.type !== "file_deleted"
|
|
407
412
|
|| receipt.mutation_id !== mutationId
|
|
408
|
-
|| receipt.file_id !== file.
|
|
413
|
+
|| receipt.file_id !== file.fileId
|
|
409
414
|
|| receipt.previous_path !== file.path) {
|
|
410
415
|
throw connectError("invalid_operation_response", "The authority returned an invalid file delete receipt.");
|
|
411
416
|
}
|
|
412
|
-
return
|
|
417
|
+
return {
|
|
418
|
+
protocolVersion: receipt.protocol_version,
|
|
419
|
+
type: receipt.type,
|
|
420
|
+
mutationId: receipt.mutation_id,
|
|
421
|
+
fileId: receipt.file_id,
|
|
422
|
+
previousPath: receipt.previous_path,
|
|
423
|
+
revision: receipt.revision
|
|
424
|
+
};
|
|
413
425
|
}
|
|
414
426
|
async uploadPart(transferId, partIndex, offset, body, contentLength, requireEtag, signal) {
|
|
415
427
|
for (let attempt = 1; attempt <= MAX_OBJECT_ATTEMPTS; attempt += 1) {
|
|
@@ -488,3 +500,15 @@ export class MdbaseFileClient {
|
|
|
488
500
|
await this.request("DELETE", `transfers/${encodeURIComponent(transferId)}`).catch(() => undefined);
|
|
489
501
|
}
|
|
490
502
|
}
|
|
503
|
+
function clientFileDescriptor(file) {
|
|
504
|
+
return {
|
|
505
|
+
fileId: file.file_id,
|
|
506
|
+
path: file.path,
|
|
507
|
+
revision: file.revision,
|
|
508
|
+
contentDigest: file.content_digest,
|
|
509
|
+
size: file.size,
|
|
510
|
+
...(file.media_type ? { mediaType: file.media_type } : {}),
|
|
511
|
+
mediaClass: file.media_class,
|
|
512
|
+
modifiedAt: file.modified_at
|
|
513
|
+
};
|
|
514
|
+
}
|