-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathe36-hook-limitations.js
More file actions
251 lines (207 loc) · 6.93 KB
/
Copy pathe36-hook-limitations.js
File metadata and controls
251 lines (207 loc) · 6.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
import dotenv from 'dotenv';
import { join } from 'path';
import S3db from '../src/index.js';
dotenv.config({ debug: false, silent: true });
const testPrefix = join('s3db', 'tests', new Date().toISOString().substring(0, 10), 'hook-limitations-' + Date.now());
// 🚨 HOOK PERSISTENCE LIMITATIONS
async function demonstrateHookLimitations() {
console.log('🚨 Demonstrating hook persistence limitations...');
const db = new S3db({
verbose: false,
bucket: 's3db',
accessKeyId: process.env.MINIO_USER,
secretAccessKey: process.env.MINIO_PASSWORD,
endpoint: 'http://localhost:9100',
forcePathStyle: true,
prefix: testPrefix,
persistHooks: true
});
await db.connect();
// 🔴 PROBLEM 1: External variables
console.log('\n1. 🔴 PROBLEM: Hooks with external variables');
const ADMIN_EMAIL = 'admin@company.com'; // External variable
const CONFIG = { maxRetries: 3 }; // External object
try {
await db.createResource({
name: 'users_with_external_vars',
behavior: 'user-managed',
attributes: {
name: 'string',
email: 'string'
},
hooks: {
beforeInsert: [
function problematicHook(user) {
// ❌ ADMIN_EMAIL and CONFIG will not exist after deserialization
if (user.email === ADMIN_EMAIL) {
console.log('Admin user detected!');
}
if (CONFIG.maxRetries > 0) {
console.log('Retry logic enabled');
}
return user;
}
]
}
});
// Works on the first connection
const resource1 = db.resources.users_with_external_vars;
await resource1.insert({ name: 'Admin', email: ADMIN_EMAIL });
console.log('✅ Worked on the first connection');
} catch (error) {
console.log('❌ Error:', error.message);
}
await db.disconnect();
// Reconnect - the hook will fail
console.log('\n2. 🔄 Reconnecting...');
const db2 = new S3db({
verbose: false,
bucket: 's3db',
accessKeyId: process.env.MINIO_USER,
secretAccessKey: process.env.MINIO_PASSWORD,
endpoint: 'http://localhost:9100',
forcePathStyle: true,
prefix: testPrefix,
persistHooks: true
});
await db2.connect();
try {
const resource2 = db2.resources.users_with_external_vars;
await resource2.insert({ name: 'Test', email: 'test@company.com' });
console.log('❌ This should not work without the external variables');
} catch (error) {
console.log('🚨 Hook failed after reconnection:', error.message);
}
await db2.disconnect();
// 🟢 SOLUTION 1: Self-contained hooks
console.log('\n3. 🟢 SOLUTION: Self-contained hooks');
const db3 = new S3db({
verbose: false,
bucket: 's3db',
accessKeyId: process.env.MINIO_USER,
secretAccessKey: process.env.MINIO_PASSWORD,
endpoint: 'http://localhost:9100',
forcePathStyle: true,
prefix: testPrefix + '-solutions',
persistHooks: true
});
await db3.connect();
await db3.createResource({
name: 'users_self_contained',
behavior: 'user-managed',
attributes: {
name: 'string',
email: 'string',
role: 'string|optional'
},
hooks: {
beforeInsert: [
function selfContainedHook(user) {
// ✅ All constants are inside the function
const ADMIN_EMAIL = 'admin@company.com';
const ALLOWED_DOMAINS = ['company.com', 'contractor.com'];
if (user.email === ADMIN_EMAIL) {
user.role = 'admin';
console.log('✅ Admin user detected and role set');
}
const domain = user.email.split('@')[1];
if (!ALLOWED_DOMAINS.includes(domain)) {
throw new Error(`Domain ${domain} not allowed`);
}
return user;
}
]
}
});
await db3.disconnect();
// Test self-contained hook after reconnection
const db4 = new S3db({
verbose: false,
bucket: 's3db',
accessKeyId: process.env.MINIO_USER,
secretAccessKey: process.env.MINIO_PASSWORD,
endpoint: 'http://localhost:9100',
forcePathStyle: true,
prefix: testPrefix + '-solutions',
persistHooks: true
});
await db4.connect();
const resource4 = db4.resources.users_self_contained;
try {
const adminUser = await resource4.insert({
name: 'Admin',
email: 'admin@company.com'
});
console.log('✅ Self-contained hook worked:', adminUser.role);
await resource4.insert({
name: 'Employee',
email: 'john@company.com'
});
console.log('✅ Domain validation worked');
} catch (error) {
console.log('❌ Unexpected error:', error.message);
}
await db4.disconnect();
// 🔴 PROBLEM 2: References to other resources
console.log('\n4. 🔴 PROBLEM: References to other resources');
const db5 = new S3db({
verbose: false,
bucket: 's3db',
accessKeyId: process.env.MINIO_USER,
secretAccessKey: process.env.MINIO_PASSWORD,
endpoint: 'http://localhost:9100',
forcePathStyle: true,
prefix: testPrefix + '-cross-ref',
persistHooks: true
});
await db5.connect();
// First, create config resource
const configResource = await db5.createResource({
name: 'config',
behavior: 'user-managed',
attributes: {
key: 'string',
value: 'string'
}
});
await configResource.insert({ key: 'max_users', value: '100' });
// ❌ PROBLEMATIC: Hook referencing another resource
console.log('⚠️ Creating hook that references another resource (problematic)...');
await db5.createResource({
name: 'users_with_cross_ref',
behavior: 'user-managed',
attributes: {
name: 'string',
email: 'string'
},
hooks: {
beforeInsert: [
function problematicCrossRefHook(user) {
// ❌ 'this' will not be the same after deserialization
// ❌ 'configResource' does not exist in scope
// This code fails after reconnection
try {
const maxUsers = this.database.resources.config;
console.log('Checking user limit...');
} catch (error) {
console.log('❌ Cross-reference failed:', error.message);
}
return user;
}
]
}
});
await db5.disconnect();
console.log('\n✨ Summary of limitations:');
console.log('🔴 External variables: Not serialized');
console.log('🔴 Closures: Captured scope is lost');
console.log("🔴 References to resources: 'this' context may be lost");
console.log('🔴 Imported modules: Not automatically re-imported');
console.log('\n💡 Best practices:');
console.log('✅ Keep hooks self-contained');
console.log('✅ Define constants inside the function');
console.log('✅ Use simple and direct validations');
console.log('✅ Avoid external dependencies');
console.log('✅ Use only basic JavaScript types');
}
demonstrateHookLimitations().catch(console.error);