synomem 0.9.6 → 0.10.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.
Files changed (118) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +5 -3
  3. package/dist/actor-directory.d.ts +34 -0
  4. package/dist/actor-directory.d.ts.map +1 -0
  5. package/dist/actor-directory.js +52 -0
  6. package/dist/actor-directory.js.map +1 -0
  7. package/dist/backend.d.ts +3 -0
  8. package/dist/backend.d.ts.map +1 -1
  9. package/dist/backend.js +26 -3
  10. package/dist/backend.js.map +1 -1
  11. package/dist/bookmarks.d.ts +30 -0
  12. package/dist/bookmarks.d.ts.map +1 -0
  13. package/dist/bookmarks.js +87 -0
  14. package/dist/bookmarks.js.map +1 -0
  15. package/dist/cli.d.ts.map +1 -1
  16. package/dist/cli.js +291 -61
  17. package/dist/cli.js.map +1 -1
  18. package/dist/client.d.ts +115 -4
  19. package/dist/client.d.ts.map +1 -1
  20. package/dist/client.js +854 -138
  21. package/dist/client.js.map +1 -1
  22. package/dist/cursors.d.ts +30 -0
  23. package/dist/cursors.d.ts.map +1 -0
  24. package/dist/cursors.js +101 -0
  25. package/dist/cursors.js.map +1 -0
  26. package/dist/errors.d.ts +1 -1
  27. package/dist/errors.d.ts.map +1 -1
  28. package/dist/errors.js +2 -0
  29. package/dist/errors.js.map +1 -1
  30. package/dist/import.d.ts +605 -39
  31. package/dist/import.d.ts.map +1 -1
  32. package/dist/index.d.ts +19 -1
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +11 -1
  35. package/dist/index.js.map +1 -1
  36. package/dist/mcp/index.d.ts +1 -1
  37. package/dist/mcp/index.d.ts.map +1 -1
  38. package/dist/mcp/index.js +329 -22
  39. package/dist/mcp/index.js.map +1 -1
  40. package/dist/mutation-receipts.d.ts +8 -0
  41. package/dist/mutation-receipts.d.ts.map +1 -0
  42. package/dist/mutation-receipts.js +2 -0
  43. package/dist/mutation-receipts.js.map +1 -0
  44. package/dist/notifications.d.ts +73 -0
  45. package/dist/notifications.d.ts.map +1 -0
  46. package/dist/notifications.js +309 -0
  47. package/dist/notifications.js.map +1 -0
  48. package/dist/participation-repository.d.ts +46 -0
  49. package/dist/participation-repository.d.ts.map +1 -0
  50. package/dist/participation-repository.js +296 -0
  51. package/dist/participation-repository.js.map +1 -0
  52. package/dist/participation.d.ts +97 -0
  53. package/dist/participation.d.ts.map +1 -0
  54. package/dist/participation.js +10 -0
  55. package/dist/participation.js.map +1 -0
  56. package/dist/policy.d.ts +31 -0
  57. package/dist/policy.d.ts.map +1 -0
  58. package/dist/policy.js +71 -0
  59. package/dist/policy.js.map +1 -0
  60. package/dist/ports/repository.d.ts +23 -0
  61. package/dist/ports/repository.d.ts.map +1 -1
  62. package/dist/profiles.d.ts +12 -0
  63. package/dist/profiles.d.ts.map +1 -1
  64. package/dist/profiles.js +2 -1
  65. package/dist/profiles.js.map +1 -1
  66. package/dist/projections.d.ts.map +1 -1
  67. package/dist/projections.js +29 -11
  68. package/dist/projections.js.map +1 -1
  69. package/dist/remote.d.ts +79 -0
  70. package/dist/remote.d.ts.map +1 -1
  71. package/dist/remote.js +65 -6
  72. package/dist/remote.js.map +1 -1
  73. package/dist/schemas.d.ts +746 -58
  74. package/dist/schemas.d.ts.map +1 -1
  75. package/dist/schemas.js +114 -16
  76. package/dist/schemas.js.map +1 -1
  77. package/dist/search.d.ts +25 -0
  78. package/dist/search.d.ts.map +1 -0
  79. package/dist/search.js +2 -0
  80. package/dist/search.js.map +1 -0
  81. package/dist/service.d.ts +89 -0
  82. package/dist/service.d.ts.map +1 -1
  83. package/dist/storage.d.ts +38 -3
  84. package/dist/storage.d.ts.map +1 -1
  85. package/dist/storage.js +494 -365
  86. package/dist/storage.js.map +1 -1
  87. package/dist/types.d.ts +65 -34
  88. package/dist/types.d.ts.map +1 -1
  89. package/docs/cli.md +6 -0
  90. package/docs/human-participation.md +20 -0
  91. package/docs/mcp.md +2 -0
  92. package/docs/storage-format.md +4 -15
  93. package/openapi/synomem-v1.yaml +187 -10
  94. package/package.json +2 -1
  95. package/skills/synomem/SKILL.md +2 -0
  96. package/src/actor-directory.ts +87 -0
  97. package/src/backend.ts +29 -3
  98. package/src/bookmarks.ts +127 -0
  99. package/src/cli.ts +941 -546
  100. package/src/client.ts +1143 -279
  101. package/src/cursors.ts +132 -0
  102. package/src/errors.ts +2 -0
  103. package/src/index.ts +50 -0
  104. package/src/mcp/index.ts +412 -96
  105. package/src/mutation-receipts.ts +7 -0
  106. package/src/notifications.ts +483 -0
  107. package/src/participation-repository.ts +406 -0
  108. package/src/participation.ts +103 -0
  109. package/src/policy.ts +109 -0
  110. package/src/ports/repository.ts +33 -0
  111. package/src/profiles.ts +2 -1
  112. package/src/projections.ts +49 -48
  113. package/src/remote.ts +227 -37
  114. package/src/schemas.ts +116 -16
  115. package/src/search.ts +24 -0
  116. package/src/service.ts +122 -8
  117. package/src/storage.ts +1039 -697
  118. package/src/types.ts +67 -32
