@serve.zone/interfaces 20.0.0 → 20.2.0

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 CHANGED
@@ -120,6 +120,130 @@ must reject unknown manifest fields and unsupported required feature IDs before
120
120
  provisioning. Legacy `volumes` and `platformRequirements.s3` remain deprecated
121
121
  inputs for strict resolver normalization only.
122
122
 
123
+ `platform.storagemigration` defines the provider-neutral cutover contract for a
124
+ named object-storage binding. Corestore atomically owns and fences the source
125
+ binding after validating a distinct, unfenced active-object snapshot. Onebox
126
+ only receives a held candidate, stages that exact candidate, stops the matching
127
+ workload generation, and attests the quiesced state. The candidate cannot start
128
+ until `destinationBindingStartAuthorized` is true. Corestore continues returning
129
+ `consumerAction: 'startDestination'` until Onebox submits the mutation-fenced
130
+ consumer activation request and the acknowledgement becomes durable evidence.
131
+ Candidate-issued abort tombstones authorize only `startSource` and never retain
132
+ the staged candidate binding.
133
+
134
+ Every migration DTO and status has an exact, versioned runtime normalizer.
135
+ Unknown fields, provider or pool identifiers, unbounded strings, unsafe
136
+ integers, stale mutation revisions, identity drift, and lifecycle-inconsistent
137
+ fields are rejected. Migration-created digests use strict canonical JSON and a
138
+ bare lowercase 64-hex SHA-256 value; portable golden vectors cover the source
139
+ snapshot, target request, prepare intent, and candidate binding. Pre-cutover
140
+ failures may retry or abort; after the durable commit point, recovery can only
141
+ retry or roll forward. Physical pool IDs, mount details, provider receipts, and
142
+ publication capabilities remain private.
143
+
144
+ Primary exports include:
145
+
146
+ - `IObjectStorageMigrationPrepareRequest` and
147
+ `TObjectStorageMigrationStatus` for the immutable intent and status journal.
148
+ - `IObjectStorageMigrationConsumerQuiesceRequest` with
149
+ `IStorageMigrationConsumerQuiesceEvidence` for exact candidate staging and
150
+ source-workload shutdown.
151
+ - `IObjectStorageMigrationConsumerActivationRequest` with
152
+ `IStorageMigrationConsumerActivationEvidence` for durable destination-start
153
+ acknowledgement.
154
+ - `normalizeObjectStorageMigrationStatus`,
155
+ `bindObjectStorageMigrationConsumerQuiesceRequest`, and
156
+ `bindObjectStorageMigrationConsumerActivationRequest` for strict ingress and
157
+ current-revision mutation fencing.
158
+ - The `create*Sha256` helpers for source snapshots, prepare intent, target
159
+ requests, candidate/active bindings, and persisted staging or activation
160
+ evidence.
161
+
162
+ The phase and consumer-action progression is exact:
163
+
164
+ | Phase | `consumerAction` | Destination start authorized |
165
+ | --- | --- | --- |
166
+ | `preparing`, `transferring` | `wait` | No |
167
+ | `awaitingConsumerQuiesce` | `stageCandidateAndStop` | No |
168
+ | `finalizing`, `committing` | `wait` | No |
169
+ | `readyToStart` | `startDestination` | Yes |
170
+ | `cleanupPending`, `complete` | `none` | Yes; durable activation evidence is required |
171
+ | `aborting` | `wait` | No |
172
+ | `aborted` | `startSource` | No; only the active source binding may restart |
173
+
174
+ Normalize every status before acting, and bind consumer mutations to that exact
175
+ status revision:
176
+
177
+ ```typescript
178
+ const status =
179
+ await platform.storagemigration.normalizeObjectStorageMigrationStatus(
180
+ untrustedStatusPayload,
181
+ );
182
+
183
+ if (status.phase === 'awaitingConsumerQuiesce') {
184
+ const request =
185
+ await platform.storagemigration.bindObjectStorageMigrationConsumerQuiesceRequest(
186
+ untrustedQuiescePayload,
187
+ status,
188
+ );
189
+ await submitQuiesceAcknowledgement(request);
190
+ }
191
+
192
+ if (status.phase === 'readyToStart') {
193
+ if (
194
+ !status.destinationBindingStartAuthorized ||
195
+ status.consumerAction !== 'startDestination'
196
+ ) {
197
+ throw new Error('destination binding is not authorized to start');
198
+ }
199
+ await startWorkload(status.activeBinding);
200
+ const request =
201
+ await platform.storagemigration.bindObjectStorageMigrationConsumerActivationRequest(
202
+ untrustedActivationPayload,
203
+ status,
204
+ );
205
+ await submitActivationAcknowledgement(request);
206
+ }
207
+
208
+ if (status.phase === 'cleanupPending') {
209
+ // Cleanup is reachable only after this durable acknowledgement was accepted.
210
+ const durableActivation = status.consumerActivationEvidence;
211
+ }
212
+
213
+ if (status.phase === 'aborted') {
214
+ if (
215
+ status.destinationBindingStartAuthorized ||
216
+ status.consumerAction !== 'startSource'
217
+ ) {
218
+ throw new Error('invalid aborted migration status');
219
+ }
220
+ await startWorkload(status.activeBinding);
221
+ }
222
+ ```
223
+
224
+ Here `startWorkload`, `submitQuiesceAcknowledgement`, and
225
+ `submitActivationAcknowledgement` are consumer-owned operations, not package
226
+ exports. Canonical digests are produced from normalized payloads:
227
+
228
+ ```typescript
229
+ const snapshotSha256 =
230
+ await platform.storagemigration.createUnfencedObjectStorageBindingControlSnapshotSha256(
231
+ snapshotDigestPayload,
232
+ );
233
+ const migrationSha256 =
234
+ await platform.storagemigration.createObjectStorageMigrationSha256(
235
+ prepareRequest,
236
+ );
237
+ const candidateSha256 =
238
+ await platform.storagemigration.createObjectStorageMigrationBindingSha256(
239
+ candidateBinding,
240
+ );
241
+ const activationRecordSha256 =
242
+ await platform.storagemigration.createObjectStorageMigrationPersistedActivationSha256(
243
+ activationDigestPayload,
244
+ );
245
+ ```
246
+
123
247
  ## Data Contracts
