data-primals-engine 1.3.3 β†’ 1.4.0

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 (36) hide show
  1. package/README.md +797 -782
  2. package/client/README.md +20 -0
  3. package/client/package-lock.json +717 -151
  4. package/client/package.json +37 -36
  5. package/client/src/App.jsx +9 -10
  6. package/client/src/App.scss +42 -1
  7. package/client/src/Dashboard.jsx +349 -208
  8. package/client/src/DashboardView.jsx +569 -547
  9. package/client/src/DataEditor.jsx +20 -2
  10. package/client/src/DataLayout.jsx +54 -13
  11. package/client/src/DataTable.jsx +807 -760
  12. package/client/src/DataTable.scss +187 -90
  13. package/client/src/Dialog.scss +0 -3
  14. package/client/src/Field.jsx +1783 -1583
  15. package/client/src/ModelCreator.jsx +25 -3
  16. package/client/src/ModelCreatorField.jsx +906 -804
  17. package/client/src/ModelList.jsx +20 -2
  18. package/client/src/constants.js +16 -4
  19. package/client/src/contexts/UIContext.jsx +19 -10
  20. package/client/src/translations.js +265 -3
  21. package/package.json +4 -3
  22. package/server.js +1 -0
  23. package/src/core.js +18 -0
  24. package/src/defaultModels.js +70 -10
  25. package/src/email.js +2 -1
  26. package/src/filter.js +260 -256
  27. package/src/i18n.js +503 -30
  28. package/src/modules/data/data.core.js +5 -1
  29. package/src/modules/data/data.history.js +489 -489
  30. package/src/modules/data/data.js +76 -10
  31. package/src/modules/data/data.routes.js +3 -20
  32. package/src/modules/user.js +19 -0
  33. package/src/modules/workflow.js +1808 -1817
  34. package/client/public/demo/26899917-d1ba-4df4-bb33-48d09a8778da.jpg +0 -0
  35. package/client/public/demo/4b9894c7-12cd-466d-8fd3-a2757b09ec96.jpg +0 -0
  36. package/client/public/demo/7ed369ac-a1a2-4b45-b0cd-4fd5bcf5a75a.jpg +0 -0
