spectrum-ts 1.5.0 → 1.7.2

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.
@@ -128,7 +128,7 @@ var mapPhoneType = (prop) => {
128
128
  if (types.length > 0) {
129
129
  return "other";
130
130
  }
131
- return void 0;
131
+ return;
132
132
  };
133
133
  var mapSimpleType = (prop) => {
134
134
  const types = paramTypes(prop);
@@ -141,14 +141,14 @@ var mapSimpleType = (prop) => {
141
141
  if (types.length > 0) {
142
142
  return "other";
143
143
  }
144
- return void 0;
144
+ return;
145
145
  };
146
146
  var splitStructured = (value) => value.split(";").map((part) => part.trim());
147
147
  var extractName = (card) => {
148
148
  const fn = propString(card.data.fn);
149
149
  const n = propString(card.data.n);
150
150
  if (!(fn || n)) {
151
- return void 0;
151
+ return;
152
152
  }
153
153
  const result = {};
154
154
  if (fn) {
@@ -177,7 +177,7 @@ var extractName = (card) => {
177
177
  var extractPhones = (card) => {
178
178
  const props = asPropertyArray(card.data.tel);
179
179
  if (props.length === 0) {
180
- return void 0;
180
+ return;
181
181
  }
182
182
  return props.map((p) => {
183
183
  const entry = { value: p.valueOf().trim() };
@@ -191,7 +191,7 @@ var extractPhones = (card) => {
191
191
  var extractEmails = (card) => {
192
192
  const props = asPropertyArray(card.data.email);
193
193
  if (props.length === 0) {
194
- return void 0;
194
+ return;
195
195
  }
196
196
  return props.map((p) => {
197
197
  const entry = { value: p.valueOf().trim() };
@@ -205,7 +205,7 @@ var extractEmails = (card) => {
205
205
  var extractAddresses = (card) => {
206
206
  const props = asPropertyArray(card.data.adr);
207
207
  if (props.length === 0) {
208
- return void 0;
208
+ return;
209
209
  }
210
210
  return props.map((p) => {
211
211
  const [, , street, city, region, postalCode, country] = splitStructured(
@@ -238,7 +238,7 @@ var extractOrg = (card) => {
238
238
  const orgStr = propString(card.data.org);
239
239
  const title = propString(card.data.title);
240
240
  if (!(orgStr || title)) {
241
- return void 0;
241
+ return;
242
242
  }
243
243
  const result = {};
244
244
  if (orgStr) {
@@ -258,7 +258,7 @@ var extractOrg = (card) => {
258
258
  var extractUrls = (card) => {
259
259
  const props = asPropertyArray(card.data.url);
260
260
  if (props.length === 0) {
261
- return void 0;
261
+ return;
262
262
  }
263
263
  return props.map((p) => p.valueOf().trim());
264
264
  };
@@ -276,7 +276,7 @@ var DATA_URI_PATTERN = /^data:([^;,]+);base64,(.*)$/i;
276
276
  var extractPhoto = (card) => {
277
277
  const [prop] = asPropertyArray(card.data.photo);
278
278
  if (!prop) {
279
- return void 0;
279
+ return;
280
280
  }
281
281
  const value = prop.valueOf();
282
282
  const dataUriMatch = DATA_URI_PATTERN.exec(value);
@@ -362,7 +362,7 @@ var phoneTypeParam = (type) => {
362
362
  if (type === "home" || type === "work" || type === "other") {
363
363
  return type.toUpperCase();
364
364
  }
365
- return void 0;
365
+ return;
366
366
  };
367
367
  var simpleTypeParam = (type) => type ? type.toUpperCase() : void 0;
368
368
  var photoTypeParam = (mimeType) => {
@@ -571,19 +571,96 @@ var resolveContents = (items) => Promise.all(
571
571
  items.map((c) => typeof c === "string" ? text(c).build() : c.build())
572
572
  );
573
573
 
574
- // src/content/reaction.ts
574
+ // src/content/edit.ts
575
575
  import z6 from "zod";
576
576
  var isMessage = (v) => typeof v === "object" && v !== null && "id" in v && "content" in v;
577
- var reactionSchema = z6.object({
578
- type: z6.literal("reaction"),
579
- emoji: z6.string().min(1),
577
+ var isContent = (v) => typeof v === "object" && v !== null && "type" in v && typeof v.type === "string";
578
+ var editSchema = z6.object({
579
+ type: z6.literal("edit"),
580
+ content: z6.custom(isContent, {
581
+ message: "edit content must be a Content value"
582
+ }),
580
583
  target: z6.custom(isMessage, {
584
+ message: "edit target must be a Message"
585
+ })
586
+ });
587
+ var asEdit = (input) => editSchema.parse({ type: "edit", ...input });
588
+ function edit(content, target) {
589
+ return {
590
+ build: async () => {
591
+ const [resolved] = await resolveContents([content]);
592
+ if (!resolved) {
593
+ throw new Error("edit() requires content");
594
+ }
595
+ if (resolved.type === "edit" || resolved.type === "reply" || resolved.type === "reaction" || resolved.type === "group" || resolved.type === "typing") {
596
+ throw new Error(`edit() cannot wrap "${resolved.type}" content`);
597
+ }
598
+ return asEdit({ content: resolved, target });
599
+ }
600
+ };
601
+ }
602
+
603
+ // src/content/reaction.ts
604
+ import z7 from "zod";
605
+ var isMessage2 = (v) => typeof v === "object" && v !== null && "id" in v && "content" in v;
606
+ var reactionSchema = z7.object({
607
+ type: z7.literal("reaction"),
608
+ emoji: z7.string().min(1),
609
+ target: z7.custom(isMessage2, {
581
610
  message: "reaction target must be a Message"
582
611
  })
583
612
  });
584
613
  var asReaction = (input) => reactionSchema.parse({ type: "reaction", ...input });
585
614
  function reaction(emoji, target) {
586
- return { build: async () => asReaction({ emoji, target }) };
615
+ return {
616
+ build: async () => {
617
+ if (target.content.type === "reaction") {
618
+ throw new Error('reaction() cannot target "reaction" content');
619
+ }
620
+ return asReaction({ emoji, target });
621
+ }
622
+ };
623
+ }
624
+
625
+ // src/content/reply.ts
626
+ import z8 from "zod";
627
+ var isMessage3 = (v) => typeof v === "object" && v !== null && "id" in v && "content" in v;
628
+ var isContent2 = (v) => typeof v === "object" && v !== null && "type" in v && typeof v.type === "string";
629
+ var replySchema = z8.object({
630
+ type: z8.literal("reply"),
631
+ content: z8.custom(isContent2, {
632
+ message: "reply content must be a Content value"
633
+ }),
634
+ target: z8.custom(isMessage3, {
635
+ message: "reply target must be a Message"
636
+ })
637
+ });
638
+ var asReply = (input) => replySchema.parse({ type: "reply", ...input });
639
+ function reply(content, target) {
640
+ return {
641
+ build: async () => {
642
+ const [resolved] = await resolveContents([content]);
643
+ if (!resolved) {
644
+ throw new Error("reply() requires content");
645
+ }
646
+ if (resolved.type === "reply" || resolved.type === "edit" || resolved.type === "reaction" || resolved.type === "group" || resolved.type === "typing") {
647
+ throw new Error(`reply() cannot wrap "${resolved.type}" content`);
648
+ }
649
+ return asReply({ content: resolved, target });
650
+ }
651
+ };
652
+ }
653
+
654
+ // src/content/typing.ts
655
+ import z9 from "zod";
656
+ var typingSchema = z9.object({
657
+ type: z9.literal("typing"),
658
+ state: z9.enum(["start", "stop"])
659
+ });
660
+ function typing(state = "start") {
661
+ return {
662
+ build: async () => typingSchema.parse({ type: "typing", state })
663
+ };
587
664
  }
588
665
 
589
666
  // src/utils/errors.ts
@@ -649,6 +726,28 @@ var supportsAnsiColor = () => {
649
726
  }
650
727
  return Boolean(process.stderr?.isTTY);
651
728
  };
729
+ var FIRE_AND_FORGET_TYPES = /* @__PURE__ */ new Set([
730
+ "reaction",
731
+ "typing",
732
+ "edit"
733
+ ]);
734
+ var isFireAndForget = (item) => FIRE_AND_FORGET_TYPES.has(item.type) || item.__fireAndForget === true;
735
+ var RESERVED_SPACE_KEYS = /* @__PURE__ */ new Set([
736
+ "__platform",
737
+ "id",
738
+ "send",
739
+ "edit",
740
+ "getMessage",
741
+ "startTyping",
742
+ "stopTyping",
743
+ "responding"
744
+ ]);
745
+ var warnReservedAction = (name, platform) => {
746
+ const body = `[spectrum-ts] ${platform} declared space action "${name}" which collides with a reserved Space key; skipping.`;
747
+ console.warn(
748
+ supportsAnsiColor() ? `${ANSI_YELLOW}${body}${ANSI_RESET}` : body
749
+ );
750
+ };
652
751
  var warnUnsupported = (err, fallbackPlatform) => {
653
752
  const platform = err.platform ?? fallbackPlatform;
654
753
  const subject = err.kind === "content" ? `content type "${err.contentType ?? "unknown"}"` : `action "${err.action ?? "unknown"}"`;
@@ -667,6 +766,9 @@ var findUnsupportedPlatformContent = (content, platform) => {
667
766
  if (scopedPlatform && scopedPlatform !== platform) {
668
767
  return scopedPlatform;
669
768
  }
769
+ if (content.type === "reply" || content.type === "edit") {
770
+ return findUnsupportedPlatformContent(content.content, platform);
771
+ }
670
772
  if (content.type !== "group") {
671
773
  return;
672
774
  }
@@ -744,6 +846,16 @@ var wrapNestedContent = (content, ctx, direction) => {
744
846
  }
745
847
  return content;
746
848
  }
849
+ if (content.type === "edit") {
850
+ const target = content.target;
851
+ if (isRawProviderRecord(target)) {
852
+ return {
853
+ ...content,
854
+ target: wrapProviderMessage(target, ctx, "outbound")
855
+ };
856
+ }
857
+ return content;
858
+ }
747
859
  if (content.type === "group") {
748
860
  const items = content.items.map((item) => {
749
861
  const raw = item;
@@ -764,29 +876,8 @@ var isRawProviderRecord = (v) => {
764
876
  return "id" in record && "content" in record && typeof record.react !== "function" && typeof record.reply !== "function";
765
877
  };
766
878
  function buildSpace(params) {
767
- const { spaceRef, extras, typingCtx, definition, client, config, store } = params;
879
+ const { spaceRef, extras, actionCtx, definition, client, config, store } = params;
768
880
  let space;
769
- async function dispatchReaction(item) {
770
- try {
771
- if (!definition.actions.reactToMessage) {
772
- throw UnsupportedError.action("react", definition.name);
773
- }
774
- await definition.actions.reactToMessage({
775
- space: spaceRef,
776
- target: item.target,
777
- reaction: item.emoji,
778
- client,
779
- config,
780
- store
781
- });
782
- } catch (err) {
783
- if (err instanceof UnsupportedError) {
784
- warnUnsupported(err, definition.name);
785
- return;
786
- }
787
- throw err;
788
- }
789
- }
790
881
  async function dispatchSend(item) {
791
882
  let raw;
792
883
  try {
@@ -797,8 +888,8 @@ function buildSpace(params) {
797
888
  if (platformError) {
798
889
  throw platformError;
799
890
  }
800
- raw = await definition.actions.send({
801
- ...typingCtx,
891
+ raw = await definition.send({
892
+ ...actionCtx,
802
893
  content: item
803
894
  });
804
895
  } catch (err) {
@@ -809,6 +900,9 @@ function buildSpace(params) {
809
900
  throw err;
810
901
  }
811
902
  if (!raw?.id) {
903
+ if (isFireAndForget(item)) {
904
+ return;
905
+ }
812
906
  throw new Error(
813
907
  `Platform "${definition.name}" send did not return a message id`
814
908
  );
@@ -823,10 +917,6 @@ function buildSpace(params) {
823
917
  const resolved = await resolveContents(content);
824
918
  const results = [];
825
919
  for (const item of resolved) {
826
- if (item.type === "reaction") {
827
- await dispatchReaction(item);
828
- continue;
829
- }
830
920
  const sent = await dispatchSend(item);
831
921
  if (sent) {
832
922
  results.push(sent);
@@ -838,7 +928,8 @@ function buildSpace(params) {
838
928
  return results;
839
929
  }
840
930
  async function getMessageImpl(id) {
841
- if (!definition.actions.getMessage) {
931
+ const getMessage = definition.actions?.getMessage;
932
+ if (!getMessage) {
842
933
  warnUnsupported(
843
934
  UnsupportedError.action("getMessage", definition.name),
844
935
  definition.name
@@ -847,7 +938,7 @@ function buildSpace(params) {
847
938
  }
848
939
  let raw;
849
940
  try {
850
- raw = await definition.actions.getMessage({
941
+ raw = await getMessage({
851
942
  space: spaceRef,
852
943
  messageId: id,
853
944
  client,
@@ -870,26 +961,38 @@ function buildSpace(params) {
870
961
  "inbound"
871
962
  );
872
963
  }
964
+ const platformActions = {};
965
+ const declaredActions = definition.space.actions;
966
+ if (declaredActions) {
967
+ for (const [name, factory] of Object.entries(declaredActions)) {
968
+ if (RESERVED_SPACE_KEYS.has(name)) {
969
+ warnReservedAction(name, definition.name);
970
+ continue;
971
+ }
972
+ platformActions[name] = (...args) => space.send(factory(...args));
973
+ }
974
+ }
873
975
  space = {
874
976
  ...extras,
875
977
  ...spaceRef,
978
+ ...platformActions,
876
979
  send: sendImpl,
877
980
  edit: async (message, newContent) => {
878
- await message.edit(newContent);
981
+ await space.send(edit(newContent, message));
879
982
  },
880
983
  getMessage: getMessageImpl,
881
984
  startTyping: async () => {
882
- await definition.actions.startTyping?.(typingCtx);
985
+ await space.send(typing("start"));
883
986
  },
884
987
  stopTyping: async () => {
885
- await definition.actions.stopTyping?.(typingCtx);
988
+ await space.send(typing("stop"));
886
989
  },
887
990
  responding: async (fn) => {
888
- await definition.actions.startTyping?.(typingCtx);
991
+ await space.send(typing("start"));
889
992
  try {
890
993
  return await fn();
891
994
  } finally {
892
- await definition.actions.stopTyping?.(typingCtx).catch(() => {
995
+ await space.send(typing("stop")).catch(() => {
893
996
  });
894
997
  }
895
998
  }
@@ -897,37 +1000,11 @@ function buildSpace(params) {
897
1000
  return space;
898
1001
  }
899
1002
  function buildMessage(params) {
900
- const { definition, client, config, spaceRef, space, store } = params;
1003
+ const { definition, space } = params;
901
1004
  let self;
902
- const react = async (reaction2) => {
903
- if (!definition.actions.reactToMessage) {
904
- warnUnsupported(
905
- UnsupportedError.action("react", definition.name),
906
- definition.name
907
- );
908
- return;
909
- }
910
- if (!self) {
911
- throw new Error(
912
- "react() called before message construction completed (internal bug)"
913
- );
914
- }
915
- try {
916
- await definition.actions.reactToMessage({
917
- space: spaceRef,
918
- target: self,
919
- reaction: reaction2,
920
- client,
921
- config,
922
- store
923
- });
924
- } catch (err) {
925
- if (err instanceof UnsupportedError) {
926
- warnUnsupported(err, definition.name);
927
- return;
928
- }
929
- throw err;
930
- }
1005
+ const react = async (emoji) => {
1006
+ const target = requireBuiltMessage("react");
1007
+ await space.send(reaction(emoji, target));
931
1008
  };
932
1009
  const requireBuiltMessage = (action) => {
933
1010
  if (!self) {
@@ -937,65 +1014,10 @@ function buildMessage(params) {
937
1014
  }
938
1015
  return self;
939
1016
  };
940
- const dispatchReplyItem = async (item, target, replyToMessage) => {
941
- let raw;
942
- try {
943
- const platformError = unsupportedPlatformContentError(
944
- item,
945
- definition.name
946
- );
947
- if (platformError) {
948
- throw platformError;
949
- }
950
- raw = await replyToMessage({
951
- space: spaceRef,
952
- messageId: params.id,
953
- target,
954
- content: item,
955
- client,
956
- config,
957
- store
958
- });
959
- } catch (err) {
960
- if (err instanceof UnsupportedError) {
961
- warnUnsupported(err, definition.name);
962
- return;
963
- }
964
- throw err;
965
- }
966
- if (!raw?.id) {
967
- throw new Error(
968
- `Platform "${definition.name}" reply did not return a message id`
969
- );
970
- }
971
- return wrapProviderMessage(
972
- raw,
973
- { client, config, definition, space, spaceRef, store },
974
- "outbound"
975
- );
976
- };
977
- async function reply(...content) {
978
- const replyToMessage = definition.actions.replyToMessage;
979
- if (!replyToMessage) {
980
- warnUnsupported(
981
- UnsupportedError.action("reply", definition.name),
982
- definition.name
983
- );
984
- return content.length === 1 ? void 0 : [];
985
- }
986
- const resolved = await resolveContents(content);
1017
+ async function reply2(...content) {
987
1018
  const target = requireBuiltMessage("reply");
988
- const results = [];
989
- for (const item of resolved) {
990
- const sent = await dispatchReplyItem(item, target, replyToMessage);
991
- if (sent) {
992
- results.push(sent);
993
- }
994
- }
995
- if (content.length === 1) {
996
- return results[0];
997
- }
998
- return results;
1019
+ const wrapped = content.map((c) => reply(c, target));
1020
+ return space.send(...wrapped);
999
1021
  }
1000
1022
  const senderWithPlatform = params.sender === void 0 ? void 0 : { ...params.sender, __platform: definition.name };
1001
1023
  if (params.direction === "outbound") {
@@ -1006,43 +1028,10 @@ function buildMessage(params) {
1006
1028
  direction: "outbound",
1007
1029
  platform: definition.name,
1008
1030
  react,
1009
- reply,
1031
+ reply: reply2,
1010
1032
  edit: async (newContent) => {
1011
- if (!definition.actions.editMessage) {
1012
- warnUnsupported(
1013
- UnsupportedError.action("edit", definition.name),
1014
- definition.name
1015
- );
1016
- return;
1017
- }
1018
- const [resolved] = await resolveContents([newContent]);
1019
- if (!resolved) {
1020
- return;
1021
- }
1022
- const platformError = unsupportedPlatformContentError(
1023
- resolved,
1024
- definition.name
1025
- );
1026
- if (platformError) {
1027
- warnUnsupported(platformError, definition.name);
1028
- return;
1029
- }
1030
- try {
1031
- await definition.actions.editMessage({
1032
- space: spaceRef,
1033
- messageId: params.id,
1034
- content: resolved,
1035
- client,
1036
- config,
1037
- store
1038
- });
1039
- } catch (err) {
1040
- if (err instanceof UnsupportedError) {
1041
- warnUnsupported(err, definition.name);
1042
- return;
1043
- }
1044
- throw err;
1045
- }
1033
+ const target = requireBuiltMessage("edit");
1034
+ await space.send(edit(newContent, target));
1046
1035
  },
1047
1036
  sender: senderWithPlatform,
1048
1037
  space,
@@ -1058,7 +1047,7 @@ function buildMessage(params) {
1058
1047
  direction: "inbound",
1059
1048
  platform: definition.name,
1060
1049
  react,
1061
- reply,
1050
+ reply: reply2,
1062
1051
  sender: senderWithPlatform,
1063
1052
  space,
1064
1053
  timestamp: params.timestamp
@@ -1069,9 +1058,7 @@ function buildMessage(params) {
1069
1058
 
1070
1059
  // src/platform/define.ts
1071
1060
  function createPlatformInstance(def, runtime) {
1072
- const isPlatformUser = (value) => {
1073
- return typeof value === "object" && value !== null && "__platform" in value && value.__platform === def.name;
1074
- };
1061
+ const isPlatformUser = (value) => typeof value === "object" && value !== null && "__platform" in value && value.__platform === def.name;
1075
1062
  const resolveUserID = async (userID) => {
1076
1063
  const resolved = await def.user.resolve({
1077
1064
  input: { userID },
@@ -1157,7 +1144,7 @@ function createPlatformInstance(def, runtime) {
1157
1144
  id: parsedSpace.id,
1158
1145
  __platform: def.name
1159
1146
  };
1160
- const typingCtx = {
1147
+ const actionCtx = {
1161
1148
  space: spaceRef,
1162
1149
  client: runtime.client,
1163
1150
  config: runtime.config,
@@ -1166,7 +1153,7 @@ function createPlatformInstance(def, runtime) {
1166
1153
  return buildSpace({
1167
1154
  spaceRef,
1168
1155
  extras: parsedSpace,
1169
- typingCtx,
1156
+ actionCtx,
1170
1157
  definition: def,
1171
1158
  client: runtime.client,
1172
1159
  config: runtime.config,
@@ -1175,11 +1162,9 @@ function createPlatformInstance(def, runtime) {
1175
1162
  }
1176
1163
  };
1177
1164
  const eventProperties = {};
1178
- for (const eventName of Object.keys(def.events)) {
1179
- if (eventName === "messages") {
1180
- continue;
1181
- }
1182
- const producer = def.events[eventName];
1165
+ const customEvents = def.events ?? {};
1166
+ for (const eventName of Object.keys(customEvents)) {
1167
+ const producer = customEvents[eventName];
1183
1168
  if (producer) {
1184
1169
  eventProperties[eventName] = producer({
1185
1170
  client: runtime.client,
@@ -1290,9 +1275,12 @@ export {
1290
1275
  asText,
1291
1276
  text,
1292
1277
  resolveContents,
1278
+ edit,
1293
1279
  reactionSchema,
1294
1280
  asReaction,
1295
1281
  reaction,
1282
+ reply,
1283
+ typing,
1296
1284
  UnsupportedError,
1297
1285
  wrapProviderMessage,
1298
1286
  buildSpace,
@@ -3,7 +3,7 @@ import {
3
3
  readSchema,
4
4
  resolveContents,
5
5
  streamSchema
6
- } from "./chunk-LH4YEBG3.js";
6
+ } from "./chunk-JWWIFSI7.js";
7
7
 
8
8
  // src/content/group.ts
9
9
  import z from "zod";
@@ -2,7 +2,7 @@ import {
2
2
  bufferToStream,
3
3
  readSchema,
4
4
  streamSchema
5
- } from "./chunk-LH4YEBG3.js";
5
+ } from "./chunk-JWWIFSI7.js";
6
6
 
7
7
  // src/content/voice.ts
8
8
  import { createReadStream } from "fs";
@@ -29,7 +29,7 @@ var resolveVoiceName = (input, name) => {
29
29
  if (typeof input === "string") {
30
30
  return basename(input);
31
31
  }
32
- return void 0;
32
+ return;
33
33
  };
34
34
  var resolveVoiceMimeType = (name, mimeType) => {
35
35
  if (mimeType) {