@unchainedshop/platform 5.0.0-alpha.1 → 5.0.0-alpha.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/README.md +76 -106
- package/bin/unchained.js +164 -0
- package/lib/migrations/createMigrationRunner.js +3 -9
- package/lib/migrations/runMigrations.js +3 -8
- package/lib/setup/setupWorkqueue.js +15 -5
- package/lib/startPlatform.d.ts +3 -1
- package/lib/startPlatform.js +17 -1
- package/lib/templates/order-parser/getOrderPositionsData.js +1 -1
- package/lib/templates/order-parser/getOrderSummaryData.js +2 -2
- package/lib/templates/resolveErrorReportTemplate.js +2 -2
- package/package.json +19 -13
package/README.md
CHANGED
|
@@ -3,146 +3,116 @@
|
|
|
3
3
|
|
|
4
4
|
# @unchainedshop/platform
|
|
5
5
|
|
|
6
|
-
Umbrella package for the Unchained Engine.
|
|
6
|
+
Umbrella package for the Unchained Engine. Initializes the database, core modules and services, plugins, GraphQL server, message templates, and work queue.
|
|
7
7
|
|
|
8
8
|
## Installation
|
|
9
9
|
|
|
10
10
|
```bash
|
|
11
|
-
npm install @unchainedshop/platform
|
|
11
|
+
npm install @unchainedshop/platform express
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
##
|
|
14
|
+
## Quick start
|
|
15
|
+
|
|
16
|
+
Register plugins, initialize the platform, and connect it to your HTTP server:
|
|
15
17
|
|
|
16
18
|
```typescript
|
|
17
|
-
import { startPlatform } from '@unchainedshop/platform';
|
|
18
19
|
import express from 'express';
|
|
20
|
+
import { startPlatform } from '@unchainedshop/platform';
|
|
21
|
+
import { connect } from '@unchainedshop/api/express';
|
|
22
|
+
import { registerBasePlugins } from '@unchainedshop/plugins/presets/base';
|
|
19
23
|
|
|
20
|
-
|
|
24
|
+
registerBasePlugins();
|
|
21
25
|
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
// Platform options
|
|
26
|
-
},
|
|
27
|
-
});
|
|
26
|
+
const app = express();
|
|
27
|
+
const platform = await startPlatform({});
|
|
28
|
+
await connect(app, platform);
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
const products = await platform.unchainedAPI.modules.products.findProducts({});
|
|
30
31
|
app.listen(4010);
|
|
31
32
|
```
|
|
32
33
|
|
|
33
|
-
|
|
34
|
+
For Fastify, use `connect` from `@unchainedshop/api/fastify` with a Fastify instance. `connect()` mounts GraphQL, MCP, and registered plugin routes. Pass `{ adminUI: true }` as its third argument to serve an installed `@unchainedshop/admin-ui` package.
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
| Export | Description |
|
|
38
|
-
|--------|-------------|
|
|
39
|
-
| `startPlatform` | Initialize complete Unchained platform |
|
|
40
|
-
| `runMigrations` | Run database migrations |
|
|
41
|
-
| `printRuntimeConfiguration` | Log registered templates, events, and adapters |
|
|
42
|
-
|
|
43
|
-
### Context Helpers
|
|
44
|
-
|
|
45
|
-
| Export | Description |
|
|
46
|
-
|--------|-------------|
|
|
47
|
-
| `setAccessToken` | Set access token for user session |
|
|
48
|
-
| `getAccessToken` | Get current access token |
|
|
49
|
-
| `invalidateAccessToken` | Invalidate/logout access token |
|
|
50
|
-
|
|
51
|
-
### Templates
|
|
52
|
-
|
|
53
|
-
| Export | Description |
|
|
54
|
-
|--------|-------------|
|
|
55
|
-
| `MessageTypes` | Available message/notification types |
|
|
56
|
-
| `setupTemplates` | Register message templates |
|
|
57
|
-
|
|
58
|
-
### Message Types
|
|
59
|
-
|
|
60
|
-
| Type | Description |
|
|
61
|
-
|------|-------------|
|
|
62
|
-
| `ACCOUNT_ACTION` | Account verification, password reset |
|
|
63
|
-
| `DELIVERY` | Delivery notifications |
|
|
64
|
-
| `ORDER_CONFIRMATION` | Order confirmation emails |
|
|
65
|
-
| `ORDER_REJECTION` | Order rejection notifications |
|
|
66
|
-
| `QUOTATION_STATUS` | Quotation status updates |
|
|
67
|
-
| `ENROLLMENT_STATUS` | Subscription status updates |
|
|
68
|
-
| `FORWARD_DELIVERY` | Forward delivery notifications |
|
|
69
|
-
|
|
70
|
-
## Quick Start
|
|
71
|
-
|
|
72
|
-
```typescript
|
|
73
|
-
import express from 'express';
|
|
74
|
-
import { startPlatform, MessageTypes } from '@unchainedshop/platform';
|
|
75
|
-
|
|
76
|
-
const app = express();
|
|
77
|
-
|
|
78
|
-
const { unchainedAPI, graphqlHandler } = await startPlatform({
|
|
79
|
-
express: app,
|
|
80
|
-
options: {
|
|
81
|
-
modules: {
|
|
82
|
-
// Module-specific configuration
|
|
83
|
-
},
|
|
84
|
-
plugins: [
|
|
85
|
-
// Plugin imports
|
|
86
|
-
],
|
|
87
|
-
},
|
|
88
|
-
workQueueConfig: {
|
|
89
|
-
// Worker queue configuration
|
|
90
|
-
},
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
// Access unchained API
|
|
94
|
-
const products = await unchainedAPI.modules.products.findProducts({});
|
|
95
|
-
|
|
96
|
-
// Start server
|
|
97
|
-
app.use('/graphql', graphqlHandler);
|
|
98
|
-
app.listen(4010);
|
|
99
|
-
```
|
|
36
|
+
Before startup, set `ROOT_URL`, `EMAIL_WEBSITE_NAME`, `EMAIL_WEBSITE_URL`, `EMAIL_FROM`, and `UNCHAINED_TOKEN_SECRET` (at least 32 characters). Set `MONGO_URL` for an external MongoDB; otherwise the database package starts a local instance.
|
|
100
37
|
|
|
101
38
|
## Configuration
|
|
102
39
|
|
|
103
40
|
```typescript
|
|
104
41
|
const platform = await startPlatform({
|
|
105
|
-
express: app,
|
|
106
42
|
options: {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
// Order module options
|
|
110
|
-
},
|
|
111
|
-
products: {
|
|
112
|
-
// Product module options
|
|
113
|
-
},
|
|
114
|
-
},
|
|
115
|
-
services: {
|
|
116
|
-
// Custom services
|
|
43
|
+
orders: {
|
|
44
|
+
// Order module settings
|
|
117
45
|
},
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
// Custom import handlers
|
|
121
|
-
},
|
|
46
|
+
products: {
|
|
47
|
+
// Product module settings
|
|
122
48
|
},
|
|
123
49
|
},
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
50
|
+
modules: {
|
|
51
|
+
// Custom modules with a configure({ db, migrationRepository, options }) function
|
|
52
|
+
},
|
|
53
|
+
services: {
|
|
54
|
+
// Custom services
|
|
55
|
+
},
|
|
56
|
+
bulkImporter: {
|
|
57
|
+
handlers: {
|
|
58
|
+
// Custom import handlers
|
|
59
|
+
},
|
|
127
60
|
},
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
return {
|
|
131
|
-
...context,
|
|
132
|
-
// Custom context properties
|
|
133
|
-
};
|
|
61
|
+
workQueueOptions: {
|
|
62
|
+
skipInvalidationOnStartup: true,
|
|
134
63
|
},
|
|
64
|
+
context: (defaultResolver) => async (props, req, res) => ({
|
|
65
|
+
...(await defaultResolver(props, req, res)),
|
|
66
|
+
// Additional request context
|
|
67
|
+
}),
|
|
135
68
|
});
|
|
136
69
|
```
|
|
137
70
|
|
|
138
|
-
|
|
71
|
+
`PlatformOptions` combines core configuration with GraphQL server options, `rolesOptions`, `workQueueOptions`, and `auditLog`. Built-in modules are included automatically. Module settings are direct properties of `options`; module/service overrides and import/export handlers are top-level options.
|
|
72
|
+
|
|
73
|
+
The work queue runs migrations and starts queue managers during startup unless workers are disabled. See [work queue options](src/setup/setupWorkqueue.ts) and [platform initialization](src/startPlatform.ts) for the current options.
|
|
74
|
+
|
|
75
|
+
## Download documentation for coding assistants
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npx --package @unchainedshop/platform unchained download-llm-docs
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Requires Git. Downloads docs into `.unchained-docs/` for the installed platform
|
|
82
|
+
version's tag, falling back to `master` when the tag is unavailable. Updates a
|
|
83
|
+
managed file index in existing `CLAUDE.md` and `AGENTS.md` files (legacy `agents.md`
|
|
84
|
+
is also supported), or creates `CLAUDE.md` if none exists. Re-running replaces the
|
|
85
|
+
downloaded docs and index while preserving surrounding instructions. Add
|
|
86
|
+
`.unchained-docs/` to your project's `.gitignore`.
|
|
87
|
+
|
|
88
|
+
## Exports
|
|
89
|
+
|
|
90
|
+
| Export | Description |
|
|
91
|
+
|--------|-------------|
|
|
92
|
+
| `startPlatform` | Initialize the platform |
|
|
93
|
+
| `PlatformOptions` | Platform configuration type |
|
|
94
|
+
| `runMigrations` | Run registered database migrations |
|
|
95
|
+
| `printRuntimeConfiguration` | Log registered templates, events, and adapters |
|
|
96
|
+
| `MessageTypes` | Built-in message type constants |
|
|
97
|
+
|
|
98
|
+
The package also exports the [built-in template resolvers](src/templates/index.ts). Platform startup registers them automatically; custom templates use `MessagingDirector.registerTemplate()` from `@unchainedshop/core`.
|
|
99
|
+
|
|
100
|
+
| Message type | Purpose |
|
|
101
|
+
|--------------|---------|
|
|
102
|
+
| `ACCOUNT_ACTION` | Email verification and password reset |
|
|
103
|
+
| `DELIVERY` | Forward delivery notifications |
|
|
104
|
+
| `ORDER_CONFIRMATION` | Order confirmations |
|
|
105
|
+
| `ORDER_REJECTION` | Order rejections |
|
|
106
|
+
| `QUOTATION_STATUS` | Quotation status updates |
|
|
107
|
+
| `ENROLLMENT_STATUS` | Enrollment status updates |
|
|
108
|
+
| `ERROR_REPORT` | Error reports |
|
|
139
109
|
|
|
140
|
-
|
|
110
|
+
## Return value
|
|
141
111
|
|
|
142
112
|
| Property | Description |
|
|
143
113
|
|----------|-------------|
|
|
144
|
-
| `unchainedAPI` |
|
|
145
|
-
| `graphqlHandler` | GraphQL Yoga server instance
|
|
114
|
+
| `unchainedAPI` | Core modules, services, bulk importer/exporter, and options |
|
|
115
|
+
| `graphqlHandler` | GraphQL Yoga server instance |
|
|
146
116
|
| `db` | MongoDB database instance |
|
|
147
117
|
|
|
148
118
|
## License
|
package/bin/unchained.js
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { execFileSync } from 'node:child_process';
|
|
4
|
+
import {
|
|
5
|
+
cpSync,
|
|
6
|
+
existsSync,
|
|
7
|
+
mkdtempSync,
|
|
8
|
+
readFileSync,
|
|
9
|
+
readdirSync,
|
|
10
|
+
rmSync,
|
|
11
|
+
writeFileSync,
|
|
12
|
+
} from 'node:fs';
|
|
13
|
+
import { join, relative } from 'node:path';
|
|
14
|
+
import { tmpdir } from 'node:os';
|
|
15
|
+
import { createRequire } from 'node:module';
|
|
16
|
+
|
|
17
|
+
const GITHUB_REPO = 'https://github.com/unchainedshop/unchained.git';
|
|
18
|
+
const DOCS_SUBPATH = 'docs/docs';
|
|
19
|
+
const DOCS_DIR = join(process.cwd(), '.unchained-docs');
|
|
20
|
+
const INDEX_START = '<!-- UNCHAINED_DOCS_INDEX:BEGIN -->';
|
|
21
|
+
const INDEX_END = '<!-- UNCHAINED_DOCS_INDEX:END -->';
|
|
22
|
+
|
|
23
|
+
const command = process.argv[2];
|
|
24
|
+
|
|
25
|
+
if (command !== 'download-llm-docs') {
|
|
26
|
+
console.log(`Usage: unchained <command>
|
|
27
|
+
|
|
28
|
+
Commands:
|
|
29
|
+
download-llm-docs Download Unchained Engine docs for LLM coding assistants
|
|
30
|
+
(Claude Code, Cursor, etc.) and inject a file index into
|
|
31
|
+
CLAUDE.md and/or AGENTS.md`);
|
|
32
|
+
process.exit(command ? 1 : 0);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Resolve version from own package.json
|
|
36
|
+
const require = createRequire(import.meta.url);
|
|
37
|
+
const pkg = require('../package.json');
|
|
38
|
+
const version = pkg.version;
|
|
39
|
+
|
|
40
|
+
// Determine git ref: try tag first, fallback to master
|
|
41
|
+
function tagExists(tag) {
|
|
42
|
+
try {
|
|
43
|
+
execFileSync('git', ['ls-remote', '--tags', '--exit-code', GITHUB_REPO, `refs/tags/${tag}`], {
|
|
44
|
+
stdio: 'pipe',
|
|
45
|
+
});
|
|
46
|
+
return true;
|
|
47
|
+
} catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const tag = `v${version}`;
|
|
53
|
+
const ref = tagExists(tag) ? tag : 'master';
|
|
54
|
+
if (ref === 'master') {
|
|
55
|
+
console.log(`Tag ${tag} not found on remote, falling back to master branch.`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Clone docs using sparse-checkout into a temp directory
|
|
59
|
+
const tempDir = mkdtempSync(join(tmpdir(), 'unchained-docs-'));
|
|
60
|
+
console.log(`Cloning docs from ${ref} using sparse-checkout ...`);
|
|
61
|
+
try {
|
|
62
|
+
execFileSync(
|
|
63
|
+
'git',
|
|
64
|
+
[
|
|
65
|
+
'clone',
|
|
66
|
+
'--filter=blob:none',
|
|
67
|
+
'--no-checkout',
|
|
68
|
+
'--depth',
|
|
69
|
+
'1',
|
|
70
|
+
'--branch',
|
|
71
|
+
ref,
|
|
72
|
+
GITHUB_REPO,
|
|
73
|
+
tempDir,
|
|
74
|
+
],
|
|
75
|
+
{ stdio: 'inherit' },
|
|
76
|
+
);
|
|
77
|
+
execFileSync('git', ['-C', tempDir, 'sparse-checkout', 'set', DOCS_SUBPATH], {
|
|
78
|
+
stdio: 'inherit',
|
|
79
|
+
});
|
|
80
|
+
execFileSync('git', ['-C', tempDir, 'checkout'], { stdio: 'inherit' });
|
|
81
|
+
} catch {
|
|
82
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
83
|
+
console.error('Failed to clone docs repository.');
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Copy docs/docs/ contents to .unchained-docs/
|
|
88
|
+
const sourceDir = join(tempDir, DOCS_SUBPATH);
|
|
89
|
+
if (!existsSync(sourceDir)) {
|
|
90
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
91
|
+
console.error(`Docs directory not found at ${DOCS_SUBPATH} in the repository.`);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
rmSync(DOCS_DIR, { recursive: true, force: true });
|
|
96
|
+
cpSync(sourceDir, DOCS_DIR, { recursive: true });
|
|
97
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
98
|
+
|
|
99
|
+
// Scan for markdown files
|
|
100
|
+
const markdownsByDir = Map.groupBy(
|
|
101
|
+
readdirSync(DOCS_DIR, { withFileTypes: true, recursive: true }).filter(
|
|
102
|
+
(e) => e.isFile() && (e.name.endsWith('.md') || e.name.endsWith('.mdx')),
|
|
103
|
+
),
|
|
104
|
+
(e) => relative(DOCS_DIR, e.parentPath) || '.',
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const fileCount = [...markdownsByDir.values()].flat().length;
|
|
108
|
+
console.log(`Found ${fileCount} markdown files in .unchained-docs/`);
|
|
109
|
+
|
|
110
|
+
// Build compact pipe-delimited index
|
|
111
|
+
const indexSnippet = [
|
|
112
|
+
INDEX_START,
|
|
113
|
+
[
|
|
114
|
+
`[Unchained Engine v${version} Docs Index]`,
|
|
115
|
+
'root: ./.unchained-docs',
|
|
116
|
+
'Search these docs before answering Unchained questions.',
|
|
117
|
+
'If docs missing, run: npx --package @unchainedshop/platform unchained download-llm-docs',
|
|
118
|
+
...[...markdownsByDir]
|
|
119
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
120
|
+
.map(
|
|
121
|
+
([dir, entries]) =>
|
|
122
|
+
`${dir}:{${entries
|
|
123
|
+
.map((e) => e.name)
|
|
124
|
+
.sort()
|
|
125
|
+
.join(',')}}`,
|
|
126
|
+
),
|
|
127
|
+
].join('|'),
|
|
128
|
+
INDEX_END,
|
|
129
|
+
].join('');
|
|
130
|
+
|
|
131
|
+
// Upsert index into target files
|
|
132
|
+
function upsertIndex(filePath) {
|
|
133
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
134
|
+
const [before, ...afterParts] = content.split(INDEX_START);
|
|
135
|
+
const after =
|
|
136
|
+
afterParts.length > 0
|
|
137
|
+
? afterParts.join(INDEX_START).split(INDEX_END).slice(1).join(INDEX_END)
|
|
138
|
+
: undefined;
|
|
139
|
+
|
|
140
|
+
writeFileSync(
|
|
141
|
+
filePath,
|
|
142
|
+
after !== undefined ? before + indexSnippet + after : `${content.trimEnd()}\n${indexSnippet}\n`,
|
|
143
|
+
);
|
|
144
|
+
console.log(`Updated ${relative(process.cwd(), filePath)} with docs index.`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const claudeMdPath = join(process.cwd(), 'CLAUDE.md');
|
|
148
|
+
const agentsMdPath = join(process.cwd(), 'AGENTS.md');
|
|
149
|
+
const legacyAgentsMdPath = join(process.cwd(), 'agents.md');
|
|
150
|
+
const claudeExists = existsSync(claudeMdPath);
|
|
151
|
+
const agentsExists = existsSync(agentsMdPath);
|
|
152
|
+
const legacyAgentsExists = !agentsExists && existsSync(legacyAgentsMdPath);
|
|
153
|
+
|
|
154
|
+
if (claudeExists) upsertIndex(claudeMdPath);
|
|
155
|
+
if (agentsExists) upsertIndex(agentsMdPath);
|
|
156
|
+
if (legacyAgentsExists) upsertIndex(legacyAgentsMdPath);
|
|
157
|
+
|
|
158
|
+
if (!claudeExists && !agentsExists && !legacyAgentsExists) {
|
|
159
|
+
writeFileSync(claudeMdPath, `${indexSnippet}\n`);
|
|
160
|
+
console.log('Created CLAUDE.md with docs index.');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Remind about .gitignore
|
|
164
|
+
console.log('\nDone! Remember to add .unchained-docs to your .gitignore.');
|
|
@@ -35,14 +35,8 @@ export const createMigrationRunner = ({ currentId, logger = console, migrationRe
|
|
|
35
35
|
.map(this.operationFactory('up'));
|
|
36
36
|
},
|
|
37
37
|
async run() {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
return [mostRecentId, migrations.length];
|
|
42
|
-
}
|
|
43
|
-
catch (e) {
|
|
44
|
-
logger.error(`Migration failed: ${e.name} - ${e.message}`);
|
|
45
|
-
return [e.migrationId, null];
|
|
46
|
-
}
|
|
38
|
+
const migrations = this.migrateToLatest();
|
|
39
|
+
const mostRecentId = await migrations.reduce((idPromise, migration) => Promise.resolve(idPromise).then(migration), currentId);
|
|
40
|
+
return [mostRecentId, migrations.length];
|
|
47
41
|
},
|
|
48
42
|
});
|
|
@@ -32,15 +32,10 @@ export const runMigrations = async ({ migrationRepository, logger = createLogger
|
|
|
32
32
|
unchainedAPI,
|
|
33
33
|
});
|
|
34
34
|
const [lastMigrationId, operationCount] = await runner.run();
|
|
35
|
-
if (operationCount
|
|
36
|
-
|
|
37
|
-
logger.info(`All ${operationCount} migrations completed with most recent id: ${lastMigrationId}`);
|
|
38
|
-
}
|
|
39
|
-
else {
|
|
40
|
-
logger.debug(`No migrations run, already at latest id: ${currentId}`);
|
|
41
|
-
}
|
|
35
|
+
if (operationCount > 0) {
|
|
36
|
+
logger.info(`All ${operationCount} migrations completed with most recent id: ${lastMigrationId}`);
|
|
42
37
|
}
|
|
43
38
|
else {
|
|
44
|
-
logger.
|
|
39
|
+
logger.debug(`No migrations run, already at latest id: ${currentId}`);
|
|
45
40
|
}
|
|
46
41
|
};
|
|
@@ -12,17 +12,27 @@ export const queueWorkers = [];
|
|
|
12
12
|
export async function setupWorkqueue({ unchainedAPI, migrationRepository, ...workQueueOptions }) {
|
|
13
13
|
if (workQueueOptions.disableWorker || UNCHAINED_DISABLE_WORKER)
|
|
14
14
|
return;
|
|
15
|
-
|
|
15
|
+
try {
|
|
16
|
+
await runMigrations({ migrationRepository, unchainedAPI });
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
logger.error('Migration failed; continuing startup', {
|
|
20
|
+
error: error instanceof Error ? error.message : String(error),
|
|
21
|
+
});
|
|
22
|
+
}
|
|
16
23
|
(workQueueOptions?.enabledQueueManagers || defaultQueueManagers).forEach((f) => {
|
|
17
24
|
const handler = f.actions(workQueueOptions, unchainedAPI);
|
|
18
25
|
queueWorkers.push(handler);
|
|
19
26
|
handler.start();
|
|
20
27
|
});
|
|
21
28
|
if (workQueueOptions.invalidateProviders ?? !UNCHAINED_DISABLE_PROVIDER_INVALIDATION) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
29
|
+
await unchainedAPI.modules.worker.addWorkIfNotExists({
|
|
30
|
+
type: 'INVALIDATE_CARTS',
|
|
31
|
+
input: workQueueOptions.providerInvalidationMaxAgeDays !== undefined
|
|
32
|
+
? { maxAgeDays: workQueueOptions.providerInvalidationMaxAgeDays }
|
|
33
|
+
: {},
|
|
34
|
+
retries: 0,
|
|
35
|
+
}, (work) => !work.autoscheduled);
|
|
26
36
|
}
|
|
27
37
|
if (workQueueOptions.assignCartForUsers ?? Boolean(UNCHAINED_ASSIGN_CART_FOR_USERS)) {
|
|
28
38
|
const users = await unchainedAPI.modules.users.findUsers({});
|
package/lib/startPlatform.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { type UnchainedServerOptions } from '@unchainedshop/api';
|
|
|
2
2
|
import { type UnchainedCoreOptions } from '@unchainedshop/core';
|
|
3
3
|
import { mongodb } from '@unchainedshop/mongodb';
|
|
4
4
|
import type { UnchainedCore } from '@unchainedshop/core';
|
|
5
|
+
import { type AuditLogOptions } from '@unchainedshop/events';
|
|
5
6
|
import { MessageTypes } from './setup/setupTemplates.ts';
|
|
6
7
|
import { type SetupWorkqueueOptions } from './setup/setupWorkqueue.ts';
|
|
7
8
|
import type { IRoleOptionConfig } from '@unchainedshop/roles';
|
|
@@ -9,8 +10,9 @@ export { MessageTypes };
|
|
|
9
10
|
export type PlatformOptions = {
|
|
10
11
|
rolesOptions?: IRoleOptionConfig;
|
|
11
12
|
workQueueOptions?: SetupWorkqueueOptions;
|
|
13
|
+
auditLog?: AuditLogOptions | false;
|
|
12
14
|
} & Omit<UnchainedCoreOptions, 'migrationRepository' | 'db'> & Omit<UnchainedServerOptions, 'roles' | 'unchainedAPI'>;
|
|
13
|
-
export declare const startPlatform: ({ modules, services, options, rolesOptions, bulkImporter, bulkExporter, workQueueOptions, ...arbitraryAPIServerConfiguration }: PlatformOptions) => Promise<{
|
|
15
|
+
export declare const startPlatform: ({ modules, services, options, rolesOptions, bulkImporter, bulkExporter, workQueueOptions, auditLog: auditLogConfig, ...arbitraryAPIServerConfiguration }: PlatformOptions) => Promise<{
|
|
14
16
|
unchainedAPI: UnchainedCore;
|
|
15
17
|
graphqlHandler: any;
|
|
16
18
|
db: mongodb.Db;
|
package/lib/startPlatform.js
CHANGED
|
@@ -2,6 +2,8 @@ import { startAPIServer, roles } from '@unchainedshop/api';
|
|
|
2
2
|
import { initCore, pluginRegistry } from '@unchainedshop/core';
|
|
3
3
|
import { initDb, mongodb, stopDb } from '@unchainedshop/mongodb';
|
|
4
4
|
import { defaultLogger } from '@unchainedshop/logger';
|
|
5
|
+
import { getEmitAdapter } from '@unchainedshop/events';
|
|
6
|
+
import { createAuditLog, configureAuditIntegration, setAuditLogInstance, } from '@unchainedshop/events';
|
|
5
7
|
import { setupAccounts } from "./setup/setupAccounts.js";
|
|
6
8
|
import { setupUploadHandlers } from "./setup/setupUploadHandlers.js";
|
|
7
9
|
import { setupTemplates, MessageTypes } from "./setup/setupTemplates.js";
|
|
@@ -30,7 +32,7 @@ const existOnInvalidEnvironmentVariables = () => {
|
|
|
30
32
|
process.exit(1);
|
|
31
33
|
}
|
|
32
34
|
};
|
|
33
|
-
export const startPlatform = async ({ modules, services, options, rolesOptions, bulkImporter, bulkExporter, workQueueOptions, ...arbitraryAPIServerConfiguration }) => {
|
|
35
|
+
export const startPlatform = async ({ modules, services, options, rolesOptions, bulkImporter, bulkExporter, workQueueOptions, auditLog: auditLogConfig, ...arbitraryAPIServerConfiguration }) => {
|
|
34
36
|
const start = performance.now();
|
|
35
37
|
exitOnMissingEnvironmentVariables();
|
|
36
38
|
existOnInvalidEnvironmentVariables();
|
|
@@ -58,6 +60,11 @@ export const startPlatform = async ({ modules, services, options, rolesOptions,
|
|
|
58
60
|
bulkExporter,
|
|
59
61
|
});
|
|
60
62
|
await pluginRegistry.initialize(unchainedAPI);
|
|
63
|
+
let auditLog;
|
|
64
|
+
if (auditLogConfig !== false) {
|
|
65
|
+
auditLog = createAuditLog(auditLogConfig || {});
|
|
66
|
+
setAuditLogInstance(auditLog);
|
|
67
|
+
}
|
|
61
68
|
setupAccounts(unchainedAPI);
|
|
62
69
|
setupTemplates(unchainedAPI);
|
|
63
70
|
setupUploadHandlers(unchainedAPI);
|
|
@@ -66,6 +73,9 @@ export const startPlatform = async ({ modules, services, options, rolesOptions,
|
|
|
66
73
|
roles: configuredRoles,
|
|
67
74
|
...arbitraryAPIServerConfiguration,
|
|
68
75
|
});
|
|
76
|
+
if (auditLog) {
|
|
77
|
+
configureAuditIntegration(auditLog);
|
|
78
|
+
}
|
|
69
79
|
await setupWorkqueue({
|
|
70
80
|
unchainedAPI,
|
|
71
81
|
migrationRepository,
|
|
@@ -91,8 +101,14 @@ export const startPlatform = async ({ modules, services, options, rolesOptions,
|
|
|
91
101
|
stopWorkqueue();
|
|
92
102
|
defaultLogger.debug('Shutting down plugins', { signal });
|
|
93
103
|
await pluginRegistry.shutdown(unchainedAPI);
|
|
104
|
+
defaultLogger.debug('Shutting down event emitter', { signal });
|
|
105
|
+
await getEmitAdapter()?.shutdown?.();
|
|
94
106
|
defaultLogger.debug('Stopping GraphQL server', { signal });
|
|
95
107
|
await graphqlHandler.dispose();
|
|
108
|
+
if (auditLog) {
|
|
109
|
+
defaultLogger.debug('Closing audit log', { signal });
|
|
110
|
+
await auditLog.close();
|
|
111
|
+
}
|
|
96
112
|
defaultLogger.debug('Stopping DB Connection', { signal });
|
|
97
113
|
await stopDb();
|
|
98
114
|
defaultLogger.debug(`Unchained Engine exiting gracefully`, { signal, version });
|
|
@@ -2,7 +2,7 @@ import { ProductPricingSheet } from '@unchainedshop/core';
|
|
|
2
2
|
import { ch } from '@unchainedshop/utils';
|
|
3
3
|
export const getOrderPositionsData = async (order, params, context) => {
|
|
4
4
|
const { modules } = context;
|
|
5
|
-
const { useNetPrice, format = ch.priceToString } = params || {};
|
|
5
|
+
const { useNetPrice, format = (price) => ch.priceToString({ ...price, locale: params?.locale }) } = params || {};
|
|
6
6
|
const orderPositions = await modules.orders.positions.findOrderPositions({
|
|
7
7
|
orderId: order._id,
|
|
8
8
|
});
|
|
@@ -2,7 +2,7 @@ import { OrderPricingSheet, OrderPricingRowCategory } from '@unchainedshop/core'
|
|
|
2
2
|
import { ch } from '@unchainedshop/utils';
|
|
3
3
|
export const getOrderSummaryData = async (order, params, context) => {
|
|
4
4
|
const { modules } = context;
|
|
5
|
-
const { useNetPrice, format = ch.priceToString } = params || {};
|
|
5
|
+
const { useNetPrice, format = (price) => ch.priceToString({ ...price, locale: params?.locale }) } = params || {};
|
|
6
6
|
const orderDelivery = order.deliveryId &&
|
|
7
7
|
(await modules.orders.deliveries.findDelivery({
|
|
8
8
|
orderDeliveryId: order.deliveryId,
|
|
@@ -21,7 +21,7 @@ export const getOrderSummaryData = async (order, params, context) => {
|
|
|
21
21
|
const deliveryProvider = await modules.delivery.findProvider({
|
|
22
22
|
deliveryProviderId: orderDelivery.deliveryProviderId,
|
|
23
23
|
});
|
|
24
|
-
const deliveryAddress = ch.addressToString(orderDelivery?.context?.deliveryAddress || order.billingAddress);
|
|
24
|
+
const deliveryAddress = ch.addressToString(orderDelivery?.context?.address || orderDelivery?.context?.deliveryAddress || order.billingAddress);
|
|
25
25
|
const billingAddress = ch.addressToString(order.billingAddress);
|
|
26
26
|
const orderPricing = OrderPricingSheet({
|
|
27
27
|
calculation: order.calculation,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { safeStringify } from '@unchainedshop/logger';
|
|
2
2
|
const { EMAIL_FROM = 'noreply@unchained.local', EMAIL_ERROR_REPORT_RECIPIENT = 'support@unchained.local', EMAIL_WEBSITE_NAME = 'Unchained', ROOT_URL, } = process.env;
|
|
3
3
|
const formatWorkItems = (workItems) => {
|
|
4
4
|
return workItems
|
|
5
5
|
.map(({ _id, type, started, error }) => {
|
|
6
|
-
const stringifiedErrors =
|
|
6
|
+
const stringifiedErrors = safeStringify(error, null, 2);
|
|
7
7
|
return `${new Date(started).toLocaleString()} ${type} (${_id}): ${stringifiedErrors}`;
|
|
8
8
|
})
|
|
9
9
|
.join('\n');
|
package/package.json
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unchainedshop/platform",
|
|
3
3
|
"description": "Complete e-commerce platform bundle for the Unchained Engine",
|
|
4
|
-
"version": "5.0.0-alpha.
|
|
4
|
+
"version": "5.0.0-alpha.10",
|
|
5
|
+
"files": [
|
|
6
|
+
"lib",
|
|
7
|
+
"bin"
|
|
8
|
+
],
|
|
5
9
|
"main": "lib/platform-index.js",
|
|
6
10
|
"types": "lib/platform-index.d.ts",
|
|
7
11
|
"type": "module",
|
|
12
|
+
"bin": {
|
|
13
|
+
"unchained": "./bin/unchained.js"
|
|
14
|
+
},
|
|
8
15
|
"scripts": {
|
|
9
16
|
"clean": "tsc -b --clean",
|
|
10
17
|
"build": "tsc -b",
|
|
@@ -34,22 +41,21 @@
|
|
|
34
41
|
},
|
|
35
42
|
"homepage": "https://github.com/unchainedshop/unchained#readme",
|
|
36
43
|
"engines": {
|
|
37
|
-
"node": ">=
|
|
44
|
+
"node": ">=26.0.0",
|
|
38
45
|
"npm": ">=10.0.0"
|
|
39
46
|
},
|
|
40
47
|
"dependencies": {
|
|
41
|
-
"@unchainedshop/api": "^5.0.0-alpha.
|
|
42
|
-
"@unchainedshop/core": "^5.0.0-alpha.
|
|
43
|
-
"@unchainedshop/events": "^5.0.0-alpha.
|
|
44
|
-
"@unchainedshop/logger": "^5.0.0-alpha.
|
|
45
|
-
"@unchainedshop/mongodb": "^5.0.0-alpha.
|
|
46
|
-
"@unchainedshop/plugins": "^5.0.0-alpha.
|
|
47
|
-
"@unchainedshop/roles": "^5.0.0-alpha.
|
|
48
|
-
"@unchainedshop/utils": "^5.0.0-alpha.
|
|
49
|
-
"safe-stable-stringify": "^2.5.0"
|
|
48
|
+
"@unchainedshop/api": "^5.0.0-alpha.10",
|
|
49
|
+
"@unchainedshop/core": "^5.0.0-alpha.10",
|
|
50
|
+
"@unchainedshop/events": "^5.0.0-alpha.10",
|
|
51
|
+
"@unchainedshop/logger": "^5.0.0-alpha.10",
|
|
52
|
+
"@unchainedshop/mongodb": "^5.0.0-alpha.10",
|
|
53
|
+
"@unchainedshop/plugins": "^5.0.0-alpha.10",
|
|
54
|
+
"@unchainedshop/roles": "^5.0.0-alpha.10",
|
|
55
|
+
"@unchainedshop/utils": "^5.0.0-alpha.10"
|
|
50
56
|
},
|
|
51
57
|
"devDependencies": {
|
|
52
|
-
"@types/node": "^
|
|
53
|
-
"typescript": "^
|
|
58
|
+
"@types/node": "^26.2.0",
|
|
59
|
+
"typescript": "^6.0.3"
|
|
54
60
|
}
|
|
55
61
|
}
|