124
248
 
125
249
  Use `data` when you need object shapes that are persisted, exchanged between services, or exposed through the Cloudly API.
@@ -356,6 +480,35 @@ const provisioning: requests.gateway.IReq_ProvisionGatewayClientCredential['requ
356
480
 
357
481
  `getGatewayClientContext` returns effective live policy. A `gatewayClient` role necessarily includes the credential ID, bound client ID/type, and `policyGeneration`; consumers should reject admin/operator or mismatched contexts rather than falling back to a caller-supplied owner ID. `getGatewayClientMailOverview` provides an owner-scoped domain and recent-message summary. `getGatewayClientMailDomainCount` derives ownership exclusively from the authenticating gateway credential and returns `{ count: number }` for its distinct configured mail domains. Cloudly therefore uses only `dcrouterGatewayApiToken`; the former `dcrouterOpsApiToken` setting is not part of `data.ICloudlySettings`.
358
482
 
483
+ ### CoreMail contracts
484
+
485
+ `requests.coremail` is the shared contract boundary for authenticated workload
486
+ sessions, Coreflow reconciliation, and the CoreMail-to-dcrouter gateway
487
+ session. Only the three authentication handshakes carry reusable peer
488
+ credentials. Subsequent transfer operations may carry a scoped, short-lived,
489
+ one-time bearer capability, while every subsequent workload request derives
490
+ tenant, service, binding, capabilities, allowed senders, and credential version
491
+ from the server-owned TypedSocket peer.
492
+
493
+ Large content never travels inside TypedRequest JSON. Outbound body parts and
494
+ attachments use prepare/upload/complete operations with short-lived one-time
495
+ HTTP transfer grants. Inbound delivery uses bounded delivery listing followed
496
+ by prepare/fetch/complete and an explicit acknowledgement after the workload
497
+ has processed the exact byte count and SHA-256 digest. `data.coreMailLimits`
498
+ defines the 64 KiB control-frame boundary, bounded structured content, the
499
+ 30 MiB serialized MIME ceiling, transfer deadlines, and five-minute grant
500
+ lifetime.
501
+
502
+ Coreflow applies `data.ICoreMailDesiredState` with a config-epoch
503
+ compare-and-set fence. It stages the digest-fenced JSON snapshot through a
504
+ bounded one-time HTTP upload, then applies it by reconciliation ID, so a large
505
+ binding set never bypasses the 64 KiB control-frame boundary. Desired bindings
506
+ contain password verifiers and secret references only, never plaintext
507
+ workload or dcrouter credentials.
508
+ Reconciliation status carries the exact CoreMail task, rollout generation, and
509
+ image digest so Coreflow can correlate every response with its authoritative
510
+ current task roster.
511
+
359
512
  Request groups are exported by product area:
360
513
 
361
514
  - `requests.admin`
@@ -366,6 +519,7 @@ Request groups are exported by product area:
366
519
  - `requests.certificate`
367
520
  - `requests.cluster`
368
521
  - `requests.config`
522
+ - `requests.coremail`
369
523
  - `requests.corestore`
370
524
  - `requests.deployment`
371
525
  - `requests.dns`
@@ -438,6 +592,8 @@ Available platform modules:
438
592
  - `platform.pushnotification` is the deprecated legacy device-token push contract. New browser Web Push integrations use `requests.webpush`.
439
593
  - `platform.letter` for physical letter workflows.
440
594
  - `platform.ai`, `platform.database`, `platform.objectstorage`, `platform.logging`, `platform.backup`, and `platform.sip` for infrastructure and application capabilities.
595
+ - `platform.storage` for provider-neutral storage classes, requests, capabilities, and resolved bindings.
596
+ - `platform.storagemigration` for fenced object-storage migration intent, status, consumer acknowledgements, canonical digests, and strict normalizers.
441
597
  - `platform.types` plus root re-exports for shared capability, provider, binding, credential, and endpoint metadata.
442
598
 
443
599
  ## Legacy Platformservice Contracts
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/interfaces',
6
- version: '20.0.0',
6
+ version: '20.2.0',
7
7
  description: 'Shared TypeScript interfaces and TypedRequest contracts for the serve.zone ecosystem.'
8
8
  }
