@ossy/platform 3.7.0 → 3.8.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.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ossy/platform",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.8.0",
|
|
4
4
|
"description": "Ossy application server runtime",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -45,16 +45,16 @@
|
|
|
45
45
|
"@aws-sdk/util-format-url": "^3.972.17",
|
|
46
46
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
47
47
|
"@ossy/config": "^3.0.9",
|
|
48
|
-
"@ossy/event-store": "^3.
|
|
48
|
+
"@ossy/event-store": "^3.8.0",
|
|
49
49
|
"@ossy/locale": "^3.4.0",
|
|
50
|
-
"@ossy/manifest": "^3.
|
|
50
|
+
"@ossy/manifest": "^3.8.0",
|
|
51
51
|
"@ossy/observability": "^3.0.9",
|
|
52
52
|
"@ossy/policies": "^3.0.9",
|
|
53
|
-
"@ossy/schema": "^3.
|
|
53
|
+
"@ossy/schema": "^3.8.0",
|
|
54
54
|
"@ossy/sdk": "^3.5.0",
|
|
55
55
|
"@ossy/tokens": "^3.5.0",
|
|
56
|
-
"@ossy/users": "^3.
|
|
57
|
-
"@ossy/workspaces": "^3.
|
|
56
|
+
"@ossy/users": "^3.8.0",
|
|
57
|
+
"@ossy/workspaces": "^3.8.0",
|
|
58
58
|
"cookie-parser": "^1.4.7",
|
|
59
59
|
"dotenv": ">=16.0.0 <18.0.0",
|
|
60
60
|
"express": ">=5.0.0 <6.0.0",
|
|
@@ -75,5 +75,5 @@
|
|
|
75
75
|
"Dockerfile",
|
|
76
76
|
"docker-healthcheck.js"
|
|
77
77
|
],
|
|
78
|
-
"gitHead": "
|
|
78
|
+
"gitHead": "14548dfc2019e7258e8c75db527acbf66fe829bd"
|
|
79
79
|
}
|
|
@@ -15,6 +15,15 @@ import { rejectOnAbort } from '../request-diagnostics.js'
|
|
|
15
15
|
|
|
16
16
|
const log = createLogger('platform/task-run')
|
|
17
17
|
|
|
18
|
+
/** Workspace scope must be on every TaskRun event so list projections can Apply it. */
|
|
19
|
+
function taskRunScopePayload ({ taskId, workspaceId }) {
|
|
20
|
+
return {
|
|
21
|
+
taskId,
|
|
22
|
+
moduleId: moduleIdFromTaskId(taskId),
|
|
23
|
+
belongsTo: workspaceId,
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
18
27
|
function assertNotAborted (context) {
|
|
19
28
|
if (context.signal?.aborted) {
|
|
20
29
|
throw context.signal.reason instanceof Error
|
|
@@ -91,6 +100,8 @@ export const TaskRunService = {
|
|
|
91
100
|
const durationMs = Date.now() - startMs
|
|
92
101
|
await TaskRunService._appendCompleted({
|
|
93
102
|
runId,
|
|
103
|
+
taskId,
|
|
104
|
+
workspaceId,
|
|
94
105
|
actorId,
|
|
95
106
|
durationMs,
|
|
96
107
|
resultSummary: summarizeResult(result),
|
|
@@ -114,6 +125,8 @@ export const TaskRunService = {
|
|
|
114
125
|
const durationMs = Date.now() - startMs
|
|
115
126
|
await TaskRunService._appendFailed({
|
|
116
127
|
runId,
|
|
128
|
+
taskId,
|
|
129
|
+
workspaceId,
|
|
117
130
|
actorId,
|
|
118
131
|
durationMs,
|
|
119
132
|
error: summarizeError(error),
|
|
@@ -180,26 +193,34 @@ export const TaskRunService = {
|
|
|
180
193
|
}
|
|
181
194
|
},
|
|
182
195
|
|
|
183
|
-
async _appendCompleted ({ runId, actorId, durationMs, resultSummary }) {
|
|
196
|
+
async _appendCompleted ({ runId, taskId, workspaceId, actorId, durationMs, resultSummary }) {
|
|
184
197
|
try {
|
|
185
198
|
await Aggregate.Of(TaskRun, runId)
|
|
186
199
|
.then(Aggregate.Add({
|
|
187
200
|
event: 'Completed',
|
|
188
201
|
createdBy: actorId ?? undefined,
|
|
189
|
-
payload: {
|
|
202
|
+
payload: {
|
|
203
|
+
durationMs,
|
|
204
|
+
resultSummary,
|
|
205
|
+
...taskRunScopePayload({ taskId, workspaceId }),
|
|
206
|
+
},
|
|
190
207
|
}))
|
|
191
208
|
} catch (err) {
|
|
192
209
|
log.warn('[TaskRunService] Failed to record Completed', { runId }, err)
|
|
193
210
|
}
|
|
194
211
|
},
|
|
195
212
|
|
|
196
|
-
async _appendFailed ({ runId, actorId, durationMs, error }) {
|
|
213
|
+
async _appendFailed ({ runId, taskId, workspaceId, actorId, durationMs, error }) {
|
|
197
214
|
try {
|
|
198
215
|
await Aggregate.Of(TaskRun, runId)
|
|
199
216
|
.then(Aggregate.Add({
|
|
200
217
|
event: 'Failed',
|
|
201
218
|
createdBy: actorId ?? undefined,
|
|
202
|
-
payload: {
|
|
219
|
+
payload: {
|
|
220
|
+
durationMs,
|
|
221
|
+
error,
|
|
222
|
+
...taskRunScopePayload({ taskId, workspaceId }),
|
|
223
|
+
},
|
|
203
224
|
}))
|
|
204
225
|
} catch (err) {
|
|
205
226
|
log.warn('[TaskRunService] Failed to record Failed', { runId }, err)
|
package/src/server.js
CHANGED
|
@@ -495,6 +495,8 @@ export async function startServer (options = {}) {
|
|
|
495
495
|
} = resolvePageLayoutRender(layoutsById, pageEntry)
|
|
496
496
|
const { language, messages, fallbackMessages } = resolveRequestLocale(pageRouter, requestUrl, buildDir, config)
|
|
497
497
|
let schemasForPage = cloneSerializable(manifest.schemas || [])
|
|
498
|
+
let ssrWorkspaceName
|
|
499
|
+
let ssrWorkspaceServices
|
|
498
500
|
if (req.workspaceId && loadWorkspaceForEntitlements) {
|
|
499
501
|
const workspaceTimer = createTimedOperation(`SSR loadWorkspace ${req.workspaceId}`, { log })
|
|
500
502
|
try {
|
|
@@ -506,6 +508,8 @@ export async function startServer (options = {}) {
|
|
|
506
508
|
cloneSerializable(workspace.schemas),
|
|
507
509
|
)
|
|
508
510
|
}
|
|
511
|
+
ssrWorkspaceName = workspace?.name
|
|
512
|
+
ssrWorkspaceServices = workspace?.services
|
|
509
513
|
} catch (err) {
|
|
510
514
|
workspaceTimer.finish({
|
|
511
515
|
pageId: pageRoute.id,
|
|
@@ -527,6 +531,9 @@ export async function startServer (options = {}) {
|
|
|
527
531
|
const props = {
|
|
528
532
|
...config,
|
|
529
533
|
...(req.userAppSettings || {}),
|
|
534
|
+
...(req.workspaceId ? { workspaceId: req.workspaceId } : {}),
|
|
535
|
+
...(ssrWorkspaceName != null ? { workspaceName: ssrWorkspaceName } : {}),
|
|
536
|
+
...(ssrWorkspaceServices != null ? { workspaceServices: cloneSerializable(ssrWorkspaceServices) } : {}),
|
|
530
537
|
theme: req.userAppSettings?.theme || cloneSerializable(config?.theme),
|
|
531
538
|
themes: cloneSerializable(config?.themes),
|
|
532
539
|
schemas: schemasForPage,
|
|
@@ -6,6 +6,24 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
const KEY_PATTERN = /^[A-Za-z0-9_-]+(:[A-Za-z0-9_-]+)?$/
|
|
9
|
+
const RESOURCE_ID_PATTERN = /^[A-Za-z0-9_-]+$/
|
|
10
|
+
|
|
11
|
+
export function isStorageKey (key) {
|
|
12
|
+
return typeof key === 'string' && KEY_PATTERN.test(key)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Map a stored `content.Key` to a logical key for `/r/{id}` URLs.
|
|
17
|
+
* Legacy S3 paths (`media/{workspaceId}/{resourceId}/original.ext`) fall back
|
|
18
|
+
* to `resourceId` so folder list does not 500 after leftover File docs appear.
|
|
19
|
+
*/
|
|
20
|
+
export function coerceStorageKey (key, fallbackResourceId) {
|
|
21
|
+
if (isStorageKey(key)) return key
|
|
22
|
+
if (typeof fallbackResourceId === 'string' && RESOURCE_ID_PATTERN.test(fallbackResourceId)) {
|
|
23
|
+
return fallbackResourceId
|
|
24
|
+
}
|
|
25
|
+
return null
|
|
26
|
+
}
|
|
9
27
|
|
|
10
28
|
export function originalObjectKey (resourceId) {
|
|
11
29
|
assertResourceId(resourceId)
|
|
@@ -31,7 +49,7 @@ export function assertStorageKey (key) {
|
|
|
31
49
|
}
|
|
32
50
|
|
|
33
51
|
function assertResourceId (resourceId) {
|
|
34
|
-
if (!resourceId || typeof resourceId !== 'string' ||
|
|
52
|
+
if (!resourceId || typeof resourceId !== 'string' || !RESOURCE_ID_PATTERN.test(resourceId)) {
|
|
35
53
|
throw new Error(`[storage-keys] invalid resourceId: ${resourceId}`)
|
|
36
54
|
}
|
|
37
55
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from '@jest/globals'
|
|
2
|
-
import { originalObjectKey, derivativeObjectKey, assertStorageKey } from './storage-keys.js'
|
|
2
|
+
import { originalObjectKey, derivativeObjectKey, assertStorageKey, coerceStorageKey } from './storage-keys.js'
|
|
3
3
|
|
|
4
4
|
describe('storage-keys', () => {
|
|
5
5
|
it('originalObjectKey returns flat resourceId', () => {
|
|
@@ -13,4 +13,10 @@ describe('storage-keys', () => {
|
|
|
13
13
|
it('rejects path-like keys', () => {
|
|
14
14
|
expect(() => assertStorageKey('media/ws/file')).toThrow()
|
|
15
15
|
})
|
|
16
|
+
|
|
17
|
+
it('coerces legacy media paths to the resource id', () => {
|
|
18
|
+
expect(coerceStorageKey('media/ws/abc123/original.pdf', 'abc123')).toBe('abc123')
|
|
19
|
+
expect(coerceStorageKey('abc123:galleryMedium', 'abc123')).toBe('abc123:galleryMedium')
|
|
20
|
+
expect(coerceStorageKey('media/ws/file', 'not valid')).toBe(null)
|
|
21
|
+
})
|
|
16
22
|
})
|
|
@@ -132,12 +132,28 @@ describe('TaskRunList projection', () => {
|
|
|
132
132
|
resourceId: 'run1',
|
|
133
133
|
event: 'Completed',
|
|
134
134
|
created: 2000,
|
|
135
|
-
payload: {
|
|
135
|
+
payload: {
|
|
136
|
+
durationMs: 1000,
|
|
137
|
+
resultSummary: null,
|
|
138
|
+
belongsTo: 'ws1',
|
|
139
|
+
taskId: '@ossy/media-tasks/tasks/resize',
|
|
140
|
+
moduleId: 'media-tasks',
|
|
141
|
+
},
|
|
136
142
|
}, state)
|
|
137
143
|
|
|
138
144
|
expect(state.runs[0].status).toBe('success')
|
|
139
145
|
expect(state.runs[0].durationMs).toBe(1000)
|
|
140
146
|
})
|
|
147
|
+
|
|
148
|
+
it('scopes Completed/Failed from payload.belongsTo (changestream dispatch)', () => {
|
|
149
|
+
expect(TaskRunListProjection.scopeFromEvent({
|
|
150
|
+
payload: { durationMs: 10 },
|
|
151
|
+
})).toBeNull()
|
|
152
|
+
|
|
153
|
+
expect(TaskRunListProjection.scopeFromEvent({
|
|
154
|
+
payload: { durationMs: 10, belongsTo: 'ws1' },
|
|
155
|
+
})).toBe('ws1')
|
|
156
|
+
})
|
|
141
157
|
})
|
|
142
158
|
|
|
143
159
|
describe('ActionInvocation entity fold', () => {
|