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.
Files changed (35) hide show
  1. package/README.md +1 -0
  2. package/build/configuration-types.js +7 -0
  3. package/build/configuration.js +74 -2
  4. package/build/database/record/auditing.js +31 -3
  5. package/build/database/tenants/schema-cloner.js +49 -13
  6. package/build/frontend-models/resource-definition.js +144 -0
  7. package/build/routes/built-in/api-manifest/controller.js +14 -0
  8. package/build/src/configuration-types.d.ts +20 -0
  9. package/build/src/configuration-types.d.ts.map +1 -1
  10. package/build/src/configuration-types.js +7 -1
  11. package/build/src/configuration.d.ts +34 -1
  12. package/build/src/configuration.d.ts.map +1 -1
  13. package/build/src/configuration.js +68 -3
  14. package/build/src/database/record/auditing.d.ts.map +1 -1
  15. package/build/src/database/record/auditing.js +26 -4
  16. package/build/src/database/tenants/schema-cloner.d.ts +11 -1
  17. package/build/src/database/tenants/schema-cloner.d.ts.map +1 -1
  18. package/build/src/database/tenants/schema-cloner.js +45 -13
  19. package/build/src/frontend-models/resource-definition.d.ts +9 -0
  20. package/build/src/frontend-models/resource-definition.d.ts.map +1 -1
  21. package/build/src/frontend-models/resource-definition.js +130 -1
  22. package/build/src/routes/built-in/api-manifest/controller.d.ts +9 -0
  23. package/build/src/routes/built-in/api-manifest/controller.d.ts.map +1 -0
  24. package/build/src/routes/built-in/api-manifest/controller.js +13 -0
  25. package/build/src/sync/sync-resource-base.js +3 -3
  26. package/build/sync/sync-resource-base.js +2 -2
  27. package/build/tsconfig.tsbuildinfo +1 -1
  28. package/package.json +1 -1
  29. package/src/configuration-types.js +7 -0
  30. package/src/configuration.js +74 -2
  31. package/src/database/record/auditing.js +31 -3
  32. package/src/database/tenants/schema-cloner.js +49 -13
  33. package/src/frontend-models/resource-definition.js +144 -0
  34. package/src/routes/built-in/api-manifest/controller.js +14 -0
  35. 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 {forcedNonBlankStringParam} from "typanic"
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 = forcedNonBlankStringParam(scopeParams, "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)) {