velocious 1.0.430 → 1.0.431
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/bin/velocious.js +48 -0
- package/build/bin/velocious.js +39 -34
- package/build/index.js +1 -2
- package/build/src/application.js +214 -187
- package/build/src/authorization/ability.d.ts +24 -23
- package/build/src/authorization/ability.d.ts.map +1 -1
- package/build/src/authorization/ability.js +300 -252
- package/build/src/authorization/base-resource.d.ts +20 -26
- package/build/src/authorization/base-resource.d.ts.map +1 -1
- package/build/src/authorization/base-resource.js +136 -118
- package/build/src/background-jobs/client.js +47 -43
- package/build/src/background-jobs/cron-expression.js +166 -127
- package/build/src/background-jobs/forked-runner-child.js +47 -37
- package/build/src/background-jobs/job-record.js +10 -8
- package/build/src/background-jobs/job-registry.js +84 -72
- package/build/src/background-jobs/job-runner.js +81 -74
- package/build/src/background-jobs/job.js +72 -62
- package/build/src/background-jobs/json-socket.js +70 -65
- package/build/src/background-jobs/main.js +900 -841
- package/build/src/background-jobs/normalize-error.js +11 -12
- package/build/src/background-jobs/scheduler.js +247 -205
- package/build/src/background-jobs/socket-request.js +65 -60
- package/build/src/background-jobs/status-reporter.js +96 -86
- package/build/src/background-jobs/store.js +980 -862
- package/build/src/background-jobs/types.js +3 -2
- package/build/src/background-jobs/web/authorization.js +50 -38
- package/build/src/background-jobs/web/controller.js +268 -232
- package/build/src/background-jobs/web/index.js +40 -36
- package/build/src/background-jobs/web/path-matcher.js +48 -45
- package/build/src/background-jobs/web/registry.js +14 -9
- package/build/src/background-jobs/worker.js +639 -585
- package/build/src/beacon/client.js +293 -264
- package/build/src/beacon/in-process-broker.js +25 -20
- package/build/src/beacon/in-process-client.js +116 -104
- package/build/src/beacon/server.js +126 -110
- package/build/src/beacon/types.js +8 -2
- package/build/src/cli/base-command.js +57 -49
- package/build/src/cli/browser-cli.js +42 -37
- package/build/src/cli/commands/background-jobs-main.js +5 -5
- package/build/src/cli/commands/background-jobs-runner.js +5 -5
- package/build/src/cli/commands/background-jobs-worker.js +5 -5
- package/build/src/cli/commands/beacon.js +5 -5
- package/build/src/cli/commands/console.js +10 -10
- package/build/src/cli/commands/db/base-command.js +76 -71
- package/build/src/cli/commands/db/create.js +61 -53
- package/build/src/cli/commands/db/drop.js +71 -62
- package/build/src/cli/commands/db/migrate.js +15 -13
- package/build/src/cli/commands/db/reset.js +19 -16
- package/build/src/cli/commands/db/rollback.js +13 -12
- package/build/src/cli/commands/db/schema/dump.js +9 -9
- package/build/src/cli/commands/db/schema/load.js +9 -9
- package/build/src/cli/commands/db/seed.js +9 -9
- package/build/src/cli/commands/db/tenants/check.js +35 -32
- package/build/src/cli/commands/db/tenants/create.js +29 -26
- package/build/src/cli/commands/db/tenants/migrate.js +44 -40
- package/build/src/cli/commands/destroy/migration.js +5 -5
- package/build/src/cli/commands/generate/base-models.js +5 -5
- package/build/src/cli/commands/generate/frontend-models.js +9 -9
- package/build/src/cli/commands/generate/migration.js +5 -5
- package/build/src/cli/commands/generate/model.js +5 -5
- package/build/src/cli/commands/init.js +9 -7
- package/build/src/cli/commands/routes.js +6 -6
- package/build/src/cli/commands/run-script.js +9 -9
- package/build/src/cli/commands/runner.js +9 -9
- package/build/src/cli/commands/server.js +6 -6
- package/build/src/cli/commands/test.js +7 -6
- package/build/src/cli/index.js +141 -127
- package/build/src/cli/tenant-database-command-helper.js +185 -154
- package/build/src/cli/use-browser-cli.js +20 -15
- package/build/src/configuration-resolver.js +54 -47
- package/build/src/configuration-types.d.ts +21 -2
- package/build/src/configuration-types.d.ts.map +1 -1
- package/build/src/configuration-types.js +60 -3
- package/build/src/configuration.js +2547 -2240
- package/build/src/controller.js +407 -363
- package/build/src/current-configuration.js +12 -9
- package/build/src/current.js +75 -70
- package/build/src/database/annotations-async-hooks.js +22 -16
- package/build/src/database/annotations.js +18 -12
- package/build/src/database/drivers/base-column.js +179 -155
- package/build/src/database/drivers/base-columns-index.js +78 -69
- package/build/src/database/drivers/base-foreign-key.js +101 -89
- package/build/src/database/drivers/base-table.js +149 -124
- package/build/src/database/drivers/base.js +1489 -1306
- package/build/src/database/drivers/mssql/column.js +50 -39
- package/build/src/database/drivers/mssql/columns-index.js +3 -2
- package/build/src/database/drivers/mssql/connect-connection.js +9 -11
- package/build/src/database/drivers/mssql/foreign-key.js +9 -8
- package/build/src/database/drivers/mssql/index.js +587 -507
- package/build/src/database/drivers/mssql/options.js +75 -68
- package/build/src/database/drivers/mssql/query-parser.js +3 -2
- package/build/src/database/drivers/mssql/sql/alter-table.js +2 -2
- package/build/src/database/drivers/mssql/sql/create-database.js +31 -24
- package/build/src/database/drivers/mssql/sql/create-index.js +2 -2
- package/build/src/database/drivers/mssql/sql/create-table.js +2 -2
- package/build/src/database/drivers/mssql/sql/delete.js +16 -14
- package/build/src/database/drivers/mssql/sql/drop-database.js +31 -24
- package/build/src/database/drivers/mssql/sql/drop-table.js +2 -2
- package/build/src/database/drivers/mssql/sql/insert.js +2 -2
- package/build/src/database/drivers/mssql/sql/update.js +28 -24
- package/build/src/database/drivers/mssql/sql/upsert.js +20 -18
- package/build/src/database/drivers/mssql/structure-sql.js +114 -102
- package/build/src/database/drivers/mssql/table.js +96 -81
- package/build/src/database/drivers/mysql/column.js +92 -75
- package/build/src/database/drivers/mysql/columns-index.js +19 -16
- package/build/src/database/drivers/mysql/foreign-key.js +9 -8
- package/build/src/database/drivers/mysql/index.js +457 -396
- package/build/src/database/drivers/mysql/options.js +30 -26
- package/build/src/database/drivers/mysql/query-parser.js +3 -2
- package/build/src/database/drivers/mysql/query.js +29 -26
- package/build/src/database/drivers/mysql/sql/alter-table.js +3 -2
- package/build/src/database/drivers/mysql/sql/create-database.js +28 -23
- package/build/src/database/drivers/mysql/sql/create-index.js +3 -2
- package/build/src/database/drivers/mysql/sql/create-table.js +3 -2
- package/build/src/database/drivers/mysql/sql/delete.js +17 -14
- package/build/src/database/drivers/mysql/sql/drop-database.js +3 -2
- package/build/src/database/drivers/mysql/sql/drop-table.js +3 -2
- package/build/src/database/drivers/mysql/sql/insert.js +3 -2
- package/build/src/database/drivers/mysql/sql/update.js +29 -24
- package/build/src/database/drivers/mysql/sql/upsert.js +10 -8
- package/build/src/database/drivers/mysql/structure-sql.js +88 -79
- package/build/src/database/drivers/mysql/table.js +98 -83
- package/build/src/database/drivers/pgsql/column.js +72 -56
- package/build/src/database/drivers/pgsql/columns-index.js +3 -2
- package/build/src/database/drivers/pgsql/foreign-key.js +9 -8
- package/build/src/database/drivers/pgsql/index.js +438 -377
- package/build/src/database/drivers/pgsql/options.js +28 -25
- package/build/src/database/drivers/pgsql/query-parser.js +3 -2
- package/build/src/database/drivers/pgsql/sql/alter-table.js +3 -2
- package/build/src/database/drivers/pgsql/sql/create-database.js +23 -19
- package/build/src/database/drivers/pgsql/sql/create-index.js +3 -2
- package/build/src/database/drivers/pgsql/sql/create-table.js +3 -2
- package/build/src/database/drivers/pgsql/sql/delete.js +17 -14
- package/build/src/database/drivers/pgsql/sql/drop-database.js +3 -2
- package/build/src/database/drivers/pgsql/sql/drop-table.js +3 -2
- package/build/src/database/drivers/pgsql/sql/insert.js +3 -2
- package/build/src/database/drivers/pgsql/sql/update.js +29 -24
- package/build/src/database/drivers/pgsql/sql/upsert.js +11 -9
- package/build/src/database/drivers/pgsql/structure-sql.js +120 -108
- package/build/src/database/drivers/pgsql/table.js +77 -60
- package/build/src/database/drivers/sqlite/base.js +478 -405
- package/build/src/database/drivers/sqlite/column.js +69 -54
- package/build/src/database/drivers/sqlite/columns-index.js +27 -22
- package/build/src/database/drivers/sqlite/connection-sql-js.js +42 -35
- package/build/src/database/drivers/sqlite/foreign-key.js +21 -18
- package/build/src/database/drivers/sqlite/index.js +373 -330
- package/build/src/database/drivers/sqlite/index.native.js +64 -55
- package/build/src/database/drivers/sqlite/index.web.js +87 -69
- package/build/src/database/drivers/sqlite/options.js +28 -25
- package/build/src/database/drivers/sqlite/query-parser.js +3 -2
- package/build/src/database/drivers/sqlite/query.js +24 -21
- package/build/src/database/drivers/sqlite/query.native.js +25 -20
- package/build/src/database/drivers/sqlite/query.web.js +37 -30
- package/build/src/database/drivers/sqlite/sql/alter-table.js +179 -159
- package/build/src/database/drivers/sqlite/sql/create-index.js +3 -2
- package/build/src/database/drivers/sqlite/sql/create-table.js +3 -2
- package/build/src/database/drivers/sqlite/sql/delete.js +22 -17
- package/build/src/database/drivers/sqlite/sql/drop-table.js +3 -2
- package/build/src/database/drivers/sqlite/sql/insert.js +3 -2
- package/build/src/database/drivers/sqlite/sql/update.js +29 -24
- package/build/src/database/drivers/sqlite/sql/upsert.js +11 -9
- package/build/src/database/drivers/sqlite/structure-sql.js +52 -49
- package/build/src/database/drivers/sqlite/table-rebuilder.js +75 -62
- package/build/src/database/drivers/sqlite/table.js +125 -102
- package/build/src/database/drivers/structure-sql/utils.js +17 -14
- package/build/src/database/handler.js +10 -9
- package/build/src/database/initializer-from-require-context.js +87 -76
- package/build/src/database/migration/index.js +395 -332
- package/build/src/database/migrator/files-finder.js +50 -40
- package/build/src/database/migrator/types.js +30 -2
- package/build/src/database/migrator.js +526 -454
- package/build/src/database/pool/async-tracked-multi-connection.js +1147 -997
- package/build/src/database/pool/base-methods-forward.js +43 -40
- package/build/src/database/pool/base.js +343 -298
- package/build/src/database/pool/single-multi-use.js +110 -93
- package/build/src/database/query/alter-table-base.js +99 -84
- package/build/src/database/query/base.js +46 -39
- package/build/src/database/query/create-database-base.js +30 -25
- package/build/src/database/query/create-index-base.js +94 -75
- package/build/src/database/query/create-table-base.js +193 -151
- package/build/src/database/query/delete-base.js +16 -14
- package/build/src/database/query/drop-database-base.js +28 -23
- package/build/src/database/query/drop-table-base.js +53 -42
- package/build/src/database/query/from-base.js +33 -30
- package/build/src/database/query/from-plain.js +13 -11
- package/build/src/database/query/from-table.js +15 -13
- package/build/src/database/query/index.js +472 -410
- package/build/src/database/query/insert-base.js +164 -143
- package/build/src/database/query/join-base.js +40 -35
- package/build/src/database/query/join-object.js +153 -128
- package/build/src/database/query/join-plain.js +15 -13
- package/build/src/database/query/join-tracker.js +90 -76
- package/build/src/database/query/model-class-query.js +1370 -1134
- package/build/src/database/query/order-base.js +30 -27
- package/build/src/database/query/order-column.js +53 -44
- package/build/src/database/query/order-plain.js +24 -20
- package/build/src/database/query/preloader/belongs-to.js +258 -210
- package/build/src/database/query/preloader/ensure-model-class-initialized.js +9 -8
- package/build/src/database/query/preloader/has-many.js +301 -240
- package/build/src/database/query/preloader/has-one.js +117 -91
- package/build/src/database/query/preloader/selection.js +129 -117
- package/build/src/database/query/preloader.js +185 -160
- package/build/src/database/query/query-data.js +201 -157
- package/build/src/database/query/select-base.js +27 -25
- package/build/src/database/query/select-plain.js +15 -13
- package/build/src/database/query/select-table-and-column.js +25 -21
- package/build/src/database/query/update-base.js +38 -35
- package/build/src/database/query/upsert-base.js +100 -93
- package/build/src/database/query/where-base.js +35 -32
- package/build/src/database/query/where-combinator.d.ts.map +1 -1
- package/build/src/database/query/where-combinator.js +28 -26
- package/build/src/database/query/where-hash.js +68 -61
- package/build/src/database/query/where-model-class-hash.js +469 -414
- package/build/src/database/query/where-not.js +20 -18
- package/build/src/database/query/where-plain.js +17 -15
- package/build/src/database/query/with-count.js +159 -125
- package/build/src/database/query-parser/base-query-parser.js +37 -32
- package/build/src/database/query-parser/from-parser.js +45 -36
- package/build/src/database/query-parser/group-parser.js +50 -42
- package/build/src/database/query-parser/joins-parser.js +33 -28
- package/build/src/database/query-parser/limit-parser.js +70 -67
- package/build/src/database/query-parser/options.js +82 -75
- package/build/src/database/query-parser/order-parser.js +40 -36
- package/build/src/database/query-parser/select-parser.js +60 -49
- package/build/src/database/query-parser/where-parser.js +41 -36
- package/build/src/database/record/acts-as-list.js +273 -235
- package/build/src/database/record/attachments/download.js +45 -44
- package/build/src/database/record/attachments/handle.js +161 -141
- package/build/src/database/record/attachments/normalize-input.js +138 -128
- package/build/src/database/record/attachments/storage-drivers/filesystem.js +91 -77
- package/build/src/database/record/attachments/storage-drivers/native.js +121 -112
- package/build/src/database/record/attachments/storage-drivers/s3.js +208 -177
- package/build/src/database/record/attachments/store.d.ts +1 -1
- package/build/src/database/record/attachments/store.d.ts.map +1 -1
- package/build/src/database/record/attachments/store.js +540 -468
- package/build/src/database/record/index.d.ts +17 -15
- package/build/src/database/record/index.d.ts.map +1 -1
- package/build/src/database/record/index.js +3894 -3361
- package/build/src/database/record/instance-relationships/base.js +268 -234
- package/build/src/database/record/instance-relationships/belongs-to.js +73 -58
- package/build/src/database/record/instance-relationships/has-many.js +264 -225
- package/build/src/database/record/instance-relationships/has-one.js +105 -85
- package/build/src/database/record/record-not-found-error.js +2 -3
- package/build/src/database/record/relationships/base.d.ts +2 -2
- package/build/src/database/record/relationships/base.d.ts.map +1 -1
- package/build/src/database/record/relationships/base.js +167 -145
- package/build/src/database/record/relationships/belongs-to.js +51 -44
- package/build/src/database/record/relationships/has-many.js +40 -32
- package/build/src/database/record/relationships/has-one.js +40 -32
- package/build/src/database/record/state-machine.js +208 -156
- package/build/src/database/record/user-module.js +38 -32
- package/build/src/database/record/validators/base.js +24 -22
- package/build/src/database/record/validators/format.js +46 -36
- package/build/src/database/record/validators/presence.js +20 -18
- package/build/src/database/record/validators/uniqueness.js +117 -99
- package/build/src/database/table-data/index.js +231 -199
- package/build/src/database/table-data/table-column.js +382 -338
- package/build/src/database/table-data/table-foreign-key.js +66 -57
- package/build/src/database/table-data/table-index.js +36 -29
- package/build/src/database/table-data/table-reference.js +10 -10
- package/build/src/database/use-database.js +40 -32
- package/build/src/environment-handlers/base.js +544 -484
- package/build/src/environment-handlers/browser.js +294 -241
- package/build/src/environment-handlers/node/cli/commands/background-jobs-main.js +19 -16
- package/build/src/environment-handlers/node/cli/commands/background-jobs-runner.js +21 -18
- package/build/src/environment-handlers/node/cli/commands/background-jobs-worker.js +29 -22
- package/build/src/environment-handlers/node/cli/commands/beacon.js +19 -16
- package/build/src/environment-handlers/node/cli/commands/cli-command-context.js +15 -14
- package/build/src/environment-handlers/node/cli/commands/console.js +120 -99
- package/build/src/environment-handlers/node/cli/commands/db/schema/dump.js +39 -34
- package/build/src/environment-handlers/node/cli/commands/db/schema/load.js +63 -57
- package/build/src/environment-handlers/node/cli/commands/db/seed.js +63 -51
- package/build/src/environment-handlers/node/cli/commands/destroy/migration.js +40 -32
- package/build/src/environment-handlers/node/cli/commands/generate/base-models.d.ts.map +1 -1
- package/build/src/environment-handlers/node/cli/commands/generate/base-models.js +353 -298
- package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.js +844 -729
- package/build/src/environment-handlers/node/cli/commands/generate/migration.js +38 -34
- package/build/src/environment-handlers/node/cli/commands/generate/model.js +38 -34
- package/build/src/environment-handlers/node/cli/commands/init.js +61 -56
- package/build/src/environment-handlers/node/cli/commands/routes.js +59 -51
- package/build/src/environment-handlers/node/cli/commands/run-script.js +68 -54
- package/build/src/environment-handlers/node/cli/commands/runner.js +74 -56
- package/build/src/environment-handlers/node/cli/commands/server.js +106 -93
- package/build/src/environment-handlers/node/cli/commands/test.js +113 -97
- package/build/src/environment-handlers/node.js +874 -753
- package/build/src/error-logger.js +21 -22
- package/build/src/frontend-model-controller.d.ts +6 -6
- package/build/src/frontend-model-controller.d.ts.map +1 -1
- package/build/src/frontend-model-controller.js +3288 -2788
- package/build/src/frontend-model-resource/base-resource.d.ts +18 -17
- package/build/src/frontend-model-resource/base-resource.d.ts.map +1 -1
- package/build/src/frontend-model-resource/base-resource.js +869 -759
- package/build/src/frontend-models/base.d.ts +19 -12
- package/build/src/frontend-models/base.d.ts.map +1 -1
- package/build/src/frontend-models/base.js +3602 -3114
- package/build/src/frontend-models/clear-pending-debounced-callback.js +8 -7
- package/build/src/frontend-models/event-hook-models.js +21 -16
- package/build/src/frontend-models/model-registry.js +11 -9
- package/build/src/frontend-models/outgoing-event-buffer.js +17 -10
- package/build/src/frontend-models/preloader.d.ts +6 -6
- package/build/src/frontend-models/preloader.d.ts.map +1 -1
- package/build/src/frontend-models/preloader.js +149 -131
- package/build/src/frontend-models/query.d.ts.map +1 -1
- package/build/src/frontend-models/query.js +1855 -1560
- package/build/src/frontend-models/resource-config-validation.js +37 -27
- package/build/src/frontend-models/resource-definition.js +288 -234
- package/build/src/frontend-models/transport-serialization.js +266 -203
- package/build/src/frontend-models/use-created-event.js +7 -5
- package/build/src/frontend-models/use-destroyed-event.js +93 -80
- package/build/src/frontend-models/use-model-class-event.js +91 -79
- package/build/src/frontend-models/use-updated-event.js +97 -84
- package/build/src/frontend-models/websocket-channel.js +441 -381
- package/build/src/frontend-models/websocket-publishers.js +173 -140
- package/build/src/http-client/header.js +14 -13
- package/build/src/http-client/index.js +132 -116
- package/build/src/http-client/request.js +87 -71
- package/build/src/http-client/response.js +140 -122
- package/build/src/http-client/websocket-client.js +17 -15
- package/build/src/http-server/client/index.js +465 -409
- package/build/src/http-server/client/params-to-object.js +135 -124
- package/build/src/http-server/client/request-buffer/form-data-part.js +132 -111
- package/build/src/http-server/client/request-buffer/header.js +16 -15
- package/build/src/http-server/client/request-buffer/index.js +506 -446
- package/build/src/http-server/client/request-parser.js +186 -163
- package/build/src/http-server/client/request-runner.js +259 -226
- package/build/src/http-server/client/request-timing.js +151 -132
- package/build/src/http-server/client/request.js +108 -96
- package/build/src/http-server/client/response.js +235 -213
- package/build/src/http-server/client/uploaded-file/memory-uploaded-file.js +29 -25
- package/build/src/http-server/client/uploaded-file/temporary-uploaded-file.js +29 -25
- package/build/src/http-server/client/uploaded-file/uploaded-file.js +33 -33
- package/build/src/http-server/client/websocket-request.js +137 -114
- package/build/src/http-server/client/websocket-session.js +1657 -1452
- package/build/src/http-server/cookie.js +236 -216
- package/build/src/http-server/development-reloader.js +221 -190
- package/build/src/http-server/index.js +525 -451
- package/build/src/http-server/remote-address.js +50 -38
- package/build/src/http-server/server-client.js +208 -181
- package/build/src/http-server/server-lock.js +167 -153
- package/build/src/http-server/websocket-channel-subscribers.js +93 -81
- package/build/src/http-server/websocket-channel.js +117 -104
- package/build/src/http-server/websocket-connection.js +104 -96
- package/build/src/http-server/websocket-event-log-store.js +404 -350
- package/build/src/http-server/websocket-events-host.js +164 -145
- package/build/src/http-server/websocket-events.js +47 -47
- package/build/src/http-server/worker-handler/channel-subscriber-dispatch.js +14 -13
- package/build/src/http-server/worker-handler/in-process.js +141 -123
- package/build/src/http-server/worker-handler/index.js +349 -313
- package/build/src/http-server/worker-handler/worker-script.js +5 -4
- package/build/src/http-server/worker-handler/worker-thread.js +269 -240
- package/build/src/initializer.js +36 -31
- package/build/src/jobs/mail-delivery.js +15 -13
- package/build/src/logger/base-logger.js +26 -24
- package/build/src/logger/console-logger.js +23 -21
- package/build/src/logger/file-logger.js +31 -29
- package/build/src/logger/outputs/array-output.js +42 -37
- package/build/src/logger/outputs/console-output.js +24 -20
- package/build/src/logger/outputs/file-output.js +48 -43
- package/build/src/logger/outputs/stdout-output.js +48 -39
- package/build/src/logger.js +394 -338
- package/build/src/mailer/backends/smtp.js +163 -134
- package/build/src/mailer/base.js +251 -211
- package/build/src/mailer/delivery.js +64 -56
- package/build/src/mailer/index.js +22 -4
- package/build/src/mailer.js +13 -4
- package/build/src/plugins/sqljs-wasm-route-controller.js +52 -42
- package/build/src/plugins/sqljs-wasm-route.js +38 -28
- package/build/src/record-payload-values.js +28 -25
- package/build/src/routes/app-routes.js +14 -12
- package/build/src/routes/base-route.js +130 -112
- package/build/src/routes/basic-route.js +102 -83
- package/build/src/routes/built-in/debug/controller.js +10 -10
- package/build/src/routes/built-in/errors/controller.js +5 -5
- package/build/src/routes/get-route.js +63 -50
- package/build/src/routes/hooks/frontend-model-command-route-hook.js +80 -66
- package/build/src/routes/index.js +43 -36
- package/build/src/routes/namespace-route.js +47 -38
- package/build/src/routes/plugin-routes.js +124 -107
- package/build/src/routes/post-route.js +62 -51
- package/build/src/routes/resolver.js +494 -422
- package/build/src/routes/resource-route.js +143 -124
- package/build/src/routes/root-route.js +8 -7
- package/build/src/testing/base-expect.js +14 -13
- package/build/src/testing/browser-frontend-model-event-hook-scenarios.js +405 -329
- package/build/src/testing/browser-test-app.js +29 -23
- package/build/src/testing/expect-to-change.js +50 -41
- package/build/src/testing/expect-utils.js +184 -139
- package/build/src/testing/expect.js +731 -638
- package/build/src/testing/request-client.js +85 -70
- package/build/src/testing/test-files-finder.js +339 -285
- package/build/src/testing/test-filter-parser.js +155 -124
- package/build/src/testing/test-runner.js +1020 -883
- package/build/src/testing/test-suite-splitter.js +142 -114
- package/build/src/testing/test.js +256 -216
- package/build/src/utils/backtrace-cleaner-node.js +69 -62
- package/build/src/utils/backtrace-cleaner.js +216 -188
- package/build/src/utils/ensure-error.js +7 -7
- package/build/src/utils/event-emitter.js +6 -4
- package/build/src/utils/file-exists.js +10 -9
- package/build/src/utils/format-value.js +76 -67
- package/build/src/utils/model-scope.js +31 -27
- package/build/src/utils/nest-callbacks.js +13 -10
- package/build/src/utils/plain-object.js +6 -5
- package/build/src/utils/ransack.d.ts.map +1 -1
- package/build/src/utils/ransack.js +563 -449
- package/build/src/utils/rest-args-error.js +6 -5
- package/build/src/utils/singularize-model-name.js +11 -9
- package/build/src/utils/split-sql-statements.js +79 -68
- package/build/src/utils/to-import-specifier.js +30 -24
- package/build/src/utils/with-tracked-stack-async-hooks.js +74 -60
- package/build/src/utils/with-tracked-stack.js +18 -14
- package/build/src/velocious-error.js +30 -27
- package/index.js +1 -0
- package/package.json +10 -4
- package/scripts/clean-build.js +8 -0
- package/scripts/ensure-bin-executable.js +13 -0
- package/scripts/run-tests.js +37 -0
- package/scripts/test-browser.js +486 -0
- package/src/application.js +229 -0
- package/src/authorization/ability.js +329 -0
- package/src/authorization/base-resource.js +143 -0
- package/src/background-jobs/client.js +50 -0
- package/src/background-jobs/cron-expression.js +277 -0
- package/src/background-jobs/forked-runner-child.js +86 -0
- package/src/background-jobs/job-record.js +13 -0
- package/src/background-jobs/job-registry.js +92 -0
- package/src/background-jobs/job-runner.js +107 -0
- package/src/background-jobs/job.js +77 -0
- package/src/background-jobs/json-socket.js +78 -0
- package/src/background-jobs/main.js +926 -0
- package/src/background-jobs/normalize-error.js +26 -0
- package/src/background-jobs/scheduler.js +274 -0
- package/src/background-jobs/socket-request.js +68 -0
- package/src/background-jobs/status-reporter.js +101 -0
- package/src/background-jobs/store.js +994 -0
- package/src/background-jobs/types.js +70 -0
- package/src/background-jobs/web/authorization.js +89 -0
- package/src/background-jobs/web/controller.js +280 -0
- package/src/background-jobs/web/index.js +57 -0
- package/src/background-jobs/web/path-matcher.js +74 -0
- package/src/background-jobs/web/registry.js +49 -0
- package/src/background-jobs/worker.js +683 -0
- package/src/beacon/client.js +330 -0
- package/src/beacon/in-process-broker.js +71 -0
- package/src/beacon/in-process-client.js +139 -0
- package/src/beacon/server.js +148 -0
- package/src/beacon/types.js +55 -0
- package/src/cli/base-command.js +67 -0
- package/src/cli/browser-cli.js +45 -0
- package/src/cli/commands/background-jobs-main.js +7 -0
- package/src/cli/commands/background-jobs-runner.js +7 -0
- package/src/cli/commands/background-jobs-worker.js +7 -0
- package/src/cli/commands/beacon.js +7 -0
- package/src/cli/commands/console.js +12 -0
- package/src/cli/commands/db/base-command.js +82 -0
- package/src/cli/commands/db/create.js +64 -0
- package/src/cli/commands/db/drop.js +75 -0
- package/src/cli/commands/db/migrate.js +17 -0
- package/src/cli/commands/db/reset.js +22 -0
- package/src/cli/commands/db/rollback.js +15 -0
- package/src/cli/commands/db/schema/dump.js +12 -0
- package/src/cli/commands/db/schema/load.js +12 -0
- package/src/cli/commands/db/seed.js +12 -0
- package/src/cli/commands/db/tenants/check.js +38 -0
- package/src/cli/commands/db/tenants/create.js +33 -0
- package/src/cli/commands/db/tenants/migrate.js +49 -0
- package/src/cli/commands/destroy/migration.js +7 -0
- package/src/cli/commands/generate/base-models.js +7 -0
- package/src/cli/commands/generate/frontend-models.js +12 -0
- package/src/cli/commands/generate/migration.js +7 -0
- package/src/cli/commands/generate/model.js +7 -0
- package/src/cli/commands/init.js +11 -0
- package/src/cli/commands/routes.js +7 -0
- package/src/cli/commands/run-script.js +12 -0
- package/src/cli/commands/runner.js +12 -0
- package/src/cli/commands/server.js +7 -0
- package/src/cli/commands/test.js +9 -0
- package/src/cli/index.js +152 -0
- package/src/cli/tenant-database-command-helper.js +198 -0
- package/src/cli/use-browser-cli.js +30 -0
- package/src/configuration-resolver.js +65 -0
- package/src/configuration-types.js +429 -0
- package/src/configuration.js +2590 -0
- package/src/controller.js +421 -0
- package/src/current-configuration.js +31 -0
- package/src/current.js +80 -0
- package/src/database/annotations-async-hooks.js +47 -0
- package/src/database/annotations.js +40 -0
- package/src/database/drivers/base-column.js +182 -0
- package/src/database/drivers/base-columns-index.js +81 -0
- package/src/database/drivers/base-foreign-key.js +104 -0
- package/src/database/drivers/base-table.js +156 -0
- package/src/database/drivers/base.js +1609 -0
- package/src/database/drivers/mssql/column.js +74 -0
- package/src/database/drivers/mssql/columns-index.js +6 -0
- package/src/database/drivers/mssql/connect-connection.js +16 -0
- package/src/database/drivers/mssql/foreign-key.js +12 -0
- package/src/database/drivers/mssql/index.js +590 -0
- package/src/database/drivers/mssql/options.js +79 -0
- package/src/database/drivers/mssql/query-parser.js +6 -0
- package/src/database/drivers/mssql/sql/alter-table.js +4 -0
- package/src/database/drivers/mssql/sql/create-database.js +36 -0
- package/src/database/drivers/mssql/sql/create-index.js +4 -0
- package/src/database/drivers/mssql/sql/create-table.js +4 -0
- package/src/database/drivers/mssql/sql/delete.js +19 -0
- package/src/database/drivers/mssql/sql/drop-database.js +36 -0
- package/src/database/drivers/mssql/sql/drop-table.js +4 -0
- package/src/database/drivers/mssql/sql/insert.js +4 -0
- package/src/database/drivers/mssql/sql/update.js +31 -0
- package/src/database/drivers/mssql/sql/upsert.js +23 -0
- package/src/database/drivers/mssql/structure-sql.js +120 -0
- package/src/database/drivers/mssql/table.js +145 -0
- package/src/database/drivers/mysql/column.js +112 -0
- package/src/database/drivers/mysql/columns-index.js +22 -0
- package/src/database/drivers/mysql/foreign-key.js +12 -0
- package/src/database/drivers/mysql/index.js +473 -0
- package/src/database/drivers/mysql/options.js +34 -0
- package/src/database/drivers/mysql/query-parser.js +6 -0
- package/src/database/drivers/mysql/query.js +37 -0
- package/src/database/drivers/mysql/sql/alter-table.js +6 -0
- package/src/database/drivers/mysql/sql/create-database.js +39 -0
- package/src/database/drivers/mysql/sql/create-index.js +6 -0
- package/src/database/drivers/mysql/sql/create-table.js +6 -0
- package/src/database/drivers/mysql/sql/delete.js +21 -0
- package/src/database/drivers/mysql/sql/drop-database.js +6 -0
- package/src/database/drivers/mysql/sql/drop-table.js +6 -0
- package/src/database/drivers/mysql/sql/insert.js +6 -0
- package/src/database/drivers/mysql/sql/update.js +33 -0
- package/src/database/drivers/mysql/sql/upsert.js +13 -0
- package/src/database/drivers/mysql/structure-sql.js +93 -0
- package/src/database/drivers/mysql/table.js +121 -0
- package/src/database/drivers/pgsql/column.js +90 -0
- package/src/database/drivers/pgsql/columns-index.js +6 -0
- package/src/database/drivers/pgsql/foreign-key.js +12 -0
- package/src/database/drivers/pgsql/index.js +441 -0
- package/src/database/drivers/pgsql/options.js +32 -0
- package/src/database/drivers/pgsql/query-parser.js +6 -0
- package/src/database/drivers/pgsql/sql/alter-table.js +6 -0
- package/src/database/drivers/pgsql/sql/create-database.js +38 -0
- package/src/database/drivers/pgsql/sql/create-index.js +6 -0
- package/src/database/drivers/pgsql/sql/create-table.js +6 -0
- package/src/database/drivers/pgsql/sql/delete.js +21 -0
- package/src/database/drivers/pgsql/sql/drop-database.js +6 -0
- package/src/database/drivers/pgsql/sql/drop-table.js +6 -0
- package/src/database/drivers/pgsql/sql/insert.js +6 -0
- package/src/database/drivers/pgsql/sql/update.js +33 -0
- package/src/database/drivers/pgsql/sql/upsert.js +14 -0
- package/src/database/drivers/pgsql/structure-sql.js +126 -0
- package/src/database/drivers/pgsql/table.js +135 -0
- package/src/database/drivers/sqlite/base.js +509 -0
- package/src/database/drivers/sqlite/column.js +75 -0
- package/src/database/drivers/sqlite/columns-index.js +30 -0
- package/src/database/drivers/sqlite/connection-sql-js.js +46 -0
- package/src/database/drivers/sqlite/foreign-key.js +24 -0
- package/src/database/drivers/sqlite/index.js +394 -0
- package/src/database/drivers/sqlite/index.native.js +72 -0
- package/src/database/drivers/sqlite/index.web.js +99 -0
- package/src/database/drivers/sqlite/options.js +32 -0
- package/src/database/drivers/sqlite/query-parser.js +6 -0
- package/src/database/drivers/sqlite/query.js +35 -0
- package/src/database/drivers/sqlite/query.native.js +35 -0
- package/src/database/drivers/sqlite/query.web.js +49 -0
- package/src/database/drivers/sqlite/sql/alter-table.js +187 -0
- package/src/database/drivers/sqlite/sql/create-index.js +6 -0
- package/src/database/drivers/sqlite/sql/create-table.js +6 -0
- package/src/database/drivers/sqlite/sql/delete.js +26 -0
- package/src/database/drivers/sqlite/sql/drop-table.js +6 -0
- package/src/database/drivers/sqlite/sql/insert.js +6 -0
- package/src/database/drivers/sqlite/sql/update.js +33 -0
- package/src/database/drivers/sqlite/sql/upsert.js +14 -0
- package/src/database/drivers/sqlite/structure-sql.js +56 -0
- package/src/database/drivers/sqlite/table-rebuilder.js +96 -0
- package/src/database/drivers/sqlite/table.js +131 -0
- package/src/database/drivers/structure-sql/utils.js +35 -0
- package/src/database/handler.js +13 -0
- package/src/database/initializer-from-require-context.js +101 -0
- package/src/database/migration/index.js +438 -0
- package/src/database/migrator/files-finder.js +55 -0
- package/src/database/migrator/types.js +31 -0
- package/src/database/migrator.js +557 -0
- package/src/database/pool/async-tracked-multi-connection.js +1164 -0
- package/src/database/pool/base-methods-forward.js +52 -0
- package/src/database/pool/base.js +380 -0
- package/src/database/pool/single-multi-use.js +118 -0
- package/src/database/query/alter-table-base.js +104 -0
- package/src/database/query/base.js +49 -0
- package/src/database/query/create-database-base.js +42 -0
- package/src/database/query/create-index-base.js +117 -0
- package/src/database/query/create-table-base.js +205 -0
- package/src/database/query/delete-base.js +19 -0
- package/src/database/query/drop-database-base.js +38 -0
- package/src/database/query/drop-table-base.js +58 -0
- package/src/database/query/from-base.js +36 -0
- package/src/database/query/from-plain.js +16 -0
- package/src/database/query/from-table.js +18 -0
- package/src/database/query/index.js +533 -0
- package/src/database/query/insert-base.js +172 -0
- package/src/database/query/join-base.js +43 -0
- package/src/database/query/join-object.js +167 -0
- package/src/database/query/join-plain.js +18 -0
- package/src/database/query/join-tracker.js +93 -0
- package/src/database/query/model-class-query.js +1577 -0
- package/src/database/query/order-base.js +33 -0
- package/src/database/query/order-column.js +77 -0
- package/src/database/query/order-plain.js +28 -0
- package/src/database/query/preloader/belongs-to.js +267 -0
- package/src/database/query/preloader/ensure-model-class-initialized.js +18 -0
- package/src/database/query/preloader/has-many.js +316 -0
- package/src/database/query/preloader/has-one.js +123 -0
- package/src/database/query/preloader/selection.js +152 -0
- package/src/database/query/preloader.js +201 -0
- package/src/database/query/query-data.js +305 -0
- package/src/database/query/select-base.js +30 -0
- package/src/database/query/select-plain.js +18 -0
- package/src/database/query/select-table-and-column.js +28 -0
- package/src/database/query/update-base.js +41 -0
- package/src/database/query/upsert-base.js +103 -0
- package/src/database/query/where-base.js +38 -0
- package/src/database/query/where-combinator.js +31 -0
- package/src/database/query/where-hash.js +77 -0
- package/src/database/query/where-model-class-hash.js +505 -0
- package/src/database/query/where-not.js +23 -0
- package/src/database/query/where-plain.js +20 -0
- package/src/database/query/with-count.js +219 -0
- package/src/database/query-parser/base-query-parser.js +40 -0
- package/src/database/query-parser/from-parser.js +49 -0
- package/src/database/query-parser/group-parser.js +55 -0
- package/src/database/query-parser/joins-parser.js +37 -0
- package/src/database/query-parser/limit-parser.js +77 -0
- package/src/database/query-parser/options.js +94 -0
- package/src/database/query-parser/order-parser.js +45 -0
- package/src/database/query-parser/select-parser.js +67 -0
- package/src/database/query-parser/where-parser.js +46 -0
- package/src/database/record/acts-as-list.js +374 -0
- package/src/database/record/attachments/download.js +49 -0
- package/src/database/record/attachments/handle.js +188 -0
- package/src/database/record/attachments/normalize-input.js +213 -0
- package/src/database/record/attachments/storage-drivers/filesystem.js +114 -0
- package/src/database/record/attachments/storage-drivers/native.js +146 -0
- package/src/database/record/attachments/storage-drivers/s3.js +245 -0
- package/src/database/record/attachments/store.js +591 -0
- package/src/database/record/index.js +3970 -0
- package/src/database/record/instance-relationships/base.js +289 -0
- package/src/database/record/instance-relationships/belongs-to.js +84 -0
- package/src/database/record/instance-relationships/has-many.js +284 -0
- package/src/database/record/instance-relationships/has-one.js +117 -0
- package/src/database/record/record-not-found-error.js +3 -0
- package/src/database/record/relationships/base.js +195 -0
- package/src/database/record/relationships/belongs-to.js +57 -0
- package/src/database/record/relationships/has-many.js +46 -0
- package/src/database/record/relationships/has-one.js +46 -0
- package/src/database/record/state-machine.js +278 -0
- package/src/database/record/user-module.js +43 -0
- package/src/database/record/validators/base.js +27 -0
- package/src/database/record/validators/format.js +50 -0
- package/src/database/record/validators/presence.js +24 -0
- package/src/database/record/validators/uniqueness.js +124 -0
- package/src/database/table-data/index.js +241 -0
- package/src/database/table-data/table-column.js +416 -0
- package/src/database/table-data/table-foreign-key.js +69 -0
- package/src/database/table-data/table-index.js +46 -0
- package/src/database/table-data/table-reference.js +13 -0
- package/src/database/use-database.js +48 -0
- package/src/environment-handlers/base.js +561 -0
- package/src/environment-handlers/browser.js +338 -0
- package/src/environment-handlers/node/cli/commands/background-jobs-main.js +21 -0
- package/src/environment-handlers/node/cli/commands/background-jobs-runner.js +24 -0
- package/src/environment-handlers/node/cli/commands/background-jobs-worker.js +47 -0
- package/src/environment-handlers/node/cli/commands/beacon.js +21 -0
- package/src/environment-handlers/node/cli/commands/cli-command-context.js +31 -0
- package/src/environment-handlers/node/cli/commands/console.js +149 -0
- package/src/environment-handlers/node/cli/commands/db/schema/dump.js +43 -0
- package/src/environment-handlers/node/cli/commands/db/schema/load.js +69 -0
- package/src/environment-handlers/node/cli/commands/db/seed.js +79 -0
- package/src/environment-handlers/node/cli/commands/destroy/migration.js +47 -0
- package/src/environment-handlers/node/cli/commands/generate/base-models.js +367 -0
- package/src/environment-handlers/node/cli/commands/generate/frontend-models.js +872 -0
- package/src/environment-handlers/node/cli/commands/generate/migration.js +45 -0
- package/src/environment-handlers/node/cli/commands/generate/model.js +45 -0
- package/src/environment-handlers/node/cli/commands/init.js +68 -0
- package/src/environment-handlers/node/cli/commands/routes.js +63 -0
- package/src/environment-handlers/node/cli/commands/run-script.js +85 -0
- package/src/environment-handlers/node/cli/commands/runner.js +84 -0
- package/src/environment-handlers/node/cli/commands/server.js +151 -0
- package/src/environment-handlers/node/cli/commands/test.js +118 -0
- package/src/environment-handlers/node.js +887 -0
- package/src/error-logger.js +30 -0
- package/src/frontend-model-controller.js +3491 -0
- package/src/frontend-model-resource/base-resource.js +935 -0
- package/src/frontend-models/base.js +4004 -0
- package/src/frontend-models/clear-pending-debounced-callback.js +16 -0
- package/src/frontend-models/event-hook-models.js +49 -0
- package/src/frontend-models/model-registry.js +28 -0
- package/src/frontend-models/outgoing-event-buffer.js +51 -0
- package/src/frontend-models/preloader.js +169 -0
- package/src/frontend-models/query.js +2245 -0
- package/src/frontend-models/resource-config-validation.js +56 -0
- package/src/frontend-models/resource-definition.js +399 -0
- package/src/frontend-models/transport-serialization.js +369 -0
- package/src/frontend-models/use-created-event.js +21 -0
- package/src/frontend-models/use-destroyed-event.js +148 -0
- package/src/frontend-models/use-model-class-event.js +164 -0
- package/src/frontend-models/use-updated-event.js +152 -0
- package/src/frontend-models/websocket-channel.js +494 -0
- package/src/frontend-models/websocket-publishers.js +224 -0
- package/src/http-client/header.js +17 -0
- package/src/http-client/index.js +139 -0
- package/src/http-client/request.js +94 -0
- package/src/http-client/response.js +151 -0
- package/src/http-client/websocket-client.js +27 -0
- package/src/http-server/client/index.js +507 -0
- package/src/http-server/client/params-to-object.js +152 -0
- package/src/http-server/client/request-buffer/form-data-part.js +139 -0
- package/src/http-server/client/request-buffer/header.js +19 -0
- package/src/http-server/client/request-buffer/index.js +535 -0
- package/src/http-server/client/request-parser.js +195 -0
- package/src/http-server/client/request-runner.js +321 -0
- package/src/http-server/client/request-timing.js +171 -0
- package/src/http-server/client/request.js +114 -0
- package/src/http-server/client/response.js +251 -0
- package/src/http-server/client/uploaded-file/memory-uploaded-file.js +32 -0
- package/src/http-server/client/uploaded-file/temporary-uploaded-file.js +32 -0
- package/src/http-server/client/uploaded-file/uploaded-file.js +36 -0
- package/src/http-server/client/websocket-request.js +147 -0
- package/src/http-server/client/websocket-session.js +1755 -0
- package/src/http-server/cookie.js +245 -0
- package/src/http-server/development-reloader.js +240 -0
- package/src/http-server/index.js +561 -0
- package/src/http-server/remote-address.js +77 -0
- package/src/http-server/server-client.js +222 -0
- package/src/http-server/server-lock.js +178 -0
- package/src/http-server/websocket-channel-subscribers.js +110 -0
- package/src/http-server/websocket-channel.js +137 -0
- package/src/http-server/websocket-connection.js +118 -0
- package/src/http-server/websocket-event-log-store.js +433 -0
- package/src/http-server/websocket-events-host.js +170 -0
- package/src/http-server/websocket-events.js +50 -0
- package/src/http-server/worker-handler/channel-subscriber-dispatch.js +28 -0
- package/src/http-server/worker-handler/in-process.js +155 -0
- package/src/http-server/worker-handler/index.js +370 -0
- package/src/http-server/worker-handler/worker-script.js +6 -0
- package/src/http-server/worker-handler/worker-thread.js +286 -0
- package/src/initializer.js +39 -0
- package/src/jobs/.gitkeep +1 -0
- package/src/jobs/mail-delivery.js +22 -0
- package/src/logger/base-logger.js +34 -0
- package/src/logger/console-logger.js +28 -0
- package/src/logger/file-logger.js +36 -0
- package/src/logger/outputs/array-output.js +50 -0
- package/src/logger/outputs/console-output.js +32 -0
- package/src/logger/outputs/file-output.js +55 -0
- package/src/logger/outputs/stdout-output.js +64 -0
- package/src/logger.js +507 -0
- package/src/mailer/backends/smtp.js +197 -0
- package/src/mailer/base.js +337 -0
- package/src/mailer/delivery.js +70 -0
- package/src/mailer/index.js +24 -0
- package/src/mailer.js +15 -0
- package/src/plugins/sqljs-wasm-route-controller.js +70 -0
- package/src/plugins/sqljs-wasm-route.js +71 -0
- package/src/record-payload-values.js +83 -0
- package/src/routes/app-routes.js +17 -0
- package/src/routes/base-route.js +133 -0
- package/src/routes/basic-route.js +109 -0
- package/src/routes/built-in/debug/controller.js +12 -0
- package/src/routes/built-in/errors/controller.js +7 -0
- package/src/routes/built-in/errors/not-found.ejs +1 -0
- package/src/routes/get-route.js +75 -0
- package/src/routes/hooks/frontend-model-command-route-hook.js +100 -0
- package/src/routes/index.js +50 -0
- package/src/routes/namespace-route.js +51 -0
- package/src/routes/plugin-routes.js +141 -0
- package/src/routes/post-route.js +74 -0
- package/src/routes/resolver.js +535 -0
- package/src/routes/resource-route.js +154 -0
- package/src/routes/root-route.js +11 -0
- package/src/templates/configuration.js +61 -0
- package/src/templates/generate-migration.js +11 -0
- package/src/templates/generate-model.js +6 -0
- package/src/templates/routes.js +11 -0
- package/src/testing/base-expect.js +17 -0
- package/src/testing/browser-frontend-model-event-hook-scenarios.js +520 -0
- package/src/testing/browser-test-app.js +32 -0
- package/src/testing/expect-to-change.js +55 -0
- package/src/testing/expect-utils.js +269 -0
- package/src/testing/expect.js +763 -0
- package/src/testing/request-client.js +90 -0
- package/src/testing/test-files-finder.js +364 -0
- package/src/testing/test-filter-parser.js +198 -0
- package/src/testing/test-runner.js +1168 -0
- package/src/testing/test-suite-splitter.js +177 -0
- package/src/testing/test.js +370 -0
- package/src/types/external-modules.d.ts +57 -0
- package/src/utils/backtrace-cleaner-node.js +87 -0
- package/src/utils/backtrace-cleaner.js +266 -0
- package/src/utils/ensure-error.js +15 -0
- package/src/utils/event-emitter.js +8 -0
- package/src/utils/file-exists.js +18 -0
- package/src/utils/format-value.js +101 -0
- package/src/utils/model-scope.js +56 -0
- package/src/utils/nest-callbacks.js +22 -0
- package/src/utils/plain-object.js +14 -0
- package/src/utils/ransack.js +859 -0
- package/src/utils/rest-args-error.js +14 -0
- package/src/utils/singularize-model-name.js +18 -0
- package/src/utils/split-sql-statements.js +88 -0
- package/src/utils/to-import-specifier.js +53 -0
- package/src/utils/with-tracked-stack-async-hooks.js +103 -0
- package/src/utils/with-tracked-stack.js +38 -0
- package/src/velocious-error.js +34 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,935 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import AuthorizationBaseResource from "../authorization/base-resource.js"
|
|
4
|
+
import * as inflection from "inflection"
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* FrontendModelResourceControllerArgs type.
|
|
8
|
+
* @typedef {object} FrontendModelResourceControllerArgs
|
|
9
|
+
* @property {import("../controller.js").default} controller - Frontend-model controller instance.
|
|
10
|
+
* @property {typeof import("../database/record/index.js").default} modelClass - Backing model class.
|
|
11
|
+
* @property {string} modelName - Model name.
|
|
12
|
+
* @property {import("../configuration-types.js").VelociousLooseObject} params - Request params.
|
|
13
|
+
* @property {import("../configuration-types.js").NormalizedFrontendModelResourceConfiguration | import("../configuration-types.js").FrontendModelResourceConfiguration} resourceConfiguration - Normalized resource configuration (or raw input shape during early bootstrap).
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* FrontendModelResourceAbilityArgs type.
|
|
18
|
+
* @typedef {object} FrontendModelResourceAbilityArgs
|
|
19
|
+
* @property {import("../authorization/ability.js").default} [ability] - Ability instance when the resource is used directly for authorization.
|
|
20
|
+
* @property {import("../configuration-types.js").VelociousLooseObject} [context] - Ability context.
|
|
21
|
+
* @property {import("../configuration-types.js").VelociousLooseObject} [locals] - Ability locals.
|
|
22
|
+
* @property {typeof import("../database/record/index.js").default} [modelClass] - Optional backing model class override.
|
|
23
|
+
* @property {string} [modelName] - Optional model name override.
|
|
24
|
+
* @property {import("../configuration-types.js").VelociousLooseObject} [params] - Optional params override.
|
|
25
|
+
* @property {import("../configuration-types.js").NormalizedFrontendModelResourceConfiguration | import("../configuration-types.js").FrontendModelResourceConfiguration} [resourceConfiguration] - Optional normalized resource configuration.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Base class for backend frontend-model resources.
|
|
30
|
+
*/
|
|
31
|
+
export default class FrontendModelBaseResource extends AuthorizationBaseResource {
|
|
32
|
+
/**
|
|
33
|
+
* Backing model class.
|
|
34
|
+
@type {typeof import("../database/record/index.js").default | undefined} */
|
|
35
|
+
static ModelClass = undefined
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Attributes.
|
|
39
|
+
@type {Record<string, ?> | string[] | undefined} */
|
|
40
|
+
static attributes = undefined
|
|
41
|
+
/**
|
|
42
|
+
* Abilities.
|
|
43
|
+
@type {string[] | undefined} */
|
|
44
|
+
static abilities = undefined
|
|
45
|
+
/**
|
|
46
|
+
* Attachments.
|
|
47
|
+
@type {Record<string, ?> | undefined} */
|
|
48
|
+
static attachments = undefined
|
|
49
|
+
/**
|
|
50
|
+
* Collection commands.
|
|
51
|
+
@type {string[] | undefined} */
|
|
52
|
+
static collectionCommands = undefined
|
|
53
|
+
/**
|
|
54
|
+
* Built in collection commands.
|
|
55
|
+
@type {string[] | undefined} */
|
|
56
|
+
static builtInCollectionCommands = undefined
|
|
57
|
+
/**
|
|
58
|
+
* Member commands.
|
|
59
|
+
@type {string[] | undefined} */
|
|
60
|
+
static memberCommands = undefined
|
|
61
|
+
/**
|
|
62
|
+
* Built in member commands.
|
|
63
|
+
@type {string[] | undefined} */
|
|
64
|
+
static builtInMemberCommands = undefined
|
|
65
|
+
/**
|
|
66
|
+
* Relationships.
|
|
67
|
+
@type {string[] | undefined} */
|
|
68
|
+
static relationships = undefined
|
|
69
|
+
/**
|
|
70
|
+
* Translated attributes.
|
|
71
|
+
@type {string[] | undefined} */
|
|
72
|
+
static translatedAttributes = undefined
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Runs constructor.
|
|
76
|
+
* @param {FrontendModelResourceAbilityArgs | FrontendModelResourceControllerArgs} args - Resource args.
|
|
77
|
+
*/
|
|
78
|
+
constructor(args) {
|
|
79
|
+
super({
|
|
80
|
+
ability: "ability" in args ? args.ability : undefined,
|
|
81
|
+
context: "context" in args ? args.context || {} : {},
|
|
82
|
+
locals: "locals" in args ? args.locals || {} : {}
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
this.controller = "controller" in args ? args.controller : undefined
|
|
86
|
+
this.modelClassValue = "modelClass" in args ? args.modelClass : /**
|
|
87
|
+
* Narrows the runtime value to the documented type.
|
|
88
|
+
@type {typeof import("../database/record/index.js").default | undefined} */ (/**
|
|
89
|
+
* Narrows the runtime value to the documented type.
|
|
90
|
+
@type {typeof FrontendModelBaseResource} */ (this.constructor).modelClass())
|
|
91
|
+
this.modelNameValue = "modelName" in args ? args.modelName : (this.modelClassValue?.getModelName ? this.modelClassValue.getModelName() : this.modelClassValue?.name || "")
|
|
92
|
+
this.paramsValue = "params" in args ? args.params : undefined
|
|
93
|
+
this.resourceConfigurationValue = "resourceConfiguration" in args ? args.resourceConfiguration : /**
|
|
94
|
+
* Narrows the runtime value to the documented type.
|
|
95
|
+
@type {import("../configuration-types.js").FrontendModelResourceConfiguration} */ ({attributes: []})
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Runs typed controller instance.
|
|
100
|
+
* @returns {import("../controller.js").default & {
|
|
101
|
+
* frontendModelAuthorizedQuery: (action: "index" | "find" | "create" | "update" | "destroy" | "attach" | "download" | "url") => import("../database/query/model-class-query.js").default<typeof import("../database/record/index.js").default>,
|
|
102
|
+
* frontendModelIndexQuery: () => import("../database/query/model-class-query.js").default<typeof import("../database/record/index.js").default>,
|
|
103
|
+
* frontendModelPreload: () => import("../database/query/index.js").NestedPreloadRecord | null,
|
|
104
|
+
* serializeFrontendModel: (model: import("../database/record/index.js").default) => Promise<Record<string, unknown>>
|
|
105
|
+
* }} - Controller instance with frontend-model helpers.
|
|
106
|
+
*/
|
|
107
|
+
typedControllerInstance() {
|
|
108
|
+
return /** Narrows the runtime value to the documented type. @type {?} */ (this.controller)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Runs resource config.
|
|
113
|
+
* @returns {import("../configuration-types.js").FrontendModelResourceConfiguration} - Static resource config (raw user input shape; consumers normalize).
|
|
114
|
+
*/
|
|
115
|
+
static resourceConfig() {
|
|
116
|
+
/**
|
|
117
|
+
* Config.
|
|
118
|
+
@type {import("../configuration-types.js").FrontendModelResourceConfiguration} */
|
|
119
|
+
const config = {
|
|
120
|
+
attributes: this.attributes || []
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (this.abilities) config.abilities = this.abilities
|
|
124
|
+
if (this.attachments) config.attachments = this.attachments
|
|
125
|
+
if (this.builtInCollectionCommands) config.builtInCollectionCommands = this.builtInCollectionCommands
|
|
126
|
+
if (this.builtInMemberCommands) config.builtInMemberCommands = this.builtInMemberCommands
|
|
127
|
+
if (this.collectionCommands) config.collectionCommands = this.collectionCommands
|
|
128
|
+
if (this.memberCommands) config.memberCommands = this.memberCommands
|
|
129
|
+
if (this.relationships) config.relationships = this.relationships
|
|
130
|
+
|
|
131
|
+
return config
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Runs static model class.
|
|
136
|
+
* @returns {typeof import("../database/record/index.js").default | undefined} - Backing model class.
|
|
137
|
+
*/
|
|
138
|
+
static modelClass() {
|
|
139
|
+
return this.ModelClass
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Runs controller instance.
|
|
144
|
+
* @returns {import("../controller.js").default} - Controller instance.
|
|
145
|
+
*/
|
|
146
|
+
controllerInstance() {
|
|
147
|
+
if (!this.controller) throw new Error(`${this.constructor.name} requires a controller instance.`)
|
|
148
|
+
|
|
149
|
+
return this.controller
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Runs model class.
|
|
154
|
+
* @returns {typeof import("../database/record/index.js").default} - Model class.
|
|
155
|
+
*/
|
|
156
|
+
modelClass() {
|
|
157
|
+
if (!this.modelClassValue) {
|
|
158
|
+
throw new Error(`${this.constructor.name} requires a model class.`)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return this.modelClassValue
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Runs model name.
|
|
166
|
+
* @returns {string} - Model name.
|
|
167
|
+
*/
|
|
168
|
+
modelName() {
|
|
169
|
+
if (!this.modelNameValue) throw new Error(`${this.constructor.name} requires a model name.`)
|
|
170
|
+
|
|
171
|
+
return this.modelNameValue
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Runs params.
|
|
176
|
+
* @returns {import("../configuration-types.js").VelociousLooseObject} - Params.
|
|
177
|
+
*/
|
|
178
|
+
params() { return this.paramsValue || super.params() || {} }
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Runs resource configuration.
|
|
182
|
+
* @returns {import("../configuration-types.js").NormalizedFrontendModelResourceConfiguration | import("../configuration-types.js").FrontendModelResourceConfiguration} - Resource config (normalized at runtime; raw during early bootstrap).
|
|
183
|
+
*/
|
|
184
|
+
resourceConfiguration() {
|
|
185
|
+
if (!this.resourceConfigurationValue) throw new Error(`${this.constructor.name} requires a resource configuration.`)
|
|
186
|
+
|
|
187
|
+
return this.resourceConfigurationValue
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Returns a Rails-strong-params / api_maker-style permit spec declaring
|
|
192
|
+
* which attributes and nested attributes are writable for the current
|
|
193
|
+
* request. Submitting an attribute or nested-relationship key that is
|
|
194
|
+
* not permitted raises an error and fails the write.
|
|
195
|
+
*
|
|
196
|
+
* The returned value is a flat array that mixes:
|
|
197
|
+
* - `"attributeName"` strings for plain attribute writes
|
|
198
|
+
* - `{<relationshipName>Attributes: [...]}` objects where the value
|
|
199
|
+
* is itself a permit spec for the nested relationship
|
|
200
|
+
*
|
|
201
|
+
* This matches Rails strong_params (`permit(:first_name, :last_name,
|
|
202
|
+
* contact_attributes: [:email, details_attributes: [:detail]])`) and
|
|
203
|
+
* the api_maker sister project. Include `"_destroy"` inside a nested
|
|
204
|
+
* permit to allow `_destroy: true` entries for that relationship —
|
|
205
|
+
* the model must also declare `acceptsNestedAttributesFor(name,
|
|
206
|
+
* {allowDestroy: true})` for the destroy to be applied.
|
|
207
|
+
*
|
|
208
|
+
* Example:
|
|
209
|
+
*
|
|
210
|
+
* class ProjectResource extends FrontendModelBaseResource {
|
|
211
|
+
* permittedParams(arg) {
|
|
212
|
+
* return [
|
|
213
|
+
* "name",
|
|
214
|
+
* "description",
|
|
215
|
+
* {tasksAttributes: ["id", "_destroy", "name",
|
|
216
|
+
* {subtasksAttributes: ["id", "_destroy", "name"]}
|
|
217
|
+
* ]}
|
|
218
|
+
* ]
|
|
219
|
+
* }
|
|
220
|
+
* }
|
|
221
|
+
*
|
|
222
|
+
* Default implementation returns `[]` — nothing permitted. Subclasses
|
|
223
|
+
* must override to enable writes. A resource that does not declare
|
|
224
|
+
* `permittedParams` cannot accept any write.
|
|
225
|
+
* @param {{action?: "create" | "update", params?: Record<string, ?>, ability?: import("../authorization/ability.js").default, locals?: Record<string, ?>}} [arg] - Request context.
|
|
226
|
+
* @returns {Array<string | Record<string, ?>>} - Permit spec.
|
|
227
|
+
*/
|
|
228
|
+
permittedParams(arg) {
|
|
229
|
+
void arg
|
|
230
|
+
|
|
231
|
+
return []
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Runs primary key.
|
|
236
|
+
* @returns {string} - Primary key.
|
|
237
|
+
*/
|
|
238
|
+
primaryKey() { return this.modelClass().primaryKey() }
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Runs authorized query.
|
|
242
|
+
* @param {"index" | "find" | "create" | "update" | "destroy" | "attach" | "download" | "url"} action - Ability action.
|
|
243
|
+
* @template {typeof import("../database/record/index.js").default} [MC=typeof import("../database/record/index.js").default]
|
|
244
|
+
* @returns {import("../database/query/model-class-query.js").default<MC>} - Authorized query.
|
|
245
|
+
*/
|
|
246
|
+
authorizedQuery(action) {
|
|
247
|
+
return /** Narrows the authorized query to the resource's model class. @type {import("../database/query/model-class-query.js").default<MC>} */ (this.typedControllerInstance().frontendModelAuthorizedQuery(action))
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Runs supports pluck.
|
|
252
|
+
* @param {"index" | "find" | "create" | "update" | "destroy" | "attach" | "download" | "url"} action - Action.
|
|
253
|
+
* @returns {boolean | Promise<boolean>} - Whether pluck is supported.
|
|
254
|
+
*/
|
|
255
|
+
supportsPluck(action) {
|
|
256
|
+
void action
|
|
257
|
+
|
|
258
|
+
return Object.getPrototypeOf(this).records === FrontendModelBaseResource.prototype.records
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Runs supports count.
|
|
263
|
+
* @param {"index" | "find" | "create" | "update" | "destroy" | "attach" | "download" | "url"} action - Action.
|
|
264
|
+
* @returns {boolean | Promise<boolean>} - Whether count is supported.
|
|
265
|
+
*/
|
|
266
|
+
supportsCount(action) {
|
|
267
|
+
void action
|
|
268
|
+
|
|
269
|
+
return Object.getPrototypeOf(this).records === FrontendModelBaseResource.prototype.records ||
|
|
270
|
+
Object.getPrototypeOf(this).count !== FrontendModelBaseResource.prototype.count
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Runs before action.
|
|
275
|
+
* @param {"index" | "find" | "create" | "update" | "destroy" | "attach" | "download" | "url"} action - Action.
|
|
276
|
+
* @returns {boolean | void | Promise<boolean | void>} - Continue processing unless false.
|
|
277
|
+
*/
|
|
278
|
+
beforeAction(action) {
|
|
279
|
+
void action
|
|
280
|
+
|
|
281
|
+
// No-op by default.
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Runs records.
|
|
286
|
+
* @returns {Promise<import("../database/record/index.js").default[]>} - Records for index action.
|
|
287
|
+
*/
|
|
288
|
+
async records() {
|
|
289
|
+
return await this.typedControllerInstance().frontendModelIndexQuery().toArray()
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Runs count.
|
|
294
|
+
* @returns {Promise<number>} - Records count for index action.
|
|
295
|
+
*/
|
|
296
|
+
async count() {
|
|
297
|
+
return await this.typedControllerInstance().frontendModelIndexQuery().count()
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Runs find.
|
|
302
|
+
* @param {"find" | "update" | "destroy" | "attach" | "download" | "url"} action - Action.
|
|
303
|
+
* @param {string | number} id - Record id.
|
|
304
|
+
* @returns {Promise<import("../database/record/index.js").default | null>} - Located model.
|
|
305
|
+
*/
|
|
306
|
+
async find(action, id) {
|
|
307
|
+
let query = this.authorizedQuery(action)
|
|
308
|
+
const preload = action === "find" ? this.typedControllerInstance().frontendModelPreload() : null
|
|
309
|
+
|
|
310
|
+
if (preload) {
|
|
311
|
+
query = query.preload(preload)
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return await query.findBy({[this.primaryKey()]: id})
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Runs create.
|
|
319
|
+
* @param {Record<string, ?>} attributes - Create attributes.
|
|
320
|
+
* @param {{controller?: ?, nestedAttributes?: Record<string, ?> | null}} [options] - Save options.
|
|
321
|
+
* @returns {Promise<import("../database/record/index.js").default>} - Created model.
|
|
322
|
+
*/
|
|
323
|
+
async create(attributes, options = {}) {
|
|
324
|
+
const permit = parsePermittedParams(this.permittedParams({action: "create", ability: this.ability, locals: this.locals, params: attributes}))
|
|
325
|
+
const filtered = filterWritableFrontendModelAttributes(this.modelClass().prototype, attributes, this, permit.attributes)
|
|
326
|
+
const ModelClass = this.modelClass()
|
|
327
|
+
const model = new ModelClass()
|
|
328
|
+
|
|
329
|
+
await ModelClass.transaction(async () => {
|
|
330
|
+
await this._assignWithVirtualSetters(model, filtered)
|
|
331
|
+
await model.save()
|
|
332
|
+
|
|
333
|
+
if (options.nestedAttributes) {
|
|
334
|
+
await this._applyNestedAttributes(model, options.nestedAttributes, options.controller || null, permit)
|
|
335
|
+
}
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
await this._preloadNestedWritableRelationships(model, permit)
|
|
339
|
+
|
|
340
|
+
return model
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Runs handle unauthorized created model.
|
|
345
|
+
* @param {import("../database/record/index.js").default} model - Created model.
|
|
346
|
+
* @returns {Promise<void>} - Cleanup after failed authorization.
|
|
347
|
+
*/
|
|
348
|
+
async handleUnauthorizedCreatedModel(model) {
|
|
349
|
+
await model.destroy()
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Runs update.
|
|
354
|
+
* @param {import("../database/record/index.js").default} model - Existing model.
|
|
355
|
+
* @param {Record<string, ?>} attributes - Update attributes.
|
|
356
|
+
* @param {{controller?: ?, nestedAttributes?: Record<string, ?> | null}} [options] - Save options.
|
|
357
|
+
* @returns {Promise<import("../database/record/index.js").default>} - Updated model.
|
|
358
|
+
*/
|
|
359
|
+
async update(model, attributes, options = {}) {
|
|
360
|
+
const permit = parsePermittedParams(this.permittedParams({action: "update", ability: this.ability, locals: this.locals, params: attributes}))
|
|
361
|
+
const filtered = filterWritableFrontendModelAttributes(model, attributes, this, permit.attributes)
|
|
362
|
+
const ModelClass = this.modelClass()
|
|
363
|
+
|
|
364
|
+
await ModelClass.transaction(async () => {
|
|
365
|
+
await this._assignWithVirtualSetters(model, filtered)
|
|
366
|
+
await model.save()
|
|
367
|
+
|
|
368
|
+
if (options.nestedAttributes) {
|
|
369
|
+
await this._applyNestedAttributes(model, options.nestedAttributes, options.controller || null, permit)
|
|
370
|
+
}
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
await this._preloadNestedWritableRelationships(model, permit)
|
|
374
|
+
|
|
375
|
+
return model
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Assigns attributes to a model, using virtual setters on the resource when available.
|
|
380
|
+
* @param {import("../database/record/index.js").default} model - Model instance.
|
|
381
|
+
* @param {Record<string, ?>} attributes - Attributes to assign.
|
|
382
|
+
* @returns {Promise<void>}
|
|
383
|
+
*/
|
|
384
|
+
async _assignWithVirtualSetters(model, attributes) {
|
|
385
|
+
/**
|
|
386
|
+
* Direct attributes.
|
|
387
|
+
@type {Record<string, ?>} */
|
|
388
|
+
const directAttributes = {}
|
|
389
|
+
const translatedSet = new Set(/**
|
|
390
|
+
* Narrows the runtime value to the documented type.
|
|
391
|
+
@type {typeof FrontendModelBaseResource} */ (this.constructor).translatedAttributes || [])
|
|
392
|
+
|
|
393
|
+
for (const [name, value] of Object.entries(attributes)) {
|
|
394
|
+
const resourceSetterName = `set${inflection.camelize(name)}Attribute`
|
|
395
|
+
|
|
396
|
+
if (typeof /**
|
|
397
|
+
* Narrows the runtime value to the documented type.
|
|
398
|
+
@type {Record<string, ?>} */ (/**
|
|
399
|
+
* Narrows the runtime value to the documented type.
|
|
400
|
+
@type {?} */ (this))[resourceSetterName] === "function") {
|
|
401
|
+
await /**
|
|
402
|
+
* Narrows the runtime value to the documented type.
|
|
403
|
+
@type {Record<string, ?>} */ (/**
|
|
404
|
+
* Narrows the runtime value to the documented type.
|
|
405
|
+
@type {?} */ (this))[resourceSetterName](model, value)
|
|
406
|
+
} else if (translatedSet.has(name)) {
|
|
407
|
+
await this._setTranslatedAttributeOnModel(model, name, value)
|
|
408
|
+
} else {
|
|
409
|
+
directAttributes[name] = value
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (Object.keys(directAttributes).length > 0) {
|
|
414
|
+
model.assign(directAttributes)
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Sets a translated attribute on a model via the translations relationship.
|
|
420
|
+
* @param {import("../database/record/index.js").default} model - Model instance.
|
|
421
|
+
* @param {string} name - Attribute name.
|
|
422
|
+
* @param {?} value - Attribute value.
|
|
423
|
+
* @returns {Promise<void>}
|
|
424
|
+
*/
|
|
425
|
+
async _setTranslatedAttributeOnModel(model, name, value) {
|
|
426
|
+
const locale = this.context?.configuration?.getLocale?.() || "en"
|
|
427
|
+
const instanceRelationship = model.getRelationshipByName("translations")
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Defines translation.
|
|
431
|
+
@type {import("../database/record/index.js").default | undefined} */
|
|
432
|
+
let translation
|
|
433
|
+
|
|
434
|
+
if (model.isNewRecord()) {
|
|
435
|
+
const loaded = instanceRelationship.loaded()
|
|
436
|
+
|
|
437
|
+
if (Array.isArray(loaded)) {
|
|
438
|
+
translation = loaded.find((/**
|
|
439
|
+
* Narrows the runtime value to the documented type.
|
|
440
|
+
@type {Record<string, ?>} */ t) => t.locale() === locale)
|
|
441
|
+
}
|
|
442
|
+
} else {
|
|
443
|
+
if (!instanceRelationship.getPreloaded()) {
|
|
444
|
+
await model.loadRelationship("translations")
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const loaded = instanceRelationship.loaded()
|
|
448
|
+
|
|
449
|
+
if (Array.isArray(loaded)) {
|
|
450
|
+
translation = loaded.find((/**
|
|
451
|
+
* Narrows the runtime value to the documented type.
|
|
452
|
+
@type {Record<string, ?>} */ t) => t.locale() === locale)
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
if (!translation) {
|
|
457
|
+
translation = instanceRelationship.build({locale})
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Assignments.
|
|
462
|
+
@type {Record<string, ?>} */
|
|
463
|
+
const assignments = {}
|
|
464
|
+
|
|
465
|
+
assignments[name] = value
|
|
466
|
+
translation.assign(assignments)
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Runs destroy.
|
|
471
|
+
* @param {import("../database/record/index.js").default} model - Existing model.
|
|
472
|
+
* @returns {Promise<void>} - No return value.
|
|
473
|
+
*/
|
|
474
|
+
async destroy(model) {
|
|
475
|
+
await model.destroy()
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Runs serialize.
|
|
480
|
+
* @param {import("../database/record/index.js").default} model - Model to serialize.
|
|
481
|
+
* @param {"index" | "find" | "create" | "update"} [action] - Action.
|
|
482
|
+
* @returns {Promise<Record<string, ?>>} - Serialized model payload.
|
|
483
|
+
*/
|
|
484
|
+
async serialize(model, action) {
|
|
485
|
+
void action
|
|
486
|
+
|
|
487
|
+
return await this.typedControllerInstance().serializeFrontendModel(model)
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Applies a `nestedAttributes` payload to a freshly-saved parent model,
|
|
492
|
+
* cascading create/update/destroy writes across the declared relationships.
|
|
493
|
+
*
|
|
494
|
+
* Each child is authorized against its own resource's abilities (never the
|
|
495
|
+
* parent's). Destroys run before updates, updates before creates, to avoid
|
|
496
|
+
* unique-constraint conflicts when replacing a child at the same natural key.
|
|
497
|
+
*
|
|
498
|
+
* Attribute filtering for nested children uses the parent resource's
|
|
499
|
+
* permit spec for that relationship — api_maker-style. Policy options
|
|
500
|
+
* (allowDestroy, limit, rejectIf) come from the MODEL's
|
|
501
|
+
* `acceptedNestedAttributesFor(name)` declaration.
|
|
502
|
+
* @param {import("../database/record/index.js").default} parent - Parent model instance.
|
|
503
|
+
* @param {Record<string, ?>} nestedAttributes - Nested-attribute payload keyed by relationship name.
|
|
504
|
+
* @param {?} controller - Controller instance for resource resolution and authorization.
|
|
505
|
+
* @param {{attributes: string[], nested: Record<string, ?>} | null} [parentPermit] - Parsed parent permit spec.
|
|
506
|
+
* @returns {Promise<void>}
|
|
507
|
+
*/
|
|
508
|
+
async _applyNestedAttributes(parent, nestedAttributes, controller, parentPermit = null) {
|
|
509
|
+
const resolvedParent = parentPermit
|
|
510
|
+
|| parsePermittedParams(this.permittedParams({action: "update", ability: this.ability, locals: this.locals, params: {}}))
|
|
511
|
+
|
|
512
|
+
for (const relationshipName of Object.keys(nestedAttributes)) {
|
|
513
|
+
const childPermit = resolvedParent.nested[relationshipName]
|
|
514
|
+
|
|
515
|
+
if (!childPermit) {
|
|
516
|
+
throw new Error(`Nested attributes for '${relationshipName}' are not permitted by ${this.constructor.name}.permittedParams(). Include {${relationshipName}Attributes: [...]} in the returned permit.`)
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const entries = nestedAttributes[relationshipName]
|
|
520
|
+
|
|
521
|
+
if (!Array.isArray(entries)) {
|
|
522
|
+
throw new Error(`Expected array for nestedAttributes['${relationshipName}'] but got: ${typeof entries}`)
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const parentModelClass = /**
|
|
526
|
+
* Narrows the runtime value to the documented type.
|
|
527
|
+
@type {?} */ (parent.getModelClass())
|
|
528
|
+
const modelAcceptance = parentModelClass.acceptedNestedAttributesFor?.(relationshipName)
|
|
529
|
+
|
|
530
|
+
if (!modelAcceptance) {
|
|
531
|
+
throw new Error(`Model ${parentModelClass.name} does not accept nested attributes for '${relationshipName}'. Declare it via ${parentModelClass.name}.acceptsNestedAttributesFor('${relationshipName}').`)
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
const destroyPermitted = childPermit.attributes.includes("_destroy")
|
|
535
|
+
|
|
536
|
+
if (destroyPermitted && !modelAcceptance.allowDestroy) {
|
|
537
|
+
throw new Error(`Resource permits _destroy on nestedAttributes['${relationshipName}'] but the model ${parentModelClass.name} does not allow destroy for that relationship. Set {allowDestroy: true} on ${parentModelClass.name}.acceptsNestedAttributesFor('${relationshipName}', ...).`)
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if (typeof modelAcceptance.limit === "number" && entries.length > modelAcceptance.limit) {
|
|
541
|
+
throw new Error(`nestedAttributes['${relationshipName}'] exceeds model-declared limit of ${modelAcceptance.limit}.`)
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
const parentRelationship = parent.getRelationshipByName(relationshipName)
|
|
545
|
+
const relationshipDefinitions = parentModelClass.relationships?.() || {}
|
|
546
|
+
const definition = relationshipDefinitions[relationshipName]
|
|
547
|
+
|
|
548
|
+
if (!definition || definition.type !== "hasMany") {
|
|
549
|
+
throw new Error(`Nested attributes for '${relationshipName}' require a hasMany relationship. v1 does not support '${definition?.type}'.`)
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
const targetModelClass = /**
|
|
553
|
+
* Narrows the runtime value to the documented type.
|
|
554
|
+
@type {?} */ (parent.getModelClass()).relationshipModelClass?.(relationshipName)
|
|
555
|
+
|
|
556
|
+
if (!targetModelClass) {
|
|
557
|
+
throw new Error(`No target model class resolved for relationship '${relationshipName}' on ${parent.getModelClass().name}.`)
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
const childResourceConfig = controller?.frontendModelResourceConfigurationForModelClass?.(targetModelClass)
|
|
561
|
+
|
|
562
|
+
if (!childResourceConfig) {
|
|
563
|
+
throw new Error(`No frontend-model resource registered for child model '${targetModelClass.getModelName?.() || targetModelClass.name}' under relationship '${relationshipName}'.`)
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const childResource = new childResourceConfig.resourceClass({
|
|
567
|
+
ability: this.ability,
|
|
568
|
+
controller,
|
|
569
|
+
context: this.context || {},
|
|
570
|
+
locals: this.locals || {},
|
|
571
|
+
modelClass: targetModelClass,
|
|
572
|
+
modelName: childResourceConfig.modelName,
|
|
573
|
+
params: controller?.frontendModelParams?.() || {},
|
|
574
|
+
resourceConfiguration: childResourceConfig.resourceConfiguration
|
|
575
|
+
})
|
|
576
|
+
|
|
577
|
+
const foreignKey = definition.foreignKey || this._inferForeignKey(parent, definition)
|
|
578
|
+
const ability = controller?.currentAbility?.()
|
|
579
|
+
|
|
580
|
+
const destroyEntries = []
|
|
581
|
+
const updateEntries = []
|
|
582
|
+
const createEntries = []
|
|
583
|
+
|
|
584
|
+
for (const entry of entries) {
|
|
585
|
+
if (typeof modelAcceptance.rejectIf === "function" && modelAcceptance.rejectIf(entry?.attributes || {})) continue
|
|
586
|
+
|
|
587
|
+
if (entry?._destroy) {
|
|
588
|
+
if (!destroyPermitted) {
|
|
589
|
+
throw new Error(`nestedAttributes['${relationshipName}'] entry requested _destroy but "_destroy" is not in the permit for this relationship.`)
|
|
590
|
+
}
|
|
591
|
+
if (!entry.id) {
|
|
592
|
+
throw new Error(`nestedAttributes['${relationshipName}'] _destroy entry is missing an id.`)
|
|
593
|
+
}
|
|
594
|
+
destroyEntries.push(entry)
|
|
595
|
+
} else if (entry?.id) {
|
|
596
|
+
updateEntries.push(entry)
|
|
597
|
+
} else {
|
|
598
|
+
createEntries.push(entry)
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// The permit's attribute list governs what child fields can be written.
|
|
603
|
+
// Exclude `_destroy` from the writable set since it's a control flag,
|
|
604
|
+
// not an attribute on the record.
|
|
605
|
+
const childWritableAttributes = /**
|
|
606
|
+
* Narrows the runtime value to the documented type.
|
|
607
|
+
@type {string[]} */ (childPermit.attributes).filter((name) => name !== "_destroy")
|
|
608
|
+
|
|
609
|
+
for (const entry of destroyEntries) {
|
|
610
|
+
const existing = await this._findScopedChild({
|
|
611
|
+
ability,
|
|
612
|
+
action: "destroy",
|
|
613
|
+
childResourceConfiguration: childResourceConfig.resourceConfiguration,
|
|
614
|
+
foreignKey,
|
|
615
|
+
id: entry.id,
|
|
616
|
+
parent,
|
|
617
|
+
relationshipName,
|
|
618
|
+
targetModelClass
|
|
619
|
+
})
|
|
620
|
+
|
|
621
|
+
await childResource.destroy(existing)
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
for (const entry of updateEntries) {
|
|
625
|
+
const existing = await this._findScopedChild({
|
|
626
|
+
ability,
|
|
627
|
+
action: "update",
|
|
628
|
+
childResourceConfiguration: childResourceConfig.resourceConfiguration,
|
|
629
|
+
foreignKey,
|
|
630
|
+
id: entry.id,
|
|
631
|
+
parent,
|
|
632
|
+
relationshipName,
|
|
633
|
+
targetModelClass
|
|
634
|
+
})
|
|
635
|
+
|
|
636
|
+
if (entry.attributes && typeof entry.attributes === "object") {
|
|
637
|
+
const filtered = filterWritableFrontendModelAttributes(existing, entry.attributes, childResource, childWritableAttributes)
|
|
638
|
+
await /**
|
|
639
|
+
* Narrows the runtime value to the documented type.
|
|
640
|
+
@type {?} */ (childResource)._assignWithVirtualSetters(existing, filtered)
|
|
641
|
+
await existing.save()
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
if (entry.nestedAttributes) {
|
|
645
|
+
await /**
|
|
646
|
+
* Narrows the runtime value to the documented type.
|
|
647
|
+
@type {?} */ (childResource)._applyNestedAttributes(existing, entry.nestedAttributes, controller, childPermit)
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
for (const entry of createEntries) {
|
|
652
|
+
const childAttributes = entry?.attributes && typeof entry.attributes === "object" ? entry.attributes : {}
|
|
653
|
+
|
|
654
|
+
const child = parentRelationship.build({...childAttributes, [foreignKey]: parent.id()})
|
|
655
|
+
|
|
656
|
+
const filtered = filterWritableFrontendModelAttributes(child, childAttributes, childResource, childWritableAttributes)
|
|
657
|
+
|
|
658
|
+
await /**
|
|
659
|
+
* Narrows the runtime value to the documented type.
|
|
660
|
+
@type {?} */ (childResource)._assignWithVirtualSetters(child, filtered)
|
|
661
|
+
await child.save()
|
|
662
|
+
|
|
663
|
+
await this._authorizeCreatedChild({
|
|
664
|
+
ability,
|
|
665
|
+
child,
|
|
666
|
+
childResourceConfiguration: childResourceConfig.resourceConfiguration,
|
|
667
|
+
relationshipName,
|
|
668
|
+
targetModelClass
|
|
669
|
+
})
|
|
670
|
+
|
|
671
|
+
if (entry.nestedAttributes) {
|
|
672
|
+
await /**
|
|
673
|
+
* Narrows the runtime value to the documented type.
|
|
674
|
+
@type {?} */ (childResource)._applyNestedAttributes(child, entry.nestedAttributes, controller, childPermit)
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* Resolves the ability action for a child resource using the child's own
|
|
682
|
+
* `abilities` mapping — never the parent controller's. This preserves
|
|
683
|
+
* custom mappings like `{update: "manage"}` and catches unmapped actions
|
|
684
|
+
* instead of silently defaulting to the raw action name.
|
|
685
|
+
* @param {import("../configuration-types.js").FrontendModelResourceConfiguration} childResourceConfiguration - Child resource configuration.
|
|
686
|
+
* @param {"create" | "update" | "destroy"} action - Frontend action.
|
|
687
|
+
* @returns {string} - Ability action for the child resource.
|
|
688
|
+
*/
|
|
689
|
+
_resolveChildAbilityAction(childResourceConfiguration, action) {
|
|
690
|
+
const abilities = childResourceConfiguration?.abilities
|
|
691
|
+
|
|
692
|
+
if (!abilities || typeof abilities !== "object" || Array.isArray(abilities)) {
|
|
693
|
+
throw new Error(`Nested child resource must define an 'abilities' object to authorize nested ${action}.`)
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
const abilityAction = /**
|
|
697
|
+
* Narrows the runtime value to the documented type.
|
|
698
|
+
@type {Record<string, string>} */ (abilities)[action]
|
|
699
|
+
|
|
700
|
+
if (typeof abilityAction !== "string" || abilityAction.length < 1) {
|
|
701
|
+
throw new Error(`Nested child resource must define abilities.${action}.`)
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
return abilityAction
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
/**
|
|
708
|
+
* Finds an existing child for a nested update/destroy, scoped to the
|
|
709
|
+
* child's own model class, the parent's foreign key, AND the child
|
|
710
|
+
* resource's ability mapping for the requested action. Throws when the
|
|
711
|
+
* child does not exist, does not belong to the current parent, or is
|
|
712
|
+
* not authorized — all of which must roll the transaction back.
|
|
713
|
+
* @param {object} args - Arguments.
|
|
714
|
+
* @param {import("../authorization/ability.js").default | undefined} args.ability - Current ability.
|
|
715
|
+
* @param {"update" | "destroy"} args.action - Frontend action.
|
|
716
|
+
* @param {import("../configuration-types.js").FrontendModelResourceConfiguration} args.childResourceConfiguration - Child resource configuration.
|
|
717
|
+
* @param {string} args.foreignKey - Foreign-key attribute on the child pointing to the parent.
|
|
718
|
+
* @param {string | number} args.id - Child id from the payload.
|
|
719
|
+
* @param {import("../database/record/index.js").default} args.parent - Parent model instance.
|
|
720
|
+
* @param {string} args.relationshipName - Parent's relationship name (for error messages).
|
|
721
|
+
* @param {typeof import("../database/record/index.js").default} args.targetModelClass - Child model class.
|
|
722
|
+
* @returns {Promise<import("../database/record/index.js").default>} - Authorized, parent-linked child model.
|
|
723
|
+
*/
|
|
724
|
+
async _findScopedChild({ability, action, childResourceConfiguration, foreignKey, id, parent, relationshipName, targetModelClass}) {
|
|
725
|
+
const primaryKey = targetModelClass.primaryKey()
|
|
726
|
+
const lookup = {[primaryKey]: id, [foreignKey]: parent.id()}
|
|
727
|
+
const query = ability
|
|
728
|
+
? /**
|
|
729
|
+
* Narrows the runtime value to the documented type.
|
|
730
|
+
@type {?} */ (targetModelClass).accessibleFor(this._resolveChildAbilityAction(childResourceConfiguration, action), ability)
|
|
731
|
+
: /**
|
|
732
|
+
* Narrows the runtime value to the documented type.
|
|
733
|
+
@type {?} */ (targetModelClass).where({})
|
|
734
|
+
|
|
735
|
+
const existing = await query.findBy(lookup)
|
|
736
|
+
|
|
737
|
+
if (!existing) {
|
|
738
|
+
throw new Error(`Cannot ${action} nested ${relationshipName}[id=${id}]: record not found, does not belong to parent ${parent.getModelClass().name}[id=${parent.id()}], or is not authorized.`)
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
return existing
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/**
|
|
745
|
+
* Verifies an already-saved nested child is authorized under the child
|
|
746
|
+
* resource's own `create` ability. Rolls back via thrown error when not
|
|
747
|
+
* authorized so the outer transaction destroys the insert.
|
|
748
|
+
* @param {object} args - Arguments.
|
|
749
|
+
* @param {import("../authorization/ability.js").default | undefined} args.ability - Current ability.
|
|
750
|
+
* @param {import("../database/record/index.js").default} args.child - Child model instance just created.
|
|
751
|
+
* @param {import("../configuration-types.js").FrontendModelResourceConfiguration} args.childResourceConfiguration - Child resource configuration.
|
|
752
|
+
* @param {string} args.relationshipName - Parent's relationship name (for error messages).
|
|
753
|
+
* @param {typeof import("../database/record/index.js").default} args.targetModelClass - Child model class.
|
|
754
|
+
* @returns {Promise<void>}
|
|
755
|
+
*/
|
|
756
|
+
async _authorizeCreatedChild({ability, child, childResourceConfiguration, relationshipName, targetModelClass}) {
|
|
757
|
+
if (!ability) return
|
|
758
|
+
|
|
759
|
+
const abilityAction = this._resolveChildAbilityAction(childResourceConfiguration, "create")
|
|
760
|
+
const primaryKey = targetModelClass.primaryKey()
|
|
761
|
+
const authorizedIds = await /**
|
|
762
|
+
* Narrows the runtime value to the documented type.
|
|
763
|
+
@type {?} */ (targetModelClass)
|
|
764
|
+
.accessibleFor(abilityAction, ability)
|
|
765
|
+
.where({[primaryKey]: child.readAttribute(primaryKey)})
|
|
766
|
+
.pluck(primaryKey)
|
|
767
|
+
|
|
768
|
+
if (authorizedIds.length === 0) {
|
|
769
|
+
throw new Error(`Nested create on ${relationshipName}[${targetModelClass.name}] not authorized.`)
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
/**
|
|
774
|
+
* Best-effort foreign-key inference for relationships that don't declare it.
|
|
775
|
+
* @param {import("../database/record/index.js").default} parent - Parent model.
|
|
776
|
+
* @param {{foreignKey?: string}} definition - Relationship definition.
|
|
777
|
+
* @returns {string} - Foreign-key attribute name.
|
|
778
|
+
*/
|
|
779
|
+
_inferForeignKey(parent, definition) {
|
|
780
|
+
if (definition.foreignKey) return definition.foreignKey
|
|
781
|
+
|
|
782
|
+
const parentModelName = parent.getModelClass().name || ""
|
|
783
|
+
const underscored = parentModelName.replace(/([A-Z])/g, (match, letter, index) => (index === 0 ? letter.toLowerCase() : `_${letter.toLowerCase()}`))
|
|
784
|
+
|
|
785
|
+
return `${inflection.camelize(underscored, true)}Id`
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
/**
|
|
789
|
+
* After nested writes, preload every relationship declared in the
|
|
790
|
+
* parent's permit so the post-save serialize step emits them and the
|
|
791
|
+
* client can reconcile ids.
|
|
792
|
+
* @param {import("../database/record/index.js").default} model - Saved parent model.
|
|
793
|
+
* @param {{attributes: string[], nested: Record<string, ?>}} permit - Parsed parent permit.
|
|
794
|
+
* @returns {Promise<void>}
|
|
795
|
+
*/
|
|
796
|
+
async _preloadNestedWritableRelationships(model, permit) {
|
|
797
|
+
const relationshipNames = Object.keys(permit.nested)
|
|
798
|
+
|
|
799
|
+
if (relationshipNames.length === 0) return
|
|
800
|
+
|
|
801
|
+
for (const relationshipName of relationshipNames) {
|
|
802
|
+
if (typeof /**
|
|
803
|
+
* Narrows the runtime value to the documented type.
|
|
804
|
+
@type {?} */ (model).loadRelationship === "function") {
|
|
805
|
+
await /**
|
|
806
|
+
* Narrows the runtime value to the documented type.
|
|
807
|
+
@type {?} */ (model).loadRelationship(relationshipName)
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
/**
|
|
814
|
+
* Parses the Rails/api_maker-style flat permit spec returned from
|
|
815
|
+
* `permittedParams(arg)` into a structured shape used internally by the
|
|
816
|
+
* write pipeline. Strings become attribute permits; objects whose keys
|
|
817
|
+
* end in `Attributes` become nested permits (the key prefix names the
|
|
818
|
+
* relationship).
|
|
819
|
+
*
|
|
820
|
+
* parsePermittedParams(["firstName", "lastName",
|
|
821
|
+
* {tasksAttributes: ["id", "_destroy", "name"]}
|
|
822
|
+
* ])
|
|
823
|
+
* // → {
|
|
824
|
+
* // attributes: ["firstName", "lastName"],
|
|
825
|
+
* // nested: {
|
|
826
|
+
* // tasks: {attributes: ["id", "_destroy", "name"], nested: {}}
|
|
827
|
+
* // }
|
|
828
|
+
* // }
|
|
829
|
+
* @param {Array<string | Record<string, ?>> | undefined} permitSpec - Flat permit spec.
|
|
830
|
+
* @returns {{attributes: string[], nested: Record<string, {attributes: string[], nested: Record<string, ?>}>}} - Parsed structure.
|
|
831
|
+
*/
|
|
832
|
+
function parsePermittedParams(permitSpec) {
|
|
833
|
+
/**
|
|
834
|
+
* Attributes.
|
|
835
|
+
@type {string[]} */
|
|
836
|
+
const attributes = []
|
|
837
|
+
/**
|
|
838
|
+
* Nested.
|
|
839
|
+
@type {Record<string, {attributes: string[], nested: Record<string, ?>}>} */
|
|
840
|
+
const nested = {}
|
|
841
|
+
|
|
842
|
+
if (!Array.isArray(permitSpec)) return {attributes, nested}
|
|
843
|
+
|
|
844
|
+
for (const entry of permitSpec) {
|
|
845
|
+
if (typeof entry === "string") {
|
|
846
|
+
attributes.push(entry)
|
|
847
|
+
} else if (entry && typeof entry === "object" && !Array.isArray(entry)) {
|
|
848
|
+
for (const [key, value] of Object.entries(entry)) {
|
|
849
|
+
if (!key.endsWith("Attributes")) {
|
|
850
|
+
throw new Error(`Invalid permittedParams entry: nested relationship keys must end in "Attributes" (got "${key}"). Use "${key}Attributes" instead.`)
|
|
851
|
+
}
|
|
852
|
+
const relationshipName = key.slice(0, -"Attributes".length)
|
|
853
|
+
|
|
854
|
+
if (!relationshipName) {
|
|
855
|
+
throw new Error(`Invalid permittedParams entry: empty relationship name in key "${key}".`)
|
|
856
|
+
}
|
|
857
|
+
if (!Array.isArray(value)) {
|
|
858
|
+
throw new Error(`Invalid permittedParams entry for "${key}": expected array permit spec, got ${typeof value}.`)
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
nested[relationshipName] = parsePermittedParams(value)
|
|
862
|
+
}
|
|
863
|
+
} else {
|
|
864
|
+
throw new Error(`Invalid permittedParams entry: expected string or nested-attributes object, got ${typeof entry}.`)
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
return {attributes, nested}
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
/**
|
|
872
|
+
* Runs filter writable frontend model attributes.
|
|
873
|
+
* @param {Record<string, ?>} receiver - Model instance or prototype.
|
|
874
|
+
* @param {Record<string, ?>} attributes - Incoming frontend-model attributes.
|
|
875
|
+
* @param {FrontendModelBaseResource | null} [resource] - Resource instance for virtual-setter detection.
|
|
876
|
+
* @param {string[] | null} [permittedAttributeNames] - Optional explicit permit list. `null` falls back to setter-existence checks only.
|
|
877
|
+
* @returns {Record<string, ?>} - Writable attributes only.
|
|
878
|
+
*/
|
|
879
|
+
function filterWritableFrontendModelAttributes(receiver, attributes, resource = /**
|
|
880
|
+
* Narrows the runtime value to the documented type.
|
|
881
|
+
@type {FrontendModelBaseResource | null} */ (null), permittedAttributeNames = null) {
|
|
882
|
+
// Frontend-model writes should fail fast when callers submit read-only or unknown attrs.
|
|
883
|
+
// Silent drops hide contract mistakes in generated models and app-side wrapper code.
|
|
884
|
+
/**
|
|
885
|
+
* Writable attributes.
|
|
886
|
+
@type {Record<string, ?>} */
|
|
887
|
+
const writableAttributes = {}
|
|
888
|
+
/**
|
|
889
|
+
* Invalid attributes.
|
|
890
|
+
@type {string[]} */
|
|
891
|
+
const invalidAttributes = []
|
|
892
|
+
/**
|
|
893
|
+
* Not permitted attributes.
|
|
894
|
+
@type {string[]} */
|
|
895
|
+
const notPermittedAttributes = []
|
|
896
|
+
|
|
897
|
+
const permitSet = Array.isArray(permittedAttributeNames) ? new Set(permittedAttributeNames) : null
|
|
898
|
+
const translatedSet = resource ? new Set(/**
|
|
899
|
+
* Narrows the runtime value to the documented type.
|
|
900
|
+
@type {typeof FrontendModelBaseResource} */ (resource.constructor).translatedAttributes || []) : new Set()
|
|
901
|
+
|
|
902
|
+
for (const [attributeName, value] of Object.entries(attributes)) {
|
|
903
|
+
if (permitSet && !permitSet.has(attributeName)) {
|
|
904
|
+
notPermittedAttributes.push(attributeName)
|
|
905
|
+
continue
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
const setterName = `set${inflection.camelize(attributeName)}`
|
|
909
|
+
const resourceSetterName = `${setterName}Attribute`
|
|
910
|
+
|
|
911
|
+
if (setterName in receiver) {
|
|
912
|
+
writableAttributes[attributeName] = value
|
|
913
|
+
} else if (resource && typeof /**
|
|
914
|
+
* Narrows the runtime value to the documented type.
|
|
915
|
+
@type {Record<string, ?>} */ (/**
|
|
916
|
+
* Narrows the runtime value to the documented type.
|
|
917
|
+
@type {?} */ (resource))[resourceSetterName] === "function") {
|
|
918
|
+
writableAttributes[attributeName] = value
|
|
919
|
+
} else if (translatedSet.has(attributeName)) {
|
|
920
|
+
writableAttributes[attributeName] = value
|
|
921
|
+
} else {
|
|
922
|
+
invalidAttributes.push(attributeName)
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
if (notPermittedAttributes.length > 0) {
|
|
927
|
+
throw new Error(`Frontend model write attributes not permitted by permittedParams(): ${notPermittedAttributes.join(", ")}`)
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
if (invalidAttributes.length > 0) {
|
|
931
|
+
throw new Error(`Invalid frontend model write attributes: ${invalidAttributes.join(", ")}`)
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
return writableAttributes
|
|
935
|
+
}
|