@quenty/messagingserviceutils 7.13.0 → 7.13.1-canary.640.11bc5b4.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/CHANGELOG.md CHANGED
@@ -3,6 +3,17 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [7.13.1-canary.640.11bc5b4.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/messagingserviceutils@7.13.0...@quenty/messagingserviceutils@7.13.1-canary.640.11bc5b4.0) (2026-01-14)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * DataStores now use one message per a place instead of many, which cuts down on subscription costs ([11bc5b4](https://github.com/Quenty/NevermoreEngine/commit/11bc5b49abeac516970bb015f11b7cf1e7d03d25))
12
+
13
+
14
+
15
+
16
+
6
17
  # [7.13.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/messagingserviceutils@7.12.6...@quenty/messagingserviceutils@7.13.0) (2026-01-13)
7
18
 
8
19
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quenty/messagingserviceutils",
3
- "version": "7.13.0",
3
+ "version": "7.13.1-canary.640.11bc5b4.0",
4
4
  "description": "Utility functions for messaging srevice",
5
5
  "keywords": [
6
6
  "Roblox",
@@ -30,7 +30,11 @@
30
30
  ],
31
31
  "dependencies": {
32
32
  "@quenty/loader": "10.9.3",
33
- "@quenty/promise": "10.13.0"
33
+ "@quenty/maid": "3.5.3",
34
+ "@quenty/promise": "10.13.0",
35
+ "@quenty/rx": "13.22.0",
36
+ "@quenty/servicebag": "11.13.6",
37
+ "@quenty/statestack": "14.25.0"
34
38
  },
35
39
  "devDependencies": {
36
40
  "@quenty/loader": "workspace:*"
@@ -38,5 +42,5 @@
38
42
  "publishConfig": {
39
43
  "access": "public"
40
44
  },
41
- "gitHead": "fbe4f7a70d781c1b2dd62ec19aec1ce6ce2d742c"
45
+ "gitHead": "11bc5b49abeac516970bb015f11b7cf1e7d03d25"
42
46
  }
@@ -0,0 +1,252 @@
1
+ --!strict
2
+ --[=[
3
+ Provides a centralized messaging service for the current place, to other places.
4
+ @class PlaceMessagingService
5
+ ]=]
6
+
7
+ local require = require(script.Parent.loader).load(script)
8
+
9
+ local RunService = game:GetService("RunService")
10
+
11
+ local Maid = require("Maid")
12
+ local MessagingServiceUtils = require("MessagingServiceUtils")
13
+ local Observable = require("Observable")
14
+ local ObservableSubscriptionTable = require("ObservableSubscriptionTable")
15
+ local Promise = require("Promise")
16
+ local ServiceBag = require("ServiceBag")
17
+ local StateStack = require("StateStack")
18
+
19
+ local LOG_DEBUG = false
20
+
21
+ local PlaceMessagingService = {}
22
+ PlaceMessagingService.ServiceName = "PlaceMessagingService"
23
+
24
+ export type PlaceMessagingService = typeof(setmetatable(
25
+ {} :: {
26
+ _serviceBag: ServiceBag.ServiceBag,
27
+ _subscriptionTable: ObservableSubscriptionTable.ObservableSubscriptionTable<(any, PlacePacketMetadata)>,
28
+ _connectionRequire: StateStack.StateStack<boolean>,
29
+ _maid: Maid.Maid,
30
+ },
31
+ {} :: typeof({ __index = PlaceMessagingService })
32
+ ))
33
+
34
+ export type PlaceAddress = {
35
+ jobId: string,
36
+ placeId: number,
37
+ }
38
+
39
+ type PlaceMessagingPacket = {
40
+ topic: string,
41
+ from: PlaceAddress,
42
+ message: any,
43
+ }
44
+
45
+ export type PlacePacketMetadata = {
46
+ sent: number, -- Same as the MessagingService timestamp
47
+ topic: string,
48
+ from: PlaceAddress,
49
+ }
50
+
51
+ function PlaceMessagingService.Init(self: PlaceMessagingService, serviceBag: ServiceBag.ServiceBag): ()
52
+ assert(not (self :: any)._serviceBag, "Already initialized")
53
+ self._serviceBag = assert(serviceBag, "No serviceBag")
54
+ self._maid = Maid.new()
55
+
56
+ self._connectionRequire = self._maid:Add(StateStack.new(false, "boolean"))
57
+ self._subscriptionTable = self._maid:Add(ObservableSubscriptionTable.new() :: any)
58
+ end
59
+
60
+ function PlaceMessagingService.Start(self: PlaceMessagingService): ()
61
+ -- Subscribe as needed
62
+ self._maid:GiveTask(self._connectionRequire
63
+ :ObserveBrio(function(required)
64
+ return required
65
+ end)
66
+ :Subscribe(function(brio)
67
+ if brio:IsDead() then
68
+ return
69
+ end
70
+
71
+ local maid = brio:ToMaid()
72
+ local address = self:GetPlaceAddress()
73
+ local topic = self:PlaceAddressToTopicString(address)
74
+
75
+ if LOG_DEBUG then
76
+ print(`[PlaceMessagingService] - Subscribing to global topic {topic}`)
77
+ end
78
+
79
+ maid:GiveTask(
80
+ MessagingServiceUtils.promiseSubscribe(topic, function(data: MessagingServiceUtils.SubscriptionData)
81
+ self:_handleIncomingPacket(data)
82
+ end):Then(function(connection: RBXScriptConnection)
83
+ maid:GiveTask(connection)
84
+ end)
85
+ )
86
+ end))
87
+ end
88
+
89
+ function PlaceMessagingService._handleIncomingPacket(
90
+ self: PlaceMessagingService,
91
+ data: MessagingServiceUtils.SubscriptionData
92
+ ): ()
93
+ local packet: PlaceMessagingPacket = data.Data :: any
94
+
95
+ if LOG_DEBUG then
96
+ print(`[PlaceMessagingService] - Receipted {MessagingServiceUtils.toHumanReadable(packet)}`)
97
+ end
98
+
99
+ if type(packet) ~= "table" then
100
+ warn("[PlaceMessagingService] - Received invalid packet on place messaging service")
101
+ return
102
+ end
103
+
104
+ local topic = packet.topic
105
+ if type(topic) ~= "string" then
106
+ warn("[PlaceMessagingService] - Received invalid topic on place messaging service")
107
+ return
108
+ end
109
+
110
+ local message = packet.message
111
+ if message == nil then
112
+ warn("[PlaceMessagingService] - Received nil message on place messaging service")
113
+ return
114
+ end
115
+
116
+ local metadata: PlacePacketMetadata = {
117
+ sent = data.Sent,
118
+ topic = topic,
119
+ from = packet.from,
120
+ }
121
+ self._subscriptionTable:Fire(packet.topic, packet.message, metadata)
122
+ end
123
+
124
+ --[=[
125
+ Observes messages for the current place on the given topic
126
+
127
+ The returned value should be the same as the message published.
128
+
129
+ :::tip
130
+ This observable will only be active while there is at least one active
131
+ subscription to it. This is to help limit unnecessary load on MessagingService.
132
+ :::
133
+
134
+ @param topic string
135
+ @return Observable<unknown>
136
+ ]=]
137
+ function PlaceMessagingService.ObserveMessages(
138
+ self: PlaceMessagingService,
139
+ topic: string
140
+ ): Observable.Observable<any, PlacePacketMetadata>
141
+ local observable = self._subscriptionTable:Observe(topic)
142
+ return Observable.new(function(sub)
143
+ local inner = observable:Subscribe(sub:GetFireFailComplete())
144
+ local removePush = self._connectionRequire:PushState(true)
145
+
146
+ if LOG_DEBUG then
147
+ print(`[PlaceMessagingService] - Subscribing to internal {topic}`)
148
+ end
149
+
150
+ return function()
151
+ removePush()
152
+ inner:Destroy()
153
+ end
154
+ end) :: any
155
+ end
156
+
157
+ --[=[
158
+ Sends a message to the given place and job
159
+
160
+ @param placeId number
161
+ @param jobId string
162
+ @param topic string
163
+ @param message any
164
+ @return Promise<()>
165
+ ]=]
166
+ function PlaceMessagingService.SendMessage(
167
+ self: PlaceMessagingService,
168
+ placeId: number,
169
+ jobId: string,
170
+ topic: string,
171
+ message: any
172
+ ): Promise.Promise<()>
173
+ local address = self:PlaceAndJobToServerAddress(placeId, jobId)
174
+ return self:SendMessageToAddress(address, topic, message)
175
+ end
176
+
177
+ --[=[
178
+ Sends a message to the given place address
179
+
180
+ @param address PlaceAddress
181
+ @param topic string
182
+ @param message any
183
+ @return Promise<()>
184
+ ]=]
185
+ function PlaceMessagingService.SendMessageToAddress(
186
+ self: PlaceMessagingService,
187
+ address: PlaceAddress,
188
+ topic: string,
189
+ message: any
190
+ ): Promise.Promise<()>
191
+ local packet: PlaceMessagingPacket = {
192
+ topic = topic,
193
+ from = self:GetPlaceAddress(),
194
+ message = message,
195
+ }
196
+ local addressString = self:PlaceAddressToTopicString(address)
197
+
198
+ if LOG_DEBUG then
199
+ print(`[PlaceMessagingService] - To {addressString} sending {MessagingServiceUtils.toHumanReadable(packet)}`)
200
+ end
201
+
202
+ return self._maid:GivePromise(MessagingServiceUtils.promisePublish(addressString, packet))
203
+ end
204
+
205
+ --[=[
206
+ Gets the current place address
207
+
208
+ @return PlaceAddress
209
+ ]=]
210
+ function PlaceMessagingService.GetPlaceAddress(self: PlaceMessagingService): PlaceAddress
211
+ local jobId = game.JobId
212
+ if jobId == "" and RunService:IsStudio() then
213
+ jobId = "studio"
214
+ end
215
+
216
+ return self:PlaceAndJobToServerAddress(game.PlaceId, jobId)
217
+ end
218
+
219
+ --[=[
220
+ Converts a place address to a topic string
221
+
222
+ @param address PlaceAddress
223
+ @return string
224
+ ]=]
225
+ function PlaceMessagingService.PlaceAddressToTopicString(_self: PlaceMessagingService, address: PlaceAddress): string
226
+ return `PlaceMessagingService_{address.placeId}_{address.jobId}`
227
+ end
228
+
229
+ --[=[
230
+ Converts a placeId and jobId to a place address
231
+
232
+ @param placeId number
233
+ @param jobId string
234
+ @return PlaceAddress
235
+ ]=]
236
+ function PlaceMessagingService.PlaceAndJobToServerAddress(
237
+ _self: PlaceMessagingService,
238
+ placeId: number,
239
+ jobId: string
240
+ ): PlaceAddress
241
+ return {
242
+ placeId = placeId,
243
+ jobId = jobId,
244
+ }
245
+ end
246
+
247
+ function PlaceMessagingService.Destroy(self: PlaceMessagingService)
248
+ self._maid:DoCleaning()
249
+ self._maid = nil :: any
250
+ end
251
+
252
+ return PlaceMessagingService