@objectstack/driver-memory 3.0.10 → 3.0.11

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/index.js CHANGED
@@ -1,8 +1,13 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
6
11
  var __export = (target, all) => {
7
12
  for (var name in all)
8
13
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -15,12 +20,169 @@ var __copyProps = (to, from, except, desc) => {
15
20
  }
16
21
  return to;
17
22
  };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
24
+ // If the importer is in node compatibility mode or this is not an ESM
25
+ // file that has been converted to a CommonJS file using a Babel-
26
+ // compatible transform (i.e. "__esModule" has not been set), then set
27
+ // "default" to the CommonJS "module.exports" for node compatibility.
28
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
29
+ mod
30
+ ));
18
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
32
 
33
+ // src/persistence/local-storage-adapter.ts
34
+ var local_storage_adapter_exports = {};
35
+ __export(local_storage_adapter_exports, {
36
+ LocalStoragePersistenceAdapter: () => LocalStoragePersistenceAdapter
37
+ });
38
+ var _LocalStoragePersistenceAdapter, LocalStoragePersistenceAdapter;
39
+ var init_local_storage_adapter = __esm({
40
+ "src/persistence/local-storage-adapter.ts"() {
41
+ "use strict";
42
+ _LocalStoragePersistenceAdapter = class _LocalStoragePersistenceAdapter {
43
+ // 4.5MB warning threshold
44
+ constructor(options) {
45
+ this.storageKey = options?.key || "objectstack:memory-db";
46
+ }
47
+ /**
48
+ * Load persisted data from localStorage.
49
+ * Returns null if no data exists.
50
+ */
51
+ async load() {
52
+ try {
53
+ const raw = localStorage.getItem(this.storageKey);
54
+ if (!raw) return null;
55
+ return JSON.parse(raw);
56
+ } catch {
57
+ return null;
58
+ }
59
+ }
60
+ /**
61
+ * Save data to localStorage.
62
+ * Warns if data size approaches the ~5MB localStorage limit.
63
+ */
64
+ async save(db) {
65
+ const json = JSON.stringify(db);
66
+ if (json.length > _LocalStoragePersistenceAdapter.SIZE_WARNING_BYTES) {
67
+ console.warn(
68
+ `[ObjectStack] localStorage persistence data size (${(json.length / 1024 / 1024).toFixed(2)}MB) is approaching the ~5MB limit. Consider using a different persistence strategy.`
69
+ );
70
+ }
71
+ try {
72
+ localStorage.setItem(this.storageKey, json);
73
+ } catch (e) {
74
+ console.error("[ObjectStack] Failed to persist data to localStorage:", e?.message || e);
75
+ }
76
+ }
77
+ /**
78
+ * Flush is a no-op for localStorage (writes are synchronous).
79
+ */
80
+ async flush() {
81
+ }
82
+ };
83
+ _LocalStoragePersistenceAdapter.SIZE_WARNING_BYTES = 4.5 * 1024 * 1024;
84
+ LocalStoragePersistenceAdapter = _LocalStoragePersistenceAdapter;
85
+ }
86
+ });
87
+
88
+ // src/persistence/file-adapter.ts
89
+ var file_adapter_exports = {};
90
+ __export(file_adapter_exports, {
91
+ FileSystemPersistenceAdapter: () => FileSystemPersistenceAdapter
92
+ });
93
+ var fs, path, FileSystemPersistenceAdapter;
94
+ var init_file_adapter = __esm({
95
+ "src/persistence/file-adapter.ts"() {
96
+ "use strict";
97
+ fs = __toESM(require("fs"));
98
+ path = __toESM(require("path"));
99
+ FileSystemPersistenceAdapter = class {
100
+ constructor(options) {
101
+ this.dirty = false;
102
+ this.timer = null;
103
+ this.currentDb = null;
104
+ this.filePath = options?.path || path.join(".objectstack", "data", "memory-driver.json");
105
+ this.autoSaveInterval = options?.autoSaveInterval ?? 2e3;
106
+ }
107
+ /**
108
+ * Load persisted data from disk.
109
+ * Returns null if no file exists.
110
+ */
111
+ async load() {
112
+ try {
113
+ if (!fs.existsSync(this.filePath)) {
114
+ return null;
115
+ }
116
+ const raw = fs.readFileSync(this.filePath, "utf-8");
117
+ const data = JSON.parse(raw);
118
+ return data;
119
+ } catch {
120
+ return null;
121
+ }
122
+ }
123
+ /**
124
+ * Save data to disk using atomic write (temp file + rename).
125
+ */
126
+ async save(db) {
127
+ this.currentDb = db;
128
+ this.dirty = true;
129
+ }
130
+ /**
131
+ * Flush pending writes to disk immediately.
132
+ */
133
+ async flush() {
134
+ if (!this.dirty || !this.currentDb) return;
135
+ await this.writeToDisk(this.currentDb);
136
+ this.dirty = false;
137
+ }
138
+ /**
139
+ * Start the auto-save timer.
140
+ */
141
+ startAutoSave() {
142
+ if (this.timer) return;
143
+ this.timer = setInterval(async () => {
144
+ if (this.dirty && this.currentDb) {
145
+ await this.writeToDisk(this.currentDb);
146
+ this.dirty = false;
147
+ }
148
+ }, this.autoSaveInterval);
149
+ if (this.timer) {
150
+ this.timer.unref();
151
+ }
152
+ }
153
+ /**
154
+ * Stop the auto-save timer and flush pending writes.
155
+ */
156
+ async stopAutoSave() {
157
+ if (this.timer) {
158
+ clearInterval(this.timer);
159
+ this.timer = null;
160
+ }
161
+ await this.flush();
162
+ }
163
+ /**
164
+ * Atomic write: write to temp file, then rename.
165
+ */
166
+ async writeToDisk(db) {
167
+ const dir = path.dirname(this.filePath);
168
+ if (!fs.existsSync(dir)) {
169
+ fs.mkdirSync(dir, { recursive: true });
170
+ }
171
+ const tmpPath = this.filePath + ".tmp";
172
+ const json = JSON.stringify(db, null, 2);
173
+ fs.writeFileSync(tmpPath, json, "utf-8");
174
+ fs.renameSync(tmpPath, this.filePath);
175
+ }
176
+ };
177
+ }
178
+ });
179
+
20
180
  // src/index.ts
