@rbxts/replion 1.0.15 → 1.0.17

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.15"`
5
+ `Replion = "shouxtech/replion@1.0.17"`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rbxts/replion",
3
- "version": "1.0.15",
3
+ "version": "1.0.17",
4
4
  "description": "",
5
5
  "main": "src/init.lua",
6
6
  "scripts": {},
@@ -20,9 +20,5 @@
20
20
  "@rbxts/types": "^1.0.896",
21
21
  "roblox-ts": "^3.0.0",
22
22
  "typescript": "^5.9.3"
23
- },
24
- "dependencies": {
25
- "@rbxts/sift": "^0.0.11",
26
- "@rbxts/sleitnick-signal": "^1.0.8"
27
23
  }
28
- }
24
+ }
package/src/Client.lua CHANGED
@@ -5,6 +5,7 @@ local Shared = require(script.Parent.Shared);
5
5
  local Signal = require(isTypeScriptEnv and dependencies['sleitnick-signal'] or dependencies.Signal);
6
6
 
7
7
  type Observer = (newValue: any, oldValue: any) -> ();
8
+ type Deletion = { path: { string }, oldTable: any };
8
9
 
9
10
  local remote = script.Parent:WaitForChild(Shared.REMOTE_NAME) :: RemoteEvent;
10
11
 
@@ -77,10 +78,46 @@ function Client:observe(path: { string }?, callback: Observer)
77
78
  local initialValue = Shared.getNestedValue(self.data, path);
78
79
 
79
80
  task.spawn(callback, initialValue, nil);
80
- return self._signals[signalKey]:Connect(callback);
81
+ local connection = self._signals[signalKey]:Connect(callback);
82
+ return function()
83
+ connection:Disconnect();
84
+ end;
85
+ end;
86
+
87
+ function Client:_collectDeletions(target: any, updates: any, path: { string }, out: { Deletion })
88
+ for k, v in updates do
89
+ local currentVal = target[k];
90
+ local newPath = table.clone(path);
91
+ table.insert(newPath, k);
92
+
93
+ if v == Shared.NIL_SENTINEL or (typeof(currentVal) == 'table' and typeof(v) ~= 'table') then
94
+ if typeof(currentVal) == 'table' then
95
+ table.insert(out, { path = newPath, oldTable = currentVal });
96
+ end;
97
+ elseif typeof(v) == 'table' and typeof(currentVal) == 'table' then
98
+ self:_collectDeletions(currentVal, v, newPath, out);
99
+ end;
100
+ end;
101
+ end;
102
+
103
+ function Client:_notifyDeepDeletion(path: { string }, oldTable: any)
104
+ for k, v in oldTable do
105
+ local nextPath = table.clone(path);
106
+ table.insert(nextPath, k);
107
+
108
+ local signalKey = Shared.getSignalKey(nextPath);
109
+ local signal = self._signals[signalKey];
110
+ if signal then
111
+ signal:Fire(nil, v);
112
+ end;
113
+
114
+ if typeof(v) == 'table' then
115
+ self:_notifyDeepDeletion(nextPath, v);
116
+ end;
117
+ end;
81
118
  end;
82
119
 
83
- function Client:_recursiveSignalCheck(currentPath: { string }, updateTree: any)
120
+ function Client:_notifyRecursiveUpdates(currentPath: { string }, updateTree: any)
84
121
  for k, v in updateTree do
85
122
  local nextPath = table.clone(currentPath);
86
123
  table.insert(nextPath, k);
@@ -94,30 +131,36 @@ function Client:_recursiveSignalCheck(currentPath: { string }, updateTree: any)
94
131
  end;
95
132
 
96
133
  if typeof(v) == 'table' then
97
- self:_recursiveSignalCheck(nextPath, v);
134
+ self:_notifyRecursiveUpdates(nextPath, v);
98
135
  end;
99
136
  end;
100
137
  end;
101
138
 
102
139
  function Client:_applyUpdates(updates: Shared.GenericDataTable)
140
+ local deletions: { Deletion } = {};
141
+ self:_collectDeletions(self.data, updates, {}, deletions);
142
+
103
143
  local oldData = table.clone(self.data); -- Note: Shallow copy instead of deep copy.
104
144
 
105
145
  deepMergeAndApply(self.data, updates);
106
146
 
107
147
  local anyChanged = false;
108
148
 
149
+ for _, item in deletions do
150
+ self:_notifyDeepDeletion(item.path, item.oldTable);
151
+ end;
152
+
109
153
  for key, newValue in updates do
110
154
  local oldValue = oldData[key];
111
155
 
112
156
  -- If it's a table, we assume it changed if it's in the updates list.
113
157
  if typeof(newValue) == 'table' then
114
- anyChanged = true
158
+ anyChanged = true;
115
159
  local signal = self._signals[key];
