@converse/skeletor 3.1.4 → 3.1.6

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/package.json CHANGED
@@ -10,15 +10,18 @@
10
10
  "exports": {
11
11
  ".": {
12
12
  "node": {
13
+ "types": "./dist/skeletor.node.d.ts",
13
14
  "require": "./dist/skeletor.node.js",
14
15
  "import": "./dist/skeletor.node.mjs"
15
16
  },
16
17
  "default": {
18
+ "types": "./dist/skeletor.d.ts",
17
19
  "import": "./dist/skeletor.esm.js",
18
20
  "require": "./dist/skeletor.js"
19
21
  }
20
22
  },
21
23
  "./node": {
24
+ "types": "./dist/skeletor.node.d.ts",
22
25
  "require": "./dist/skeletor.node.js",
23
26
  "import": "./dist/skeletor.node.mjs"
24
27
  }
@@ -36,7 +39,7 @@
36
39
  "contributors": [
37
40
  "JC Brand"
38
41
  ],
39
- "version": "3.1.4",
42
+ "version": "3.1.6",
40
43
  "engines": {
41
44
  "node": ">=22.0.0"
42
45
  },
@@ -47,7 +50,7 @@
47
50
  },
48
51
  "scripts": {
49
52
  "build": "npm run prebuild && npm run build:declarations && rollup -c",
50
- "build:declarations": "npm run build:types && api-extractor run --local && rm -rf dist/defs",
53
+ "build:declarations": "npm run build:types && api-extractor run --local && api-extractor run --local -c api-extractor.node.json && rm -rf dist/defs",
51
54
  "build:types": "tsc -p tsconfig.declarations.json",
52
55
  "check": "npm run check:types && npm run test && npm run test:node && npm run lint",
53
56
  "check:types": "tsc --noEmit",
@@ -72,6 +75,7 @@
72
75
  "dist/skeletor.node.mjs",
73
76
  "dist/skeletor.d.ts",
74
77
  "dist/skeletor.d.ts.map",
78
+ "dist/skeletor.node.d.ts",
75
79
  "src/**/*.ts"
76
80
  ],
