@mistriwork/engine 0.0.0-stage → 1.0.0-rc.10
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/LICENSE +21 -0
- package/README.md +17 -2
- package/dist/adapters/adapter.d.ts +693 -0
- package/dist/adapters/adapter.js +444 -0
- package/dist/adapters/cache/cacheStore.d.ts +75 -0
- package/dist/adapters/cache/cacheStore.js +160 -0
- package/dist/adapters/cache/resp.d.ts +52 -0
- package/dist/adapters/cache/resp.js +237 -0
- package/dist/adapters/commands/canonicalJson.d.ts +19 -0
- package/dist/adapters/commands/canonicalJson.js +154 -0
- package/dist/adapters/connect.d.ts +29 -0
- package/dist/adapters/connect.js +40 -0
- package/dist/adapters/connectionBudget.d.ts +101 -0
- package/dist/adapters/connectionBudget.js +128 -0
- package/dist/adapters/dbLatency.d.ts +35 -0
- package/dist/adapters/dbLatency.js +79 -0
- package/dist/adapters/epochCache.d.ts +50 -0
- package/dist/adapters/epochCache.js +85 -0
- package/dist/adapters/http/egress.d.ts +48 -0
- package/dist/adapters/http/egress.js +175 -0
- package/dist/adapters/http/httpAdapter.d.ts +71 -0
- package/dist/adapters/http/httpAdapter.js +512 -0
- package/dist/adapters/http/httpDescription.d.ts +32 -0
- package/dist/adapters/http/httpDescription.js +246 -0
- package/dist/adapters/http/openapi.d.ts +98 -0
- package/dist/adapters/http/openapi.js +487 -0
- package/dist/adapters/layoutRefusal.d.ts +24 -0
- package/dist/adapters/layoutRefusal.js +35 -0
- package/dist/adapters/net/networkPolicy.d.ts +44 -0
- package/dist/adapters/net/networkPolicy.js +123 -0
- package/dist/adapters/net/untrustedDatabase.d.ts +18 -0
- package/dist/adapters/net/untrustedDatabase.js +120 -0
- package/dist/adapters/poolAdmission.d.ts +43 -0
- package/dist/adapters/poolAdmission.js +184 -0
- package/dist/adapters/poolMetrics.d.ts +121 -0
- package/dist/adapters/poolMetrics.js +185 -0
- package/dist/adapters/postgres.d.ts +436 -0
- package/dist/adapters/postgres.js +1890 -0
- package/dist/adapters/postgresCatalog.d.ts +96 -0
- package/dist/adapters/postgresCatalog.js +387 -0
- package/dist/adapters/postgresCatalogDigest.d.ts +30 -0
- package/dist/adapters/postgresCatalogDigest.js +87 -0
- package/dist/adapters/postgresCatalogRows.d.ts +62 -0
- package/dist/adapters/postgresCatalogRows.js +37 -0
- package/dist/adapters/postgresDates.d.ts +1 -0
- package/dist/adapters/postgresDates.js +39 -0
- package/dist/adapters/postgresMaintenance.d.ts +33 -0
- package/dist/adapters/postgresMaintenance.js +179 -0
- package/dist/adapters/postgresPool.d.ts +255 -0
- package/dist/adapters/postgresPool.js +838 -0
- package/dist/adapters/postgresPooler.d.ts +28 -0
- package/dist/adapters/postgresPooler.js +267 -0
- package/dist/adapters/postgresReadCatalog.d.ts +31 -0
- package/dist/adapters/postgresReadCatalog.js +226 -0
- package/dist/adapters/postgresReadSnapshot.d.ts +46 -0
- package/dist/adapters/postgresReadSnapshot.js +133 -0
- package/dist/adapters/postgresSchemaEpoch.d.ts +24 -0
- package/dist/adapters/postgresSchemaEpoch.js +149 -0
- package/dist/adapters/postgresTransactionHandle.d.ts +173 -0
- package/dist/adapters/postgresTransactionHandle.js +524 -0
- package/dist/adapters/postgresValues.d.ts +30 -0
- package/dist/adapters/postgresValues.js +75 -0
- package/dist/adapters/replicas.d.ts +70 -0
- package/dist/adapters/replicas.js +208 -0
- package/dist/adapters/reservedSql.d.ts +38 -0
- package/dist/adapters/reservedSql.js +264 -0
- package/dist/adapters/rolePrivileges.d.ts +17 -0
- package/dist/adapters/rolePrivileges.js +22 -0
- package/dist/adapters/scoped/keyRows.d.ts +65 -0
- package/dist/adapters/scoped/keyRows.js +173 -0
- package/dist/adapters/scoped/postgresScopeCatalog.d.ts +16 -0
- package/dist/adapters/scoped/postgresScopeCatalog.js +117 -0
- package/dist/adapters/scoped/scopedCommandOperations.d.ts +5 -0
- package/dist/adapters/scoped/scopedCommandOperations.js +48 -0
- package/dist/adapters/scoped/scopedReads.d.ts +20 -0
- package/dist/adapters/scoped/scopedReads.js +174 -0
- package/dist/adapters/scoped/scopedTransaction.d.ts +89 -0
- package/dist/adapters/scoped/scopedTransaction.js +303 -0
- package/dist/adapters/scoped/sqliteScopeCatalog.d.ts +16 -0
- package/dist/adapters/scoped/sqliteScopeCatalog.js +241 -0
- package/dist/adapters/scoped/updateGuard.d.ts +3 -0
- package/dist/adapters/scoped/updateGuard.js +40 -0
- package/dist/adapters/serverSettings.d.ts +45 -0
- package/dist/adapters/serverSettings.js +46 -0
- package/dist/adapters/sqlite.d.ts +262 -0
- package/dist/adapters/sqlite.js +1434 -0
- package/dist/adapters/sqliteCostlyReads.d.ts +33 -0
- package/dist/adapters/sqliteCostlyReads.js +49 -0
- package/dist/adapters/sqliteDecimals.d.ts +22 -0
- package/dist/adapters/sqliteDecimals.js +57 -0
- package/dist/adapters/sqliteHandles.d.ts +43 -0
- package/dist/adapters/sqliteHandles.js +143 -0
- package/dist/adapters/sqliteStatementProcess.d.ts +29 -0
- package/dist/adapters/sqliteStatementProcess.js +131 -0
- package/dist/adapters/statementControl.d.ts +34 -0
- package/dist/adapters/statementControl.js +90 -0
- package/dist/adapters/storeHost.d.ts +130 -0
- package/dist/adapters/storeHost.js +13 -0
- package/dist/adapters/telemetry/drivers.d.ts +21 -0
- package/dist/adapters/telemetry/drivers.js +321 -0
- package/dist/adapters/telemetry/metrics.d.ts +63 -0
- package/dist/adapters/telemetry/metrics.js +122 -0
- package/dist/adapters/telemetry/otlp.d.ts +12 -0
- package/dist/adapters/telemetry/otlp.js +320 -0
- package/dist/adapters/telemetry/otlpEncode.d.ts +48 -0
- package/dist/adapters/telemetry/otlpEncode.js +366 -0
- package/dist/adapters/telemetry/otlpEnv.d.ts +1 -0
- package/dist/adapters/telemetry/otlpEnv.js +12 -0
- package/dist/adapters/telemetry/serverTiming.d.ts +83 -0
- package/dist/adapters/telemetry/serverTiming.js +503 -0
- package/dist/adapters/telemetry/start.d.ts +10 -0
- package/dist/adapters/telemetry/start.js +25 -0
- package/dist/adapters/telemetry/statement.d.ts +16 -0
- package/dist/adapters/telemetry/statement.js +134 -0
- package/dist/adapters/telemetry/trace.d.ts +127 -0
- package/dist/adapters/telemetry/trace.js +295 -0
- package/dist/adapters/writeAhead.d.ts +27 -0
- package/dist/adapters/writeAhead.js +46 -0
- package/dist/adapters/writeAttribution.d.ts +18 -0
- package/dist/adapters/writeAttribution.js +23 -0
- package/dist/adapters/writeCapture.d.ts +73 -0
- package/dist/adapters/writeCapture.js +20 -0
- package/dist/adapters/writeLanes.d.ts +94 -0
- package/dist/adapters/writeLanes.js +232 -0
- package/dist/engine.d.ts +15 -0
- package/dist/engine.js +13 -0
- package/dist/server/auth/credentials.d.ts +61 -0
- package/dist/server/auth/credentials.js +260 -0
- package/dist/server/cacheService.d.ts +50 -0
- package/dist/server/cacheService.js +110 -0
- package/dist/server/cli/args.d.ts +176 -0
- package/dist/server/cli/args.js +342 -0
- package/dist/server/cli/bin.d.ts +5 -0
- package/dist/server/cli/bin.js +66 -0
- package/dist/server/cli/browser.d.ts +15 -0
- package/dist/server/cli/browser.js +37 -0
- package/dist/server/cli/config.d.ts +50 -0
- package/dist/server/cli/config.js +206 -0
- package/dist/server/cli/startup.d.ts +36 -0
- package/dist/server/cli/startup.js +128 -0
- package/dist/server/cli/startupOptions.d.ts +63 -0
- package/dist/server/cli/startupOptions.js +143 -0
- package/dist/server/cli/subcommand.d.ts +28 -0
- package/dist/server/cli/subcommand.js +32 -0
- package/dist/server/cli/usage.d.ts +13 -0
- package/dist/server/cli/usage.js +31 -0
- package/dist/server/clientHttp.d.ts +184 -0
- package/dist/server/clientHttp.js +700 -0
- package/dist/server/clientSurface.d.ts +128 -0
- package/dist/server/clientSurface.js +182 -0
- package/dist/server/columnGrants.d.ts +103 -0
- package/dist/server/columnGrants.js +730 -0
- package/dist/server/compileCache.d.ts +1 -0
- package/dist/server/compileCache.js +30 -0
- package/dist/server/config/locations.d.ts +1 -0
- package/dist/server/config/locations.js +15 -0
- package/dist/server/configPaths.d.ts +7 -0
- package/dist/server/configPaths.js +34 -0
- package/dist/server/configSections.d.ts +64 -0
- package/dist/server/configSections.js +72 -0
- package/dist/server/connectionRedaction.d.ts +30 -0
- package/dist/server/connectionRedaction.js +77 -0
- package/dist/server/connections.d.ts +353 -0
- package/dist/server/connections.js +978 -0
- package/dist/server/constraints.d.ts +82 -0
- package/dist/server/constraints.js +169 -0
- package/dist/server/demo.d.ts +9 -0
- package/dist/server/demo.js +554 -0
- package/dist/server/devclient.d.ts +108 -0
- package/dist/server/devclient.js +361 -0
- package/dist/server/evidence.d.ts +55 -0
- package/dist/server/evidence.js +323 -0
- package/dist/server/hiddenColumns.d.ts +32 -0
- package/dist/server/hiddenColumns.js +238 -0
- package/dist/server/http/baselineHeaders.d.ts +24 -0
- package/dist/server/http/baselineHeaders.js +12 -0
- package/dist/server/http/captured.d.ts +35 -0
- package/dist/server/http/captured.js +73 -0
- package/dist/server/http/clock.d.ts +1 -0
- package/dist/server/http/clock.js +14 -0
- package/dist/server/http/connectionSource.d.ts +4 -0
- package/dist/server/http/connectionSource.js +48 -0
- package/dist/server/http/context.d.ts +189 -0
- package/dist/server/http/context.js +11 -0
- package/dist/server/http/crawlerText.d.ts +15 -0
- package/dist/server/http/crawlerText.js +32 -0
- package/dist/server/http/dataRoutes.d.ts +88 -0
- package/dist/server/http/dataRoutes.js +137 -0
- package/dist/server/http/door.d.ts +57 -0
- package/dist/server/http/door.js +39 -0
- package/dist/server/http/exportRoute.d.ts +15 -0
- package/dist/server/http/exportRoute.js +320 -0
- package/dist/server/http/firstPaint.d.ts +108 -0
- package/dist/server/http/firstPaint.js +262 -0
- package/dist/server/http/httpOperationRoutes.d.ts +11 -0
- package/dist/server/http/httpOperationRoutes.js +50 -0
- package/dist/server/http/pageReads.d.ts +20 -0
- package/dist/server/http/pageReads.js +205 -0
- package/dist/server/http/pageRoutes.d.ts +39 -0
- package/dist/server/http/pageRoutes.js +291 -0
- package/dist/server/http/probes.d.ts +22 -0
- package/dist/server/http/probes.js +27 -0
- package/dist/server/http/rateLimit.d.ts +131 -0
- package/dist/server/http/rateLimit.js +351 -0
- package/dist/server/http/readRoutes.d.ts +9 -0
- package/dist/server/http/readRoutes.js +131 -0
- package/dist/server/http/readRouting.d.ts +20 -0
- package/dist/server/http/readRouting.js +80 -0
- package/dist/server/http/recordCompose.d.ts +67 -0
- package/dist/server/http/recordCompose.js +167 -0
- package/dist/server/http/recordWrites.d.ts +89 -0
- package/dist/server/http/recordWrites.js +298 -0
- package/dist/server/http/refusalReport.d.ts +38 -0
- package/dist/server/http/refusalReport.js +63 -0
- package/dist/server/http/reply.d.ts +130 -0
- package/dist/server/http/reply.js +394 -0
- package/dist/server/http/requestOrigin.d.ts +11 -0
- package/dist/server/http/requestOrigin.js +8 -0
- package/dist/server/http/router.d.ts +45 -0
- package/dist/server/http/router.js +22 -0
- package/dist/server/http/serverCore.d.ts +68 -0
- package/dist/server/http/serverCore.js +177 -0
- package/dist/server/http/traceRequest.d.ts +30 -0
- package/dist/server/http/traceRequest.js +224 -0
- package/dist/server/http/trust.d.ts +261 -0
- package/dist/server/http/trust.js +472 -0
- package/dist/server/http/writeRefusal.d.ts +33 -0
- package/dist/server/http/writeRefusal.js +24 -0
- package/dist/server/httpConnection.d.ts +49 -0
- package/dist/server/httpConnection.js +170 -0
- package/dist/server/impact.d.ts +55 -0
- package/dist/server/impact.js +47 -0
- package/dist/server/journal.d.ts +134 -0
- package/dist/server/journal.js +398 -0
- package/dist/server/keysetReadHttp.d.ts +52 -0
- package/dist/server/keysetReadHttp.js +85 -0
- package/dist/server/layouts.d.ts +49 -0
- package/dist/server/layouts.js +192 -0
- package/dist/server/migrate/cli.d.ts +55 -0
- package/dist/server/migrate/cli.js +517 -0
- package/dist/server/migrate/database.d.ts +37 -0
- package/dist/server/migrate/database.js +247 -0
- package/dist/server/migrate/desired.d.ts +76 -0
- package/dist/server/migrate/desired.js +292 -0
- package/dist/server/migrate/files.d.ts +34 -0
- package/dist/server/migrate/files.js +141 -0
- package/dist/server/migrate/flyway.d.ts +7 -0
- package/dist/server/migrate/flyway.js +69 -0
- package/dist/server/migrate/history.d.ts +70 -0
- package/dist/server/migrate/history.js +211 -0
- package/dist/server/migrate/runner.d.ts +100 -0
- package/dist/server/migrate/runner.js +409 -0
- package/dist/server/migrate/snapshot.d.ts +30 -0
- package/dist/server/migrate/snapshot.js +239 -0
- package/dist/server/migrate/target.d.ts +33 -0
- package/dist/server/migrate/target.js +82 -0
- package/dist/server/openapiDraft.d.ts +25 -0
- package/dist/server/openapiDraft.js +171 -0
- package/dist/server/operations/diagnostics.d.ts +101 -0
- package/dist/server/operations/diagnostics.js +284 -0
- package/dist/server/operations/requestMetrics.d.ts +33 -0
- package/dist/server/operations/requestMetrics.js +94 -0
- package/dist/server/packageVersion.d.ts +1 -0
- package/dist/server/packageVersion.js +11 -0
- package/dist/server/poolConfig.d.ts +34 -0
- package/dist/server/poolConfig.js +114 -0
- package/dist/server/product.d.ts +20 -0
- package/dist/server/product.js +12 -0
- package/dist/server/recordHistoryOperations.d.ts +39 -0
- package/dist/server/recordHistoryOperations.js +43 -0
- package/dist/server/recordWriteOperations.d.ts +111 -0
- package/dist/server/recordWriteOperations.js +329 -0
- package/dist/server/resourceReadHttp.d.ts +81 -0
- package/dist/server/resourceReadHttp.js +606 -0
- package/dist/server/resourceReadOperations.d.ts +147 -0
- package/dist/server/resourceReadOperations.js +452 -0
- package/dist/server/scaffoldMachine.d.ts +54 -0
- package/dist/server/scaffoldMachine.js +184 -0
- package/dist/server/seed/cli.d.ts +20 -0
- package/dist/server/seed/cli.js +164 -0
- package/dist/server/seed/lexicon.d.ts +26 -0
- package/dist/server/seed/lexicon.js +585 -0
- package/dist/server/seed/seed.d.ts +70 -0
- package/dist/server/seed/seed.js +779 -0
- package/dist/server/seed/words.d.ts +21 -0
- package/dist/server/seed/words.js +164 -0
- package/dist/server/serverRequests.d.ts +6 -0
- package/dist/server/serverRequests.js +33 -0
- package/dist/server/serverTimeouts.d.ts +33 -0
- package/dist/server/serverTimeouts.js +32 -0
- package/dist/server/shapeonly.d.ts +32 -0
- package/dist/server/shapeonly.js +110 -0
- package/dist/server/startupWarnings.d.ts +32 -0
- package/dist/server/startupWarnings.js +92 -0
- package/dist/shared/addressParams.d.ts +28 -0
- package/dist/shared/addressParams.js +47 -0
- package/dist/shared/appCards.d.ts +123 -0
- package/dist/shared/appCards.js +385 -0
- package/dist/shared/appComponents.d.ts +177 -0
- package/dist/shared/appComponents.js +422 -0
- package/dist/shared/appDocuments.d.ts +43 -0
- package/dist/shared/appDocuments.js +131 -0
- package/dist/shared/appEmbeds.d.ts +21 -0
- package/dist/shared/appEmbeds.js +24 -0
- package/dist/shared/appIcons.d.ts +28 -0
- package/dist/shared/appIcons.js +223 -0
- package/dist/shared/appLinks.d.ts +17 -0
- package/dist/shared/appLinks.js +124 -0
- package/dist/shared/appListColumns.d.ts +3 -0
- package/dist/shared/appListColumns.js +66 -0
- package/dist/shared/appLocale.d.ts +36 -0
- package/dist/shared/appLocale.js +86 -0
- package/dist/shared/appMessages.d.ts +44 -0
- package/dist/shared/appMessages.js +78 -0
- package/dist/shared/appNoun.d.ts +21 -0
- package/dist/shared/appNoun.js +32 -0
- package/dist/shared/autoBucket.d.ts +98 -0
- package/dist/shared/autoBucket.js +305 -0
- package/dist/shared/autoValues.d.ts +25 -0
- package/dist/shared/autoValues.js +25 -0
- package/dist/shared/breakdown.d.ts +228 -0
- package/dist/shared/breakdown.js +557 -0
- package/dist/shared/breakdowntext.d.ts +41 -0
- package/dist/shared/breakdowntext.js +400 -0
- package/dist/shared/builtInThemes.d.ts +2 -0
- package/dist/shared/builtInThemes.js +771 -0
- package/dist/shared/chartSeparation.d.ts +11 -0
- package/dist/shared/chartSeparation.js +58 -0
- package/dist/shared/clauses.d.ts +22 -0
- package/dist/shared/clauses.js +97 -0
- package/dist/shared/columnCaption.d.ts +16 -0
- package/dist/shared/columnCaption.js +90 -0
- package/dist/shared/commandFieldPath.d.ts +2 -0
- package/dist/shared/commandFieldPath.js +7 -0
- package/dist/shared/commandWire.d.ts +26 -0
- package/dist/shared/commandWire.js +41 -0
- package/dist/shared/computed.d.ts +161 -0
- package/dist/shared/computed.js +378 -0
- package/dist/shared/computedParse.d.ts +13 -0
- package/dist/shared/computedParse.js +278 -0
- package/dist/shared/computedValue.d.ts +47 -0
- package/dist/shared/computedValue.js +191 -0
- package/dist/shared/connection.d.ts +30 -0
- package/dist/shared/connection.js +1 -0
- package/dist/shared/csv.d.ts +20 -0
- package/dist/shared/csv.js +63 -0
- package/dist/shared/dashLayout.d.ts +30 -0
- package/dist/shared/dashLayout.js +132 -0
- package/dist/shared/dashboardAsks.d.ts +93 -0
- package/dist/shared/dashboardAsks.js +129 -0
- package/dist/shared/dashboardTrend.d.ts +20 -0
- package/dist/shared/dashboardTrend.js +50 -0
- package/dist/shared/declaredReferences.d.ts +50 -0
- package/dist/shared/declaredReferences.js +202 -0
- package/dist/shared/definedFields.d.ts +63 -0
- package/dist/shared/definedFields.js +103 -0
- package/dist/shared/definedValues.d.ts +63 -0
- package/dist/shared/definedValues.js +184 -0
- package/dist/shared/displayDefaults.d.ts +135 -0
- package/dist/shared/displayDefaults.js +529 -0
- package/dist/shared/dueDate.d.ts +6 -0
- package/dist/shared/dueDate.js +6 -0
- package/dist/shared/duration.d.ts +4 -0
- package/dist/shared/duration.js +23 -0
- package/dist/shared/fileBounds.d.ts +9 -0
- package/dist/shared/fileBounds.js +9 -0
- package/dist/shared/filter.d.ts +217 -0
- package/dist/shared/filter.js +305 -0
- package/dist/shared/firstPaint.d.ts +24 -0
- package/dist/shared/firstPaint.js +33 -0
- package/dist/shared/form.d.ts +21 -0
- package/dist/shared/form.js +25 -0
- package/dist/shared/formValues.d.ts +104 -0
- package/dist/shared/formValues.js +352 -0
- package/dist/shared/funnel.d.ts +45 -0
- package/dist/shared/funnel.js +108 -0
- package/dist/shared/httpOperations.d.ts +48 -0
- package/dist/shared/httpOperations.js +9 -0
- package/dist/shared/iconGlyphs.d.ts +4 -0
- package/dist/shared/iconGlyphs.js +151 -0
- package/dist/shared/iconWeights.d.ts +3 -0
- package/dist/shared/iconWeights.js +162 -0
- package/dist/shared/imageWidths.d.ts +32 -0
- package/dist/shared/imageWidths.js +38 -0
- package/dist/shared/keysetPage.d.ts +64 -0
- package/dist/shared/keysetPage.js +161 -0
- package/dist/shared/like.d.ts +20 -0
- package/dist/shared/like.js +29 -0
- package/dist/shared/listEmpty.d.ts +12 -0
- package/dist/shared/listEmpty.js +36 -0
- package/dist/shared/listFilters.d.ts +28 -0
- package/dist/shared/listFilters.js +84 -0
- package/dist/shared/listPresentations.d.ts +199 -0
- package/dist/shared/listPresentations.js +7 -0
- package/dist/shared/listQuestion.d.ts +52 -0
- package/dist/shared/listQuestion.js +137 -0
- package/dist/shared/lookPalettes.d.ts +62 -0
- package/dist/shared/lookPalettes.js +306 -0
- package/dist/shared/maskQuoted.d.ts +24 -0
- package/dist/shared/maskQuoted.js +48 -0
- package/dist/shared/mentionLabel.d.ts +19 -0
- package/dist/shared/mentionLabel.js +38 -0
- package/dist/shared/mentionLookup.d.ts +17 -0
- package/dist/shared/mentionLookup.js +64 -0
- package/dist/shared/menuCounts.d.ts +16 -0
- package/dist/shared/menuCounts.js +74 -0
- package/dist/shared/model.d.ts +79 -0
- package/dist/shared/model.js +153 -0
- package/dist/shared/money.d.ts +44 -0
- package/dist/shared/money.js +81 -0
- package/dist/shared/moneyEntry.d.ts +7 -0
- package/dist/shared/moneyEntry.js +27 -0
- package/dist/shared/mount.d.ts +118 -0
- package/dist/shared/mount.js +248 -0
- package/dist/shared/niceTicks.d.ts +26 -0
- package/dist/shared/niceTicks.js +50 -0
- package/dist/shared/order.d.ts +59 -0
- package/dist/shared/order.js +133 -0
- package/dist/shared/page.d.ts +135 -0
- package/dist/shared/page.js +1187 -0
- package/dist/shared/pageAnatomy.d.ts +99 -0
- package/dist/shared/pageAnatomy.js +370 -0
- package/dist/shared/pageConfig.d.ts +25 -0
- package/dist/shared/pageConfig.js +1308 -0
- package/dist/shared/pageEmptyActions.d.ts +12 -0
- package/dist/shared/pageEmptyActions.js +20 -0
- package/dist/shared/pageLayouts.d.ts +72 -0
- package/dist/shared/pageLayouts.js +205 -0
- package/dist/shared/pageModel.d.ts +785 -0
- package/dist/shared/pageModel.js +121 -0
- package/dist/shared/pagePaths.d.ts +180 -0
- package/dist/shared/pagePaths.js +869 -0
- package/dist/shared/pagePlans.d.ts +52 -0
- package/dist/shared/pagePlans.js +108 -0
- package/dist/shared/pageRoutes.d.ts +9 -0
- package/dist/shared/pageRoutes.js +9 -0
- package/dist/shared/query.d.ts +166 -0
- package/dist/shared/query.js +1302 -0
- package/dist/shared/queryDates.d.ts +12 -0
- package/dist/shared/queryDates.js +101 -0
- package/dist/shared/queryOperands.d.ts +99 -0
- package/dist/shared/queryOperands.js +131 -0
- package/dist/shared/reads.d.ts +103 -0
- package/dist/shared/reads.js +154 -0
- package/dist/shared/recordLabel.d.ts +51 -0
- package/dist/shared/recordLabel.js +123 -0
- package/dist/shared/recordReadKeys.d.ts +30 -0
- package/dist/shared/recordReadKeys.js +51 -0
- package/dist/shared/recordReads.d.ts +268 -0
- package/dist/shared/recordReads.js +368 -0
- package/dist/shared/recordview.d.ts +198 -0
- package/dist/shared/recordview.js +356 -0
- package/dist/shared/referenceModel.d.ts +58 -0
- package/dist/shared/referenceModel.js +194 -0
- package/dist/shared/refusal.d.ts +64 -0
- package/dist/shared/refusal.js +144 -0
- package/dist/shared/relatedCreate.d.ts +30 -0
- package/dist/shared/relatedCreate.js +162 -0
- package/dist/shared/resourceHttp.d.ts +464 -0
- package/dist/shared/resourceHttp.js +1 -0
- package/dist/shared/richText.d.ts +85 -0
- package/dist/shared/richText.js +305 -0
- package/dist/shared/rollups.d.ts +143 -0
- package/dist/shared/rollups.js +64 -0
- package/dist/shared/rowScope.d.ts +72 -0
- package/dist/shared/rowScope.js +376 -0
- package/dist/shared/runtimeTables.d.ts +44 -0
- package/dist/shared/runtimeTables.js +79 -0
- package/dist/shared/schema.d.ts +523 -0
- package/dist/shared/schema.js +627 -0
- package/dist/shared/schematext.d.ts +38 -0
- package/dist/shared/schematext.js +101 -0
- package/dist/shared/scopeCatalog.d.ts +29 -0
- package/dist/shared/scopeCatalog.js +468 -0
- package/dist/shared/servedApp.d.ts +681 -0
- package/dist/shared/servedApp.js +45 -0
- package/dist/shared/sessionLease.d.ts +34 -0
- package/dist/shared/sessionLease.js +3 -0
- package/dist/shared/shape.d.ts +55 -0
- package/dist/shared/shape.js +168 -0
- package/dist/shared/slug.d.ts +1 -0
- package/dist/shared/slug.js +12 -0
- package/dist/shared/sortText.d.ts +14 -0
- package/dist/shared/sortText.js +26 -0
- package/dist/shared/sqlRelation.d.ts +4 -0
- package/dist/shared/sqlRelation.js +8 -0
- package/dist/shared/sqlsaved.d.ts +44 -0
- package/dist/shared/sqlsaved.js +63 -0
- package/dist/shared/stitchedPaths.d.ts +27 -0
- package/dist/shared/stitchedPaths.js +38 -0
- package/dist/shared/structuredFilter.d.ts +22 -0
- package/dist/shared/structuredFilter.js +1 -0
- package/dist/shared/subtitlePath.d.ts +20 -0
- package/dist/shared/subtitlePath.js +24 -0
- package/dist/shared/suggest.d.ts +18 -0
- package/dist/shared/suggest.js +59 -0
- package/dist/shared/tableSizing.d.ts +4 -0
- package/dist/shared/tableSizing.js +9 -0
- package/dist/shared/tagLists.d.ts +57 -0
- package/dist/shared/tagLists.js +34 -0
- package/dist/shared/textRules.d.ts +54 -0
- package/dist/shared/textRules.js +133 -0
- package/dist/shared/theme.d.ts +94 -0
- package/dist/shared/theme.js +800 -0
- package/dist/shared/themeColor.d.ts +34 -0
- package/dist/shared/themeColor.js +109 -0
- package/dist/shared/themeFaces.d.ts +30 -0
- package/dist/shared/themeFaces.js +42 -0
- package/dist/shared/themeInk.d.ts +20 -0
- package/dist/shared/themeInk.js +52 -0
- package/dist/shared/themeLooks.d.ts +9 -0
- package/dist/shared/themeLooks.js +572 -0
- package/dist/shared/themeParts.d.ts +26 -0
- package/dist/shared/themeParts.js +71 -0
- package/dist/shared/themePartsCheck.d.ts +21 -0
- package/dist/shared/themePartsCheck.js +91 -0
- package/dist/shared/themePresets.d.ts +85 -0
- package/dist/shared/themePresets.js +139 -0
- package/dist/shared/themeRecipeWords.d.ts +156 -0
- package/dist/shared/themeRecipeWords.js +156 -0
- package/dist/shared/themeRecipes.d.ts +213 -0
- package/dist/shared/themeRecipes.js +234 -0
- package/dist/shared/themeResolve.d.ts +55 -0
- package/dist/shared/themeResolve.js +52 -0
- package/dist/shared/themeTokens.d.ts +107 -0
- package/dist/shared/themeTokens.js +116 -0
- package/dist/shared/themeType.d.ts +38 -0
- package/dist/shared/themeType.js +205 -0
- package/dist/shared/unitWord.d.ts +4 -0
- package/dist/shared/unitWord.js +37 -0
- package/dist/shared/view.d.ts +195 -0
- package/dist/shared/view.js +987 -0
- package/dist/shared/viewLabels.d.ts +47 -0
- package/dist/shared/viewLabels.js +459 -0
- package/dist/shared/viewPaths.d.ts +248 -0
- package/dist/shared/viewPaths.js +294 -0
- package/dist/shared/viewtext.d.ts +57 -0
- package/dist/shared/viewtext.js +282 -0
- package/dist/shared/vocabulary.d.ts +71 -0
- package/dist/shared/vocabulary.js +249 -0
- package/dist/shared/weekStart.d.ts +7 -0
- package/dist/shared/weekStart.js +36 -0
- package/mark.svg +1 -0
- package/package.json +261 -4
- package/schema/model.d.ts +298 -0
- package/schema/theme.d.ts +587 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { findTable } from '../../shared/schema.js';
|
|
2
|
+
import { validatePage } from '../../shared/page.js';
|
|
3
|
+
import { RECORD_COMPOSE_PATH, recordPageReads, rowBody, whenBody, } from '../../shared/recordReads.js';
|
|
4
|
+
import { canonicalText } from '../../shared/recordReadKeys.js';
|
|
5
|
+
import { CapturedReply } from './captured.js';
|
|
6
|
+
import { HttpError, send } from './reply.js';
|
|
7
|
+
export const COMPOSE_PATH = RECORD_COMPOSE_PATH;
|
|
8
|
+
/** How many related rows a reader may ask for a page: the sizes the list offers (client relatedRows.ts), and no more. */
|
|
9
|
+
const MOST_ROWS = 50;
|
|
10
|
+
/** Commands asked about at once, as the availability route takes them. */
|
|
11
|
+
const MOST_CHECKS = 16;
|
|
12
|
+
const failed = (answer) => !answer ||
|
|
13
|
+
answer.status >= 400 ||
|
|
14
|
+
Boolean(answer.body &&
|
|
15
|
+
typeof answer.body === 'object' &&
|
|
16
|
+
('error' in answer.body ||
|
|
17
|
+
('errors' in answer.body &&
|
|
18
|
+
Array.isArray(answer.body.errors) &&
|
|
19
|
+
answer.body.errors.length > 0)));
|
|
20
|
+
export async function composeRecordPage({ ctx, source, body, post, hooks, }) {
|
|
21
|
+
const { res, identity } = ctx;
|
|
22
|
+
const key = body.key;
|
|
23
|
+
const rows = body.rows ?? 10;
|
|
24
|
+
const tab = body.tab;
|
|
25
|
+
if (!key ||
|
|
26
|
+
typeof key !== 'object' ||
|
|
27
|
+
Array.isArray(key) ||
|
|
28
|
+
typeof rows !== 'number' ||
|
|
29
|
+
!Number.isInteger(rows) ||
|
|
30
|
+
rows < 1 ||
|
|
31
|
+
rows > MOST_ROWS ||
|
|
32
|
+
(tab !== undefined && (typeof tab !== 'string' || tab.length > 512)) ||
|
|
33
|
+
(typeof body.page !== 'string' && (!body.page || typeof body.page !== 'object' || Array.isArray(body.page)))) {
|
|
34
|
+
throw new HttpError(400, 'A composed record page names a page, a record key and a page size.');
|
|
35
|
+
}
|
|
36
|
+
/* A part asked on the page's behalf: through the route its section's own
|
|
37
|
+
request reaches, answered into memory. What that route would refuse, or
|
|
38
|
+
throw, leaves the part out. */
|
|
39
|
+
const ask = async (path, given) => {
|
|
40
|
+
if (!hooks.mayAsk(ctx, path))
|
|
41
|
+
return undefined;
|
|
42
|
+
const reply = new CapturedReply();
|
|
43
|
+
try {
|
|
44
|
+
await post(reply.asResponse(), path, given);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
return reply.answer();
|
|
50
|
+
};
|
|
51
|
+
const record = key;
|
|
52
|
+
const resources = hooks.resources(ctx, source);
|
|
53
|
+
const names = {
|
|
54
|
+
schema: source.schema,
|
|
55
|
+
app: ctx.appConfigured,
|
|
56
|
+
recordLabel: (table) => resources[table]?.recordLabel,
|
|
57
|
+
defined: (table) => resources[table]?.defined,
|
|
58
|
+
};
|
|
59
|
+
const options = { rows, history: true, ...(tab === undefined ? {} : { tab: tab }) };
|
|
60
|
+
/* What the page reads that its plan does not decide — the record's row,
|
|
61
|
+
its history, its verbs, each `when` — is asked beside the plan, from
|
|
62
|
+
the page as the plan will find it; the lists and figures the plan
|
|
63
|
+
builds are asked once it comes. Should the plan's page not be that
|
|
64
|
+
page after all, the early answers are dropped and asked again. */
|
|
65
|
+
const reading = (page, base, plans, started = []) => {
|
|
66
|
+
const answered = [];
|
|
67
|
+
const keep = (asked, answer) => {
|
|
68
|
+
if (!failed(answer))
|
|
69
|
+
answered.push({ ...asked, status: answer.status, reply: answer.body });
|
|
70
|
+
};
|
|
71
|
+
const { row, parts, reads } = recordPageReads(names, page, base, plans, record, options);
|
|
72
|
+
/* The record's fixed edits ask their `when` of the row, as a section does. */
|
|
73
|
+
const actions = resources[base.id]?.actions ?? [];
|
|
74
|
+
for (const when of new Set(actions.flatMap((action) => (!action.command && action.when ? [action.when] : [])))) {
|
|
75
|
+
reads.push({ path: '/api/run', body: whenBody(base, record, when) });
|
|
76
|
+
}
|
|
77
|
+
const whole = async () => {
|
|
78
|
+
const asked = { path: '/api/view/run', body: rowBody(page.id, base, record, row, 'row') };
|
|
79
|
+
const answer = await ask(asked.path, asked.body);
|
|
80
|
+
if (!failed(answer))
|
|
81
|
+
return keep(asked, answer);
|
|
82
|
+
/* One column this reader may not read refuses the whole row: each reader of it is asked alone, as it would ask. */
|
|
83
|
+
await Promise.all(parts.map(async ({ part, columns }) => {
|
|
84
|
+
const alone = { path: '/api/view/run', body: rowBody(page.id, base, record, columns, part) };
|
|
85
|
+
keep(alone, await ask(alone.path, alone.body));
|
|
86
|
+
}));
|
|
87
|
+
};
|
|
88
|
+
const commands = actions.flatMap((action) => (action.command && action.command.target === base.id ? [action.command] : []));
|
|
89
|
+
const checks = [...new Map(commands.map((command) => [command.id, command])).values()]
|
|
90
|
+
.slice(0, MOST_CHECKS)
|
|
91
|
+
.map((command) => ({ command: command.id, commandVersion: command.version, keys: [record] }));
|
|
92
|
+
const availability = async () => {
|
|
93
|
+
if (!checks.length || identity.id === undefined || !hooks.availability || !hooks.mayAsk(ctx, '/api/commands/availability'))
|
|
94
|
+
return;
|
|
95
|
+
const asked = { path: '/api/commands/availability', body: { checks } };
|
|
96
|
+
keep(asked, await hooks.availability(ctx, checks));
|
|
97
|
+
};
|
|
98
|
+
const begun = new Map(started.map(([asked, answer]) => [readKey(asked), answer]));
|
|
99
|
+
/* The page's own row first, its verbs last: they are advice, and wait behind what is shown. */
|
|
100
|
+
const done = Promise.all([
|
|
101
|
+
whole(),
|
|
102
|
+
...reads.map(async (asked) => keep(asked, await (begun.get(readKey(asked)) ?? ask(asked.path, asked.body)))),
|
|
103
|
+
availability(),
|
|
104
|
+
]);
|
|
105
|
+
return { reads: new Set(reads.map((asked) => readKey(asked))), done: done.then(() => answered) };
|
|
106
|
+
};
|
|
107
|
+
const expected = expectedPage(ctx, source, body.page, hooks);
|
|
108
|
+
const expectedBase = expected && findTable(source.schema, expected.base);
|
|
109
|
+
/* The history is asked first: it is the longest chain (its store is
|
|
110
|
+
attested, then read), and a reader pool is a queue. Then the plan, which
|
|
111
|
+
the lists wait on; then the rest. */
|
|
112
|
+
const history = expected && expectedBase
|
|
113
|
+
? recordPageReads(names, expected, expectedBase, [], record, options)
|
|
114
|
+
.reads.filter((asked) => asked.path === '/api/record-history')
|
|
115
|
+
.map((asked) => [asked, ask(asked.path, asked.body)])
|
|
116
|
+
: [];
|
|
117
|
+
const planning = ask('/api/pages/plan', { page: body.page, key });
|
|
118
|
+
const early = expected && expectedBase ? reading(expected, expectedBase, [], history) : undefined;
|
|
119
|
+
const plan = await planning;
|
|
120
|
+
const planned = failed(plan) ? undefined : plan.body;
|
|
121
|
+
const base = planned && findTable(source.schema, planned.page.base);
|
|
122
|
+
/* No plan, no page: the client asks the plan itself, and is told why in
|
|
123
|
+
the plan's own words. What was asked beside it is dropped, once it has
|
|
124
|
+
settled: every read ends inside the request (and the row policy's
|
|
125
|
+
context) that asked it. */
|
|
126
|
+
if (!planned || !base) {
|
|
127
|
+
await Promise.allSettled([early?.done, ...history.map(([, answer]) => answer)]);
|
|
128
|
+
return send(res, 200, { planned: false });
|
|
129
|
+
}
|
|
130
|
+
const kept = early && canonicalText(expected) === canonicalText(planned.page) ? early : undefined;
|
|
131
|
+
if (early && !kept)
|
|
132
|
+
await early.done;
|
|
133
|
+
let answered;
|
|
134
|
+
if (kept) {
|
|
135
|
+
/* Only what the plan adds: its lists, figures, timelines and custom reads. */
|
|
136
|
+
const { reads } = recordPageReads(names, planned.page, base, planned.plans, record, options);
|
|
137
|
+
const later = reads.filter((asked) => !kept.reads.has(readKey(asked)));
|
|
138
|
+
const more = await Promise.all(later.map(async (asked) => ({ asked, answer: await ask(asked.path, asked.body) })));
|
|
139
|
+
answered = [
|
|
140
|
+
...(await kept.done),
|
|
141
|
+
...more.flatMap(({ asked, answer }) => (failed(answer) ? [] : [{ ...asked, status: answer.status, reply: answer.body }])),
|
|
142
|
+
];
|
|
143
|
+
}
|
|
144
|
+
else
|
|
145
|
+
answered = await reading(planned.page, base, planned.plans).done;
|
|
146
|
+
send(res, 200, { planned: true, page: planned.page, plans: planned.plans, reads: answered });
|
|
147
|
+
}
|
|
148
|
+
const readKey = (asked) => `${asked.path}\u0000${canonicalText(asked.body)}`;
|
|
149
|
+
/**
|
|
150
|
+
* The page the plan will find, found as `/api/pages/plan` finds it
|
|
151
|
+
* (authoringRoutes.ts): given whole, the table's suggested page, or the
|
|
152
|
+
* connection's configured page by name — validated against this schema.
|
|
153
|
+
* Only to ask the page's own reads beside the plan; the plan still decides,
|
|
154
|
+
* and an answer read for any other page is dropped.
|
|
155
|
+
*/
|
|
156
|
+
function expectedPage(ctx, source, asked, hooks) {
|
|
157
|
+
const name = ctx.registry.list().find((c) => c.id === source.connectionId)?.name;
|
|
158
|
+
const found = typeof asked === 'object' && asked !== null
|
|
159
|
+
? asked
|
|
160
|
+
: String(asked ?? '').startsWith('suggested:')
|
|
161
|
+
? hooks.suggested(source, String(asked).slice('suggested:'.length))
|
|
162
|
+
: ctx.configPages.find((page) => page.id === String(asked ?? '') && (!page.connection || page.connection === source.connectionId || page.connection === name));
|
|
163
|
+
if (!found || typeof found.base !== 'string' || !Array.isArray(found.sections))
|
|
164
|
+
return undefined;
|
|
165
|
+
const page = validatePage(source.schema, found);
|
|
166
|
+
return page.error ? undefined : page;
|
|
167
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The record writes both products serve: update, insert (a file's upload
|
|
3
|
+
* among them) and delete. Every one asks the product's write gate first
|
|
4
|
+
* (`gates.write`: the connection's `writable` flag, then the reader's
|
|
5
|
+
* grant), and every committed write goes through the one recorder that
|
|
6
|
+
* anchors it to the source it touched, then to the product's `after.write`.
|
|
7
|
+
*
|
|
8
|
+
* What an App adds is `RecordRules`: the words its refusals are said in, the
|
|
9
|
+
* label its history gives an edit, what it does not delete, a New form's
|
|
10
|
+
* file, tags and lines, and replies that say no more than an App reader is
|
|
11
|
+
* told. Without them a write is the database browser's: journaled for its
|
|
12
|
+
* Undo, and refused in the database's own words.
|
|
13
|
+
*/
|
|
14
|
+
import type { ServerResponse } from 'node:http';
|
|
15
|
+
import { type InsertRequest, type InsertResult } from '../../adapters/adapter.js';
|
|
16
|
+
import { type LineCollection } from '../recordWriteOperations.js';
|
|
17
|
+
import { sendResource } from './reply.js';
|
|
18
|
+
import type { PostContext } from './context.js';
|
|
19
|
+
type Values = Record<string, string | number | boolean | null>;
|
|
20
|
+
/** What a write that failed says, in its errors list: a message, and in an App its code and safe fields. */
|
|
21
|
+
type WriteFailure = NonNullable<Parameters<typeof sendResource<'/api/insert'>>[3]['errors']>[number];
|
|
22
|
+
/** A file placed before its row is written, discarded when the row is not (files/fileStorage.ts). */
|
|
23
|
+
interface PlacedValues {
|
|
24
|
+
readonly values: Values;
|
|
25
|
+
discard(): Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
/** What a New form sends beside its record, judged: the link rows its tags are, and its lines. */
|
|
28
|
+
interface Alongside {
|
|
29
|
+
readonly alongside?: (result: InsertResult) => InsertRequest[];
|
|
30
|
+
readonly lines?: LineCollection[];
|
|
31
|
+
}
|
|
32
|
+
/** An answer a rule gave instead of going on. */
|
|
33
|
+
interface Answered {
|
|
34
|
+
readonly status: number;
|
|
35
|
+
readonly error: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* An App's rules for its record writes (mistri: http/appRecordWrites.ts).
|
|
39
|
+
* Absent, the database browser's: a write is journaled, the upload, tags and
|
|
40
|
+
* lines of a New form are refused as no App declares them, and a failure is
|
|
41
|
+
* said in the database's own words.
|
|
42
|
+
*/
|
|
43
|
+
export interface RecordRules {
|
|
44
|
+
/** What the record history calls an edit (recordHistory.ts updateActionLabel). */
|
|
45
|
+
label(post: PostContext, table: string, values: Values): string | undefined;
|
|
46
|
+
/** Whether the App deletes rows of `tableId` at all (appDeletable). */
|
|
47
|
+
deletes(post: PostContext, tableId: string): boolean;
|
|
48
|
+
/** An upload's columns, placed where its resource keeps its bytes. */
|
|
49
|
+
upload(post: PostContext, tableId: string | undefined): Promise<PlacedValues | Answered>;
|
|
50
|
+
/** A New form's tags and lines, judged against what the App declares. */
|
|
51
|
+
alongside(post: PostContext, tableId: string | undefined): Answered | Alongside;
|
|
52
|
+
/** The App's own rules refusing the record (its validation, a line's `consumes`): answered in the App's shape. True when answered. */
|
|
53
|
+
refused(res: ServerResponse, path: '/api/insert' | '/api/update', error: unknown): boolean;
|
|
54
|
+
/** A write that failed, said by code and never in a driver's words (appErrors.ts). */
|
|
55
|
+
failure(post: PostContext, error: unknown, completed: boolean): WriteFailure;
|
|
56
|
+
/** A delete's impact report, when a count fails. */
|
|
57
|
+
impact(error: unknown): string;
|
|
58
|
+
/** A reply cut to what an App reader is told (appResponses.ts). */
|
|
59
|
+
reply(body: Record<string, unknown>, kind: 'read' | 'write'): Record<string, unknown>;
|
|
60
|
+
}
|
|
61
|
+
/** What a product decides about the record writes. */
|
|
62
|
+
export interface RecordHooks {
|
|
63
|
+
readonly gates: {
|
|
64
|
+
/** Why a write to `tableId` is refused, or null. An update may reach a table a role runs fixed actions on. */
|
|
65
|
+
write(post: PostContext, tableId: string | undefined, update?: boolean): string | null;
|
|
66
|
+
};
|
|
67
|
+
readonly after?: {
|
|
68
|
+
/** A committed write's record: its search document follows it, queued and never awaited. */
|
|
69
|
+
write(post: PostContext, table: string, key: Record<string, unknown>): void;
|
|
70
|
+
};
|
|
71
|
+
readonly rules?: RecordRules;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* What every write on this request shares: the options the recorded
|
|
75
|
+
* operations take — the source's own adapter, and whether the write is
|
|
76
|
+
* journaled. The database browser journals every write, for its Undo; an App
|
|
77
|
+
* has no Undo and journals none (recordWriteOperations.ts `unrecorded`). The
|
|
78
|
+
* import route (importRoutes.ts) writes through the same.
|
|
79
|
+
*/
|
|
80
|
+
export declare function recordedWrites(post: Pick<PostContext, 'adapter' | 'connection' | 'who' | 'resources'>, journal: boolean): {
|
|
81
|
+
writeOptions: (table: string) => {
|
|
82
|
+
adapter: import("@mistriwork/engine/adapters/adapter.js").Adapter;
|
|
83
|
+
connection: string;
|
|
84
|
+
by: string | undefined;
|
|
85
|
+
journal: boolean;
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
export declare function recordWriteRoutes(post: PostContext, hooks: RecordHooks): Promise<boolean>;
|
|
89
|
+
export {};
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import { Refusal, TransactionOutcomeUnknown, WriteConflict } from '../../adapters/adapter.js';
|
|
2
|
+
import { attributeAction } from '../../adapters/writeAttribution.js';
|
|
3
|
+
import { isPoolBusy } from '../../adapters/poolMetrics.js';
|
|
4
|
+
import { lockTimedOut } from '../../adapters/writeLanes.js';
|
|
5
|
+
import { deletionDependents, recordedDelete, recordedInsert, recordedUpdate } from '../recordWriteOperations.js';
|
|
6
|
+
import { AppAccessDenied } from '../columnGrants.js';
|
|
7
|
+
import { deleteImpact } from '../impact.js';
|
|
8
|
+
import { explainWriteFailure, writeFailureOutcome, writeRefusalStatus } from '../constraints.js';
|
|
9
|
+
import { findTable } from '../../shared/schema.js';
|
|
10
|
+
import { FILE_UPLOAD_PATH, bytesLabelled, send, sendBusy, sendResource, sendRowLocked } from './reply.js';
|
|
11
|
+
/* A failed insert or update's status. The database refusing it for good — a key taken, a reference to nothing, a
|
|
12
|
+
value its column cannot hold — is 409 or 422 (constraints.ts writeRefusalStatus); it was a 200 whose body alone
|
|
13
|
+
said `rejected`, and a client that read the status took a refused write for a saved one (a live load test). One
|
|
14
|
+
that may have landed, or that nothing recognizes as refused, keeps the 200 and its `unknown` outcome. */
|
|
15
|
+
const refusedStatus = (err, completed) => (completed ? 200 : (writeRefusalStatus(err) ?? 200));
|
|
16
|
+
/**
|
|
17
|
+
* What every write on this request shares: the options the recorded
|
|
18
|
+
* operations take — the source's own adapter, and whether the write is
|
|
19
|
+
* journaled. The database browser journals every write, for its Undo; an App
|
|
20
|
+
* has no Undo and journals none (recordWriteOperations.ts `unrecorded`). The
|
|
21
|
+
* import route (importRoutes.ts) writes through the same.
|
|
22
|
+
*/
|
|
23
|
+
export function recordedWrites(post, journal) {
|
|
24
|
+
const { adapter, connection, who, resources } = post;
|
|
25
|
+
// resources has already been authorized. Anchor the write to the exact
|
|
26
|
+
// source without reopening an unguarded registry adapter or using its default.
|
|
27
|
+
const writeOptions = (table) => {
|
|
28
|
+
const owner = resources?.connectionFor(table) ?? connection;
|
|
29
|
+
return { adapter: resources?.forConnection(owner) ?? adapter, connection: owner, by: who, journal };
|
|
30
|
+
};
|
|
31
|
+
return { writeOptions };
|
|
32
|
+
}
|
|
33
|
+
export async function recordWriteRoutes(post, hooks) {
|
|
34
|
+
const { res, path, body, adapter, schema, registry, access, wholeRowWrites } = post;
|
|
35
|
+
const rules = hooks.rules;
|
|
36
|
+
const { writeOptions } = recordedWrites(post, !rules);
|
|
37
|
+
const indexed = (table, key) => hooks.after?.write(post, table, key);
|
|
38
|
+
/* A record's lines are records of their own, and what follows a write follows them too. */
|
|
39
|
+
const linesIndexed = (lines) => {
|
|
40
|
+
for (const line of lines ?? [])
|
|
41
|
+
for (const key of line.written ?? [])
|
|
42
|
+
indexed(line.table, key);
|
|
43
|
+
};
|
|
44
|
+
/* A file's object and hash are written for the reader and never shown to it (appAccess.ts withFileColumns). */
|
|
45
|
+
const internal = access?.internal?.get(String(body.table ?? ''));
|
|
46
|
+
const shown = (record) => internal && record && typeof record === 'object' && !Array.isArray(record)
|
|
47
|
+
? Object.fromEntries(Object.entries(record).filter(([column]) => !internal.has(column)))
|
|
48
|
+
: record;
|
|
49
|
+
const sendData = (value) => {
|
|
50
|
+
const reply = { ...value };
|
|
51
|
+
for (const field of ['applied', 'row', 'before'])
|
|
52
|
+
if (field in reply)
|
|
53
|
+
reply[field] = bytesLabelled(shown(reply[field]));
|
|
54
|
+
send(res, 200, rules ? rules.reply(reply, 'write') : reply);
|
|
55
|
+
};
|
|
56
|
+
const failureOf = async (err, completed, values) => rules
|
|
57
|
+
? rules.failure(post, err, completed)
|
|
58
|
+
: { message: await explainWriteFailure(adapter, schema, String(body.table ?? ''), values, err.message) };
|
|
59
|
+
/**
|
|
60
|
+
* Update one record through the shared transaction and history service.
|
|
61
|
+
*
|
|
62
|
+
* Note what is *not* checked here: whether the client thinks it is in write
|
|
63
|
+
* mode. That toggle is a UI affordance, and a guard that lives in the
|
|
64
|
+
* browser is not a guard. The adapter independently verifies the key is the
|
|
65
|
+
* whole primary key, counts the matching rows, and refuses anything other
|
|
66
|
+
* than exactly one.
|
|
67
|
+
*/
|
|
68
|
+
if (path === '/api/update') {
|
|
69
|
+
/* The gate the server can actually enforce.
|
|
70
|
+
|
|
71
|
+
Write mode in the browser is a deliberate act before typing, and it is
|
|
72
|
+
not a permission — the client decides whether to *offer* editing, and a
|
|
73
|
+
guard in the browser is not a guard. Until a connection says it is
|
|
74
|
+
writable, anything that could reach this endpoint could write to it —
|
|
75
|
+
and an app may narrow that to the tables it names. */
|
|
76
|
+
const refused = hooks.gates.write(post, findTable(schema, String(body.table ?? ''))?.id, true);
|
|
77
|
+
if (refused) {
|
|
78
|
+
send(res, 403, { error: refused, code: 'forbidden' });
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
if (!adapter.update) {
|
|
82
|
+
send(res, 501, { error: `${schema.dialect} connections are read-only in this build.` });
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
/* `values` is the real shape; `column`/`value` is accepted as the
|
|
86
|
+
one-field form so a single edit does not have to build an object. */
|
|
87
|
+
const values = (body.values ?? (body.column ? { [String(body.column)]: body.value ?? null } : {}));
|
|
88
|
+
let completed = false;
|
|
89
|
+
try {
|
|
90
|
+
const table = String(body.table ?? '');
|
|
91
|
+
const key = (body.key ?? {});
|
|
92
|
+
const expected = body.expected;
|
|
93
|
+
const { result, undoId } = await attributeAction(rules?.label(post, table, values), () => recordedUpdate(writeOptions(table), { table, key, values, ...(expected === undefined ? {} : { expected }) }));
|
|
94
|
+
completed = true;
|
|
95
|
+
indexed(table, key);
|
|
96
|
+
sendData({ ...result, ...(undoId === undefined ? {} : { undoId }) });
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
if (!completed && lockTimedOut(err)) {
|
|
101
|
+
sendRowLocked(res);
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
if (!completed && isPoolBusy(err)) {
|
|
105
|
+
sendBusy(res, {}, true, err);
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
if (!completed && rules?.refused(res, path, err))
|
|
109
|
+
return true;
|
|
110
|
+
if (!completed && (err instanceof Refusal || err instanceof WriteConflict))
|
|
111
|
+
throw err;
|
|
112
|
+
/* Pointing an existing row at a row that is not there is the same
|
|
113
|
+
mistake as inserting one, and gets the same answer. */
|
|
114
|
+
const failure = await failureOf(err, completed, values);
|
|
115
|
+
sendResource(res, path, refusedStatus(err, completed), {
|
|
116
|
+
errors: [failure],
|
|
117
|
+
outcome: completed ? 'unknown' : writeFailureOutcome(err),
|
|
118
|
+
});
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Add a row. The same gate as /api/update, because it is the same act one
|
|
124
|
+
* tense earlier. Refusals — a column that does not exist, null into NOT
|
|
125
|
+
* NULL — are 400s the adapter words; a constraint the *database* enforces
|
|
126
|
+
* comes back with its own message in the `rejected` envelope, because a
|
|
127
|
+
* UNIQUE violation is the user's to fix and the engine names the constraint
|
|
128
|
+
* better than a paraphrase would — at 409 or 422 (refusedStatus), never a
|
|
129
|
+
* 2xx a client reading only the status would take for a save. The outcome
|
|
130
|
+
* distinguishes recognized constraint rejections from unconfirmed failures
|
|
131
|
+
* that must not invite a blind retry.
|
|
132
|
+
*/
|
|
133
|
+
if (path === '/api/insert' || path === FILE_UPLOAD_PATH) {
|
|
134
|
+
const tableId = findTable(schema, String(body.table ?? ''))?.id;
|
|
135
|
+
const refused = hooks.gates.write(post, tableId);
|
|
136
|
+
if (refused) {
|
|
137
|
+
send(res, 403, { error: refused, code: 'forbidden' });
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
if (!adapter.insert) {
|
|
141
|
+
send(res, 501, { error: `${schema.dialect} connections are read-only in this build.` });
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
let values;
|
|
145
|
+
let placed;
|
|
146
|
+
if (path === FILE_UPLOAD_PATH) {
|
|
147
|
+
/* A file is a row like any other: one insert on this path, with the
|
|
148
|
+
grants, validation and history an insert gets, and the upload's bytes,
|
|
149
|
+
name, type and size in the columns the resource's `file` names. */
|
|
150
|
+
const upload = rules ? await rules.upload(post, tableId) : { status: 404, error: 'This resource does not hold files.' };
|
|
151
|
+
if ('error' in upload) {
|
|
152
|
+
send(res, upload.status, { error: upload.error });
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
placed = upload;
|
|
156
|
+
values = placed.values;
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
if (!body.values || typeof body.values !== 'object' || Array.isArray(body.values)) {
|
|
160
|
+
send(res, 400, { error: 'Insert values must be an object. Use {} explicitly to insert database defaults.' });
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
values = body.values;
|
|
164
|
+
}
|
|
165
|
+
/* A record's tags and its lines, sent with it from a New form: each written
|
|
166
|
+
in the record's transaction, so the record never stands without them. */
|
|
167
|
+
let alongside = {};
|
|
168
|
+
if (path === '/api/insert' && (body.tags !== undefined || body.lines !== undefined)) {
|
|
169
|
+
const judged = rules
|
|
170
|
+
? rules.alongside(post, tableId)
|
|
171
|
+
: body.tags !== undefined
|
|
172
|
+
? { status: 400, error: 'This resource carries no tags.' }
|
|
173
|
+
: { status: 400, error: 'Lines are saved with a record of an App that declares them.' };
|
|
174
|
+
if ('error' in judged) {
|
|
175
|
+
send(res, judged.status, { error: judged.error });
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
alongside = judged;
|
|
179
|
+
}
|
|
180
|
+
let completed = false;
|
|
181
|
+
try {
|
|
182
|
+
const table = String(body.table ?? '');
|
|
183
|
+
const { result, undoId } = await recordedInsert(writeOptions(table), { table, values }, alongside.alongside, alongside.lines);
|
|
184
|
+
completed = true;
|
|
185
|
+
indexed(table, result.key);
|
|
186
|
+
linesIndexed(alongside.lines);
|
|
187
|
+
sendData({ ...result, ...(undoId === undefined ? {} : { undoId }) });
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
catch (err) {
|
|
191
|
+
// A row that was not written leaves no object behind; one that may have been keeps it for the sweep to judge.
|
|
192
|
+
if (placed && !completed && !(err instanceof TransactionOutcomeUnknown))
|
|
193
|
+
await placed.discard();
|
|
194
|
+
if (!completed && lockTimedOut(err)) {
|
|
195
|
+
sendRowLocked(res);
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
if (!completed && isPoolBusy(err)) {
|
|
199
|
+
sendBusy(res, {}, true, err);
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
if (!completed && rules?.refused(res, '/api/insert', err))
|
|
203
|
+
return true;
|
|
204
|
+
if (!completed && err instanceof Refusal)
|
|
205
|
+
throw err;
|
|
206
|
+
/* "FOREIGN KEY constraint failed" names nothing on SQLite. The graph
|
|
207
|
+
is right here, and one lookup on the failure path turns it into the
|
|
208
|
+
column, the value and the row that is missing. */
|
|
209
|
+
const failure = await failureOf(err, completed, values);
|
|
210
|
+
sendResource(res, '/api/insert', refusedStatus(err, completed), {
|
|
211
|
+
errors: [failure],
|
|
212
|
+
outcome: completed ? 'unknown' : writeFailureOutcome(err),
|
|
213
|
+
});
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Remove a row, having first said what removing it would do.
|
|
219
|
+
*
|
|
220
|
+
* This is the endpoint tablewalk can do better than most clients, and the
|
|
221
|
+
* reason is structural rather than clever: it already counts what points at a
|
|
222
|
+
* row, so it can answer "what breaks" *before* the delete rather than
|
|
223
|
+
* reporting a constraint violation afterwards.
|
|
224
|
+
*
|
|
225
|
+
* The count alone is not the answer. Three rows pointing at this one means
|
|
226
|
+
* three rows deleted under `cascade`, three rows left with a hole under `set
|
|
227
|
+
* null`, and a refused delete under `restrict` — three different outcomes
|
|
228
|
+
* behind one number. So the impact report carries the rule beside the count,
|
|
229
|
+
* and a rule the adapter could not determine is reported as unknown rather
|
|
230
|
+
* than assumed harmless.
|
|
231
|
+
*
|
|
232
|
+
* Two calls, deliberately. Without `confirm` this only reports; with it, it
|
|
233
|
+
* deletes. A destructive endpoint that also happens to be the way you ask
|
|
234
|
+
* about the damage is one typo away from doing it.
|
|
235
|
+
*/
|
|
236
|
+
if (path === '/api/delete') {
|
|
237
|
+
if (!adapter.remove) {
|
|
238
|
+
send(res, 501, { error: `${schema.dialect} connections cannot delete in this build.` });
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
const refused = hooks.gates.write(post, findTable(schema, String(body.table ?? ''))?.id);
|
|
242
|
+
if (refused) {
|
|
243
|
+
send(res, 403, { error: refused, code: 'forbidden' });
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
if (rules && !rules.deletes(post, findTable(schema, String(body.table ?? ''))?.id ?? '')) {
|
|
247
|
+
send(res, 403, { error: 'This app does not delete these records.' });
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
const tableId = String(body.table ?? '');
|
|
251
|
+
const key = (body.key ?? {});
|
|
252
|
+
const table = findTable(schema, tableId);
|
|
253
|
+
if (!table) {
|
|
254
|
+
send(res, 404, { error: `Unknown table "${tableId}"` });
|
|
255
|
+
return true;
|
|
256
|
+
}
|
|
257
|
+
if (wholeRowWrites && !wholeRowWrites.has(tableId))
|
|
258
|
+
throw new AppAccessDenied();
|
|
259
|
+
const impact = await deleteImpact(adapter, schema, tableId, key, rules ? (error) => rules.impact(error) : undefined);
|
|
260
|
+
if (body.confirm !== true) {
|
|
261
|
+
sendResource(res, path, 200, { impact, deleted: false });
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
/* `restrict` and `no action` are left to the database rather than refused
|
|
265
|
+
here. The engine enforces them and its message names the constraint;
|
|
266
|
+
a second implementation of the same rule is how the two come to
|
|
267
|
+
disagree, and tablewalk would be the one that was wrong. */
|
|
268
|
+
let completed = false;
|
|
269
|
+
try {
|
|
270
|
+
const options = writeOptions(tableId);
|
|
271
|
+
/* What the deletion may change, for the journal's Undo, is read off
|
|
272
|
+
the database's own catalog. An App journals nothing. */
|
|
273
|
+
const dependents = options.journal
|
|
274
|
+
? deletionDependents((await registry.open(options.connection)).schema, tableId, (table) => table)
|
|
275
|
+
: undefined;
|
|
276
|
+
const { result, undoId } = await recordedDelete(options, { table: tableId, key }, dependents);
|
|
277
|
+
completed = true;
|
|
278
|
+
indexed(tableId, key);
|
|
279
|
+
sendData({ impact, deleted: true, ...result, ...(undoId === undefined ? {} : { undoId }) });
|
|
280
|
+
return true;
|
|
281
|
+
}
|
|
282
|
+
catch (err) {
|
|
283
|
+
if (!completed && lockTimedOut(err)) {
|
|
284
|
+
sendRowLocked(res, { impact, deleted: false });
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
287
|
+
if (!completed && isPoolBusy(err)) {
|
|
288
|
+
sendBusy(res, { impact, deleted: false }, true, err);
|
|
289
|
+
return true;
|
|
290
|
+
}
|
|
291
|
+
const outcome = completed ? 'unknown' : err instanceof Refusal ? 'rejected' : writeFailureOutcome(err);
|
|
292
|
+
const errors = [rules ? rules.failure(post, err, completed) : { message: err.message }];
|
|
293
|
+
sendResource(res, path, outcome === 'unknown' ? 200 : (writeRefusalStatus(err) ?? 400), outcome === 'unknown' ? { impact, deleted: null, outcome, errors } : { impact, deleted: false, outcome, errors });
|
|
294
|
+
return true;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every refusal names its cause — to the people allowed to hear it.
|
|
3
|
+
*
|
|
4
|
+
* A caller is told one fixed sentence, and must be: a driver's error names
|
|
5
|
+
* columns and values, a clash on a hidden unique column says that a row
|
|
6
|
+
* exists, and a signed-out visitor is owed nothing about how an App is built.
|
|
7
|
+
* But a bare 503 with nothing logged anywhere cost Keystone's authors hours
|
|
8
|
+
* each time — a scoped command whose parent was outside its footprint, an
|
|
9
|
+
* audit entry past its bound, `me` in a rollup. So the reason is recorded on
|
|
10
|
+
* the refusal where it is decided (`recordRefusalCause`, shared/refusal.ts),
|
|
11
|
+
* and said here, at the one place a request's failure becomes its answer:
|
|
12
|
+
*
|
|
13
|
+
* - on the operator's stderr, always, for a server failure (5xx) or a refusal
|
|
14
|
+
* that recorded why — the terminal an author runs the server in;
|
|
15
|
+
* - in the answer itself, as `cause`, only when the reader is the App's
|
|
16
|
+
* author at work (`noteAuthorRequest`): signed in as its administrator (the
|
|
17
|
+
* `admin` role, the account that may already act as anyone), over a
|
|
18
|
+
* loopback listener nothing else can reach, at an App the server is
|
|
19
|
+
* watching in its source directory — the edit loop, where a save reloads.
|
|
20
|
+
* Never a signed-out visitor or an ordinary account, never a build, never a
|
|
21
|
+
* listener the network reaches or a proxy speaks for.
|
|
22
|
+
*/
|
|
23
|
+
import type { IncomingMessage } from 'node:http';
|
|
24
|
+
import { type Bound } from './trust.js';
|
|
25
|
+
/** This App is served from its source and reloads on save. */
|
|
26
|
+
export declare function authoringApp(path: string): void;
|
|
27
|
+
/** Mark this request as the author's own, when it is: see the module comment. */
|
|
28
|
+
export declare function noteAuthorRequest(req: IncomingMessage, bound: Bound, administrator: boolean, apps: ReadonlyArray<{
|
|
29
|
+
path: string;
|
|
30
|
+
build?: unknown;
|
|
31
|
+
}>): void;
|
|
32
|
+
/**
|
|
33
|
+
* Say a refused request to the operator, and return the sentence its author
|
|
34
|
+
* may read, if this request is the author's. Quiet for an ordinary refusal
|
|
35
|
+
* that recorded nothing — a 404 for a typo is not the operator's business —
|
|
36
|
+
* unless `always`: an error that was not a refusal at all, answered as one.
|
|
37
|
+
*/
|
|
38
|
+
export declare function reportRefusal(req: IncomingMessage, status: number, code: string | undefined, heard: string | undefined, error: unknown, always?: boolean): string | undefined;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { refusalReasons, refusalSaid } from '../../shared/refusal.js';
|
|
2
|
+
import { clientTrusted } from './trust.js';
|
|
3
|
+
import { AppAccessDenied } from '../columnGrants.js';
|
|
4
|
+
import { TenantContextError } from '../evidence.js';
|
|
5
|
+
import { currentSpan, log } from '../../adapters/telemetry/trace.js';
|
|
6
|
+
import { product } from '../product.js';
|
|
7
|
+
/* A denial's own sentence says nothing its caller was not told: an ordinary
|
|
8
|
+
refusal of an ordinary request, not a reason. */
|
|
9
|
+
const OPAQUE = new Set([new AppAccessDenied().message, new TenantContextError().message]);
|
|
10
|
+
const authors = new WeakSet();
|
|
11
|
+
/* The Apps this process is authoring: served from source and watched, so a
|
|
12
|
+
save reloads them (cli/serve.ts). By path, since a reload replaces the
|
|
13
|
+
loaded App and a request works on a copy of it. */
|
|
14
|
+
const authoring = new Set();
|
|
15
|
+
/** This App is served from its source and reloads on save. */
|
|
16
|
+
export function authoringApp(path) {
|
|
17
|
+
authoring.add(path);
|
|
18
|
+
}
|
|
19
|
+
/** Mark this request as the author's own, when it is: see the module comment. */
|
|
20
|
+
export function noteAuthorRequest(req, bound, administrator, apps) {
|
|
21
|
+
if (administrator &&
|
|
22
|
+
apps.length &&
|
|
23
|
+
apps.every((app) => !app.build && authoring.has(app.path)) &&
|
|
24
|
+
clientTrusted(bound) &&
|
|
25
|
+
!bound.identityHeader &&
|
|
26
|
+
!bound.trustedProxies?.length)
|
|
27
|
+
authors.add(req);
|
|
28
|
+
}
|
|
29
|
+
/** What the operator is told: the error's own words when it is no refusal at
|
|
30
|
+
all (a driver's, a bug), then each reason recorded beneath it. */
|
|
31
|
+
function reasonsFor(error, heard) {
|
|
32
|
+
const own = error instanceof Error && error.message && error.message !== heard ? [error.message] : [];
|
|
33
|
+
return [...new Set([...own, ...recorded(error)])].filter((reason) => reason !== heard && !OPAQUE.has(reason));
|
|
34
|
+
}
|
|
35
|
+
const recorded = (error) => refusalReasons(error).filter((reason) => !OPAQUE.has(reason));
|
|
36
|
+
/**
|
|
37
|
+
* Say a refused request to the operator, and return the sentence its author
|
|
38
|
+
* may read, if this request is the author's. Quiet for an ordinary refusal
|
|
39
|
+
* that recorded nothing — a 404 for a typo is not the operator's business —
|
|
40
|
+
* unless `always`: an error that was not a refusal at all, answered as one.
|
|
41
|
+
*/
|
|
42
|
+
export function reportRefusal(req, status, code, heard, error, always = false) {
|
|
43
|
+
const reasons = reasonsFor(error, heard);
|
|
44
|
+
/* The same reasons, on the request's span: the code and status travel, the
|
|
45
|
+
reasons stay local for the dev observer, where "Open in observer" leads. */
|
|
46
|
+
const span = currentSpan();
|
|
47
|
+
span?.setAttributes({ 'mistri.refusal.code': code ?? String(status), 'mistri.refusal.status': status });
|
|
48
|
+
span?.detail(() => ({ refusal: { status, ...(code ? { code } : {}), ...(heard ? { said: heard } : {}) }, because: reasons }));
|
|
49
|
+
log(status >= 500 ? 'error' : 'warn', 'refusal', `Request refused (${status}${code ? ` ${code}` : ''})`, {
|
|
50
|
+
attributes: { 'mistri.refusal.code': code, 'http.response.status_code': status },
|
|
51
|
+
detail: () => ({ said: heard, because: reasons }),
|
|
52
|
+
});
|
|
53
|
+
/* A refusal that said its rule (sayRefusal) is the operator's business too: a role or a policy that refuses
|
|
54
|
+
what its author meant it to allow is found here, not first with diagnostics on (field report TR7). */
|
|
55
|
+
if (status < 500 && !recorded(error).length && !refusalSaid(error) && !always)
|
|
56
|
+
return undefined;
|
|
57
|
+
const path = (req.url ?? '').split('?')[0];
|
|
58
|
+
console.error(`${product()}: ${req.method ?? 'GET'} ${path} refused (${status}${code ? ` ${code}` : ''})${heard ? `: ${heard}` : ''}`);
|
|
59
|
+
for (const reason of reasons)
|
|
60
|
+
console.error(` because: ${reason}`);
|
|
61
|
+
// The innermost reason is the one that names the rule, the table or the column.
|
|
62
|
+
return authors.has(req) ? reasons.at(-1) : undefined;
|
|
63
|
+
}
|