@peers-app/peers-sdk 0.6.15 → 0.6.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.
@@ -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 = (0, observable_1.observable)();
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
- // const userIdPVar = deviceVar<string>('myUserId', { dbValue: varDbValues['myUserId'], userContext: this });
118
- // this.userId(userIdPVar());
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,
@@ -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 IPersistentVarOptions<T> {
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?: Omit<IPersistentVarOptions<T>, 'scope' | 'dataContext'>): PersistentVar<T>;
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?: Omit<IPersistentVarOptions<T>, 'scope' | 'dataContext'>): PersistentVar<T>;
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?: Omit<IPersistentVarOptions<T>, 'scope'>): PersistentVar<T>;
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?: Omit<IPersistentVarOptions<T>, 'scope'>): PersistentVar<T>;
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?: Omit<IPersistentVarOptions<T>, 'scope'>): PersistentVar<T>;
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
- return pvar(name, { ...opts, scope: 'device' });
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
- return pvar(name, { ...opts, scope: 'user' });
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
- return pvar(name, { ...opts, scope: 'group' });
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
- return pvar(name, { ...opts, scope: 'groupDevice' });
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
- return pvar(name, { ...opts, scope: 'groupUser' });
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 pvar(name, opts) {
119
- const currentValue = (0, observable_1.observable)(opts?.dbValue || opts?.defaultValue);
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
- currentValue.loadingPromise = new Promise(async (resolve) => {
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: (0, utils_1.newid)(),
173
+ persistentVarId: '',
159
174
  name,
160
175
  scope,
161
176
  isSecret,
162
- value: { value: opts?.defaultValue },
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 saveVarToDb(value) {
172
- if (!rec) {
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 === opts?.defaultValue) {
184
- await table.delete(rec);
185
- rec.persistentVarId = (0, utils_1.newid)();
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
- await table.save(rec);
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 && !rec) {
196
- rec = await loadRecFromDb();
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.persistentVarId = (0, utils_1.newid)(); // change id so next save creates new pVar
202
- rec.value.value = opts?.defaultValue;
242
+ rec = undefined;
243
+ if (defaultValue !== undefined) {
244
+ persistentVar(defaultValue);
245
+ }
203
246
  }
204
- if (!(0, lodash_1.isEqual)(currentValue(), rec.value.value)) {
205
- currentValue(rec.value.value);
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 (newDataContext) => {
258
+ userContext.defaultDataContext.subscribe(async () => {
214
259
  rec = await loadRecFromDb();
215
- currentValue(rec.value.value);
260
+ persistentVar(rec.value.value);
216
261
  });
217
262
  }
218
- if (!Object.keys(opts ?? {}).includes('dbValue')) {
263
+ if (!setWithDbValue) {
219
264
  rec = await loadRecFromDb();
220
- if (!(0, lodash_1.isEqual)(rec.value.value, currentValue())) {
221
- currentValue(rec.value.value);
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
- currentValue.subscribe(newValue => {
225
- saveVarToDb(newValue);
272
+ persistentVar.subscribe(newValue => {
273
+ persistentVar.loadingPromise = persistentVar.loadingPromise.then(async () => {
274
+ await reactToValueChanged(newValue);
275
+ return persistentVar;
276
+ });
226
277
  });
227
- resolve(currentValue);
278
+ resolve(persistentVar);
228
279
  });
229
- return currentValue;
280
+ return persistentVar;
230
281
  }
@@ -1,14 +1,18 @@
1
- type ObservableFn<T> = ((newValue?: T) => 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: (newValue?: T) => void;
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?: T): Observable<T>;
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;
@@ -12,20 +12,18 @@ const runningComputes = {};
12
12
  function observable(initialValue) {
13
13
  let value = initialValue;
14
14
  const subscribers = [];
15
- const obs = ((newValue) => {
15
+ const obs = ((...args) => {
16
16
  Object.values(runningComputes).forEach(c => c.push(obs));
17
- if (newValue !== undefined && newValue !== value) {
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 = (newValue) => {
25
- if (newValue !== undefined) {
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);
@@ -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);
@@ -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: string): string;
19
- export declare function camelCaseToHyphens(s: string): string;
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peers-app/peers-sdk",
3
- "version": "0.6.15",
3
+ "version": "0.6.17",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/peers-app/peers-sdk.git"