velocious 1.0.451 → 1.0.453

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 (78) hide show
  1. package/README.md +11 -5
  2. package/build/configuration-types.js +23 -0
  3. package/build/configuration.js +5 -5
  4. package/build/database/drivers/mssql/index.js +1 -1
  5. package/build/database/drivers/mysql/index.js +1 -1
  6. package/build/database/drivers/pgsql/index.js +1 -1
  7. package/build/database/drivers/sqlite/base.js +1 -1
  8. package/build/database/record/attachments/attachment-record.js +121 -0
  9. package/build/database/record/attachments/store.js +2 -0
  10. package/build/database/table-data/index.js +1 -1
  11. package/build/environment-handlers/node/cli/commands/generate/frontend-models.js +7 -2
  12. package/build/frontend-model-controller.js +302 -113
  13. package/build/frontend-model-resource/base-resource.js +2 -1
  14. package/build/frontend-model-resource/velocious-attachment-resource.js +221 -0
  15. package/build/frontend-models/base.js +127 -1
  16. package/build/frontend-models/built-in-resources.js +32 -0
  17. package/build/frontend-models/websocket-publishers.js +13 -3
  18. package/build/routes/hooks/frontend-model-command-route-hook.js +3 -2
  19. package/build/src/configuration-types.d.ts +56 -0
  20. package/build/src/configuration-types.d.ts.map +1 -1
  21. package/build/src/configuration-types.js +21 -1
  22. package/build/src/configuration.d.ts +9 -17
  23. package/build/src/configuration.d.ts.map +1 -1
  24. package/build/src/configuration.js +6 -6
  25. package/build/src/database/drivers/mssql/index.d.ts.map +1 -1
  26. package/build/src/database/drivers/mssql/index.js +2 -2
  27. package/build/src/database/drivers/mysql/index.d.ts.map +1 -1
  28. package/build/src/database/drivers/mysql/index.js +2 -2
  29. package/build/src/database/drivers/pgsql/index.d.ts.map +1 -1
  30. package/build/src/database/drivers/pgsql/index.js +2 -2
  31. package/build/src/database/drivers/sqlite/base.d.ts.map +1 -1
  32. package/build/src/database/drivers/sqlite/base.js +2 -2
  33. package/build/src/database/record/attachments/attachment-record.d.ts +67 -0
  34. package/build/src/database/record/attachments/attachment-record.d.ts.map +1 -0
  35. package/build/src/database/record/attachments/attachment-record.js +106 -0
  36. package/build/src/database/record/attachments/store.d.ts.map +1 -1
  37. package/build/src/database/record/attachments/store.js +2 -1
  38. package/build/src/database/table-data/index.js +2 -2
  39. package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.d.ts.map +1 -1
  40. package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.js +7 -3
  41. package/build/src/frontend-model-controller.d.ts +82 -9
  42. package/build/src/frontend-model-controller.d.ts.map +1 -1
  43. package/build/src/frontend-model-controller.js +270 -112
  44. package/build/src/frontend-model-resource/base-resource.d.ts +2 -0
  45. package/build/src/frontend-model-resource/base-resource.d.ts.map +1 -1
  46. package/build/src/frontend-model-resource/base-resource.js +3 -2
  47. package/build/src/frontend-model-resource/velocious-attachment-resource.d.ts +105 -0
  48. package/build/src/frontend-model-resource/velocious-attachment-resource.d.ts.map +1 -0
  49. package/build/src/frontend-model-resource/velocious-attachment-resource.js +185 -0
  50. package/build/src/frontend-models/base.d.ts +87 -1
  51. package/build/src/frontend-models/base.d.ts.map +1 -1
  52. package/build/src/frontend-models/base.js +112 -2
  53. package/build/src/frontend-models/built-in-resources.d.ts +18 -0
  54. package/build/src/frontend-models/built-in-resources.d.ts.map +1 -0
  55. package/build/src/frontend-models/built-in-resources.js +29 -0
  56. package/build/src/frontend-models/websocket-publishers.d.ts.map +1 -1
  57. package/build/src/frontend-models/websocket-publishers.js +14 -4
  58. package/build/src/routes/hooks/frontend-model-command-route-hook.d.ts.map +1 -1
  59. package/build/src/routes/hooks/frontend-model-command-route-hook.js +4 -3
  60. package/build/tsconfig.tsbuildinfo +1 -1
  61. package/package.json +1 -1
  62. package/src/configuration-types.js +23 -0
  63. package/src/configuration.js +5 -5
  64. package/src/database/drivers/mssql/index.js +1 -1
  65. package/src/database/drivers/mysql/index.js +1 -1
  66. package/src/database/drivers/pgsql/index.js +1 -1
  67. package/src/database/drivers/sqlite/base.js +1 -1
  68. package/src/database/record/attachments/attachment-record.js +121 -0
  69. package/src/database/record/attachments/store.js +2 -0
  70. package/src/database/table-data/index.js +1 -1
  71. package/src/environment-handlers/node/cli/commands/generate/frontend-models.js +7 -2
  72. package/src/frontend-model-controller.js +302 -113
  73. package/src/frontend-model-resource/base-resource.js +2 -1
  74. package/src/frontend-model-resource/velocious-attachment-resource.js +221 -0
  75. package/src/frontend-models/base.js +127 -1
  76. package/src/frontend-models/built-in-resources.js +32 -0
  77. package/src/frontend-models/websocket-publishers.js +13 -3
  78. package/src/routes/hooks/frontend-model-command-route-hook.js +3 -2
