@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.mjs CHANGED
@@ -1,14 +1,171 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ };
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+
11
+ // src/persistence/local-storage-adapter.ts
12
+ var local_storage_adapter_exports = {};
13
+ __export(local_storage_adapter_exports, {
14
+ LocalStoragePersistenceAdapter: () => LocalStoragePersistenceAdapter
15
+ });
16
+ var _LocalStoragePersistenceAdapter, LocalStoragePersistenceAdapter;
17
+ var init_local_storage_adapter = __esm({
18
+ "src/persistence/local-storage-adapter.ts"() {
19
+ "use strict";
20
+ _LocalStoragePersistenceAdapter = class _LocalStoragePersistenceAdapter {
21
+ // 4.5MB warning threshold
22
+ constructor(options) {
23
+ this.storageKey = options?.key || "objectstack:memory-db";
24
+ }
25
+ /**
26
+ * Load persisted data from localStorage.
27
+ * Returns null if no data exists.
28
+ */
29
+ async load() {
30
+ try {
31
+ const raw = localStorage.getItem(this.storageKey);
32
+ if (!raw) return null;
33
+ return JSON.parse(raw);
34
+ } catch {
35
+ return null;
36
+ }
37
+ }
38
+ /**
39
+ * Save data to localStorage.
40
+ * Warns if data size approaches the ~5MB localStorage limit.
41
+ */
42
+ async save(db) {
43
+ const json = JSON.stringify(db);
44
+ if (json.length > _LocalStoragePersistenceAdapter.SIZE_WARNING_BYTES) {
45
+ console.warn(
46
+ `[ObjectStack] localStorage persistence data size (${(json.length / 1024 / 1024).toFixed(2)}MB) is approaching the ~5MB limit. Consider using a different persistence strategy.`
47
+ );
48
+ }
49
+ try {
50
+ localStorage.setItem(this.storageKey, json);
51
+ } catch (e) {
52
+ console.error("[ObjectStack] Failed to persist data to localStorage:", e?.message || e);
53
+ }
54
+ }
55
+ /**
56
+ * Flush is a no-op for localStorage (writes are synchronous).
57
+ */
58
+ async flush() {
59
+ }
60
+ };
61
+ _LocalStoragePersistenceAdapter.SIZE_WARNING_BYTES = 4.5 * 1024 * 1024;
62
+ LocalStoragePersistenceAdapter = _LocalStoragePersistenceAdapter;
63
+ }
64
+ });
65
+
66
+ // src/persistence/file-adapter.ts
67
+ var file_adapter_exports = {};
68
+ __export(file_adapter_exports, {
69
+ FileSystemPersistenceAdapter: () => FileSystemPersistenceAdapter
70
+ });
71
+ import * as fs from "fs";
72
+ import * as path from "path";
73
+ var FileSystemPersistenceAdapter;
74
+ var init_file_adapter = __esm({
75
+ "src/persistence/file-adapter.ts"() {
76
+ "use strict";
77
+ FileSystemPersistenceAdapter = class {
78
+ constructor(options) {
79
+ this.dirty = false;
80
+ this.timer = null;
81
+ this.currentDb = null;
82
+ this.filePath = options?.path || path.join(".objectstack", "data", "memory-driver.json");
83
+ this.autoSaveInterval = options?.autoSaveInterval ?? 2e3;
84
+ }
85
+ /**
86
+ * Load persisted data from disk.
87
+ * Returns null if no file exists.
88
+ */
89
+ async load() {
90
+ try {
91
+ if (!fs.existsSync(this.filePath)) {
92
+ return null;
93
+ }
94
+ const raw = fs.readFileSync(this.filePath, "utf-8");
95
+ const data = JSON.parse(raw);
96
+ return data;
97
+ } catch {
98
+ return null;
99
+ }
100
+ }
101
+ /**
102
+ * Save data to disk using atomic write (temp file + rename).
103
+ */
104
+ async save(db) {
105
+ this.currentDb = db;
106
+ this.dirty = true;
107
+ }
108
+ /**
109
+ * Flush pending writes to disk immediately.
110
+ */
111
+ async flush() {
112
+ if (!this.dirty || !this.currentDb) return;
113
+ await this.writeToDisk(this.currentDb);
114
+ this.dirty = false;
115
+ }
116
+ /**
117
+ * Start the auto-save timer.
118
+ */
119
+ startAutoSave() {
120
+ if (this.timer) return;
121
+ this.timer = setInterval(async () => {
122
+ if (this.dirty && this.currentDb) {
123
+ await this.writeToDisk(this.currentDb);
124
+ this.dirty = false;
125
+ }
126
+ }, this.autoSaveInterval);
127
+ if (this.timer) {
128
+ this.timer.unref();
129
+ }
130
+ }
131
+ /**
132
+ * Stop the auto-save timer and flush pending writes.
133
+ */
134
+ async stopAutoSave() {
135
+ if (this.timer) {
136
+ clearInterval(this.timer);
137
+ this.timer = null;
138
+ }
139
+ await this.flush();
140
+ }
141
+ /**
142
+ * Atomic write: write to temp file, then rename.
143
+ */
144
+ async writeToDisk(db) {
145
+ const dir = path.dirname(this.filePath);
146
+ if (!fs.existsSync(dir)) {
147
+ fs.mkdirSync(dir, { recursive: true });
148
+ }
149
+ const tmpPath = this.filePath + ".tmp";
150
+ const json = JSON.stringify(db, null, 2);
151
+ fs.writeFileSync(tmpPath, json, "utf-8");
152
+ fs.renameSync(tmpPath, this.filePath);
153
+ }
154
+ };
155
+ }
156
+ });
157
+
1
158
  // src/memory-driver.ts
