masterrecord 0.3.5 → 0.3.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.
@@ -0,0 +1,148 @@
1
+ const assert = require('assert');
2
+ const QueryCache = require('../Cache/QueryCache');
3
+
4
+ describe('QueryCache', () => {
5
+ let cache;
6
+
7
+ beforeEach(() => {
8
+ cache = new QueryCache({ ttl: 1000, maxSize: 3 });
9
+ });
10
+
11
+ test('cache miss returns null', () => {
12
+ const key = cache.generateKey('SELECT * FROM users', [], 'users');
13
+ assert.strictEqual(cache.get(key), null);
14
+ });
15
+
16
+ test('cache hit returns stored data', () => {
17
+ const key = cache.generateKey('SELECT * FROM users', [], 'users');
18
+ const data = [{ id: 1, name: 'John' }];
19
+
20
+ cache.set(key, data, 'users');
21
+ const cached = cache.get(key);
22
+
23
+ assert.deepStrictEqual(cached, data);
24
+ });
25
+
26
+ test('TTL expiration removes entries', (done) => {
27
+ const key = cache.generateKey('SELECT * FROM users', [], 'users');
28
+ cache.set(key, { id: 1 }, 'users');
29
+
30
+ setTimeout(() => {
31
+ assert.strictEqual(cache.get(key), null);
32
+ done();
33
+ }, 1100); // Wait for TTL expiration
34
+ });
35
+
36
+ test('LRU eviction removes oldest entry', () => {
37
+ cache.set('key1', 'data1', 'table1');
38
+ cache.set('key2', 'data2', 'table2');
39
+ cache.set('key3', 'data3', 'table3');
40
+
41
+ // Access key1 to make it recently used
42
+ cache.get('key1');
43
+
44
+ // Add key4 - should evict key2 (least recently used)
45
+ cache.set('key4', 'data4', 'table4');
46
+
47
+ assert.strictEqual(cache.get('key1'), 'data1'); // Still in cache
48
+ assert.strictEqual(cache.get('key2'), null); // Evicted
49
+ assert.strictEqual(cache.get('key3'), 'data3'); // Still in cache
50
+ assert.strictEqual(cache.get('key4'), 'data4'); // Newly added
51
+ });
52
+
53
+ test('invalidateTable removes all entries for table', () => {
54
+ cache.set('key1', 'data1', 'users');
55
+ cache.set('key2', 'data2', 'users');
56
+ cache.set('key3', 'data3', 'posts');
57
+
58
+ cache.invalidateTable('users');
59
+
60
+ assert.strictEqual(cache.get('key1'), null);
61
+ assert.strictEqual(cache.get('key2'), null);
62
+ assert.strictEqual(cache.get('key3'), 'data3'); // Different table
63
+ });
64
+
65
+ test('getStats returns accurate metrics', () => {
66
+ const key1 = cache.generateKey('query1', [], 'users');
67
+ const key2 = cache.generateKey('query2', [], 'posts');
68
+
69
+ cache.set(key1, 'data1', 'users');
70
+ cache.get(key1); // Hit
71
+ cache.get(key2); // Miss
72
+
73
+ const stats = cache.getStats();
74
+ assert.strictEqual(stats.size, 1);
75
+ assert.strictEqual(stats.hits, 1);
76
+ assert.strictEqual(stats.misses, 1);
77
+ assert.strictEqual(stats.hitRate, '50.00%');
78
+ });
79
+
80
+ test('clear removes all cache entries', () => {
81
+ const key1 = cache.generateKey('query1', [], 'users');
82
+ const key2 = cache.generateKey('query2', [], 'posts');
83
+
84
+ cache.set(key1, 'data1', 'users');
85
+ cache.set(key2, 'data2', 'posts');
86
+
87
+ assert.strictEqual(cache.cache.size, 2);
88
+
89
+ cache.clear();
90
+
91
+ assert.strictEqual(cache.cache.size, 0);
92
+ assert.strictEqual(cache.hitCount, 0);
93
+ assert.strictEqual(cache.missCount, 0);
94
+ });
95
+
96
+ test('disabled cache does not store or retrieve data', () => {
97
+ cache.enabled = false;
98
+
99
+ const key = cache.generateKey('SELECT * FROM users', [], 'users');
100
+ cache.set(key, 'data', 'users');
101
+
102
+ assert.strictEqual(cache.get(key), null);
103
+ assert.strictEqual(cache.cache.size, 0);
104
+ });
105
+
106
+ test('generateKey creates consistent keys for same query', () => {
107
+ const key1 = cache.generateKey('SELECT * FROM users WHERE id = ?', [1], 'users');
108
+ const key2 = cache.generateKey('SELECT * FROM users WHERE id = ?', [1], 'users');
109
+ const key3 = cache.generateKey('SELECT * FROM users WHERE id = ?', [2], 'users');
110
+
111
+ assert.strictEqual(key1, key2); // Same query + params = same key
112
+ assert.notStrictEqual(key1, key3); // Different params = different key
113
+ });
114
+
115
+ test('generateKey normalizes whitespace', () => {
116
+ const key1 = cache.generateKey('SELECT * FROM users', [], 'users');
117
+ const key2 = cache.generateKey('SELECT * FROM users', [], 'users');
118
+
119
+ assert.strictEqual(key1, key2); // Whitespace normalized
120
+ });
121
+
122
+ test('cache updates lastAccess on hit', () => {
123
+ const key = cache.generateKey('query', [], 'users');
124
+ cache.set(key, 'data', 'users');
125
+
126
+ const entry1 = cache.cache.get(key);
127
+ const originalAccess = entry1.lastAccess;
128
+
129
+ // Wait a bit
130
+ setTimeout(() => {
131
+ cache.get(key);
132
+ const entry2 = cache.cache.get(key);
133
+ assert(entry2.lastAccess > originalAccess);
134
+ }, 10);
135
+ });
136
+
137
+ test('cache tracks hit count per entry', () => {
138
+ const key = cache.generateKey('query', [], 'users');
139
+ cache.set(key, 'data', 'users');
140
+
141
+ cache.get(key);
142
+ cache.get(key);
143
+ cache.get(key);
144
+
145
+ const entry = cache.cache.get(key);
146
+ assert.strictEqual(entry.hits, 3);
147
+ });
148
+ });
@@ -1,215 +0,0 @@
1
- /**
2
- * Real-World Example: Storing JavaScript Arrays as JSON Strings
3
- *
4
- * This example demonstrates how to use field transformers to store
5
- * JavaScript arrays in database string columns, solving the common
6
- * problem of "Type validation blocking array-to-JSON transformation"
7
- *
8
- * BEFORE (using raw SQL - not ideal):
9
- * - Some fields saved through ORM
10
- * - Array fields saved via raw SQL to bypass validation
11
- * - Inconsistent, error-prone, loses ORM benefits
12
- *
13
- * AFTER (using transformers - production-ready):
14
- * - All fields saved through ORM consistently
15
- * - Arrays automatically transformed to/from JSON
16
- * - Type-safe, maintainable, elegant
17
- */
18
-
19
- const masterrecord = require('masterrecord');
20
-
21
- // ============================================================================
22
- // 1. Define Entity with Transformers
23
- // ============================================================================
24
-
25
- class User {
26
- constructor() {
27
- // Regular fields - no transformation needed
28
- this.id = { type: "integer", primary: true, auto: true };
29
- this.name = { type: "string" };
30
- this.email = { type: "string" };
31
- this.role = { type: "string" };
32
-
33
- // 🔥 ARRAY FIELDS WITH TRANSFORMERS
34
- // These fields store arrays as JSON strings in the database
35
- this.certified_models = {
36
- type: "string", // Database column type
37
- nullable: true,
38
- transform: {
39
- // Transform JavaScript array → JSON string for database
40
- toDatabase: (value) => {
41
- if (value === null || value === undefined) return null;
42
- if (Array.isArray(value)) return JSON.stringify(value);
43
- // Already a string (maybe from edit scenario)
44
- return value;
45
- },
46
-
47
- // Transform JSON string → JavaScript array from database
48
- fromDatabase: (value) => {
49
- if (!value) return [];
50
- if (Array.isArray(value)) return value; // Already parsed
51
- try {
52
- return JSON.parse(value);
53
- } catch {
54
- console.warn(`Failed to parse certified_models: ${value}`);
55
- return [];
56
- }
57
- }
58
- }
59
- };
60
-
61
- this.certified_agent_types = {
62
- type: "string",
63
- nullable: true,
64
- transform: {
65
- toDatabase: (value) => {
66
- if (value === null || value === undefined) return null;
67
- if (Array.isArray(value)) return JSON.stringify(value);
68
- return value;
69
- },
70
- fromDatabase: (value) => {
71
- if (!value) return [];
72
- if (Array.isArray(value)) return value;
73
- try {
74
- return JSON.parse(value);
75
- } catch {
76
- console.warn(`Failed to parse certified_agent_types: ${value}`);
77
- return [];
78
- }
79
- }
80
- }
81
- };
82
-
83
- // Regular numeric field
84
- this.calibration_score = { type: "integer", nullable: true };
85
- }
86
- }
87
-
88
- // ============================================================================
89
- // 2. Create Context
90
- // ============================================================================
91
-
92
- class AppContext extends masterrecord.context {
93
- constructor(config) {
94
- super(config);
95
- }
96
-
97
- onConfig(db) {
98
- this.User = this.dbset(User, "User");
99
- }
100
- }
101
-
102
- // ============================================================================
103
- // 3. Usage Example - Creating a User with Arrays
104
- // ============================================================================
105
-
106
- console.log("╔════════════════════════════════════════════════════════════════╗");
107
- console.log("║ JSON Array Transformer - Real-World Example ║");
108
- console.log("╚════════════════════════════════════════════════════════════════╝\n");
109
-
110
- console.log("📝 Scenario: User certification management system");
111
- console.log(" - Users can be certified for multiple AI models");
112
- console.log(" - Users can handle multiple agent types");
113
- console.log(" - Arrays stored as JSON strings in database\n");
114
-
115
- // Simulated context (in real app, this would connect to actual database)
116
- console.log("1️⃣ Creating new user with array fields");
117
- console.log("──────────────────────────────────────────────────");
118
-
119
- const user = new User();
120
- user.name = "Alex Rich";
121
- user.email = "alex@example.com";
122
- user.role = "calibrator";
123
-
124
- // ✨ Arrays assigned naturally - NO raw SQL needed!
125
- user.certified_models = [1, 2, 5, 8]; // Array of model IDs
126
- user.certified_agent_types = [10, 20, 30]; // Array of agent type IDs
127
- user.calibration_score = 95;
128
-
129
- console.log(` Name: ${user.name}`);
130
- console.log(` Certified Models (array): [${user.certified_models.join(', ')}]`);
131
- console.log(` Certified Agent Types (array): [${user.certified_agent_types.join(', ')}]`);
132
- console.log(` Calibration Score: ${user.calibration_score}\n`);
133
-
134
- // When saved, transformers automatically convert:
135
- // [1, 2, 5, 8] → "[1,2,5,8]" (stored in DB)
136
- console.log("2️⃣ What happens when saving");
137
- console.log("──────────────────────────────────────────────────");
138
- console.log(" User provides: [1, 2, 5, 8]");
139
- console.log(" ↓");
140
- console.log(" Transformer (toDatabase): [1, 2, 5, 8] → '[1,2,5,8]'");
141
- console.log(" ↓");
142
- console.log(" Type Validation: string '[1,2,5,8]' ✓ matches type: 'string'");
143
- console.log(" ↓");
144
- console.log(" Database Stores: '[1,2,5,8]' (as string column)\n");
145
-
146
- // Standard save - no raw SQL required!
147
- // context.User.add(user);
148
- // context.saveChanges();
149
-
150
- console.log("3️⃣ What happens when loading");
151
- console.log("──────────────────────────────────────────────────");
152
- console.log(" Database Returns: '[1,2,5,8]' (string)");
153
- console.log(" ↓");
154
- console.log(" Transformer (fromDatabase): '[1,2,5,8]' → [1, 2, 5, 8]");
155
- console.log(" ↓");
156
- console.log(" Application Receives: [1, 2, 5, 8] (JavaScript array)");
157
- console.log(" ↓");
158
- console.log(" Code: user.certified_models.includes(2) → true ✓\n");
159
-
160
- // When loaded from DB, transformers automatically convert back:
161
- // "[1,2,5,8]" → [1, 2, 5, 8] (JavaScript array)
162
- // const users = context.User.where(u => u.id == $$, userId).toList();
163
- // console.log(users[0].certified_models); // [1, 2, 5, 8]
164
-
165
- console.log("4️⃣ Updating existing user");
166
- console.log("──────────────────────────────────────────────────");
167
- console.log(" const user = context.User.where(u => u.id == $$, 14).single();");
168
- console.log(" user.certified_models = [1, 2, 5, 8, 12]; // Add model 12");
169
- console.log(" context.saveChanges(); // ✓ Works perfectly!\n");
170
-
171
- console.log("5️⃣ Benefits over raw SQL approach");
172
- console.log("──────────────────────────────────────────────────");
173
- console.log(" ✅ Consistent ORM usage (no raw SQL needed)");
174
- console.log(" ✅ Automatic transformation (transparent to application code)");
175
- console.log(" ✅ Type-safe (validation happens after transformation)");
176
- console.log(" ✅ Maintainable (transformation logic in one place)");
177
- console.log(" ✅ Testable (transformers are pure functions)");
178
- console.log(" ✅ Works with all ORM features (tracking, relationships, etc.)\n");
179
-
180
- console.log("6️⃣ Common Patterns");
181
- console.log("──────────────────────────────────────────────────");
182
-
183
- console.log("\n Pattern A: Simple Arrays");
184
- console.log(" ─────────────────────────");
185
- console.log(" certified_models: [1, 2, 3] → '[1,2,3]'");
186
-
187
- console.log("\n Pattern B: String Arrays");
188
- console.log(" ─────────────────────────");
189
- console.log(" tags: ['urgent', 'review'] → '[\"urgent\",\"review\"]'");
190
-
191
- console.log("\n Pattern C: Complex Objects");
192
- console.log(" ─────────────────────────");
193
- console.log(" metadata: {key: 'value'} → '{\"key\":\"value\"}'");
194
- console.log(" transform: { toDatabase: JSON.stringify, fromDatabase: JSON.parse }");
195
-
196
- console.log("\n Pattern D: Defaults for Null");
197
- console.log(" ─────────────────────────");
198
- console.log(" fromDatabase: (v) => v ? JSON.parse(v) : []");
199
-
200
- console.log("\n\n╔════════════════════════════════════════════════════════════════╗");
201
- console.log("║ Summary ║");
202
- console.log("╚════════════════════════════════════════════════════════════════╝\n");
203
-
204
- console.log("✨ PROBLEM SOLVED!");
205
- console.log("\nBefore: Had to use raw SQL to bypass type validation");
206
- console.log(" const sql = `UPDATE User SET certified_models = ? WHERE id = ?`;");
207
- console.log(" context.User.raw(sql, [jsonString, userId]);");
208
- console.log("\nAfter: Use ORM naturally with automatic transformation");
209
- console.log(" user.certified_models = [1, 2, 3];");
210
- console.log(" context.saveChanges();");
211
-
212
- console.log("\n🎯 Use Case: This example solves the exact problem from the");
213
- console.log(" BookBag calibration system where arrays needed to bypass ORM.\n");
214
-
215
- console.log("📖 See readme.md 'Field Transformers' section for full documentation.\n");