77
81
  "dependencies": {
@@ -0,0 +1,11 @@
1
+ /**
2
+ * `@converse/localforage-getitems` ships its types as an ambient script that
3
+ * declares the *unscoped* module name (`localforage-getitems`), left over from
4
+ * the package it was forked from. TypeScript therefore resolves the scoped
5
+ * specifier to a `typings/index.d.ts` that isn't a module (TS2306), so declare
6
+ * the shape we use here instead.
7
+ */
8
+ declare module '@converse/localforage-getitems' {
9
+ export function extendPrototype<T>(localforage: T): T;
10
+ export const extendPrototypeResult: boolean;
11
+ }
@@ -9,6 +9,7 @@ const DEFAULT_BASE_DIR = '.skeletor-storage';
9
9
  /**
10
10
  * SQLite-backed implementation of StorageDriver.
11
11
  * Uses a single table with key/value columns, storing values as JSON strings.
12
+ * @public
12
13
  */
13
14
  export class NodeSQLiteStorage implements StorageDriver {
14
15
  private db: DatabaseSync;
@@ -27,11 +28,6 @@ export class NodeSQLiteStorage implements StorageDriver {
27
28
  return Promise.resolve();
28
29
  }
29
30
 
30
- debouncedSetItems?: {
31
- (items: Record<string, any>): Promise<void>;
32
- flush?: () => void;
33
- };
34
-
35
31
  constructor(name: string, storageDir?: string, inMemory?: boolean) {
36
32
  if (inMemory) {
37
33
  this.db = new DatabaseSync(':memory:');
@@ -116,7 +112,7 @@ export class NodeSQLiteStorage implements StorageDriver {
116
112
  return Promise.resolve(rows.map((r) => r.key));
117
113
  }
118
114
 
119
- setItems(items: Record<string, any>): Promise<void> {
115
+ setItems(items: Record<string, any>): Promise<Record<string, any>> {
120
116
  this.db.exec('BEGIN IMMEDIATE');
121
117
  try {
122
118
  for (const [key, value] of Object.entries(items)) {
@@ -137,7 +133,7 @@ export class NodeSQLiteStorage implements StorageDriver {
137
133
  }
138
134
  throw e;
139
135
  }
140
- return Promise.resolve();
136
+ return Promise.resolve(items);
141
137
  }
142
138
 
143
139
  getItems(keys: string[]): Promise<Record<string, any>> {
@@ -11,10 +11,16 @@ export interface StorageDriver {
11
11
  length(): Promise<number>;
12
12
  key(keyIndex: number): Promise<string | null>;
13
13
  keys(): Promise<string[]>;
14
- setItems?(items: Record<string, any>): Promise<void>;
14
+ /**
15
+ * Write several items in one go. A successful write is signalled by the
16
+ * promise settling, not by what it resolves with: drivers may resolve with
17
+ * the written items (localForage does) or with nothing, and callers must
18
+ * treat both as success.
19
+ */
20
+ setItems?(items: Record<string, any>): Promise<Record<string, any> | void>;
15
21
  getItems?(keys: string[]): Promise<Record<string, any>>;
16
22
  debouncedSetItems?: {
17
- (items: Record<string, any>): Promise<void>;
23
+ (items: Record<string, any>): Promise<Record<string, any> | void>;
18
24
  flush?: () => void;
19
25
  };
20
26
  }
package/src/index.ts CHANGED
@@ -63,6 +63,7 @@ export { noConflict, Collection, EventEmitter, Model, PersistentStorage, Browser
63
63
  export { getStorage } from './helpers';
64
64
 
65
65
  export type { StorageDriver, StoreType } from './drivers/types';
66
+ export type { NodeStorageConstructor } from './storage';
66
67
  export type {
67
68
  ClassConstructor,
68
69
  CollectionOptions,
package/src/storage.ts CHANGED
@@ -8,7 +8,7 @@ import localForage from 'localforage';
8
8
  import mergebounce from 'mergebounce';
9
9
  import sessionStorageWrapper from './drivers/sessionStorage';
10
10
  import { extendPrototype as extendPrototypeWithSetItems } from 'localforage-setitems';
11
- import { extendPrototype as extendPrototypeWithGetItems } from '@converse/localforage-getitems/dist/localforage-getitems.es6';
11
+ import { extendPrototype as extendPrototypeWithGetItems } from '@converse/localforage-getitems';
12
12
  import { guid } from './helpers';
13
13
  import type { Model } from './model';
14
14
  import type { Collection } from './collection';
@@ -36,8 +36,13 @@ const registered = new WeakSet<PersistentStorage>();
36
36
  /**
37
37
  * Constructor for a Node.js-only storage driver. Registered by the
38
38
  * `@converse/skeletor/node` entry point via {@link PersistentStorage.nodeStorage}.
39
+ * @public
39
40
  */
40
- type NodeStorageConstructor = new (name: string) => StorageDriver;
41
+ export type NodeStorageConstructor = new (
42
+ name: string,
43
+ storageDir?: string,
44
+ inMemory?: boolean,
45
+ ) => StorageDriver;
41
46
 
42
47
  /**
43
48
  * @public
@@ -60,6 +65,19 @@ class PersistentStorage {
60
65
  // bundle; the `@converse/skeletor/node` entry sets it.
61
66
  static nodeStorage: NodeStorageConstructor | null = null;
62
67
 
68
+ /**
69
+ * Directory the `'node'` store type writes its database files to. The driver's
70
+ * own default is relative, so it resolves against `process.cwd()` and a process
71
+ * started from two directories ends up with two separate sets of state. Set an
72
+ * absolute path (`$XDG_STATE_HOME/<app>`, say) to pin it. Left `null`, the
73
+ * driver's default applies.
74
+ *
75
+ * Only the `'node'` string store type reads this; passing a driver instance to
76
+ * the constructor means you've already chosen a directory yourself.
77
+ * @public
78
+ */
79
+ static nodeStorageDir: string | null = null;
80
+
63
81
  /** Register a storage instance with a weak reference. @public */
64
82
  static register(instance: PersistentStorage): void {
65
83
  if (registered.has(instance)) return;
@@ -128,7 +146,7 @@ class PersistentStorage {
128
146
  "Import from '@converse/skeletor/node' instead of '@converse/skeletor'.",
129
147
  );
130
148
  }
131
- this.store = new NodeSQLiteStorage(this.name);
149
+ this.store = new NodeSQLiteStorage(this.name, PersistentStorage.nodeStorageDir ?? undefined);
132
150
  if (batchedWrites) {
133
151
  this.store.debouncedSetItems = mergebounce((items: Record<string, any>) => this.store.setItems!(items), 50, {
134
152
  'promise': true,
@@ -155,9 +173,11 @@ class PersistentStorage {
155
173
  async clear(): Promise<void> {
156
174
  await this.storeInitialized;
157
175
  await this.store.removeItem(this.name).catch((e) => console.error(e));
176
+
158
177
  const re = new RegExp(`^${this.name}-`);
159
178
  const keys = await this.store.keys();
160
179
  const removed_keys = keys.filter((k) => re.test(k));
180
+
161
181
  await Promise.all(removed_keys.map((k) => this.store.removeItem(k).catch((e) => console.error(e))));
162
182
  }
163
183
 
@@ -231,31 +251,16 @@ class PersistentStorage {
231
251
  }
232
252
  }
233
253
 
234
- if (resp) {
235
- if (options && options.success) {
236
- // When storing, we don't pass back the response (which is
237
- // the set attributes returned from localforage because
238
- // Skeletor sets them again on the model and due to the async
239
- // nature of localforage it can cause stale attributes to be
240
- // set on a model after it's been updated in the meantime.
241
- const data = method === 'read' ? resp : null;
242
- options.success(data, options);
243
- }
244
- } else if (method === 'read' && !caughtError) {
245
- // A read that finds no record is a normal empty result (first run /
246
- // empty storage), not an error. Resolve with `null` so the callback
247
- // and promise forms of fetch() agree and a first-run read never
248
- // rejects.
249
- if (options && options.success) {
250
- options.success(null, options);
251
- }
252
- } else {
253
- // A genuine failure (storage threw, or a write produced no result):
254
- // surface a real Error so consumers get a stack and `instanceof Error`
255
- // holds.
256
- if (options && options.error) {
257
- options.error(caughtError ?? new Error('Record Not Found'));
258
- }
254
+ if (caughtError) {
255
+ options?.error?.(caughtError);
256
+ } else if (options?.success) {
257
+ // When storing, we don't pass back the response (which is the set
258
+ // attributes returned from localforage) because Skeletor sets them
259
+ // again on the model and due to the async nature of localforage it can
260
+ // cause stale attributes to be set on a model after it's been updated
261
+ // in the meantime.
262
+ const data = method === 'read' ? (resp ?? null) : null;
263
+ options.success(data, options);
259
264
  }
260
265
  }
261
266
  localSync.__name__ = 'localSync';