proto.io 0.0.227 → 0.0.229

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.
Files changed (65) hide show
  1. package/README.md +1017 -0
  2. package/dist/adapters/file/aliyun-oss.d.mts +26 -0
  3. package/dist/adapters/file/aliyun-oss.d.mts.map +1 -0
  4. package/dist/adapters/file/aliyun-oss.d.ts +3 -3
  5. package/dist/adapters/file/database.d.mts +23 -0
  6. package/dist/adapters/file/database.d.mts.map +1 -0
  7. package/dist/adapters/file/database.d.ts +2 -2
  8. package/dist/adapters/file/database.js +1 -1
  9. package/dist/adapters/file/database.mjs +1 -1
  10. package/dist/adapters/file/filesystem.d.mts +25 -0
  11. package/dist/adapters/file/filesystem.d.mts.map +1 -0
  12. package/dist/adapters/file/filesystem.d.ts +3 -3
  13. package/dist/adapters/file/google-cloud-storage.d.mts +29 -0
  14. package/dist/adapters/file/google-cloud-storage.d.mts.map +1 -0
  15. package/dist/adapters/file/google-cloud-storage.d.ts +3 -3
  16. package/dist/adapters/storage/postgres.d.mts +299 -0
  17. package/dist/adapters/storage/postgres.d.mts.map +1 -0
  18. package/dist/adapters/storage/postgres.d.ts +5 -1
  19. package/dist/adapters/storage/postgres.d.ts.map +1 -1
  20. package/dist/adapters/storage/postgres.js +182 -74
  21. package/dist/adapters/storage/postgres.js.map +1 -1
  22. package/dist/adapters/storage/postgres.mjs +182 -74
  23. package/dist/adapters/storage/postgres.mjs.map +1 -1
  24. package/dist/client.d.mts +16 -0
  25. package/dist/client.d.mts.map +1 -0
  26. package/dist/client.d.ts +3 -3
  27. package/dist/client.js +1 -1
  28. package/dist/client.mjs +2 -2
  29. package/dist/index.d.mts +151 -0
  30. package/dist/index.d.mts.map +1 -0
  31. package/dist/index.d.ts +3 -3
  32. package/dist/index.js +68 -25
  33. package/dist/index.js.map +1 -1
  34. package/dist/index.mjs +69 -26
  35. package/dist/index.mjs.map +1 -1
  36. package/dist/internals/{base-DSo02iAX.d.ts → base-Bhrj5Pq1.d.ts} +2 -2
  37. package/dist/internals/{base-DSo02iAX.d.ts.map → base-Bhrj5Pq1.d.ts.map} +1 -1
  38. package/dist/internals/base-CiZHXD0o.d.mts +27 -0
  39. package/dist/internals/base-CiZHXD0o.d.mts.map +1 -0
  40. package/dist/internals/chunk-Cp2QN7ug.d.mts +17 -0
  41. package/dist/internals/chunk-Cp2QN7ug.d.mts.map +1 -0
  42. package/dist/internals/{chunk-BhwfdCdq.d.ts → chunk-o7lWIP-f.d.ts} +3 -3
  43. package/dist/internals/{chunk-BhwfdCdq.d.ts.map → chunk-o7lWIP-f.d.ts.map} +1 -1
  44. package/dist/internals/{index-vOFh8pVc.js → index-B0TO6h9r.js} +8 -1
  45. package/dist/internals/index-B0TO6h9r.js.map +1 -0
  46. package/dist/internals/{index-Cj45GkKv.d.ts → index-B710pfTH.d.ts} +2 -2
  47. package/dist/internals/{index-Cj45GkKv.d.ts.map → index-B710pfTH.d.ts.map} +1 -1
  48. package/dist/internals/{index-BWZIV3_T.mjs → index-DG2-4tQ1.mjs} +8 -1
  49. package/dist/internals/index-DG2-4tQ1.mjs.map +1 -0
  50. package/dist/internals/index-DwjvuRyl.d.mts +92 -0
  51. package/dist/internals/index-DwjvuRyl.d.mts.map +1 -0
  52. package/dist/internals/index-OwgXw07h.d.mts +2107 -0
  53. package/dist/internals/index-OwgXw07h.d.mts.map +1 -0
  54. package/dist/internals/{index-1ZK5N4yb.d.ts → index-OwgXw07h.d.ts} +49 -7
  55. package/dist/internals/index-OwgXw07h.d.ts.map +1 -0
  56. package/dist/internals/{validator-Bc1jRJfA.js → validator-CFlx3oyq.js} +33 -1
  57. package/dist/internals/validator-CFlx3oyq.js.map +1 -0
  58. package/dist/internals/{validator-Boj1PUjM.mjs → validator-DubDY921.mjs} +32 -2
  59. package/dist/internals/validator-DubDY921.mjs.map +1 -0
  60. package/package.json +7 -19
  61. package/dist/internals/index-1ZK5N4yb.d.ts.map +0 -1
  62. package/dist/internals/index-BWZIV3_T.mjs.map +0 -1
  63. package/dist/internals/index-vOFh8pVc.js.map +0 -1
  64. package/dist/internals/validator-Bc1jRJfA.js.map +0 -1
  65. package/dist/internals/validator-Boj1PUjM.mjs.map +0 -1