package/README.md CHANGED
@@ -1,782 +1,797 @@
1
- # Data Primals Engine
2
- [![Node.js CI](https://github.com/anonympins/data-primals-engine/actions/workflows/node.js.yml/badge.svg?branch=main)](https://github.com/anonympins/data-primals-engine/actions/workflows/node.js.yml)
3
- ![](https://img.shields.io/npm/dw/data-primals-engine)
4
- ![](https://img.shields.io/npm/last-update/data-primals-engine)
5
- ![](https://img.shields.io/github/v/release/anonympins/data-primals-engine)
6
- ![](https://img.shields.io/github/license/anonympins/data-primals-engine)
7
-
8
- **data-primals-engine** is a powerful and flexible **Node.js** backend framework designed to accelerate development of complex data-driven applications. Built on **Express.js** and **MongoDB**, it offers dynamic data modeling, automation workflows, advanced user management, and more.
9
-
10
- > Whether you're building a CRM, e-commerce site, CMS, or SaaS platform, **data-primals-engine** provides the strong foundations so you can focus on what makes your application unique.
11
-
12
- <p align="center">
13
- <a href="https://data.primals.net/prez1.jpg" target="_blank"><img alt="Light" src="https://data.primals.net/prez1.jpg" width="35%"></a>
14
- <a href="https://data.primals.net/prez6.jpg" target="_blank"><img alt="Light" src="https://data.primals.net/prez6.jpg" width="35%"></a>
15
- <a href="https://data.primals.net/api-docs" target="_blank"><img alt="Dark" src="https://data.primals.net/prez5.jpg" width="25%"></a>
16
- </p>
17
- ---
18
-
19
- ## πŸš€ Key Features
20
-
21
- - **Dynamic Data Modeling**: Define and update schemas using JSON, no migrations required.
22
- - **Custom API Endpoints**: Create server-side logic and new API endpoints directly from the UI in a secure, sandboxed environment.
23
- - **Automation Workflows**: Trigger complex actions based on data events (create, update, delete) or schedules (cron).
24
- - **Advanced Querying & Aggregation**: Go beyond simple filters with deep relation expansion, complex lookups, and dynamic calculated fields.
25
- - **Integrated Backup & Restore**: Secure, encrypted user data backups with rotation policies, supporting both local and AWS S3 storage.
26
- - **Automatic Data Auditing**: Automatically tracks all changes (create, update, delete) for every record, providing a complete version history for auditing and traceability.
27
- - **Event-Driven & Extensible**: A core event system allows for deep customization and the easy creation of new modules or plugins.
28
- - **Authentication & Authorization**: Robust role-based access control (RBAC) and pluggable user providers.
29
- - **Built-in File Management**: Handle file uploads seamlessly with integrated support for AWS S3 storage.
30
- - **🧠 AI Integration**: Natively supports OpenAI, DeepSeek and Google Gemini models via LangChain for content generation, analysis, and more.
31
- - **🌐 Internationalization (i18n)**: Fully supports multilingual interfaces and user-specific translated data.
32
- - **πŸ“¦ Starter Packs**: Quickly bootstrap applications with pre-built data packs for CRM, e-commerce, and more.
33
- - **πŸ“„Auto-Generated API Documentation**: Interactive API documentation available via the interface or at `/api-docs`.
34
-
35
-
36
- ## 🌟 Why Choose data-primals-engine?
37
-
38
- - **Zero Boilerplate**: Focus on your business logic, not infrastructure
39
- - **Scalability**: Architecture designed for rapidly growing applications
40
- - **Modularity**: Enable/disable features as needed
41
- - **Batteries Included**: Everything you need to get started quickly
42
- - **Proven Performance**: Handles 50k+ documents efficiently
43
- - **AI Ready**: Built-in LangChain integration for main providers (OpenAI,Gemini,Anthropic,DeepSeek)
44
-
45
- ---
46
-
47
- ## βš™οΈ Requirements
48
-
49
- - Node.js β‰₯ 20
50
- - MongoDB (local or remote), see [installation guide](https://www.mongodb.com/docs/manual/installation/)
51
- - NPM or Yarn
52
-
53
- ---
54
-
55
- ## ⚑ Quick Start
56
-
57
- ### check
58
- ```bash
59
- # Verify required versions
60
- node -v # Must show β‰₯ v20
61
- mongod --version # Must be installed
62
- ```
63
-
64
- ### install
65
-
66
- ```bash
67
- npm i data-primals-engine
68
- ```
69
- or
70
- ```bash
71
- git clone https://github.com/anonympins/data-primals-engine.git
72
- cd data-primals-engine
73
- npm install
74
- ```
75
-
76
- ### configure
77
- Possibly create a `.env` file:
78
- ```env
79
- MONGO_DB_URL=mongodb://127.0.0.1:27017
80
- ```
81
- | Variable | Description | Example |
82
- |:----------------------|:------------------------------------------------------------------------|:-----------------------------------------|
83
- | MONGO_DB_URL | Connection URL for your MongoDB database. | mongodb://user:pass@host:27017/db |
84
- | PORT | Port on which the Express server will listen. | 7633 |
85
- | JWT_SECRET | Secret key for signing JWT authentication tokens. | a_long_random_secret_string |
86
- | OPENAI_API_KEY | Your optional OpenAI API key for AI features. | sk-xxxxxxxxxxxxxxxxxxxx |
87
- | GOOGLE_API_KEY | Your optional Google (Gemini) API key for AI features. | AIzaSyxxxxxxxxxxxxxxxxxxxx |
88
- | DEEPSEEK_API_KEY | Your optional DeepSeek API key for AI features. | sk-xxxxxxxxxxxxxxxxxxxx |
89
- | ANTHROPIC_API_KEY | Your optional Anthropic API key for AI features. | sk-ant-xxxxxxxxxxxxxxxxxxx |
90
- | AWS_ACCESS_KEY_ID | AWS access key for S3 storage (files, backups). Keep empty to disable | AKIAIOSFODNN7EXAMPLE |
91
- | AWS_SECRET_ACCESS_KEY | AWS secret access key. | wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY | |
92
- | AWS_REGION | Region for your S3 bucket. | eu-west-3 | |
93
- | AWS_S3_BUCKET_NAME | Name of the S3 bucket to use. | my-backup-bucket | |
94
- | SMTP_HOST | SMTP server host for sending emails. | smtp.example.com |
95
- | SMTP_PORT | SMTP server port. | 587 |
96
- | SMTP_USER | Username for SMTP authentication. | user@example.com |
97
- | SMTP_PASS | Password for SMTP authentication. | password |
98
- | TLS | Encrypted connection (TLS) mode. Disabled by default | 0/1 false/true |
99
- | CA_CERT | Path to CA cert file. | certs/ca.crt |
100
- | CERT | Path to cert file. | certs/cert.pem |
101
- | CERT_KEY | Path to the key file for your certificate. | certs/key.pem |
102
-
103
- ### Start the server
104
- ```bash
105
- # Development mode
106
- npm run devserver
107
-
108
- # Production mode
109
- npm run server
110
- ```
111
-
112
- By default, the app runs on port **7633** : http://localhost:7633
113
-
114
-
115
- ---
116
-
117
- ## 🧠 Concepts
118
-
119
- ### Models
120
- Define schemas using JSON:
121
- ```json
122
- {
123
- "name": "product",
124
- "description": "E-commerce product schema",
125
- "fields": [
126
- { "name": "name", "type": "string", "required": true },
127
- { "name": "price", "type": "number", "required": true },
128
- { "name": "stock", "type": "number", "default": 0 },
129
- { "name": "category", "type": "relation", "relation": "taxonomy",
130
- "relationFilter": { "$eq": ["$type", "category"] }
131
- },
132
- { "name": "tags", "type": "relation", "relation": "taxonomy", "multiple": true,
133
- "relationFilter": { "$eq": ["$type", "keyword"] }
134
- }
135
- ]
136
- }
137
- ```
138
- ### Smart Relations
139
- - Handles up to 2,000 direct relations by default (can be customized)
140
- - For larger datasets, use intermediate collections
141
- - Automatic indexing on key fields
142
- - Custom indexing on fields
143
- - Custom fields :
144
-
145
- | Type | Description | Properties/Notes |
146
- |:------------|:------------------------------------------------------------------------------------|:--------------------------------------------------------------------------|
147
- | string | Character string. | minLength, maxLength |
148
- | string_t | International character string ID. | same as string, translated in { key, value } |
149
- | number | Numeric value (integer or float). | min, max |
150
- | boolean | Boolean value (true/false). | – |
151
- | date | Stores a ISO date. | – |
152
- | datetime | Stores an ISO date and time. | – |
153
- | richtext | Rich text field (HTML) for WYSIWYG editors. | |
154
- | richtext_t | International rich text field (HTML) for WYSIWYG editors. | i18n |
155
- | email | String validated as an email address. | – |
156
- | password | String that will be automatically hashed. | – |
157
- | enum | Allows selecting a value from a predefined list. | items: ["value1", "value2"] |
158
- | relation | Creates a link to a document in another model. | relation: "target_model_name", multiple: true/false |
159
- | file | For uploading a file (stored on S3 if configured). | allowedTypes:['image/jpeg', 'image/png', 'image/bmp'], maxSize: 1024*1000 |
160
- | image | Specialized file type for images, with preview. | – |
161
- | array | Stores a list of values. | itemsType: 'enum' // any type except relations |
162
- | object | Stores a nested JSON object. – | |
163
- | code | Stores language="*" as string, stores language="json" as arbitrary JSON structure. | language="json" conditionBuilder=true |
164
- | color | Stores an hexadecimal value of an RGB color | '#FF0000' |
165
- | model | Stores a model by name | – |
166
- | modelField | Stores a model field path | – |
167
-
168
- ### Model constraints
169
- ```javascript
170
- {
171
- "name": "modelName",
172
- "fields": [
173
- { "name": "fieldName1", .... },
174
- { "name": "fieldName2", .... }
175
- ],
176
- "constraints": [
177
- // uniqueness
178
- { "name": "uniqueConstraint", type: "unique", keys: ["fieldName1", "fieldName2"] }
179
- ]
180
- }
181
- ```
182
-
183
- ## πŸ—οΈ Use Case Examples
184
-
185
- ### πŸ›’ E-Commerce Backoffice
186
- - Install ecommerce-starter pack
187
- - Add products via API/UI
188
- - Customize order workflows
189
-
190
- ### 🎫 Support Ticket System
191
- - Create ticket model with [open, pending, resolved] statuses
192
- - Configure notification workflows
193
- - Add custom endpoints or dashboards/kpi for analytics
194
-
195
- ### πŸ€– AI Chatbot
196
- - Define your model
197
- - Set up workflow: "When new entry β†’ generate AI content"
198
-
199
- ### πŸ“§ Email Campaign Management
200
- The "Marketing & Campaigning" starter pack provides a powerful solution for sending large-scale email campaigns without overloading your server.
201
-
202
- - **Install the Pack**: A single command installs the necessary models (`campaign`, `audience`) and a sophisticated workflow.
203
- - **Dynamic Audiences**: Create target audiences with MongoDB filters. For example, select all contacts with the "newsletter" tag or located in a specific country.
204
- - **Personalized Content**: Use variables like `{recipient.firstName}` in the subject and body of your emails for a personal touch.
205
- - **Automated & Scalable Sending**: When you schedule a campaign, a pre-configured workflow takes over:
206
- - It processes your audience in small batches (e.g., 10 recipients at a time).
207
- - It sends emails to each batch and waits before processing the next, ensuring stability.
208
- - It tracks processed recipients to avoid duplicates and allow the campaign to be safely paused and resumed.
209
- - Once all emails are sent, the campaign is automatically marked as "completed".
210
-
211
- This use case demonstrates how starter packs and workflows can automate complex, performance-critical business logic right out of the box.
212
-
213
- ---
214
-
215
- ## πŸ“ Project Structure
216
- ```
217
- data-primals-engine/
218
- β”œβ”€β”€ src/
219
- β”‚ β”œβ”€β”€ middlewares/
220
- β”‚ β”œβ”€β”€ migrations/
221
- β”‚ β”œβ”€β”€ modules/
222
- β”‚ β”œβ”€β”€ workers/
223
- β”‚ β”œβ”€β”€ engine.js // The Express engine that serves the API
224
- β”‚ β”œβ”€β”€ constants.js // The inner-application constants definitions
225
- β”‚ β”œβ”€β”€ packs.js // The packs that will be loaded and available with installPack() method
226
- β”‚ β”œβ”€β”€ defaultModels.js // The default models available at startup.
227
- β”‚ β”œβ”€β”€ ...
228
- └── server.js
229
- ```
230
-
231
- ---
232
-
233
- ## πŸ”Œ API Examples (using `curl`)
234
-
235
- ### πŸ“ Model Management
236
-
237
- #### Create a model
238
- ```bash
239
- curl -X POST http://localhost:7633/api/model?_user=demo \
240
- -H "Authorization: Bearer demotoken" \
241
- -H "Content-Type: application/json" \
242
- -d '{
243
- "name": "newModel",
244
- "description": "A test model.",
245
- "fields": [
246
- {"name": "title", "type": "string", "required": true},
247
- {"name": "price", "type": "number"}
248
- ]
249
- }'
250
- ```
251
-
252
- #### Update a model
253
- ```bash
254
- curl -X PUT http://localhost:7633/api/model/60d0fe4f5311236168a109ca?_user=demo \
255
- -H "Authorization: Bearer demotoken" \
256
- -H "Content-Type: application/json" \
257
- -d '{
258
- "name": "updatedModel",
259
- "description": "Updated description.",
260
- "fields": [
261
- {"name": "title", "type": "string", "required": true},
262
- {"name": "status", "type": "enum", "items": ["active", "inactive"]}
263
- ]
264
- }'
265
- ```
266
-
267
- #### Delete a model
268
- ```bash
269
- curl -X DELETE "http://localhost:7633/api/model?_user=demo&name=newModel" \
270
- -H "Authorization: Bearer demotoken"
271
- ```
272
-
273
- ### πŸ—‚οΈ Data Management
274
-
275
- #### Create a document
276
- ```bash
277
- curl -X POST http://localhost:7633/api/data?_user=demo \
278
- -H "Authorization: Bearer demotoken" \
279
- -H "Content-Type: application/json" \
280
- -d '{
281
- "model": "product",
282
- "data": {
283
- "name": "Widget Extraordinaire",
284
- "price": 99.99,
285
- "stock": 150,
286
- "published": true
287
- }
288
- }'
289
- ```
290
-
291
- #### Search documents
292
- ```bash
293
- curl -X POST http://localhost:7633/api/data/search?_user=demo \
294
- -H "Authorization: Bearer demotoken" \
295
- -H "Content-Type: application/json" \
296
- -d '{
297
- "model": "product",
298
- "filter": { "price": { "$gt": 50 } }
299
- }'
300
- ```
301
-
302
- #### Update a document by ID
303
- ```bash
304
- curl -X PUT http://localhost:7633/api/data/64a31c123ef59d4c8d55aa99?_user=demo \
305
- -H "Authorization: Bearer demotoken" \
306
- -H "Content-Type: application/json" \
307
- -d '{
308
- "model": "product",
309
- "data": { "price": 109.99 }
310
- }'
311
- ```
312
-
313
- #### Bulk update
314
- ```bash
315
- curl -X PUT http://localhost:7633/api/data?_user=demo \
316
- -H "Authorization: Bearer demotoken" \
317
- -H "Content-Type: application/json" \
318
- -d '{
319
- "model": "product",
320
- "data": { "stock": 999 }
321
- }'
322
- ```
323
-
324
- #### Delete documents
325
- ```bash
326
- curl -X DELETE http://localhost:7633/api/data?_user=demo \
327
- -H "Authorization: Bearer demotoken" \
328
- -H "Content-Type: application/json" \
329
- -d '{
330
- "ids": ["64a31c123ef59d4c8d55aa99"]
331
- }'
332
- ```
333
-
334
- ## Other operations
335
-
336
- Make sure you use the code below to initialize the user :
337
- ```javascript
338
- import express from "express";
339
- import {Engine, insertData, searchData } from 'data-primals-engine';
340
-
341
- // Ensure the engine is initialized
342
-
343
- const app = express();
344
- const engine = await Engine.Create({ app });
345
- const currentUser = await engine.userProvider.findUserByUsername('demo');
346
- if (!currentUser) {
347
- throw new Error("Could not retrieve the user. Please check credentials or user provider.");
348
- }
349
- console.log(`Successfully authenticated as ${currentUser.username}`);
350
- ```
351
-
352
- ### insertData(modelName, data, files, user)
353
-
354
- > Inserts one or more documents, intelligently handling nested relationships.
355
-
356
- ```javascript
357
- // Uses the `currentUser` object defined above
358
- const newProduct = { name: 'Super Widget', price: 99.99, status: 'available' };
359
- const result = await insertData('product', newProduct, {}, currentUser);
360
-
361
- if (result.success) {
362
- console.log(`Successfully inserted document with ID: ${result.insertedIds[0]}`);
363
- }
364
- ```
365
-
366
- ### editData(modelName, filter, data, files, user)
367
-
368
- > Updates existing data matching the filter.
369
-
370
- Example:
371
-
372
- ```javascript
373
- await editData(
374
- "userProfile",
375
- { _id: "507f1f77bcf86cd799439011" },
376
- { bio: "Updated bio text" },
377
- null, // No files
378
- currentUser
379
- );
380
- ```
381
-
382
- ### patchData(modelName, filter, data, files, user)
383
-
384
- > Partially updates data (only modifies specified fields).
385
-
386
- Example:
387
-
388
- ```javascript
389
- await patchData(
390
- "settings",
391
- { userId: "507f1f77bcf86cd799439011" },
392
- { theme: "dark" },
393
- null,
394
- currentUser
395
- );
396
- ```
397
-
398
- ### deleteData(modelName, filter, user)
399
-
400
- >Deletes data with cascading relation cleanup.
401
-
402
- Examples:
403
-
404
- ```javascript
405
- // Delete by IDs
406
- await deleteData("comments", ["61d1f1a9e3f1a9e3f1a9e3f1"], user);
407
-
408
- // Delete by filter
409
- await deleteData("logs", { createdAt: { $lt: "2023-01-01" } }, user);
410
- ```
411
-
412
- ### searchData(query, user)
413
-
414
- Powerful search with relation expansion and filtering.
415
-
416
- Query Options:
417
-
418
- - model: Model name to search
419
- - filter: MongoDB-style filter
420
- - depth: Relation expansion depth (default: 1)
421
- - limit/page: Pagination
422
- - sort: Sorting criteria
423
-
424
- Example:
425
- ```javascript
426
- const results = await searchData({
427
- model: "blogPost",
428
- filter: { status: "published" },
429
- depth: 2, // Expand author and comments
430
- limit: 10,
431
- sort: "createdAt:DESC"
432
- }, user);
433
- ```
434
-
435
- ## Import/Export
436
- ### importData(options, files, user)
437
- > Imports data from Excel / JSON / CSV files.
438
-
439
- Supported Formats:
440
-
441
- - Excel with headers or field mapping
442
- - CSV with headers or field mapping
443
- - JSON arrays
444
- - JSON with model-keyed objects
445
-
446
- Example:
447
-
448
- ```javascript
449
- const result = await importData(
450
- {
451
- model: "products",
452
- hasHeaders: true
453
- },
454
- {
455
- file: req.files.myFileField // from multipart body
456
- },
457
- currentUser
458
- );
459
- ```
460
-
461
- ### exportData(options, user)
462
- > Exports data to a structured format.
463
-
464
- Example:
465
-
466
- ```javascript
467
- await exportData(
468
- {
469
- models: ["products", "categories"],
470
- depth: 1, // Include relations
471
- lang: "fr" // Localized data
472
- },
473
- currentUser
474
- );
475
- // Returns: { success: true, data: { products: [...], categories: [...] } }
476
- ```
477
-
478
- ## Backup & Restore
479
- ### dumpUserData(user)
480
- > Creates an encrypted backup of user data.
481
-
482
- Features:
483
-
484
- - Automatic encryption
485
- - S3 or local storage
486
- - Retention policies by plan (daily/weekly/monthly)
487
-
488
- Example:
489
-
490
- ```javascript
491
- await dumpUserData(currentUser);
492
- // Backup saved to S3 or ./backups/
493
- ```
494
-
495
- ### loadFromDump(user, options)
496
-
497
- > Restores user data from backup.
498
-
499
- Options:
500
- - modelsOnly: Restore only model definitions
501
-
502
- Example:
503
-
504
- ```javascript
505
- await loadFromDump(currentUser, { modelsOnly: false });
506
- // Full restore including data
507
- ```
508
-
509
- ## Pack Management
510
-
511
- ### installPack(packId, user, lang)
512
-
513
- > Installs a data pack.
514
-
515
- Example:
516
-
517
- ```javascript
518
- const result = await installPack([
519
- {
520
- "name": "My custom pack",
521
- "description": "Markdown **description** of the pack",
522
- "tags": ["customPack", "tag1", "tag2"],
523
- "models": [
524
- "env", // default model
525
- {
526
- "name": "post",
527
- "description": "Defines a post",
528
- "fields": [
529
- {"name": "subject", "type": "string", "required": true},
530
- ]
531
- }, // or custom
532
- ],
533
- "data": {
534
- "all": { // all languages installed data
535
- "post": [
536
- {"subject": "My pack first data"}
537
- ]
538
- },
539
- "en": { // English specific installed data
540
- "post": [
541
- {"subject": "My english first post"}
542
- ]
543
- }
544
- }
545
- }
546
- ], user, "en");
547
- // Returns installation summary
548
- ```
549
-
550
- ```javascript
551
- const result = await installPack("61d1f1a9e3f1a9e3f1a9e3f1", user, "en");
552
- // Returns installation summary
553
- ```
554
-
555
- > You can also open the pack gallery to see the JSON structure of each pack, before installing them.
556
-
557
- ---
558
- ### Data Auditing & History
559
- data-primals-engine includes a built-in, automatic auditing system that creates a complete history for every record in your database.
560
-
561
- >This feature is essential for traceability, debugging, and maintaining a clear audit trail of all data manipulations.
562
-
563
- #### How It Works
564
- The history feature is non-intrusive and fully automatic.
565
-
566
- Whenever you use the standard data management functions (insertData, patchData, deleteData, etc.), the engine performs two actions:
567
- 1. It executes the requested operation (create, update, or delete) on the target document.
568
- 2. It saves a complete "snapshot" of the document's state into a dedicated history collection.
569
-
570
- >Each history entry contains the original data along with crucial metadata:
571
- - `_op`: The type of operation (i for insert, u for update, d for delete).
572
- - `_v`: The version number of the record.
573
- - `_user`: The username of the user who performed the action.
574
- - `_updatedAt`: The timestamp of the operation.
575
- - `_rid`: The ID of the original record, linking all history entries together.
576
-
577
- ### Accessing the History
578
- You can retrieve the full history for any record via a simple API endpoint:
579
- ```x-sh
580
- curl -X GET http://localhost:7633/api/data/history/:modelName/:recordId
581
- ```
582
-
583
- ---
584
-
585
- ## Workflows: Automate Your Business Logic
586
-
587
- > Workflows are the automation engine of your application.
588
-
589
- They allow you to define complex business processes that run in response to specific events, without writing custom code.
590
-
591
- This is perfect for tasks like **sending welcome emails**, managing **order fulfillment**, or triggering data synchronization.
592
-
593
- A workflow is composed of two main parts: **Triggers** and **Actions**.
594
-
595
- > A 'workflowTrigger' is the event that initiates a workflow run.
596
- - **DataAdded**: Fires when a new document is created (e.g., a new user signs up).
597
- - **DataEdited**: Fires when a document is updated (e.g., an order status changes to "shipped").
598
- - **DataDeleted**: Fires when a document is removed.
599
- - **Scheduled**: Runs at a specific time or interval using a Cron expression (e.g., 0 0 * * * for a nightly data cleanup job).
600
- - **Manual**: Triggered on-demand via an API call, allowing you to integrate workflows into any part of your application.
601
-
602
- > A 'workflowAction' is the individual steps a workflow executes. You can chain them together to create sophisticated logic.
603
- - **CreateData**: Create a new document in any model.
604
- - **UpdateData**: Modify one or more existing documents that match a filter.
605
- - **SendEmail**: Send a transactional email using dynamic data from the trigger.
606
- - **HttpRequest**: Make an HTTP request (GET, POST, etc.) to an external service or API.
607
- - **ExecuteScript**: Run a custom JavaScript snippet for complex logic, data transformation, or conditional branching.
608
- - **GenerateAIContent**: Use an integrated AI provider (like OpenAI or Gemini) to generate text, summarize content, or make decisions.
609
- - **Wait**: Pause the workflow for a specific duration before continuing to the next step
610
-
611
- See the details of the workflow models for more details.
612
-
613
- ## ⚑ Dynamic API Endpoints
614
- Beyond standard CRUD operations, data-primals-engine allows you to create your own custom API endpoints directly from the UI. This feature acts like a built-in serverless function environment, enabling you to write custom business logic, integrate with third-party services, or create complex data aggregations on the fly.
615
-
616
- Your code is executed in a secure, isolated sandbox, with access to the core data functions and the incoming request context.
617
-
618
- ### How It Works
619
- Define an Endpoint: You create a new document in the endpoint model.
620
- Write Your Logic: In the code field, you write the JavaScript that will be executed.
621
- Activate: The engine automatically listens for requests on /api/actions/:path that match your endpoint's definition.
622
-
623
- ### The endpoint Model
624
- To create a custom endpoint, you need to define a document with the following structure:
625
- ```json
626
- {
627
- "name": "GetContactPostCount",
628
- "path": "postCount/:name",
629
- "method": "GET",
630
- "code": "const posts = await db.find('content', { author: { $find: { $eq: ['$lastName', request.params.name]}}}); return { postCount: posts.length };",
631
- "isActive": true,
632
- "isPublic": true
633
- }
634
- ```
635
- | Field | Type | Description |
636
- |:---------|:--------|:---------------------------------------------------------------------|
637
- | name | string | A descriptive name for your endpoint (e.g., "Calculate User Stats"). |
638
- | path | string | The URL path. It can include parameters like :id. |
639
- | method | enum | The HTTP method: GET, POST, PUT, PATCH, or DELETE. |
640
- | code | code | The JavaScript code to execute when the endpoint is called. |
641
- | isActive | boolean | A flag to enable or disable the endpoint without deleting it. |
642
- | isPublic | boolean | A flag to enable public access (private by default). |
643
- ---
644
-
645
- ### The Execution Context
646
- Your JavaScript code runs in an async context and has access to several global objects that are securely injected into the sandbox:
647
- Your current user is used to make the calls.
648
-
649
- #### The context Object
650
- > This object contains all the information about the incoming HTTP request.
651
- - context.request.**body**: The parsed request body (for POST, PUT, PATCH).
652
- - context.request.**query**: The URL query parameters as an object.
653
- - context.request.**params**: The URL path parameters (e.g., username from /user-summary/:username).
654
- - context.request.**headers**: The incoming request headers.
655
-
656
- #### The db Object
657
- > A secure API to interact with the database. All methods are async and must be awaited. They automatically operate within the authenticated user's permissions.
658
- - await db.**create**(modelName, dataObject): Inserts a new document.
659
- - await db.**find**(modelName, filter): Finds multiple documents. Returns an array.
660
- - await db.**findOne**(modelName, filter): Finds a single document. Returns an object or null.
661
- - await db.**update**(modelName, filter, updateObject): Partially updates documents matching the filter (similar to a PATCH).
662
- - await db.**delete**(modelName, filter): Deletes documents matching the filter.
663
-
664
- #### The logger Object
665
- > A safe way to log messages from your script. These logs will be collected and can be returned in the API response if an error occurs, which is very useful for debugging.
666
- - logger.**info**(...args)
667
- - logger.**warn**(...args)
668
- - logger.**error**(...args)
669
-
670
- #### The env Object
671
- > Provides access to the user-defined variables stored in the env model, not the server's process.env.
672
- - await env.**get**(variableName): Retrieves a single environment variable's value.
673
- - await env.**getAll**(): Retrieves all user environment variables as an object.
674
-
675
- #### Example: Creating a User Summary Endpoint
676
- Let's create an endpoint that fetches a user's profile and counts how many posts they have published.
677
- 1. Create the Endpoint Document
678
- Create a new document in the endpoint model with the following data:
679
- ```json
680
- {
681
- "name": "Get User Summary",
682
- "path": "user-summary/:username",
683
- "method": "GET",
684
- "isActive": true,
685
- "code": "const { username } = context.request.params;\n\n if (!username) {\n logger.error('Username parameter is required.');\n return { success: false, error: 'Username is required.' };\n }\n\n logger.info(`Fetching summary for user: ${username}`);\n\n // Fetch the user profile using the sandboxed db API\n const userProfile = await db.findOne('userProfile', { username: username });\n\n if (!userProfile) {\n return { success: false, error: 'User not found' };\n }\n\n // Count the user's published posts\n const posts = await db.find('post', { \n authorId: userProfile._id.toString(), \n status: 'published' \n });\n\n return {\n profile: userProfile,\n publishedPosts: posts.length\n };\n});",
686
- }
687
- ```
688
- 2. Call the New Endpoint
689
- You can now call this custom endpoint like any other API route:
690
-
691
- Expected response :
692
- ```json
693
- {
694
- "profile": {
695
- "_id": "60d0fe4f5311236168a109ca",
696
- "username": "demo",
697
- "bio": "A demo user profile.",
698
- "...": "..."
699
- },
700
- "publishedPosts": 15
701
- }
702
-
703
- ```
704
-
705
- ---
706
- ## Extensibility
707
-
708
- ### Events (Triggers) Table
709
- > You can use the events below to access the engine and manipulate API responses.
710
- > It is useful for custom modules or middlewares for your application.
711
-
712
- Just use
713
-
714
- ```javascript
715
- Event.Listen("OnDataAdded", (data) => {
716
- my_callback()
717
- }, "event", "user");
718
- ```
719
-
720
- or the system version
721
- ```javascript
722
- Event.Listen("OnDataAdded", (engine, data) => {
723
- my_callback()
724
- }, "event", "system");
725
- ```
726
-
727
- | Event | Description | Scope | Triggered by | Arguments (Payload) |
728
- |:-----------------|:------------------------------------------------------------------------|:--------------|:---------------------|:-----------------------------------------------------------------------------------------------------------------------------------------|
729
- | OnServerStart | Triggered once the HTTP server is started and listening. | System | engine.start() | engine |
730
- | OnServerStop | Triggered right after the HTTP server is stopped. | System | engine.stop() | engine |
731
- | OnModelsLoaded | Triggered after the initial models are loaded and validated at startup. | System | setupInitialModels() | engine, dbModels |
732
- | OnModelsDeleted | Triggered after all models are deleted via the reset function. | System | engine.resetModels() | engine |
733
- | OnUserDataDumped | Triggered after a user's data has been backed up (dumped). | System | jobDumpUserData() | engine |
734
- | OnDataRestored | Triggered after a user's data has been restored from a backup. | System | loadFromDump() | (none) |
735
- | OnPackInstalled | Triggered after a data pack has been successfully installed. | System | installPack() | pack |
736
- | OnModelEdited | Triggered after a model definition has been modified. | System & User | editModel() | System: engine, newModel (Pipeline*)<br>User: newModel (or the version modified by the system) |
737
- | OnDataAdded | Triggered after new data has been inserted. | System & User | insertData() | System: engine, insertedIds (Pipeline*)<br>User: insertedIds (or the version modified by the system) |
738
- | OnDataDeleted | Triggered just after data is actually deleted. | System & User | deleteData() | System: engine, {model, filter} (Pipeline*)<br>User: {model, filter} |
739
- | OnDataSearched | Triggered after a data search. | System & User | searchData() | System: engine, {data, count} (Pipeline*)<br>User: {data, count} (or the version modified by the system) |
740
- | OnDataExported | Triggered after a data export. | System & User | exportData() | System: engine, exportResults, modelsToExport (Pipeline*)<br>User: exportResults, modelsToExport (or the version modified by the system) |
741
- | OnDataInsert | Triggered just before data insertion. It will use the overrided data. | System | internal | (data) |
742
- | OnDataValidate | Triggered to override validation check. | System | internal | (value, field, data) |
743
- | OnDataFilter | Triggered to override data filtering operation. | System | internal | (filteredValue, field, data) |
744
- | OnEmailTemplate | Triggered to override custom email templates | System | internal | (filteredValue, field, data) |
745
-
746
- ### Triggering events
747
-
748
- If you want to provide your own hooks, you can call :
749
- ```javascript
750
- const result = await Event.Trigger("OnMyCustomEvent", "event", "user", ...args);
751
- ```
752
- Results are merged together if multiple events are triggered.
753
- - strings are concatenated
754
- - numbers are added
755
- - booleans are ANDed
756
- - arrays are concatenated
757
- - objects are merged using spread operator
758
-
759
-
760
- ---
761
-
762
- ## 🀝 Contributing
763
-
764
- Find the issues available for [contributions here](https://github.com/anonympins/data-primals-engine/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22help%20wanted%22%20no%3Aassignee)
765
-
766
- 1. Fork the repo
767
- 2. Create your feature branch: `git checkout -b feature/your-feature`
768
- 3. Launch ```npm run dev``` and make your changes with hot-reload on local port
769
- 3. Commit changes: `git commit -m "Add new feature"`
770
- 4. Push to your branch: `git push origin feature/your-feature`
771
- 5. Open a pull request
772
-
773
- Star ⭐ the repo if you find it useful!
774
-
775
- ---
776
-
777
- ## πŸ“„ License
778
- Distributed under the **MIT License**. See `LICENSE` file.
779
-
780
- ---
781
-
782
- ## [πŸ”Ό](https://github.com/anonympins/data-primals-engine?tab=readme-ov-file#data-primals-engine) Back to Top
1
+ # Data Primals Engine
2
+ [![Node.js CI](https://github.com/anonympins/data-primals-engine/actions/workflows/node.js.yml/badge.svg?branch=main)](https://github.com/anonympins/data-primals-engine/actions/workflows/node.js.yml)
3
+ ![](https://img.shields.io/npm/dw/data-primals-engine)
4
+ ![](https://img.shields.io/npm/last-update/data-primals-engine)
5
+ ![](https://img.shields.io/github/v/release/anonympins/data-primals-engine)
6
+ ![](https://img.shields.io/github/license/anonympins/data-primals-engine)
7
+
8
+ **data-primals-engine** is a powerful and flexible **Node.js** backend framework designed to accelerate development of complex data-driven applications. Built on **Express.js** and **MongoDB**, it offers dynamic data modeling, automation workflows, advanced user management, and more.
9
+
10
+ > Whether you're building a CRM, e-commerce site, CMS, or SaaS platform, **data-primals-engine** provides the strong foundations so you can focus on what makes your application unique.
11
+
12
+ <p align="center">
13
+ <a href="https://data.primals.net/prez1.jpg" target="_blank"><img alt="Light" src="https://data.primals.net/prez1.jpg" width="35%"></a>
14
+ <a href="https://data.primals.net/prez6.jpg" target="_blank"><img alt="Light" src="https://data.primals.net/prez6.jpg" width="35%"></a>
15
+ <a href="https://data.primals.net/api-docs" target="_blank"><img alt="Dark" src="https://data.primals.net/prez5.jpg" width="25%"></a>
16
+ </p>
17
+ ---
18
+
19
+ ## πŸš€ Key Features
20
+
21
+ - **Visual Data Modeling**: Define and update schemas using a powerful UI Model Creator, or directly with JSON. No migrations required.
22
+ - **Custom API Endpoints**: Create server-side logic and new API endpoints directly from the UI in a secure, sandboxed environment.
23
+ - **Automation Workflows**: Trigger complex actions based on data events (create, update, delete) or schedules (cron).
24
+ - **Advanced Querying & Aggregation**: Go beyond simple filters with a visual Condition Builder, deep relation expansion, complex lookups, and dynamic calculated fields.
25
+ - **Rich UI Data Views**: Ready-to-use React components to display your data, including a powerful and configurable Data Table, a Kanban board, and a Calendar view.
26
+ - **Integrated Backup & Restore**: Secure, encrypted user data backups with rotation policies, supporting both local and AWS S3 storage.
27
+ - **Automatic Data Auditing**: Automatically tracks all changes (create, update, delete) for every record, providing a complete version history for auditing and traceability.
28
+ - **Event-Driven & Extensible**: A core event system allows for deep customization and the easy creation of new modules or plugins.
29
+ - **Authentication & Authorization**: Robust role-based access control (RBAC) and pluggable user providers.
30
+ - **Built-in File Management**: Handle file uploads seamlessly with integrated support for AWS S3 storage.
31
+ - **🧠 AI Integration**: Natively supports OpenAI, DeepSeek and Google Gemini models via LangChain for content generation, analysis, and more.
32
+ - **🌐 Internationalization (i18n)**: Fully supports multilingual interfaces and user-specific translated data.
33
+ - **πŸ“¦ Starter Packs**: Quickly bootstrap applications with pre-built data packs for CRM, e-commerce, and more.
34
+ - **πŸ“„Auto-Generated API Documentation**: Interactive API documentation available via the interface or at `/api-docs`.
35
+
36
+ ## βš›οΈ Frontend Integration (React)
37
+
38
+ This engine is designed to work seamlessly with its dedicated client library, **`data-primals-engine/client`**. This library provides a complete set of React hooks and UI components to build a rich, data-centric user interface on top of the backend.
39
+
40
+ While this README focuses on the backend engine and its API, you can find detailed instructions on how to integrate the client part in your React application here:
41
+
42
+ ➑️ **[View the Frontend Integration Guide](https://github.com/anonympins/data-primals-engine/tree/develop/client)**
43
+
44
+
45
+ ## 🌟 Why Choose data-primals-engine?
46
+
47
+ - **Zero Boilerplate**: Focus on your business logic, not infrastructure
48
+ - **Scalability**: Architecture designed for rapidly growing applications
49
+ - **Modularity**: Enable/disable features as needed
50
+ - **Batteries Included**: Everything you need to get started quickly
51
+ - **Proven Performance**: Handles 50k+ documents efficiently
52
+ - **AI Ready**: Built-in LangChain integration for main providers (OpenAI,Gemini,Anthropic,DeepSeek)
53
+
54
+ ---
55
+
56
+ ## βš™οΈ Requirements
57
+
58
+ - Node.js β‰₯ 20
59
+ - MongoDB (local or remote), see [installation guide](https://www.mongodb.com/docs/manual/installation/)
60
+ - NPM or Yarn
61
+
62
+ ---
63
+
64
+ ## ⚑ Quick Start
65
+
66
+ ### check
67
+ ```bash
68
+ # Verify required versions
69
+ node -v # Must show β‰₯ v20
70
+ mongod --version # Must be installed
71
+ ```
72
+
73
+ ### install
74
+
75
+ ```bash
76
+ npm i data-primals-engine
77
+ ```
78
+ or
79
+ ```bash
80
+ git clone https://github.com/anonympins/data-primals-engine.git
81
+ cd data-primals-engine
82
+ npm install
83
+ ```
84
+
85
+ ### configure
86
+ Possibly create a `.env` file:
87
+ ```env
88
+ MONGO_DB_URL=mongodb://127.0.0.1:27017
89
+ ```
90
+ | Variable | Description | Example |
91
+ |:----------------------|:------------------------------------------------------------------------|:-----------------------------------------|
92
+ | MONGO_DB_URL | Connection URL for your MongoDB database. | mongodb://user:pass@host:27017/db |
93
+ | PORT | Port on which the Express server will listen. | 7633 |
94
+ | JWT_SECRET | Secret key for signing JWT authentication tokens. | a_long_random_secret_string |
95
+ | OPENAI_API_KEY | Your optional OpenAI API key for AI features. | sk-xxxxxxxxxxxxxxxxxxxx |
96
+ | GOOGLE_API_KEY | Your optional Google (Gemini) API key for AI features. | AIzaSyxxxxxxxxxxxxxxxxxxxx |
97
+ | DEEPSEEK_API_KEY | Your optional DeepSeek API key for AI features. | sk-xxxxxxxxxxxxxxxxxxxx |
98
+ | ANTHROPIC_API_KEY | Your optional Anthropic API key for AI features. | sk-ant-xxxxxxxxxxxxxxxxxxx |
99
+ | AWS_ACCESS_KEY_ID | AWS access key for S3 storage (files, backups). Keep empty to disable | AKIAIOSFODNN7EXAMPLE |
100
+ | AWS_SECRET_ACCESS_KEY | AWS secret access key. | wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY | |
101
+ | AWS_REGION | Region for your S3 bucket. | eu-west-3 | |
102
+ | AWS_S3_BUCKET_NAME | Name of the S3 bucket to use. | my-backup-bucket | |
103
+ | SMTP_HOST | SMTP server host for sending emails. | smtp.example.com |
104
+ | SMTP_PORT | SMTP server port. | 587 |
105
+ | SMTP_USER | Username for SMTP authentication. | user@example.com |
106
+ | SMTP_PASS | Password for SMTP authentication. | password |
107
+ | TLS | Encrypted connection (TLS) mode. Disabled by default | 0/1 false/true |
108
+ | CA_CERT | Path to CA cert file. | certs/ca.crt |
109
+ | CERT | Path to cert file. | certs/cert.pem |
110
+ | CERT_KEY | Path to the key file for your certificate. | certs/key.pem |
111
+
112
+ ### Start the server
113
+ ```bash
114
+ # Development mode
115
+ npm run devserver
116
+
117
+ # Production mode
118
+ npm run server
119
+ ```
120
+
121
+ By default, the app runs on port **7633** : http://localhost:7633
122
+
123
+
124
+ ---
125
+
126
+ ## 🧠 Concepts
127
+
128
+ ### Authentication & Authorization
129
+
130
+ The engine includes a pluggable system for user management. For development and testing, it uses a `DefaultUserProvider` which creates a volatile **`demo`** user. This is perfect for getting started quickly without setting up a user database.
131
+
132
+ For production environments, you should extend the base `UserProvider` class to connect to your actual user database (e.g., another MongoDB collection, a SQL database, or an external authentication service). This allows you to implement your own logic for finding users and validating passwords.
133
+
134
+ ### Models
135
+ Define schemas using JSON:
136
+ ```json
137
+ {
138
+ "name": "product",
139
+ "description": "E-commerce product schema",
140
+ "fields": [
141
+ { "name": "name", "type": "string", "required": true },
142
+ { "name": "price", "type": "number", "required": true },
143
+ { "name": "stock", "type": "number", "default": 0 },
144
+ { "name": "category", "type": "relation", "relation": "taxonomy",
145
+ "relationFilter": { "$eq": ["$type", "category"] }
146
+ },
147
+ { "name": "tags", "type": "relation", "relation": "taxonomy", "multiple": true,
148
+ "relationFilter": { "$eq": ["$type", "keyword"] }
149
+ }
150
+ ]
151
+ }
152
+ ```
153
+ ### Smart Relations
154
+ - Handles up to 2,000 direct relations by default (can be customized)
155
+ - For larger datasets, use intermediate collections
156
+ - Automatic indexing on key fields
157
+ - Custom indexing on fields
158
+ - Custom fields :
159
+
160
+ | Type | Description | Properties/Notes |
161
+ |:------------|:------------------------------------------------------------------------------------|:--------------------------------------------------------------------------|
162
+ | string | Character string. | minLength, maxLength |
163
+ | string_t | International character string ID. | same as string, translated in { key, value } |
164
+ | number | Numeric value (integer or float). | min, max |
165
+ | boolean | Boolean value (true/false). | – |
166
+ | date | Stores a ISO date. | – |
167
+ | datetime | Stores an ISO date and time. | – |
168
+ | richtext | Rich text field (HTML) for WYSIWYG editors. | |
169
+ | richtext_t | International rich text field (HTML) for WYSIWYG editors. | i18n |
170
+ | email | String validated as an email address. | – |
171
+ | password | String that will be automatically hashed. | – |
172
+ | enum | Allows selecting a value from a predefined list. | items: ["value1", "value2"] |
173
+ | relation | Creates a link to a document in another model. | relation: "target_model_name", multiple: true/false |
174
+ | file | For uploading a file (stored on S3 if configured). | allowedTypes:['image/jpeg', 'image/png', 'image/bmp'], maxSize: 1024*1000 |
175
+ | image | Specialized file type for images, with preview. | – |
176
+ | array | Stores a list of values. | itemsType: 'enum' // any type except relations |
177
+ | object | Stores a nested JSON object. – | |
178
+ | code | Stores language="*" as string, stores language="json" as arbitrary JSON structure. | language="json" conditionBuilder=true |
179
+ | color | Stores an hexadecimal value of an RGB color | '#FF0000' |
180
+ | model | Stores a model by name | – |
181
+ | modelField | Stores a model field path | – |
182
+
183
+ ### Model constraints
184
+ ```javascript
185
+ {
186
+ "name": "modelName",
187
+ "fields": [
188
+ { "name": "fieldName1", .... },
189
+ { "name": "fieldName2", .... }
190
+ ],
191
+ "constraints": [
192
+ // uniqueness
193
+ { "name": "uniqueConstraint", type: "unique", keys: ["fieldName1", "fieldName2"] }
194
+ ]
195
+ }
196
+ ```
197
+
198
+ ## πŸ—οΈ Use Case Examples
199
+
200
+ ### πŸ›’ E-Commerce Backoffice
201
+ - Install ecommerce-starter pack
202
+ - Add products via API/UI
203
+ - Customize order workflows
204
+
205
+ ### 🎫 Support Ticket System
206
+ - Create ticket model with [open, pending, resolved] statuses
207
+ - Configure notification workflows
208
+ - Add custom endpoints or dashboards/kpi for analytics
209
+
210
+ ### πŸ€– AI Chatbot
211
+ - Define your model
212
+ - Set up workflow: "When new entry β†’ generate AI content"
213
+
214
+ ### πŸ“§ Email Campaign Management
215
+ The "Marketing & Campaigning" starter pack provides a powerful solution for sending large-scale email campaigns without overloading your server.
216
+
217
+ - **Install the Pack**: A single command installs the necessary models (`campaign`, `audience`) and a sophisticated workflow.
218
+ - **Dynamic Audiences**: Create target audiences with MongoDB filters. For example, select all contacts with the "newsletter" tag or located in a specific country.
219
+ - **Personalized Content**: Use variables like `{recipient.firstName}` in the subject and body of your emails for a personal touch.
220
+ - **Automated & Scalable Sending**: When you schedule a campaign, a pre-configured workflow takes over:
221
+ - It processes your audience in small batches (e.g., 10 recipients at a time).
222
+ - It sends emails to each batch and waits before processing the next, ensuring stability.
223
+ - It tracks processed recipients to avoid duplicates and allow the campaign to be safely paused and resumed.
224
+ - Once all emails are sent, the campaign is automatically marked as "completed".
225
+
226
+ This use case demonstrates how starter packs and workflows can automate complex, performance-critical business logic right out of the box.
227
+
228
+ ---
229
+
230
+ ## πŸ“ Project Structure
231
+ ```
232
+ data-primals-engine/
233
+ β”œβ”€β”€ src/
234
+ β”‚ β”œβ”€β”€ middlewares/
235
+ β”‚ β”œβ”€β”€ migrations/
236
+ β”‚ β”œβ”€β”€ modules/
237
+ β”‚ β”œβ”€β”€ workers/
238
+ β”‚ β”œβ”€β”€ engine.js // The Express engine that serves the API
239
+ β”‚ β”œβ”€β”€ constants.js // The inner-application constants definitions
240
+ β”‚ β”œβ”€β”€ packs.js // The packs that will be loaded and available with installPack() method
241
+ β”‚ β”œβ”€β”€ defaultModels.js // The default models available at startup.
242
+ β”‚ β”œβ”€β”€ ...
243
+ └── server.js
244
+ ```
245
+
246
+ ---
247
+
248
+ ## πŸ”Œ API Examples (using `curl`)
249
+
250
+ ### πŸ“ Model Management
251
+
252
+ #### Create a model
253
+ ```bash
254
+ curl -X POST http://localhost:7633/api/model?_user=demo \
255
+ -H "Authorization: Bearer demotoken" \
256
+ -H "Content-Type: application/json" \
257
+ -d '{
258
+ "name": "newModel",
259
+ "description": "A test model.",
260
+ "fields": [
261
+ {"name": "title", "type": "string", "required": true},
262
+ {"name": "price", "type": "number"}
263
+ ]
264
+ }'
265
+ ```
266
+
267
+ #### Update a model
268
+ ```bash
269
+ curl -X PUT http://localhost:7633/api/model/60d0fe4f5311236168a109ca?_user=demo \
270
+ -H "Authorization: Bearer demotoken" \
271
+ -H "Content-Type: application/json" \
272
+ -d '{
273
+ "name": "updatedModel",
274
+ "description": "Updated description.",
275
+ "fields": [
276
+ {"name": "title", "type": "string", "required": true},
277
+ {"name": "status", "type": "enum", "items": ["active", "inactive"]}
278
+ ]
279
+ }'
280
+ ```
281
+
282
+ #### Delete a model
283
+ ```bash
284
+ curl -X DELETE "http://localhost:7633/api/model?_user=demo&name=newModel" \
285
+ -H "Authorization: Bearer demotoken"
286
+ ```
287
+
288
+ ### πŸ—‚οΈ Data Management
289
+
290
+ #### Create a document
291
+ ```bash
292
+ curl -X POST http://localhost:7633/api/data?_user=demo \
293
+ -H "Authorization: Bearer demotoken" \
294
+ -H "Content-Type: application/json" \
295
+ -d '{
296
+ "model": "product",
297
+ "data": {
298
+ "name": "Widget Extraordinaire",
299
+ "price": 99.99,
300
+ "stock": 150,
301
+ "published": true
302
+ }
303
+ }'
304
+ ```
305
+
306
+ #### Search documents
307
+ ```bash
308
+ curl -X POST http://localhost:7633/api/data/search?_user=demo \
309
+ -H "Authorization: Bearer demotoken" \
310
+ -H "Content-Type: application/json" \
311
+ -d '{
312
+ "model": "product",
313
+ "filter": { "price": { "$gt": 50 } }
314
+ }'
315
+ ```
316
+
317
+ #### Update a document by ID
318
+ ```bash
319
+ curl -X PUT http://localhost:7633/api/data/64a31c123ef59d4c8d55aa99?_user=demo \
320
+ -H "Authorization: Bearer demotoken" \
321
+ -H "Content-Type: application/json" \
322
+ -d '{
323
+ "model": "product",
324
+ "data": { "price": 109.99 }
325
+ }'
326
+ ```
327
+
328
+ #### Bulk update
329
+ ```bash
330
+ curl -X PUT http://localhost:7633/api/data?_user=demo \
331
+ -H "Authorization: Bearer demotoken" \
332
+ -H "Content-Type: application/json" \
333
+ -d '{
334
+ "model": "product",
335
+ "data": { "stock": 999 }
336
+ }'
337
+ ```
338
+
339
+ #### Delete documents
340
+ ```bash
341
+ curl -X DELETE http://localhost:7633/api/data?_user=demo \
342
+ -H "Authorization: Bearer demotoken" \
343
+ -H "Content-Type: application/json" \
344
+ -d '{
345
+ "ids": ["64a31c123ef59d4c8d55aa99"]
346
+ }'
347
+ ```
348
+
349
+ ## Other operations
350
+
351
+ Make sure you use the code below to initialize the user :
352
+ ```javascript
353
+ import express from "express";
354
+ import {Engine, insertData, searchData } from 'data-primals-engine';
355
+
356
+ // Ensure the engine is initialized
357
+
358
+ const app = express();
359
+ const engine = await Engine.Create({ app });
360
+ const currentUser = await engine.userProvider.findUserByUsername('demo');
361
+ if (!currentUser) {
362
+ throw new Error("Could not retrieve the user. Please check credentials or user provider.");
363
+ }
364
+ console.log(`Successfully authenticated as ${currentUser.username}`);
365
+ ```
366
+
367
+ ### insertData(modelName, data, files, user)
368
+
369
+ > Inserts one or more documents, intelligently handling nested relationships.
370
+
371
+ ```javascript
372
+ // Uses the `currentUser` object defined above
373
+ const newProduct = { name: 'Super Widget', price: 99.99, status: 'available' };
374
+ const result = await insertData('product', newProduct, {}, currentUser);
375
+
376
+ if (result.success) {
377
+ console.log(`Successfully inserted document with ID: ${result.insertedIds[0]}`);
378
+ }
379
+ ```
380
+
381
+ ### editData(modelName, filter, data, files, user)
382
+
383
+ > Updates existing data matching the filter.
384
+
385
+ Example:
386
+
387
+ ```javascript
388
+ await editData(
389
+ "userProfile",
390
+ { _id: "507f1f77bcf86cd799439011" },
391
+ { bio: "Updated bio text" },
392
+ null, // No files
393
+ currentUser
394
+ );
395
+ ```
396
+
397
+ ### patchData(modelName, filter, data, files, user)
398
+
399
+ > Partially updates data (only modifies specified fields).
400
+
401
+ Example:
402
+
403
+ ```javascript
404
+ await patchData(
405
+ "settings",
406
+ { userId: "507f1f77bcf86cd799439011" },
407
+ { theme: "dark" },
408
+ null,
409
+ currentUser
410
+ );
411
+ ```
412
+
413
+ ### deleteData(modelName, filter, user)
414
+
415
+ >Deletes data with cascading relation cleanup.
416
+
417
+ Examples:
418
+
419
+ ```javascript
420
+ // Delete by IDs
421
+ await deleteData("comments", ["61d1f1a9e3f1a9e3f1a9e3f1"], user);
422
+
423
+ // Delete by filter
424
+ await deleteData("logs", { createdAt: { $lt: "2023-01-01" } }, user);
425
+ ```
426
+
427
+ ### searchData(query, user)
428
+
429
+ Powerful search with relation expansion and filtering.
430
+
431
+ Query Options:
432
+
433
+ - model: Model name to search
434
+ - filter: MongoDB-style filter
435
+ - depth: Relation expansion depth (default: 1)
436
+ - limit/page: Pagination
437
+ - sort: Sorting criteria
438
+
439
+ Example:
440
+ ```javascript
441
+ const results = await searchData({
442
+ model: "blogPost",
443
+ filter: { status: "published" },
444
+ depth: 2, // Expand author and comments
445
+ limit: 10,
446
+ sort: "createdAt:DESC"
447
+ }, user);
448
+ ```
449
+
450
+ ## Import/Export
451
+ ### importData(options, files, user)
452
+ > Imports data from Excel / JSON / CSV files.
453
+
454
+ Supported Formats:
455
+
456
+ - Excel with headers or field mapping
457
+ - CSV with headers or field mapping
458
+ - JSON arrays
459
+ - JSON with model-keyed objects
460
+
461
+ Example:
462
+
463
+ ```javascript
464
+ const result = await importData(
465
+ {
466
+ model: "products",
467
+ hasHeaders: true
468
+ },
469
+ {
470
+ file: req.files.myFileField // from multipart body
471
+ },
472
+ currentUser
473
+ );
474
+ ```
475
+
476
+ ### exportData(options, user)
477
+ > Exports data to a structured format.
478
+
479
+ Example:
480
+
481
+ ```javascript
482
+ await exportData(
483
+ {
484
+ models: ["products", "categories"],
485
+ depth: 1, // Include relations
486
+ lang: "fr" // Localized data
487
+ },
488
+ currentUser
489
+ );
490
+ // Returns: { success: true, data: { products: [...], categories: [...] } }
491
+ ```
492
+
493
+ ## Backup & Restore
494
+ ### dumpUserData(user)
495
+ > Creates an encrypted backup of user data.
496
+
497
+ Features:
498
+
499
+ - Automatic encryption
500
+ - S3 or local storage
501
+ - Retention policies by plan (daily/weekly/monthly)
502
+
503
+ Example:
504
+
505
+ ```javascript
506
+ await dumpUserData(currentUser);
507
+ // Backup saved to S3 or ./backups/
508
+ ```
509
+
510
+ ### loadFromDump(user, options)
511
+
512
+ > Restores user data from backup.
513
+
514
+ Options:
515
+ - modelsOnly: Restore only model definitions
516
+
517
+ Example:
518
+
519
+ ```javascript
520
+ await loadFromDump(currentUser, { modelsOnly: false });
521
+ // Full restore including data
522
+ ```
523
+
524
+ ## Pack Management
525
+
526
+ ### installPack(packId, user, lang)
527
+
528
+ > Installs a data pack.
529
+
530
+ Example:
531
+
532
+ ```javascript
533
+ const result = await installPack([
534
+ {
535
+ "name": "My custom pack",
536
+ "description": "Markdown **description** of the pack",
537
+ "tags": ["customPack", "tag1", "tag2"],
538
+ "models": [
539
+ "env", // default model
540
+ {
541
+ "name": "post",
542
+ "description": "Defines a post",
543
+ "fields": [
544
+ {"name": "subject", "type": "string", "required": true},
545
+ ]
546
+ }, // or custom
547
+ ],
548
+ "data": {
549
+ "all": { // all languages installed data
550
+ "post": [
551
+ {"subject": "My pack first data"}
552
+ ]
553
+ },
554
+ "en": { // English specific installed data
555
+ "post": [
556
+ {"subject": "My english first post"}
557
+ ]
558
+ }
559
+ }
560
+ }
561
+ ], user, "en");
562
+ // Returns installation summary
563
+ ```
564
+
565
+ ```javascript
566
+ const result = await installPack("61d1f1a9e3f1a9e3f1a9e3f1", user, "en");
567
+ // Returns installation summary
568
+ ```
569
+
570
+ > You can also open the pack gallery to see the JSON structure of each pack, before installing them.
571
+
572
+ ---
573
+ ### Data Auditing & History
574
+ data-primals-engine includes a built-in, automatic auditing system that creates a complete history for every record in your database.
575
+
576
+ >This feature is essential for traceability, debugging, and maintaining a clear audit trail of all data manipulations.
577
+
578
+ #### How It Works
579
+ The history feature is non-intrusive and fully automatic.
580
+
581
+ Whenever you use the standard data management functions (insertData, patchData, deleteData, etc.), the engine performs two actions:
582
+ 1. It executes the requested operation (create, update, or delete) on the target document.
583
+ 2. It saves a complete "snapshot" of the document's state into a dedicated history collection.
584
+
585
+ >Each history entry contains the original data along with crucial metadata:
586
+ - `_op`: The type of operation (i for insert, u for update, d for delete).
587
+ - `_v`: The version number of the record.
588
+ - `_user`: The username of the user who performed the action.
589
+ - `_updatedAt`: The timestamp of the operation.
590
+ - `_rid`: The ID of the original record, linking all history entries together.
591
+
592
+ ### Accessing the History
593
+ You can retrieve the full history for any record via a simple API endpoint:
594
+ ```x-sh
595
+ curl -X GET http://localhost:7633/api/data/history/:modelName/:recordId
596
+ ```
597
+
598
+ ---
599
+
600
+ ## Workflows: Automate Your Business Logic
601
+
602
+ > Workflows are the automation engine of your application.
603
+
604
+ They allow you to define complex business processes that run in response to specific events, without writing custom code.
605
+
606
+ This is perfect for tasks like **sending welcome emails**, managing **order fulfillment**, or triggering data synchronization.
607
+
608
+ A workflow is composed of two main parts: **Triggers** and **Actions**.
609
+
610
+ > A 'workflowTrigger' is the event that initiates a workflow run.
611
+ - **DataAdded**: Fires when a new document is created (e.g., a new user signs up).
612
+ - **DataEdited**: Fires when a document is updated (e.g., an order status changes to "shipped").
613
+ - **DataDeleted**: Fires when a document is removed.
614
+ - **Scheduled**: Runs at a specific time or interval using a Cron expression (e.g., 0 0 * * * for a nightly data cleanup job).
615
+ - **Manual**: Triggered on-demand via an API call, allowing you to integrate workflows into any part of your application.
616
+
617
+ > A 'workflowAction' is the individual steps a workflow executes. You can chain them together to create sophisticated logic.
618
+ - **CreateData**: Create a new document in any model.
619
+ - **UpdateData**: Modify one or more existing documents that match a filter.
620
+ - **SendEmail**: Send a transactional email using dynamic data from the trigger.
621
+ - **HttpRequest**: Make an HTTP request (GET, POST, etc.) to an external service or API.
622
+ - **ExecuteScript**: Run a custom JavaScript snippet for complex logic, data transformation, or conditional branching.
623
+ - **GenerateAIContent**: Use an integrated AI provider (like OpenAI or Gemini) to generate text, summarize content, or make decisions.
624
+ - **Wait**: Pause the workflow for a specific duration before continuing to the next step
625
+
626
+ See the details of the workflow models for more details.
627
+
628
+ ## ⚑ Dynamic API Endpoints
629
+ Beyond standard CRUD operations, data-primals-engine allows you to create your own custom API endpoints directly from the UI. This feature acts like a built-in serverless function environment, enabling you to write custom business logic, integrate with third-party services, or create complex data aggregations on the fly.
630
+
631
+ Your code is executed in a secure, isolated sandbox, with access to the core data functions and the incoming request context.
632
+
633
+ ### How It Works
634
+ Define an Endpoint: You create a new document in the endpoint model.
635
+ Write Your Logic: In the code field, you write the JavaScript that will be executed.
636
+ Activate: The engine automatically listens for requests on /api/actions/:path that match your endpoint's definition.
637
+
638
+ ### The endpoint Model
639
+ To create a custom endpoint, you need to define a document with the following structure:
640
+ ```json
641
+ {
642
+ "name": "GetContactPostCount",
643
+ "path": "postCount/:name",
644
+ "method": "GET",
645
+ "code": "const posts = await db.find('content', { author: { $find: { $eq: ['$lastName', request.params.name]}}}); return { postCount: posts.length };",
646
+ "isActive": true,
647
+ "isPublic": true
648
+ }
649
+ ```
650
+ | Field | Type | Description |
651
+ |:---------|:--------|:---------------------------------------------------------------------|
652
+ | name | string | A descriptive name for your endpoint (e.g., "Calculate User Stats"). |
653
+ | path | string | The URL path. It can include parameters like :id. |
654
+ | method | enum | The HTTP method: GET, POST, PUT, PATCH, or DELETE. |
655
+ | code | code | The JavaScript code to execute when the endpoint is called. |
656
+ | isActive | boolean | A flag to enable or disable the endpoint without deleting it. |
657
+ | isPublic | boolean | A flag to enable public access (private by default). |
658
+ ---
659
+
660
+ ### The Execution Context
661
+ Your JavaScript code runs in an async context and has access to several global objects that are securely injected into the sandbox:
662
+ Your current user is used to make the calls.
663
+
664
+ #### The context Object
665
+ > This object contains all the information about the incoming HTTP request.
666
+ - context.request.**body**: The parsed request body (for POST, PUT, PATCH).
667
+ - context.request.**query**: The URL query parameters as an object.
668
+ - context.request.**params**: The URL path parameters (e.g., username from /user-summary/:username).
669
+ - context.request.**headers**: The incoming request headers.
670
+
671
+ #### The db Object
672
+ > A secure API to interact with the database. All methods are async and must be awaited. They automatically operate within the authenticated user's permissions.
673
+ - await db.**create**(modelName, dataObject): Inserts a new document.
674
+ - await db.**find**(modelName, filter): Finds multiple documents. Returns an array.
675
+ - await db.**findOne**(modelName, filter): Finds a single document. Returns an object or null.
676
+ - await db.**update**(modelName, filter, updateObject): Partially updates documents matching the filter (similar to a PATCH).
677
+ - await db.**delete**(modelName, filter): Deletes documents matching the filter.
678
+
679
+ #### The logger Object
680
+ > A safe way to log messages from your script. These logs will be collected and can be returned in the API response if an error occurs, which is very useful for debugging.
681
+ - logger.**info**(...args)
682
+ - logger.**warn**(...args)
683
+ - logger.**error**(...args)
684
+
685
+ #### The env Object
686
+ > Provides access to the user-defined variables stored in the env model, not the server's process.env.
687
+ - await env.**get**(variableName): Retrieves a single environment variable's value.
688
+ - await env.**getAll**(): Retrieves all user environment variables as an object.
689
+
690
+ #### Example: Creating a User Summary Endpoint
691
+ Let's create an endpoint that fetches a user's profile and counts how many posts they have published.
692
+ 1. Create the Endpoint Document
693
+ Create a new document in the endpoint model with the following data:
694
+ ```json
695
+ {
696
+ "name": "Get User Summary",
697
+ "path": "user-summary/:username",
698
+ "method": "GET",
699
+ "isActive": true,
700
+ "code": "const { username } = context.request.params;\n\n if (!username) {\n logger.error('Username parameter is required.');\n return { success: false, error: 'Username is required.' };\n }\n\n logger.info(`Fetching summary for user: ${username}`);\n\n // Fetch the user profile using the sandboxed db API\n const userProfile = await db.findOne('userProfile', { username: username });\n\n if (!userProfile) {\n return { success: false, error: 'User not found' };\n }\n\n // Count the user's published posts\n const posts = await db.find('post', { \n authorId: userProfile._id.toString(), \n status: 'published' \n });\n\n return {\n profile: userProfile,\n publishedPosts: posts.length\n };\n});",
701
+ }
702
+ ```
703
+ 2. Call the New Endpoint
704
+ You can now call this custom endpoint like any other API route:
705
+
706
+ Expected response :
707
+ ```json
708
+ {
709
+ "profile": {
710
+ "_id": "60d0fe4f5311236168a109ca",
711
+ "username": "demo",
712
+ "bio": "A demo user profile.",
713
+ "...": "..."
714
+ },
715
+ "publishedPosts": 15
716
+ }
717
+
718
+ ```
719
+
720
+ ---
721
+ ## Extensibility
722
+
723
+ ### Events (Triggers) Table
724
+ > You can use the events below to access the engine and manipulate API responses.
725
+ > It is useful for custom modules or middlewares for your application.
726
+
727
+ Just use
728
+
729
+ ```javascript
730
+ Event.Listen("OnDataAdded", (data) => {
731
+ my_callback()
732
+ }, "event", "user");
733
+ ```
734
+
735
+ or the system version
736
+ ```javascript
737
+ Event.Listen("OnDataAdded", (engine, data) => {
738
+ my_callback()
739
+ }, "event", "system");
740
+ ```
741
+
742
+ | Event | Description | Scope | Triggered by | Arguments (Payload) |
743
+ |:-----------------|:------------------------------------------------------------------------|:--------------|:---------------------|:-----------------------------------------------------------------------------------------------------------------------------------------|
744
+ | OnServerStart | Triggered once the HTTP server is started and listening. | System | engine.start() | engine |
745
+ | OnServerStop | Triggered right after the HTTP server is stopped. | System | engine.stop() | engine |
746
+ | OnModelsLoaded | Triggered after the initial models are loaded and validated at startup. | System | setupInitialModels() | engine, dbModels |
747
+ | OnModelsDeleted | Triggered after all models are deleted via the reset function. | System | engine.resetModels() | engine |
748
+ | OnUserDataDumped | Triggered after a user's data has been backed up (dumped). | System | jobDumpUserData() | engine |
749
+ | OnDataRestored | Triggered after a user's data has been restored from a backup. | System | loadFromDump() | (none) |
750
+ | OnPackInstalled | Triggered after a data pack has been successfully installed. | System | installPack() | pack |
751
+ | OnModelEdited | Triggered after a model definition has been modified. | System & User | editModel() | System: engine, newModel (Pipeline*)<br>User: newModel (or the version modified by the system) |
752
+ | OnDataAdded | Triggered after new data has been inserted. | System & User | insertData() | System: engine, insertedIds (Pipeline*)<br>User: insertedIds (or the version modified by the system) |
753
+ | OnDataDeleted | Triggered just after data is actually deleted. | System & User | deleteData() | System: engine, {model, filter} (Pipeline*)<br>User: {model, filter} |
754
+ | OnDataSearched | Triggered after a data search. | System & User | searchData() | System: engine, {data, count} (Pipeline*)<br>User: {data, count} (or the version modified by the system) |
755
+ | OnDataExported | Triggered after a data export. | System & User | exportData() | System: engine, exportResults, modelsToExport (Pipeline*)<br>User: exportResults, modelsToExport (or the version modified by the system) |
756
+ | OnDataInsert | Triggered just before data insertion. It will use the overrided data. | System | internal | (data) |
757
+ | OnDataValidate | Triggered to override validation check. | System | internal | (value, field, data) |
758
+ | OnDataFilter | Triggered to override data filtering operation. | System | internal | (filteredValue, field, data) |
759
+ | OnEmailTemplate | Triggered to override custom email templates | System | internal | (filteredValue, field, data) |
760
+
761
+ ### Triggering events
762
+
763
+ If you want to provide your own hooks, you can call :
764
+ ```javascript
765
+ const result = await Event.Trigger("OnMyCustomEvent", "event", "user", ...args);
766
+ ```
767
+ Results are merged together if multiple events are triggered.
768
+ - strings are concatenated
769
+ - numbers are added
770
+ - booleans are ANDed
771
+ - arrays are concatenated
772
+ - objects are merged using spread operator
773
+
774
+
775
+ ---
776
+
777
+ ## 🀝 Contributing
778
+
779
+ Find the issues available for [contributions here](https://github.com/anonympins/data-primals-engine/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22help%20wanted%22%20no%3Aassignee)
780
+
781
+ 1. Fork the repo
782
+ 2. Create your feature branch: `git checkout -b feature/your-feature`
783
+ 3. Launch ```npm run dev``` and make your changes with hot-reload on local port
784
+ 3. Commit changes: `git commit -m "Add new feature"`
785
+ 4. Push to your branch: `git push origin feature/your-feature`
786
+ 5. Open a pull request
787
+
788
+ Star ⭐ the repo if you find it useful!
789
+
790
+ ---
791
+
792
+ ## πŸ“„ License
793
+ Distributed under the **MIT License**. See `LICENSE` file.
794
+
795
+ ---
796
+
797
+ ## [πŸ”Ό](https://github.com/anonympins/data-primals-engine?tab=readme-ov-file#data-primals-engine) Back to Top