@peers-app/peers-sdk 0.6.15 → 0.6.16
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/dist/context/user-context.js +5 -16
- package/dist/data/devices.d.ts +1 -1
- package/dist/data/persistent-vars.d.ts +23 -11
- package/dist/data/persistent-vars.js +104 -53
- package/dist/observable.d.ts +7 -3
- package/dist/observable.js +6 -8
- package/dist/observable.test.js +130 -4
- package/dist/rpc-types.d.ts +0 -1
- package/dist/rpc-types.js +2 -3
- package/dist/utils.d.ts +2 -2
- package/dist/utils.js +2 -2
- package/package.json +1 -1
|
@@ -12,7 +12,6 @@ const utils_1 = require("../utils");
|
|
|
12
12
|
class UserContext {
|
|
13
13
|
userId;
|
|
14
14
|
dataSourceFactory;
|
|
15
|
-
// public readonly userId: Observable<string> = observable<string>('');
|
|
16
15
|
deviceId = (0, observable_1.observable)('');
|
|
17
16
|
currentlyActiveGroupId = (0, observable_1.observable)();
|
|
18
17
|
packagesRootDir = (0, observable_1.observable)('');
|
|
@@ -21,12 +20,13 @@ class UserContext {
|
|
|
21
20
|
groupIds = (0, observable_1.observable)([]);
|
|
22
21
|
userDataContext;
|
|
23
22
|
groupDataContexts = new Map();
|
|
24
|
-
defaultDataContext
|
|
23
|
+
defaultDataContext;
|
|
25
24
|
loadingPromise;
|
|
26
25
|
constructor(userId, dataSourceFactory) {
|
|
27
26
|
this.userId = userId;
|
|
28
27
|
this.dataSourceFactory = dataSourceFactory;
|
|
29
28
|
this.userDataContext = new data_context_1.DataContext(this);
|
|
29
|
+
this.defaultDataContext = (0, observable_1.observable)(this.userDataContext);
|
|
30
30
|
this.defaultDataContext(this.userDataContext);
|
|
31
31
|
this.loadingPromise = this.init();
|
|
32
32
|
}
|
|
@@ -114,13 +114,11 @@ class UserContext {
|
|
|
114
114
|
acc[curr.name] = curr.value?.value;
|
|
115
115
|
return acc;
|
|
116
116
|
}, {});
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
// linkObservables(userIdPVar, this.userId);
|
|
120
|
-
const deviceIdPVar = (0, data_1.deviceVar)('thisDeviceId', { dbValue: varDbValues['thisDeviceId'], userContext: this });
|
|
117
|
+
const deviceIdDefaultValue = (typeof process !== 'undefined' && process.env?.DEVICE_ID) || '';
|
|
118
|
+
const deviceIdPVar = (0, data_1.deviceVar)('thisDeviceId', { defaultValue: deviceIdDefaultValue, dbValue: varDbValues['thisDeviceId'], userContext: this });
|
|
121
119
|
this.deviceId(deviceIdPVar());
|
|
122
120
|
(0, observable_1.linkObservables)(deviceIdPVar, this.deviceId);
|
|
123
|
-
const currentlyActiveGroupIdPVar = (0, data_1.deviceVar)('currentlyActiveGroupId', { dbValue: varDbValues['currentlyActiveGroupId'], userContext: this });
|
|
121
|
+
const currentlyActiveGroupIdPVar = (0, data_1.deviceVar)('currentlyActiveGroupId', { dbValue: varDbValues['currentlyActiveGroupId'], defaultValue: '', userContext: this });
|
|
124
122
|
this.currentlyActiveGroupId(currentlyActiveGroupIdPVar());
|
|
125
123
|
(0, observable_1.linkObservables)(currentlyActiveGroupIdPVar, this.currentlyActiveGroupId);
|
|
126
124
|
const packagesRootDirPVar = (0, data_1.deviceVar)('packagesRootDir', { defaultValue: '~/peers-packages', dbValue: varDbValues['packagesRootDir'], userContext: this });
|
|
@@ -132,16 +130,7 @@ class UserContext {
|
|
|
132
130
|
const packageLocalPathsResolvedPVar = (0, data_1.deviceVar)('packageLocalPathsResolved', { defaultValue: {}, dbValue: varDbValues['packageLocalPathsResolved'], userContext: this });
|
|
133
131
|
this.packageLocalPathsResolved(packageLocalPathsResolvedPVar());
|
|
134
132
|
(0, observable_1.linkObservables)(packageLocalPathsResolvedPVar, this.packageLocalPathsResolved);
|
|
135
|
-
if (typeof process !== 'undefined' && process.env) {
|
|
136
|
-
// if (process.env.USER_ID) {
|
|
137
|
-
// this.userId(process.env.USER_ID);
|
|
138
|
-
// }
|
|
139
|
-
if (process.env.DEVICE_ID) {
|
|
140
|
-
this.deviceId(process.env.DEVICE_ID);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
133
|
await Promise.all([
|
|
144
|
-
// userIdPVar.loadingPromise,
|
|
145
134
|
deviceIdPVar.loadingPromise,
|
|
146
135
|
packagesRootDirPVar.loadingPromise,
|
|
147
136
|
reloadPackagesOnPageRefreshPVar.loadingPromise,
|
package/dist/data/devices.d.ts
CHANGED
|
@@ -48,4 +48,4 @@ export declare function Devices(dataContext?: DataContext): import("..").Table<{
|
|
|
48
48
|
serverUrl?: string | undefined;
|
|
49
49
|
}>;
|
|
50
50
|
export declare const trustedServers: import("./persistent-vars").PersistentVar<string[]>;
|
|
51
|
-
export declare const thisDeviceId: import("./persistent-vars").PersistentVar<string>;
|
|
51
|
+
export declare const thisDeviceId: import("./persistent-vars").PersistentVar<string | undefined>;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { UserContext } from "../context";
|
|
3
|
+
import type { DataContext } from "../context/data-context";
|
|
2
4
|
import { Observable } from "../observable";
|
|
5
|
+
import { ISaveOptions } from "./orm";
|
|
3
6
|
import { Table } from "./orm/table";
|
|
4
7
|
import { ITableMetaData } from "./orm/types";
|
|
5
|
-
import type { DataContext } from "../context/data-context";
|
|
6
|
-
import { UserContext } from "../context";
|
|
7
|
-
import { ISaveOptions } from "./orm";
|
|
8
8
|
declare const schema: z.ZodObject<{
|
|
9
9
|
persistentVarId: z.ZodString;
|
|
10
10
|
name: z.ZodString;
|
|
@@ -53,35 +53,47 @@ export type PersistentVar<T> = Observable<T> & {
|
|
|
53
53
|
loadingPromise: Promise<PersistentVar<T>>;
|
|
54
54
|
};
|
|
55
55
|
export declare function getPersistentVar(name: string, dataContext?: DataContext): Promise<IPersistentVar | undefined>;
|
|
56
|
-
interface
|
|
56
|
+
interface IPersistentVarOptionsBase {
|
|
57
57
|
scope?: 'device' | 'user' | 'group' | 'groupDevice' | 'groupUser';
|
|
58
|
-
defaultValue?: T;
|
|
59
58
|
userContext?: UserContext;
|
|
60
59
|
dataContext?: DataContext;
|
|
61
|
-
dbValue?: T;
|
|
62
60
|
isSecret?: boolean;
|
|
63
61
|
}
|
|
62
|
+
interface IPersistentVarOptionsWithDefault<T> extends IPersistentVarOptionsBase {
|
|
63
|
+
defaultValue: T;
|
|
64
|
+
dbValue?: T;
|
|
65
|
+
}
|
|
66
|
+
interface IPersistentVarOptionsWithoutDefault<T> extends IPersistentVarOptionsBase {
|
|
67
|
+
defaultValue?: undefined;
|
|
68
|
+
dbValue?: T;
|
|
69
|
+
}
|
|
64
70
|
/**
|
|
65
71
|
* A persistent variable that is stored only on this device, in the user's personal db
|
|
66
72
|
*/
|
|
67
|
-
export declare function deviceVar<T = string>(name: string, opts
|
|
73
|
+
export declare function deviceVar<T = string>(name: string, opts: Omit<IPersistentVarOptionsWithDefault<T>, 'scope' | 'dataContext'>): PersistentVar<T>;
|
|
74
|
+
export declare function deviceVar<T = string>(name: string, opts?: Omit<IPersistentVarOptionsWithoutDefault<T>, 'scope' | 'dataContext'>): PersistentVar<T | undefined>;
|
|
68
75
|
/**
|
|
69
76
|
* A persistent variable that is stored in the user's personal db and synced across all their devices
|
|
70
77
|
*/
|
|
71
|
-
export declare function userVar<T = string>(name: string, opts
|
|
78
|
+
export declare function userVar<T = string>(name: string, opts: Omit<IPersistentVarOptionsWithDefault<T>, 'scope' | 'dataContext'>): PersistentVar<T>;
|
|
79
|
+
export declare function userVar<T = string>(name: string, opts?: Omit<IPersistentVarOptionsWithoutDefault<T>, 'scope' | 'dataContext'>): PersistentVar<T | undefined>;
|
|
72
80
|
/**
|
|
73
81
|
* A persistent variable that is shared by all users in a group. It is stored in the group's db and synced across all users and devices in the group.
|
|
74
82
|
* It can have a different value for each group on the device.
|
|
83
|
+
* If secret=true, it is encrypted with the group's secret key
|
|
75
84
|
*/
|
|
76
|
-
export declare function groupVar<T = string>(name: string, opts
|
|
85
|
+
export declare function groupVar<T = string>(name: string, opts: Omit<IPersistentVarOptionsWithDefault<T>, 'scope'>): PersistentVar<T>;
|
|
86
|
+
export declare function groupVar<T = string>(name: string, opts?: Omit<IPersistentVarOptionsWithoutDefault<T>, 'scope'>): PersistentVar<T | undefined>;
|
|
77
87
|
/**
|
|
78
88
|
* A persistent variable that is stored only on this device, in the user's personal db.
|
|
79
89
|
* It can have a different value for each group on the device but is not synced to other devices.
|
|
80
90
|
*/
|
|
81
|
-
export declare function groupDeviceVar<T = string>(name: string, opts
|
|
91
|
+
export declare function groupDeviceVar<T = string>(name: string, opts: Omit<IPersistentVarOptionsWithDefault<T>, 'scope'>): PersistentVar<T>;
|
|
92
|
+
export declare function groupDeviceVar<T = string>(name: string, opts?: Omit<IPersistentVarOptionsWithoutDefault<T>, 'scope'>): PersistentVar<T | undefined>;
|
|
82
93
|
/**
|
|
83
94
|
* A persistent variable that is stored in the user's personal db and synced across all their devices in the group.
|
|
84
95
|
* It can have a different value for each user in the group and a different value for each group the user is in.
|
|
85
96
|
*/
|
|
86
|
-
export declare function groupUserVar<T = string>(name: string, opts
|
|
97
|
+
export declare function groupUserVar<T = string>(name: string, opts: Omit<IPersistentVarOptionsWithDefault<T>, 'scope'>): PersistentVar<T>;
|
|
98
|
+
export declare function groupUserVar<T = string>(name: string, opts?: Omit<IPersistentVarOptionsWithoutDefault<T>, 'scope'>): PersistentVar<T | undefined>;
|
|
87
99
|
export {};
|
|
@@ -10,13 +10,13 @@ exports.groupDeviceVar = groupDeviceVar;
|
|
|
10
10
|
exports.groupUserVar = groupUserVar;
|
|
11
11
|
const lodash_1 = require("lodash");
|
|
12
12
|
const zod_1 = require("zod");
|
|
13
|
+
const user_context_singleton_1 = require("../context/user-context-singleton");
|
|
13
14
|
const observable_1 = require("../observable");
|
|
15
|
+
const rpc_types_1 = require("../rpc-types");
|
|
14
16
|
const utils_1 = require("../utils");
|
|
15
17
|
const table_1 = require("./orm/table");
|
|
16
|
-
const user_context_singleton_1 = require("../context/user-context-singleton");
|
|
17
18
|
const table_definitions_system_1 = require("./orm/table-definitions.system");
|
|
18
19
|
const types_1 = require("./orm/types");
|
|
19
|
-
const rpc_types_1 = require("../rpc-types");
|
|
20
20
|
const schema = zod_1.z.object({
|
|
21
21
|
persistentVarId: zod_1.z.string(),
|
|
22
22
|
name: zod_1.z.string(),
|
|
@@ -82,45 +82,60 @@ async function getPersistentVar(name, dataContext) {
|
|
|
82
82
|
}
|
|
83
83
|
return pVars[0];
|
|
84
84
|
}
|
|
85
|
-
/**
|
|
86
|
-
* A persistent variable that is stored only on this device, in the user's personal db
|
|
87
|
-
*/
|
|
88
85
|
function deviceVar(name, opts) {
|
|
89
|
-
|
|
86
|
+
const fullOpts = { ...opts, scope: 'device' };
|
|
87
|
+
if (opts?.defaultValue !== undefined) {
|
|
88
|
+
return persistentVarFactory(name, fullOpts);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
return persistentVarFactory(name, fullOpts);
|
|
92
|
+
}
|
|
90
93
|
}
|
|
91
|
-
/**
|
|
92
|
-
* A persistent variable that is stored in the user's personal db and synced across all their devices
|
|
93
|
-
*/
|
|
94
94
|
function userVar(name, opts) {
|
|
95
|
-
|
|
95
|
+
const fullOpts = { ...opts, scope: 'user' };
|
|
96
|
+
if (opts?.defaultValue !== undefined) {
|
|
97
|
+
return persistentVarFactory(name, fullOpts);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
return persistentVarFactory(name, fullOpts);
|
|
101
|
+
}
|
|
96
102
|
}
|
|
97
|
-
/**
|
|
98
|
-
* A persistent variable that is shared by all users in a group. It is stored in the group's db and synced across all users and devices in the group.
|
|
99
|
-
* It can have a different value for each group on the device.
|
|
100
|
-
*/
|
|
101
103
|
function groupVar(name, opts) {
|
|
102
|
-
|
|
104
|
+
const fullOpts = { ...opts, scope: 'group' };
|
|
105
|
+
if (opts?.defaultValue !== undefined) {
|
|
106
|
+
return persistentVarFactory(name, fullOpts);
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
return persistentVarFactory(name, fullOpts);
|
|
110
|
+
}
|
|
103
111
|
}
|
|
104
|
-
/**
|
|
105
|
-
* A persistent variable that is stored only on this device, in the user's personal db.
|
|
106
|
-
* It can have a different value for each group on the device but is not synced to other devices.
|
|
107
|
-
*/
|
|
108
112
|
function groupDeviceVar(name, opts) {
|
|
109
|
-
|
|
113
|
+
const fullOpts = { ...opts, scope: 'groupDevice' };
|
|
114
|
+
if (opts?.defaultValue !== undefined) {
|
|
115
|
+
return persistentVarFactory(name, fullOpts);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
return persistentVarFactory(name, fullOpts);
|
|
119
|
+
}
|
|
110
120
|
}
|
|
111
|
-
/**
|
|
112
|
-
* A persistent variable that is stored in the user's personal db and synced across all their devices in the group.
|
|
113
|
-
* It can have a different value for each user in the group and a different value for each group the user is in.
|
|
114
|
-
*/
|
|
115
121
|
function groupUserVar(name, opts) {
|
|
116
|
-
|
|
122
|
+
const fullOpts = { ...opts, scope: 'groupUser' };
|
|
123
|
+
if (opts?.defaultValue !== undefined) {
|
|
124
|
+
return persistentVarFactory(name, fullOpts);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
return persistentVarFactory(name, fullOpts);
|
|
128
|
+
}
|
|
117
129
|
}
|
|
118
|
-
function
|
|
119
|
-
const
|
|
130
|
+
function persistentVarFactory(name, opts) {
|
|
131
|
+
const setWithDbValue = Object.keys(opts ?? {}).includes('dbValue');
|
|
132
|
+
const defaultValue = opts?.defaultValue;
|
|
133
|
+
const initialValue = setWithDbValue ? opts?.dbValue : defaultValue;
|
|
134
|
+
const persistentVar = (initialValue !== undefined ? (0, observable_1.observable)(initialValue) : (0, observable_1.observable)());
|
|
120
135
|
const scope = opts.scope || 'device';
|
|
121
136
|
let isSecret = opts.isSecret;
|
|
122
137
|
let rec = undefined;
|
|
123
|
-
|
|
138
|
+
persistentVar.loadingPromise = new Promise(async (resolve) => {
|
|
124
139
|
const userContext = opts?.userContext || await (0, user_context_singleton_1.getUserContext)();
|
|
125
140
|
function getDataContext() {
|
|
126
141
|
if (opts?.dataContext) {
|
|
@@ -155,11 +170,11 @@ function pvar(name, opts) {
|
|
|
155
170
|
}
|
|
156
171
|
if (!dbRec) {
|
|
157
172
|
dbRec = {
|
|
158
|
-
persistentVarId:
|
|
173
|
+
persistentVarId: '',
|
|
159
174
|
name,
|
|
160
175
|
scope,
|
|
161
176
|
isSecret,
|
|
162
|
-
value: { value:
|
|
177
|
+
value: { value: defaultValue },
|
|
163
178
|
};
|
|
164
179
|
}
|
|
165
180
|
// if the db says it's secret but the caller didn't specify, assume it's secret
|
|
@@ -168,8 +183,13 @@ function pvar(name, opts) {
|
|
|
168
183
|
}
|
|
169
184
|
return dbRec;
|
|
170
185
|
}
|
|
171
|
-
async function
|
|
172
|
-
if (
|
|
186
|
+
async function reactToValueChanged(value) {
|
|
187
|
+
if (name === 'colorModePreference')
|
|
188
|
+
console.log(`colorModePreferences pvar value set: ${value}`);
|
|
189
|
+
if (!rec?.persistentVarId && value === defaultValue) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if (!rec?.persistentVarId) {
|
|
173
193
|
rec = await loadRecFromDb();
|
|
174
194
|
}
|
|
175
195
|
const oldValue = rec.value.value;
|
|
@@ -180,51 +200,82 @@ function pvar(name, opts) {
|
|
|
180
200
|
const dc = getDataContext();
|
|
181
201
|
const table = PersistentVars(dc);
|
|
182
202
|
// delete if value equals default value
|
|
183
|
-
if (rec.value.value
|
|
184
|
-
|
|
185
|
-
|
|
203
|
+
if ((0, lodash_1.isEqual)(rec.value.value, defaultValue)) {
|
|
204
|
+
if (rec.persistentVarId) {
|
|
205
|
+
if (name === 'colorModePreference') {
|
|
206
|
+
console.log(`deleted persistent var ${name} from db:`, rec.value.value);
|
|
207
|
+
}
|
|
208
|
+
await table.delete(rec);
|
|
209
|
+
}
|
|
210
|
+
rec = undefined;
|
|
186
211
|
}
|
|
187
212
|
else {
|
|
188
|
-
|
|
213
|
+
try {
|
|
214
|
+
await table.save(rec);
|
|
215
|
+
if (name === 'colorModePreference') {
|
|
216
|
+
console.log(`Saved var ${name} to db:`, rec.value.value);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
catch (err) {
|
|
220
|
+
const errMsg = err?.message || String(err) || '';
|
|
221
|
+
if (errMsg.includes('UNIQUE constraint failed')) {
|
|
222
|
+
console.warn(`Detected UNIQUE constraint failed error when saving persistent var, reloading and retrying: ${name}`);
|
|
223
|
+
rec = await loadRecFromDb();
|
|
224
|
+
persistentVar(rec.value.value);
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
console.error('Error saving persistent var', { name, value, rec, err });
|
|
228
|
+
throw err;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
189
231
|
}
|
|
190
232
|
}
|
|
191
233
|
// subscribe to db changes
|
|
192
234
|
userContext.subscribeToDataChangedAcrossAllGroups(PersistentVars(), async (evt) => {
|
|
193
235
|
const dbRec = evt.data.dataObject;
|
|
194
236
|
const dbName = getVarNameInDb();
|
|
195
|
-
if (dbRec.name === dbName
|
|
196
|
-
rec =
|
|
237
|
+
if (!rec?.persistentVarId && dbRec.name === dbName) {
|
|
238
|
+
rec = dbRec;
|
|
197
239
|
}
|
|
198
240
|
if (dbRec.persistentVarId === rec?.persistentVarId) {
|
|
199
|
-
rec = dbRec;
|
|
200
241
|
if (evt.data.op === 'delete') {
|
|
201
|
-
rec
|
|
202
|
-
|
|
242
|
+
rec = undefined;
|
|
243
|
+
if (defaultValue !== undefined) {
|
|
244
|
+
persistentVar(defaultValue);
|
|
245
|
+
}
|
|
203
246
|
}
|
|
204
|
-
|
|
205
|
-
|
|
247
|
+
else {
|
|
248
|
+
rec = dbRec;
|
|
249
|
+
if (!(0, lodash_1.isEqual)(persistentVar(), rec.value.value)) {
|
|
250
|
+
persistentVar(rec.value.value);
|
|
251
|
+
}
|
|
206
252
|
}
|
|
207
|
-
return;
|
|
208
253
|
}
|
|
209
254
|
});
|
|
210
255
|
// update group-dependent variables if group context changes and it hasn't been pinned to a specific data context
|
|
211
256
|
const groupDependentScopes = ['group', 'groupDevice', 'groupUser'];
|
|
212
257
|
if (!opts?.dataContext && groupDependentScopes.includes(scope)) {
|
|
213
|
-
userContext.defaultDataContext.subscribe(async (
|
|
258
|
+
userContext.defaultDataContext.subscribe(async () => {
|
|
214
259
|
rec = await loadRecFromDb();
|
|
215
|
-
|
|
260
|
+
persistentVar(rec.value.value);
|
|
216
261
|
});
|
|
217
262
|
}
|
|
218
|
-
if (!
|
|
263
|
+
if (!setWithDbValue) {
|
|
219
264
|
rec = await loadRecFromDb();
|
|
220
|
-
if (!(0, lodash_1.isEqual)(rec.value.value,
|
|
221
|
-
|
|
265
|
+
if (!(0, lodash_1.isEqual)(rec.value.value, persistentVar())) {
|
|
266
|
+
if (name === 'colorModePreference') {
|
|
267
|
+
console.log(`Loaded persistent var ${name} from db:`, rec.value.value);
|
|
268
|
+
}
|
|
269
|
+
persistentVar(rec.value.value);
|
|
222
270
|
}
|
|
223
271
|
}
|
|
224
|
-
|
|
225
|
-
|
|
272
|
+
persistentVar.subscribe(newValue => {
|
|
273
|
+
persistentVar.loadingPromise = persistentVar.loadingPromise.then(async () => {
|
|
274
|
+
await reactToValueChanged(newValue);
|
|
275
|
+
return persistentVar;
|
|
276
|
+
});
|
|
226
277
|
});
|
|
227
|
-
resolve(
|
|
278
|
+
resolve(persistentVar);
|
|
228
279
|
});
|
|
229
|
-
return
|
|
280
|
+
return persistentVar;
|
|
230
281
|
}
|
package/dist/observable.d.ts
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
type ObservableFn<T> =
|
|
1
|
+
type ObservableFn<T> = {
|
|
2
|
+
(): T;
|
|
3
|
+
(newValue: T): T;
|
|
4
|
+
};
|
|
2
5
|
export type Subscription = {
|
|
3
6
|
dispose: () => void;
|
|
4
7
|
};
|
|
5
8
|
type ObservableProps<T> = {
|
|
6
9
|
subscribe: (subscriber: (newValue: T) => void) => Subscription;
|
|
7
|
-
notifySubscribers: (
|
|
10
|
+
notifySubscribers: () => void;
|
|
8
11
|
subscriberCount: () => number;
|
|
9
12
|
};
|
|
10
13
|
export type Observable<T> = ObservableFn<T> & ObservableProps<T>;
|
|
11
|
-
export declare function observable<T>(initialValue
|
|
14
|
+
export declare function observable<T>(initialValue: T): Observable<T>;
|
|
15
|
+
export declare function observable<T = undefined>(): Observable<T | undefined>;
|
|
12
16
|
export declare function computed<T>(fn: ({
|
|
13
17
|
read: () => T;
|
|
14
18
|
write?: (val: T) => void;
|
package/dist/observable.js
CHANGED
|
@@ -12,20 +12,18 @@ const runningComputes = {};
|
|
|
12
12
|
function observable(initialValue) {
|
|
13
13
|
let value = initialValue;
|
|
14
14
|
const subscribers = [];
|
|
15
|
-
const obs = ((
|
|
15
|
+
const obs = ((...args) => {
|
|
16
16
|
Object.values(runningComputes).forEach(c => c.push(obs));
|
|
17
|
-
|
|
17
|
+
const isSetting = args.length > 0;
|
|
18
|
+
const newValue = args[0];
|
|
19
|
+
if (isSetting && newValue !== value) {
|
|
18
20
|
value = newValue;
|
|
19
21
|
obs.notifySubscribers();
|
|
20
22
|
}
|
|
21
|
-
// TODO - get typings to say if a value is passed in it's type T otherwise it's T | undefined
|
|
22
23
|
return value;
|
|
23
24
|
});
|
|
24
|
-
obs.notifySubscribers = (
|
|
25
|
-
|
|
26
|
-
value = newValue;
|
|
27
|
-
}
|
|
28
|
-
subscribers.forEach(subscriber => subscriber(value)); // TODO fix typing here too
|
|
25
|
+
obs.notifySubscribers = () => {
|
|
26
|
+
subscribers.forEach(subscriber => subscriber(value));
|
|
29
27
|
};
|
|
30
28
|
obs.subscribe = (subscriber) => {
|
|
31
29
|
subscribers.push(subscriber);
|
package/dist/observable.test.js
CHANGED
|
@@ -29,6 +29,132 @@ describe('observable.ts', () => {
|
|
|
29
29
|
const obs = (0, observable_1.observable)(3);
|
|
30
30
|
expect(obs(4)).toBe(4);
|
|
31
31
|
});
|
|
32
|
+
it('should be able to set an observable to undefined', () => {
|
|
33
|
+
const obs = (0, observable_1.observable)(3);
|
|
34
|
+
obs(undefined);
|
|
35
|
+
expect(obs()).toBe(undefined);
|
|
36
|
+
});
|
|
37
|
+
it('should notify subscribers when value is set to undefined', () => {
|
|
38
|
+
const obs = (0, observable_1.observable)(3);
|
|
39
|
+
let value = 99;
|
|
40
|
+
const sub = obs.subscribe(newValue => {
|
|
41
|
+
value = newValue;
|
|
42
|
+
});
|
|
43
|
+
obs(undefined);
|
|
44
|
+
expect(value).toBe(undefined);
|
|
45
|
+
sub.dispose();
|
|
46
|
+
});
|
|
47
|
+
it('should be able to initialize an observable with undefined', () => {
|
|
48
|
+
const obs = (0, observable_1.observable)(undefined);
|
|
49
|
+
expect(obs()).toBe(undefined);
|
|
50
|
+
});
|
|
51
|
+
it('should distinguish between getting (no args) and setting to undefined (with arg)', () => {
|
|
52
|
+
const obs = (0, observable_1.observable)(5);
|
|
53
|
+
// Getting with no args
|
|
54
|
+
expect(obs()).toBe(5);
|
|
55
|
+
// Setting to undefined with explicit arg
|
|
56
|
+
obs(undefined);
|
|
57
|
+
expect(obs()).toBe(undefined);
|
|
58
|
+
});
|
|
59
|
+
it('should not notify subscribers when value does not change', () => {
|
|
60
|
+
const obs = (0, observable_1.observable)(3);
|
|
61
|
+
let notifyCount = 0;
|
|
62
|
+
obs.subscribe(() => {
|
|
63
|
+
notifyCount++;
|
|
64
|
+
});
|
|
65
|
+
obs(3);
|
|
66
|
+
expect(notifyCount).toBe(0);
|
|
67
|
+
obs(4);
|
|
68
|
+
expect(notifyCount).toBe(1);
|
|
69
|
+
});
|
|
70
|
+
it('should handle setting undefined multiple times', () => {
|
|
71
|
+
const obs = (0, observable_1.observable)('test');
|
|
72
|
+
let notifyCount = 0;
|
|
73
|
+
obs.subscribe(() => {
|
|
74
|
+
notifyCount++;
|
|
75
|
+
});
|
|
76
|
+
obs(undefined);
|
|
77
|
+
expect(notifyCount).toBe(1);
|
|
78
|
+
obs(undefined);
|
|
79
|
+
// Should not notify since value hasn't changed
|
|
80
|
+
expect(notifyCount).toBe(1);
|
|
81
|
+
obs('new');
|
|
82
|
+
expect(notifyCount).toBe(2);
|
|
83
|
+
obs(undefined);
|
|
84
|
+
expect(notifyCount).toBe(3);
|
|
85
|
+
});
|
|
86
|
+
it('should correctly detect args.length for get vs set', () => {
|
|
87
|
+
const obs = (0, observable_1.observable)(10);
|
|
88
|
+
const calls = [];
|
|
89
|
+
// Wrap the observable to track calls
|
|
90
|
+
const originalObs = obs;
|
|
91
|
+
const wrappedObs = ((...args) => {
|
|
92
|
+
calls.push({ argsLength: args.length, value: args[0] });
|
|
93
|
+
return originalObs(...args);
|
|
94
|
+
});
|
|
95
|
+
Object.assign(wrappedObs, obs);
|
|
96
|
+
// Get (no args)
|
|
97
|
+
wrappedObs();
|
|
98
|
+
expect(calls[0]).toEqual({ argsLength: 0, value: undefined });
|
|
99
|
+
// Set to undefined (1 arg)
|
|
100
|
+
wrappedObs(undefined);
|
|
101
|
+
expect(calls[1]).toEqual({ argsLength: 1, value: undefined });
|
|
102
|
+
// Get again (no args)
|
|
103
|
+
wrappedObs();
|
|
104
|
+
expect(calls[2]).toEqual({ argsLength: 0, value: undefined });
|
|
105
|
+
});
|
|
106
|
+
it('should allow creating observable without initial value for types that include undefined', () => {
|
|
107
|
+
const obs = (0, observable_1.observable)();
|
|
108
|
+
expect(obs()).toBe(undefined);
|
|
109
|
+
obs('test');
|
|
110
|
+
expect(obs()).toBe('test');
|
|
111
|
+
obs(undefined);
|
|
112
|
+
expect(obs()).toBe(undefined);
|
|
113
|
+
});
|
|
114
|
+
it('should work with explicit type and initial value', () => {
|
|
115
|
+
const obs = (0, observable_1.observable)(42);
|
|
116
|
+
expect(obs()).toBe(42);
|
|
117
|
+
obs(100);
|
|
118
|
+
expect(obs()).toBe(100);
|
|
119
|
+
});
|
|
120
|
+
it('should not allow creating observable without initial value for types that cannot be undefined', () => {
|
|
121
|
+
const obs = (0, observable_1.observable)();
|
|
122
|
+
// @ts-expect-error
|
|
123
|
+
const s = obs();
|
|
124
|
+
expect(s).toBeUndefined();
|
|
125
|
+
obs('s');
|
|
126
|
+
expect(obs()).toEqual('s');
|
|
127
|
+
obs(undefined);
|
|
128
|
+
expect(obs()).toEqual(undefined);
|
|
129
|
+
const obs2 = (0, observable_1.observable)('');
|
|
130
|
+
const s2 = obs2();
|
|
131
|
+
expect(s2).toEqual('');
|
|
132
|
+
// @ts-expect-error
|
|
133
|
+
obs2(undefined);
|
|
134
|
+
expect(obs2()).toBeUndefined();
|
|
135
|
+
});
|
|
136
|
+
it('should infer type from initial value', () => {
|
|
137
|
+
// Infer number from numeric literal
|
|
138
|
+
const numObs = (0, observable_1.observable)(42);
|
|
139
|
+
const num = numObs();
|
|
140
|
+
expect(num).toBe(42);
|
|
141
|
+
// Infer string from string literal
|
|
142
|
+
const strObs = (0, observable_1.observable)('hello');
|
|
143
|
+
const str = strObs();
|
|
144
|
+
expect(str).toBe('hello');
|
|
145
|
+
// Infer boolean from boolean literal
|
|
146
|
+
const boolObs = (0, observable_1.observable)(true);
|
|
147
|
+
const bool = boolObs();
|
|
148
|
+
expect(bool).toBe(true);
|
|
149
|
+
// Infer object type
|
|
150
|
+
const objObs = (0, observable_1.observable)({ name: 'test', count: 5 });
|
|
151
|
+
const obj = objObs();
|
|
152
|
+
expect(obj).toEqual({ name: 'test', count: 5 });
|
|
153
|
+
// Infer array type
|
|
154
|
+
const arrObs = (0, observable_1.observable)([1, 2, 3]);
|
|
155
|
+
const arr = arrObs();
|
|
156
|
+
expect(arr).toEqual([1, 2, 3]);
|
|
157
|
+
});
|
|
32
158
|
});
|
|
33
159
|
describe('computed', () => {
|
|
34
160
|
it('should be able to create a computed observable', () => {
|
|
@@ -160,10 +286,10 @@ describe('observable.ts', () => {
|
|
|
160
286
|
expect(recomputeCount).toBe(2);
|
|
161
287
|
expect(com1()).toBe(4);
|
|
162
288
|
expect(com1()).toBe(4);
|
|
163
|
-
com1.notifySubscribers(5);
|
|
164
|
-
expect(newValue).toBe(5);
|
|
165
|
-
expect(com1()).toBe(5);
|
|
166
|
-
expect(recomputeCount).toBe(2);
|
|
289
|
+
// com1.notifySubscribers(5);
|
|
290
|
+
// expect(newValue).toBe(5);
|
|
291
|
+
// expect(com1()).toBe(5);
|
|
292
|
+
// expect(recomputeCount).toBe(2);
|
|
167
293
|
});
|
|
168
294
|
it('should allow configuring a write function', () => {
|
|
169
295
|
const obs1 = (0, observable_1.observable)(1);
|
package/dist/rpc-types.d.ts
CHANGED
|
@@ -34,7 +34,6 @@ export declare const rpcServerCalls: {
|
|
|
34
34
|
getFileContents: ((fileId: string, encoding?: BufferEncoding) => Promise<string>);
|
|
35
35
|
resetAllDeviceSyncInfo: (() => Promise<void>);
|
|
36
36
|
importGroupShare: ((groupShareJson: string) => Promise<string>);
|
|
37
|
-
setAppTheme: ((theme: "light" | "dark") => Promise<void>);
|
|
38
37
|
};
|
|
39
38
|
export declare const rpcClientCalls: {
|
|
40
39
|
ping: (msg: string) => Promise<string>;
|
package/dist/rpc-types.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.isClient = exports.rpcClientCalls = exports.rpcServerCalls = void 0;
|
|
4
4
|
function rpcStub(rpcName) {
|
|
5
|
-
return async () => {
|
|
6
|
-
console.warn(rpcName + ': rpc fn not set. This means that an RPC call is happening before the RPC function has been set. This is either a race condition or the RPC handler was never set.');
|
|
5
|
+
return async (...args) => {
|
|
6
|
+
console.warn(rpcName + ': rpc fn not set. This means that an RPC call is happening before the RPC function has been set. This is either a race condition or the RPC handler was never set.' + JSON.stringify({ rpcName, args }, null, 2));
|
|
7
7
|
};
|
|
8
8
|
}
|
|
9
9
|
exports.rpcServerCalls = {
|
|
@@ -31,7 +31,6 @@ exports.rpcServerCalls = {
|
|
|
31
31
|
getFileContents: rpcStub('getFileContents'),
|
|
32
32
|
resetAllDeviceSyncInfo: rpcStub('resetAllDeviceSyncInfo'),
|
|
33
33
|
importGroupShare: rpcStub('importGroupShare'),
|
|
34
|
-
setAppTheme: rpcStub('setAppTheme'),
|
|
35
34
|
// TODO try to get rid of this and rely on the client-side table and server-side table individually emitting events
|
|
36
35
|
// emitEvent: _na as ((event: IEventData) => Promise<boolean>),
|
|
37
36
|
};
|
package/dist/utils.d.ts
CHANGED
|
@@ -15,8 +15,8 @@ export declare function simpleHash(str: string): number;
|
|
|
15
15
|
export type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
|
|
16
16
|
export declare function simpleObjectHash(obj: any): number;
|
|
17
17
|
export declare function sleep(ms?: number): Promise<void>;
|
|
18
|
-
export declare function camelCaseToSpaces(s
|
|
19
|
-
export declare function camelCaseToHyphens(s
|
|
18
|
+
export declare function camelCaseToSpaces(s?: string): string;
|
|
19
|
+
export declare function camelCaseToHyphens(s?: string): string;
|
|
20
20
|
export declare const moneyFormatter: Intl.NumberFormat;
|
|
21
21
|
export declare function formatMoney(value: number, precision?: number): string;
|
|
22
22
|
export declare function memoizePromise<F extends (...args: any[]) => Promise<any>>(fn: F, opts?: {
|
package/dist/utils.js
CHANGED
|
@@ -109,14 +109,14 @@ function simpleObjectHash(obj) {
|
|
|
109
109
|
function sleep(ms = 0) {
|
|
110
110
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
111
111
|
}
|
|
112
|
-
function camelCaseToSpaces(s) {
|
|
112
|
+
function camelCaseToSpaces(s = '') {
|
|
113
113
|
s = s.replace(/([a-z])([A-Z])/g, '$1 $2');
|
|
114
114
|
// s = s.replace(/(GL)([A-Z])/g, '$1 $2'); // anything like GLAccounts, GLBatches, etc.
|
|
115
115
|
s = s.replace("_", " ");
|
|
116
116
|
s = s[0]?.toUpperCase() + s.substr(1);
|
|
117
117
|
return s;
|
|
118
118
|
}
|
|
119
|
-
function camelCaseToHyphens(s) {
|
|
119
|
+
function camelCaseToHyphens(s = '') {
|
|
120
120
|
s = camelCaseToSpaces(s);
|
|
121
121
|
return s.split(' ').join('-').toLowerCase();
|
|
122
122
|
}
|