package/README.md ADDED
@@ -0,0 +1,1017 @@
1
+ # Proto.io
2
+
3
+ A full-featured backend-as-a-service (BaaS) framework built with TypeScript, providing database abstraction, real-time capabilities, file storage, authentication, and more.
4
+
5
+ ## Overview
6
+
7
+ Proto.io is a comprehensive backend framework that provides a Parse-like API for building scalable applications. It includes support for multiple database backends, file storage adapters, real-time queries, authentication, role-based permissions, and job scheduling.
8
+
9
+ ## Features
10
+
11
+ - **Database Abstraction**: Support for PostgreSQL with extensible adapter pattern
12
+ - **Real-time Capabilities**: Live queries and event notifications via Socket.io
13
+ - **File Storage**: Multiple storage backends (filesystem, database, cloud providers)
14
+ - **Authentication & Authorization**: JWT-based auth with role-based permissions
15
+ - **Schema Management**: Type-safe schema definitions with validation
16
+ - **Job Scheduling**: Background job processing with cron-like scheduling
17
+ - **Cloud Functions**: Server-side function execution
18
+ - **Vector Support**: Vector database operations for ML/AI applications
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ npm install proto.io
24
+ ```
25
+
26
+ ## Quick Start
27
+
28
+ ### Server Setup
29
+
30
+ ```typescript
31
+ import { ProtoRoute, ProtoService, schema } from 'proto.io';
32
+ import { PostgresStorage } from 'proto.io/adapters/storage/postgres';
33
+ import { DatabaseFileStorage } from 'proto.io/adapters/file/database';
34
+
35
+ // Define your schema
36
+ const mySchema = schema({
37
+ Post: {
38
+ fields: {
39
+ title: schema.string(),
40
+ content: schema.string(),
41
+ author: schema.pointer('User'),
42
+ tags: schema.stringArray(),
43
+ published: schema.boolean(false),
44
+ publishedAt: schema.date(),
45
+ },
46
+ classLevelPermissions: {
47
+ find: ['*'],
48
+ get: ['*'],
49
+ create: ['authenticated'],
50
+ update: ['authenticated'],
51
+ delete: ['authenticated'],
52
+ }
53
+ }
54
+ });
55
+
56
+ // Create ProtoService
57
+ const proto = new ProtoService({
58
+ endpoint: 'http://localhost:1337/api',
59
+ schema: mySchema,
60
+ storage: new PostgresStorage({
61
+ connectionString: 'postgresql://user:pass@localhost/db'
62
+ }),
63
+ fileStorage: new DatabaseFileStorage(),
64
+ jwtToken: 'your-jwt-secret',
65
+ masterUsers: [{ user: 'master', pass: 'your-master-key' }],
66
+ });
67
+
68
+ // Create Express router
69
+ const router = await ProtoRoute({ proto });
70
+
71
+ // Mount to your Express app
72
+ app.use('/api', router);
73
+ ```
74
+
75
+ ### Client Usage
76
+
77
+ ```typescript
78
+ import { ProtoClient } from 'proto.io/client';
79
+
80
+ const client = new ProtoClient({
81
+ endpoint: 'http://localhost:1337/api',
82
+ });
83
+
84
+ // Query data
85
+ const posts = await client.Query('Post')
86
+ .equalTo('published', true)
87
+ .includes('author')
88
+ .find();
89
+
90
+ // Create objects using Query
91
+ const post = await client.Query('Post').insert({
92
+ title: 'Hello World',
93
+ content: 'This is my first post',
94
+ published: true,
95
+ });
96
+
97
+ // Alternative: Create objects using Object
98
+ const post2 = client.Object('Post');
99
+ post2.set('title', 'Hello World');
100
+ post2.set('content', 'This is my first post');
101
+ post2.set('published', true);
102
+ await post2.save();
103
+
104
+ // Real-time subscriptions
105
+ const subscription = client.Query('Post')
106
+ .equalTo('published', true)
107
+ .subscribe();
108
+
109
+ subscription.on('create', (post) => {
110
+ console.log('New post created:', post);
111
+ });
112
+
113
+ subscription.on('update', (post) => {
114
+ console.log('Post updated:', post);
115
+ });
116
+ ```
117
+
118
+ ## Architecture
119
+
120
+ ### Core Components
121
+
122
+ #### ProtoService
123
+ The main service class that orchestrates all functionality:
124
+ - Schema validation and management
125
+ - Database operations
126
+ - Authentication and authorization
127
+ - File handling
128
+ - Job scheduling
129
+ - Real-time notifications
130
+
131
+ #### Storage Adapters
132
+ Pluggable storage backends:
133
+ - **PostgreSQL**: Primary SQL database adapter with full feature support
134
+ - **SQL Base**: Abstract base for SQL adapters
135
+
136
+ #### File Storage Adapters
137
+ Multiple file storage options:
138
+ - **Database**: Store files as base64 chunks in database
139
+ - **Filesystem**: Store files on local filesystem
140
+ - **Google Cloud Storage**: Google Cloud Storage integration
141
+ - **Aliyun OSS**: Alibaba Cloud Object Storage Service
142
+
143
+ #### Query System
144
+ Type-safe query builder with:
145
+ - Filtering and sorting
146
+ - Relations and joins
147
+ - Aggregations and grouping
148
+ - Pagination
149
+ - Live queries for real-time updates
150
+
151
+ ### Project Structure
152
+
153
+ ```
154
+ src/
155
+ ├── index.ts # Main exports and ProtoRoute factory
156
+ ├── client/ # Client-side SDK
157
+ │ ├── index.ts # Client exports
158
+ │ ├── options.ts # Client configuration
159
+ │ ├── query.ts # Client query builder
160
+ │ ├── request.ts # HTTP request handling
161
+ │ └── proto/ # Client protocol implementation
162
+ ├── server/ # Server-side implementation
163
+ │ ├── auth/ # Authentication middleware
164
+ │ ├── crypto/ # Password hashing and utilities
165
+ │ ├── file/ # File handling interface
166
+ │ ├── proto/ # Core ProtoService implementation
167
+ │ ├── pubsub/ # Pub/Sub for real-time features
168
+ │ ├── query/ # Query processing and dispatching
169
+ │ ├── routes/ # HTTP API endpoints
170
+ │ ├── storage/ # Storage interface
171
+ │ ├── schedule.ts # Job scheduling
172
+ │ └── utils.ts # Server utilities
173
+ ├── internals/ # Internal type definitions and utilities
174
+ │ ├── buffer.ts # Buffer utilities
175
+ │ ├── codec.ts # Serialization/deserialization
176
+ │ ├── const.ts # Constants
177
+ │ ├── options.ts # Option types
178
+ │ ├── private.ts # Private key for internal access
179
+ │ ├── schema.ts # Schema type definitions
180
+ │ ├── types.ts # Core type definitions
181
+ │ ├── utils.ts # Utility functions
182
+ │ ├── liveQuery/ # Live query implementation
183
+ │ ├── object/ # Object type definitions
184
+ │ ├── proto/ # Protocol type definitions
185
+ │ └── query/ # Query type definitions
186
+ └── adapters/ # Storage and file adapters
187
+ ├── file/ # File storage adapters
188
+ │ ├── base/ # Base file storage classes
189
+ │ ├── database/ # Database file storage
190
+ │ ├── filesystem/ # Filesystem storage
191
+ │ ├── google-cloud-storage/ # Google Cloud Storage
192
+ │ └── aliyun-oss/ # Aliyun Object Storage
193
+ └── storage/ # Database storage adapters
194
+ ├── sql/ # SQL base adapter
195
+ └── postgres/ # PostgreSQL adapter
196
+ ```
197
+
198
+ ## Schema Definition
199
+
200
+ Proto.io uses a type-safe schema system:
201
+
202
+ ```typescript
203
+ import { schema } from 'proto.io';
204
+
205
+ const mySchema = schema({
206
+ User: {
207
+ fields: {
208
+ username: schema.string(),
209
+ email: schema.string(),
210
+ emailVerified: schema.boolean(false),
211
+ profile: schema.object(),
212
+ tags: schema.stringArray(),
213
+ avatar: schema.pointer('File'),
214
+ preferences: schema.shape({
215
+ theme: schema.string('light'),
216
+ notifications: schema.boolean(true),
217
+ }),
218
+ embedding: schema.vector(1536), // For AI/ML applications
219
+ },
220
+ classLevelPermissions: {
221
+ find: ['*'],
222
+ get: ['*'],
223
+ create: ['*'],
224
+ update: [],
225
+ delete: [],
226
+ },
227
+ fieldLevelPermissions: {
228
+ email: { read: [] },
229
+ },
230
+ indexes: [
231
+ { keys: { username: 1 }, unique: true },
232
+ { keys: { email: 1 }, unique: true },
233
+ ]
234
+ },
235
+
236
+ Post: {
237
+ fields: {
238
+ title: schema.string(),
239
+ content: schema.string(),
240
+ author: schema.pointer('User'),
241
+ comments: schema.relation('Comment', 'post'),
242
+ publishedAt: schema.date(),
243
+ }
244
+ },
245
+
246
+ Comment: {
247
+ fields: {
248
+ content: schema.string(),
249
+ author: schema.pointer('User'),
250
+ post: schema.pointer('Post'),
251
+ }
252
+ }
253
+ });
254
+ ```
255
+
256
+ ### Schema Types
257
+
258
+ - `schema.boolean(defaultValue?)` - Boolean field
259
+ - `schema.number(defaultValue?)` - Number field
260
+ - `schema.decimal(defaultValue?)` - Decimal.js field for precision
261
+ - `schema.string(defaultValue?)` - String field
262
+ - `schema.stringArray(defaultValue?)` - Array of strings
263
+ - `schema.date(defaultValue?)` - Date field
264
+ - `schema.object(defaultValue?)` - Generic object
265
+ - `schema.array(defaultValue?)` - Generic array
266
+ - `schema.vector(dimension, defaultValue?)` - Vector for ML/AI
267
+ - `schema.shape(definition)` - Structured object with typed fields
268
+ - `schema.pointer(targetClass)` - Reference to another object
269
+ - `schema.relation(targetClass, foreignField?)` - One-to-many relation
270
+
271
+ ## Authentication & Authorization
272
+
273
+ ### User Authentication
274
+
275
+ Proto.io uses server-side cloud functions for authentication rather than built-in client methods:
276
+
277
+ ```typescript
278
+ // Server-side: Define authentication functions
279
+ proto.define('createUser', async ({ params, req }) => {
280
+ const { username, email, password } = params;
281
+ const user = await proto.Query('User').insert({ username, email }, { master: true });
282
+ await proto.setPassword(user, password, { master: true });
283
+ await proto.becomeUser(req, user);
284
+ return user;
285
+ });
286
+
287
+ proto.define('loginUser', async ({ params, req }) => {
288
+ const { username, password } = params;
289
+ const user = await proto.Query('User')
290
+ .equalTo('username', username)
291
+ .first({ master: true });
292
+
293
+ if (!user || !await proto.verifyPassword(user, password, { master: true })) {
294
+ throw new Error('Invalid credentials');
295
+ }
296
+
297
+ await proto.becomeUser(req, user);
298
+ return user;
299
+ });
300
+
301
+ // Client-side: Use cloud functions for authentication
302
+ await client.run('createUser', {
303
+ username: 'john_doe',
304
+ email: 'john@example.com',
305
+ password: 'secure_password'
306
+ });
307
+
308
+ await client.run('loginUser', {
309
+ username: 'john_doe',
310
+ password: 'secure_password'
311
+ });
312
+
313
+ // Get current user
314
+ const currentUser = await client.currentUser();
315
+
316
+ // Log out
317
+ await client.logout();
318
+ ```
319
+
320
+ ### Permissions
321
+
322
+ #### Valid Permission Values
323
+
324
+ Proto.io supports the following permission values:
325
+
326
+ - **`'*'`** - Public access (anyone)
327
+ - **`'role:roleName'`** - Users with specific role (e.g., `'role:admin'`, `'role:moderator'`)
328
+ - **User IDs** - Specific user access (e.g., `'user123'`)
329
+ - **`[]`** - Empty array (no access)
330
+
331
+ #### Class-Level Permissions
332
+ Control access to entire collections:
333
+
334
+ ```typescript
335
+ classLevelPermissions: {
336
+ find: ['*'], // Anyone can query
337
+ get: ['*'], // Anyone can get by ID
338
+ create: ['*'], // Anyone can create
339
+ update: ['role:admin'], // Admin role only
340
+ delete: ['role:admin'] // Admin role only
341
+ }
342
+ ```
343
+
344
+ #### Field-Level Permissions
345
+ Control access to specific fields:
346
+
347
+ ```typescript
348
+ fieldLevelPermissions: {
349
+ email: {
350
+ read: ['role:admin'], // Only admin can read
351
+ create: [], // No one can create
352
+ update: [] // No one can update
353
+ },
354
+ password: {
355
+ create: [], // No one can create
356
+ update: [] // No one can update
357
+ }
358
+ }
359
+ ```
360
+
361
+ ### Roles
362
+
363
+ ```typescript
364
+ // Create role
365
+ const adminRole = await proto.Query('Role').insert({
366
+ name: 'admin',
367
+ users: [user],
368
+ roles: [moderatorRole] // Role inheritance
369
+ }, { master: true });
370
+
371
+ // Check user roles
372
+ const roles = await proto.currentRoles();
373
+ ```
374
+
375
+ ## Real-time Features
376
+
377
+ ### Live Queries
378
+
379
+ ```typescript
380
+ // Client-side live query
381
+ const subscription = client.Query('Post')
382
+ .equalTo('published', true)
383
+ .subscribe();
384
+
385
+ subscription.on('create', (post) => {
386
+ console.log('New post created:', post);
387
+ });
388
+
389
+ subscription.on('update', (post) => {
390
+ console.log('Post updated:', post);
391
+ });
392
+
393
+ subscription.on('delete', (post) => {
394
+ console.log('Post deleted:', post);
395
+ });
396
+
397
+ // Unsubscribe from specific events
398
+ const { remove } = subscription.on('create', callback);
399
+ remove(); // Remove this specific listener
400
+ ```
401
+
402
+ ### Event Notifications
403
+
404
+ ```typescript
405
+ // Server-side: Send notification
406
+ await proto.notify({
407
+ type: 'new_message',
408
+ message: 'Hello!',
409
+ _rperm: [user.id] // Read permissions - use actual user ID from user object
410
+ });
411
+
412
+ // Or for roles:
413
+ await proto.notify({
414
+ type: 'new_message',
415
+ message: 'Hello!',
416
+ _rperm: ['role:admin'] // Role permissions - use "role:" prefix
417
+ });
418
+
419
+ // Client-side: Listen for events
420
+ client.on('new_message', (data) => {
421
+ console.log('New message:', data.message);
422
+ });
423
+ ```
424
+
425
+ ## File Storage
426
+
427
+ ### Upload Files
428
+
429
+ ```typescript
430
+ // Server-side file upload
431
+ app.post('/upload', upload.single('file'), async (req, res) => {
432
+ const proto = getProtoInstance(req);
433
+
434
+ const file = await proto.Query('File').insert({
435
+ name: req.file.originalname,
436
+ data: req.file.buffer,
437
+ mimeType: req.file.mimetype,
438
+ });
439
+
440
+ res.json(file);
441
+ });
442
+
443
+ // Client-side
444
+ const fileInput = document.querySelector('input[type="file"]');
445
+ const formData = new FormData();
446
+ formData.append('file', fileInput.files[0]);
447
+
448
+ const response = await fetch('/api/upload', {
449
+ method: 'POST',
450
+ body: formData,
451
+ headers: {
452
+ 'Authorization': `Bearer ${sessionToken}`
453
+ }
454
+ });
455
+ ```
456
+
457
+ ### File Storage Adapters
458
+
459
+ #### Database Storage
460
+ ```typescript
461
+ import { DatabaseFileStorage } from 'proto.io/adapters/file/database';
462
+
463
+ const fileStorage = new DatabaseFileStorage({
464
+ chunkSize: 16 * 1024, // 16KB chunks
465
+ parallel: 8 // Parallel uploads
466
+ });
467
+ ```
468
+
469
+ #### Filesystem Storage
470
+ ```typescript
471
+ import { FileSystemStorage } from 'proto.io/adapters/file/filesystem';
472
+
473
+ const fileStorage = new FileSystemStorage('/var/uploads', {
474
+ chunkSize: 64 * 1024
475
+ });
476
+ ```
477
+
478
+ #### Google Cloud Storage
479
+ ```typescript
480
+ import { GoogleCloudStorage } from 'proto.io/adapters/file/google-cloud-storage';
481
+ import { Storage } from '@google-cloud/storage';
482
+
483
+ const storage = new Storage({
484
+ projectId: 'your-project-id',
485
+ keyFilename: 'path/to/service-account.json'
486
+ });
487
+
488
+ const fileStorage = new GoogleCloudStorage(storage, 'your-bucket-name');
489
+ ```
490
+
491
+ ## Cloud Functions
492
+
493
+ ```typescript
494
+ // Define server function
495
+ proto.define('sendEmail', async ({ params, user, master }) => {
496
+ if (!user && !master) throw new Error('Authentication required');
497
+
498
+ const { to, subject, body } = params;
499
+
500
+ await emailService.send({
501
+ to,
502
+ subject,
503
+ body,
504
+ from: 'noreply@yourapp.com'
505
+ });
506
+
507
+ return { success: true };
508
+ });
509
+
510
+ // Call from client
511
+ const result = await client.run('sendEmail', {
512
+ to: 'user@example.com',
513
+ subject: 'Welcome!',
514
+ body: 'Welcome to our app!'
515
+ });
516
+ ```
517
+
518
+ ## Background Jobs
519
+
520
+ ```typescript
521
+ // Define job
522
+ proto.defineJob('processImages', async ({ params, user, master }) => {
523
+ const { imageIds } = params;
524
+
525
+ for (const imageId of imageIds) {
526
+ const image = await proto.Query('File').get(imageId, { master: true });
527
+ // Process image...
528
+ await processImage(image);
529
+ }
530
+ });
531
+
532
+ // Schedule job
533
+ await proto.scheduleJob('processImages', {
534
+ imageIds: ['img1', 'img2', 'img3']
535
+ });
536
+
537
+ // Recurring jobs can be scheduled with cron syntax
538
+ // This would be done in your scheduler setup
539
+ ```
540
+
541
+ ## Database Adapters
542
+
543
+ ### PostgreSQL Adapter
544
+
545
+ ```typescript
546
+ import { PostgresStorage } from 'proto.io/adapters/storage/postgres';
547
+
548
+ const storage = new PostgresStorage({
549
+ connectionString: 'postgresql://user:pass@localhost:5432/dbname',
550
+ // or individual options:
551
+ host: 'localhost',
552
+ port: 5432,
553
+ database: 'myapp',
554
+ user: 'postgres',
555
+ password: 'password',
556
+ ssl: false,
557
+
558
+ // Connection pool options
559
+ max: 20, // Maximum connections
560
+ idleTimeoutMillis: 30000,
561
+ connectionTimeoutMillis: 2000,
562
+ });
563
+ ```
564
+
565
+ ## Advanced Features
566
+
567
+ ### Vector Search (AI/ML)
568
+
569
+ ```typescript
570
+ // Define schema with vector field
571
+ const schema = {
572
+ Document: {
573
+ fields: {
574
+ content: schema.string(),
575
+ embedding: schema.vector(1536), // OpenAI embedding dimension
576
+ }
577
+ }
578
+ };
579
+
580
+ // Store embeddings
581
+ await client.Query('Document').insert({
582
+ content: 'This is a sample document',
583
+ embedding: await getEmbedding('This is a sample document')
584
+ });
585
+
586
+ // Vector similarity search using distance expressions
587
+ const similar = await client.Query('Document')
588
+ .filter({
589
+ $expr: {
590
+ $lt: [
591
+ {
592
+ $distance: [
593
+ { $key: 'embedding' },
594
+ { $value: queryEmbedding }
595
+ ]
596
+ },
597
+ { $value: 0.2 } // threshold of 0.2
598
+ ]
599
+ }
600
+ })
601
+ .sort([{
602
+ order: 1, // 1 for ascending (closest first), -1 for descending
603
+ expr: {
604
+ $distance: [
605
+ { $key: 'embedding' },
606
+ { $value: queryEmbedding }
607
+ ]
608
+ }
609
+ }])
610
+ .limit(10)
611
+ .find();
612
+ ```
613
+
614
+ ### Transactions
615
+
616
+ ```typescript
617
+ await proto.withTransaction(async (txProto) => {
618
+ const user = await txProto.Query('User').get(userId, { master: true });
619
+
620
+ await txProto.Query('User').update(userId, {
621
+ balance: user.get('balance') - amount
622
+ }, { master: true });
623
+
624
+ await txProto.Query('Transaction').insert({
625
+ user: user,
626
+ amount: -amount,
627
+ type: 'debit'
628
+ }, { master: true });
629
+ });
630
+ ```
631
+
632
+ ### Aggregations
633
+
634
+ Proto.io provides two aggregation methods:
635
+
636
+ 1. **`groupFind`** - Direct aggregation across all matching documents (terminal operation)
637
+ 2. **`groupMatches`** - Aggregation on relation fields within a query
638
+
639
+ #### Direct Aggregations with `groupFind`
640
+
641
+ The `groupFind` method executes a query and returns aggregated results directly:
642
+
643
+ ```typescript
644
+ // Count matching documents
645
+ const stats = await client.Query('Order')
646
+ .equalTo('status', 'completed')
647
+ .groupFind({
648
+ totalOrders: { $count: true }
649
+ });
650
+ console.log(stats.totalOrders); // e.g., 42
651
+
652
+ // Multiple aggregations in one query
653
+ const salesStats = await client.Query('Order')
654
+ .equalTo('status', 'completed')
655
+ .greaterThanOrEqualTo('createdAt', startOfMonth)
656
+ .groupFind({
657
+ orderCount: { $count: true },
658
+ totalRevenue: { $sum: { $key: 'amount' } },
659
+ avgOrderValue: { $avg: { $key: 'amount' } },
660
+ minOrder: { $min: { $key: 'amount' } },
661
+ maxOrder: { $max: { $key: 'amount' } }
662
+ });
663
+
664
+ console.log(`Revenue: $${salesStats.totalRevenue}`);
665
+ console.log(`Average: $${salesStats.avgOrderValue}`);
666
+
667
+ // Group by field and aggregate each group
668
+ const salesByRegion = await client.Query('Order')
669
+ .equalTo('status', 'completed')
670
+ .groupFind({
671
+ countByRegion: {
672
+ $group: {
673
+ key: { $key: 'region' },
674
+ value: { $count: true }
675
+ }
676
+ },
677
+ revenueByRegion: {
678
+ $group: {
679
+ key: { $key: 'region' },
680
+ value: { $sum: { $key: 'amount' } }
681
+ }
682
+ }
683
+ });
684
+
685
+ // Results are arrays of { key, value } objects
686
+ salesByRegion.countByRegion.forEach(({ key, value }) => {
687
+ console.log(`${key}: ${value} orders`);
688
+ });
689
+ // Output: US: 120 orders, EU: 85 orders, APAC: 43 orders
690
+
691
+ salesByRegion.revenueByRegion.forEach(({ key, value }) => {
692
+ console.log(`${key}: $${value}`);
693
+ });
694
+ // Output: US: $45000, EU: $32000, APAC: $18000
695
+
696
+ // Statistical aggregations
697
+ const surveyStats = await client.Query('Survey')
698
+ .groupFind({
699
+ median: {
700
+ $percentile: {
701
+ input: { $key: 'score' },
702
+ p: 0.5,
703
+ mode: 'continuous'
704
+ }
705
+ },
706
+ stdDev: { $stdDevPop: { $key: 'score' } },
707
+ mostCommon: { $most: { $key: 'category' } }
708
+ });
709
+ ```
710
+
711
+ #### Relation Aggregations with `groupMatches`
712
+
713
+ The `groupMatches` method performs aggregations on relation fields:
714
+
715
+ ```typescript
716
+ // Count items in a relation
717
+ const result = await client.Query('Order')
718
+ .equalTo('_id', orderId)
719
+ .groupMatches('items', {
720
+ count: { $count: true }
721
+ })
722
+ .first();
723
+
724
+ console.log(result?.get('items.count')); // Number of items
725
+
726
+ // Sum total values in order items
727
+ const orderTotal = await client.Query('Order')
728
+ .equalTo('_id', orderId)
729
+ .groupMatches('items', {
730
+ total: { $sum: { $key: 'price' } },
731
+ average: { $avg: { $key: 'price' } },
732
+ maxPrice: { $max: { $key: 'price' } },
733
+ minPrice: { $min: { $key: 'price' } }
734
+ })
735
+ .first();
736
+
737
+ console.log(orderTotal?.get('items.total'));
738
+ console.log(orderTotal?.get('items.average'));
739
+
740
+ // Group by category and aggregate each group
741
+ const groupedStats = await client.Query('Order')
742
+ .equalTo('_id', orderId)
743
+ .groupMatches('items', {
744
+ countByCategory: {
745
+ $group: {
746
+ key: { $key: 'category' },
747
+ value: { $count: true }
748
+ }
749
+ },
750
+ totalByCategory: {
751
+ $group: {
752
+ key: { $key: 'category' },
753
+ value: { $sum: { $key: 'price' } }
754
+ }
755
+ }
756
+ })
757
+ .first();
758
+
759
+ // Results are arrays of { key, value } objects
760
+ const countByCategory = groupedStats?.get('items.countByCategory');
761
+ // Example: [{ key: 'Electronics', value: 5 }, { key: 'Books', value: 3 }]
762
+
763
+ const totalByCategory = groupedStats?.get('items.totalByCategory');
764
+ // Example: [{ key: 'Electronics', value: 1500 }, { key: 'Books', value: 50 }]
765
+
766
+ // Advanced aggregations with percentiles
767
+ const stats = await client.Query('Survey')
768
+ .equalTo('_id', surveyId)
769
+ .groupMatches('responses', {
770
+ median: {
771
+ $percentile: {
772
+ input: { $key: 'rating' },
773
+ p: 0.5
774
+ }
775
+ },
776
+ standardDev: { $stdDevPop: { $key: 'rating' } },
777
+ variance: { $varPop: { $key: 'rating' } }
778
+ })
779
+ .first();
780
+ ```
781
+
782
+ #### Available Aggregation Operators
783
+
784
+ - `$count` - Count of documents
785
+ - `$sum` - Sum of values
786
+ - `$avg` - Average of values
787
+ - `$max` - Maximum value
788
+ - `$min` - Minimum value
789
+ - `$most` - Most frequent value (mode)
790
+ - `$stdDevPop` - Population standard deviation
791
+ - `$stdDevSamp` - Sample standard deviation
792
+ - `$varPop` - Population variance
793
+ - `$varSamp` - Sample variance
794
+ - `$percentile` - Percentile calculation with options for discrete/continuous mode
795
+ - `$group` - Group by key expression and apply aggregation to each group (returns array of `{key, value}` objects)
796
+
797
+ **Key Differences:**
798
+ - **`groupFind`**: Terminal operation that executes query and returns aggregated values directly
799
+ - **`groupMatches`**: Aggregates relation fields within parent objects, used with `.first()` or `.find()`
800
+
801
+ ## Configuration
802
+
803
+ ### Environment Variables
804
+
805
+ ```bash
806
+ # Database
807
+ DATABASE_URL=postgresql://user:pass@localhost:5432/dbname
808
+
809
+ # App Configuration
810
+ SERVER_URL=http://localhost:1337/api
811
+ MASTER_KEY=your-master-key
812
+ JWT_SECRET=your-jwt-secret
813
+
814
+ # File Storage
815
+ FILE_STORAGE_TYPE=filesystem
816
+ FILE_STORAGE_PATH=/var/uploads
817
+
818
+ # Redis (for pub/sub)
819
+ REDIS_URL=redis://localhost:6379
820
+
821
+ # JWT Configuration
822
+ JWT_EXPIRES_IN=30d
823
+ ```
824
+
825
+ ### Full Configuration Example
826
+
827
+ ```typescript
828
+ const proto = new ProtoService({
829
+ // Core configuration
830
+ endpoint: process.env.SERVER_URL,
831
+ jwtToken: process.env.JWT_SECRET,
832
+ masterUsers: [{ user: 'master', pass: process.env.MASTER_KEY }],
833
+
834
+ // Schema definition
835
+ schema: mySchema,
836
+
837
+ // Storage adapter
838
+ storage: new PostgresStorage({
839
+ connectionString: process.env.DATABASE_URL
840
+ }),
841
+
842
+ // File storage
843
+ fileStorage: new FileSystemStorage('/var/uploads'),
844
+
845
+ // Performance settings
846
+ objectIdSize: 10,
847
+ maxFetchLimit: 1000,
848
+ maxUploadSize: 20 * 1024 * 1024, // 20MB
849
+
850
+ // Authentication
851
+ jwtSignOptions: { expiresIn: '30d' },
852
+ cookieOptions: {
853
+ maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days
854
+ httpOnly: true,
855
+ secure: process.env.NODE_ENV === 'production'
856
+ },
857
+
858
+ // Password hashing
859
+ passwordHashOptions: {
860
+ alg: 'scrypt',
861
+ log2n: 14,
862
+ blockSize: 8,
863
+ parallel: 1,
864
+ keySize: 64,
865
+ saltSize: 64,
866
+ },
867
+
868
+ // Pub/Sub for real-time features
869
+ pubsub: redisPubSub,
870
+
871
+ // Role resolution
872
+ roleResolver: {
873
+ inheritKeys: ['users', 'roles'],
874
+ resolver: async (user, defaultResolver) => {
875
+ // Custom role resolution logic
876
+ return defaultResolver();
877
+ }
878
+ },
879
+
880
+ // Class extensions
881
+ classExtends: {
882
+ User: {
883
+ prototype: {
884
+ async sendWelcomeEmail() {
885
+ // Custom user methods
886
+ }
887
+ }
888
+ }
889
+ },
890
+
891
+ // Logging
892
+ logger: {
893
+ loggerLevel: 'info',
894
+ info: console.info,
895
+ warn: console.warn,
896
+ error: console.error,
897
+ }
898
+ });
899
+ ```
900
+
901
+ ## Testing
902
+
903
+ The project includes comprehensive tests covering:
904
+
905
+ - CRUD operations
906
+ - Query functionality
907
+ - Real-time features
908
+ - Authentication and authorization
909
+ - File storage
910
+ - Background jobs
911
+ - Vector operations
912
+ - Edge cases and error handling
913
+
914
+ ```bash
915
+ # Run tests
916
+ npm test
917
+
918
+ # Run specific test suites
919
+ npm test -- --testPathPattern=query
920
+ npm test -- --testPathPattern=auth
921
+ ```
922
+
923
+ ## API Reference
924
+
925
+ ### ProtoService Methods
926
+
927
+ #### Query Operations
928
+ - `Query(className)` - Create a query for a class
929
+ - `Relation(object, key)` - Create a relation query
930
+ - `InsecureQuery(className)` - Create an insecure query (bypasses ACL)
931
+
932
+ #### Authentication
933
+ - `currentUser()` - Get current authenticated user
934
+ - `currentRoles()` - Get current user's roles
935
+ - `becomeUser(req, user)` - Sign in as user
936
+ - `logoutUser(req)` - Sign out current user
937
+
938
+ #### Functions & Jobs
939
+ - `run(name, params)` - Execute cloud function
940
+ - `define(name, callback)` - Define cloud function
941
+ - `scheduleJob(name, params)` - Schedule background job
942
+ - `defineJob(name, callback)` - Define job handler
943
+
944
+ #### Configuration
945
+ - `config()` - Get app configuration
946
+ - `setConfig(values)` - Set configuration values
947
+
948
+ #### Real-time
949
+ - `notify(data)` - Send notification
950
+ - `listen(callback)` - Listen for notifications
951
+
952
+ #### Utilities
953
+ - `withTransaction(callback)` - Execute in transaction
954
+ - `generateUploadToken()` - Generate file upload token
955
+ - `gc()` - Run garbage collection
956
+
957
+ ### ProtoClient Methods
958
+
959
+ #### Queries & Real-time
960
+ - `Query(className)` - Create query for data access
961
+ - `Query(className).subscribe()` - Create live query subscription
962
+ - `Relation(object, key)` - Create relation query
963
+
964
+ #### Objects & Files
965
+ - `Object(className, id?)` - Create new object instance
966
+ - `File(name, data, mimeType)` - Create file object
967
+
968
+ #### Functions & Jobs
969
+ - `run(name, params)` - Execute cloud function
970
+ - `scheduleJob(name, params)` - Schedule background job
971
+
972
+ #### Authentication & Session
973
+ - `currentUser()` - Get current authenticated user
974
+ - `logout()` - Log out current user
975
+ - `setSessionToken(token)` - Set session token
976
+ - `sessionInfo()` - Get session information
977
+ - `setPassword(user, password, options)` - Set user password (requires master)
978
+ - `unsetPassword(user, options)` - Remove user password (requires master)
979
+
980
+ #### Configuration & System
981
+ - `online()` - Check if server is online
982
+ - `config(options?)` - Get configuration values
983
+ - `configAcl(options)` - Get configuration ACLs (requires master)
984
+ - `setConfig(values, options)` - Set configuration (requires master)
985
+ - `schema(options)` - Get schema information (requires master)
986
+
987
+ #### Real-time Events
988
+ - `listen(callback, selector?)` - Listen for custom events
989
+ - `notify(data, options?)` - Send custom notifications
990
+
991
+ #### Utilities
992
+ - `refs(object, options?)` - Get all references to an object
993
+ - `refreshSocketSession()` - Refresh WebSocket session
994
+ - `rebind(object)` - Rebind object to proto instance
995
+
996
+ ## Migration Guide
997
+
998
+ When upgrading between versions, check the migration guide for breaking changes and upgrade instructions.
999
+
1000
+ ## Contributing
1001
+
1002
+ 1. Fork the repository
1003
+ 2. Create a feature branch
1004
+ 3. Make your changes
1005
+ 4. Add tests for new functionality
1006
+ 5. Run the test suite
1007
+ 6. Submit a pull request
1008
+
1009
+ ## License
1010
+
1011
+ MIT License - see LICENSE file for details.
1012
+
1013
+ ## Support
1014
+
1015
+ - GitHub Issues: Report bugs and request features
1016
+ - Documentation: Full API documentation available
1017
+ - Examples: Sample applications and use cases