21
181
  var index_exports = {};
22
182
  __export(index_exports, {
183
+ FileSystemPersistenceAdapter: () => FileSystemPersistenceAdapter,
23
184
  InMemoryDriver: () => InMemoryDriver,
185
+ LocalStoragePersistenceAdapter: () => LocalStoragePersistenceAdapter,
24
186
  MemoryAnalyticsService: () => MemoryAnalyticsService,
25
187
  default: () => index_default
26
188
  });
@@ -31,12 +193,12 @@ var import_core = require("@objectstack/core");
31
193
  var import_mingo = require("mingo");
32
194
 
33
195
  // src/memory-matcher.ts
34
- function getValueByPath(obj, path) {
35
- if (path === "_id" && obj._id === void 0 && obj.id !== void 0) {
196
+ function getValueByPath(obj, path2) {
197
+ if (path2 === "_id" && obj._id === void 0 && obj.id !== void 0) {
36
198
  return obj.id;
37
199
  }
38
- if (!path.includes(".")) return obj[path];
39
- return path.split(".").reduce((o, i) => o ? o[i] : void 0, obj);
200
+ if (!path2.includes(".")) return obj[path2];
201
+ return path2.split(".").reduce((o, i) => o ? o[i] : void 0, obj);
40
202
  }
41
203
 
42
204
  // src/memory-driver.ts
@@ -47,6 +209,7 @@ var InMemoryDriver = class {
47
209
  this.version = "1.0.0";
48
210
  this.idCounters = /* @__PURE__ */ new Map();
49
211
  this.transactions = /* @__PURE__ */ new Map();
212
+ this.persistenceAdapter = null;
50
213
  this.supports = {
51
214
  // Transaction & Connection Management
52
215
  transactions: true,
@@ -100,6 +263,31 @@ var InMemoryDriver = class {
100
263
  // Lifecycle
101
264
  // ===================================
102
265
  async connect() {
266
+ await this.initPersistence();
267
+ if (this.persistenceAdapter) {
268
+ const persisted = await this.persistenceAdapter.load();
269
+ if (persisted) {
270
+ for (const [objectName, records] of Object.entries(persisted)) {
271
+ this.db[objectName] = records;
272
+ for (const record of records) {
273
+ if (record.id && typeof record.id === "string") {
274
+ const parts = record.id.split("-");
275
+ const lastPart = parts[parts.length - 1];
276
+ const counter = parseInt(lastPart, 10);
277
+ if (!isNaN(counter)) {
278
+ const current = this.idCounters.get(objectName) || 0;
279
+ if (counter > current) {
280
+ this.idCounters.set(objectName, counter);
281
+ }
282
+ }
283
+ }
284
+ }
285
+ }
286
+ this.logger.info("InMemory Database restored from persistence", {
287
+ tables: Object.keys(persisted).length
288
+ });
289
+ }
290
+ }
103
291
  if (this.config.initialData) {
104
292
  for (const [objectName, records] of Object.entries(this.config.initialData)) {
105
293
  const table = this.getTable(objectName);
@@ -114,8 +302,17 @@ var InMemoryDriver = class {
114
302
  } else {
115
303
  this.logger.info("InMemory Database Connected (Virtual)");
116
304
  }
305
+ if (this.persistenceAdapter?.startAutoSave) {
306
+ this.persistenceAdapter.startAutoSave();
307
+ }
117
308
  }
118
309
  async disconnect() {
310
+ if (this.persistenceAdapter) {
311
+ if (this.persistenceAdapter.stopAutoSave) {
312
+ await this.persistenceAdapter.stopAutoSave();
313
+ }
314
+ await this.persistenceAdapter.flush();
315
+ }
119
316
  const tableCount = Object.keys(this.db).length;
120
317
  const recordCount = Object.values(this.db).reduce((sum, table) => sum + table.length, 0);
121
318
  this.db = {};
@@ -195,6 +392,7 @@ var InMemoryDriver = class {
195
392
  updated_at: data.updated_at || (/* @__PURE__ */ new Date()).toISOString()
196
393
  };
197
394
  table.push(newRecord);
395
+ this.markDirty();
198
396
  this.logger.debug("Record created", { object, id: newRecord.id, tableSize: table.length });
199
397
  return { ...newRecord };
200
398
  }
@@ -219,6 +417,7 @@ var InMemoryDriver = class {
219
417
  updated_at: (/* @__PURE__ */ new Date()).toISOString()
220
418
  };
221
419
  table[index] = updatedRecord;
420
+ this.markDirty();
222
421
  this.logger.debug("Record updated", { object, id });
223
422
  return { ...updatedRecord };
224
423
  }
@@ -251,6 +450,7 @@ var InMemoryDriver = class {
251
450
  return false;
252
451
  }
253
452
  table.splice(index, 1);
453
+ this.markDirty();
254
454
  this.logger.debug("Record deleted", { object, id, tableSize: table.length });
255
455
  return true;
256
456
  }
@@ -299,6 +499,7 @@ var InMemoryDriver = class {
299
499
  table[index] = updated;
300
500
  }
301
501
  }
502
+ if (count > 0) this.markDirty();
302
503
  this.logger.debug("UpdateMany completed", { object, count });
303
504
  return { count };
304
505
  }
@@ -320,6 +521,7 @@ var InMemoryDriver = class {
320
521
  this.db[object] = [];
321
522
  }
322
523
  const count = initialLength - this.db[object].length;
524
+ if (count > 0) this.markDirty();
323
525
  this.logger.debug("DeleteMany completed", { object, count });
324
526
  return { count };
325
527
  }
@@ -367,6 +569,7 @@ var InMemoryDriver = class {
367
569
  const tx = this.transactions.get(txId);
368
570
  this.db = tx.snapshot;
369
571
  this.transactions.delete(txId);
572
+ this.markDirty();
370
573
  this.logger.debug("Transaction rolled back", { txId });
371
574
  }
372
575
  // ===================================
@@ -378,6 +581,7 @@ var InMemoryDriver = class {
378
581
  async clear() {
379
582
  this.db = {};
380
583
  this.idCounters.clear();
584
+ this.markDirty();
381
585
  this.logger.debug("All data cleared");
382
586
  }
383
587
  /**
@@ -614,8 +818,8 @@ var InMemoryDriver = class {
614
818
  return null;
615
819
  }
616
820
  }
617
- setValueByPath(obj, path, value) {
618
- const parts = path.split(".");
821
+ setValueByPath(obj, path2, value) {
822
+ const parts = path2.split(".");
619
823
  let current = obj;
620
824
  for (let i = 0; i < parts.length - 1; i++) {
621
825
  const part = parts[i];
@@ -702,8 +906,100 @@ var InMemoryDriver = class {
702
906
  const timestamp = Date.now();
703
907
  return `${key}-${timestamp}-${counter}`;
704
908
  }
909
+ // ===================================
910
+ // Persistence
911
+ // ===================================
912
+ /**
913
+ * Mark the database as dirty, triggering persistence save.
914
+ */
915
+ markDirty() {
916
+ if (this.persistenceAdapter) {
917
+ this.persistenceAdapter.save(this.db);
918
+ }
919
+ }
920
+ /**
921
+ * Flush pending persistence writes to ensure data is safely stored.
922
+ */
923
+ async flush() {
924
+ if (this.persistenceAdapter) {
925
+ await this.persistenceAdapter.flush();
926
+ }
927
+ }
928
+ /**
929
+ * Detect whether the current runtime is a browser environment.
930
+ */
931
+ isBrowserEnvironment() {
932
+ return typeof globalThis.localStorage !== "undefined";
933
+ }
934
+ /**
935
+ * Initialize the persistence adapter based on configuration.
936
+ * Defaults to 'auto' when persistence is not specified.
937
+ * Use `persistence: false` to explicitly disable persistence.
938
+ */
939
+ async initPersistence() {
940
+ const persistence = this.config.persistence === void 0 ? "auto" : this.config.persistence;
941
+ if (persistence === false) return;
942
+ if (typeof persistence === "string") {
943
+ if (persistence === "auto") {
944
+ if (this.isBrowserEnvironment()) {
945
+ const { LocalStoragePersistenceAdapter: LocalStoragePersistenceAdapter2 } = await Promise.resolve().then(() => (init_local_storage_adapter(), local_storage_adapter_exports));
946
+ this.persistenceAdapter = new LocalStoragePersistenceAdapter2();
947
+ this.logger.debug("Auto-detected browser environment, using localStorage persistence");
948
+ } else {
949
+ const { FileSystemPersistenceAdapter: FileSystemPersistenceAdapter2 } = await Promise.resolve().then(() => (init_file_adapter(), file_adapter_exports));
950
+ this.persistenceAdapter = new FileSystemPersistenceAdapter2();
951
+ this.logger.debug("Auto-detected Node.js environment, using file persistence");
952
+ }
953
+ } else if (persistence === "file") {
954
+ const { FileSystemPersistenceAdapter: FileSystemPersistenceAdapter2 } = await Promise.resolve().then(() => (init_file_adapter(), file_adapter_exports));
955
+ this.persistenceAdapter = new FileSystemPersistenceAdapter2();
956
+ } else if (persistence === "local") {
957
+ const { LocalStoragePersistenceAdapter: LocalStoragePersistenceAdapter2 } = await Promise.resolve().then(() => (init_local_storage_adapter(), local_storage_adapter_exports));
958
+ this.persistenceAdapter = new LocalStoragePersistenceAdapter2();
959
+ } else {
960
+ throw new Error(`Unknown persistence type: "${persistence}". Use 'file', 'local', or 'auto'.`);
961
+ }
962
+ } else if ("adapter" in persistence && persistence.adapter) {
963
+ this.persistenceAdapter = persistence.adapter;
964
+ } else if ("type" in persistence) {
965
+ if (persistence.type === "auto") {
966
+ if (this.isBrowserEnvironment()) {
967
+ const { LocalStoragePersistenceAdapter: LocalStoragePersistenceAdapter2 } = await Promise.resolve().then(() => (init_local_storage_adapter(), local_storage_adapter_exports));
968
+ this.persistenceAdapter = new LocalStoragePersistenceAdapter2({
969
+ key: persistence.key
970
+ });
971
+ this.logger.debug("Auto-detected browser environment, using localStorage persistence");
972
+ } else {
973
+ const { FileSystemPersistenceAdapter: FileSystemPersistenceAdapter2 } = await Promise.resolve().then(() => (init_file_adapter(), file_adapter_exports));
974
+ this.persistenceAdapter = new FileSystemPersistenceAdapter2({
975
+ path: persistence.path,
976
+ autoSaveInterval: persistence.autoSaveInterval
977
+ });
978
+ this.logger.debug("Auto-detected Node.js environment, using file persistence");
979
+ }
980
+ } else if (persistence.type === "file") {
981
+ const { FileSystemPersistenceAdapter: FileSystemPersistenceAdapter2 } = await Promise.resolve().then(() => (init_file_adapter(), file_adapter_exports));
982
+ this.persistenceAdapter = new FileSystemPersistenceAdapter2({
983
+ path: persistence.path,
984
+ autoSaveInterval: persistence.autoSaveInterval
985
+ });
986
+ } else if (persistence.type === "local") {
987
+ const { LocalStoragePersistenceAdapter: LocalStoragePersistenceAdapter2 } = await Promise.resolve().then(() => (init_local_storage_adapter(), local_storage_adapter_exports));
988
+ this.persistenceAdapter = new LocalStoragePersistenceAdapter2({
989
+ key: persistence.key
990
+ });
991
+ }
992
+ }
993
+ if (this.persistenceAdapter) {
994
+ this.logger.debug("Persistence adapter initialized");
995
+ }
996
+ }
705
997
  };
706
998
 
999
+ // src/index.ts
1000
+ init_file_adapter();
1001
+ init_local_storage_adapter();
1002
+
707
1003
  // src/memory-analytics.ts
708
1004
  var import_core2 = require("@objectstack/core");
709
1005
  var MemoryAnalyticsService = class {
@@ -1115,7 +1411,9 @@ var index_default = {
1115
1411
  };
1116
1412
  // Annotate the CommonJS export names for ESM import in node:
1117
1413
  0 && (module.exports = {
1414
+ FileSystemPersistenceAdapter,
1118
1415
  InMemoryDriver,
1416
+ LocalStoragePersistenceAdapter,
1119
1417
  MemoryAnalyticsService
1120
1418
  });
1121
1419
  //# sourceMappingURL=index.js.map