@objectstack/driver-memory 3.0.10 → 3.1.0

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,22 +193,23 @@ 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
43
- var InMemoryDriver = class {
205
+ var _InMemoryDriver = class _InMemoryDriver {
44
206
  constructor(config) {
45
207
  this.name = "com.objectstack.driver.memory";
46
208
  this.type = "driver";
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,7 +906,131 @@ 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
+ * Detect whether the current runtime is a serverless/edge environment.
936
+ *
937
+ * Checks well-known environment variables set by serverless platforms:
938
+ * - `VERCEL` / `VERCEL_ENV` — Vercel Functions / Edge
939
+ * - `AWS_LAMBDA_FUNCTION_NAME` — AWS Lambda
940
+ * - `NETLIFY` — Netlify Functions
941
+ * - `FUNCTIONS_WORKER_RUNTIME` — Azure Functions
942
+ * - `K_SERVICE` — Google Cloud Run / Cloud Functions
943
+ * - `FUNCTION_TARGET` — Google Cloud Functions (Node.js)
944
+ * - `DENO_DEPLOYMENT_ID` — Deno Deploy
945
+ *
946
+ * Returns `false` when `process` or `process.env` is unavailable
947
+ * (e.g. browser or edge runtimes without a Node.js process object).
948
+ */
949
+ isServerlessEnvironment() {
950
+ if (typeof globalThis.process === "undefined" || !globalThis.process.env) {
951
+ return false;
952
+ }
953
+ const env = globalThis.process.env;
954
+ return !!(env.VERCEL || env.VERCEL_ENV || env.AWS_LAMBDA_FUNCTION_NAME || env.NETLIFY || env.FUNCTIONS_WORKER_RUNTIME || env.K_SERVICE || env.FUNCTION_TARGET || env.DENO_DEPLOYMENT_ID);
955
+ }
956
+ /**
957
+ * Initialize the persistence adapter based on configuration.
958
+ * Defaults to 'auto' when persistence is not specified.
959
+ * Use `persistence: false` to explicitly disable persistence.
960
+ *
961
+ * In serverless environments (Vercel, AWS Lambda, etc.), auto mode disables
962
+ * file-system persistence and emits a warning. Use `persistence: false` or
963
+ * supply a custom adapter for serverless-safe operation.
964
+ */
965
+ async initPersistence() {
966
+ const persistence = this.config.persistence === void 0 ? "auto" : this.config.persistence;
967
+ if (persistence === false) return;
968
+ if (typeof persistence === "string") {
969
+ if (persistence === "auto") {
970
+ if (this.isBrowserEnvironment()) {
971
+ const { LocalStoragePersistenceAdapter: LocalStoragePersistenceAdapter2 } = await Promise.resolve().then(() => (init_local_storage_adapter(), local_storage_adapter_exports));
972
+ this.persistenceAdapter = new LocalStoragePersistenceAdapter2();
973
+ this.logger.debug("Auto-detected browser environment, using localStorage persistence");
974
+ } else if (this.isServerlessEnvironment()) {
975
+ this.logger.warn(_InMemoryDriver.SERVERLESS_PERSISTENCE_WARNING);
976
+ } else {
977
+ const { FileSystemPersistenceAdapter: FileSystemPersistenceAdapter2 } = await Promise.resolve().then(() => (init_file_adapter(), file_adapter_exports));
978
+ this.persistenceAdapter = new FileSystemPersistenceAdapter2();
979
+ this.logger.debug("Auto-detected Node.js environment, using file persistence");
980
+ }
981
+ } else if (persistence === "file") {
982
+ const { FileSystemPersistenceAdapter: FileSystemPersistenceAdapter2 } = await Promise.resolve().then(() => (init_file_adapter(), file_adapter_exports));
983
+ this.persistenceAdapter = new FileSystemPersistenceAdapter2();
984
+ } else if (persistence === "local") {
985
+ const { LocalStoragePersistenceAdapter: LocalStoragePersistenceAdapter2 } = await Promise.resolve().then(() => (init_local_storage_adapter(), local_storage_adapter_exports));
986
+ this.persistenceAdapter = new LocalStoragePersistenceAdapter2();
987
+ } else {
988
+ throw new Error(`Unknown persistence type: "${persistence}". Use 'file', 'local', or 'auto'.`);
989
+ }
990
+ } else if ("adapter" in persistence && persistence.adapter) {
991
+ this.persistenceAdapter = persistence.adapter;
992
+ } else if ("type" in persistence) {
993
+ if (persistence.type === "auto") {
994
+ if (this.isBrowserEnvironment()) {
995
+ const { LocalStoragePersistenceAdapter: LocalStoragePersistenceAdapter2 } = await Promise.resolve().then(() => (init_local_storage_adapter(), local_storage_adapter_exports));
996
+ this.persistenceAdapter = new LocalStoragePersistenceAdapter2({
997
+ key: persistence.key
998
+ });
999
+ this.logger.debug("Auto-detected browser environment, using localStorage persistence");
1000
+ } else if (this.isServerlessEnvironment()) {
1001
+ this.logger.warn(_InMemoryDriver.SERVERLESS_PERSISTENCE_WARNING);
1002
+ } else {
1003
+ const { FileSystemPersistenceAdapter: FileSystemPersistenceAdapter2 } = await Promise.resolve().then(() => (init_file_adapter(), file_adapter_exports));
1004
+ this.persistenceAdapter = new FileSystemPersistenceAdapter2({
1005
+ path: persistence.path,
1006
+ autoSaveInterval: persistence.autoSaveInterval
1007
+ });
1008
+ this.logger.debug("Auto-detected Node.js environment, using file persistence");
1009
+ }
1010
+ } else if (persistence.type === "file") {
1011
+ const { FileSystemPersistenceAdapter: FileSystemPersistenceAdapter2 } = await Promise.resolve().then(() => (init_file_adapter(), file_adapter_exports));
1012
+ this.persistenceAdapter = new FileSystemPersistenceAdapter2({
1013
+ path: persistence.path,
1014
+ autoSaveInterval: persistence.autoSaveInterval
1015
+ });
1016
+ } else if (persistence.type === "local") {
1017
+ const { LocalStoragePersistenceAdapter: LocalStoragePersistenceAdapter2 } = await Promise.resolve().then(() => (init_local_storage_adapter(), local_storage_adapter_exports));
1018
+ this.persistenceAdapter = new LocalStoragePersistenceAdapter2({
1019
+ key: persistence.key
1020
+ });
1021
+ }
1022
+ }
1023
+ if (this.persistenceAdapter) {
1024
+ this.logger.debug("Persistence adapter initialized");
1025
+ }
1026
+ }
705
1027
  };
1028
+ _InMemoryDriver.SERVERLESS_PERSISTENCE_WARNING = "Serverless environment detected \u2014 file-system persistence is disabled in auto mode. Data will NOT be persisted across function invocations. Set persistence: false to silence this warning, or provide a custom adapter (e.g. Upstash Redis, Vercel KV) via persistence: { adapter: yourAdapter }.";
1029
+ var InMemoryDriver = _InMemoryDriver;
1030
+
1031
+ // src/index.ts
1032
+ init_file_adapter();
1033
+ init_local_storage_adapter();
706
1034
 
707
1035
  // src/memory-analytics.ts
708
1036
  var import_core2 = require("@objectstack/core");
@@ -1115,7 +1443,9 @@ var index_default = {
1115
1443
  };
1116
1444
  // Annotate the CommonJS export names for ESM import in node:
1117
1445
  0 && (module.exports = {
1446
+ FileSystemPersistenceAdapter,
1118
1447
  InMemoryDriver,
1448
+ LocalStoragePersistenceAdapter,
1119
1449
  MemoryAnalyticsService
1120
1450
  });
1121
1451
  //# sourceMappingURL=index.js.map