package/src/remote.ts CHANGED
@@ -1,3 +1,26 @@
1
+ import type {
2
+ ActorDirectoryInput,
3
+ ActorDirectoryPage,
4
+ ActorProfile,
5
+ ActorProfileInput,
6
+ } from './actor-directory.js';
7
+ import type {
8
+ NotificationInput as HpNotificationInput,
9
+ NotificationPage as HpNotificationPage,
10
+ } from './notifications.js';
11
+ import type {
12
+ ReactionSetInput as HpReactionSetInput,
13
+ ReactionSummary as HpReactionSummary,
14
+ ReplyCreateInput as HpReplyCreateInput,
15
+ ReplyDeleteInput as HpReplyDeleteInput,
16
+ ReplyRecord as HpReplyRecord,
17
+ ThreadInput as HpThreadInput,
18
+ ThreadPage as HpThreadPage,
19
+ } from './participation.js';
20
+ import type { BookmarkInput, BookmarkPage } from './bookmarks.js';
21
+ import type { SearchInput, SearchPage } from './search.js';
22
+ import type { OverrideTaskDecisionInput } from './types.js';
23
+ import type { ActorRef, AddressableActor } from './policy.js';
1
24
  import { actorSchema } from './schemas.js';
2
25
  import { errorCodes, SynomemError, type SynomemErrorCode } from './errors.js';
3
26
  import type { SynomemService, SynomemServiceCapabilities, SynomemServiceInfo } from './service.js';
@@ -117,7 +140,7 @@ function queryString(input: object): string {
117
140
  if (Array.isArray(value)) {
118
141
  for (const item of value) parameters.append(key, String(item));
119
142
  } else {
120
- parameters.set(key, String(value));
143
+ parameters.set(key, typeof value === 'object' ? JSON.stringify(value) : String(value));
121
144
  }
122
145
  }
123
146
  const encoded = parameters.toString();
