@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 +1 -1
- package/package.json +2 -2
- package/src/BaseReplion.lua +68 -0
- package/src/Client.lua +28 -42
- package/src/Server.lua +29 -47
- package/src/Shared.lua +0 -2
- package/src/index.d.ts +6 -1
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -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:
|
|
51
|
+
function Client.new(channel: string, data: BaseReplion.GenericDataTable)
|
|
52
52
|
local self = setmetatable({}, Client);
|
|
53
53
|
|
|
54
54
|
self.channel = channel;
|
|
55
|
-
|
|
56
|
-
self.
|
|
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
|
-
|
|
64
|
-
return Shared.getNestedValue(self.data, path);
|
|
62
|
+
return self._base:get(path);
|
|
65
63
|
end;
|
|
66
64
|
|
|
67
|
-
function Client:
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
81
|
-
|
|
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 =
|
|
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 =
|
|
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:
|
|
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.
|
|
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:
|
|
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
|
-
|
|
78
|
+
|
|
79
|
+
self._base = BaseReplion.new(Sift.Dictionary.copyDeep(config.data));
|
|
80
80
|
|
|
81
|
-
self.
|
|
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:
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
103
|
+
function Server:observe(path: { string }?, callback: BaseReplion.Observer)
|
|
104
|
+
return self._base:observe(path, callback);
|
|
105
|
+
end;
|
|
114
106
|
|
|
115
|
-
|
|
116
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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.
|
|
206
|
-
|
|
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
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,
|