-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathe50-patch-replace-update.js
More file actions
458 lines (383 loc) Β· 15.1 KB
/
Copy pathe50-patch-replace-update.js
File metadata and controls
458 lines (383 loc) Β· 15.1 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
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
/**
* Example 50: patch(), replace(), and update() - Performance Comparison
*
* This example demonstrates the three methods for updating records in s3db.js:
* - update(): Traditional GET + merge + PUT (2 requests, full merge)
* - patch(): Optimized partial update (HEAD + COPY for metadata-only, or fallback to update())
* - replace(): Full object replacement (PUT only, 1 request, no merge)
*
* Key Differences:
* βββββββββββββββ¬βββββββββββββββββββ¬βββββββββββββββ¬ββββββββββββββββββββββββββ
* β Method β S3 Requests β Use Case β Performance β
* βββββββββββββββΌβββββββββββββββββββΌβββββββββββββββΌββββββββββββββββββββββββββ€
* β update() β GET + PUT β Merge data β Baseline β
* β patch() β HEAD + COPY* β Partial β 40-60% faster* β
* β replace() β PUT only β Full replace β 30-40% faster, no merge β
* βββββββββββββββ΄βββββββββββββββββββ΄βββββββββββββββ΄ββββββββββββββββββββββββββ
*
* * patch() uses HEAD + COPY only for metadata-only behaviors (enforce-limits,
* truncate-data) with simple field updates. Falls back to update() for:
* - body behaviors (body-overflow, body-only)
* - nested field updates (dot notation)
*/
import { Database } from '../../src/index.js';
// Connect to S3
const database = new Database({
connectionString: process.env.BUCKET_CONNECTION_STRING || 's3://test:test@test-bucket?region=us-east-1&endpoint=http://localhost:4566&pathStyle=true'
});
await database.connect();
console.log('='.repeat(80));
console.log('Example 50: patch(), replace(), and update() - Performance Comparison');
console.log('='.repeat(80));
// ============================================================================
// PART 1: Basic Usage Examples
// ============================================================================
console.log('\nπ PART 1: Basic Usage Examples\n');
// Create a resource with enforce-limits behavior (metadata-only)
const users = await database.createResource({
name: 'users',
attributes: {
id: 'string|required',
name: 'string|required',
email: 'string|required',
status: 'string|default:active',
loginCount: 'number|default:0',
bio: 'string|optional'
},
behavior: 'enforce-limits', // All data fits in metadata (<2KB)
timestamps: true
});
// Insert initial record
const userId = 'user-123';
await users.insert({
id: userId,
name: 'Alice Johnson',
email: 'alice@example.com',
status: 'active',
loginCount: 0,
bio: 'Software engineer'
});
console.log('β
Initial record created\n');
// ----------------------------------------------------------------------------
// 1A. update() - Traditional approach (GET + merge + PUT)
// ----------------------------------------------------------------------------
console.log('1A. update() - Traditional GET + merge + PUT:');
console.log('-'.repeat(80));
const updated = await users.update(userId, {
loginCount: 5,
status: 'premium'
});
console.log('Result:', {
id: updated.id,
name: updated.name,
email: updated.email,
status: updated.status,
loginCount: updated.loginCount
});
console.log('β
Other fields preserved (name, email, bio)');
console.log('π Requests: GET (fetch current) + PUT (save merged)\n');
// ----------------------------------------------------------------------------
// 1B. patch() - Optimized partial update (HEAD + COPY for metadata-only)
// ----------------------------------------------------------------------------
console.log('1B. patch() - Optimized HEAD + COPY (metadata-only):');
console.log('-'.repeat(80));
const patched = await users.patch(userId, {
loginCount: 10
});
console.log('Result:', {
id: patched.id,
name: patched.name,
email: patched.email,
status: patched.status,
loginCount: patched.loginCount
});
console.log('β
Other fields preserved (name, email, bio, status)');
console.log('π Requests: HEAD (metadata only) + COPY (atomic metadata update)');
console.log('π Performance: ~40-60% faster (no body transfer)\n');
// ----------------------------------------------------------------------------
// 1C. replace() - Full object replacement (PUT only, no GET)
// ----------------------------------------------------------------------------
console.log('1C. replace() - Full object replacement (PUT only):');
console.log('-'.repeat(80));
const replaced = await users.replace(userId, {
name: 'Alice Smith', // Changed
email: 'alice.smith@example.com', // Changed
status: 'active',
loginCount: 0, // Reset
bio: 'Senior software engineer' // Changed
});
console.log('Result:', {
id: replaced.id,
name: replaced.name,
email: replaced.email,
status: replaced.status,
loginCount: replaced.loginCount,
bio: replaced.bio
});
console.log('β οΈ All fields must be provided (no merge with existing data)');
console.log('π Requests: PUT only (no GET)');
console.log('π Performance: ~30-40% faster (1 request vs 2)\n');
// ============================================================================
// PART 2: Behavior Differences
// ============================================================================
console.log('\nπ PART 2: Behavior Differences\n');
// ----------------------------------------------------------------------------
// 2A. patch() with body-overflow behavior (falls back to update())
// ----------------------------------------------------------------------------
console.log('2A. patch() with body-overflow behavior:');
console.log('-'.repeat(80));
const posts = await database.createResource({
name: 'posts',
attributes: {
id: 'string|required',
title: 'string|required',
content: 'string|required',
author: 'string|required'
},
behavior: 'body-overflow', // Large content goes to body
timestamps: true
});
await posts.insert({
id: 'post-1',
title: 'Hello World',
content: 'This is a blog post with potentially large content...',
author: 'Alice'
});
const patchedPost = await posts.patch('post-1', {
title: 'Hello World - Updated'
});
console.log('Result:', {
id: patchedPost.id,
title: patchedPost.title,
author: patchedPost.author
});
console.log('β οΈ Falls back to update() (body behavior requires full merge)');
console.log('π Requests: GET + PUT (same as update())\n');
// ----------------------------------------------------------------------------
// 2B. Nested object updates (workaround for known limitation)
// ----------------------------------------------------------------------------
console.log('2B. Nested object updates (known limitation):');
console.log('-'.repeat(80));
const profiles = await database.createResource({
name: 'profiles',
attributes: {
id: 'string|required',
name: 'string|required',
settings: {
type: 'object',
props: {
theme: 'string|optional',
notifications: 'boolean|optional',
language: 'string|optional'
}
}
},
behavior: 'enforce-limits',
timestamps: true
});
await profiles.insert({
id: 'profile-1',
name: 'Bob',
settings: {
theme: 'dark',
notifications: true,
language: 'en'
}
});
// β DON'T: Dot notation loses sibling fields
// const bad = await profiles.patch('profile-1', {
// 'settings.theme': 'light'
// });
// // Result: settings = { theme: 'light' } (notifications and language lost!)
// β
DO: Update entire object
const good = await profiles.patch('profile-1', {
settings: {
theme: 'light', // Changed
notifications: true, // Preserved
language: 'en' // Preserved
}
});
console.log('Result:', {
id: good.id,
name: good.name,
settings: good.settings
});
console.log('β
All nested fields preserved (update entire object)');
console.log('β οΈ Known limitation: Dot notation (e.g., "settings.theme") not supported');
console.log('π Workaround: Update the entire nested object\n');
// ============================================================================
// PART 3: Partition Updates
// ============================================================================
console.log('\nπ PART 3: Partition Updates\n');
console.log('3. patch() and replace() with partitions:');
console.log('-'.repeat(80));
const orders = await database.createResource({
name: 'orders',
attributes: {
id: 'string|required',
customerId: 'string|required',
region: 'string|required',
status: 'string|required',
total: 'number|required'
},
behavior: 'enforce-limits',
partitions: {
byRegion: { fields: { region: 'string' } }
},
asyncPartitions: false, // Sync mode for this example
timestamps: true
});
await orders.insert({
id: 'order-1',
customerId: 'cust-1',
region: 'US',
status: 'pending',
total: 100.00
});
// patch() updates partition indexes
const patchedOrder = await orders.patch('order-1', {
status: 'completed'
});
console.log('β
patch() updated partition indexes (status changed)');
// Changing partition field moves record between partitions
const movedOrder = await orders.patch('order-1', {
region: 'EU' // Changes partition!
});
console.log('β
patch() moved record from US partition to EU partition');
// replace() also handles partition migrations
const replacedOrder = await orders.replace('order-1', {
customerId: 'cust-1',
region: 'APAC', // Another partition change!
status: 'shipped',
total: 150.00
});
console.log('β
replace() moved record from EU partition to APAC partition\n');
// ============================================================================
// PART 4: Performance Comparison
// ============================================================================
console.log('\nπ PART 4: Performance Comparison\n');
console.log('4. Benchmark: update() vs patch() vs replace():');
console.log('-'.repeat(80));
const iterations = 100;
const testUser = 'perf-test-user';
await users.insert({
id: testUser,
name: 'Performance Test',
email: 'perf@example.com',
status: 'active',
loginCount: 0
});
// Benchmark update()
const updateStart = Date.now();
for (let i = 0; i < iterations; i++) {
await users.update(testUser, { loginCount: i });
}
const updateTime = Date.now() - updateStart;
// Benchmark patch()
const patchStart = Date.now();
for (let i = 0; i < iterations; i++) {
await users.patch(testUser, { loginCount: i });
}
const patchTime = Date.now() - patchStart;
// Benchmark replace()
const replaceStart = Date.now();
for (let i = 0; i < iterations; i++) {
await users.replace(testUser, {
name: 'Performance Test',
email: 'perf@example.com',
status: 'active',
loginCount: i
});
}
const replaceTime = Date.now() - replaceStart;
console.log(`\nπ Results (${iterations} iterations):\n`);
console.log(`update(): ${updateTime}ms (baseline)`);
console.log(`patch(): ${patchTime}ms (${((1 - patchTime/updateTime) * 100).toFixed(1)}% faster) β‘`);
console.log(`replace(): ${replaceTime}ms (${((1 - replaceTime/updateTime) * 100).toFixed(1)}% faster) π\n`);
console.log('π‘ Insights:');
console.log(' - patch() uses HEAD + COPY (no body transfer) for metadata-only behaviors');
console.log(' - replace() skips GET entirely (1 request vs 2)');
console.log(' - Both maintain partition consistency and validation\n');
// ============================================================================
// PART 5: Method Selection Guide
// ============================================================================
console.log('\nπ PART 5: Method Selection Guide\n');
console.log('When to use each method:');
console.log('-'.repeat(80));
console.log('');
console.log('β
Use update():');
console.log(' - Default choice for most use cases');
console.log(' - Merges with existing data (preserves unspecified fields)');
console.log(' - Works with all behaviors');
console.log(' - Handles nested objects and complex merges');
console.log('');
console.log('β
Use patch():');
console.log(' - Updating a few fields on metadata-only behaviors (enforce-limits, truncate-data)');
console.log(' - Need 40-60% performance boost for simple updates');
console.log(' - Want automatic optimization with fallback to update()');
console.log(' - Same guarantees as update() (partitions, validation, events)');
console.log('');
console.log('β
Use replace():');
console.log(' - Have the complete object already');
console.log(' - Want maximum performance (30-40% faster, 1 request vs 2)');
console.log(' - True upsert behavior (creates if missing, replaces if exists)');
console.log(' - Don\'t need to preserve any existing fields');
console.log('');
console.log('β οΈ Avoid:');
console.log(' - patch() with dot notation for nested objects (use full object update)');
console.log(' - replace() when you need to preserve some fields (use update/patch instead)');
console.log('');
// ============================================================================
// PART 6: Error Handling
// ============================================================================
console.log('\nπ PART 6: Error Handling\n');
console.log('6. Validation and error handling:');
console.log('-'.repeat(80));
try {
// patch() validates data
await users.patch('user-123', {
status: 123 // β Wrong type (should be string)
});
} catch (err) {
console.log('β
patch() validation error caught:', err.message);
}
try {
// replace() requires all required fields
await users.replace('user-123', {
name: 'Test' // β Missing required field: email
});
} catch (err) {
console.log('β
replace() validation error caught:', err.message);
}
try {
// Empty ID
await users.patch('', { status: 'active' });
} catch (err) {
console.log('β
Empty ID error caught:', err.message);
}
console.log('');
// ============================================================================
// Summary
// ============================================================================
console.log('\n' + '='.repeat(80));
console.log('π Summary');
console.log('='.repeat(80));
console.log('');
console.log('New Methods Added:');
console.log(' β’ patch(id, fields, options) - Smart partial update with optimization');
console.log(' β’ replace(id, fullData, options) - Full replacement without GET');
console.log('');
console.log('Key Benefits:');
console.log(' β’ 40-60% faster partial updates (patch with metadata-only behaviors)');
console.log(' β’ 30-40% faster full replacements (replace vs update)');
console.log(' β’ Automatic optimization with intelligent fallbacks');
console.log(' β’ Full partition, validation, and event support');
console.log('');
console.log('Known Limitations:');
console.log(' β’ Dot notation for nested objects not supported (schema system limitation)');
console.log(' β’ Workaround: Update entire nested object instead');
console.log('');
console.log('See CLAUDE.md for complete documentation.');
console.log('='.repeat(80));
console.log('');
await database.disconnect();