@rbxts/replion 1.0.10 → 1.0.12

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.10"`
5
+ `Replion = "shouxtech/replion@1.0.12"`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rbxts/replion",
3
- "version": "1.0.10",
3
+ "version": "1.0.12",
4
4
  "description": "",
5
5
  "main": "src/init.lua",
6
6
  "scripts": {},
package/src/Client.lua CHANGED
@@ -58,29 +58,45 @@ function Client.new(channel: string, data: Shared.GenericDataTable)
58
58
  return self;
59
59
  end;
60
60
 
61
- function Client:get(key: (string | {string})?)
62
- if not key then return self.data; end;
63
- if typeof(key) == 'table' then
64
- return Shared.getNestedValue(self.data, key);
65
- end;
66
-
67
- return self.data[key];
61
+ function Client:get(path: { string }?)
62
+ if not path then return self.data; end;
63
+ return Shared.getNestedValue(self.data, path);
68
64
  end;
69
65
 
70
- function Client:observe(key: string?, callback: Observer)
71
- if not key then
66
+ function Client:observe(path: { string }?, callback: Observer)
67
+ if not path then
72
68
  task.spawn(callback, self.data, nil);
73
-
74
69
  return self._allSignal:Connect(callback);
75
70
  end;
76
71
 
77
- if not self._signals[key] then
78
- self._signals[key] = Signal.new();
72
+ local signalKey = Shared.getSignalKey(path);
73
+ if not self._signals[signalKey] then
74
+ self._signals[signalKey] = Signal.new();
79
75
  end;
80
76
 
81
- task.spawn(callback, self.data[key], nil);
77
+ local initialValue = Shared.getNestedValue(self.data, path);
82
78
 
83
- return self._signals[key]:Connect(callback);
79
+ task.spawn(callback, initialValue, nil);
80
+ return self._signals[signalKey]:Connect(callback);
81
+ end;
82
+
83
+ function Client:_recursiveSignalCheck(currentPath: { string }, updateTree: any)
84
+ for k, v in updateTree do
85
+ local nextPath = table.clone(currentPath);
86
+ table.insert(nextPath, k);
87
+
88
+ local signalKey = Shared.getSignalKey(nextPath);
89
+ local signal = self._signals[signalKey];
90
+
91
+ if signal then
92
+ local newValue = Shared.getNestedValue(self.data, nextPath);
93
+ signal:Fire(newValue, newValue); -- Warning: Sends improper 'old' argument due to shallow copy optimization.
94
+ end;
95
+
96
+ if typeof(v) == 'table' then
97
+ self:_recursiveSignalCheck(nextPath, v);
98
+ end;
99
+ end;
84
100
  end;
85
101
 
86
102
  function Client:_applyUpdates(updates: Shared.GenericDataTable)
@@ -93,13 +109,15 @@ function Client:_applyUpdates(updates: Shared.GenericDataTable)
93
109
  for key, newValue in updates do
94
110
  local oldValue = oldData[key];
95
111
 
96
- -- If it's a table, we assume it changed if it's in the update list.
112
+ -- If it's a table, we assume it changed if it's in the updates list.
97
113
  if typeof(newValue) == 'table' then
98
114
  anyChanged = true
99
115
  local signal = self._signals[key];
100
116
  if signal then
101
117
  signal:Fire(self.data[key], self.data[key]); -- Warning: Sends improper 'old' argument due to shallow copy optimization.
102
118
  end;
119
+
120
+ self:_recursiveSignalCheck({key}, newValue);
103
121
  elseif self.data[key] ~= oldValue then
104
122
  anyChanged = true;
105
123
  local signal = self._signals[key];