116
160
  if signal then
117
161
  signal:Fire(self.data[key], self.data[key]); -- Warning: Sends improper 'old' argument due to shallow copy optimization.
118
162
  end;
119
-
120
- self:_recursiveSignalCheck({key}, newValue);
163
+ self:_notifyRecursiveUpdates({key}, newValue);
121
164
  elseif self.data[key] ~= oldValue then
122
165
  anyChanged = true;
123
166
  local signal = self._signals[key];
package/src/Server.lua CHANGED
@@ -113,24 +113,44 @@ function Server:observe(path: { string }?, callback: Observer)
113
113
  local initialValue = Shared.getNestedValue(self.data, path);
114
114
 
115
115
  task.spawn(callback, initialValue, nil);
116
- return self._signals[signalKey]:Connect(callback);
116
+ local connection = self._signals[signalKey]:Connect(callback);
117
+ return function()
118
+ connection:Disconnect();
119
+ end;
117
120
  end;
118
121
 
119
- function Server:_recursiveSignalCheck(currentPath: { string }, updateTree: any)
120
- for k, v in updateTree do
121
- local nextPath = table.clone(currentPath);
122
- table.insert(nextPath, k);
123
-
124
- local signalKey = Shared.getSignalKey(nextPath);
125
- local signal = self._signals[signalKey];
122
+ function Server:_notifyDeep(currentPath: { string }, newValue: any, oldValue: any)
123
+ if typeof(newValue) == 'table' then
124
+ for k, v in newValue do
125
+ local nextPath = table.clone(currentPath);
126
+ table.insert(nextPath, k);
127
+
128
+ local nextOld = if typeof(oldValue) == 'table' then oldValue[k] else nil;
129
+
130
+ local signalKey = Shared.getSignalKey(nextPath);
131
+ local signal = self._signals[signalKey];
132
+ if signal then
133
+ signal:Fire(v, nextOld);
134
+ end;
126
135
 
127
- if signal then
128
- local newValue = Shared.getNestedValue(self.data, nextPath);
129
- signal:Fire(newValue, newValue); -- Warning: Sends improper 'old' argument due to shallow copy optimization.
136
+ self:_notifyDeep(nextPath, v, nextOld);
130
137
  end;
138
+ end;
139
+
140
+ if typeof(oldValue) == 'table' then
141
+ for k, v in oldValue do
142
+ if typeof(newValue) == 'table' and newValue[k] ~= nil then continue; end;
131
143
 
132
- if typeof(v) == 'table' then
133
- self:_recursiveSignalCheck(nextPath, v);
144
+ local nextPath = table.clone(currentPath);
145
+ table.insert(nextPath, k);
146
+
147
+ local signalKey = Shared.getSignalKey(nextPath);
148
+ local signal = self._signals[signalKey];
149
+ if signal then
150
+ signal:Fire(nil, v);
151
+ end;
152
+
153
+ self:_notifyDeep(nextPath, nil, v);
134
154
  end;
135
155
  end;
136
156
  end;
@@ -163,10 +183,8 @@ function Server:set(path: { string }, value: any)
163
183
  pathSignal:Fire(newValue, oldValue)
164
184
  end;
165
185
  end;
166
-
167
- if typeof(newValue) == 'table' then
168
- self:_recursiveSignalCheck(path, newValue);
169
- end;
186
+
187
+ self:_notifyDeep(path, newValue, oldValue);
170
188
 
171
189
  local thisUpdate = {};
172
190
  setNestedValue(thisUpdate, path, newValue);
package/src/index.d.ts CHANGED
@@ -1,5 +1,3 @@
1
- import { Connection } from '@rbxts/sleitnick-signal';
2
-
3
1
  declare namespace Replion {
4
2
  /**
5
3
  * Recursively builds a union of all valid path tuples for object T.
@@ -20,6 +18,8 @@ declare namespace Replion {
20
18
  : never
21
19
  : never;
22
20
 
21
+ type Cleanup = () => void;
22
+
23
23
  interface ServerConfig<T extends object> {
24
24
  channel: string;
25
25
  replicateTo?: Player;
@@ -30,11 +30,11 @@ declare namespace Replion {
30
30
  get(): T;
31
31
  get<P extends Path<T>>(path: P): PathValue<T, P>;
32
32
 
33
- observe(key: undefined, callback: (newValue: T, oldValue: Partial<T>) => void): Connection;
33
+ observe(key: undefined, callback: (newValue: T, oldValue: Partial<T>) => void): Cleanup;
34
34
  observe<P extends Path<T>>(
35
35
  path: P,
36
36
  callback: (newValue: PathValue<T, P>, oldValue: PathValue<T, P> | undefined) => void,
37
- ): Connection;
37
+ ): Cleanup;
38
38
 
39
39
  destroy(): void;
40
40
  }