@ossy/platform 1.39.2 → 3.0.1
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 +21 -17
- package/package.json +20 -11
- package/src/Definition.js +2 -1
- package/src/PlatformShell.jsx +10 -10
- package/src/actions/action.service.js +85 -7
- package/src/audit/action-invocation-service.js +143 -0
- package/src/audit/action-invocation.aggregate.js +57 -0
- package/src/audit/audit-helpers.js +61 -0
- package/src/audit/detect-channel.js +19 -0
- package/src/audit/list-task-runs.action.js +5 -0
- package/src/audit/list-task-runs.task.js +17 -0
- package/src/audit/task-run-list.aggregate.js +76 -0
- package/src/audit/task-run-service.js +208 -0
- package/src/audit/task-run.aggregate.js +58 -0
- package/src/auth/action-scopes.js +3 -0
- package/src/capability-schemas/action-capability.schema.js +1 -0
- package/src/capability-schemas/action-meta.schema.js +1 -0
- package/src/capability-schemas/component-capability.schema.js +1 -0
- package/src/capability-schemas/page-capability.schema.js +1 -0
- package/src/capability-schemas/page-meta.schema.js +1 -0
- package/src/capability-schemas/task-capability.schema.js +1 -0
- package/src/capability-schemas/task-meta.schema.js +1 -0
- package/src/capability-schemas/task-output.schema.js +1 -0
- package/src/capability-schemas/task-trigger-authoring.schema.js +1 -0
- package/src/capability-schemas/task-trigger.schema.js +1 -0
- package/src/directory.schema.js +7 -0
- package/src/entitlements/action-entitlement.js +69 -0
- package/src/file.schema.js +11 -0
- package/src/index.js +12 -3
- package/src/mcp/create-ossy-mcp-server.js +97 -0
- package/src/mcp/json-schema-to-zod.js +64 -0
- package/src/mcp/mount-ossy-mcp.js +72 -0
- package/src/mcp/mount-platform-mcp.js +101 -0
- package/src/mcp/upload-file-tool.js +62 -0
- package/src/metering/metering-service.js +91 -0
- package/src/{platform-config.resource.js → platform-config.schema.js} +1 -1
- package/src/proxy-internal.js +13 -16
- package/src/push/mount-push-sse.js +91 -0
- package/src/request-diagnostics.js +144 -0
- package/src/request-diagnostics.spec.js +40 -0
- package/src/resources/index.js +3 -2
- package/src/resources/schema.registry.js +26 -0
- package/src/resources/schema.service.js +54 -0
- package/src/resources/schema.validation.js +90 -0
- package/src/runtime.js +20 -6
- package/src/server.js +281 -62
- package/src/storage/filesystem-storage.client.js +109 -0
- package/src/storage/local-storage.client.js +2 -65
- package/src/storage/resource-read-url.js +36 -0
- package/src/storage/resource-read-url.spec.js +22 -0
- package/src/storage/s3-storage.client.js +102 -0
- package/src/storage/s3.client.js +27 -23
- package/src/storage/storage-keys.js +37 -0
- package/src/storage/storage-keys.spec.js +16 -0
- package/src/storage/storage.client.js +52 -8
- package/src/storage/storage.integration.js +40 -0
- package/src/tasks/change-stream.js +78 -11
- package/src/tasks/task-service.js +211 -34
- package/src/tasks/task-service.spec.js +187 -0
- package/src/test/e2e.util.js +18 -39
- package/src/test/flow-runner.js +476 -0
- package/src/test/test.util.js +30 -29
- package/src/user-app-settings.js +44 -0
- package/src/users.middleware.js +61 -17
- package/src/resources/resource-template.registry.js +0 -29
- package/src/resources/resource-template.validation.js +0 -232
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createLogger } from '@ossy/observability'
|
|
2
|
+
|
|
3
|
+
const log = createLogger('platform')
|
|
4
|
+
|
|
5
|
+
/** @type {object[]} */
|
|
6
|
+
const systemSchemas = []
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Register a single schema POJO (as inlined in `build/manifest.json`).
|
|
10
|
+
*
|
|
11
|
+
* @param {object} schema
|
|
12
|
+
*/
|
|
13
|
+
export function registerSchema (schema) {
|
|
14
|
+
if (!schema || typeof schema !== 'object' || Array.isArray(schema)) return
|
|
15
|
+
if (typeof schema.id !== 'string' || schema.id.trim() === '') return
|
|
16
|
+
if (systemSchemas.find(s => s.id === schema.id)) return
|
|
17
|
+
systemSchemas.push(schema)
|
|
18
|
+
log.info(`[SchemaRegistry] Registered system schema: ${schema.id}`)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @returns {object[]}
|
|
23
|
+
*/
|
|
24
|
+
export function getSystemSchemas () {
|
|
25
|
+
return systemSchemas
|
|
26
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Schema } from '@ossy/schema'
|
|
2
|
+
|
|
3
|
+
/** @type {Schema | null} */
|
|
4
|
+
let platformSchema = null
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Platform-wide schema engine from build manifest (no workspace overlays).
|
|
8
|
+
*
|
|
9
|
+
* @param {{ schemas?: object[], actions?: { id: string }[], tasks?: { id: string }[] }} manifest
|
|
10
|
+
*/
|
|
11
|
+
export function initPlatformSchema (manifest) {
|
|
12
|
+
platformSchema = Schema.of(manifest, {
|
|
13
|
+
actionIds: (manifest.actions ?? []).map((a) => a.id),
|
|
14
|
+
taskIds: (manifest.tasks ?? []).map((t) => t.id),
|
|
15
|
+
strictRefs: false,
|
|
16
|
+
})
|
|
17
|
+
return platformSchema
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @returns {Schema}
|
|
22
|
+
*/
|
|
23
|
+
export function getPlatformSchema () {
|
|
24
|
+
if (!platformSchema) {
|
|
25
|
+
throw new Error('[@ossy/platform] Platform Schema not initialized — call initPlatformSchema after loadManifest')
|
|
26
|
+
}
|
|
27
|
+
return platformSchema
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Build-time or per-request engine with optional strict ref checks.
|
|
32
|
+
*
|
|
33
|
+
* @param {{ schemas?: object[], actions?: { id: string }[], tasks?: { id: string }[] }} manifest
|
|
34
|
+
* @param {{ strictRefs?: boolean, actionIds?: string[], taskIds?: string[] }} [options]
|
|
35
|
+
*/
|
|
36
|
+
export function createSchemaEngine (manifest, options = {}) {
|
|
37
|
+
return Schema.of(manifest, {
|
|
38
|
+
actionIds: options.actionIds ?? (manifest.actions ?? []).map((a) => a.id),
|
|
39
|
+
taskIds: options.taskIds ?? (manifest.tasks ?? []).map((t) => t.id),
|
|
40
|
+
strictRefs: options.strictRefs ?? false,
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Platform schema engine with optional workspace schema overlays.
|
|
46
|
+
*
|
|
47
|
+
* @param {object[]} [workspaceSchemas]
|
|
48
|
+
*/
|
|
49
|
+
export function schemaForWorkspace (workspaceSchemas = []) {
|
|
50
|
+
const platform = getPlatformSchema()
|
|
51
|
+
const extra = (workspaceSchemas ?? []).filter((s) => s?.id && !platform.has(s.id))
|
|
52
|
+
if (!extra.length) return platform
|
|
53
|
+
return platform.withWorkspaceSchemas(extra)
|
|
54
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { Schema } from '@ossy/schema'
|
|
2
|
+
|
|
3
|
+
/** Field input types accepted in schema definitions (UI + API contract). */
|
|
4
|
+
export const ALLOWED_FIELD_TYPES = new Set([
|
|
5
|
+
'text',
|
|
6
|
+
'email',
|
|
7
|
+
'textarea',
|
|
8
|
+
'richtext',
|
|
9
|
+
'number',
|
|
10
|
+
'select',
|
|
11
|
+
'multiselect',
|
|
12
|
+
'file',
|
|
13
|
+
'image',
|
|
14
|
+
'boolean',
|
|
15
|
+
'date',
|
|
16
|
+
'timestamp',
|
|
17
|
+
'date-range',
|
|
18
|
+
'date-ranges',
|
|
19
|
+
'id',
|
|
20
|
+
'path',
|
|
21
|
+
])
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Normalize template field `type` to the canonical editor type.
|
|
25
|
+
* `image` is an alias for `file` with default `accept: 'image/*'` when accept is omitted.
|
|
26
|
+
*
|
|
27
|
+
* @param {string | undefined} type
|
|
28
|
+
* @returns {{ type: string, defaultAccept?: string }}
|
|
29
|
+
*/
|
|
30
|
+
export function normalizeFieldType (type) {
|
|
31
|
+
const raw = typeof type === 'string' ? type.trim() : ''
|
|
32
|
+
if (raw === 'image') return { type: 'file', defaultAccept: 'image/*' }
|
|
33
|
+
return { type: raw }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param {{ type?: string, accept?: string, min?: number, max?: number } & Record<string, unknown>} field
|
|
38
|
+
*/
|
|
39
|
+
export function resolveFieldDef (field) {
|
|
40
|
+
const { type: canonical, defaultAccept } = normalizeFieldType(field?.type)
|
|
41
|
+
return {
|
|
42
|
+
...field,
|
|
43
|
+
type: canonical,
|
|
44
|
+
accept: field?.accept ?? defaultAccept,
|
|
45
|
+
max: field?.max ?? 1,
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Validates a batch import of workspace schemas.
|
|
51
|
+
*
|
|
52
|
+
* @param {unknown} templates
|
|
53
|
+
* @param {Set<string>} reservedIds - System schema ids that must not be redefined
|
|
54
|
+
* @returns {{ ok: true } | { ok: false, code: string, message: string }}
|
|
55
|
+
*/
|
|
56
|
+
export function validateSchemasForImport (templates, reservedIds) {
|
|
57
|
+
if (!Array.isArray(templates)) {
|
|
58
|
+
return { ok: false, code: 'INVALID_PAYLOAD', message: 'Body must be a JSON array of schemas' }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const engine = Schema.of({ schemas: templates })
|
|
62
|
+
const seenIds = new Set()
|
|
63
|
+
|
|
64
|
+
for (const template of templates) {
|
|
65
|
+
if (reservedIds?.has(template?.id)) {
|
|
66
|
+
return {
|
|
67
|
+
ok: false,
|
|
68
|
+
code: 'RESERVED_SCHEMA_ID',
|
|
69
|
+
message: `Template id "${template.id}" is reserved by a system template`,
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const result = engine.validate(template)
|
|
74
|
+
if (!result.ok) {
|
|
75
|
+
const first = result.errors[0]
|
|
76
|
+
return { ok: false, code: first.code, message: first.message }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (seenIds.has(template.id)) {
|
|
80
|
+
return {
|
|
81
|
+
ok: false,
|
|
82
|
+
code: 'DUPLICATE_SCHEMA_ID',
|
|
83
|
+
message: `Duplicate schema id "${template.id}" in import payload`,
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
seenIds.add(template.id)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return { ok: true }
|
|
90
|
+
}
|
package/src/runtime.js
CHANGED
|
@@ -4,7 +4,7 @@ import express from 'express'
|
|
|
4
4
|
import cookieParser from 'cookie-parser'
|
|
5
5
|
import morgan from 'morgan'
|
|
6
6
|
import { Router as OssyRouter } from '@ossy/router'
|
|
7
|
-
import { loadManifest, resolveEntryUrl } from './server.js'
|
|
7
|
+
import { loadManifest, resolveEntryUrl, loadLayoutsById, resolvePageLayoutRender } from './server.js'
|
|
8
8
|
import { resolveRequestLocale } from './locale.js'
|
|
9
9
|
import { buildManifestSummary } from '@ossy/app/manifest/build-manifest-summary'
|
|
10
10
|
import { ProxyInternal } from './proxy-internal.js'
|
|
@@ -31,7 +31,7 @@ function cloneSerializable (value) {
|
|
|
31
31
|
*/
|
|
32
32
|
const siteContextCache = new Map()
|
|
33
33
|
|
|
34
|
-
function buildSiteContext (manifest, buildDir) {
|
|
34
|
+
function buildSiteContext (manifest, buildDir, layoutsById) {
|
|
35
35
|
const config = manifest.config
|
|
36
36
|
const supportedLanguages = Array.isArray(config.supportedLanguages) ? config.supportedLanguages : []
|
|
37
37
|
|
|
@@ -50,14 +50,15 @@ function buildSiteContext (manifest, buildDir) {
|
|
|
50
50
|
return promise
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
return { pageRouter, apiRouter, manifest, manifestSummary: buildManifestSummary(manifest), loadEntry, buildDir }
|
|
53
|
+
return { pageRouter, apiRouter, manifest, manifestSummary: buildManifestSummary(manifest), loadEntry, buildDir, layoutsById }
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
async function getSiteContext (domain) {
|
|
57
57
|
if (siteContextCache.has(domain)) return siteContextCache.get(domain)
|
|
58
58
|
const buildDir = await loadSite(domain)
|
|
59
59
|
const manifest = loadManifest(buildDir)
|
|
60
|
-
const
|
|
60
|
+
const layoutsById = await loadLayoutsById(manifest.layouts, buildDir, log)
|
|
61
|
+
const context = buildSiteContext(manifest, buildDir, layoutsById)
|
|
61
62
|
siteContextCache.set(domain, context)
|
|
62
63
|
return context
|
|
63
64
|
}
|
|
@@ -129,7 +130,7 @@ export async function startRuntime ({ port } = {}) {
|
|
|
129
130
|
return
|
|
130
131
|
}
|
|
131
132
|
|
|
132
|
-
const { pageRouter, apiRouter, manifest, manifestSummary, loadEntry, buildDir } = context
|
|
133
|
+
const { pageRouter, apiRouter, manifest, manifestSummary, loadEntry, buildDir, layoutsById } = context
|
|
133
134
|
|
|
134
135
|
try {
|
|
135
136
|
const apiRoute = apiRouter.getPageByUrl(requestUrl)
|
|
@@ -169,13 +170,21 @@ export async function startRuntime ({ port } = {}) {
|
|
|
169
170
|
|
|
170
171
|
const config = manifest.config
|
|
171
172
|
const { language, messages, fallbackMessages } = resolveRequestLocale(pageRouter, requestUrl, buildDir, config)
|
|
173
|
+
const {
|
|
174
|
+
Layout,
|
|
175
|
+
layoutEntry,
|
|
176
|
+
layoutSlots,
|
|
177
|
+
} = resolvePageLayoutRender(layoutsById, pageEntry)
|
|
172
178
|
const props = {
|
|
173
179
|
...config,
|
|
174
180
|
...(req.userAppSettings || {}),
|
|
175
181
|
theme: req.userAppSettings?.theme || cloneSerializable(config?.theme),
|
|
176
182
|
themes: cloneSerializable(config?.themes),
|
|
177
|
-
|
|
183
|
+
schemas: cloneSerializable(manifest.schemas),
|
|
178
184
|
manifestSummary: cloneSerializable(manifestSummary),
|
|
185
|
+
actionRoutes: cloneSerializable(manifest.actionRoutes || {}),
|
|
186
|
+
taskCatalog: cloneSerializable(manifest.taskCatalog || []),
|
|
187
|
+
taskGraphEdges: cloneSerializable(manifest.taskGraphEdges || []),
|
|
179
188
|
url: requestUrl,
|
|
180
189
|
isAuthenticated: !!req.isAuthenticated,
|
|
181
190
|
language,
|
|
@@ -187,6 +196,11 @@ export async function startRuntime ({ port } = {}) {
|
|
|
187
196
|
...(page.package ? { package: page.package } : {}),
|
|
188
197
|
})),
|
|
189
198
|
pageId: pageRoute.id,
|
|
199
|
+
componentEntries: manifest.components,
|
|
200
|
+
resolveComponentEntry: (entry) => resolveEntryUrl(entry, buildDir),
|
|
201
|
+
layoutSlots,
|
|
202
|
+
Layout,
|
|
203
|
+
layoutEntry,
|
|
190
204
|
}
|
|
191
205
|
const html = await mod.render(props)
|
|
192
206
|
res.status(200).type('html').send(html)
|