2
159
  import { createLogger } from "@objectstack/core";
3
160
  import { Query, Aggregator } from "mingo";
4
161
 
5
162
  // src/memory-matcher.ts
6
- function getValueByPath(obj, path) {
7
- if (path === "_id" && obj._id === void 0 && obj.id !== void 0) {
163
+ function getValueByPath(obj, path2) {
164
+ if (path2 === "_id" && obj._id === void 0 && obj.id !== void 0) {
8
165
  return obj.id;
9
166
  }
10
- if (!path.includes(".")) return obj[path];
11
- return path.split(".").reduce((o, i) => o ? o[i] : void 0, obj);
167
+ if (!path2.includes(".")) return obj[path2];
168
+ return path2.split(".").reduce((o, i) => o ? o[i] : void 0, obj);
12
169
  }
13
170
 
14
171
  // src/memory-driver.ts
@@ -19,6 +176,7 @@ var InMemoryDriver = class {
19
176
  this.version = "1.0.0";
20
177
  this.idCounters = /* @__PURE__ */ new Map();
21
178
  this.transactions = /* @__PURE__ */ new Map();
179
+ this.persistenceAdapter = null;
22
180
  this.supports = {
23
181
  // Transaction & Connection Management
24
182
  transactions: true,
@@ -72,6 +230,31 @@ var InMemoryDriver = class {
72
230
  // Lifecycle
73
231
  // ===================================
74
232
  async connect() {
233
+ await this.initPersistence();
234
+ if (this.persistenceAdapter) {
235
+ const persisted = await this.persistenceAdapter.load();
236
+ if (persisted) {
237
+ for (const [objectName, records] of Object.entries(persisted)) {
238
+ this.db[objectName] = records;
239
+ for (const record of records) {
240
+ if (record.id && typeof record.id === "string") {
241
+ const parts = record.id.split("-");
242
+ const lastPart = parts[parts.length - 1];
243
+ const counter = parseInt(lastPart, 10);
244
+ if (!isNaN(counter)) {
245
+ const current = this.idCounters.get(objectName) || 0;
246
+ if (counter > current) {
247
+ this.idCounters.set(objectName, counter);
248
+ }
249
+ }
250
+ }
251
+ }
252
+ }
253
+ this.logger.info("InMemory Database restored from persistence", {
254
+ tables: Object.keys(persisted).length
255
+ });
256
+ }
257
+ }
75
258
  if (this.config.initialData) {
76
259
  for (const [objectName, records] of Object.entries(this.config.initialData)) {
77
260
  const table = this.getTable(objectName);
@@ -86,8 +269,17 @@ var InMemoryDriver = class {
86
269
  } else {
87
270
  this.logger.info("InMemory Database Connected (Virtual)");
88
271
  }
272
+ if (this.persistenceAdapter?.startAutoSave) {
273
+ this.persistenceAdapter.startAutoSave();
274
+ }
89
275
  }
90
276
  async disconnect() {
277
+ if (this.persistenceAdapter) {
278
+ if (this.persistenceAdapter.stopAutoSave) {
279
+ await this.persistenceAdapter.stopAutoSave();
280
+ }
281
+ await this.persistenceAdapter.flush();
282
+ }
91
283
  const tableCount = Object.keys(this.db).length;
92
284
  const recordCount = Object.values(this.db).reduce((sum, table) => sum + table.length, 0);
93
285
  this.db = {};
@@ -167,6 +359,7 @@ var InMemoryDriver = class {
167
359
  updated_at: data.updated_at || (/* @__PURE__ */ new Date()).toISOString()
168
360
  };
169
361
  table.push(newRecord);
362
+ this.markDirty();
170
363
  this.logger.debug("Record created", { object, id: newRecord.id, tableSize: table.length });
171
364
  return { ...newRecord };
172
365
  }
@@ -191,6 +384,7 @@ var InMemoryDriver = class {
191
384
  updated_at: (/* @__PURE__ */ new Date()).toISOString()
192
385
  };
193
386
  table[index] = updatedRecord;
387
+ this.markDirty();
194
388
  this.logger.debug("Record updated", { object, id });
195
389
  return { ...updatedRecord };
196
390
  }
@@ -223,6 +417,7 @@ var InMemoryDriver = class {
223
417
  return false;
224
418
  }
225
419
  table.splice(index, 1);
420
+ this.markDirty();
226
421
  this.logger.debug("Record deleted", { object, id, tableSize: table.length });
227
422
  return true;
228
423
  }
@@ -271,6 +466,7 @@ var InMemoryDriver = class {
271
466
  table[index] = updated;
272
467
  }
273
468
  }
469
+ if (count > 0) this.markDirty();
274
470
  this.logger.debug("UpdateMany completed", { object, count });
275
471
  return { count };
276
472
  }
@@ -292,6 +488,7 @@ var InMemoryDriver = class {
292
488
  this.db[object] = [];
293
489
  }
294
490
  const count = initialLength - this.db[object].length;
491
+ if (count > 0) this.markDirty();
295
492
  this.logger.debug("DeleteMany completed", { object, count });
296
493
  return { count };
297
494
  }
@@ -339,6 +536,7 @@ var InMemoryDriver = class {
339
536
  const tx = this.transactions.get(txId);
340
537
  this.db = tx.snapshot;
341
538
  this.transactions.delete(txId);
539
+ this.markDirty();
342
540
  this.logger.debug("Transaction rolled back", { txId });
343
541
  }
344
542
  // ===================================
@@ -350,6 +548,7 @@ var InMemoryDriver = class {
350
548
  async clear() {
351
549
  this.db = {};
352
550
  this.idCounters.clear();
551
+ this.markDirty();
353
552
  this.logger.debug("All data cleared");
354
553
  }
355
554
  /**
@@ -586,8 +785,8 @@ var InMemoryDriver = class {
586
785
  return null;
587
786
  }
588
787
  }
589
- setValueByPath(obj, path, value) {
590
- const parts = path.split(".");
788
+ setValueByPath(obj, path2, value) {
789
+ const parts = path2.split(".");
591
790
  let current = obj;
592
791
  for (let i = 0; i < parts.length - 1; i++) {
593
792
  const part = parts[i];
@@ -674,8 +873,100 @@ var InMemoryDriver = class {
674
873
  const timestamp = Date.now();
675
874
  return `${key}-${timestamp}-${counter}`;
676
875
  }
876
+ // ===================================
877
+ // Persistence
878
+ // ===================================
879
+ /**
880
+ * Mark the database as dirty, triggering persistence save.
881
+ */
882
+ markDirty() {
883
+ if (this.persistenceAdapter) {
884
+ this.persistenceAdapter.save(this.db);
885
+ }
886
+ }
887
+ /**
888
+ * Flush pending persistence writes to ensure data is safely stored.
889
+ */
890
+ async flush() {
891
+ if (this.persistenceAdapter) {
892
+ await this.persistenceAdapter.flush();
893
+ }
894
+ }
895
+ /**
896
+ * Detect whether the current runtime is a browser environment.
897
+ */
898
+ isBrowserEnvironment() {
899
+ return typeof globalThis.localStorage !== "undefined";
900
+ }
901
+ /**
902
+ * Initialize the persistence adapter based on configuration.
903
+ * Defaults to 'auto' when persistence is not specified.
904
+ * Use `persistence: false` to explicitly disable persistence.
905
+ */
906
+ async initPersistence() {
907
+ const persistence = this.config.persistence === void 0 ? "auto" : this.config.persistence;
908
+ if (persistence === false) return;
909
+ if (typeof persistence === "string") {
910
+ if (persistence === "auto") {
911
+ if (this.isBrowserEnvironment()) {
912
+ const { LocalStoragePersistenceAdapter: LocalStoragePersistenceAdapter2 } = await Promise.resolve().then(() => (init_local_storage_adapter(), local_storage_adapter_exports));
913
+ this.persistenceAdapter = new LocalStoragePersistenceAdapter2();
914
+ this.logger.debug("Auto-detected browser environment, using localStorage persistence");
915
+ } else {
916
+ const { FileSystemPersistenceAdapter: FileSystemPersistenceAdapter2 } = await Promise.resolve().then(() => (init_file_adapter(), file_adapter_exports));
917
+ this.persistenceAdapter = new FileSystemPersistenceAdapter2();
918
+ this.logger.debug("Auto-detected Node.js environment, using file persistence");
919
+ }
920
+ } else if (persistence === "file") {
921
+ const { FileSystemPersistenceAdapter: FileSystemPersistenceAdapter2 } = await Promise.resolve().then(() => (init_file_adapter(), file_adapter_exports));
922
+ this.persistenceAdapter = new FileSystemPersistenceAdapter2();
923
+ } else if (persistence === "local") {
924
+ const { LocalStoragePersistenceAdapter: LocalStoragePersistenceAdapter2 } = await Promise.resolve().then(() => (init_local_storage_adapter(), local_storage_adapter_exports));
925
+ this.persistenceAdapter = new LocalStoragePersistenceAdapter2();
926
+ } else {
927
+ throw new Error(`Unknown persistence type: "${persistence}". Use 'file', 'local', or 'auto'.`);
928
+ }
929
+ } else if ("adapter" in persistence && persistence.adapter) {
930
+ this.persistenceAdapter = persistence.adapter;
931
+ } else if ("type" in persistence) {
932
+ if (persistence.type === "auto") {
933
+ if (this.isBrowserEnvironment()) {
934
+ const { LocalStoragePersistenceAdapter: LocalStoragePersistenceAdapter2 } = await Promise.resolve().then(() => (init_local_storage_adapter(), local_storage_adapter_exports));
935
+ this.persistenceAdapter = new LocalStoragePersistenceAdapter2({
936
+ key: persistence.key
937
+ });
938
+ this.logger.debug("Auto-detected browser environment, using localStorage persistence");
939
+ } else {
940
+ const { FileSystemPersistenceAdapter: FileSystemPersistenceAdapter2 } = await Promise.resolve().then(() => (init_file_adapter(), file_adapter_exports));
941
+ this.persistenceAdapter = new FileSystemPersistenceAdapter2({
942
+ path: persistence.path,
943
+ autoSaveInterval: persistence.autoSaveInterval
944
+ });
945
+ this.logger.debug("Auto-detected Node.js environment, using file persistence");
946
+ }
947
+ } else if (persistence.type === "file") {
948
+ const { FileSystemPersistenceAdapter: FileSystemPersistenceAdapter2 } = await Promise.resolve().then(() => (init_file_adapter(), file_adapter_exports));
949
+ this.persistenceAdapter = new FileSystemPersistenceAdapter2({
950
+ path: persistence.path,
951
+ autoSaveInterval: persistence.autoSaveInterval
952
+ });
953
+ } else if (persistence.type === "local") {
954
+ const { LocalStoragePersistenceAdapter: LocalStoragePersistenceAdapter2 } = await Promise.resolve().then(() => (init_local_storage_adapter(), local_storage_adapter_exports));
955
+ this.persistenceAdapter = new LocalStoragePersistenceAdapter2({
956
+ key: persistence.key
957
+ });
958
+ }
959
+ }
960
+ if (this.persistenceAdapter) {
961
+ this.logger.debug("Persistence adapter initialized");
962
+ }
963
+ }
677
964
  };
678
965
 
966
+ // src/index.ts
967
+ init_file_adapter();
968
+ init_local_storage_adapter();
969
+
679
970
  // src/memory-analytics.ts
680
971
  import { createLogger as createLogger2 } from "@objectstack/core";
681
972
  var MemoryAnalyticsService = class {
@@ -1086,7 +1377,9 @@ var index_default = {
1086
1377
  }
1087
1378
  };
1088
1379
  export {
1380
+ FileSystemPersistenceAdapter,
1089
1381
  InMemoryDriver,
1382
+ LocalStoragePersistenceAdapter,
1090
1383
  MemoryAnalyticsService,
1091
1384
  index_default as default
1092
1385
  };