@ossy/platform 1.36.1 → 1.36.2

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 (2) hide show
  1. package/package.json +9 -9
  2. package/src/server.js +41 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ossy/platform",
3
- "version": "1.36.1",
3
+ "version": "1.36.2",
4
4
  "description": "Ossy application server runtime",
5
5
  "repository": {
6
6
  "type": "git",
@@ -38,13 +38,13 @@
38
38
  "@aws-sdk/s3-request-presigner": "^3.1057.0",
39
39
  "@aws-sdk/util-create-request": "^3.972.26",
40
40
  "@aws-sdk/util-format-url": "^3.972.17",
41
- "@ossy/event-store": "^1.5.1",
42
- "@ossy/observability": "^1.5.1",
43
- "@ossy/policies": "^1.10.1",
44
- "@ossy/router": "^1.37.1",
45
- "@ossy/sdk": "^1.37.1",
46
- "@ossy/tokens": "^1.10.1",
47
- "@ossy/users": "^1.10.1",
41
+ "@ossy/event-store": "^1.5.2",
42
+ "@ossy/observability": "^1.5.2",
43
+ "@ossy/policies": "^1.10.2",
44
+ "@ossy/router": "^1.37.2",
45
+ "@ossy/sdk": "^1.37.2",
46
+ "@ossy/tokens": "^1.10.2",
47
+ "@ossy/users": "^1.10.2",
48
48
  "cookie-parser": "^1.4.7",
49
49
  "dotenv": ">=16.0.0 <18.0.0",
50
50
  "express": ">=5.0.0 <6.0.0",
@@ -62,5 +62,5 @@
62
62
  "src",
63
63
  "Dockerfile"
64
64
  ],
65
- "gitHead": "d5b71d16ec56c819401c7a74f95d12b2347ca3c4"
65
+ "gitHead": "723d9d35aba3cd43700ecd6e72a7e7de96acff65"
66
66
  }
package/src/server.js CHANGED
@@ -17,6 +17,7 @@ import { createLogger } from '@ossy/observability'
17
17
  import { ConfigService } from './config.service.js'
18
18
  import { UsersMiddleware } from './users.middleware.js'
19
19
  import { WorkspacesMiddleware } from './workspaces.middleware.js'
20
+ import { matchesGlob } from './tasks/glob.js'
20
21
 
21
22
  const log = createLogger('@ossy/platform')
22
23
 
@@ -61,8 +62,8 @@ export function loadManifest (buildDir) {
61
62
  const integrations = Array.isArray(manifest.integrations) ? manifest.integrations : []
62
63
  const startups = Array.isArray(manifest.startups) ? manifest.startups : []
63
64
  const actions = Array.isArray(manifest.actions) ? manifest.actions : []
64
- for (const e of entries) {
65
- if ((e.type === 'page' || e.type === 'api') && !validRoutable(e)) {
65
+ const layouts = Array.isArray(manifest.layouts) ? manifest.layouts : []
66
+ for (const e of entries) { if ((e.type === 'page' || e.type === 'api') && !validRoutable(e)) {
66
67
  log.warn(`Skipping ${e.type} "${e.id}" — manifest entry has no path.`)
67
68
  }
68
69
  }
@@ -77,6 +78,7 @@ export function loadManifest (buildDir) {
77
78
  integrations,
78
79
  startups,
79
80
  actions,
81
+ layouts,
80
82
  config: manifest.config || {},
81
83
  }
82
84
  }
@@ -107,7 +109,6 @@ export async function startServer (options = {}) {
107
109
 
108
110
  const manifest = loadManifest(buildDir)
109
111
  const config = manifest.config
110
-
111
112
  for (const task of manifest.tasks ?? []) {
112
113
  try {
113
114
  const mod = await import(resolveEntryUrl(task.entry, buildDir))
@@ -176,6 +177,41 @@ export async function startServer (options = {}) {
176
177
  }
177
178
  }
178
179
 
180
+ // Layout loading — import each bundled layout module and store the default-
181
+ // exported React component in a map keyed by the layout's id. At render
182
+ // time we pick the first layout whose `applies` glob matches the page path.
183
+ const layoutModuleMap = new Map()
184
+ for (const layoutEntry of manifest.layouts ?? []) {
185
+ try {
186
+ const mod = await import(resolveEntryUrl(layoutEntry.entry, buildDir))
187
+ if (typeof mod.default === 'function') {
188
+ layoutModuleMap.set(layoutEntry.id, { component: mod.default, applies: layoutEntry.applies ?? '*' })
189
+ }
190
+ } catch (err) {
191
+ log.warn(`Failed to load layout "${layoutEntry.id}"`, undefined, err)
192
+ }
193
+ }
194
+
195
+ function findLayoutForPage (pagePathOrPaths) {
196
+ if (!layoutModuleMap.size) return null
197
+ const paths = typeof pagePathOrPaths === 'string'
198
+ ? [pagePathOrPaths]
199
+ : Object.values(pagePathOrPaths || {})
200
+ // Check specific (non-wildcard) layouts first, then fall back to catch-all '*'.
201
+ // This lets e.g. `applies: '/'` override a global `applies: '*'` layout.
202
+ let catchAll = null
203
+ for (const [, layout] of layoutModuleMap) {
204
+ if (layout.applies === '*') {
205
+ if (!catchAll) catchAll = layout.component
206
+ continue
207
+ }
208
+ for (const p of paths) {
209
+ if (matchesGlob(layout.applies, p)) return layout.component
210
+ }
211
+ }
212
+ return catchAll
213
+ }
214
+
179
215
  // Register the SDK so all tasks receive it as `sdk`.
180
216
  // Priority: explicit options.sdk → SDK.of() from env vars → null (direct-DB fallback in tasks).
181
217
  const botSdk = (process.env.API_URL && process.env.OSSY_API_KEY)
@@ -288,6 +324,7 @@ export async function startServer (options = {}) {
288
324
  res.status(503).type('text').send('SSR runtime unavailable')
289
325
  return
290
326
  }
327
+ const Layout = findLayoutForPage(pageEntry.path)
291
328
  const props = {
292
329
  ...config,
293
330
  ...(req.userAppSettings || {}),
@@ -299,6 +336,7 @@ export async function startServer (options = {}) {
299
336
  pages: manifest.pages.map((page) => ({ id: page.id, path: page.path })),
300
337
  pageId: pageRoute.id,
301
338
  componentEntries: manifest.components,
339
+ Layout: Layout ?? null,
302
340
  }
303
341
  const html = await mod.render(props)
304
342
  res.status(200).type('html').send(html)