spectrum-ts 1.1.0 → 1.1.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.
@@ -672,29 +672,36 @@ var extractExtras = (raw, definition) => {
672
672
  const extra = Object.fromEntries(entries);
673
673
  return definition.message?.schema ? definition.message.schema.parse(extra) : extra;
674
674
  };
675
- function wrapProviderMessage(raw, ctx) {
676
- const wrappedContent = wrapNestedContent(raw.content, ctx);
677
- return buildMessage({
675
+ function wrapProviderMessage(raw, ctx, direction) {
676
+ const wrappedContent = wrapNestedContent(raw.content, ctx, direction);
677
+ const base = {
678
678
  id: raw.id,
679
679
  content: wrappedContent,
680
- sender: raw.sender,
681
680
  timestamp: raw.timestamp ?? /* @__PURE__ */ new Date(),
682
681
  extras: extractExtras(raw, ctx.definition),
683
682
  spaceRef: ctx.spaceRef,
684
683
  space: ctx.space,
685
684
  definition: ctx.definition,
686
685
  client: ctx.client,
687
- config: ctx.config,
688
- direction: "inbound"
689
- });
686
+ config: ctx.config
687
+ };
688
+ if (direction === "inbound") {
689
+ if (!raw.sender) {
690
+ throw new Error(
691
+ `Inbound provider message missing sender (platform "${ctx.definition.name}", id "${raw.id}")`
692
+ );
693
+ }
694
+ return buildMessage({ ...base, sender: raw.sender, direction: "inbound" });
695
+ }
696
+ return buildMessage({ ...base, sender: raw.sender, direction: "outbound" });
690
697
  }
691
- var wrapNestedContent = (content, ctx) => {
698
+ var wrapNestedContent = (content, ctx, direction) => {
692
699
  if (content.type === "reaction") {
693
700
  const target = content.target;
694
701
  if (isRawProviderRecord(target)) {
695
702
  return {
696
703
  ...content,
697
- target: wrapProviderMessage(target, ctx)
704
+ target: wrapProviderMessage(target, ctx, "inbound")
698
705
  };
699
706
  }
700
707
  return content;
@@ -702,7 +709,10 @@ var wrapNestedContent = (content, ctx) => {
702
709
  if (content.type === "group") {
703
710
  const items = content.items.map((item) => {
704
711
  const raw = item;
705
- return isRawProviderRecord(raw) ? wrapProviderMessage(raw, ctx) : item;
712
+ if (!isRawProviderRecord(raw)) {
713
+ return item;
714
+ }
715
+ return direction === "inbound" ? wrapProviderMessage(raw, ctx, "inbound") : wrapProviderMessage(raw, ctx, "outbound");
706
716
  });
707
717
  return { ...content, items };
708
718
  }
@@ -739,9 +749,9 @@ function buildSpace(params) {
739
749
  }
740
750
  }
741
751
  async function dispatchSend(item) {
742
- let sendResult;
752
+ let raw;
743
753
  try {
744
- sendResult = await definition.actions.send({
754
+ raw = await definition.actions.send({
745
755
  ...typingCtx,
746
756
  content: item
747
757
  });
@@ -752,46 +762,16 @@ function buildSpace(params) {
752
762
  }
753
763
  throw err;
754
764
  }
755
- if (!sendResult?.id) {
765
+ if (!raw?.id) {
756
766
  throw new Error(
757
767
  `Platform "${definition.name}" send did not return a message id`
758
768
  );
759
769
  }
760
- const outboundContent = item.type === "group" && sendResult.groupMembers ? {
761
- ...item,
762
- items: item.items.map((stub, idx) => {
763
- const member = sendResult?.groupMembers?.[idx];
764
- if (!member?.id) {
765
- return stub;
766
- }
767
- return buildMessage({
768
- id: member.id,
769
- content: stub.content,
770
- sender: member.sender,
771
- timestamp: member.timestamp ?? /* @__PURE__ */ new Date(),
772
- extras: {},
773
- spaceRef,
774
- space,
775
- definition,
776
- client,
777
- config,
778
- direction: "outbound"
779
- });
780
- })
781
- } : item;
782
- return buildMessage({
783
- id: sendResult.id,
784
- content: outboundContent,
785
- sender: sendResult.sender,
786
- timestamp: sendResult.timestamp ?? /* @__PURE__ */ new Date(),
787
- extras: {},
788
- spaceRef,
789
- space,
790
- definition,
791
- client,
792
- config,
793
- direction: "outbound"
794
- });
770
+ return wrapProviderMessage(
771
+ raw,
772
+ { client, config, definition, space, spaceRef },
773
+ "outbound"
774
+ );
795
775
  }
796
776
  async function sendImpl(...content) {
797
777
  const resolved = await resolveContents(content);
@@ -837,13 +817,11 @@ function buildSpace(params) {
837
817
  if (!raw) {
838
818
  return;
839
819
  }
840
- return wrapProviderMessage(raw, {
841
- client,
842
- config,
843
- definition,
844
- space,
845
- spaceRef
846
- });
820
+ return wrapProviderMessage(
821
+ raw,
822
+ { client, config, definition, space, spaceRef },
823
+ "inbound"
824
+ );
847
825
  }
848
826
  space = {
849
827
  ...extras,
@@ -903,8 +881,46 @@ function buildMessage(params) {
903
881
  throw err;
904
882
  }
905
883
  };
884
+ const requireBuiltMessage = (action) => {
885
+ if (!self) {
886
+ throw new Error(
887
+ `${action}() called before message construction completed (internal bug)`
888
+ );
889
+ }
890
+ return self;
891
+ };
892
+ const dispatchReplyItem = async (item, target, replyToMessage) => {
893
+ let raw;
894
+ try {
895
+ raw = await replyToMessage({
896
+ space: spaceRef,
897
+ messageId: params.id,
898
+ target,
899
+ content: item,
900
+ client,
901
+ config
902
+ });
903
+ } catch (err) {
904
+ if (err instanceof UnsupportedError) {
905
+ warnUnsupported(err, definition.name);
906
+ return;
907
+ }
908
+ throw err;
909
+ }
910
+ if (!raw?.id) {
911
+ throw new Error(
912
+ `Platform "${definition.name}" reply did not return a message id`
913
+ );
914
+ }
915
+ return wrapProviderMessage(
916
+ raw,
917
+ { client, config, definition, space, spaceRef },
918
+ "outbound"
919
+ );
920
+ };
906
921
  async function reply(...content) {
907
- if (!definition.actions.replyToMessage) {
922
+ const replyToMessage = definition.actions.replyToMessage;
923
+ if (!replyToMessage) {
908
924
  warnUnsupported(
909
925
  UnsupportedError.action("reply", definition.name),
910
926
  definition.name
@@ -912,44 +928,13 @@ function buildMessage(params) {
912
928
  return content.length === 1 ? void 0 : [];
913
929
  }
914
930
  const resolved = await resolveContents(content);
931
+ const target = requireBuiltMessage("reply");
915
932
  const results = [];
916
933
  for (const item of resolved) {
917
- let sendResult;
918
- try {
919
- sendResult = await definition.actions.replyToMessage({
920
- space: spaceRef,
921
- messageId: params.id,
922
- content: item,
923
- client,
924
- config
925
- });
926
- } catch (err) {
927
- if (err instanceof UnsupportedError) {
928
- warnUnsupported(err, definition.name);
929
- continue;
930
- }
931
- throw err;
932
- }
933
- if (!sendResult?.id) {
934
- throw new Error(
935
- `Platform "${definition.name}" reply did not return a message id`
936
- );
934
+ const sent = await dispatchReplyItem(item, target, replyToMessage);
935
+ if (sent) {
936
+ results.push(sent);
937
937
  }
938
- results.push(
939
- buildMessage({
940
- id: sendResult.id,
941
- content: item,
942
- sender: sendResult.sender,
943
- timestamp: sendResult.timestamp ?? /* @__PURE__ */ new Date(),
944
- extras: {},
945
- spaceRef,
946
- space,
947
- definition,
948
- client,
949
- config,
950
- direction: "outbound"
951
- })
952
- );
953
938
  }
954
939
  if (content.length === 1) {
955
940
  return results[0];
@@ -1131,6 +1116,14 @@ function createPlatformInstance(def, runtime) {
1131
1116
  });
1132
1117
  }
1133
1118
  }
1119
+ let messagesIterable;
1120
+ Object.defineProperty(base, "messages", {
1121
+ enumerable: true,
1122
+ get() {
1123
+ messagesIterable ??= runtime.subscribeMessages();
1124
+ return messagesIterable;
1125
+ }
1126
+ });
1134
1127
  return Object.assign(base, eventProperties);
1135
1128
  }
1136
1129
  function definePlatform(name, def) {
@@ -108,6 +108,87 @@ function mergeStreams(streams) {
108
108
  };
109
109
  });
110
110
  }
111
+ function broadcast(source) {
112
+ const consumers = /* @__PURE__ */ new Set();
113
+ let pumping = false;
114
+ let terminated = false;
115
+ let terminalError;
116
+ let pumpPromise;
117
+ let closed = false;
118
+ const startPump = () => {
119
+ if (pumping || terminated) {
120
+ return;
121
+ }
122
+ pumping = true;
123
+ pumpPromise = (async () => {
124
+ try {
125
+ for await (const value of source) {
126
+ for (const consumer of consumers) {
127
+ consumer.deliveries = consumer.deliveries.then(
128
+ () => consumer.emit(value).catch(() => {
129
+ })
130
+ );
131
+ }
132
+ }
133
+ terminated = true;
134
+ await Promise.allSettled(
135
+ Array.from(consumers, (consumer) => consumer.deliveries)
136
+ );
137
+ for (const consumer of consumers) {
138
+ consumer.end();
139
+ }
140
+ consumers.clear();
141
+ } catch (error) {
142
+ terminated = true;
143
+ terminalError = error;
144
+ for (const consumer of consumers) {
145
+ consumer.end(error);
146
+ }
147
+ consumers.clear();
148
+ }
149
+ })();
150
+ };
151
+ return {
152
+ subscribe() {
153
+ return stream((emit, end) => {
154
+ if (terminated) {
155
+ end(terminalError);
156
+ return;
157
+ }
158
+ const consumer = {
159
+ emit,
160
+ end,
161
+ deliveries: Promise.resolve()
162
+ };
163
+ consumers.add(consumer);
164
+ startPump();
165
+ return () => {
166
+ consumers.delete(consumer);
167
+ };
168
+ });
169
+ },
170
+ async close() {
171
+ if (closed) {
172
+ return;
173
+ }
174
+ closed = true;
175
+ try {
176
+ await source.close();
177
+ if (pumpPromise) {
178
+ await pumpPromise;
179
+ }
180
+ } finally {
181
+ if (!terminated) {
182
+ terminated = true;
183
+ for (const consumer of consumers) {
184
+ consumer.end();
185
+ }
186
+ consumers.clear();
187
+ }
188
+ }
189
+ }
190
+ };
191
+ }
111
192
 
112
193
  // src/utils/cloud.ts
113
194
  var SPECTRUM_CLOUD_URL = `https://${process.env.SPECTRUM_CLOUD_URL ?? "spectrum.photon.codes"}`;
@@ -183,6 +264,7 @@ export {
183
264
  poll,
184
265
  stream,
185
266
  mergeStreams,
267
+ broadcast,
186
268
  SpectrumCloudError,
187
269
  cloud
188
270
  };
@@ -2,7 +2,7 @@ import {
2
2
  bufferToStream,
3
3
  readSchema,
4
4
  streamSchema
5
- } from "./chunk-XMAI2AAN.js";
5
+ } from "./chunk-5GQ2OMFY.js";
6
6
 
7
7
  // src/content/voice.ts
8
8
  import { createReadStream } from "fs";
@@ -3,7 +3,7 @@ import {
3
3
  readSchema,
4
4
  resolveContents,
5
5
  streamSchema
6
- } from "./chunk-XMAI2AAN.js";
6
+ } from "./chunk-5GQ2OMFY.js";
7
7
 
8
8
  // src/content/group.ts
9
9
  import z from "zod";
@@ -162,6 +162,7 @@ function richlink(url) {
162
162
  }
163
163
 
164
164
  export {
165
+ groupSchema,
165
166
  asGroup,
166
167
  group,
167
168
  asRichlink,
package/dist/index.d.ts CHANGED
@@ -1,8 +1,7 @@
1
- import { C as ContentBuilder, U as User, M as Message, a as ContentInput, b as Content, P as ProviderMessage, c as PlatformDef, d as Platform, e as PlatformProviderConfig, S as SpectrumLike, f as CustomEventStreams, g as Space, I as InboundMessage, O as OutboundMessage } from './types-DJQLFwWW.js';
2
- export { A as AnyPlatformDef, E as EventProducer, h as PlatformInstance, i as PlatformMessage, j as PlatformSpace, k as PlatformUser, l as SchemaMessage } from './types-DJQLFwWW.js';
1
+ import { C as ContentBuilder, U as User, M as Message, a as ContentInput, b as Content, P as ProviderMessage, c as PlatformDef, d as Platform, e as PlatformProviderConfig, S as SpectrumLike, f as CustomEventStreams, g as Space, I as InboundMessage, O as OutboundMessage } from './types-GhOtIIqj.js';
2
+ export { A as AnyPlatformDef, B as Broadcaster, E as EventProducer, h as InboundPlatformMessage, i as ManagedStream, j as PlatformInstance, k as PlatformMessage, l as PlatformRuntime, m as PlatformSpace, n as PlatformUser, o as SchemaMessage, p as broadcast, q as mergeStreams, s as stream } from './types-GhOtIIqj.js';
3
3
  import vCard from 'vcf';
4
4
  import z__default from 'zod';
5
- export { M as ManagedStream, m as mergeStreams, s as stream } from './stream-B55k7W8-.js';
6
5
  import 'hotscript';
7
6
 
8
7
  declare function attachment(input: string | Buffer, options?: {
package/dist/index.js CHANGED
@@ -1,18 +1,19 @@
1
1
  import {
2
2
  group,
3
3
  richlink
4
- } from "./chunk-LAGNM6I7.js";
4
+ } from "./chunk-UWUPCIB4.js";
5
5
  import {
6
6
  voice
7
- } from "./chunk-7Q7KJKGL.js";
7
+ } from "./chunk-QZ5DJ7VR.js";
8
8
  import {
9
9
  SpectrumCloudError,
10
+ broadcast,
10
11
  cloud,
11
12
  mergeStreams,
12
13
  option,
13
14
  poll,
14
15
  stream
15
- } from "./chunk-PXX7ISZ6.js";
16
+ } from "./chunk-FF2R4EP3.js";
16
17
  import {
17
18
  UnsupportedError,
18
19
  attachment,
@@ -26,7 +27,7 @@ import {
26
27
  text,
27
28
  toVCard,
28
29
  wrapProviderMessage
29
- } from "./chunk-XMAI2AAN.js";
30
+ } from "./chunk-5GQ2OMFY.js";
30
31
 
31
32
  // src/emoji/generated.ts
32
33
  var GeneratedEmoji = {
@@ -1986,23 +1987,9 @@ async function Spectrum(options) {
1986
1987
  } = options;
1987
1988
  const flattenGroups = runtimeOptions?.flattenGroups ?? false;
1988
1989
  const platformStates = /* @__PURE__ */ new Map();
1990
+ const messageBroadcasters = /* @__PURE__ */ new Map();
1989
1991
  const customEventStreams = /* @__PURE__ */ new Map();
1990
1992
  let stopped = false;
1991
- for (const provider of providers) {
1992
- const providerConfig = provider;
1993
- const def = providerConfig.__definition;
1994
- const userConfig = def.config.parse(providerConfig.config);
1995
- const client = await def.lifecycle.createClient({
1996
- config: userConfig,
1997
- projectId,
1998
- projectSecret
1999
- });
2000
- platformStates.set(def.name, {
2001
- client,
2002
- config: userConfig,
2003
- definition: def
2004
- });
2005
- }
2006
1993
  const adaptIterable = (iterable) => {
2007
1994
  return stream((emit, end) => {
2008
1995
  const iterator = iterable[Symbol.asyncIterator]();
@@ -2045,13 +2032,17 @@ async function Spectrum(options) {
2045
2032
  client,
2046
2033
  config
2047
2034
  });
2048
- const normalizedMessage = wrapProviderMessage(msg, {
2049
- client,
2050
- config,
2051
- definition,
2052
- space,
2053
- spaceRef
2054
- });
2035
+ const normalizedMessage = wrapProviderMessage(
2036
+ msg,
2037
+ {
2038
+ client,
2039
+ config,
2040
+ definition,
2041
+ space,
2042
+ spaceRef
2043
+ },
2044
+ "inbound"
2045
+ );
2055
2046
  if (flattenGroups && normalizedMessage.content.type === "group") {
2056
2047
  for (const item of normalizedMessage.content.items) {
2057
2048
  yield [space, item];
@@ -2063,10 +2054,46 @@ async function Spectrum(options) {
2063
2054
  };
2064
2055
  return adaptIterable(bindSend());
2065
2056
  };
2057
+ const getOrCreateMessageBroadcast = (state) => {
2058
+ if (stopped) {
2059
+ throw new Error(
2060
+ `Spectrum instance has been stopped; cannot subscribe to "${state.definition.name}" messages`
2061
+ );
2062
+ }
2063
+ const name = state.definition.name;
2064
+ let broadcaster = messageBroadcasters.get(name);
2065
+ if (!broadcaster) {
2066
+ broadcaster = broadcast(createProviderMessagesStream(state));
2067
+ messageBroadcasters.set(name, broadcaster);
2068
+ }
2069
+ return broadcaster;
2070
+ };
2071
+ for (const provider of providers) {
2072
+ const providerConfig = provider;
2073
+ const def = providerConfig.__definition;
2074
+ const userConfig = def.config.parse(providerConfig.config);
2075
+ const client = await def.lifecycle.createClient({
2076
+ config: userConfig,
2077
+ projectId,
2078
+ projectSecret
2079
+ });
2080
+ const state = {
2081
+ client,
2082
+ config: userConfig,
2083
+ definition: def
2084
+ };
2085
+ platformStates.set(def.name, {
2086
+ ...state,
2087
+ subscribeMessages: () => getOrCreateMessageBroadcast(state).subscribe()
2088
+ });
2089
+ }
2066
2090
  const createMessagesStream = () => {
2067
2091
  return stream((emit, end) => {
2068
2092
  const merged = mergeStreams(
2069
- Array.from(platformStates.values(), createProviderMessagesStream)
2093
+ Array.from(
2094
+ platformStates.values(),
2095
+ (runtime) => runtime.subscribeMessages()
2096
+ )
2070
2097
  );
2071
2098
  const pump = (async () => {
2072
2099
  try {
@@ -2130,6 +2157,10 @@ async function Spectrum(options) {
2130
2157
  ...Array.from(
2131
2158
  customEventStreams.values(),
2132
2159
  (eventStream) => eventStream.close()
2160
+ ),
2161
+ ...Array.from(
2162
+ messageBroadcasters.values(),
2163
+ (broadcaster) => broadcaster.close()
2133
2164
  )
2134
2165
  ];
2135
2166
  process.off("SIGINT", handleSignal);
@@ -2143,6 +2174,7 @@ async function Spectrum(options) {
2143
2174
  );
2144
2175
  await Promise.allSettled(clientShutdowns);
2145
2176
  customEventStreams.clear();
2177
+ messageBroadcasters.clear();
2146
2178
  platformStates.clear();
2147
2179
  };
2148
2180
  const handleSignal = () => {
@@ -2200,6 +2232,7 @@ export {
2200
2232
  SpectrumCloudError,
2201
2233
  UnsupportedError,
2202
2234
  attachment,
2235
+ broadcast,
2203
2236
  cloud,
2204
2237
  contact,
2205
2238
  custom,
@@ -1,5 +1,4 @@
1
- import { M as ManagedStream } from '../../stream-B55k7W8-.js';
2
- import { l as SchemaMessage, d as Platform, c as PlatformDef, P as ProviderMessage } from '../../types-DJQLFwWW.js';
1
+ import { o as SchemaMessage, d as Platform, c as PlatformDef, P as ProviderMessage, i as ManagedStream } from '../../types-GhOtIIqj.js';
3
2
  import * as zod_v4_core from 'zod/v4/core';
4
3
  import * as z from 'zod';
5
4
  import z__default from 'zod';