@@ -0,0 +1,277 @@
1
+ /**
2
+ * Shared CoreMail contracts.
3
+ *
4
+ * Workload authority is always derived from an authenticated transport
5
+ * session. Application-facing request DTOs therefore carry message or
6
+ * delivery identifiers only and never caller-selected tenant, service, or
7
+ * binding authority.
8
+ */
9
+
10
+ export type TCoreMailSha256 = `sha256:${string}`;
11
+
12
+ export type TCoreMailCapability = 'outbound' | 'inbound';
13
+ export type TCoreMailBindingState = 'active' | 'disabled';
14
+ export type TCoreMailCredentialState = 'current' | 'retiring';
15
+ export type TCoreMailPartKind = 'text' | 'html' | 'attachment';
16
+ export type TCoreMailTransferMethod = 'PUT' | 'GET';
17
+ export type TCoreMailSubmissionState =
18
+ | 'preparing'
19
+ | 'uploading'
20
+ | 'ready'
21
+ | 'accepted'
22
+ | 'queued'
23
+ | 'delivering'
24
+ | 'delivered'
25
+ | 'deferred'
26
+ | 'failed'
27
+ | 'deadLettered';
28
+ export type TCoreMailInboundDeliveryState = 'pending' | 'fetching' | 'fetched' | 'acknowledged';
29
+ export type TCoreMailInboundAckOutcome = 'processed' | 'discarded';
30
+ export type TCoreMailRecipientAction = 'accept' | 'reject' | 'defer';
31
+ export type TCoreMailReconciliationState = 'applying' | 'ready' | 'failed';
32
+
33
+ export type TCoreMailErrorCode =
34
+ | 'AUTHENTICATION_FAILED'
35
+ | 'AUTHENTICATION_EXPIRED'
36
+ | 'AUTHORITY_REVOKED'
37
+ | 'CAPABILITY_DENIED'
38
+ | 'NOT_FOUND'
39
+ | 'INVALID_REQUEST'
40
+ | 'INVALID_MAILBOX'
41
+ | 'INVALID_SENDER'
42
+ | 'INVALID_RECIPIENT'
43
+ | 'INVALID_HEADER'
44
+ | 'PAYLOAD_LIMIT_EXCEEDED'
45
+ | 'IDEMPOTENCY_CONFLICT'
46
+ | 'STATE_CONFLICT'
47
+ | 'TRANSFER_GRANT_EXPIRED'
48
+ | 'TRANSFER_GRANT_REPLAYED'
49
+ | 'TRANSFER_LENGTH_MISMATCH'
50
+ | 'TRANSFER_DIGEST_MISMATCH'
51
+ | 'TRANSFER_ENCODING_UNSUPPORTED'
52
+ | 'OBJECT_INTEGRITY_CONFLICT'
53
+ | 'DELIVERY_NOT_FETCHED'
54
+ | 'GATEWAY_UNAVAILABLE'
55
+ | 'RECONCILIATION_FENCE_MISMATCH';
56
+
57
+ export interface ICoreMailErrorData {
58
+ code: TCoreMailErrorCode;
59
+ retryable: boolean;
60
+ retryAfterMs?: number;
61
+ }
62
+
63
+ export interface ICoreMailMailbox {
64
+ address: string;
65
+ displayName?: string;
66
+ }
67
+
68
+ export interface ICoreMailEnvelope {
69
+ mailFrom: string;
70
+ rcptTo: string[];
71
+ }
72
+
73
+ export interface ICoreMailHeader {
74
+ name: string;
75
+ value: string;
76
+ }
77
+
78
+ export interface ICoreMailContentDescriptor {
79
+ partId: string;
80
+ kind: TCoreMailPartKind;
81
+ contentType: string;
82
+ filename?: string;
83
+ contentId?: string;
84
+ sha256: TCoreMailSha256;
85
+ lengthBytes: number;
86
+ }
87
+
88
+ export interface ICoreMailOutboundMessageDescriptor {
89
+ sender: ICoreMailMailbox;
90
+ recipients: {
91
+ to: ICoreMailMailbox[];
92
+ cc?: ICoreMailMailbox[];
93
+ bcc?: ICoreMailMailbox[];
94
+ };
95
+ replyTo?: ICoreMailMailbox;
96
+ subject: string;
97
+ /** Ordered headers. Transport-owned MIME headers are not accepted here. */
98
+ headers?: ICoreMailHeader[];
99
+ /** Ordered body and attachment descriptors. Bytes move through bounded HTTP transfers. */
100
+ parts: ICoreMailContentDescriptor[];
101
+ }
102
+
103
+ export interface ICoreMailTransferGrant {
104
+ grantId: string;
105
+ method: TCoreMailTransferMethod;
106
+ /** Same-origin path. Callers use the authenticated CoreMail or gateway endpoint they already hold. */
107
+ path: string;
108
+ /** One-time bearer capability. It must never be logged or persisted by a consumer. */
109
+ bearerToken: string;
110
+ sha256: TCoreMailSha256;
111
+ lengthBytes: number;
112
+ contentType: string;
113
+ expiresAt: number;
114
+ }
115
+
116
+ export interface ICoreMailUploadGrant extends ICoreMailTransferGrant {
117
+ method: 'PUT';
118
+ }
119
+
120
+ export interface ICoreMailDownloadGrant extends ICoreMailTransferGrant {
121
+ method: 'GET';
122
+ }
123
+
124
+ export interface ICoreMailPartStatus {
125
+ partId: string;
126
+ state: 'missing' | 'uploading' | 'complete' | 'failed';
127
+ sha256: TCoreMailSha256;
128
+ lengthBytes: number;
129
+ }
130
+
131
+ export interface ICoreMailSubmission {
132
+ submissionId: string;
133
+ idempotencyKey: string;
134
+ submissionDigest: TCoreMailSha256;
135
+ state: TCoreMailSubmissionState;
136
+ parts: ICoreMailPartStatus[];
137
+ transportMessageId?: string;
138
+ attempts: number;
139
+ nextAttemptAt?: number;
140
+ acceptedAt?: number;
141
+ deliveredAt?: number;
142
+ createdAt: number;
143
+ updatedAt: number;
144
+ }
145
+
146
+ export interface ICoreMailInboundDelivery {
147
+ deliveryId: string;
148
+ transportDeliveryId: string;
149
+ state: TCoreMailInboundDeliveryState;
150
+ envelope: ICoreMailEnvelope;
151
+ rawMime: {
152
+ sha256: TCoreMailSha256;
153
+ lengthBytes: number;
154
+ contentType: 'message/rfc822';
155
+ };
156
+ messageId?: string;
157
+ subject?: string;
158
+ receivedAt: number;
159
+ updatedAt: number;
160
+ }
161
+
162
+ export interface ICoreMailBindingCredentialVerifier {
163
+ credentialId: string;
164
+ version: number;
165
+ state: TCoreMailCredentialState;
166
+ /** Password-verifier string only. Plaintext credential material is never part of desired state. */
167
+ verificationHash: string;
168
+ acceptUntil?: number;
169
+ }
170
+
171
+ export interface ICoreMailBindingDesiredState {
172
+ schemaVersion: 1;
173
+ bindingId: string;
174
+ serviceId: string;
175
+ tenantId: string;
176
+ revision: number;
177
+ state: TCoreMailBindingState;
178
+ capabilities: TCoreMailCapability[];
179
+ credentials: ICoreMailBindingCredentialVerifier[];
180
+ allowedSenders: string[];
181
+ inboundRecipients: string[];
182
+ defaultSender?: string;
183
+ limits?: {
184
+ messagesPerMinute?: number;
185
+ messagesPerDay?: number;
186
+ maxPendingInbound?: number;
187
+ };
188
+ }
189
+
190
+ export interface ICoreMailGatewayDesiredState {
191
+ endpointUrl: string;
192
+ credentialId: string;
193
+ credentialVersion: number;
194
+ /** Key in CoreMail's resolved runtime secret material, never a plaintext value. */
195
+ credentialSecretKey: string;
196
+ }
197
+
198
+ export interface ICoreMailDesiredState {
199
+ schemaVersion: 1;
200
+ configEpoch: number;
201
+ bindings: ICoreMailBindingDesiredState[];
202
+ gateway: ICoreMailGatewayDesiredState;
203
+ }
204
+
205
+ export interface ICoreMailReplicaIdentity {
206
+ taskId: string;
207
+ serviceId: string;
208
+ rolloutId: string;
209
+ rolloutGeneration: number;
210
+ imageDigest: TCoreMailSha256;
211
+ }
212
+
213
+ export interface ICoreMailBindingReconciliationStatus {
214
+ bindingId: string;
215
+ revision: number;
216
+ state: TCoreMailBindingState;
217
+ activeSessionsByCredentialVersion: Array<{
218
+ version: number;
219
+ count: number;
220
+ }>;
221
+ }
222
+
223
+ export interface ICoreMailReconciliationStatus {
224
+ replica: ICoreMailReplicaIdentity;
225
+ state: TCoreMailReconciliationState;
226
+ appliedConfigEpoch: number;
227
+ appliedDesiredStateDigest: TCoreMailSha256;
228
+ bindings: ICoreMailBindingReconciliationStatus[];
229
+ updatedAt: number;
230
+ errorCode?: TCoreMailErrorCode;
231
+ }
232
+
233
+ export interface ICoreMailRecipientResolution {
234
+ recipient: string;
235
+ action: TCoreMailRecipientAction;
236
+ /** Opaque, short-lived handle returned only for accepted recipients. */
237
+ routingHandle?: string;
238
+ smtpCode?: number;
239
+ message?: string;
240
+ }
241
+
242
+ export interface ICoreMailGatewayMessageDescriptor {
243
+ envelope: ICoreMailEnvelope;
244
+ rawMime: {
245
+ sha256: TCoreMailSha256;
246
+ lengthBytes: number;
247
+ contentType: 'message/rfc822';
248
+ };
249
+ }
250
+
251
+ export interface ICoreMailGatewayOutboundStatus {
252
+ transportMessageId: string;
253
+ state: Extract<
254
+ TCoreMailSubmissionState,
255
+ 'accepted' | 'queued' | 'delivering' | 'delivered' | 'deferred' | 'failed' | 'deadLettered'
256
+ >;
257
+ attempts: number;
258
+ nextAttemptAt?: number;
259
+ smtpCode?: number;
260
+ updatedAt: number;
261
+ }
262
+
263
+ export const coreMailLimits = Object.freeze({
264
+ controlMessageBytes: 64 * 1024,
265
+ desiredStateBytes: 8 * 1024 * 1024,
266
+ textPartBytes: 1 * 1024 * 1024,
267
+ htmlPartBytes: 2 * 1024 * 1024,
268
+ attachmentCount: 16,
269
+ attachmentBytes: 10 * 1024 * 1024,
270
+ aggregateAttachmentBytes: 17 * 1024 * 1024,
271
+ mimeFramingBytes: 512 * 1024,
272
+ serializedMimeBytes: 30 * 1024 * 1024,
273
+ transferGrantTtlMs: 5 * 60 * 1000,
274
+ transferHeaderTimeoutMs: 10 * 1000,
275
+ transferIdleTimeoutMs: 15 * 1000,
276
+ transferOverallTimeoutMs: 2 * 60 * 1000,
277
+ } as const);
package/ts/data/index.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export * from './cloudlyconfig.js';
2
2
  export * from './cluster.js';
