@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,700 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { brotliCompressSync, constants as zlib, createBrotliCompress, createGzip, gzipSync } from 'node:zlib';
|
|
3
|
+
import { readFile, readdir } from 'node:fs/promises';
|
|
4
|
+
import { extname, join, normalize, relative, sep } from 'node:path';
|
|
5
|
+
import { SOURCE_CLIENT, devClient } from './devclient.js';
|
|
6
|
+
import { localePreloads, lookPreloads, SURFACE_PAGES, surfaceFor, tenantPreloads } from './clientSurface.js';
|
|
7
|
+
import { FONT_TOKENS, PALETTE_TOKENS, parseThemes, shapeTokens } from '../shared/theme.js';
|
|
8
|
+
import { headingWeight, inheritThemePreset, lookNavRole } from '../shared/themePresets.js';
|
|
9
|
+
import { typeTokens } from '../shared/themeType.js';
|
|
10
|
+
import { fieldEdge } from '../shared/themeColor.js';
|
|
11
|
+
import { recipeAttributes } from '../shared/themeRecipes.js';
|
|
12
|
+
const MIME = {
|
|
13
|
+
'.html': 'text/html; charset=utf-8',
|
|
14
|
+
'.js': 'text/javascript; charset=utf-8',
|
|
15
|
+
'.css': 'text/css; charset=utf-8',
|
|
16
|
+
'.svg': 'image/svg+xml',
|
|
17
|
+
'.json': 'application/json',
|
|
18
|
+
'.map': 'application/json; charset=utf-8',
|
|
19
|
+
'.woff2': 'font/woff2',
|
|
20
|
+
'.txt': 'text/plain; charset=utf-8',
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* The page's Content-Security-Policy: what the shell — the one page every
|
|
24
|
+
* App, sign-in and the Workbench are drawn in — may load and talk to.
|
|
25
|
+
*
|
|
26
|
+
* The reason is egress. Every endpoint the page can reach is authorized on
|
|
27
|
+
* the server, so a script in the page can do nothing the reader could not;
|
|
28
|
+
* what the server cannot stop is a script sending what the reader sees
|
|
29
|
+
* somewhere else. So scripts come from this origin and from the inline
|
|
30
|
+
* startup scripts in index.html, pinned by their hashes, and the page talks
|
|
31
|
+
* to this origin alone (`connect-src 'self'`, which also admits the dev
|
|
32
|
+
* server's hot-reload socket on the same port). Images may come from any
|
|
33
|
+
* https address, because an App may name a picture by URL in a column (a
|
|
34
|
+
* card's image); nothing else leaves the origin. Styles keep
|
|
35
|
+
* `'unsafe-inline'`: React's style props, the layout preview's copied
|
|
36
|
+
* sheets and the dev server's injected stylesheets are all inline, and a
|
|
37
|
+
* stylesheet cannot send anything the image and connect rules do not let
|
|
38
|
+
* out. The hashes are read from the page being sent — Vite adds its own
|
|
39
|
+
* inline script in development — so a changed startup script cannot drift
|
|
40
|
+
* from its policy.
|
|
41
|
+
*/
|
|
42
|
+
export function shellPolicy(html, { fromSource = false, socket } = {}) {
|
|
43
|
+
const hashes = [];
|
|
44
|
+
for (const [, attributes, body] of html.matchAll(/<script\b([^>]*)>([\s\S]*?)<\/script>/gi)) {
|
|
45
|
+
/* A data block (firstPaint.ts) is never run, so it is never allowed to be. */
|
|
46
|
+
if (/\bsrc\s*=/i.test(attributes) || /\btype\s*=\s*"application\/json"/i.test(attributes))
|
|
47
|
+
continue;
|
|
48
|
+
hashes.push(`'sha256-${createHash('sha256').update(body, 'utf8').digest('base64')}'`);
|
|
49
|
+
}
|
|
50
|
+
return [
|
|
51
|
+
"default-src 'self'",
|
|
52
|
+
["script-src 'self'", ...hashes].join(' '),
|
|
53
|
+
"style-src 'self' 'unsafe-inline'",
|
|
54
|
+
"img-src 'self' data: blob: https:",
|
|
55
|
+
"font-src 'self' data:",
|
|
56
|
+
/* From source only, the dev client's hot-reload socket where it is not this origin's (devclient.ts devSocket). */
|
|
57
|
+
["connect-src 'self'", ...(fromSource && socket ? [socket] : [])].join(' '),
|
|
58
|
+
"object-src 'none'",
|
|
59
|
+
/* An attached PDF is previewed in a frame of a Blob the page made from
|
|
60
|
+
bytes it checked begin `%PDF-` (FilePreview.tsx): a document of this
|
|
61
|
+
origin, typed by the page, never a URL a column names. */
|
|
62
|
+
"frame-src 'self' blob:",
|
|
63
|
+
"base-uri 'none'",
|
|
64
|
+
"frame-ancestors 'self'",
|
|
65
|
+
/* From source only: Vite's client, when its hot-reload socket drops,
|
|
66
|
+
waits for the server in a shared worker it makes from a blob. The
|
|
67
|
+
built client has no such worker and is not offered one. */
|
|
68
|
+
...(fromSource ? ["worker-src 'self' blob:"] : []),
|
|
69
|
+
].join('; ');
|
|
70
|
+
}
|
|
71
|
+
/** The egress of the Apps a server serves, from their `ui`, whether each serves components, and the origin of the
|
|
72
|
+
human check widget its public forms draw (the App framework's commands/challenges.ts), if any. */
|
|
73
|
+
export function shellEgress(apps) {
|
|
74
|
+
const connect = [...new Set(apps.flatMap((app) => app.ui.csp?.connect ?? []))].sort();
|
|
75
|
+
const images = [...new Set(apps.flatMap((app) => app.ui.csp?.images ?? []))].sort();
|
|
76
|
+
const narrowImages = apps.some((app) => app.components);
|
|
77
|
+
const widgets = [...new Set(apps.flatMap((app) => (app.widget ? [new URL(app.widget).origin] : [])))].sort();
|
|
78
|
+
return connect.length || images.length || narrowImages || widgets.length
|
|
79
|
+
? { connect, images, narrowImages, ...(widgets.length ? { widgets } : {}) }
|
|
80
|
+
: undefined;
|
|
81
|
+
}
|
|
82
|
+
/** The shell's policy with the served Apps' egress in its connect and image sources (shellPolicy writes both directives exactly so). */
|
|
83
|
+
export function withEgress(policy, egress) {
|
|
84
|
+
if (!egress)
|
|
85
|
+
return policy;
|
|
86
|
+
return policy
|
|
87
|
+
.split('; ')
|
|
88
|
+
.map((directive) => {
|
|
89
|
+
const widgets = egress.widgets ?? [];
|
|
90
|
+
if (directive.startsWith("connect-src 'self'"))
|
|
91
|
+
return [directive, ...egress.connect, ...widgets].join(' ');
|
|
92
|
+
if (widgets.length && (directive.startsWith("script-src 'self'") || directive.startsWith("frame-src 'self'")))
|
|
93
|
+
return [directive, ...widgets].join(' ');
|
|
94
|
+
if (directive.startsWith('img-src ') && egress.narrowImages)
|
|
95
|
+
return ["img-src 'self' data: blob:", ...egress.images].join(' ');
|
|
96
|
+
if (directive.startsWith('img-src '))
|
|
97
|
+
return [directive, ...egress.images].join(' ');
|
|
98
|
+
return directive;
|
|
99
|
+
})
|
|
100
|
+
.join('; ');
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* The shell's policy with who may frame it: `'self'` alone, unless the page
|
|
104
|
+
* was asked for as a frame (`?embed=frame`, http/handler.ts) and a host's
|
|
105
|
+
* origins are listed for it (embed/crossOrigin.ts) — the iframe fallback for
|
|
106
|
+
* a page that is not a React app, or whose own unlayered CSS would reach an
|
|
107
|
+
* embedded shell. Only an origin listed is ever named: everyone else is
|
|
108
|
+
* refused the frame, as before. shellPolicy writes the directive exactly so.
|
|
109
|
+
*/
|
|
110
|
+
function withFrameAncestors(policy, origins) {
|
|
111
|
+
if (!origins?.length)
|
|
112
|
+
return policy;
|
|
113
|
+
return policy.replace("frame-ancestors 'self'", ["frame-ancestors 'self'", ...origins].join(' '));
|
|
114
|
+
}
|
|
115
|
+
/** A page's whole policy: its own, the served Apps' egress, and who may frame it. */
|
|
116
|
+
const pagePolicy = (policy, shell) => shell?.unframed
|
|
117
|
+
? withEgress(policy, shell.egress).replace("frame-ancestors 'self'", "frame-ancestors 'none'")
|
|
118
|
+
: withFrameAncestors(withEgress(policy, shell?.egress), shell?.frameAncestors);
|
|
119
|
+
const escapeHtml = (text) => text.replace(/[&<>"]/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"' })[c]);
|
|
120
|
+
/** Whether a page's head changes its bytes: anything but the egress, which is only its policy. */
|
|
121
|
+
const writesHead = (head) => Boolean(head?.title ||
|
|
122
|
+
head?.canonical ||
|
|
123
|
+
head?.mount ||
|
|
124
|
+
head?.workbench ||
|
|
125
|
+
head?.observe ||
|
|
126
|
+
head?.density ||
|
|
127
|
+
head?.scheme ||
|
|
128
|
+
head?.grounds ||
|
|
129
|
+
head?.branding ||
|
|
130
|
+
head?.looks?.length ||
|
|
131
|
+
head?.social ||
|
|
132
|
+
head?.pane ||
|
|
133
|
+
head?.pwa);
|
|
134
|
+
/** Generate only registered token names and validated theme values. Row text
|
|
135
|
+
* exists only in escaped JSON; it can never close a style/script element. */
|
|
136
|
+
function brandingHead(html, presentation) {
|
|
137
|
+
const theme = parseThemes([presentation.value.theme], 'Request branding')[0];
|
|
138
|
+
const declarations = (mode) => {
|
|
139
|
+
const pairs = [];
|
|
140
|
+
for (const [key, token] of Object.entries(PALETTE_TOKENS)) {
|
|
141
|
+
const value = theme[mode][key];
|
|
142
|
+
if (value)
|
|
143
|
+
pairs.push([token, value]);
|
|
144
|
+
}
|
|
145
|
+
// A command form's field edge at rest, as the App's theme writer derives it (appTheme.js).
|
|
146
|
+
const edge = fieldEdge(theme[mode]);
|
|
147
|
+
if (edge)
|
|
148
|
+
pairs.push(['--field-edge', edge]);
|
|
149
|
+
for (const [key, token] of Object.entries(FONT_TOKENS)) {
|
|
150
|
+
const value = theme.fonts?.[key];
|
|
151
|
+
if (value)
|
|
152
|
+
pairs.push([token, value]);
|
|
153
|
+
}
|
|
154
|
+
for (const [token, value] of Object.entries(shapeTokens(theme)))
|
|
155
|
+
pairs.push([token, value]);
|
|
156
|
+
for (const [token, value] of Object.entries(typeTokens(inheritThemePreset(theme).type)))
|
|
157
|
+
pairs.push([token, value]);
|
|
158
|
+
pairs.push(['--heading-weight', String(headingWeight(theme))]);
|
|
159
|
+
return pairs.map(([key, value]) => `${key}:${value}`).join(';');
|
|
160
|
+
};
|
|
161
|
+
const root = ':root[data-record-branding]';
|
|
162
|
+
const css = `${root}{${declarations('light')}}@media(prefers-color-scheme:dark){${root}:not([data-theme="light"]){${declarations('dark')}}}${root}[data-theme="dark"]{${declarations('dark')}}`;
|
|
163
|
+
const json = JSON.stringify(presentation).replace(/[<>&\u2028\u2029]/g, (char) => `\\u${char.charCodeAt(0).toString(16).padStart(4, '0')}`);
|
|
164
|
+
const block = `<style id="mistri-branding-style">${css}</style><script type="application/json" id="mistri-branding">${json}</script>`;
|
|
165
|
+
/* The recipes are menu values (parseThemes refused anything else), written as attributes the sheets answer. */
|
|
166
|
+
const looks = Object.entries(recipeAttributes(inheritThemePreset(theme), lookNavRole(theme)).attributes)
|
|
167
|
+
.map(([name, value]) => ` ${name}="${escapeHtml(value)}"`)
|
|
168
|
+
.join('');
|
|
169
|
+
let out = html.replace(/<html\b([^>]*)>/i, (_, attributes) => `<html${attributes} data-record-branding data-custom-theme data-visual-theme="${theme.id}" data-theme-style="${theme.base ?? ''}" data-theme-palette="default"${looks}>`);
|
|
170
|
+
out = /<meta charset[^>]*>/i.test(out)
|
|
171
|
+
? out.replace(/<meta charset[^>]*>/i, (match) => `${match}\n${block}`)
|
|
172
|
+
: /<head>/i.test(out)
|
|
173
|
+
? out.replace(/<head>/i, (match) => `${match}${block}`)
|
|
174
|
+
: `${block}${out}`;
|
|
175
|
+
return out;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* The shell with a page's own head written in. Under a base path the page's
|
|
179
|
+
* own files are said under it (a component's host modules are said under it
|
|
180
|
+
* where the component is served, componentRoutes.ts). An inline script the
|
|
181
|
+
* head adds changes the page, so a caller whose page changed takes its policy
|
|
182
|
+
* from the page it sends (shellPolicy), never the static file's.
|
|
183
|
+
*/
|
|
184
|
+
export function withHead(html, head) {
|
|
185
|
+
if (!writesHead(head))
|
|
186
|
+
return html;
|
|
187
|
+
let out = html;
|
|
188
|
+
if (head?.branding)
|
|
189
|
+
out = brandingHead(out, head.branding);
|
|
190
|
+
const base = head.mount?.base;
|
|
191
|
+
if (base) {
|
|
192
|
+
out = out.replace(/(\s(?:src|href)=")\/(?=assets\/)/g, (_, attribute) => `${attribute}${base}/`);
|
|
193
|
+
}
|
|
194
|
+
/* Before any script reads them: index.html's first script keys its startup colours by App and stamps its scheme. */
|
|
195
|
+
const beforeScripts = (meta) => {
|
|
196
|
+
out = /<meta charset[^>]*>/i.test(out)
|
|
197
|
+
? out.replace(/<meta charset[^>]*>/i, (match) => `${match}\n ${meta}`)
|
|
198
|
+
: /<head>/i.test(out)
|
|
199
|
+
? out.replace(/<head>/i, (match) => `${match}${meta}`)
|
|
200
|
+
: out.replace(/<title>|<script/i, (match) => `${meta}${match}`);
|
|
201
|
+
};
|
|
202
|
+
if (head.mount)
|
|
203
|
+
beforeScripts(`<meta name="mistri-mount" content="${escapeHtml(head.mount.json)}">`);
|
|
204
|
+
/* Read by the entry before it starts a main (packages/kit/src/client/shellStart.js): the Workbench's, not an App's. */
|
|
205
|
+
if (head.workbench)
|
|
206
|
+
beforeScripts('<meta name="mistri-shell" content="workbench">');
|
|
207
|
+
if (head.scheme)
|
|
208
|
+
beforeScripts(`<meta name="mistri-scheme" content="${escapeHtml(head.scheme)}">`);
|
|
209
|
+
if (head.grounds)
|
|
210
|
+
out = out.replace(/<\/head>/i, () => ` ${groundsMeta(head.grounds)}\n </head>`);
|
|
211
|
+
if (head.observe)
|
|
212
|
+
out = out.replace(/<\/head>/i, () => ` <meta name="mistri-observe" content="${escapeHtml(head.observe)}">\n </head>`);
|
|
213
|
+
if (head.density)
|
|
214
|
+
out = out.replace(/<\/head>/i, () => ` <meta name="mistri-density" content="${escapeHtml(head.density)}">\n </head>`);
|
|
215
|
+
/* An installable App: its manifest, and the script that registers its
|
|
216
|
+
worker once the page has loaded. Both from this origin, so no inline
|
|
217
|
+
script is needed and the page's policy admits them as they stand. */
|
|
218
|
+
if (head.pwa)
|
|
219
|
+
out = out.replace(/<\/head>/i, () => ` <link rel="manifest" href="${escapeHtml(head.pwa.manifest)}">\n <script src="${escapeHtml(head.pwa.register)}" defer></script>\n </head>`);
|
|
220
|
+
if (!head?.title && !head?.canonical)
|
|
221
|
+
return out;
|
|
222
|
+
/* Replaced by a function, never a replacement string: a string's `$&`,
|
|
223
|
+
`` $` `` and `$'` are patterns, and a record's name is anybody's text —
|
|
224
|
+
`$'` in a title would pour the rest of the page into it. */
|
|
225
|
+
const title = head.title && `<title>${escapeHtml(head.title)}</title>`;
|
|
226
|
+
const canonical = head.canonical && ` <link rel="canonical" href="${escapeHtml(head.canonical)}">\n`;
|
|
227
|
+
const social = head.social ? socialMeta(head.social) : '';
|
|
228
|
+
if (title)
|
|
229
|
+
out = out.replace(/<title>[^<]*<\/title>/i, () => title);
|
|
230
|
+
if (canonical || social)
|
|
231
|
+
out = out.replace(/<\/head>/i, () => `${canonical || ''}${social} </head>`);
|
|
232
|
+
return out;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* What the browser draws around the page: the form controls and scroll bars in the scheme the theme wears
|
|
236
|
+
* (`color-scheme`; a dark-only theme says dark alone), and the address bar and overscroll in its ground, one
|
|
237
|
+
* `theme-color` for each mode the reader's machine can be in. Plain HTML in the head: nothing for a script to wait on.
|
|
238
|
+
*/
|
|
239
|
+
export function groundsMeta(grounds) {
|
|
240
|
+
const colors = [grounds.light, grounds.dark].map(escapeHtml);
|
|
241
|
+
if (grounds.scheme === 'dark-only')
|
|
242
|
+
return `<meta name="color-scheme" content="dark">\n <meta name="theme-color" content="${colors[1]}">`;
|
|
243
|
+
return [
|
|
244
|
+
'<meta name="color-scheme" content="light dark">',
|
|
245
|
+
`<meta name="theme-color" content="${colors[0]}" media="(prefers-color-scheme: light)">`,
|
|
246
|
+
`<meta name="theme-color" content="${colors[1]}" media="(prefers-color-scheme: dark)">`,
|
|
247
|
+
].join('\n ');
|
|
248
|
+
}
|
|
249
|
+
/** The tags a link preview and a search result read, every value escaped: a record's name and words are anybody's text. */
|
|
250
|
+
function socialMeta(social) {
|
|
251
|
+
const tags = [
|
|
252
|
+
['name', 'description', social.description],
|
|
253
|
+
['property', 'og:type', social.type],
|
|
254
|
+
['property', 'og:site_name', social.site],
|
|
255
|
+
['property', 'og:title', social.title],
|
|
256
|
+
['property', 'og:url', social.url],
|
|
257
|
+
['property', 'og:description', social.description],
|
|
258
|
+
['property', 'og:image', social.image],
|
|
259
|
+
['property', 'og:image:alt', social.image ? social.imageAlt || undefined : undefined],
|
|
260
|
+
['name', 'twitter:card', social.image ? 'summary_large_image' : 'summary'],
|
|
261
|
+
['name', 'twitter:title', social.title],
|
|
262
|
+
['name', 'twitter:description', social.description],
|
|
263
|
+
['name', 'twitter:image', social.image],
|
|
264
|
+
];
|
|
265
|
+
return tags
|
|
266
|
+
.filter(([, , value]) => value)
|
|
267
|
+
.map(([attribute, key, value]) => ` <meta ${attribute}="${key}" content="${escapeHtml(value)}">\n`)
|
|
268
|
+
.join('');
|
|
269
|
+
}
|
|
270
|
+
/** The tag that opens the content pane, and everything to its close: the place a server-drawn pane goes. */
|
|
271
|
+
const PANE = /<main\b([^>]*\bid="content"[^>]*)>[\s\S]*?<\/main>/i;
|
|
272
|
+
/** The page with its content pane drawn in, and marked with its kind for the client (client serverDrawing.js). */
|
|
273
|
+
function withPane(html, pane) {
|
|
274
|
+
if (!pane)
|
|
275
|
+
return html;
|
|
276
|
+
return html.replace(PANE, (_, attributes) => `<main${attributes} data-ssr="${pane.kind}">${pane.html}</main>`);
|
|
277
|
+
}
|
|
278
|
+
/** The pane, or none: a page is served whole whether or not the server could draw it. */
|
|
279
|
+
async function drawnPane(shell, data) {
|
|
280
|
+
if (!shell?.pane)
|
|
281
|
+
return undefined;
|
|
282
|
+
try {
|
|
283
|
+
return await shell.pane(data?.answers);
|
|
284
|
+
}
|
|
285
|
+
catch {
|
|
286
|
+
return undefined;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
/** The page asking for its look's sheets, its reader's words and a tenant host's flows beside the entry (clientSurface.ts), ahead of the entry's own script. */
|
|
290
|
+
function withLooks(html, shell, files) {
|
|
291
|
+
const assets = [...files];
|
|
292
|
+
const links = lookPreloads(shell?.looks ?? [], assets, shell?.faces) +
|
|
293
|
+
(shell?.appFaces ?? '') +
|
|
294
|
+
localePreloads(shell?.locale, assets) +
|
|
295
|
+
(shell?.tenant && !shell.workbench && surfaceFor(shell.path ?? '') === 'shell' ? tenantPreloads(assets) : '');
|
|
296
|
+
if (!links)
|
|
297
|
+
return html;
|
|
298
|
+
return /<script type="module"/i.test(html)
|
|
299
|
+
? html.replace(/<script type="module"/i, (match) => `${links.trimStart()} ${match}`)
|
|
300
|
+
: html.replace(/<\/head>/i, () => `${links} </head>`);
|
|
301
|
+
}
|
|
302
|
+
/** The page with its data block (firstPaint.ts) at the end of its body, where nothing waits on it. */
|
|
303
|
+
function withData(html, data) {
|
|
304
|
+
if (!data)
|
|
305
|
+
return html;
|
|
306
|
+
return /<\/body>/i.test(html) ? html.replace(/<\/body>/i, () => `${data.block}\n </body>`) : `${html}${data.block}`;
|
|
307
|
+
}
|
|
308
|
+
/** The page's data, or none: a page is served whole whether or not its data could be asked. */
|
|
309
|
+
async function pageData(shell, head) {
|
|
310
|
+
if (head || !shell?.data)
|
|
311
|
+
return undefined;
|
|
312
|
+
try {
|
|
313
|
+
return await shell.data();
|
|
314
|
+
}
|
|
315
|
+
catch {
|
|
316
|
+
return undefined;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* How a page is kept. The shell alone is everybody's and revalidated; one
|
|
321
|
+
* that may carry answers is one reader's (`Vary: Cookie`) and is not kept at
|
|
322
|
+
* all: who-am-I, the first of them, never is either.
|
|
323
|
+
*/
|
|
324
|
+
const PERSONAL = 'private, no-store';
|
|
325
|
+
const pageCache = (data, shell) => (data || shell?.branding?.private ? PERSONAL : 'no-cache');
|
|
326
|
+
/**
|
|
327
|
+
* A page that carries its data, sent as it is made: everything up to the end
|
|
328
|
+
* of its body at once — the entry and its preloads start downloading — and
|
|
329
|
+
* the data block once it is asked, then the rest. The entry is a module,
|
|
330
|
+
* which runs once the page is parsed, so it reads the block however late it
|
|
331
|
+
* comes; the browser's wait for the answers overlaps its wait for the script.
|
|
332
|
+
* Compressed as it streams, flushed at the break. No ETag: the bytes are not
|
|
333
|
+
* known when the headers go, and the page is not kept anyway.
|
|
334
|
+
*/
|
|
335
|
+
async function streamPage(res, text, headers, encoding, data, shell) {
|
|
336
|
+
/* A page whose pane the server may draw is sent up to the pane at once —
|
|
337
|
+
its head, its preloads, its chrome — and the rest once the answers are
|
|
338
|
+
in and the pane is drawn from them. */
|
|
339
|
+
const at = shell?.pane ? text.search(PANE) : text.search(/<\/body>/i);
|
|
340
|
+
const [before, after] = at < 0 ? [text, ''] : [text.slice(0, at), text.slice(at)];
|
|
341
|
+
const coder = encoding === 'br'
|
|
342
|
+
? createBrotliCompress({ params: { [zlib.BROTLI_PARAM_QUALITY]: 4, [zlib.BROTLI_PARAM_MODE]: zlib.BROTLI_MODE_TEXT } })
|
|
343
|
+
: encoding === 'gzip'
|
|
344
|
+
? createGzip({ level: 6 })
|
|
345
|
+
: undefined;
|
|
346
|
+
const finished = new Promise((resolve) => {
|
|
347
|
+
const done = () => {
|
|
348
|
+
res.off('finish', done);
|
|
349
|
+
res.off('close', done);
|
|
350
|
+
resolve();
|
|
351
|
+
};
|
|
352
|
+
res.once('finish', done);
|
|
353
|
+
res.once('close', done);
|
|
354
|
+
});
|
|
355
|
+
res.writeHead(200, { ...headers, ...(encoding ? { 'content-encoding': encoding } : {}) });
|
|
356
|
+
const out = coder ?? res;
|
|
357
|
+
if (coder) {
|
|
358
|
+
coder.pipe(res);
|
|
359
|
+
coder.on('error', () => res.destroy());
|
|
360
|
+
}
|
|
361
|
+
out.write(before);
|
|
362
|
+
coder?.flush();
|
|
363
|
+
let block;
|
|
364
|
+
try {
|
|
365
|
+
block = res.destroyed ? undefined : await data();
|
|
366
|
+
}
|
|
367
|
+
catch {
|
|
368
|
+
block = undefined;
|
|
369
|
+
}
|
|
370
|
+
if (res.destroyed) {
|
|
371
|
+
coder?.destroy();
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
if (shell?.pane && at >= 0) {
|
|
375
|
+
const pane = await drawnPane(shell, block);
|
|
376
|
+
if (res.destroyed) {
|
|
377
|
+
coder?.destroy();
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
out.end(withData(withPane(after, pane), block));
|
|
381
|
+
await finished;
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
out.end(`${block ? `${block.block}\n ` : ''}${after}`);
|
|
385
|
+
await finished;
|
|
386
|
+
}
|
|
387
|
+
export async function serveClient(files, req, res, shell) {
|
|
388
|
+
const head = req.method === 'HEAD';
|
|
389
|
+
if (req.method !== 'GET' && !head) {
|
|
390
|
+
res.writeHead(405, { allow: 'GET, HEAD' }).end('Method not allowed');
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
if (res.destroyed || res.writableEnded)
|
|
394
|
+
return;
|
|
395
|
+
if (SOURCE_CLIENT) {
|
|
396
|
+
const base = shell?.mount?.base ?? '';
|
|
397
|
+
const vite = await devClient(files, base);
|
|
398
|
+
if (res.destroyed || res.writableEnded)
|
|
399
|
+
return;
|
|
400
|
+
// App routing already removed its base. Vite's native base middleware
|
|
401
|
+
// expects the external address and removes it itself when serving files.
|
|
402
|
+
const sourceURL = `${base}${req.url ?? '/'}`;
|
|
403
|
+
req.url = sourceURL;
|
|
404
|
+
// Middleware owns module/style responses; unmatched paths render the SPA.
|
|
405
|
+
// Hold the request through completion, including an in-flight HTML fallback
|
|
406
|
+
// after disconnect, so a retiring host does not release its owner early.
|
|
407
|
+
await new Promise((resolve, reject) => {
|
|
408
|
+
let fallback;
|
|
409
|
+
const clean = () => {
|
|
410
|
+
res.off('finish', complete);
|
|
411
|
+
res.off('close', complete);
|
|
412
|
+
res.off('error', fail);
|
|
413
|
+
};
|
|
414
|
+
const done = () => {
|
|
415
|
+
clean();
|
|
416
|
+
resolve();
|
|
417
|
+
};
|
|
418
|
+
const fail = (error) => {
|
|
419
|
+
clean();
|
|
420
|
+
reject(error);
|
|
421
|
+
};
|
|
422
|
+
const complete = () => {
|
|
423
|
+
if (fallback)
|
|
424
|
+
void fallback.then(done, fail);
|
|
425
|
+
else
|
|
426
|
+
done();
|
|
427
|
+
};
|
|
428
|
+
res.once('finish', complete);
|
|
429
|
+
res.once('close', complete);
|
|
430
|
+
res.once('error', fail);
|
|
431
|
+
try {
|
|
432
|
+
vite.middlewares(req, res, (error) => {
|
|
433
|
+
fallback = Promise.resolve().then(async () => {
|
|
434
|
+
if (res.destroyed || res.writableEnded)
|
|
435
|
+
return;
|
|
436
|
+
if (error) {
|
|
437
|
+
res.writeHead(500).end(head ? undefined : String(error));
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
try {
|
|
441
|
+
const raw = await readFile(join(files.source, 'index.html'), 'utf8');
|
|
442
|
+
const data = await pageData(shell, head);
|
|
443
|
+
const pane = head ? undefined : await drawnPane(shell, data);
|
|
444
|
+
/* No built files to preload from source; an App theme's own faces are the App's, and are said as in a build. */
|
|
445
|
+
const html = withData(withPane(withHead(withLooks(await vite.transformIndexHtml(sourceURL, raw), shell, []), shell), pane), data);
|
|
446
|
+
if (res.destroyed || res.writableEnded)
|
|
447
|
+
return;
|
|
448
|
+
res.writeHead(200, {
|
|
449
|
+
'content-type': MIME['.html'],
|
|
450
|
+
'cache-control': pageCache(data, shell),
|
|
451
|
+
...(data || shell?.branding?.private ? { vary: 'Cookie' } : {}),
|
|
452
|
+
'x-content-type-options': 'nosniff',
|
|
453
|
+
'content-security-policy': pagePolicy(shellPolicy(html, { fromSource: true, socket: vite.socket }), shell),
|
|
454
|
+
});
|
|
455
|
+
res.end(head ? undefined : html);
|
|
456
|
+
}
|
|
457
|
+
catch {
|
|
458
|
+
if (!res.destroyed && !res.writableEnded)
|
|
459
|
+
res.writeHead(404).end(head ? undefined : 'Not found');
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
void fallback.then(() => {
|
|
463
|
+
if (res.destroyed || res.writableFinished)
|
|
464
|
+
complete();
|
|
465
|
+
}, fail);
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
catch (error) {
|
|
469
|
+
fail(error);
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
await fromBuild(files.built)(req, res, head, shell);
|
|
475
|
+
}
|
|
476
|
+
/** Named for their content (vite.config.ts): a changed file is a new name, never new bytes under an old one. */
|
|
477
|
+
const IMMUTABLE = 'public, max-age=31536000, immutable';
|
|
478
|
+
/** A source map is named after its script, not its own content, so it is asked about each time. */
|
|
479
|
+
const hashed = (rel) => rel.startsWith('assets/') && !rel.endsWith('.map');
|
|
480
|
+
const COMPRESSED = [
|
|
481
|
+
['.br', 'br'],
|
|
482
|
+
['.gz', 'gzip'],
|
|
483
|
+
];
|
|
484
|
+
/** A strong ETag: the hash of the bytes sent, so two servers behind one balancer agree. */
|
|
485
|
+
const strongTag = (data) => `"${createHash('sha256').update(data).digest('base64url').slice(0, 27)}"`;
|
|
486
|
+
/**
|
|
487
|
+
* Every file in the directory, read once and kept. dist does not change
|
|
488
|
+
* while the process runs — a new build is a new install and a restart — so
|
|
489
|
+
* each variant's strong ETag is the hash of its bytes, taken on the first
|
|
490
|
+
* request, and every request after it is a map lookup. The hash is of the
|
|
491
|
+
* content rather than a timestamp so two servers behind one balancer agree.
|
|
492
|
+
*/
|
|
493
|
+
async function inventory(dir) {
|
|
494
|
+
const names = (await readdir(dir, { recursive: true, withFileTypes: true }))
|
|
495
|
+
.filter((entry) => entry.isFile())
|
|
496
|
+
.map((entry) => relative(dir, join(entry.parentPath, entry.name)).split(sep).join('/'));
|
|
497
|
+
const present = new Set(names);
|
|
498
|
+
const tag = async (file) => {
|
|
499
|
+
const data = await readFile(file);
|
|
500
|
+
return { size: data.length, etag: strongTag(data) };
|
|
501
|
+
};
|
|
502
|
+
const assets = new Map();
|
|
503
|
+
await Promise.all(names
|
|
504
|
+
.filter((rel) => !COMPRESSED.some(([suffix]) => rel.endsWith(suffix) && present.has(rel.slice(0, -suffix.length))))
|
|
505
|
+
.map(async (rel) => {
|
|
506
|
+
const file = join(dir, rel);
|
|
507
|
+
const variants = [];
|
|
508
|
+
for (const [suffix, encoding] of COMPRESSED) {
|
|
509
|
+
if (present.has(rel + suffix))
|
|
510
|
+
variants.push({ file: file + suffix, encoding, ...(await tag(file + suffix)) });
|
|
511
|
+
}
|
|
512
|
+
variants.push({ file, ...(await tag(file)) });
|
|
513
|
+
/* A page carries its policy, read from the page itself once (shellPolicy). */
|
|
514
|
+
const policy = extname(rel) === '.html' ? shellPolicy(await readFile(file, 'utf8')) : undefined;
|
|
515
|
+
assets.set(rel, {
|
|
516
|
+
type: MIME[extname(rel)] ?? 'application/octet-stream',
|
|
517
|
+
cache: hashed(rel) ? IMMUTABLE : 'no-cache',
|
|
518
|
+
variants,
|
|
519
|
+
...(policy ? { policy } : {}),
|
|
520
|
+
});
|
|
521
|
+
}));
|
|
522
|
+
return assets;
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* The codings a request will take (RFC 9110 §12.5.3). A coding named with
|
|
526
|
+
* `q=0` is refused, `*` answers for every coding not named, and a request
|
|
527
|
+
* that names none gets the file as it is. The server's order decides among
|
|
528
|
+
* the acceptable ones — brotli is smaller than gzip for the same text — so
|
|
529
|
+
* only a zero weight counts, not how two nonzero weights compare.
|
|
530
|
+
*/
|
|
531
|
+
function acceptable(header) {
|
|
532
|
+
const weights = new Map();
|
|
533
|
+
for (const part of (Array.isArray(header) ? header.join(',') : (header ?? '')).split(',')) {
|
|
534
|
+
const [name = '', ...params] = part.split(';').map((piece) => piece.trim().toLowerCase());
|
|
535
|
+
if (!name)
|
|
536
|
+
continue;
|
|
537
|
+
const q = params.map((param) => /^q\s*=\s*([\d.]+)$/.exec(param)?.[1]).find((value) => value !== undefined);
|
|
538
|
+
weights.set(name, q === undefined ? 1 : Number(q) || 0);
|
|
539
|
+
}
|
|
540
|
+
return (encoding) => (weights.get(encoding) ?? (encoding === 'gzip' ? weights.get('x-gzip') : undefined) ?? weights.get('*') ?? 0) > 0;
|
|
541
|
+
}
|
|
542
|
+
/** A page with its own head, compressed as it is sent: brotli at quality 4 or
|
|
543
|
+
* gzip at 6, as API replies are (http/reply.ts). The shell is a few kilobytes,
|
|
544
|
+
* well under a millisecond either way. */
|
|
545
|
+
function compressPage(page, encoding) {
|
|
546
|
+
return encoding === 'br'
|
|
547
|
+
? brotliCompressSync(page, {
|
|
548
|
+
params: {
|
|
549
|
+
[zlib.BROTLI_PARAM_QUALITY]: 4,
|
|
550
|
+
[zlib.BROTLI_PARAM_SIZE_HINT]: page.length,
|
|
551
|
+
[zlib.BROTLI_PARAM_MODE]: zlib.BROTLI_MODE_TEXT,
|
|
552
|
+
},
|
|
553
|
+
})
|
|
554
|
+
: gzipSync(page, { level: 6 });
|
|
555
|
+
}
|
|
556
|
+
/** If-None-Match names this representation, by the weak comparison the header is defined with. */
|
|
557
|
+
function unchanged(header, etag) {
|
|
558
|
+
if (!header)
|
|
559
|
+
return false;
|
|
560
|
+
return header.split(',').some((tag) => {
|
|
561
|
+
const t = tag.trim();
|
|
562
|
+
return t === '*' || t.replace(/^W\//, '') === etag;
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
/** Serves a built client directory: the one the package ships, or a test's. */
|
|
566
|
+
export function builtClient(dir) {
|
|
567
|
+
const root = dir.endsWith(sep) ? dir : dir + sep;
|
|
568
|
+
let files;
|
|
569
|
+
return async (req, res, head, shell) => {
|
|
570
|
+
const path = new URL(req.url ?? '/', 'http://localhost').pathname;
|
|
571
|
+
// Normalize before joining so .. cannot walk out of the client directory.
|
|
572
|
+
const rel = normalize(path).replace(/^(\.\.[/\\])+/, '');
|
|
573
|
+
const file = join(root, rel);
|
|
574
|
+
if (!file.startsWith(root)) {
|
|
575
|
+
res.writeHead(403).end(head ? undefined : 'Forbidden');
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
let assets;
|
|
579
|
+
try {
|
|
580
|
+
assets = await (files ??= inventory(root));
|
|
581
|
+
}
|
|
582
|
+
catch (error) {
|
|
583
|
+
files = undefined;
|
|
584
|
+
throw error;
|
|
585
|
+
}
|
|
586
|
+
/* Unknown paths are SPA routes, not missing files: the page for the
|
|
587
|
+
surface the address will draw, which preloads that part (clientSurface.ts).
|
|
588
|
+
A build without per-surface pages (a test's) has index.html alone.
|
|
589
|
+
The root is a route like any other, not the file index.html, which is
|
|
590
|
+
an App's shell page: the database browser's landing is at `/`, and its
|
|
591
|
+
page preloads its own main, not the App's chrome. */
|
|
592
|
+
const asset = (path === '/' ? undefined : assets.get(relative(root, file).split(sep).join('/'))) ??
|
|
593
|
+
assets.get(SURFACE_PAGES[shell?.workbench ? 'workbench' : surfaceFor(shell?.path ?? path)]) ??
|
|
594
|
+
assets.get('index.html');
|
|
595
|
+
if (!asset) {
|
|
596
|
+
if (!res.destroyed && !res.writableEnded)
|
|
597
|
+
res.writeHead(404).end(head ? undefined : 'Not found');
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
/* A public page's own head (withHead): the shell's bytes with it written
|
|
601
|
+
in, sent as they are. It names one record, so it is never the shared
|
|
602
|
+
file's ETag and never a cached copy of another page's: its ETag is the
|
|
603
|
+
hash of these bytes, so a revisit is still a 304 until the head — a
|
|
604
|
+
record's name, the App's — or the shell under it changes. Without one
|
|
605
|
+
every public page was downloaded whole on every visit.
|
|
606
|
+
It is compressed as it is sent, as the shared page's copies were at
|
|
607
|
+
build time: the public page is the one a stranger's first visit and
|
|
608
|
+
every link preview fetches, and it went out at its full size while the
|
|
609
|
+
signed-in shell went as brotli. The tag is of the bytes sent, so each
|
|
610
|
+
coding has its own, as the static copies do. */
|
|
611
|
+
const isPage = asset.type === MIME['.html'];
|
|
612
|
+
if (isPage && shell?.data) {
|
|
613
|
+
const plain = asset.variants.find((v) => !v.encoding);
|
|
614
|
+
const text = withHead(withLooks(await readFile(plain.file, 'utf8'), shell, assets.keys()), shell);
|
|
615
|
+
if (res.destroyed || res.writableEnded)
|
|
616
|
+
return;
|
|
617
|
+
const accepts = acceptable(req.headers['accept-encoding']);
|
|
618
|
+
const encoding = asset.variants.some((v) => v.encoding) ? COMPRESSED.map(([, e]) => e).find(accepts) : undefined;
|
|
619
|
+
const headers = {
|
|
620
|
+
'cache-control': PERSONAL,
|
|
621
|
+
'x-content-type-options': 'nosniff',
|
|
622
|
+
'content-type': asset.type,
|
|
623
|
+
vary: asset.variants.length > 1 ? 'Accept-Encoding, Cookie' : 'Cookie',
|
|
624
|
+
...(asset.policy ? { 'content-security-policy': pagePolicy(shell.mount?.base ? shellPolicy(text) : asset.policy, shell) } : {}),
|
|
625
|
+
};
|
|
626
|
+
if (head) {
|
|
627
|
+
res.writeHead(200, headers).end();
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
await streamPage(res, text, headers, encoding, shell.data, shell);
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
if (isPage && writesHead(shell)) {
|
|
634
|
+
const plain = asset.variants.find((v) => !v.encoding);
|
|
635
|
+
const pane = head ? undefined : await drawnPane(shell, undefined);
|
|
636
|
+
const text = withPane(withHead(withLooks(await readFile(plain.file, 'utf8'), shell, assets.keys()), shell), pane);
|
|
637
|
+
const page = Buffer.from(text, 'utf8');
|
|
638
|
+
if (res.destroyed || res.writableEnded)
|
|
639
|
+
return;
|
|
640
|
+
const accepts = acceptable(req.headers['accept-encoding']);
|
|
641
|
+
const encoding = asset.variants.some((v) => v.encoding) ? COMPRESSED.map(([, e]) => e).find(accepts) : undefined;
|
|
642
|
+
const body = encoding ? compressPage(page, encoding) : page;
|
|
643
|
+
const privately = shell?.branding?.private;
|
|
644
|
+
const headers = {
|
|
645
|
+
'cache-control': privately ? PERSONAL : 'no-cache',
|
|
646
|
+
...(!privately ? { etag: strongTag(body) } : {}),
|
|
647
|
+
'x-content-type-options': 'nosniff',
|
|
648
|
+
...(asset.policy ? { 'content-security-policy': pagePolicy(shell?.mount?.base ? shellPolicy(text) : asset.policy, shell) } : {}),
|
|
649
|
+
...(asset.variants.length > 1 || privately
|
|
650
|
+
? { vary: [asset.variants.length > 1 ? 'Accept-Encoding' : '', privately ? 'Cookie' : ''].filter(Boolean).join(', ') }
|
|
651
|
+
: {}),
|
|
652
|
+
};
|
|
653
|
+
if (!privately && unchanged(req.headers['if-none-match'], headers.etag)) {
|
|
654
|
+
res.writeHead(304, headers).end();
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
res.writeHead(200, {
|
|
658
|
+
...headers,
|
|
659
|
+
'content-type': asset.type,
|
|
660
|
+
'content-length': body.length,
|
|
661
|
+
...(encoding ? { 'content-encoding': encoding } : {}),
|
|
662
|
+
});
|
|
663
|
+
res.end(head ? undefined : body);
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
const accepts = acceptable(req.headers['accept-encoding']);
|
|
667
|
+
const variant = asset.variants.find((v) => !v.encoding || accepts(v.encoding));
|
|
668
|
+
const headers = {
|
|
669
|
+
'cache-control': asset.cache,
|
|
670
|
+
etag: variant.etag,
|
|
671
|
+
'x-content-type-options': 'nosniff',
|
|
672
|
+
...(asset.policy ? { 'content-security-policy': pagePolicy(asset.policy, shell) } : {}),
|
|
673
|
+
};
|
|
674
|
+
if (asset.variants.length > 1)
|
|
675
|
+
headers.vary = 'Accept-Encoding';
|
|
676
|
+
if (unchanged(req.headers['if-none-match'], variant.etag)) {
|
|
677
|
+
if (!res.destroyed && !res.writableEnded)
|
|
678
|
+
res.writeHead(304, headers).end();
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
const bytes = head ? undefined : await readFile(variant.file);
|
|
682
|
+
if (res.destroyed || res.writableEnded)
|
|
683
|
+
return;
|
|
684
|
+
res.writeHead(200, {
|
|
685
|
+
...headers,
|
|
686
|
+
'content-type': asset.type,
|
|
687
|
+
'content-length': variant.size,
|
|
688
|
+
...(variant.encoding ? { 'content-encoding': variant.encoding } : {}),
|
|
689
|
+
});
|
|
690
|
+
res.end(bytes);
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
/* One server per built page, made on its first request: a process serves one page, but a test may serve its own. */
|
|
694
|
+
const built = new Map();
|
|
695
|
+
const fromBuild = (dir) => {
|
|
696
|
+
let serve = built.get(dir);
|
|
697
|
+
if (!serve)
|
|
698
|
+
built.set(dir, (serve = builtClient(dir)));
|
|
699
|
+
return serve;
|
|
700
|
+
};
|