velocious 1.0.452 → 1.0.454

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 (91) hide show
  1. package/README.md +23 -4
  2. package/build/configuration-types.js +1 -0
  3. package/build/database/drivers/base.js +8 -0
  4. package/build/database/drivers/mssql/index.js +0 -6
  5. package/build/database/drivers/mysql/index.js +0 -6
  6. package/build/database/drivers/pgsql/index.js +0 -1
  7. package/build/database/drivers/sqlite/base.js +0 -6
  8. package/build/database/migration/index.js +6 -6
  9. package/build/database/record/attachments/attachment-record.js +121 -0
  10. package/build/database/record/attachments/store.js +2 -0
  11. package/build/database/table-data/index.js +2 -1
  12. package/build/database/table-data/table-column.js +7 -1
  13. package/build/environment-handlers/node/cli/commands/generate/frontend-models.js +7 -2
  14. package/build/frontend-model-controller.js +174 -55
  15. package/build/frontend-model-resource/base-resource.js +2 -1
  16. package/build/frontend-model-resource/velocious-attachment-resource.js +221 -0
  17. package/build/frontend-models/base.js +127 -1
  18. package/build/frontend-models/built-in-resources.js +32 -0
  19. package/build/frontend-models/websocket-publishers.js +13 -3
  20. package/build/routes/hooks/frontend-model-command-route-hook.js +3 -2
  21. package/build/src/configuration-types.d.ts +5 -0
  22. package/build/src/configuration-types.d.ts.map +1 -1
  23. package/build/src/configuration-types.js +2 -1
  24. package/build/src/database/drivers/base.d.ts +5 -0
  25. package/build/src/database/drivers/base.d.ts.map +1 -1
  26. package/build/src/database/drivers/base.js +8 -1
  27. package/build/src/database/drivers/mssql/index.d.ts +0 -5
  28. package/build/src/database/drivers/mssql/index.d.ts.map +1 -1
  29. package/build/src/database/drivers/mssql/index.js +1 -6
  30. package/build/src/database/drivers/mysql/index.d.ts +0 -5
  31. package/build/src/database/drivers/mysql/index.d.ts.map +1 -1
  32. package/build/src/database/drivers/mysql/index.js +1 -6
  33. package/build/src/database/drivers/pgsql/index.d.ts +0 -1
  34. package/build/src/database/drivers/pgsql/index.d.ts.map +1 -1
  35. package/build/src/database/drivers/pgsql/index.js +1 -2
  36. package/build/src/database/drivers/sqlite/base.d.ts +0 -5
  37. package/build/src/database/drivers/sqlite/base.d.ts.map +1 -1
  38. package/build/src/database/drivers/sqlite/base.js +1 -6
  39. package/build/src/database/migration/index.js +7 -7
  40. package/build/src/database/record/attachments/attachment-record.d.ts +67 -0
  41. package/build/src/database/record/attachments/attachment-record.d.ts.map +1 -0
  42. package/build/src/database/record/attachments/attachment-record.js +106 -0
  43. package/build/src/database/record/attachments/store.d.ts.map +1 -1
  44. package/build/src/database/record/attachments/store.js +2 -1
  45. package/build/src/database/table-data/index.d.ts +5 -0
  46. package/build/src/database/table-data/index.d.ts.map +1 -1
  47. package/build/src/database/table-data/index.js +3 -2
  48. package/build/src/database/table-data/table-column.d.ts.map +1 -1
  49. package/build/src/database/table-data/table-column.js +10 -2
  50. package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.d.ts.map +1 -1
  51. package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.js +7 -3
  52. package/build/src/frontend-model-controller.d.ts +56 -6
  53. package/build/src/frontend-model-controller.d.ts.map +1 -1
  54. package/build/src/frontend-model-controller.js +154 -52
  55. package/build/src/frontend-model-resource/base-resource.d.ts +2 -0
  56. package/build/src/frontend-model-resource/base-resource.d.ts.map +1 -1
  57. package/build/src/frontend-model-resource/base-resource.js +3 -2
  58. package/build/src/frontend-model-resource/velocious-attachment-resource.d.ts +105 -0
  59. package/build/src/frontend-model-resource/velocious-attachment-resource.d.ts.map +1 -0
  60. package/build/src/frontend-model-resource/velocious-attachment-resource.js +185 -0
  61. package/build/src/frontend-models/base.d.ts +87 -1
  62. package/build/src/frontend-models/base.d.ts.map +1 -1
  63. package/build/src/frontend-models/base.js +112 -2
  64. package/build/src/frontend-models/built-in-resources.d.ts +18 -0
  65. package/build/src/frontend-models/built-in-resources.d.ts.map +1 -0
  66. package/build/src/frontend-models/built-in-resources.js +29 -0
  67. package/build/src/frontend-models/websocket-publishers.d.ts.map +1 -1
  68. package/build/src/frontend-models/websocket-publishers.js +14 -4
  69. package/build/src/routes/hooks/frontend-model-command-route-hook.d.ts.map +1 -1
  70. package/build/src/routes/hooks/frontend-model-command-route-hook.js +4 -3
  71. package/build/tsconfig.tsbuildinfo +1 -1
  72. package/package.json +1 -1
  73. package/src/configuration-types.js +1 -0
  74. package/src/database/drivers/base.js +8 -0
  75. package/src/database/drivers/mssql/index.js +0 -6
  76. package/src/database/drivers/mysql/index.js +0 -6
  77. package/src/database/drivers/pgsql/index.js +0 -1
  78. package/src/database/drivers/sqlite/base.js +0 -6
  79. package/src/database/migration/index.js +6 -6
  80. package/src/database/record/attachments/attachment-record.js +121 -0
  81. package/src/database/record/attachments/store.js +2 -0
  82. package/src/database/table-data/index.js +2 -1
  83. package/src/database/table-data/table-column.js +7 -1
  84. package/src/environment-handlers/node/cli/commands/generate/frontend-models.js +7 -2
  85. package/src/frontend-model-controller.js +174 -55
  86. package/src/frontend-model-resource/base-resource.js +2 -1
  87. package/src/frontend-model-resource/velocious-attachment-resource.js +221 -0
  88. package/src/frontend-models/base.js +127 -1
  89. package/src/frontend-models/built-in-resources.js +32 -0
  90. package/src/frontend-models/websocket-publishers.js +13 -3
  91. package/src/routes/hooks/frontend-model-command-route-hook.js +3 -2
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  * Database models with migrations and validations
6
6
  * Database models that work almost the same in frontend and backend