3
3
  export * from './config.js';
4
+ export * from './coremail.js';
4
5
  export * from './deployment.js';
5
6
  export * from './deploymentadoption.js';
6
7
  export * from './deploymentoperation.js';
@@ -3,6 +3,13 @@ import type {
3
3
  IBackupArchiveObject,
4
4
  TIsolatedRestoreResourceMapping,
5
5
  } from './backup.js';
6
+ import {
7
+ canonicalizeStrictJson,
8
+ createCanonicalJsonSha256Hex,
9
+ createSha256Hex,
10
+ deepFreezeValue,
11
+ strictCanonicalJsonRules,
12
+ } from '../private/canonicaljson.js';
6
13
 
7
14
  /** Operations carried by a short-lived, single-operation restore grant. */
8
15
  export const isolatedRestoreGrantOperations = Object.freeze([
@@ -307,15 +314,7 @@ export class IsolatedRestoreContractError extends Error {
307
314
  }
308
315
 
309
316
  /** Cross-runtime rules for restore authority digests. */
310
- export const isolatedRestoreCanonicalJsonRules = Object.freeze({
311
- version: 1 as const,
312
- encoding: 'utf-8' as const,
313
- objectKeyOrdering: 'utf-16-code-unit-ascending' as const,
314
- arrayOrdering: 'preserved-dense' as const,
315
- unicodeNormalization: 'preserved' as const,
316
- numberEncoding: 'safe-integer-json-no-negative-zero' as const,
317
- digest: 'sha256-lowercase-hex' as const,
318
- });
317
+ export const isolatedRestoreCanonicalJsonRules = strictCanonicalJsonRules;
319
318
 
320
319
  const canonicalIdentifierPattern = /^[A-Za-z0-9][A-Za-z0-9._:@-]{0,255}$/;
321
320
  const canonicalScopedIdPattern = /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,191}$/;
@@ -360,23 +359,7 @@ export const assertIsolatedRestoreControlIngressByteLength = (
360
359
  return byteLengthArg;
361
360
  };
362
361
 
363
- const deepFreeze = <T>(valueArg: T, seenArg = new WeakSet<object>()): T => {
364
- if (!valueArg || typeof valueArg !== 'object') {
365
- return valueArg;
366
- }
367
- const object = valueArg as object;
368
- if (ArrayBuffer.isView(object) || seenArg.has(object)) {
369
- return valueArg;
370
- }
371
- seenArg.add(object);
372
- for (const key of Reflect.ownKeys(object)) {
373
- const descriptor = Object.getOwnPropertyDescriptor(object, key);
374
- if (descriptor && Object.hasOwn(descriptor, 'value')) {
375
- deepFreeze(descriptor.value, seenArg);
376
- }
377
- }
378
- return Object.freeze(valueArg);
379
- };
362
+ const deepFreeze = deepFreezeValue;
380
363
 
381
364
  const verifiedIsolatedRestoreGrantCompacts = new WeakMap<object, string>();
382
365
  const verifiedIsolatedRestoreWriteBindings = new WeakSet<object>();
@@ -1419,64 +1402,7 @@ export const normalizeIsolatedRestoreControlStatusRequest = (
1419
1402
  };
1420
1403
 
1421
1404
  const canonicalizeJson = (valueArg: unknown): string => {
1422
- const ancestors = new Set<object>();
1423
-
1424
- const visit = (currentValueArg: unknown, fieldNameArg: string): string => {
1425
- if (currentValueArg === null) {
1426
- return 'null';
1427
- }
1428
- if (typeof currentValueArg === 'boolean') {
1429
- return currentValueArg ? 'true' : 'false';
1430
- }
1431
- if (typeof currentValueArg === 'string') {
1432
- assertUnicodeScalarString(currentValueArg, fieldNameArg);
1433
- return JSON.stringify(currentValueArg);
1434
- }
1435
- if (typeof currentValueArg === 'number') {
1436
- if (
1437
- !Number.isSafeInteger(currentValueArg) ||
1438
- Object.is(currentValueArg, -0)
1439
- ) {
1440
- return fail(`${fieldNameArg} must contain safe integers only`);
1441
- }
1442
- return String(currentValueArg);
1443
- }
1444
- if (typeof currentValueArg !== 'object') {
1445
- return fail(`${fieldNameArg} contains a non-JSON value`);
1446
- }
1447
- if (ancestors.has(currentValueArg)) {
1448
- return fail(`${fieldNameArg} contains a cycle`);
1449
- }
1450
-
1451
- ancestors.add(currentValueArg);
1452
- try {
1453
- if (Array.isArray(currentValueArg)) {
1454
- return `[${currentValueArg
1455
- .map((entryArg, indexArg) =>
1456
- visit(entryArg, `${fieldNameArg}[${indexArg}]`),
1457
- )
1458
- .join(',')}]`;
1459
- }
1460
-
1461
- const record = readRecord(currentValueArg, fieldNameArg);
1462
- const keys = Object.keys(record).sort((leftArg, rightArg) => {
1463
- return leftArg < rightArg ? -1 : leftArg > rightArg ? 1 : 0;
1464
- });
1465
- return `{${keys
1466
- .map((keyArg) => {
1467
- assertUnicodeScalarString(keyArg, `${fieldNameArg} key`);
1468
- return `${JSON.stringify(keyArg)}:${visit(
1469
- record[keyArg],
1470
- `${fieldNameArg}.${keyArg}`,
1471
- )}`;
1472
- })
1473
- .join(',')}}`;
1474
- } finally {
1475
- ancestors.delete(currentValueArg);
1476
- }
1477
- };
1478
-
1479
- return visit(valueArg, 'canonical JSON input');
1405
+ return canonicalizeStrictJson(valueArg, fail);
1480
1406
  };
1481
1407
 
1482
1408
  export const canonicalizeIsolatedRestoreResourceMappings = (
@@ -1618,20 +1544,13 @@ export const canonicalizeIsolatedRestoreAuthorityProjection = (
1618
1544
  };
1619
1545
 
1620
1546
  const createSha256 = async (contentsArg: Uint8Array): Promise<string> => {
1621
- const subtle = globalThis.crypto?.subtle;
1622
- if (!subtle) {
1623
- return fail('Web Crypto SHA-256 is unavailable in this runtime');
1624
- }
1625
- const digest = await subtle.digest('SHA-256', new Uint8Array(contentsArg));
1626
- return Array.from(new Uint8Array(digest), (byteArg) =>
1627
- byteArg.toString(16).padStart(2, '0'),
1628
- ).join('');
1547
+ return createSha256Hex(contentsArg, fail);
1629
1548
  };
1630
1549
 
1631
1550
  const createCanonicalJsonSha256 = async (
1632
1551
  canonicalJsonArg: string,
1633
1552
  ): Promise<string> => {
1634
- return createSha256(new TextEncoder().encode(canonicalJsonArg));
1553
+ return createCanonicalJsonSha256Hex(canonicalJsonArg, fail);
1635
1554
  };
1636
1555
 
1637
1556
  export const createIsolatedRestoreBytesSha256 = async (
@@ -9,6 +9,7 @@ import * as pushnotification from './pushnotification.js';
9
9
  import * as sip from './sip.js';
10
10
  import * as sms from './sms.js';
11
11
  import * as storage from './storage.js';
12
+ import * as storagemigration from './storagemigration.js';
12
13
  import * as types from './types.js';
13
14
 
14
15
  export {
@@ -23,8 +24,10 @@ export {
23
24
  sip,
24
25
  sms,
25
26
  storage,
27
+ storagemigration,
26
28
  types,
27
29
  };
28
30
 
29
31
  export * from './storage.js';
32
+ export * from './storagemigration.js';
30
33
  export * from './types.js';