package/README.md CHANGED
@@ -447,6 +447,7 @@ Frontend-model `where(...)` supports nested relationship descriptors (for exampl
447
447
  Frontend-model `joins(...)` supports relationship-object descriptors only (for example `Task.joins({project: {creatingUser: true}})`) and rejects raw SQL join strings.
448
448
  Frontend-model `distinct(...)` only accepts booleans (`true` by default) and is applied server-side through the backend query API.
449
449
  Frontend-model `pluck(...)` validates attribute/path descriptors against configured model metadata and does not accept SQL fragments.
450
+ Frontend-model query fields are limited to attributes exposed by the backend resource. Use `{name: "attributeName", selectedByDefault: false}` for fields that may be selected or filtered explicitly but should stay out of default payloads.
450
451
 
451
452
  When backend payloads include `__preloadedRelationships`, nested frontend-model relationships are hydrated recursively. Relationship methods can use `getRelationshipByName("relationship").loaded()` and will throw when a relationship was not preloaded.
452
453
 
@@ -546,11 +547,14 @@ For frontend models, configure `resourceConfig().attachments` and use:
546
547
  await frontendTask.update({descriptionFile: file})
547
548
  const descriptionFile = await frontendTask.descriptionFile().download()
548
549
  const descriptionFileUrl = await frontendTask.descriptionFile().url()
550
+ const descriptionFileMetadata = await frontendTask.descriptionFile().first()
551
+ const filesMetadata = await frontendTask.files().toArray()
549
552
  await frontendTask.attach(file)
550
553
  ```
551
554
 
552
555
  Frontend model attachment input does not support `{path: ...}`.
553
556
  Use `File`/`Blob`/bytes/`contentBase64` payloads instead.
557
+ Attachment metadata is exposed through the built-in `VelociousAttachment` frontend model with safe fields only: `id`, `recordType`, `recordId`, `name`, `position`, `filename`, `contentType`, `byteSize`, `createdAt`, and `updatedAt`. Storage internals such as `driver`, `storageKey`, and `contentBase64` remain hidden and non-queryable. Direct metadata queries require owner filters: `recordType`, `recordId`, and `name`.
554
558
 
555
559
  When your frontend app calls a backend on another host/port (or under a path prefix), configure transport once:
556
560
 
@@ -583,7 +587,7 @@ const configuration = new Configuration({
583
587
 
584
588
  This opt-in is ignored in `production`; production frontend-model responses never include internal exception details.
585
589
 
586
- Backends can append client-safe metadata to frontend-model error responses with `configuration.addClientErrorPayloadReporter(...)`. Reporters receive the caught `error`, the current `request`, and a small `context` object, and should only return fields that are safe for clients to see. This is useful for attaching an error-reporting URL while keeping the normal production error message generic:
590
+ Backends can append client-safe metadata to frontend-model error responses with `configuration.addClientErrorPayloadReporter(...)`. Reporters receive the caught `error`, the current `request`, and a small `context` object, and should only return fields that are safe for clients to see. Frontend-model endpoint failures include `context.frontendModelEndpoint`, `action`, `commandType`, `model`, `requestId`, and `expectedError`. This is useful for attaching an error-reporting URL while keeping the normal production error message generic:
587
591
 
588
592
  ```js
589
593
  configuration.addClientErrorPayloadReporter(async ({error, request, context}) => {
@@ -1002,6 +1006,8 @@ npx velocious g:migration create-tasks
1002
1006
  ```
1003
1007
 
1004
1008
  ## Write a migration
1009
+ Implicit `id` primary keys and `references(...)` columns use UUIDs by default. Use an explicit numeric `id` or reference `type` only for legacy schemas or external compatibility.
1010
+
1005
1011
  ```js
1006
1012
  import Migration from "velocious/build/src/database/migration/index.js"
1007
1013
 
@@ -1011,9 +1017,9 @@ export default class CreateEvents extends Migration {
1011
1017
  t.timestamps()
1012
1018
  })
1013
1019
 
1014
- // UUID primary key
1015
- await this.createTable("uuid_items", {id: {type: "uuid"}}, (t) => {
1016
- t.string("title", {null: false})
1020
+ // Legacy numeric primary key
1021
+ await this.createTable("legacy_events", {id: {type: "bigint"}}, (t) => {
1022
+ t.references("task", {type: "bigint"})
1017
1023
  t.timestamps()
1018
1024
  })
1019
1025
 
@@ -1597,7 +1603,7 @@ configuration.getErrorEvents().on("all-error", ({error, errorType}) => {
1597
1603
  })
1598
1604
  ```
1599
1605
 
1600
- Genuinely unexpected frontend-model command failures reach this bus too. The frontend-model controller catches them to return a client-safe `Request failed.` response, but it also emits them as `framework-error`/`all-error` (with `context.frontendModelEndpoint === true`) so they are reported instead of being silently swallowed. Expected user-flow errors are excluded: validation failures are forwarded with their real message (for example `Name can't be blank`) rather than the generic one, and `error.velocious`-annotated / `safeToExpose` errors keep their own message — none of these reach the error bus.
1606
+ Genuinely unexpected frontend-model command failures reach this bus too. The frontend-model controller catches them to return a client-safe `Request failed.` response, but it also emits them as `framework-error`/`all-error` (with `context.frontendModelEndpoint === true`) so they are reported instead of being silently swallowed. Expected user-flow errors are excluded: validation failures are forwarded with their real message (for example `Name can't be blank`) rather than the generic one, and `error.velocious`-annotated / `safeToExpose` / `errorType`-marked errors keep their expected-error status — none of these reach the error bus.
1601
1607
 
1602
1608
  ## Use the Websocket client API (HTTP-like)
1603
1609
 
@@ -207,6 +207,29 @@
207
207
  * @typedef {Record<string, string>} VelociousParams
208
208
  */
209
209
 
210
+ /**
211
+ * @typedef {Record<string, import("./frontend-models/query.js").FrontendModelTransportValue>} ClientErrorPayloadReporterPayload
212
+ */
213
+
214
+ /**
215
+ * @typedef {object} ClientErrorPayloadContext
216
+ * @property {string} controller - Controller class name.
217
+ * @property {string} [action] - Controller action or endpoint label.
218
+ * @property {"index" | "find" | "create" | "update" | "destroy" | "attach" | "download" | "url" | "custom-command"} [commandType] - Frontend-model command type.
219
+ * @property {boolean} [expectedError] - Whether the error is an expected user-flow failure.
220
+ * @property {boolean} [frontendModelEndpoint] - Whether the error came from the frontend-model endpoint.
221
+ * @property {string} [model] - Frontend-model name from the failed request.
222
+ * @property {string} [requestId] - Shared frontend-model request id.
223
+ */
224
+
225
+ /**
226
+ * @typedef {function({
227
+ * context: ClientErrorPayloadContext,
228
+ * error: Error,
229
+ * request: import("./http-server/client/request.js").default | import("./http-server/client/websocket-request.js").default | undefined
230
+ * }): Promise<ClientErrorPayloadReporterPayload | void> | ClientErrorPayloadReporterPayload | void} ClientErrorPayloadReporterType
231
+ */
232
+
210
233
  /**
211
234
  * @typedef {Record<string, unknown> & {configuration?: import("./configuration.js").default, currentUser?: unknown, params?: VelociousParams, request?: import("./http-server/client/request.js").default | import("./http-server/client/websocket-request.js").default}} VelociousLooseObject
212
235
  */
@@ -143,7 +143,7 @@ export default class VelociousConfiguration {
143
143
  this._scheduledBackgroundJobs = scheduledBackgroundJobs
144
144
  this._attachments = attachments || {}
145
145
  this._backendProjects = backendProjects || []
146
- /** @type {Array<(args: {context: ?, error: Error, request: ?}) => Promise<Record<string, ?> | void> | Record<string, ?> | void>} */
146
+ /** @type {import("./configuration-types.js").ClientErrorPayloadReporterType[]} */
147
147
  this._clientErrorPayloadReporters = []
148
148
  this.cors = cors
149
149
  this._cookieSecret = cookieSecret
@@ -2386,7 +2386,7 @@ export default class VelociousConfiguration {
2386
2386
 
2387
2387
  /**
2388
2388
  * Registers a reporter that can add client-safe metadata to frontend-model error payloads.
2389
- * @param {(args: {context: ?, error: Error, request: ?}) => Promise<Record<string, ?> | void> | Record<string, ?> | void} reporter - Reporter callback.
2389
+ * @param {import("./configuration-types.js").ClientErrorPayloadReporterType} reporter - Reporter callback.
2390
2390
  * @returns {void}
2391
2391
  */
2392
2392
  addClientErrorPayloadReporter(reporter) {
@@ -2395,11 +2395,11 @@ export default class VelociousConfiguration {
2395
2395
 
2396
2396
  /**
2397
2397
  * Runs registered client error payload reporters.
2398
- * @param {{context: ?, error: Error, request: ?}} args - Reporter args.
2399
- * @returns {Promise<Record<string, ?>>} - Merged client-safe reporter payload.
2398
+ * @param {{context: import("./configuration-types.js").ClientErrorPayloadContext, error: Error, request: import("./http-server/client/request.js").default | import("./http-server/client/websocket-request.js").default | undefined}} args - Reporter args.
2399
+ * @returns {Promise<import("./configuration-types.js").ClientErrorPayloadReporterPayload>} - Merged client-safe reporter payload.
2400
2400
  */
2401
2401
  async clientErrorPayloadForError(args) {
2402
- /** @type {Record<string, ?>} */
2402
+ /** @type {import("./configuration-types.js").ClientErrorPayloadReporterPayload} */
2403
2403
  const payload = {}
2404
2404
 
2405
2405
  for (const reporter of this._clientErrorPayloadReporters) {
@@ -203,7 +203,7 @@ export default class VelociousDatabaseDriversMssql extends Base{
203
203
  * Runs primary key type.
204
204
  * @returns {string} - The primary key type.
205
205
  */
206
- primaryKeyType() { return "bigint" }
206
+ primaryKeyType() { return "uuid" }
207
207
 
208
208
  /**
209
209
  * Runs query actual.
@@ -211,7 +211,7 @@ export default class VelociousDatabaseDriversMysql extends Base{
211
211
  * Runs primary key type.
212
212
  * @returns {string} - The primary key type.
213
213
  */
214
- primaryKeyType() { return "bigint" }
214
+ primaryKeyType() { return "uuid" }
215
215
 
216
216
  /**
217
217
  * Runs retryable database error.
@@ -177,7 +177,7 @@ export default class VelociousDatabaseDriversPgsql extends Base{
177
177
  }
178
178
 
179
179
  getType() { return "pgsql" }
180
- primaryKeyType() { return "bigint" }
180
+ primaryKeyType() { return "uuid" }
181
181
 
182
182
  /**
183
183
  * Runs query actual.
@@ -269,7 +269,7 @@ export default class VelociousDatabaseDriversSqliteBase extends Base {
269
269
  * Runs primary key type.
270
270
  * @returns {string} - The type of the primary key for this driver.
271
271
  */
272
- primaryKeyType() { return "integer" } // Because bigint on SQLite doesn't support auto increment
272
+ primaryKeyType() { return "uuid" }
273
273
 
274
274
  /**
275
275
  * Runs query to sql.
@@ -0,0 +1,121 @@
1
+ // @ts-check
2
+
3
+ import DatabaseRecord from "../index.js"
4
+ import RecordAttachmentsStore from "./store.js"
5
+
6
+ const INTEGER_STRING_PATTERN = /^-?\d+$/
7
+
8
+ /** Frontend-readable metadata row for `velocious_attachments`. */
9
+ export default class VelociousAttachment extends DatabaseRecord {
10
+ /**
11
+ * Returns the backing attachment table name.
12
+ * @returns {string} - Backing attachment table name.
13
+ */
14
+ static tableName() {
15
+ return "velocious_attachments"
16
+ }
17
+
18
+ /**
19
+ * Ensures the framework-owned attachment table exists before loading metadata.
20
+ * @param {object} args - Options object.
21
+ * @param {import("../../../configuration.js").default} args.configuration - Configuration instance.
22
+ * @returns {Promise<void>} - Resolves when complete.
23
+ */
24
+ static async initializeRecord({configuration}) {
25
+ const store = new RecordAttachmentsStore({
26
+ configuration,
27
+ databaseIdentifier: this.getConfiguredDatabaseIdentifier()
28
+ })
29
+
30
+ await store.ensureReady()
31
+ await super.initializeRecord({configuration})
32
+ }
33
+
34
+ /**
35
+ * Returns the attachment id.
36
+ * @returns {string} - Attachment id.
37
+ */
38
+ id() { return this.readAttribute("id") }
39
+
40
+ /**
41
+ * Returns the owner model name.
42
+ * @returns {string} - Owner model name.
43
+ */
44
+ recordType() { return this.readAttribute("recordType") }
45
+
46
+ /**
47
+ * Returns the owner record id.
48
+ * @returns {string} - Owner record id.
49
+ */
50
+ recordId() { return this.readAttribute("recordId") }
51
+
52
+ /**
53
+ * Returns the attachment name on the owner model.
54
+ * @returns {string} - Attachment name on the owner model.
55
+ */
56
+ name() { return this.readAttribute("name") }
57
+
58
+ /**
59
+ * Returns the attachment position.
60
+ * @returns {number} - Attachment position.
61
+ */
62
+ position() { return this.readAttribute("position") }
63
+
64
+ /**
65
+ * Returns the attachment filename.
66
+ * @returns {string} - Attachment filename.
67
+ */
68
+ filename() { return this.readAttribute("filename") }
69
+
70
+ /**
71
+ * Returns the attachment content type.
72
+ * @returns {string | null} - Attachment content type.
73
+ */
74
+ contentType() { return this.readAttribute("contentType") }
75
+
76
+ /**
77
+ * Returns the attachment byte size.
78
+ * @returns {number} - Attachment byte size.
79
+ */
80
+ byteSize() { return this.safeIntegerAttribute({attributeName: "byteSize", expectedDescription: "attachment byte size"}) }
81
+
82
+ /**
83
+ * Returns the created-at timestamp in milliseconds.
84
+ * @returns {number} - Created-at timestamp in milliseconds.
85
+ */
86
+ createdAtMs() { return this.safeIntegerAttribute({attributeName: "createdAtMs", expectedDescription: "safe millisecond timestamp"}) }
87
+
88
+ /**
89
+ * Returns the updated-at timestamp in milliseconds.
90
+ * @returns {number} - Updated-at timestamp in milliseconds.
91
+ */
92
+ updatedAtMs() { return this.safeIntegerAttribute({attributeName: "updatedAtMs", expectedDescription: "safe millisecond timestamp"}) }
93
+
94
+ /**
95
+ * Returns a checked integer attribute value.
96
+ * @param {object} args - Options object.
97
+ * @param {"byteSize" | "createdAtMs" | "updatedAtMs"} args.attributeName - Integer attribute name.
98
+ * @param {string} args.expectedDescription - Description for error messages.
99
+ * @returns {number} - Safe integer value.
100
+ */
101
+ safeIntegerAttribute({attributeName, expectedDescription}) {
102
+ const value = this.readAttribute(attributeName)
103
+ let integer
104
+
105
+ if (typeof value === "number") {
106
+ integer = value
107
+ } else if (typeof value === "bigint") {
108
+ integer = Number(value)
109
+ } else if (typeof value === "string" && INTEGER_STRING_PATTERN.test(value)) {
110
+ integer = Number(value)
111
+ } else {
112
+ throw new Error(`Expected ${attributeName} to be a ${expectedDescription}`)
113
+ }
114
+
115
+ if (!Number.isSafeInteger(integer)) {
116
+ throw new Error(`Expected ${attributeName} to be a ${expectedDescription}`)
117
+ }
118
+
119
+ return integer
120
+ }
121
+ }
@@ -98,6 +98,8 @@ export default class RecordAttachmentsStore {
98
98
 
99
99
  this._readyPromise = (async () => {
100
100
  await this._withDb(async (db) => {
101
+ db.clearSchemaCache()
102
+
101
103
  if (await db.tableExists(ATTACHMENTS_TABLE)) {
102
104
  await this.ensureAttachmentStoreSchema({db})
103
105
  return
@@ -188,7 +188,7 @@ export default class TableData {
188
188
  const referenceArgs = args || {}
189
189
  const reference = new TableReference(name, referenceArgs)
190
190
  const {index, polymorphic, ...restArgs} = referenceArgs
191
- const columnArgs = Object.assign({isNewColumn: true, type: "bigint"}, restArgs)
191
+ const columnArgs = Object.assign({isNewColumn: true, type: "uuid"}, restArgs)
192
192
  const column = new TableColumn(columnName, columnArgs)
193
193
  const indexArgs = typeof index == "object" ? {unique: index.unique === true} : undefined
194
194
  const tableIndex = new TableIndex([column], indexArgs)
@@ -2,7 +2,8 @@ import BaseCommand from "../../../../../cli/base-command.js"
2
2
  import fs from "fs/promises"
3
3
  import path from "node:path"
4
4
  import * as inflection from "inflection"
5
- import {frontendModelResourceClassFromDefinition, frontendModelResourceConfigurationFromDefinition, frontendModelResourcesForBackendProject} from "../../../../../frontend-models/resource-definition.js"
5
+ import {frontendModelResourceIsBuiltIn, frontendModelResourcesWithBuiltInsForBackendProject} from "../../../../../frontend-models/built-in-resources.js"
6
+ import {frontendModelResourceClassFromDefinition, frontendModelResourceConfigurationFromDefinition} from "../../../../../frontend-models/resource-definition.js"
6
7
 
7
8
  /**
8
9
  * Attribute metadata used for generated frontend-model JSDoc.
@@ -110,6 +111,10 @@ export default class DbGenerateFrontendModels extends BaseCommand {
110
111
  this.validateModelConfig({availableFrontendModelClassNames, className, modelConfig, resourceClass})
111
112
 
112
113
  if (generatedModelNames.has(className)) {
114
+ if (frontendModelResourceIsBuiltIn({modelName: modelClassName, resourceDefinition: resources[modelClassName]})) {
115
+ continue
116
+ }
117
+
113
118
  throw new Error(`Duplicate frontend model definition for '${className}'`)
114
119
  }
115
120
 
@@ -191,7 +196,7 @@ export default class DbGenerateFrontendModels extends BaseCommand {
191
196
  * @returns {Record<string, import("../../../../../configuration-types.js").FrontendModelResourceDefinition>} - Resource definitions keyed by model class name.
192
197
  */
193
198
  resourcesForBackendProject(backendProject) {
194
- return frontendModelResourcesForBackendProject(backendProject)
199
+ return frontendModelResourcesWithBuiltInsForBackendProject(backendProject)
195
200
  }
196
201
 
197
202
  /**