@rbxts/replion 1.0.17 → 1.0.19

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/README.md CHANGED
@@ -2,4 +2,4 @@
2
2
 
3
3
  ## Install
4
4
  Install with [wally](https://wally.run/):\
5
- `Replion = "shouxtech/replion@1.0.17"`
5
+ `Replion = "shouxtech/replion@1.0.19"`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rbxts/replion",
3
- "version": "1.0.17",
3
+ "version": "1.0.19",
4
4
  "description": "",
5
5
  "main": "src/init.lua",
6
6
  "scripts": {},
@@ -21,4 +21,4 @@
21
21
  "roblox-ts": "^3.0.0",
22
22
  "typescript": "^5.9.3"
23
23
  }
24
- }
24
+ }
@@ -0,0 +1,68 @@
1
+ --!strict
2
+ local isTypeScriptEnv = script.Parent.Name == 'src';
3
+ local dependencies = if isTypeScriptEnv then script.Parent.Parent.Parent else script.Parent.Parent;
4
+ local Shared = require(script.Parent.Shared);
5
+ local Signal = require(isTypeScriptEnv and dependencies['sleitnick-signal'] or dependencies.Signal);
6
+
7
+ export type GenericDataTable = { [string]: any };
8
+ export type Observer = (newValue: any, oldValue: any) -> ();
9
+
10
+ local BaseReplion = {};
11
+ BaseReplion.__index = BaseReplion;
12
+
13
+ function BaseReplion.new(data: GenericDataTable)
14
+ local self = setmetatable({}, BaseReplion);
15
+
16
+ self.data = data;
17
+
18
+ self._signals = {} :: { [string]: any };
19
+ self._allSignal = Signal.new();
20
+
21
+ return self;
22
+ end;
23
+
24
+ function BaseReplion:get(path: { string }?)
25
+ if not path then return self.data; end;
26
+ return Shared.getNestedValue(self.data, path);
27
+ end;
28
+
29
+ function BaseReplion:subscribe(path: { string }?, callback: Observer)
30
+ if not path then
31
+ local connection = self._allSignal:Connect(callback);
32
+ return function()
33
+ connection:Disconnect()
34
+ end;
35
+ end;
36
+
37
+ local signalKey = Shared.getSignalKey(path);
38
+ if not self._signals[signalKey] then
39
+ self._signals[signalKey] = Signal.new();
40
+ end;
41
+
42
+ local connection = self._signals[signalKey]:Connect(callback);
43
+
44
+ return function()
45
+ connection:Disconnect();
46
+ end;
47
+ end;
48
+
49
+ function BaseReplion:observe(path: { string }?, callback: Observer)
50
+ if path then
51
+ local initialValue = Shared.getNestedValue(self.data, path);
52
+ task.spawn(callback, initialValue, nil);
53
+ else
54
+ task.spawn(callback, self.data, nil);
55
+ end;
56
+
57
+ return self:subscribe(path, callback);
58
+ end;
59
+
60
+ function BaseReplion:destroy()
61
+ self._allSignal:Destroy();
62
+ for _, signal in self._signals do
63
+ signal:Destroy();
64
+ end;
65
+ self._signals = {};
66
+ end;
67
+
68
+ return BaseReplion;
package/src/Client.lua CHANGED
@@ -1,10 +1,10 @@
1
1
  --!strict
2
2
  local isTypeScriptEnv = script.Parent.Name == 'src';
3
3
  local dependencies = if isTypeScriptEnv then script.Parent.Parent.Parent else script.Parent.Parent;
4
+ local BaseReplion = require(script.Parent.BaseReplion)
4
5
  local Shared = require(script.Parent.Shared);
5
6
  local Signal = require(isTypeScriptEnv and dependencies['sleitnick-signal'] or dependencies.Signal);
6
7
 
7
- type Observer = (newValue: any, oldValue: any) -> ();
8
8
  type Deletion = { path: { string }, oldTable: any };
9
9
 
10
10
  local remote = script.Parent:WaitForChild(Shared.REMOTE_NAME) :: RemoteEvent;
@@ -48,40 +48,26 @@ function Client.waitForReplion(channel: string)
48
48
  return replion;
49
49
  end;
50
50
 
51
- function Client.new(channel: string, data: Shared.GenericDataTable)
51
+ function Client.new(channel: string, data: BaseReplion.GenericDataTable)
52
52
  local self = setmetatable({}, Client);
53
53
 
54
54
  self.channel = channel;
55
- self.data = data;
56
- self._signals = {} :: { [string]: any };
57
- self._allSignal = Signal.new();
55
+
56
+ self._base = BaseReplion.new(data);
58
57
 
59
58
  return self;
60
59
  end;
61
60
 
62
61
  function Client:get(path: { string }?)
