data-primals-engine 1.0.8 → 1.0.10
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/.github/workflows/node.js.yml +59 -0
- package/.github/workflows/npm-publish.yml +23 -0
- package/Dockerfile +15 -0
- package/DockerfileTest +15 -0
- package/README.md +217 -0
- package/package.json +4 -7
- package/src/engine.js +2 -2
- package/src/i18n.js +2274 -0
- package/src/index.js +1 -1
- package/src/migrate.js +1 -1
- package/src/modules/data.js +124 -88
- package/src/modules/mongodb.js +0 -7
- package/src/setenv.js +39 -0
- package/test/data.backup.integration.test.js +141 -0
- package/test/data.integration.test.js +796 -0
- package/test/globalSetup.js +15 -0
- package/test/globalTeardown.js +8 -0
- package/test/import_export.integration.test.js +200 -0
- package/test/model.integration.test.js +219 -0
- package/test/workflow.integration.test.js +314 -0
- package/test/workflow.robustness.test.js +195 -0
- package/vitest.config.js +16 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
|
|
2
|
+
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
|
|
3
|
+
|
|
4
|
+
name: Node.js CI
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
branches: [ "develop", "main" ]
|
|
9
|
+
pull_request:
|
|
10
|
+
branches: [ "develop", "main" ]
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
build:
|
|
14
|
+
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
container:
|
|
17
|
+
image: node:18 # Specify the Docker version you needx
|
|
18
|
+
env:
|
|
19
|
+
NODE_OPTIONS: "--max_old_space_size=4096"
|
|
20
|
+
NODE_ENV: development
|
|
21
|
+
ports:
|
|
22
|
+
- 7635
|
|
23
|
+
- 27017
|
|
24
|
+
strategy:
|
|
25
|
+
matrix:
|
|
26
|
+
node-version: [18.x]
|
|
27
|
+
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
|
28
|
+
services:
|
|
29
|
+
mongodb:
|
|
30
|
+
image: mongo:5.0
|
|
31
|
+
ports:
|
|
32
|
+
- 27017:27017
|
|
33
|
+
options: >-
|
|
34
|
+
--health-cmd "mongo --eval 'db.serverStatus()'"
|
|
35
|
+
--health-interval 10s
|
|
36
|
+
--health-timeout 5s
|
|
37
|
+
--health-retries 5
|
|
38
|
+
steps:
|
|
39
|
+
- uses: actions/checkout@v4
|
|
40
|
+
|
|
41
|
+
- name: Install MongoDB Tools
|
|
42
|
+
run: |
|
|
43
|
+
wget -qO - https://www.mongodb.org/static/pgp/server-5.0.asc | apt-key add -
|
|
44
|
+
echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/5.0 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-5.0.list
|
|
45
|
+
apt-get update
|
|
46
|
+
apt-get install -y mongodb-org-tools
|
|
47
|
+
|
|
48
|
+
- name: Set up Node.js
|
|
49
|
+
uses: actions/setup-node@v2
|
|
50
|
+
with:
|
|
51
|
+
node-version: '18.18.2'
|
|
52
|
+
|
|
53
|
+
- name: Install dependencies
|
|
54
|
+
run: npm install
|
|
55
|
+
|
|
56
|
+
- name: Run tests
|
|
57
|
+
run: npm test
|
|
58
|
+
|
|
59
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
name: Publish package to GitHub Packages
|
|
2
|
+
on:
|
|
3
|
+
release:
|
|
4
|
+
types: [published]
|
|
5
|
+
jobs:
|
|
6
|
+
build:
|
|
7
|
+
runs-on: ubuntu-latest
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
packages: write
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
# Setup .npmrc file to publish to GitHub Packages
|
|
14
|
+
- uses: actions/setup-node@v4
|
|
15
|
+
with:
|
|
16
|
+
node-version: '20.x'
|
|
17
|
+
registry-url: 'https://npm.pkg.github.com'
|
|
18
|
+
# Defaults to the user or organization that owns the workflow file
|
|
19
|
+
scope: '@octocat'
|
|
20
|
+
- run: npm ci
|
|
21
|
+
- run: npm publish
|
|
22
|
+
env:
|
|
23
|
+
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
package/Dockerfile
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Base image
|
|
2
|
+
FROM node:18.18.2
|
|
3
|
+
|
|
4
|
+
# Set working directory
|
|
5
|
+
WORKDIR /
|
|
6
|
+
|
|
7
|
+
# Copy package files and install dependencies
|
|
8
|
+
COPY package.json ./
|
|
9
|
+
RUN npm install
|
|
10
|
+
|
|
11
|
+
# Copy application code
|
|
12
|
+
COPY . .
|
|
13
|
+
|
|
14
|
+
# Expose the application port
|
|
15
|
+
EXPOSE 7633
|
package/DockerfileTest
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Base image
|
|
2
|
+
FROM node:18.18.2
|
|
3
|
+
|
|
4
|
+
# Set working directory
|
|
5
|
+
WORKDIR /
|
|
6
|
+
|
|
7
|
+
# Copy package files and install dependencies
|
|
8
|
+
COPY package.json ./
|
|
9
|
+
RUN npm install
|
|
10
|
+
|
|
11
|
+
# Copy application code
|
|
12
|
+
COPY . .
|
|
13
|
+
|
|
14
|
+
# Expose the application port
|
|
15
|
+
EXPOSE 7635
|
package/README.md
CHANGED
|
@@ -194,6 +194,195 @@ curl -X DELETE http://localhost:7633/api/data?_user=demo \
|
|
|
194
194
|
}'
|
|
195
195
|
```
|
|
196
196
|
|
|
197
|
+
## Other operations
|
|
198
|
+
|
|
199
|
+
Make sure you use the code below to initialize the user :
|
|
200
|
+
```javascript
|
|
201
|
+
import { Engine } from 'data-primals-engine/engine';
|
|
202
|
+
import { insertData, searchData } from 'data-primals-engine/modules/data';
|
|
203
|
+
|
|
204
|
+
// Ensure the engine is initialized
|
|
205
|
+
const engine = await Engine.Create();
|
|
206
|
+
const currentUser = await engine.userProvider.findUserByUsername('demo');
|
|
207
|
+
if (!currentUser) {
|
|
208
|
+
throw new Error("Could not retrieve the user. Please check credentials or user provider.");
|
|
209
|
+
}
|
|
210
|
+
console.log(`Successfully authenticated as ${currentUser.username}`);
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### insertData(modelName, data, files, user)
|
|
214
|
+
|
|
215
|
+
> Inserts one or more documents, intelligently handling nested relationships.
|
|
216
|
+
|
|
217
|
+
```javascript
|
|
218
|
+
// Uses the `currentUser` object defined above
|
|
219
|
+
const newProduct = { name: 'Super Widget', price: 99.99, status: 'available' };
|
|
220
|
+
const result = await insertData('product', newProduct, {}, currentUser);
|
|
221
|
+
|
|
222
|
+
if (result.success) {
|
|
223
|
+
console.log(`Successfully inserted document with ID: ${result.insertedIds[0]}`);
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### editData(modelName, filter, data, files, user)
|
|
228
|
+
|
|
229
|
+
> Updates existing data matching the filter.
|
|
230
|
+
|
|
231
|
+
Example:
|
|
232
|
+
|
|
233
|
+
```javascript
|
|
234
|
+
await editData(
|
|
235
|
+
"userProfile",
|
|
236
|
+
{ _id: "507f1f77bcf86cd799439011" },
|
|
237
|
+
{ bio: "Updated bio text" },
|
|
238
|
+
null, // No files
|
|
239
|
+
currentUser
|
|
240
|
+
);
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### patchData(modelName, filter, data, files, user)
|
|
244
|
+
|
|
245
|
+
> Partially updates data (only modifies specified fields).
|
|
246
|
+
|
|
247
|
+
Example:
|
|
248
|
+
|
|
249
|
+
```javascript
|
|
250
|
+
await patchData(
|
|
251
|
+
"settings",
|
|
252
|
+
{ userId: "507f1f77bcf86cd799439011" },
|
|
253
|
+
{ theme: "dark" },
|
|
254
|
+
null,
|
|
255
|
+
currentUser
|
|
256
|
+
);
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### deleteData(modelName, ids, filter, user)
|
|
260
|
+
|
|
261
|
+
>Deletes data with cascading relation cleanup.
|
|
262
|
+
|
|
263
|
+
Examples:
|
|
264
|
+
|
|
265
|
+
```javascript
|
|
266
|
+
// Delete by IDs
|
|
267
|
+
await deleteData("comments", ["61d1f1a9e3f1a9e3f1a9e3f1"], null, user);
|
|
268
|
+
|
|
269
|
+
// Delete by filter
|
|
270
|
+
await deleteData("logs", null, { createdAt: { $lt: "2023-01-01" } }, user);
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### searchData({user, query})
|
|
274
|
+
|
|
275
|
+
Powerful search with relation expansion and filtering.
|
|
276
|
+
|
|
277
|
+
Query Options:
|
|
278
|
+
|
|
279
|
+
- model: Model name to search
|
|
280
|
+
- filter: MongoDB-style filter
|
|
281
|
+
- depth: Relation expansion depth (default: 1)
|
|
282
|
+
- limit/page: Pagination
|
|
283
|
+
- sort: Sorting criteria
|
|
284
|
+
|
|
285
|
+
Example:
|
|
286
|
+
```javascript
|
|
287
|
+
const results = await searchData({
|
|
288
|
+
user: currentUser,
|
|
289
|
+
query: {
|
|
290
|
+
model: "blogPost",
|
|
291
|
+
filter: { status: "published" },
|
|
292
|
+
depth: 2, // Expand author and comments
|
|
293
|
+
limit: 10,
|
|
294
|
+
sort: "createdAt:DESC"
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## Import/Export
|
|
300
|
+
### importData(options, files, user)
|
|
301
|
+
> Imports data from JSON/CSV files.
|
|
302
|
+
|
|
303
|
+
Supported Formats:
|
|
304
|
+
|
|
305
|
+
- JSON arrays
|
|
306
|
+
- JSON with model-keyed objects
|
|
307
|
+
- CSV with headers or field mapping
|
|
308
|
+
|
|
309
|
+
Example:
|
|
310
|
+
|
|
311
|
+
```javascript
|
|
312
|
+
const result = await importData(
|
|
313
|
+
{
|
|
314
|
+
model: "products",
|
|
315
|
+
hasHeaders: true
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
file: req.files.myFileField // from multipart body
|
|
319
|
+
},
|
|
320
|
+
currentUser
|
|
321
|
+
);
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### exportData(options, user)
|
|
325
|
+
> Exports data to a structured format.
|
|
326
|
+
|
|
327
|
+
Example:
|
|
328
|
+
|
|
329
|
+
```javascript
|
|
330
|
+
await exportData(
|
|
331
|
+
{
|
|
332
|
+
models: ["products", "categories"],
|
|
333
|
+
depth: 1, // Include relations
|
|
334
|
+
lang: "fr" // Localized data
|
|
335
|
+
},
|
|
336
|
+
currentUser
|
|
337
|
+
);
|
|
338
|
+
// Returns: { success: true, data: { products: [...], categories: [...] } }
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
## Backup & Restore
|
|
342
|
+
### dumpUserData(user)
|
|
343
|
+
> Creates an encrypted backup of user data.
|
|
344
|
+
|
|
345
|
+
Features:
|
|
346
|
+
|
|
347
|
+
- Automatic encryption
|
|
348
|
+
- S3 or local storage
|
|
349
|
+
- Retention policies by plan (daily/weekly/monthly)
|
|
350
|
+
|
|
351
|
+
Example:
|
|
352
|
+
|
|
353
|
+
```javascript
|
|
354
|
+
await dumpUserData(currentUser);
|
|
355
|
+
// Backup saved to S3 or ./backups/
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### loadFromDump(user, options)
|
|
359
|
+
|
|
360
|
+
> Restores user data from backup.
|
|
361
|
+
|
|
362
|
+
Options:
|
|
363
|
+
- modelsOnly: Restore only model definitions
|
|
364
|
+
|
|
365
|
+
Example:
|
|
366
|
+
|
|
367
|
+
```javascript
|
|
368
|
+
await loadFromDump(currentUser, { modelsOnly: false });
|
|
369
|
+
// Full restore including data
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
## Pack Management
|
|
373
|
+
|
|
374
|
+
### installPack(logger, packId, user, lang)
|
|
375
|
+
|
|
376
|
+
> Installs a predefined data pack.
|
|
377
|
+
|
|
378
|
+
Example:
|
|
379
|
+
|
|
380
|
+
```javascript
|
|
381
|
+
const result = await installPack(logger, "61d1f1a9e3f1a9e3f1a9e3f1", user, "en");
|
|
382
|
+
// Returns installation summary
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
|
|
197
386
|
---
|
|
198
387
|
|
|
199
388
|
## 📁 Project Structure
|
|
@@ -212,6 +401,34 @@ data-primals-engine/
|
|
|
212
401
|
└── server.js
|
|
213
402
|
```
|
|
214
403
|
|
|
404
|
+
## Workflows: Automate Your Business Logic
|
|
405
|
+
|
|
406
|
+
> Workflows are the automation engine of your application.
|
|
407
|
+
|
|
408
|
+
They allow you to define complex business processes that run in response to specific events, without writing custom code.
|
|
409
|
+
|
|
410
|
+
This is perfect for tasks like **sending welcome emails**, managing **order fulfillment**, or triggering data synchronization.
|
|
411
|
+
|
|
412
|
+
A workflow is composed of two main parts: **Triggers** and **Actions**.
|
|
413
|
+
|
|
414
|
+
> A 'workflowTrigger' is the event that initiates a workflow run.
|
|
415
|
+
- **DataAdded**: Fires when a new document is created (e.g., a new user signs up).
|
|
416
|
+
- **DataEdited**: Fires when a document is updated (e.g., an order status changes to "shipped").
|
|
417
|
+
- **DataDeleted**: Fires when a document is removed.
|
|
418
|
+
- **Scheduled**: Runs at a specific time or interval using a Cron expression (e.g., 0 0 * * * for a nightly data cleanup job).
|
|
419
|
+
- **Manual**: Triggered on-demand via an API call, allowing you to integrate workflows into any part of your application.
|
|
420
|
+
|
|
421
|
+
> A 'workflowAction' is the individual steps a workflow executes. You can chain them together to create sophisticated logic.
|
|
422
|
+
- **CreateData**: Create a new document in any model.
|
|
423
|
+
- **UpdateData**: Modify one or more existing documents that match a filter.
|
|
424
|
+
- **SendEmail**: Send a transactional email using dynamic data from the trigger.
|
|
425
|
+
- **CallWebhook**: Make an HTTP request (GET, POST, etc.) to an external service or API.
|
|
426
|
+
- **ExecuteScript**: Run a custom JavaScript snippet for complex logic, data transformation, or conditional branching.
|
|
427
|
+
- **GenerateAIContent**: Use an integrated AI provider (like OpenAI or Gemini) to generate text, summarize content, or make decisions.
|
|
428
|
+
- **Wait**: Pause the workflow for a specific duration before continuing to the next step
|
|
429
|
+
|
|
430
|
+
See the details of the workflow models for more details.
|
|
431
|
+
|
|
215
432
|
---
|
|
216
433
|
|
|
217
434
|
## 🤝 Contributing
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "data-primals-engine",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
4
4
|
"description": "data-primals-engine is the package responsible from handling large amount of data in a practical and performant way. It can handle large amount of data in a practical and performant way. It can also get workflow models working (for automation), and fully supports internationalisation.",
|
|
5
5
|
"main": "src/engine.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"scripts": {
|
|
8
|
+
"test": "cross-env MONGO_DB_URL=\"mongodb://localhost:27017\" PORT=7635 vitest --no-file-parallelism",
|
|
8
9
|
"preinstall": "npx force-resolutions",
|
|
9
10
|
"devserver": "cross-env NODE_ENV=development node server.js",
|
|
10
11
|
"server": "cross-env NODE_ENV=production node server.js",
|
|
@@ -39,11 +40,9 @@
|
|
|
39
40
|
"chalk": "^5.4.1",
|
|
40
41
|
"check-disk-space": "^3.4.0",
|
|
41
42
|
"compression": "^1.8.0",
|
|
42
|
-
"connect-mongo": "^5.1.0",
|
|
43
43
|
"cookie-parser": "^1.4.7",
|
|
44
44
|
"cronstrue": "^3.1.0",
|
|
45
45
|
"csv-parse": "^5.6.0",
|
|
46
|
-
"csv-parser": "^3.2.0",
|
|
47
46
|
"date-fns": "^4.1.0",
|
|
48
47
|
"express": "^4.21.2",
|
|
49
48
|
"express-csrf-double-submit-cookie": "^1.2.1",
|
|
@@ -61,7 +60,6 @@
|
|
|
61
60
|
"nodemailer": "^6.10.0",
|
|
62
61
|
"openai": "^5.8.2",
|
|
63
62
|
"randomcolor": "^0.6.2",
|
|
64
|
-
"rate-limiter-flexible": "^5.0.5",
|
|
65
63
|
"react-helmet": "^6.1.0",
|
|
66
64
|
"react-i18next": "^15.4.1",
|
|
67
65
|
"react-markdown": "^10.1.0",
|
|
@@ -70,10 +68,9 @@
|
|
|
70
68
|
"sirv": "^3.0.1",
|
|
71
69
|
"swagger-ui-express": "^5.0.1",
|
|
72
70
|
"tar": "^7.4.3",
|
|
73
|
-
"tar-fs": "^3.0.8",
|
|
74
71
|
"uniqid": "^5.4.0",
|
|
75
|
-
"
|
|
76
|
-
"
|
|
72
|
+
"vitest": "^3.2.3",
|
|
73
|
+
"yaml": "^2.8.0"
|
|
77
74
|
},
|
|
78
75
|
"devDependencies": {
|
|
79
76
|
"@eslint/js": "^9.17.0",
|
package/src/engine.js
CHANGED
|
@@ -17,7 +17,7 @@ import formidableMiddleware from 'express-formidable';
|
|
|
17
17
|
const isProduction = process.env.NODE_ENV === 'production'
|
|
18
18
|
|
|
19
19
|
// Connection URL
|
|
20
|
-
export const dbUrl = process.env.MONGO_DB_URL || 'mongodb://127.0.0.1:27017';
|
|
20
|
+
export const dbUrl = process.env.CI ? 'mongodb://mongodb:27017' : (process.env.MONGO_DB_URL || 'mongodb://127.0.0.1:27017');
|
|
21
21
|
export const MongoClient = new InternalMongoClient(dbUrl, { maxPoolSize: 20 });
|
|
22
22
|
|
|
23
23
|
// Database Name
|
|
@@ -87,7 +87,7 @@ export const Engine = {
|
|
|
87
87
|
}
|
|
88
88
|
};
|
|
89
89
|
|
|
90
|
-
await Promise.all(Config.Get('modules').map(async module => {
|
|
90
|
+
await Promise.all(Config.Get('modules', []).map(async module => {
|
|
91
91
|
try {
|
|
92
92
|
if( fs.existsSync(module)){
|
|
93
93
|
return await importModule(module);
|