@@ -126,7 +144,7 @@ remote.OnClientEvent:Connect(function(type, channel, payload)
126
144
  if type == 'I' then
127
145
  local newReplion = Client.new(channel, payload);
128
146
  Client._activeReplions[channel] = newReplion;
129
-
147
+
130
148
  local initializedSignal = Client._replionInitializedSignals[channel];
131
149
  if not initializedSignal then return; end;
132
150
 
package/src/Server.lua CHANGED
@@ -99,24 +99,26 @@ function Server.new(config: ServerConfig)
99
99
  return self;
100
100
  end;
101
101
 
102
- function Server:observe(key: string?, callback: Observer)
103
- if not key then
102
+ function Server:observe(path: { string }?, callback: Observer)
103
+ if not path then
104
104
  task.spawn(callback, self.data, nil);
105
105
  return self._allSignal:Connect(callback);
106
106
  end;
107
107
 
108
- if not self._signals[key] then
109
- self._signals[key] = Signal.new();
108
+ local signalKey = Shared.getSignalKey(path);
109
+ if not self._signals[signalKey] then
110
+ self._signals[signalKey] = Signal.new();
110
111
  end;
111
112
 
112
- task.spawn(callback, self.data[key], nil);
113
- return self._signals[key]:Connect(callback);
113
+ local initialValue = Shared.getNestedValue(self.data, path);
114
+
115
+ task.spawn(callback, initialValue, nil);
116
+ return self._signals[signalKey]:Connect(callback);
114
117
  end;
115
118
 
116
- function Server:set(key: string | {string}, value: any)
119
+ function Server:set(path: { string }, value: any)
117
120
  if self._destroyed then return; end;
118
121
 
119
- local path = if typeof(key) == 'table' then key else {key};
120
122
  local topKey = path[1];
121
123
 
122
124
  local oldValue = Shared.getNestedValue(self.data, path);
@@ -130,9 +132,17 @@ function Server:set(key: string | {string}, value: any)
130
132
  self:_scheduleUpdatesFlush();
131
133
 
132
134
  do
133
- local signal = self._signals[topKey];
134
- if signal then
135
- signal:Fire(self.data[topKey], oldTop);
135
+ local topSignal = self._signals[topKey];
136
+ if topSignal then
137
+ topSignal:Fire(self.data[topKey], oldTop);
138
+ end;
139
+
140
+ local signalKey = Shared.getSignalKey(path);
141
+ if signalKey ~= topKey then
142
+ local pathSignal = self._signals[signalKey];
143
+ if pathSignal then
144
+ pathSignal:Fire(newValue, oldValue)
145
+ end;
136
146
  end;
137
147
 
138
148
  local thisUpdate = {};
@@ -141,13 +151,9 @@ function Server:set(key: string | {string}, value: any)
141
151
  end;
142
152
  end;
143
153
 
144
- function Server:get(key: (string | {string})?)
145
- if not key then return self.data; end;
146
- if typeof(key) == 'table' then
147
- return Shared.getNestedValue(self.data, key);
148
- end;
149
-
150
- return self.data[key];
154
+ function Server:get(path: { string }?)
155
+ if not path then return self.data; end;
156
+ return Shared.getNestedValue(self.data, path);
151
157
  end;
152
158
 
153
159
  function Server:destroy()
@@ -230,4 +236,4 @@ Players.PlayerRemoving:Connect(function(player)
230
236
  Server._activeReplions[player] = nil;
231
237
  end);
232
238
 
233
- return Server;
239
+ return Server;
package/src/Shared.lua CHANGED
@@ -15,4 +15,9 @@ function Shared.getNestedValue(root: any, path: { string })
15
15
  return current;
16
16
  end;
17
17
 
18
+ function Shared.getSignalKey(key: string | { string })
19
+ if typeof(key) == 'string' then return key; end;
20
+ return table.concat(key, '\0');
21
+ end;
22
+
18
23
  return Shared;
package/src/index.d.ts CHANGED
@@ -1,6 +1,25 @@
1
1
  import { Connection } from '@rbxts/sleitnick-signal';
2
2
 
3
3
  declare namespace Replion {
4
+ /**
5
+ * Recursively builds a union of all valid path tuples for object T.
6
+ * E.g., { a: { b: 1 } } -> ['a'] | ['a', 'b']
7
+ */
8
+ type Path<T> = T extends object
9
+ ? { [K in keyof T & string]: [K] | [K, ...Path<T[K]>] }[keyof T & string]
10
+ : never;
11
+
12
+ /**
13
+ * Resolves the type of the value at a specific path P within object T.
14
+ */
15
+ type PathValue<T, P extends any[]> = P extends [infer K, ...infer R]
16
+ ? K extends keyof T
17
+ ? R extends []
18
+ ? T[K]
19
+ : PathValue<T[K], R>
20
+ : never
21
+ : never;
22
+
4
23
  interface ServerConfig<T extends object> {
5
24
  channel: string;
6
25
  replicateTo?: Player;
@@ -10,17 +29,18 @@ declare namespace Replion {
10
29
  class Server<T extends object> {
11
30
  constructor(config: ServerConfig<T>);
12
31
 
13
- set<K extends keyof T>(key: K, value: T[K] | ((oldValue: T[K]) => T[K])): void;
14
- set(path: string[], value: any | ((oldValue: any) => any)): void;
32
+ set<P extends Path<T>>(
33
+ path: P,
34
+ value: PathValue<T, P> | ((oldValue: PathValue<T, P>) => PathValue<T, P>),
35
+ ): void;
15
36
 
16
37
  get(): T;
17
- get<K extends keyof T>(key: K): T[K];
18
- get(path: string[]): any;
38
+ get<P extends Path<T>>(path: P): PathValue<T, P>;
19
39
 
20
40
  observe(key: undefined, callback: (newValue: T, oldValue: Partial<T>) => void): Connection;
21
- observe<K extends keyof T>(
22
- key: K,
23
- callback: (newValue: T[K], oldValue: T[K] | undefined) => void,
41
+ observe<P extends Path<T>>(
42
+ path: P,
43
+ callback: (newValue: PathValue<T, P>, oldValue: PathValue<T, P> | undefined) => void,
24
44
  ): Connection;
25
45
 
26
46
  destroy(): void;
@@ -30,13 +50,12 @@ declare namespace Replion {
30
50
  static waitForReplion: <T extends object>(channel: string) => Client<T>;
31
51
 
32
52
  get(): T;
33
- get<K extends keyof T>(key: K): T[K];
34
- get(path: string[]): any;
53
+ get<P extends Path<T>>(path: P): PathValue<T, P>;
35
54
 
36
55
  observe(key: undefined, callback: (newValue: T, oldValue: Partial<T>) => void): Connection;
37
- observe<K extends keyof T>(
38
- key: K,
39
- callback: (newValue: T[K], oldValue: T[K] | undefined) => void,
56
+ observe<P extends Path<T>>(
57
+ path: P,
58
+ callback: (newValue: PathValue<T, P>, oldValue: PathValue<T, P> | undefined) => void,
40
59
  ): Connection;
41
60
 
42
61
  destroy(): void;