7
7
  * Declarative state machines for models (see [docs/state-machine.md](docs/state-machine.md))
8
- * Migrations for schema changes
8
+ * Migrations for schema changes (see [docs/database-migrations.md](docs/database-migrations.md))
9
9
  * Controllers and views for HTTP endpoints
10
10
  * Frontend-model transport for creating, updating, querying, and subscribing to query-filtered lifecycle events over HTTP/WebSocket, with structured per-attribute validation error responses (see [docs/frontend-models.md](docs/frontend-models.md))
11
11
  * Expo / Metro compatibility guidance and a real Expo export check (see [docs/expo-metro-compatibility.md](docs/expo-metro-compatibility.md))
@@ -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
 
@@ -1002,6 +1006,21 @@ 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. Set `primaryKeyType` on a database config to change the implicit type for that database, or pass an explicit `id` / reference `type` for legacy schemas and external compatibility.
1010
+
1011
+ ```js
1012
+ export default new Configuration({
1013
+ database: {
1014
+ production: {
1015
+ default: {
1016
+ type: "pgsql",
1017
+ primaryKeyType: "bigint"
1018
+ }
1019
+ }
1020
+ }
1021
+ })
1022
+ ```
1023
+
1005
1024
  ```js
1006
1025
  import Migration from "velocious/build/src/database/migration/index.js"
1007
1026
 
@@ -1011,9 +1030,9 @@ export default class CreateEvents extends Migration {
1011
1030
  t.timestamps()
1012
1031
  })
1013
1032
 
1014
- // UUID primary key
1015
- await this.createTable("uuid_items", {id: {type: "uuid"}}, (t) => {
1016
- t.string("title", {null: false})
1033
+ // Legacy numeric primary key
1034
+ await this.createTable("legacy_events", {id: {type: "bigint"}}, (t) => {
1035
+ t.references("task", {type: "bigint"})
1017
1036
  t.timestamps()
1018
1037
  })
1019
1038
 
@@ -71,6 +71,7 @@
71
71
  * @property {boolean} [migrations] - Whether migrations are enabled for this database.
72
72
  * @property {string} [password] - Password for the database user.
73
73
  * @property {number} [port] - Database port.
74
+ * @property {string} [primaryKeyType] - Default type for implicit migration primary keys and references. Defaults to `uuid`.
74
75
  * @property {DatabasePoolConfiguration} [pool] - Velocious database pool lifecycle configuration.
75
76
  * @property {string} [name] - Friendly name for the configuration.
76
77
  * @property {(file: string) => string} [locateFile] - Optional sqlite-web sql.js wasm resolver (`initSqlJs({locateFile})`).
@@ -406,6 +406,14 @@ export default class VelociousDatabaseDriversBase {
406
406
  return this.idSeq
407
407
  }
408
408
 
409
+ /**
410
+ * Runs primary key type.
411
+ * @returns {string} - Configured primary key type, defaulting to UUID.
412
+ */
413
+ primaryKeyType() {
414
+ return this.getArgs().primaryKeyType || "uuid"
415
+ }
416
+
409
417
  /**
410
418
  * Clears cached schema metadata for this driver instance.
411
419
  * @returns {void} - No return value.
@@ -199,12 +199,6 @@ export default class VelociousDatabaseDriversMssql extends Base{
199
199
  */
200
200
  getType() { return "mssql" }
201
201
 
202
- /**
203
- * Runs primary key type.
204
- * @returns {string} - The primary key type.
205
- */
206
- primaryKeyType() { return "bigint" }
207
-
208
202
  /**
209
203
  * Runs query actual.
210
204
  * @param {string} sql - SQL string.
@@ -207,12 +207,6 @@ export default class VelociousDatabaseDriversMysql extends Base{
207
207
  */
208
208
  getType() { return "mysql" }
209
209
 
210
- /**
211
- * Runs primary key type.
212
- * @returns {string} - The primary key type.
213
- */
214
- primaryKeyType() { return "bigint" }
215
-
216
210
  /**
217
211
  * Runs retryable database error.
218
212
  * @param {Error} error - Error instance.
@@ -177,7 +177,6 @@ export default class VelociousDatabaseDriversPgsql extends Base{
177
177
  }
178
178
 
179
179
  getType() { return "pgsql" }
180
- primaryKeyType() { return "bigint" }
181
180
 
182
181
  /**
183
182
  * Runs query actual.
@@ -265,12 +265,6 @@ export default class VelociousDatabaseDriversSqliteBase extends Base {
265
265
  return this._options
266
266
  }
267
267
 
268
- /**
269
- * Runs primary key type.
270
- * @returns {string} - The type of the primary key for this driver.
271
- */
272
- primaryKeyType() { return "integer" } // Because bigint on SQLite doesn't support auto increment
273
-
274
268
  /**
275
269
  * Runs query to sql.
276
270
  * @param {import("../../query/index.js").default} query - Query instance.
@@ -351,8 +351,8 @@ export default class VelociousDatabaseMigration {
351
351
  }
352
352
 
353
353
  const {id = {}, ifNotExists = false, ...restArgs} = args
354
- const databaseIdentifier = this._getDatabaseIdentifier()
355
- const databasePool = this.configuration.getDatabasePool(databaseIdentifier)
354
+ const driver = this.getDriver()
355
+ const defaultPrimaryKeyType = driver.primaryKeyType()
356
356
  let idDefault, idType, restArgsId
357
357
 
358
358
  if (id !== false) {
@@ -362,9 +362,9 @@ export default class VelociousDatabaseMigration {
362
362
  }
363
363
 
364
364
  if (!idType) {
365
- idType = databasePool.primaryKeyType()
365
+ idType = defaultPrimaryKeyType
366
366
  }
367
- const driverSupportsDefaultUUID = this.getDriver().supportsDefaultPrimaryKeyUUID?.()
367
+ const driverSupportsDefaultUUID = driver.supportsDefaultPrimaryKeyUUID?.()
368
368
  const lowerIdType = idType?.toLowerCase()
369
369
  const isUUIDPrimaryKey = lowerIdType == "uuid"
370
370
  const numericAutoIncrementTypes = ["int", "integer", "bigint", "smallint", "tinyint"]
@@ -384,7 +384,7 @@ export default class VelociousDatabaseMigration {
384
384
  // If driver doesn't support UUID() but the caller explicitly set a default, respect it.
385
385
  }
386
386
 
387
- const tableData = new TableData(tableName, {ifNotExists})
387
+ const tableData = new TableData(tableName, {ifNotExists, primaryKeyType: defaultPrimaryKeyType})
388
388
 
389
389
  restArgsError(restArgs)
390
390
 
@@ -398,7 +398,7 @@ export default class VelociousDatabaseMigration {
398
398
  callback(tableData)
399
399
  }
400
400
 
401
- const sqls = await this.getDriver().createTableSql(tableData)
401
+ const sqls = await driver.createTableSql(tableData)
402
402
 
403
403
  for (const sql of sqls) {
404
404
  await this._db.query(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
@@ -8,6 +8,7 @@ import TableReference from "./table-reference.js"
8
8
  * TableDataArgsType type.
9
9
  * @typedef {object} TableDataArgsType
10
10
  * @property {boolean} ifNotExists - Whether to create the table only if it does not exist.
11
+ * @property {string} [primaryKeyType] - Default type for implicit primary-key references.
11
12
  */
12
13
 
13
14
  export default class TableData {
@@ -188,7 +189,7 @@ export default class TableData {
188
189
  const referenceArgs = args || {}
189
190
  const reference = new TableReference(name, referenceArgs)
190
191
  const {index, polymorphic, ...restArgs} = referenceArgs
191
- const columnArgs = Object.assign({isNewColumn: true, type: "bigint"}, restArgs)
192
+ const columnArgs = Object.assign({isNewColumn: true, type: this.args?.primaryKeyType || "uuid"}, restArgs)
192
193
  const column = new TableColumn(columnName, columnArgs)
193
194
  const indexArgs = typeof index == "object" ? {unique: index.unique === true} : undefined
194
195
  const tableIndex = new TableIndex([column], indexArgs)
@@ -326,7 +326,13 @@ export default class TableColumn {
326
326
  }
327
327
 
328
328
  if (databaseType == "pgsql" && this.getAutoIncrement() && this.getPrimaryKey()) {
329
- type = "SERIAL"
329
+ if (type == "BIGINT") {
330
+ type = "BIGSERIAL"
331
+ } else if (type == "SMALLINT") {
332
+ type = "SMALLSERIAL"
333
+ } else {
334
+ type = "SERIAL"
335
+ }
330
336
  }
331
337
 
332
338
  let sql = `${options.quoteColumnName(this.getActualName())} `
@@ -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
  /**