@rbxts/replion 1.0.11 → 1.0.13

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