@@ -195,6 +218,97 @@ export class RemoteSynomemService implements SynomemService {
195
218
  private initialized = false;
196
219
  private cachedCapabilities?: SynomemServiceCapabilities;
197
220
 
221
+ readonly bookmarks = {
222
+ list: (input: BookmarkInput = {}) =>
223
+ this.request<BookmarkPage>('GET', `bookmarks${queryString(input)}`),
224
+ has: (rootId: string) =>
225
+ this.request<{ saved: boolean }>('GET', `items/${encodeURIComponent(rootId)}/bookmark`),
226
+ set: (input: { rootId: string; present: boolean }) =>
227
+ this.request<{ saved: boolean }>(
228
+ input.present ? 'PUT' : 'DELETE',
229
+ `items/${encodeURIComponent(input.rootId)}/bookmark`,
230
+ {},
231
+ ),
232
+ };
233
+ readonly notifications = {
234
+ list: (input: HpNotificationInput = {}) =>
235
+ this.request<HpNotificationPage>('GET', `inbox${queryString(input)}`),
236
+ read: async (id: string) => {
237
+ await this.request('POST', `inbox/${encodeURIComponent(id)}/read`, {});
238
+ },
239
+ dismiss: async (id: string) => {
240
+ await this.request('POST', `inbox/${encodeURIComponent(id)}/dismiss`, {});
241
+ },
242
+ readThrough: async (through: string) => {
243
+ await this.request('POST', 'inbox/read', { through });
244
+ },
245
+ };
246
+ readonly replies = {
247
+ changes: (input: HpThreadInput) =>
248
+ this.request<HpThreadPage>('GET', `replies/changes${queryString(input)}`),
249
+ create: (input: HpReplyCreateInput) =>
250
+ this.request<HpReplyRecord>(
251
+ 'POST',
252
+ `items/${encodeURIComponent(input.rootId)}/replies`,
253
+ input,
254
+ ),
255
+ get: (id: string) => this.request<HpReplyRecord>('GET', `replies/${encodeURIComponent(id)}`),
256
+ delete: (input: HpReplyDeleteInput) =>
257
+ this.request<HpReplyRecord>('DELETE', `replies/${encodeURIComponent(input.replyId)}`, input),
258
+ };
259
+ readonly threads = {
260
+ read: async (input: { rootId: string; through: string }) => {
261
+ await this.request('PUT', `items/${encodeURIComponent(input.rootId)}/thread/read`, {
262
+ through: input.through,
263
+ });
264
+ },
265
+ get: (input: HpThreadInput) =>
266
+ this.request<HpThreadPage>(
267
+ 'GET',
268
+ `items/${encodeURIComponent(input.rootId)}/thread${queryString({ after: input.after, limit: input.limit })}`,
269
+ ),
270
+ subscription: (input: { rootId: string; following: boolean; muted: boolean }) =>
271
+ this.request<void>(
272
+ 'PUT',
273
+ `items/${encodeURIComponent(input.rootId)}/thread/subscription`,
274
+ input,
275
+ ),
276
+ };
277
+ readonly reactions = {
278
+ set: (input: HpReactionSetInput) =>
279
+ this.request<HpReactionSummary>(
280
+ input.present ? 'PUT' : 'DELETE',
281
+ `reactions/${encodeURIComponent(input.targetId)}/${input.code}`,
282
+ input,
283
+ ),
284
+ get: (targetId: string) =>
285
+ this.request<HpReactionSummary>('GET', `reactions/${encodeURIComponent(targetId)}`),
286
+ };
287
+ readonly actors = {
288
+ profile: (input: ActorProfileInput) =>
289
+ this.request<ActorProfile>(
290
+ 'GET',
291
+ `actor-directory/${input.target.kind}/${encodeURIComponent(input.target.id)}/profile${queryString({ after: input.after, limit: input.limit })}`,
292
+ ),
293
+ list: (input: ActorDirectoryInput = {}) =>
294
+ this.request<ActorDirectoryPage>('GET', `actor-directory${queryString(input)}`),
295
+ get: (ref: ActorRef) =>
296
+ this.request<AddressableActor>(
297
+ 'GET',
298
+ `actor-directory/${ref.kind}/${encodeURIComponent(ref.id)}`,
299
+ ),
300
+ registerHuman: async (_input: {
301
+ id: string;
302
+ handle: string;
303
+ displayName: string;
304
+ }): Promise<AddressableActor> => {
305
+ void _input;
306
+ throw new SynomemError(
307
+ 'MUTATION_FORBIDDEN',
308
+ 'Hosted human identities are provisioned through workspace membership.',
309
+ );
310
+ },
311
+ };
198
312
  readonly agents = {
199
313
  create: (input: CreateAgentInput) =>
200
314
  this.request<Awaited<ReturnType<SynomemService['agents']['create']>>>(
@@ -329,24 +443,41 @@ export class RemoteSynomemService implements SynomemService {
329
443
  input,
330
444
  ['postId'],
331
445
  ),
332
- archive: (input: { postId: string; reason?: string; idempotencyKey?: string }) =>
446
+ archive: (input: {
447
+ expectedVersion: number;
448
+ postId: string;
449
+ reason?: string;
450
+ idempotencyKey?: string;
451
+ }) =>
333
452
  this.mutation<Awaited<ReturnType<SynomemService['posts']['archive']>>>(
334
453
  'POST',
335
454
  `posts/${encodeURIComponent(input.postId)}/archive`,
336
455
  input,
337
456
  ['postId'],
338
457
  ),
339
- acknowledge: (input: { postId: string; note?: string; idempotencyKey?: string }) =>
458
+ acknowledge: (input: {
459
+ expectedVersion: number;
460
+ postId: string;
461
+ note?: string;
462
+ idempotencyKey?: string;
463
+ }) =>
340
464
  this.mutation<Awaited<ReturnType<SynomemService['posts']['acknowledge']>>>(
341
465
  'POST',
342
466
  `posts/${encodeURIComponent(input.postId)}/acknowledgment`,
343
467
  input,
344
468
  ['postId'],
345
469
  ),
346
- withdrawAcknowledgment: (input: { postId: string; reason?: string }) =>
347
- this.request<Awaited<ReturnType<SynomemService['posts']['withdrawAcknowledgment']>>>(
470
+ withdrawAcknowledgment: (input: {
471
+ expectedVersion: number;
472
+ postId: string;
473
+ reason?: string;
474
+ idempotencyKey?: string;
475
+ }) =>
476
+ this.mutation<Awaited<ReturnType<SynomemService['posts']['withdrawAcknowledgment']>>>(
348
477
  'DELETE',
349
478
  `posts/${encodeURIComponent(input.postId)}/acknowledgment`,
479
+ input,
480
+ ['postId'],
350
481
  ),
351
482
  roster: (postId: string) =>
352
483
  this.request<Awaited<ReturnType<SynomemService['posts']['roster']>>>(
@@ -373,17 +504,30 @@ export class RemoteSynomemService implements SynomemService {
373
504
  'GET',
374
505
  `kudos/${encodeURIComponent(id)}`,
375
506
  ),
376
- acknowledge: (input: { kudosId: string; note?: string }) =>
377
- this.request<Awaited<ReturnType<SynomemService['kudos']['acknowledge']>>>(
507
+ acknowledge: (input: {
508
+ expectedVersion: number;
509
+ kudosId: string;
510
+ note?: string;
511
+ idempotencyKey?: string;
512
+ }) =>
513
+ this.mutation<Awaited<ReturnType<SynomemService['kudos']['acknowledge']>>>(
378
514
  'POST',
379
515
  `kudos/${encodeURIComponent(input.kudosId)}/acknowledgment`,
380
- input.note === undefined ? {} : { note: input.note },
516
+ input,
517
+ ['kudosId'],
381
518
  ),
382
- revoke: (input: { kudosId: string; reason: string; administrative?: boolean }) =>
383
- this.request<Awaited<ReturnType<SynomemService['kudos']['revoke']>>>(
519
+ revoke: (input: {
520
+ expectedVersion: number;
521
+ kudosId: string;
522
+ reason: string;
523
+ administrative?: boolean;
524
+ idempotencyKey?: string;
525
+ }) =>
526
+ this.mutation<Awaited<ReturnType<SynomemService['kudos']['revoke']>>>(
384
527
  'POST',
385
528
  `kudos/${encodeURIComponent(input.kudosId)}/revocation`,
386
- { reason: input.reason },
529
+ input,
530
+ ['kudosId', 'administrative'],
387
531
  ),
388
532
  };
389
533
 
@@ -400,14 +544,14 @@ export class RemoteSynomemService implements SynomemService {
400
544
  'GET',
401
545
  `memos/${encodeURIComponent(id)}`,
402
546
  ),
403
- read: (input: { memoId: string; idempotencyKey?: string }) =>
547
+ read: (input: { expectedVersion: number; memoId: string; idempotencyKey?: string }) =>
404
548
  this.mutation<Awaited<ReturnType<SynomemService['memos']['read']>>>(
405
549
  'POST',
406
550
  `memos/${encodeURIComponent(input.memoId)}/read`,
407
551
  input,
408
552
  ['memoId'],
409
553
  ),
410
- archive: (input: { memoId: string; idempotencyKey?: string }) =>
554
+ archive: (input: { expectedVersion: number; memoId: string; idempotencyKey?: string }) =>
411
555
  this.mutation<Awaited<ReturnType<SynomemService['memos']['archive']>>>(
412
556
  'POST',
413
557
  `memos/${encodeURIComponent(input.memoId)}/archive`,
@@ -436,7 +580,7 @@ export class RemoteSynomemService implements SynomemService {
436
580
  input,
437
581
  ['noteId'],
438
582
  ),
439
- archive: (input: { noteId: string; idempotencyKey?: string }) =>
583
+ archive: (input: { expectedVersion: number; noteId: string; idempotencyKey?: string }) =>
440
584
  this.mutation<Awaited<ReturnType<SynomemService['notes']['archive']>>>(
441
585
  'POST',
442
586
  `notes/${encodeURIComponent(input.noteId)}/archive`,
@@ -446,6 +590,7 @@ export class RemoteSynomemService implements SynomemService {
446
590
  };
447
591
 
448
592
  readonly tasks = {
593
+ overrideDecision: (input: OverrideTaskDecisionInput) => this.taskTransition('override', input),
449
594
  create: (input: CreateTaskInput) =>
450
595
  this.mutation<Awaited<ReturnType<SynomemService['tasks']['create']>>>('POST', 'tasks', input),
451
596
  list: (input: Omit<ItemListInput, 'kinds'> = {}) =>
@@ -465,16 +610,32 @@ export class RemoteSynomemService implements SynomemService {
465
610
  input,
466
611
  ['taskId'],
467
612
  ),
468
- accept: (input: { taskId: string; response?: string; idempotencyKey?: string }) =>
469
- this.taskTransition('accept', input),
470
- reject: (input: { taskId: string; response: string; idempotencyKey?: string }) =>
471
- this.taskTransition('reject', input),
472
- complete: (input: { taskId: string; note?: string; idempotencyKey?: string }) =>
473
- this.taskTransition('complete', input),
474
- reopen: (input: { taskId: string; idempotencyKey?: string }) =>
613
+ accept: (input: {
614
+ expectedVersion: number;
615
+ taskId: string;
616
+ response?: string;
617
+ idempotencyKey?: string;
618
+ }) => this.taskTransition('accept', input),
619
+ reject: (input: {
620
+ expectedVersion: number;
621
+ taskId: string;
622
+ response: string;
623
+ idempotencyKey?: string;
624
+ }) => this.taskTransition('reject', input),
625
+ complete: (input: {
626
+ expectedVersion: number;
627
+ taskId: string;
628
+ note?: string;
629
+ idempotencyKey?: string;
630
+ }) => this.taskTransition('complete', input),
631
+ reopen: (input: { expectedVersion: number; taskId: string; idempotencyKey?: string }) =>
475
632
  this.taskTransition('reopen', input),
476
- cancel: (input: { taskId: string; reason?: string; idempotencyKey?: string }) =>
477
- this.taskTransition('cancel', input),
633
+ cancel: (input: {
634
+ expectedVersion: number;
635
+ taskId: string;
636
+ reason?: string;
637
+ idempotencyKey?: string;
638
+ }) => this.taskTransition('cancel', input),
478
639
  };
479
640
 
480
641
  /**
@@ -501,13 +662,21 @@ export class RemoteSynomemService implements SynomemService {
501
662
  input,
502
663
  ['todoId'],
503
664
  ),
504
- complete: (input: { todoId: string; note?: string; idempotencyKey?: string }) =>
505
- this.todoTransition('complete', input),
506
- reopen: (input: { todoId: string; idempotencyKey?: string }) =>
665
+ complete: (input: {
666
+ expectedVersion: number;
667
+ todoId: string;
668
+ note?: string;
669
+ idempotencyKey?: string;
670
+ }) => this.todoTransition('complete', input),
671
+ reopen: (input: { expectedVersion: number; todoId: string; idempotencyKey?: string }) =>
507
672
  this.todoTransition('reopen', input),
508
- cancel: (input: { todoId: string; reason?: string; idempotencyKey?: string }) =>
509
- this.todoTransition('cancel', input),
510
- archive: (input: { todoId: string; idempotencyKey?: string }) =>
673
+ cancel: (input: {
674
+ expectedVersion: number;
675
+ todoId: string;
676
+ reason?: string;
677
+ idempotencyKey?: string;
678
+ }) => this.todoTransition('cancel', input),
679
+ archive: (input: { expectedVersion: number; todoId: string; idempotencyKey?: string }) =>
511
680
  this.todoTransition('archive', input),
512
681
  };
513
682
 
@@ -540,6 +709,12 @@ export class RemoteSynomemService implements SynomemService {
540
709
  },
541
710
  };
542
711
 
712
+ async search(input: SearchInput): Promise<SearchPage> {
713
+ const capabilities = await this.capabilities();
714
+ if (!capabilities.participation?.search)
715
+ throw new SynomemError('UNSUPPORTED_BACKEND', 'This workspace does not support search.');
716
+ return this.request('GET', `search${queryString(input)}`);
717
+ }
543
718
  readonly items = {
544
719
  list: (input: ItemListInput = {}) =>
545
720
  this.request<Awaited<ReturnType<SynomemService['items']['list']>>>(
@@ -595,6 +770,11 @@ export class RemoteSynomemService implements SynomemService {
595
770
  if (this.cachedCapabilities.backend !== 'remote') {
596
771
  throw new SynomemError('REMOTE_PROTOCOL', 'The configured server is not a remote backend.');
597
772
  }
773
+ if (this.cachedCapabilities.participation?.version !== 2)
774
+ throw new SynomemError(
775
+ 'UNSUPPORTED_SCHEMA',
776
+ 'This client requires the human participation v2 contract.',
777
+ );
598
778
  const binding = this.cachedCapabilities.binding;
599
779
  if (binding.workspaceId !== this.workspaceId) {
600
780
  throw new SynomemError(
@@ -646,10 +826,8 @@ export class RemoteSynomemService implements SynomemService {
646
826
  }
647
827
 
648
828
  async capabilities(): Promise<SynomemServiceCapabilities> {
649
- return (
650
- this.cachedCapabilities ??
651
- (await this.request<SynomemServiceCapabilities>('GET', '../../capabilities'))
652
- );
829
+ await this.init();
830
+ return this.cachedCapabilities!;
653
831
  }
654
832
 
655
833
  async info(): Promise<SynomemServiceInfo> {
@@ -664,8 +842,9 @@ export class RemoteSynomemService implements SynomemService {
664
842
  }
665
843
 
666
844
  private taskTransition(
667
- transition: 'accept' | 'reject' | 'complete' | 'reopen' | 'cancel',
845
+ transition: 'accept' | 'reject' | 'complete' | 'reopen' | 'cancel' | 'override',
668
846
  input: {
847
+ expectedVersion: number;
669
848
  taskId: string;
670
849
  idempotencyKey?: string;
671
850
  reason?: string;
@@ -683,7 +862,13 @@ export class RemoteSynomemService implements SynomemService {
683
862
 
684
863
  private todoTransition(
685
864
  transition: 'complete' | 'reopen' | 'cancel' | 'archive',
686
- input: { todoId: string; idempotencyKey?: string; reason?: string; note?: string },
865
+ input: {
866
+ expectedVersion: number;
867
+ todoId: string;
868
+ idempotencyKey?: string;
869
+ reason?: string;
870
+ note?: string;
871
+ },
687
872
  ) {
688
873
  return this.mutation<Awaited<ReturnType<SynomemService['todos']['complete']>>>(
689
874
  'POST',
@@ -694,7 +879,7 @@ export class RemoteSynomemService implements SynomemService {
694
879
  }
695
880
 
696
881
  private mutation<T>(
697
- method: 'POST' | 'PATCH',
882
+ method: 'POST' | 'PATCH' | 'DELETE',
698
883
  path: string,
699
884
  input: object,
700
885
  omittedKeys: string[] = [],
@@ -708,11 +893,16 @@ export class RemoteSynomemService implements SynomemService {
708
893
  }
709
894
 
710
895
  private async request<T>(
711
- method: 'GET' | 'POST' | 'PATCH' | 'DELETE',
896
+ method: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE',
712
897
  path: string,
713
898
  body?: object,
714
899
  idempotencyKey?: string,
715
900
  ): Promise<T> {
901
+ if (method !== 'GET') {
902
+ await this.init();
903
+ if (!this.cachedCapabilities?.participation?.canWrite)
904
+ throw new SynomemError('READ_ONLY', 'The selected binding has no write capability.');
905
+ }
716
906
  const accessToken = await this.bearer(this.signal);
717
907
  if (!accessToken) {
718
908
  throw new SynomemError('AUTH_REQUIRED', 'Remote Synomem authentication is required.');