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.
- package/README.md +1017 -0
- package/dist/adapters/file/aliyun-oss.d.mts +26 -0
- package/dist/adapters/file/aliyun-oss.d.mts.map +1 -0
- package/dist/adapters/file/aliyun-oss.d.ts +3 -3
- package/dist/adapters/file/database.d.mts +23 -0
- package/dist/adapters/file/database.d.mts.map +1 -0
- package/dist/adapters/file/database.d.ts +2 -2
- package/dist/adapters/file/database.js +1 -1
- package/dist/adapters/file/database.mjs +1 -1
- package/dist/adapters/file/filesystem.d.mts +25 -0
- package/dist/adapters/file/filesystem.d.mts.map +1 -0
- package/dist/adapters/file/filesystem.d.ts +3 -3
- package/dist/adapters/file/google-cloud-storage.d.mts +29 -0
- package/dist/adapters/file/google-cloud-storage.d.mts.map +1 -0
- package/dist/adapters/file/google-cloud-storage.d.ts +3 -3
- package/dist/adapters/storage/postgres.d.mts +299 -0
- package/dist/adapters/storage/postgres.d.mts.map +1 -0
- package/dist/adapters/storage/postgres.d.ts +5 -1
- package/dist/adapters/storage/postgres.d.ts.map +1 -1
- package/dist/adapters/storage/postgres.js +182 -74
- package/dist/adapters/storage/postgres.js.map +1 -1
- package/dist/adapters/storage/postgres.mjs +182 -74
- package/dist/adapters/storage/postgres.mjs.map +1 -1
- package/dist/client.d.mts +16 -0
- package/dist/client.d.mts.map +1 -0
- package/dist/client.d.ts +3 -3
- package/dist/client.js +1 -1
- package/dist/client.mjs +2 -2
- package/dist/index.d.mts +151 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +68 -25
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +69 -26
- package/dist/index.mjs.map +1 -1
- package/dist/internals/{base-DSo02iAX.d.ts → base-Bhrj5Pq1.d.ts} +2 -2
- package/dist/internals/{base-DSo02iAX.d.ts.map → base-Bhrj5Pq1.d.ts.map} +1 -1
- package/dist/internals/base-CiZHXD0o.d.mts +27 -0
- package/dist/internals/base-CiZHXD0o.d.mts.map +1 -0
- package/dist/internals/chunk-Cp2QN7ug.d.mts +17 -0
- package/dist/internals/chunk-Cp2QN7ug.d.mts.map +1 -0
- package/dist/internals/{chunk-BhwfdCdq.d.ts → chunk-o7lWIP-f.d.ts} +3 -3
- package/dist/internals/{chunk-BhwfdCdq.d.ts.map → chunk-o7lWIP-f.d.ts.map} +1 -1
- package/dist/internals/{index-vOFh8pVc.js → index-B0TO6h9r.js} +8 -1
- package/dist/internals/index-B0TO6h9r.js.map +1 -0
- package/dist/internals/{index-Cj45GkKv.d.ts → index-B710pfTH.d.ts} +2 -2
- package/dist/internals/{index-Cj45GkKv.d.ts.map → index-B710pfTH.d.ts.map} +1 -1
- package/dist/internals/{index-BWZIV3_T.mjs → index-DG2-4tQ1.mjs} +8 -1
- package/dist/internals/index-DG2-4tQ1.mjs.map +1 -0
- package/dist/internals/index-DwjvuRyl.d.mts +92 -0
- package/dist/internals/index-DwjvuRyl.d.mts.map +1 -0
- package/dist/internals/index-OwgXw07h.d.mts +2107 -0
- package/dist/internals/index-OwgXw07h.d.mts.map +1 -0
- package/dist/internals/{index-1ZK5N4yb.d.ts → index-OwgXw07h.d.ts} +49 -7
- package/dist/internals/index-OwgXw07h.d.ts.map +1 -0
- package/dist/internals/{validator-Bc1jRJfA.js → validator-CFlx3oyq.js} +33 -1
- package/dist/internals/validator-CFlx3oyq.js.map +1 -0
- package/dist/internals/{validator-Boj1PUjM.mjs → validator-DubDY921.mjs} +32 -2
- package/dist/internals/validator-DubDY921.mjs.map +1 -0
- package/package.json +7 -19
- package/dist/internals/index-1ZK5N4yb.d.ts.map +0 -1
- package/dist/internals/index-BWZIV3_T.mjs.map +0 -1
- package/dist/internals/index-vOFh8pVc.js.map +0 -1
- package/dist/internals/validator-Bc1jRJfA.js.map +0 -1
- 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
|