@mdbase-dev/connect-protocol 0.1.0-beta.31 → 0.1.0-beta.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -7,20 +7,25 @@ import type { ApplicationAuthorizationProof } from "./application-authorization.
7
7
  export * from "./connect-problems.generated.js";
8
8
  export * from "./files.js";
9
9
  export * from "./operations.js";
10
+ export * from "./mutation-fingerprint.js";
11
+ export * from "./compatibility.js";
10
12
  export * from "./capabilities.js";
11
13
  export * from "./application-authorization.js";
12
14
  export * from "./type-packs.js";
13
15
  export declare const CONTROL_PROTOCOL_VERSION: 1;
14
- export declare const ENCRYPTED_RELAY_PROTOCOL_VERSION: 1;
15
- export declare const APPLICATION_AUTHORIZATION_PROTOCOL_VERSION: 2;
16
+ export { AUTHORIZATION_BINDING_PROTOCOL_VERSION as APPLICATION_AUTHORIZATION_PROTOCOL_VERSION } from "./compatibility.js";
16
17
  export declare const LOOPBACK_PROTOCOL_VERSION: 1;
17
18
  export declare const DEFAULT_LOOPBACK_PORT: 28485;
18
19
  export declare const RELAY_ENCRYPTION_SUITE: "P256-HKDF-SHA256-AES256GCM";
20
+ export declare const GRANT_ENCRYPTION_PROTOCOL_VERSION: 1;
19
21
  export declare const SYNC_PROTOCOL_VERSION: 1;
20
22
  export declare const CONTRACT_SETUP_CAPABILITY: "contract-setup-v1";
21
23
  export declare const FILE_RELAY_CAPABILITY: "file-relay-v1";
22
- export declare const RELAY_REQUIRED_CAPABILITIES: readonly ["application-authorization-v2", "authorization-activation", "encrypted-relay", "policy-ack"];
23
- export declare const RELAY_CAPABILITIES: readonly ["application-authorization-v2", "authorization-activation", "encrypted-relay", "policy-ack", "contract-setup-v1", "file-relay-v1"];
24
+ export declare const RELAY_REQUIRED_CAPABILITIES: readonly ["application-authorization-v3", "authorization-activation", "encrypted-relay", "policy-ack"];
25
+ export declare const MINIMUM_CONNECTOR_VERSION: "0.1.0-beta.32";
26
+ export declare const HOSTED_PROVIDER_REQUIRED_CAPABILITIES: readonly ["durable-mutation-journal-v1", "durable-file-lifecycle-v1"];
27
+ export declare const HOSTED_PROVIDER_CAPABILITIES: readonly ["durable-mutation-journal-v1", "durable-file-lifecycle-v1", "mutation-replay-after-credential-retirement-v1"];
28
+ export declare const RELAY_CAPABILITIES: readonly ["application-authorization-v3", "authorization-activation", "encrypted-relay", "policy-ack", "contract-setup-v1", "file-relay-v1"];
24
29
  export declare const AUTHORITY_PROOF_VERSION: 1;
25
30
  export declare const AUTHORITY_PROOF_ALGORITHM: "P256-SHA256";
26
31
  export declare const AUTHORITY_PROOF_DOMAIN: "mdbase-authority-request-proof-v1";
@@ -42,7 +47,7 @@ export declare const CONNECT_SCHEMA_IDS: {
42
47
  readonly dataContract: "https://mdbase.dev/schemas/v0.3/data-contract.schema.json";
43
48
  readonly eventActionInterop: "https://mdbase.dev/schemas/interop/v0.1/profile.schema.json";
44
49
  readonly protocol: "https://mdbase.dev/connect/schemas/connect-protocol.v1.json";
45
- readonly encryptedRelay: "https://mdbase.dev/connect/schemas/encrypted-relay.v1.json";
50
+ readonly encryptedRelay: "https://mdbase.dev/connect/schemas/encrypted-relay.v2.json";
46
51
  readonly files: "https://mdbase.dev/connect/schemas/files.v1.json";
47
52
  readonly sync: "https://mdbase.dev/connect/schemas/sync.v1.json";
48
53
  };