63
- if not path then return self.data; end;
64
- return Shared.getNestedValue(self.data, path);
62
+ return self._base:get(path);
65
63
  end;
66
64
 
67
- function Client:observe(path: { string }?, callback: Observer)
68
- if not path then
69
- task.spawn(callback, self.data, nil);
70
- return self._allSignal:Connect(callback);
71
- end;
72
-
73
- local signalKey = Shared.getSignalKey(path);
74
- if not self._signals[signalKey] then
75
- self._signals[signalKey] = Signal.new();
76
- end;
77
-
78
- local initialValue = Shared.getNestedValue(self.data, path);
65
+ function Client:subscribe(path: { string }?, callback: BaseReplion.Observer)
66
+ return self._base:subscribe(path, callback);
67
+ end;
79
68
 
80
- task.spawn(callback, initialValue, nil);
81
- local connection = self._signals[signalKey]:Connect(callback);
82
- return function()
83
- connection:Disconnect();
84
- end;
69
+ function Client:observe(path: { string }?, callback: BaseReplion.Observer)
70
+ return self._base:observe(path, callback);
85
71
  end;
86
72
 
87
73
  function Client:_collectDeletions(target: any, updates: any, path: { string }, out: { Deletion })
@@ -101,12 +87,14 @@ function Client:_collectDeletions(target: any, updates: any, path: { string }, o
101
87
  end;
102
88
 
103
89
  function Client:_notifyDeepDeletion(path: { string }, oldTable: any)
90
+ local signals = self._base._signals;
91
+
104
92
  for k, v in oldTable do
105
93
  local nextPath = table.clone(path);
106
94
  table.insert(nextPath, k);
107
95
 
108
96
  local signalKey = Shared.getSignalKey(nextPath);
109
- local signal = self._signals[signalKey];
97
+ local signal = signals[signalKey];
110
98
  if signal then
111
99
  signal:Fire(nil, v);
112
100
  end;
@@ -118,15 +106,17 @@ function Client:_notifyDeepDeletion(path: { string }, oldTable: any)
118
106
  end;
119
107
 
120
108
  function Client:_notifyRecursiveUpdates(currentPath: { string }, updateTree: any)
109
+ local signals = self._base._signals;
110
+
121
111
  for k, v in updateTree do
122
112
  local nextPath = table.clone(currentPath);
123
113
  table.insert(nextPath, k);
124
114
 
125
115
  local signalKey = Shared.getSignalKey(nextPath);
126
- local signal = self._signals[signalKey];
116
+ local signal = signals[signalKey];
127
117
 
128
118
  if signal then
129
- local newValue = Shared.getNestedValue(self.data, nextPath);
119
+ local newValue = Shared.getNestedValue(self._base.data, nextPath);
130
120
  signal:Fire(newValue, newValue); -- Warning: Sends improper 'old' argument due to shallow copy optimization.
131
121
  end;
132
122
 
@@ -136,13 +126,13 @@ function Client:_notifyRecursiveUpdates(currentPath: { string }, updateTree: any
136
126
  end;
137
127
  end;
138
128
 
139
- function Client:_applyUpdates(updates: Shared.GenericDataTable)
129
+ function Client:_applyUpdates(updates: BaseReplion.GenericDataTable)
140
130
  local deletions: { Deletion } = {};
141
- self:_collectDeletions(self.data, updates, {}, deletions);
131
+ self:_collectDeletions(self._base.data, updates, {}, deletions);
142
132
 
143
- local oldData = table.clone(self.data); -- Note: Shallow copy instead of deep copy.
133
+ local oldData = table.clone(self._base.data); -- Note: Shallow copy instead of deep copy.
144
134
 
145
- deepMergeAndApply(self.data, updates);
135
+ deepMergeAndApply(self._base.data, updates);
146
136
 
147
137
  local anyChanged = false;
148
138
 
@@ -156,31 +146,27 @@ function Client:_applyUpdates(updates: Shared.GenericDataTable)
156
146
  -- If it's a table, we assume it changed if it's in the updates list.
157
147
  if typeof(newValue) == 'table' then
158
148
  anyChanged = true;
159
- local signal = self._signals[key];
149
+ local signal = self._base._signals[key];
160
150
  if signal then
161
- signal:Fire(self.data[key], self.data[key]); -- Warning: Sends improper 'old' argument due to shallow copy optimization.
151
+ signal:Fire(self._base.data[key], self._base.data[key]); -- Warning: Sends improper 'old' argument due to shallow copy optimization.
162
152
  end;
163
153
  self:_notifyRecursiveUpdates({key}, newValue);
164
- elseif self.data[key] ~= oldValue then
154
+ elseif self._base.data[key] ~= oldValue then
165
155
  anyChanged = true;
166
- local signal = self._signals[key];
156
+ local signal = self._base._signals[key];
167
157
  if signal then
168
- signal:Fire(self.data[key], oldValue);
158
+ signal:Fire(self._base.data[key], oldValue);
169
159
  end;
170
160
  end;
171
161
  end;
172
162
 
173
163
  if anyChanged then
174
- self._allSignal:Fire(self.data, updates);
164
+ self._base._allSignal:Fire(self._base.data, updates);
175
165
  end;
176
166
  end;
177
167
 
178
168
  function Client:destroy()
179
- self._allSignal:Destroy();
180
- for _, signal in self._signals do
181
- signal:Destroy();
182
- end;
183
- self._signals = {};
169
+ self._base:destroy();
184
170
  end;
185
171
 
186
172
  remote.OnClientEvent:Connect(function(type, channel, payload)
package/src/Server.lua CHANGED
@@ -3,16 +3,15 @@ local Players = game:GetService('Players');
3
3
 
4
4
  local isTypeScriptEnv = script.Parent.Name == 'src';
5
5
  local dependencies = if isTypeScriptEnv then script.Parent.Parent.Parent else script.Parent.Parent;
6
+ local BaseReplion = require(script.Parent.BaseReplion)
6
7
  local Shared = require(script.Parent.Shared);
7
8
  local Sift = require(isTypeScriptEnv and dependencies.sift.out or dependencies.Sift);
8
- local Signal = require(isTypeScriptEnv and dependencies['sleitnick-signal'] or dependencies.Signal);
9
9
 
10
10
  type ServerConfig = {
11
11
  channel: string;
12
12
  replicateTo: Player?;
13
- data: Shared.GenericDataTable;
13
+ data: BaseReplion.GenericDataTable;
14
14
  };
15
- type Observer = (newValue: any, oldValue: any) -> ();
16
15
 
17
16
  local remote = script.Parent:FindFirstChild(Shared.REMOTE_NAME) :: RemoteEvent;
18
17
  if not remote then
@@ -76,12 +75,10 @@ function Server.new(config: ServerConfig)
76
75
 
77
76
  self.player = config.replicateTo;
78
77
  self.channel = config.channel;
79
- self.data = Sift.Dictionary.copyDeep(config.data);
78
+
79
+ self._base = BaseReplion.new(Sift.Dictionary.copyDeep(config.data));
80
80
 
81
- self._signals = {} :: { [string]: any };
82
- self._allSignal = Signal.new();
83
-
84
- self._queuedUpdates = {} :: Shared.GenericDataTable;
81
+ self._queuedUpdates = {} :: BaseReplion.GenericDataTable;
85
82
  self._isQueued = false;
86
83
  self._destroyed = false;
87
84
 
@@ -99,27 +96,21 @@ function Server.new(config: ServerConfig)
99
96
  return self;
100
97
  end;
101
98
 
102
- function Server:observe(path: { string }?, callback: Observer)
103
- if not path then
104
- task.spawn(callback, self.data, nil);
105
- return self._allSignal:Connect(callback);
106
- end;
107
-
108
- local signalKey = Shared.getSignalKey(path);
109
- if not self._signals[signalKey] then
110
- self._signals[signalKey] = Signal.new();
111
- end;
99
+ function Server:subscribe(path: { string }?, callback: BaseReplion.Observer)
100
+ return self._base:subscribe(path, callback);
101
+ end;
112
102
 
113
- local initialValue = Shared.getNestedValue(self.data, path);
103
+ function Server:observe(path: { string }?, callback: BaseReplion.Observer)
104
+ return self._base:observe(path, callback);
105
+ end;
114
106
 
115
- task.spawn(callback, initialValue, nil);
116
- local connection = self._signals[signalKey]:Connect(callback);
117
- return function()
118
- connection:Disconnect();
119
- end;
107
+ function Server:get(path: { string }?)
108
+ return self._base:get(path);
120
109
  end;
121
110
 
122
111
  function Server:_notifyDeep(currentPath: { string }, newValue: any, oldValue: any)
112
+ local signals = self._base._signals;
113
+
123
114
  if typeof(newValue) == 'table' then
124
115
  for k, v in newValue do
125
116
  local nextPath = table.clone(currentPath);
@@ -128,7 +119,7 @@ function Server:_notifyDeep(currentPath: { string }, newValue: any, oldValue: an
128
119
  local nextOld = if typeof(oldValue) == 'table' then oldValue[k] else nil;
129
120
 
130
121
  local signalKey = Shared.getSignalKey(nextPath);
131
- local signal = self._signals[signalKey];
122
+ local signal = signals[signalKey];
132
123
  if signal then
133
124
  signal:Fire(v, nextOld);
134
125
  end;
@@ -145,7 +136,7 @@ function Server:_notifyDeep(currentPath: { string }, newValue: any, oldValue: an
145
136
  table.insert(nextPath, k);
146
137
 
147
138
  local signalKey = Shared.getSignalKey(nextPath);
148
- local signal = self._signals[signalKey];
139
+ local signal = signals[signalKey];
149
140
  if signal then
150
141
  signal:Fire(nil, v);
151
142
  end;
@@ -160,25 +151,26 @@ function Server:set(path: { string }, value: any)
160
151
 
161
152
  local topKey = path[1];
162
153
 
163
- local oldValue = Shared.getNestedValue(self.data, path);
154
+ local oldValue = Shared.getNestedValue(self._base.data, path);
164
155
  local newValue = if typeof(value) == 'function' then value(oldValue) else value;
165
156
  if oldValue == newValue then return; end;
166
157
 
167
- local oldTop = self.data[topKey];
158
+ local oldTop = self._base.data[topKey];
168
159
 
169
- setNestedValue(self.data, path, newValue);
160
+ setNestedValue(self._base.data, path, newValue);
170
161
  queueNestedUpdate(self._queuedUpdates, path, newValue);
171
162
  self:_scheduleUpdatesFlush();
172
163
 
173
164
  do
174
- local topSignal = self._signals[topKey];
165
+ local signals = self._base._signals;
166
+ local topSignal = signals[topKey];
175
167
  if topSignal then
176
- topSignal:Fire(self.data[topKey], oldTop);
168
+ topSignal:Fire(self._base.data[topKey], oldTop);
177
169
  end;
178
170
 
179
171
  local signalKey = Shared.getSignalKey(path);
180
172
  if signalKey ~= topKey then
181
- local pathSignal = self._signals[signalKey];
173
+ local pathSignal = signals[signalKey];
182
174
  if pathSignal then
183
175
  pathSignal:Fire(newValue, oldValue)
184
176
  end;
@@ -188,26 +180,16 @@ function Server:set(path: { string }, value: any)
188
180
 
189
181
  local thisUpdate = {};
190
182
  setNestedValue(thisUpdate, path, newValue);
191
- self._allSignal:Fire(self.data, thisUpdate);
183
+ self._base._allSignal:Fire(self._base.data, thisUpdate);
192
184
  end;
193
185
  end;
194
186
 
195
- function Server:get(path: { string }?)
196
- if not path then return self.data; end;
197
- return Shared.getNestedValue(self.data, path);
198
- end;
199
-
200
187
  function Server:destroy()
201
188
  self._destroyed = true;
202
- self.data = {};
203
189
  self._queuedUpdates = {};
204
-
205
- self._allSignal:Destroy();
206
- for _, signal in self._signals do
207
- signal:Destroy();
208
- end;
209
- self._signals = {};
210
-
190
+
191
+ self._base:destroy();
192
+
211
193
  if self.player then
212
194
  local playerReplions = self._activeReplions[self.player];
213
195
  if not playerReplions then return; end;
@@ -249,7 +231,7 @@ function Server:_sendInitial(player: Player?)
249
231
  local receiver = player or self.player;
250
232
  if not receiver then return; end;
251
233
 
252
- remote:FireClient(receiver, 'I', self.channel, self.data);
234
+ remote:FireClient(receiver, 'I', self.channel, self._base.data);
253
235
  end;
254
236
 
255
237
  remote.OnServerEvent:Connect(function(player, action, ...)
package/src/Shared.lua CHANGED
@@ -1,6 +1,4 @@
1
1
  --!strict
2
- export type GenericDataTable = { [string]: any };
3
-
4
2
  local Shared = {};
5
3
 
6
4
  Shared.REMOTE_NAME = 'ReplionNetwork';
package/src/index.d.ts CHANGED
@@ -6,7 +6,6 @@ declare namespace Replion {
6
6
  type Path<T> = T extends object
7
7
  ? { [K in keyof T & string]: [K] | [K, ...Path<T[K]>] }[keyof T & string]
8
8
  : never;
9
-
10
9
  /**
11
10
  * Resolves the type of the value at a specific path P within object T.
12
11
  */
@@ -30,6 +29,12 @@ declare namespace Replion {
30
29
  get(): T;
31
30
  get<P extends Path<T>>(path: P): PathValue<T, P>;
32
31
 
32
+ subscribe(key: undefined, callback: (newValue: T, oldValue: Partial<T>) => void): Cleanup;
33
+ subscribe<P extends Path<T>>(
34
+ path: P,
35
+ callback: (newValue: PathValue<T, P>, oldValue: PathValue<T, P> | undefined) => void,
36
+ ): Cleanup;
37
+
33
38
  observe(key: undefined, callback: (newValue: T, oldValue: Partial<T>) => void): Cleanup;
34
39
  observe<P extends Path<T>>(
35
40
  path: P,