velocious 1.0.508 → 1.0.510
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 +1 -0
- package/build/configuration-types.js +7 -0
- package/build/configuration.js +74 -2
- package/build/database/record/auditing.js +31 -3
- package/build/database/tenants/schema-cloner.js +49 -13
- package/build/frontend-models/resource-definition.js +144 -0
- package/build/routes/built-in/api-manifest/controller.js +14 -0
- package/build/src/configuration-types.d.ts +20 -0
- package/build/src/configuration-types.d.ts.map +1 -1
- package/build/src/configuration-types.js +7 -1
- package/build/src/configuration.d.ts +34 -1
- package/build/src/configuration.d.ts.map +1 -1
- package/build/src/configuration.js +68 -3
- package/build/src/database/record/auditing.d.ts.map +1 -1
- package/build/src/database/record/auditing.js +26 -4
- package/build/src/database/tenants/schema-cloner.d.ts +11 -1
- package/build/src/database/tenants/schema-cloner.d.ts.map +1 -1
- package/build/src/database/tenants/schema-cloner.js +45 -13
- package/build/src/frontend-models/resource-definition.d.ts +9 -0
- package/build/src/frontend-models/resource-definition.d.ts.map +1 -1
- package/build/src/frontend-models/resource-definition.js +130 -1
- package/build/src/routes/built-in/api-manifest/controller.d.ts +9 -0
- package/build/src/routes/built-in/api-manifest/controller.d.ts.map +1 -0
- package/build/src/routes/built-in/api-manifest/controller.js +13 -0
- package/build/src/sync/sync-resource-base.js +3 -3
- package/build/sync/sync-resource-base.js +2 -2
- package/build/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/configuration-types.js +7 -0
- package/src/configuration.js +74 -2
- package/src/database/record/auditing.js +31 -3
- package/src/database/tenants/schema-cloner.js +49 -13
- package/src/frontend-models/resource-definition.js +144 -0
- package/src/routes/built-in/api-manifest/controller.js +14 -0
- package/src/sync/sync-resource-base.js +2 -2
|
@@ -282,6 +282,150 @@ export function frontendModelSyncManifestForBackendProjects(backendProjects) {
|
|
|
282
282
|
return manifest
|
|
283
283
|
}
|
|
284
284
|
|
|
285
|
+
/**
|
|
286
|
+
* Builds a frontend-safe API manifest for all registered frontend-model
|
|
287
|
+
* resources. The manifest is deterministic (sorted model names, sorted
|
|
288
|
+
* attributes, sorted commands) and includes only public-safe metadata: no
|
|
289
|
+
* secrets, no server callbacks, no backend file paths.
|
|
290
|
+
* @param {import("../configuration-types.js").BackendProjectConfiguration[]} backendProjects - Backend projects to scan.
|
|
291
|
+
* @returns {Record<string, unknown>} - Frontend-safe API manifest.
|
|
292
|
+
*/
|
|
293
|
+
export function frontendModelApiManifest(backendProjects) {
|
|
294
|
+
/** @type {Record<string, unknown>} */
|
|
295
|
+
const resources = {}
|
|
296
|
+
|
|
297
|
+
for (const backendProject of backendProjects) {
|
|
298
|
+
const projectResources = frontendModelResourcesForBackendProject(backendProject)
|
|
299
|
+
|
|
300
|
+
for (const configuredModelName of Object.keys(projectResources).sort()) {
|
|
301
|
+
const resourceDefinition = projectResources[configuredModelName]
|
|
302
|
+
const resourceConfiguration = frontendModelResourceConfigurationFromDefinition(resourceDefinition)
|
|
303
|
+
|
|
304
|
+
if (!resourceConfiguration) continue
|
|
305
|
+
|
|
306
|
+
const modelName = resourceConfiguration.modelName || configuredModelName
|
|
307
|
+
const resourcePath = `/${inflection.dasherize(inflection.pluralize(inflection.underscore(configuredModelName)))}`
|
|
308
|
+
|
|
309
|
+
/** @type {Record<string, unknown>} */
|
|
310
|
+
const entry = {
|
|
311
|
+
modelName,
|
|
312
|
+
path: resourcePath,
|
|
313
|
+
primaryKey: resourceConfiguration.primaryKey || "id",
|
|
314
|
+
attributes: manifestAttributes(resourceConfiguration.attributes),
|
|
315
|
+
abilities: resourceConfiguration.abilities,
|
|
316
|
+
builtInCommands: {
|
|
317
|
+
collection: resourceConfiguration.builtInCollectionCommands,
|
|
318
|
+
member: resourceConfiguration.builtInMemberCommands
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const relationships = resourceConfiguration.relationships
|
|
323
|
+
if (relationships && relationships.length > 0) {
|
|
324
|
+
/** @type {Record<string, unknown>} */
|
|
325
|
+
const rels = {}
|
|
326
|
+
for (const relName of relationships) {
|
|
327
|
+
rels[relName] = {}
|
|
328
|
+
}
|
|
329
|
+
entry.relationships = rels
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const attachments = resourceConfiguration.attachments
|
|
333
|
+
if (attachments && Object.keys(attachments).length > 0) {
|
|
334
|
+
entry.attachments = attachments
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const collectionCommands = manifestCommandEntries({
|
|
338
|
+
commandMetadata: resourceConfiguration.commandMetadata || {},
|
|
339
|
+
commands: resourceConfiguration.collectionCommands,
|
|
340
|
+
resourcePath,
|
|
341
|
+
scope: "collection"
|
|
342
|
+
})
|
|
343
|
+
const memberCommands = manifestCommandEntries({
|
|
344
|
+
commandMetadata: resourceConfiguration.commandMetadata || {},
|
|
345
|
+
commands: resourceConfiguration.memberCommands,
|
|
346
|
+
resourcePath,
|
|
347
|
+
scope: "member"
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
if (collectionCommands.length > 0 || memberCommands.length > 0) {
|
|
351
|
+
/** @type {Record<string, unknown>} */
|
|
352
|
+
const cmds = {}
|
|
353
|
+
if (collectionCommands.length > 0) cmds["collection"] = collectionCommands
|
|
354
|
+
if (memberCommands.length > 0) cmds["member"] = memberCommands
|
|
355
|
+
entry.commands = cmds
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (resourceConfiguration.sync?.enabled) {
|
|
359
|
+
entry.sync = resourceConfiguration.sync
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
resources[configuredModelName] = entry
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return {
|
|
367
|
+
formatVersion: 1,
|
|
368
|
+
resources: Object.keys(resources).sort().reduce((sorted, key) => {
|
|
369
|
+
/** @type {Record<string, unknown>} */ (sorted)[key] = resources[key]
|
|
370
|
+
return sorted
|
|
371
|
+
}, /** @type {Record<string, unknown>} */ ({}))
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Normalizes resource attribute definitions into a sorted array of strings.
|
|
377
|
+
* @param {?} attributes - Raw attributes config (array or object).
|
|
378
|
+
* @returns {string[]} - Sorted attribute names.
|
|
379
|
+
*/
|
|
380
|
+
function manifestAttributes(attributes) {
|
|
381
|
+
if (!attributes) return []
|
|
382
|
+
|
|
383
|
+
let names
|
|
384
|
+
|
|
385
|
+
if (Array.isArray(attributes)) {
|
|
386
|
+
names = attributes.map((entry) => typeof entry === "string" ? entry : entry.name).filter(Boolean)
|
|
387
|
+
} else if (attributes && typeof attributes === "object") {
|
|
388
|
+
names = Object.keys(attributes)
|
|
389
|
+
} else {
|
|
390
|
+
return []
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return names.sort()
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Builds manifest-safe command entry list.
|
|
398
|
+
* @param {object} args - Arguments.
|
|
399
|
+
* @param {Record<string, {args: Array<{name: string, type: string}>, returnType: string | null}>} args.commandMetadata - Per-command metadata.
|
|
400
|
+
* @param {Record<string, string>} args.commands - Method name → kebab slug map.
|
|
401
|
+
* @param {string} args.resourcePath - Resource path.
|
|
402
|
+
* @param {"collection" | "member"} args.scope - Command scope.
|
|
403
|
+
* @returns {Record<string, unknown>[]} - Manifest command entries.
|
|
404
|
+
*/
|
|
405
|
+
function manifestCommandEntries({commandMetadata, commands, resourcePath, scope}) {
|
|
406
|
+
return Object.keys(commands).sort().map((methodName) => {
|
|
407
|
+
const slug = commands[methodName]
|
|
408
|
+
const metadata = commandMetadata[methodName] || {args: [], returnType: null}
|
|
409
|
+
const path = scope === "member"
|
|
410
|
+
? `${resourcePath}/<id>/${slug}`
|
|
411
|
+
: `${resourcePath}/${slug}`
|
|
412
|
+
|
|
413
|
+
/** @type {Record<string, unknown>} */
|
|
414
|
+
const entry = {
|
|
415
|
+
methodName,
|
|
416
|
+
scope,
|
|
417
|
+
path,
|
|
418
|
+
args: metadata.args
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (metadata.returnType) {
|
|
422
|
+
entry.returnType = metadata.returnType
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
return entry
|
|
426
|
+
})
|
|
427
|
+
}
|
|
428
|
+
|
|
285
429
|
/**
|
|
286
430
|
* Normalizes sync policy metadata and computes a deterministic hash from safe policy inputs.
|
|
287
431
|
* @param {import("../configuration-types.js").FrontendModelResourceConfiguration} resourceConfiguration - Raw resource configuration.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import Controller from "../../../controller.js"
|
|
2
|
+
|
|
3
|
+
export default class BuiltInApiManifestController extends Controller {
|
|
4
|
+
/**
|
|
5
|
+
* Runs show.
|
|
6
|
+
* @returns {Promise<void>} - Resolves when the manifest has been rendered.
|
|
7
|
+
*/
|
|
8
|
+
async show() {
|
|
9
|
+
const manifest = await this.getConfiguration().getApiManifest()
|
|
10
|
+
|
|
11
|
+
this._response.setHeader("Content-Type", "application/json; charset=UTF-8")
|
|
12
|
+
this._response.setBody(`${JSON.stringify(manifest, null, 2)}\n`)
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {forcedNonBlankString} from "typanic"
|
|
4
4
|
|
|
5
5
|
import FrontendModelBaseResource from "../frontend-model-resource/base-resource.js"
|
|
6
6
|
import SyncEnvelopeReplayService from "./sync-envelope-replay-service.js"
|
|
@@ -135,7 +135,7 @@ export default class SyncResourceBase extends FrontendModelBaseResource {
|
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
const scopeParams = /** @type {Record<string, ?>} */ (scope)
|
|
138
|
-
const resourceType =
|
|
138
|
+
const resourceType = forcedNonBlankString(scopeParams.resourceType, "resourceType")
|
|
139
139
|
const conditions = scopeParams.conditions
|
|
140
140
|
|
|
141
141
|
if (!conditions || typeof conditions !== "object" || Array.isArray(conditions)) {
|