@@ -171,7 +176,7 @@ export interface GrantScope {
171
176
  }
172
177
  export interface RelayOperationRequest {
173
178
  type: "operation_request";
174
- protocol_version: 1;
179
+ protocol_version: 2;
175
180
  request_id: string;
176
181
  grant_id: string;
177
182
  collection_id: string;
@@ -181,29 +186,29 @@ export interface RelayOperationRequest {
181
186
  }
182
187
  export type RelayOperationResponse = {
183
188
  type: "operation_response";
184
- protocol_version: 1;
189
+ protocol_version: 2;
185
190
  request_id: string;
186
191
  ok: true;
187
192
  result: unknown;
188
193
  } | {
189
194
  type: "operation_response";
190
- protocol_version: 1;
195
+ protocol_version: 2;
191
196
  request_id: string;
192
197
  ok: false;
193
198
  problem: ConnectProblem;
194
199
  };
195
200
  export interface MdbaseOperationRequest<Input = unknown> {
196
- protocol_version: 1;
201
+ protocol_version: 2;
197
202
  request_id: string;
198
203
  input: Input;
199
204
  }
200
205
  export type MdbaseOperationResponse<Result = unknown> = {
201
- protocol_version: 1;
206
+ protocol_version: 2;
202
207
  request_id: string;
203
208
  ok: true;
204
209
  result: Result;
205
210
  } | {
206
- protocol_version: 1;
211
+ protocol_version: 2;
207
212
  request_id: string;
208
213
  ok: false;
209
214
  problem: ConnectProblem;
@@ -232,7 +237,9 @@ export interface GrantPolicy {
232
237
  application_authorization: ApplicationAuthorizationProof;
233
238
  }
234
239
  /** Presentation-only grant shape used outside a local authorization boundary. */
235
- export type GrantSummary = Omit<GrantPolicy, "application_authorization">;
240
+ export type GrantSummary = Omit<GrantPolicy, "application_authorization"> & {
241
+ contracts: import("./compatibility.js").ConnectContractRequirements;
242
+ };
236
243
  export interface GrantEncryption {
237
244
  protocol_version: 1;
238
245
  suite: typeof RELAY_ENCRYPTION_SUITE;
@@ -244,7 +251,7 @@ export interface GrantEncryption {
244
251
  connector_agreement_public_key: string;
245
252
  }
246
253
  export interface EncryptedRelayEnvelope {
247
- protocol_version: 1;
254
+ protocol_version: 2;
248
255
  suite: typeof RELAY_ENCRYPTION_SUITE;
249
256
  request_id: string;
250
257
  grant_id: string;
@@ -490,18 +497,22 @@ export interface RelayHello {
490
497
  protocol_version: 1;
491
498
  connector_version: string;
492
499
  capabilities: string[];
500
+ contract_support: import("./compatibility.js").ConnectContractSupport;
493
501
  }
494
502
  export interface RelayWelcome {
495
503
  type: "relay_welcome";
496
504
  protocol_version: 1;
497
505
  session_id: string;
498
506
  capabilities: string[];
507
+ contract_support: import("./compatibility.js").ConnectContractSupport;
499
508
  }
500
509
  export interface RelayIncompatible {
501
510
  type: "relay_incompatible";
502
511
  protocol_version: 1;
503
- code: "connector_upgrade_required";
512
+ code: "connector_upgrade_required" | "transport_protocol_incompatible" | "authorization_binding_incompatible" | "capability_contract_incompatible" | "durable_mutation_unsupported";
504
513
  message: string;
514
+ details?: import("./connect-problems.generated.js").ConnectProblemDetailsByCode["transport_protocol_incompatible" | "authorization_binding_incompatible" | "capability_contract_incompatible" | "durable_mutation_unsupported"];
515
+ minimum_connector_version: string;
505
516
  update_url: string;
506
517
  }
507
518
  export interface RelayPolicyApplied {
package/dist/index.js CHANGED
@@ -1,24 +1,35 @@
1
1
  export * from "./connect-problems.generated.js";
2
2
  export * from "./files.js";
3
3
  export * from "./operations.js";
4
+ export * from "./mutation-fingerprint.js";
5
+ export * from "./compatibility.js";
4
6
  export * from "./capabilities.js";
5
7
  export * from "./application-authorization.js";
6
8
  export * from "./type-packs.js";
7
9
  export const CONTROL_PROTOCOL_VERSION = 1;
8
- export const ENCRYPTED_RELAY_PROTOCOL_VERSION = 1;
9
- export const APPLICATION_AUTHORIZATION_PROTOCOL_VERSION = 2;
10
+ export { AUTHORIZATION_BINDING_PROTOCOL_VERSION as APPLICATION_AUTHORIZATION_PROTOCOL_VERSION } from "./compatibility.js";
10
11
  export const LOOPBACK_PROTOCOL_VERSION = 1;
11
12
  export const DEFAULT_LOOPBACK_PORT = 28_485;
12
13
  export const RELAY_ENCRYPTION_SUITE = "P256-HKDF-SHA256-AES256GCM";
14
+ export const GRANT_ENCRYPTION_PROTOCOL_VERSION = 1;
13
15
  export const SYNC_PROTOCOL_VERSION = 1;
14
16
  export const CONTRACT_SETUP_CAPABILITY = "contract-setup-v1";
15
17
  export const FILE_RELAY_CAPABILITY = "file-relay-v1";
16
18
  export const RELAY_REQUIRED_CAPABILITIES = [
17
- "application-authorization-v2",
19
+ "application-authorization-v3",
18
20
  "authorization-activation",
19
21
  "encrypted-relay",
20
22
  "policy-ack"
21
23
  ];
24
+ export const MINIMUM_CONNECTOR_VERSION = "0.1.0-beta.32";
25
+ export const HOSTED_PROVIDER_REQUIRED_CAPABILITIES = [
26
+ "durable-mutation-journal-v1",
27
+ "durable-file-lifecycle-v1"
28
+ ];
29
+ export const HOSTED_PROVIDER_CAPABILITIES = [
30
+ ...HOSTED_PROVIDER_REQUIRED_CAPABILITIES,
31
+ "mutation-replay-after-credential-retirement-v1"
32
+ ];
22
33
  export const RELAY_CAPABILITIES = [
23
34
  ...RELAY_REQUIRED_CAPABILITIES,
24
35
  CONTRACT_SETUP_CAPABILITY,
@@ -62,7 +73,7 @@ export const CONNECT_SCHEMA_IDS = {
62
73
  dataContract: "https://mdbase.dev/schemas/v0.3/data-contract.schema.json",
63
74
  eventActionInterop: "https://mdbase.dev/schemas/interop/v0.1/profile.schema.json",
64
75
  protocol: "https://mdbase.dev/connect/schemas/connect-protocol.v1.json",
65
- encryptedRelay: "https://mdbase.dev/connect/schemas/encrypted-relay.v1.json",
76
+ encryptedRelay: "https://mdbase.dev/connect/schemas/encrypted-relay.v2.json",
66
77
  files: "https://mdbase.dev/connect/schemas/files.v1.json",
67
78
  sync: "https://mdbase.dev/connect/schemas/sync.v1.json"
68
79
  };
@@ -0,0 +1,6 @@
1
+ import { type EncryptedOperation } from "./operations.js";
2
+ export declare const MUTATION_FINGERPRINT_SCHEMA_VERSION: 1;
3
+ export declare const MUTATION_FINGERPRINT_DOMAIN: "mdbase-connect mutation fingerprint v1\0";
4
+ export declare function canonicalMutationInput(input: unknown): string;
5
+ export declare function mutationFingerprintTranscript(operation: EncryptedOperation, input: unknown): Uint8Array<ArrayBuffer>;
6
+ export declare function mutationFingerprint(operation: EncryptedOperation, input: unknown): Promise<string>;
@@ -0,0 +1,116 @@
1
+ import { mutationOperationIdentifier, operationInputSchemaVersion } from "./operations.js";
2
+ export const MUTATION_FINGERPRINT_SCHEMA_VERSION = 1;
3
+ export const MUTATION_FINGERPRINT_DOMAIN = "mdbase-connect mutation fingerprint v1\0";
4
+ const encoder = new TextEncoder();
5
+ export function canonicalMutationInput(input) {
6
+ return canonicalize(input, new Set());
7
+ }
8
+ export function mutationFingerprintTranscript(operation, input) {
9
+ const identifier = mutationOperationIdentifier(operation, input);
10
+ if (identifier === null)
11
+ throw new TypeError("The operation is not a canonical mutation.");
12
+ const identifierBytes = encoder.encode(identifier);
13
+ const inputBytes = encoder.encode(canonicalMutationInput(input));
14
+ const domainBytes = encoder.encode(MUTATION_FINGERPRINT_DOMAIN);
15
+ const transcript = new Uint8Array(domainBytes.length + 4 + 8 + identifierBytes.length + 4 + 8 + inputBytes.length);
16
+ const view = new DataView(transcript.buffer);
17
+ let offset = 0;
18
+ transcript.set(domainBytes, offset);
19
+ offset += domainBytes.length;
20
+ view.setUint32(offset, MUTATION_FINGERPRINT_SCHEMA_VERSION, false);
21
+ offset += 4;
22
+ offset = writeField(transcript, view, offset, identifierBytes);
23
+ view.setUint32(offset, operationInputSchemaVersion(operation, input), false);
24
+ offset += 4;
25
+ writeField(transcript, view, offset, inputBytes);
26
+ return transcript;
27
+ }
28
+ export async function mutationFingerprint(operation, input) {
29
+ const digest = new Uint8Array(await globalThis.crypto.subtle.digest("SHA-256", mutationFingerprintTranscript(operation, input).buffer));
30
+ return base64Url(digest);
31
+ }
32
+ function canonicalize(value, ancestors) {
33
+ if (value === null)
34
+ return "null";
35
+ if (value === true)
36
+ return "true";
37
+ if (value === false)
38
+ return "false";
39
+ if (typeof value === "string") {
40
+ assertUnicodeScalarString(value);
41
+ return JSON.stringify(value);
42
+ }
43
+ if (typeof value === "number") {
44
+ if (!Number.isFinite(value))
45
+ throw new TypeError("Mutation input numbers must be finite.");
46
+ return JSON.stringify(value);
47
+ }
48
+ if (typeof value !== "object") {
49
+ throw new TypeError(`Mutation input contains unsupported ${typeof value}.`);
50
+ }
51
+ if (ancestors.has(value))
52
+ throw new TypeError("Mutation input must not contain cycles.");
53
+ ancestors.add(value);
54
+ try {
55
+ if (Array.isArray(value)) {
56
+ if (Object.getOwnPropertySymbols(value).length > 0) {
57
+ throw new TypeError("Mutation input must not contain symbol properties.");
58
+ }
59
+ const children = [];
60
+ for (let index = 0; index < value.length; index += 1) {
61
+ if (!Object.hasOwn(value, index))
62
+ throw new TypeError("Mutation input arrays must not be sparse.");
63
+ children.push(canonicalize(value[index], ancestors));
64
+ }
65
+ return `[${children.join(",")}]`;
66
+ }
67
+ const prototype = Object.getPrototypeOf(value);
68
+ if (prototype !== Object.prototype && prototype !== null) {
69
+ throw new TypeError("Mutation input objects must be plain JSON objects.");
70
+ }
71
+ if (Object.getOwnPropertySymbols(value).length > 0) {
72
+ throw new TypeError("Mutation input must not contain symbol properties.");
73
+ }
74
+ const entries = Object.entries(value);
75
+ for (const [key] of entries)
76
+ assertUnicodeScalarString(key);
77
+ entries.sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
78
+ return `{${entries.map(([key, child]) => {
79
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
80
+ if (!descriptor || descriptor.get || descriptor.set) {
81
+ throw new TypeError("Mutation input must not contain accessor properties.");
82
+ }
83
+ return `${JSON.stringify(key)}:${canonicalize(child, ancestors)}`;
84
+ }).join(",")}}`;
85
+ }
86
+ finally {
87
+ ancestors.delete(value);
88
+ }
89
+ }
90
+ function assertUnicodeScalarString(value) {
91
+ for (let index = 0; index < value.length; index += 1) {
92
+ const unit = value.charCodeAt(index);
93
+ if (unit >= 0xd800 && unit <= 0xdbff) {
94
+ const next = value.charCodeAt(index + 1);
95
+ if (!(next >= 0xdc00 && next <= 0xdfff)) {
96
+ throw new TypeError("Mutation input strings must not contain lone surrogates.");
97
+ }
98
+ index += 1;
99
+ }
100
+ else if (unit >= 0xdc00 && unit <= 0xdfff) {
101
+ throw new TypeError("Mutation input strings must not contain lone surrogates.");
102
+ }
103
+ }
104
+ }
105
+ function writeField(transcript, view, offset, value) {
106
+ view.setBigUint64(offset, BigInt(value.length), false);
107
+ offset += 8;
108
+ transcript.set(value, offset);
109
+ return offset + value.length;
110
+ }
111
+ function base64Url(value) {
112
+ return btoa(String.fromCharCode(...value))
113
+ .replaceAll("+", "-")
114
+ .replaceAll("/", "_")
115
+ .replace(/=+$/u, "");
116
+ }
@@ -1,9 +1,14 @@
1
- /**
2
- * The executable collection-operation contract. Runtime parsers, capability
3
- * compilation, and public types must all consume this value; do not maintain a
4
- * second operation allow-list at an SDK or service boundary.
5
- */
1
+ export declare const OPERATION_CATALOG_VERSION: 1;
6
2
  export declare const COLLECTION_OPERATIONS: readonly ["describe", "changes", "read", "query", "list_views", "execute_view", "read_view_source", "create_view_source", "update_view_source", "delete_view_source", "validate", "create", "update", "delete", "rename", "read_type", "create_type", "update_type", "assess_type_pack", "apply_type_pack", "list_timers", "put_timer", "cancel_timer", "reconcile_timers", "sync"];
7
3
  export type CollectionOperation = typeof COLLECTION_OPERATIONS[number];
4
+ export declare const FILE_CONTROL_MESSAGE_TYPES: readonly ["list_files", "open_file_upload", "open_file_download", "move_file", "delete_file", "get_file_transfer_status", "commit_file_upload", "abort_file_transfer"];
5
+ export type FileControlMessageType = typeof FILE_CONTROL_MESSAGE_TYPES[number];
6
+ export type EncryptedOperation = CollectionOperation | "file_control";
7
+ export declare const MUTATING_OPERATION_IDENTIFIERS: readonly ["create_view_source", "update_view_source", "delete_view_source", "create", "update", "delete", "rename", "create_type", "update_type", "apply_type_pack", "put_timer", "cancel_timer", "reconcile_timers", "sync:mutate", "file_control:open_file_upload", "file_control:move_file", "file_control:delete_file", "file_control:commit_file_upload", "file_control:abort_file_transfer"];
8
+ export type MutationOperationIdentifier = typeof MUTATING_OPERATION_IDENTIFIERS[number];
8
9
  export declare function isCollectionOperation(value: string): value is CollectionOperation;
9
10
  export declare function areCollectionOperations(values: readonly string[]): values is readonly CollectionOperation[];
11
+ export declare function isFileControlMessageType(value: string): value is FileControlMessageType;
12
+ export declare function operationInputSchemaVersion(operation: EncryptedOperation, input: unknown): number;
13
+ export declare function mutationOperationIdentifier(operation: EncryptedOperation, input: unknown): MutationOperationIdentifier | null;
14
+ export declare function isMutatingOperation(operation: EncryptedOperation, input: unknown): boolean;
@@ -1,8 +1,5 @@
1
- /**
2
- * The executable collection-operation contract. Runtime parsers, capability
3
- * compilation, and public types must all consume this value; do not maintain a
4
- * second operation allow-list at an SDK or service boundary.
5
- */
1
+ // Generated by scripts/generate-operation-catalog.mjs. Do not edit directly.
2
+ export const OPERATION_CATALOG_VERSION = 1;
6
3
  export const COLLECTION_OPERATIONS = [
7
4
  "describe",
8
5
  "changes",
@@ -30,10 +27,148 @@ export const COLLECTION_OPERATIONS = [
30
27
  "reconcile_timers",
31
28
  "sync"
32
29
  ];
30
+ export const FILE_CONTROL_MESSAGE_TYPES = [
31
+ "list_files",
32
+ "open_file_upload",
33
+ "open_file_download",
34
+ "move_file",
35
+ "delete_file",
36
+ "get_file_transfer_status",
37
+ "commit_file_upload",
38
+ "abort_file_transfer"
39
+ ];
40
+ const COLLECTION_MUTATION_RULES = {
41
+ "describe": "never",
42
+ "changes": "never",
43
+ "read": "never",
44
+ "query": "never",
45
+ "list_views": "never",
46
+ "execute_view": "never",
47
+ "read_view_source": "never",
48
+ "create_view_source": "always",
49
+ "update_view_source": "always",
50
+ "delete_view_source": "always",
51
+ "validate": "never",
52
+ "create": "always",
53
+ "update": "always",
54
+ "delete": "always",
55
+ "rename": "always",
56
+ "read_type": "never",
57
+ "create_type": "always",
58
+ "update_type": "always",
59
+ "assess_type_pack": "never",
60
+ "apply_type_pack": "always",
61
+ "list_timers": "never",
62
+ "put_timer": "always",
63
+ "cancel_timer": "always",
64
+ "reconcile_timers": "always",
65
+ "sync": "sync_action"
66
+ };
67
+ const COLLECTION_INPUT_SCHEMA_VERSIONS = {
68
+ "describe": 1,
69
+ "changes": 1,
70
+ "read": 1,
71
+ "query": 1,
72
+ "list_views": 1,
73
+ "execute_view": 1,
74
+ "read_view_source": 1,
75
+ "create_view_source": 1,
76
+ "update_view_source": 1,
77
+ "delete_view_source": 1,
78
+ "validate": 1,
79
+ "create": 1,
80
+ "update": 1,
81
+ "delete": 1,
82
+ "rename": 1,
83
+ "read_type": 1,
84
+ "create_type": 1,
85
+ "update_type": 1,
86
+ "assess_type_pack": 1,
87
+ "apply_type_pack": 1,
88
+ "list_timers": 1,
89
+ "put_timer": 1,
90
+ "cancel_timer": 1,
91
+ "reconcile_timers": 1,
92
+ "sync": 1
93
+ };
94
+ const FILE_CONTROL_INPUT_SCHEMA_VERSIONS = {
95
+ "list_files": 1,
96
+ "open_file_upload": 1,
97
+ "open_file_download": 1,
98
+ "move_file": 1,
99
+ "delete_file": 1,
100
+ "get_file_transfer_status": 1,
101
+ "commit_file_upload": 1,
102
+ "abort_file_transfer": 1
103
+ };
104
+ const MUTATING_FILE_CONTROL_MESSAGE_TYPES = new Set([
105
+ "open_file_upload",
106
+ "move_file",
107
+ "delete_file",
108
+ "commit_file_upload",
109
+ "abort_file_transfer"
110
+ ]);
111
+ export const MUTATING_OPERATION_IDENTIFIERS = [
112
+ "create_view_source",
113
+ "update_view_source",
114
+ "delete_view_source",
115
+ "create",
116
+ "update",
117
+ "delete",
118
+ "rename",
119
+ "create_type",
120
+ "update_type",
121
+ "apply_type_pack",
122
+ "put_timer",
123
+ "cancel_timer",
124
+ "reconcile_timers",
125
+ "sync:mutate",
126
+ "file_control:open_file_upload",
127
+ "file_control:move_file",
128
+ "file_control:delete_file",
129
+ "file_control:commit_file_upload",
130
+ "file_control:abort_file_transfer"
131
+ ];
33
132
  const COLLECTION_OPERATION_SET = new Set(COLLECTION_OPERATIONS);
133
+ const FILE_CONTROL_MESSAGE_TYPE_SET = new Set(FILE_CONTROL_MESSAGE_TYPES);
34
134
  export function isCollectionOperation(value) {
35
135
  return COLLECTION_OPERATION_SET.has(value);
36
136
  }
37
137
  export function areCollectionOperations(values) {
38
138
  return values.every(isCollectionOperation);
39
139
  }
140
+ export function isFileControlMessageType(value) {
141
+ return FILE_CONTROL_MESSAGE_TYPE_SET.has(value);
142
+ }
143
+ export function operationInputSchemaVersion(operation, input) {
144
+ if (operation === "file_control") {
145
+ const type = isObject(input) && typeof input.type === "string" ? input.type : "";
146
+ if (!isFileControlMessageType(type))
147
+ throw new Error(`Unknown file-control message: ${type}`);
148
+ return FILE_CONTROL_INPUT_SCHEMA_VERSIONS[type];
149
+ }
150
+ return COLLECTION_INPUT_SCHEMA_VERSIONS[operation];
151
+ }
152
+ export function mutationOperationIdentifier(operation, input) {
153
+ const object = isObject(input) ? input : {};
154
+ if (object.dry_run === true)
155
+ return null;
156
+ if (operation === "file_control") {
157
+ const type = typeof object.type === "string" ? object.type : "";
158
+ return MUTATING_FILE_CONTROL_MESSAGE_TYPES.has(type)
159
+ ? `file_control:${type}`
160
+ : null;
161
+ }
162
+ const rule = COLLECTION_MUTATION_RULES[operation];
163
+ if (rule === "always")
164
+ return operation;
165
+ if (rule === "sync_action" && object.action === "mutate")
166
+ return "sync:mutate";
167
+ return null;
168
+ }
169
+ export function isMutatingOperation(operation, input) {
170
+ return mutationOperationIdentifier(operation, input) !== null;
171
+ }
172
+ function isObject(value) {
173
+ return value !== null && typeof value === "object" && !Array.isArray(value);
174
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mdbase-dev/connect-protocol",
3
- "version": "0.1.0-beta.31",
3
+ "version": "0.1.0-beta.32",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -36,7 +36,7 @@
36
36
  },
37
37
  "scripts": {
38
38
  "build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.json",
39
- "test": "node --test test/*.test.mjs && node ../../scripts/generate-connect-problems.mjs --check",
39
+ "test": "node --test test/*.test.mjs && node ../../scripts/generate-connect-problems.mjs --check && node ../../scripts/generate-operation-catalog.mjs --check",
40
40
  "typecheck": "tsc -p tsconfig.json --noEmit"
41
41
  }
42
42
  }
@@ -7,7 +7,7 @@
7
7
  "connect_protocol": "./connect-protocol.v1.schema.json",
8
8
  "connect_problem": "./connect-problem.v1.schema.json",
9
9
  "connect_problem_catalog": "./connect-problems.v1.catalog.json",
10
- "encrypted_relay": "./encrypted-relay.v1.schema.json",
10
+ "encrypted_relay": "./encrypted-relay.v2.schema.json",
11
11
  "files": "./files.v1.schema.json",
12
12
  "notification_webhook": "./notification-webhook.v1.schema.json",
13
13
  "sync": "./sync.v1.schema.json"