@hot-updater/server 1.0.0-rc.14 → 1.0.0-rc.16
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/dist/adapters/drizzle.cjs +35 -74
- package/dist/adapters/drizzle.d.cts +13 -5
- package/dist/adapters/drizzle.d.mts +13 -5
- package/dist/adapters/drizzle.mjs +34 -75
- package/dist/adapters/drizzleExecutor.cjs +114 -0
- package/dist/adapters/drizzleExecutor.d.cts +9 -0
- package/dist/adapters/drizzleExecutor.d.mts +9 -0
- package/dist/adapters/drizzleExecutor.mjs +112 -0
- package/dist/adapters/kysely.cjs +25 -31
- package/dist/adapters/kysely.d.cts +10 -6
- package/dist/adapters/kysely.d.mts +10 -6
- package/dist/adapters/kysely.mjs +25 -32
- package/dist/adapters/kyselyExecutor.cjs +32 -0
- package/dist/adapters/kyselyExecutor.d.cts +12 -0
- package/dist/adapters/kyselyExecutor.d.mts +12 -0
- package/dist/adapters/kyselyExecutor.mjs +32 -0
- package/dist/adapters/mongodb.cjs +28 -62
- package/dist/adapters/mongodb.d.cts +10 -5
- package/dist/adapters/mongodb.d.mts +10 -5
- package/dist/adapters/mongodb.mjs +28 -63
- package/dist/adapters/mongodbAdapter.cjs +174 -0
- package/dist/adapters/mongodbAdapter.d.cts +9 -0
- package/dist/adapters/mongodbAdapter.d.mts +9 -0
- package/dist/adapters/mongodbAdapter.mjs +173 -0
- package/dist/adapters/prisma.cjs +38 -257
- package/dist/adapters/prisma.d.cts +12 -7
- package/dist/adapters/prisma.d.mts +12 -7
- package/dist/adapters/prisma.mjs +38 -257
- package/dist/adapters/prismaExecutor.cjs +64 -0
- package/dist/adapters/prismaExecutor.mjs +64 -0
- package/dist/adapters/sqlProviders.cjs +9 -0
- package/dist/adapters/sqlProviders.mjs +9 -0
- package/dist/apiKeys.cjs +2 -6
- package/dist/apiKeys.d.cts +1 -23
- package/dist/apiKeys.d.mts +1 -23
- package/dist/apiKeys.mjs +2 -6
- package/dist/assembly/assemblePlugins.cjs +147 -0
- package/dist/assembly/assemblePlugins.d.cts +12 -0
- package/dist/assembly/assemblePlugins.d.mts +12 -0
- package/dist/assembly/assemblePlugins.mjs +146 -0
- package/dist/assembly/databasePlugins.cjs +23 -0
- package/dist/assembly/databasePlugins.d.cts +43 -0
- package/dist/assembly/databasePlugins.d.mts +43 -0
- package/dist/assembly/databasePlugins.mjs +22 -0
- package/dist/core/api.cjs +91 -0
- package/dist/core/api.d.cts +65 -0
- package/dist/core/api.d.mts +65 -0
- package/dist/core/api.mjs +87 -0
- package/dist/core/baseCandidates.cjs +71 -0
- package/dist/core/baseCandidates.d.cts +17 -0
- package/dist/core/baseCandidates.d.mts +17 -0
- package/dist/core/baseCandidates.mjs +69 -0
- package/dist/core/operations.cjs +350 -0
- package/dist/core/operations.mjs +350 -0
- package/dist/core/reads.cjs +273 -0
- package/dist/core/reads.d.cts +48 -0
- package/dist/core/reads.d.mts +48 -0
- package/dist/core/reads.mjs +267 -0
- package/dist/core/releases.cjs +193 -0
- package/dist/core/releases.mjs +190 -0
- package/dist/core/schema.cjs +279 -0
- package/dist/core/schema.d.cts +307 -0
- package/dist/core/schema.d.mts +307 -0
- package/dist/core/schema.mjs +277 -0
- package/dist/core/writes.cjs +96 -0
- package/dist/core/writes.mjs +91 -0
- package/dist/createHotUpdaterCore.cjs +58 -75
- package/dist/createHotUpdaterCore.d.cts +52 -44
- package/dist/createHotUpdaterCore.d.mts +52 -44
- package/dist/createHotUpdaterCore.mjs +59 -75
- package/dist/database/cursor.cjs +44 -0
- package/dist/database/cursor.d.cts +7 -0
- package/dist/database/cursor.d.mts +7 -0
- package/dist/database/cursor.mjs +40 -0
- package/dist/database/database.cjs +44 -0
- package/dist/database/database.d.cts +123 -0
- package/dist/database/database.d.mts +123 -0
- package/dist/database/database.mjs +44 -0
- package/dist/database/definitions.d.cts +68 -0
- package/dist/database/definitions.d.mts +68 -0
- package/dist/database/engine.cjs +37 -0
- package/dist/database/engine.d.cts +40 -0
- package/dist/database/engine.d.mts +40 -0
- package/dist/database/engine.mjs +37 -0
- package/dist/database/engineAggregates.cjs +115 -0
- package/dist/database/engineAggregates.mjs +112 -0
- package/dist/database/engineReads.cjs +180 -0
- package/dist/database/engineReads.d.cts +35 -0
- package/dist/database/engineReads.d.mts +35 -0
- package/dist/database/engineReads.mjs +179 -0
- package/dist/database/engineTransaction.cjs +403 -0
- package/dist/database/engineTransaction.d.cts +26 -0
- package/dist/database/engineTransaction.d.mts +26 -0
- package/dist/database/engineTransaction.mjs +402 -0
- package/dist/database/errors.cjs +27 -0
- package/dist/database/errors.d.cts +21 -0
- package/dist/database/errors.d.mts +21 -0
- package/dist/database/errors.mjs +24 -0
- package/dist/database/fence.cjs +124 -0
- package/dist/database/fence.d.cts +42 -0
- package/dist/database/fence.d.mts +42 -0
- package/dist/database/fence.mjs +116 -0
- package/dist/database/index.cjs +127 -0
- package/dist/database/index.d.cts +17 -0
- package/dist/database/index.d.mts +17 -0
- package/dist/database/index.mjs +15 -0
- package/dist/database/kv/kvAdapter.cjs +273 -0
- package/dist/database/kv/kvAdapter.d.cts +101 -0
- package/dist/database/kv/kvAdapter.d.mts +101 -0
- package/dist/database/kv/kvAdapter.mjs +272 -0
- package/dist/database/resolveSchema.cjs +196 -0
- package/dist/database/resolveSchema.d.cts +42 -0
- package/dist/database/resolveSchema.d.mts +42 -0
- package/dist/database/resolveSchema.mjs +193 -0
- package/dist/database/schema.cjs +21 -0
- package/dist/database/schema.d.cts +61 -0
- package/dist/database/schema.d.mts +61 -0
- package/dist/database/schema.mjs +20 -0
- package/dist/database/sql/sqlAdapter.cjs +282 -0
- package/dist/database/sql/sqlAdapter.d.cts +46 -0
- package/dist/database/sql/sqlAdapter.d.mts +46 -0
- package/dist/database/sql/sqlAdapter.mjs +279 -0
- package/dist/database/sql/sqlSchema.cjs +115 -0
- package/dist/database/sql/sqlSchema.d.cts +8 -0
- package/dist/database/sql/sqlSchema.d.mts +8 -0
- package/dist/database/sql/sqlSchema.mjs +111 -0
- package/dist/db/builtInDatabase.cjs +104 -0
- package/dist/db/builtInDatabase.d.cts +38 -0
- package/dist/db/builtInDatabase.d.mts +38 -0
- package/dist/db/builtInDatabase.mjs +97 -0
- package/dist/db/createBundleDiff.cjs +4 -4
- package/dist/db/createBundleDiff.d.cts +3 -2
- package/dist/db/createBundleDiff.d.mts +3 -2
- package/dist/db/createBundleDiff.mjs +5 -5
- package/dist/db/engineDrizzleSchema.cjs +54 -0
- package/dist/db/engineDrizzleSchema.mjs +54 -0
- package/dist/db/engineMigrator.cjs +53 -0
- package/dist/db/engineMigrator.mjs +53 -0
- package/dist/db/enginePrismaSchema.cjs +85 -0
- package/dist/db/enginePrismaSchema.d.cts +5 -0
- package/dist/db/enginePrismaSchema.d.mts +5 -0
- package/dist/db/enginePrismaSchema.mjs +84 -0
- package/dist/db/engineSql.cjs +26 -0
- package/dist/db/engineSql.d.cts +17 -0
- package/dist/db/engineSql.d.mts +17 -0
- package/dist/db/engineSql.mjs +25 -0
- package/dist/db/engineSqlMigrator.cjs +66 -0
- package/dist/db/engineSqlMigrator.mjs +66 -0
- package/dist/db/fixedMigratorShared.cjs +0 -11
- package/dist/db/fixedMigratorShared.mjs +1 -8
- package/dist/db/index.cjs +23 -8
- package/dist/db/index.d.cts +10 -3
- package/dist/db/index.d.mts +10 -3
- package/dist/db/index.mjs +16 -8
- package/dist/db/releaseCatalog.cjs +20 -43
- package/dist/db/releaseCatalog.d.cts +2 -2
- package/dist/db/releaseCatalog.d.mts +2 -2
- package/dist/db/releaseCatalog.mjs +20 -44
- package/dist/db/schemaSettings.cjs +63 -0
- package/dist/db/schemaSettings.d.cts +12 -0
- package/dist/db/schemaSettings.d.mts +12 -0
- package/dist/db/schemaSettings.mjs +62 -0
- package/dist/db/settingsMigrator.cjs +92 -0
- package/dist/db/settingsMigrator.mjs +89 -0
- package/dist/db/types.cjs +1 -12
- package/dist/db/types.d.cts +26 -66
- package/dist/db/types.d.mts +26 -66
- package/dist/db/types.mjs +2 -11
- package/dist/db/updateArtifacts.cjs +38 -67
- package/dist/db/updateArtifacts.mjs +40 -69
- package/dist/handler.cjs +72 -43
- package/dist/handler.d.cts +3 -5
- package/dist/handler.d.mts +3 -5
- package/dist/handler.mjs +73 -43
- package/dist/handlerAdminRoutes.cjs +375 -0
- package/dist/handlerAdminRoutes.mjs +373 -0
- package/dist/handlerParameters.cjs +6 -11
- package/dist/handlerParameters.mjs +6 -10
- package/dist/handlerReleaseCatalogRoutes.cjs +11 -13
- package/dist/handlerReleaseCatalogRoutes.mjs +11 -13
- package/dist/handlerTypes.d.cts +2 -55
- package/dist/handlerTypes.d.mts +2 -55
- package/dist/handlerVersionRoutes.cjs +20 -4
- package/dist/handlerVersionRoutes.mjs +20 -4
- package/dist/index.cjs +4 -7
- package/dist/index.d.cts +8 -7
- package/dist/index.d.mts +8 -7
- package/dist/index.mjs +4 -4
- package/dist/insights/domain.d.cts +12 -0
- package/dist/insights/domain.d.mts +12 -0
- package/dist/insights/errors.cjs +34 -0
- package/dist/insights/errors.mjs +34 -1
- package/dist/insights/eventInput.cjs +14 -19
- package/dist/insights/eventInput.mjs +15 -20
- package/dist/insights/provider.cjs +47 -23
- package/dist/insights/provider.mjs +47 -24
- package/dist/insights/routes.cjs +79 -14
- package/dist/insights/routes.mjs +77 -14
- package/dist/node.d.cts +1 -1
- package/dist/node.d.mts +1 -1
- package/dist/package.cjs +1 -1
- package/dist/package.mjs +1 -1
- package/dist/plugins/api-keys/index.cjs +50 -0
- package/dist/plugins/api-keys/index.d.cts +85 -0
- package/dist/plugins/api-keys/index.d.mts +85 -0
- package/dist/plugins/api-keys/index.mjs +47 -0
- package/dist/plugins/api-keys/model.cjs +58 -0
- package/dist/plugins/api-keys/model.d.cts +8 -0
- package/dist/plugins/api-keys/model.d.mts +8 -0
- package/dist/plugins/api-keys/model.mjs +58 -0
- package/dist/plugins/api-keys/schema.cjs +38 -0
- package/dist/plugins/api-keys/schema.d.cts +44 -0
- package/dist/plugins/api-keys/schema.d.mts +44 -0
- package/dist/plugins/api-keys/schema.mjs +39 -0
- package/dist/plugins/builtIn.cjs +7 -0
- package/dist/plugins/builtIn.mjs +6 -0
- package/dist/plugins/definePlugin.cjs +5 -0
- package/dist/plugins/definePlugin.d.cts +73 -0
- package/dist/plugins/definePlugin.d.mts +73 -0
- package/dist/plugins/definePlugin.mjs +5 -0
- package/dist/plugins/index.cjs +17 -0
- package/dist/plugins/index.d.cts +9 -0
- package/dist/plugins/index.d.mts +9 -0
- package/dist/plugins/index.mjs +7 -0
- package/dist/plugins/insights/index.cjs +48 -0
- package/dist/plugins/insights/index.d.cts +295 -0
- package/dist/plugins/insights/index.d.mts +295 -0
- package/dist/plugins/insights/index.mjs +44 -0
- package/dist/plugins/insights/model.cjs +17 -0
- package/dist/plugins/insights/model.d.cts +10 -0
- package/dist/plugins/insights/model.d.mts +10 -0
- package/dist/plugins/insights/model.mjs +17 -0
- package/dist/plugins/insights/reads.cjs +471 -0
- package/dist/plugins/insights/reads.mjs +466 -0
- package/dist/plugins/insights/recordEvent.cjs +135 -0
- package/dist/plugins/insights/recordEvent.d.cts +13 -0
- package/dist/plugins/insights/recordEvent.d.mts +13 -0
- package/dist/plugins/insights/recordEvent.mjs +132 -0
- package/dist/plugins/insights/schema.cjs +347 -0
- package/dist/plugins/insights/schema.d.cts +258 -0
- package/dist/plugins/insights/schema.d.mts +258 -0
- package/dist/plugins/insights/schema.mjs +344 -0
- package/dist/types/index.d.cts +2 -28
- package/dist/types/index.d.mts +2 -28
- package/package.json +28 -5
- package/src/adapters/drizzle.conformance.integration.spec.ts +34 -0
- package/src/adapters/drizzle.readBudgets.integration.spec.ts +41 -0
- package/src/adapters/drizzle.spec.ts +270 -399
- package/src/adapters/drizzle.ts +48 -140
- package/src/adapters/drizzleExecutor.ts +166 -0
- package/src/adapters/kysely.conformance.integration.spec.ts +36 -0
- package/src/adapters/kysely.readBudgets.integration.spec.ts +42 -0
- package/src/adapters/kysely.spec.ts +204 -171
- package/src/adapters/kysely.ts +32 -93
- package/src/adapters/kyselyExecutor.ts +53 -0
- package/src/adapters/mongodb.integration.spec.ts +143 -446
- package/src/adapters/mongodb.readBudgets.integration.spec.ts +61 -0
- package/src/adapters/mongodb.ts +36 -123
- package/src/adapters/mongodbAdapter.ts +293 -0
- package/src/adapters/prisma.conformance.integration.spec.ts +36 -0
- package/src/adapters/prisma.integration.spec.ts +138 -0
- package/src/adapters/prisma.readBudgets.integration.spec.ts +40 -0
- package/src/adapters/prisma.spec.ts +216 -433
- package/src/adapters/prisma.ts +60 -524
- package/src/adapters/prismaExecutor.ts +107 -0
- package/src/adapters/prismaTestClients.ts +175 -0
- package/src/adapters/sqlProviders.ts +14 -0
- package/src/apiKeys.integration.spec.ts +19 -119
- package/src/apiKeys.spec.ts +39 -35
- package/src/apiKeys.ts +2 -8
- package/src/assembly/assemblePlugins.ts +269 -0
- package/src/assembly/assembly.spec.ts +325 -0
- package/src/assembly/assembly.types.spec.ts +155 -0
- package/src/assembly/builtInRoutes.spec.ts +213 -0
- package/src/assembly/databasePlugins.spec.ts +71 -0
- package/src/assembly/databasePlugins.ts +96 -0
- package/src/core/api.ts +139 -0
- package/src/core/baseCandidates.spec.ts +378 -0
- package/src/core/baseCandidates.ts +136 -0
- package/src/core/index.ts +44 -0
- package/src/core/operations.spec.ts +415 -0
- package/src/core/operations.ts +551 -0
- package/src/core/reads.spec.ts +300 -0
- package/src/core/reads.ts +417 -0
- package/src/core/releases.spec.ts +308 -0
- package/src/core/releases.ts +345 -0
- package/src/core/schema.ts +173 -0
- package/src/core/writes.ts +160 -0
- package/src/createHotUpdaterCore.ts +191 -181
- package/src/database/cursor.ts +65 -0
- package/src/database/database.ts +335 -0
- package/src/database/database.types.spec.ts +135 -0
- package/src/database/definitions.ts +72 -0
- package/src/database/engine.ts +62 -0
- package/src/database/engineAggregates.spec.ts +539 -0
- package/src/database/engineAggregates.ts +193 -0
- package/src/database/engineReads.spec.ts +219 -0
- package/src/database/engineReads.ts +302 -0
- package/src/database/engineTransaction.spec.ts +710 -0
- package/src/database/engineTransaction.ts +588 -0
- package/src/database/errors.ts +30 -0
- package/src/database/fence.spec.ts +223 -0
- package/src/database/fence.ts +171 -0
- package/src/database/index.ts +154 -0
- package/src/database/kv/kvAdapter.conformance.integration.spec.ts +17 -0
- package/src/database/kv/kvAdapter.readBudgets.integration.spec.ts +17 -0
- package/src/database/kv/kvAdapter.spec.ts +468 -0
- package/src/database/kv/kvAdapter.ts +450 -0
- package/src/database/kv/kvTestStore.ts +91 -0
- package/src/database/memory.readBudgets.integration.spec.ts +13 -0
- package/src/database/resolveSchema.spec.ts +230 -0
- package/src/database/resolveSchema.ts +372 -0
- package/src/database/schema.ts +169 -0
- package/src/database/schema.types.spec.ts +94 -0
- package/src/database/sql/docker-compose.yml +36 -0
- package/src/database/sql/sqlAdapter.integration.spec.ts +148 -0
- package/src/database/sql/sqlAdapter.readBudgets.integration.spec.ts +109 -0
- package/src/database/sql/sqlAdapter.spec.ts +255 -0
- package/src/database/sql/sqlAdapter.ts +532 -0
- package/src/database/sql/sqlSchema.ts +205 -0
- package/src/database/sql/sqlTestExecutors.ts +183 -0
- package/src/databaseTestSuite.spec.ts +39 -0
- package/src/db/builtInDatabase.ts +149 -0
- package/src/db/bundleRows.spec.ts +3 -3
- package/src/db/createBundleDiff.spec.ts +43 -29
- package/src/db/createBundleDiff.ts +13 -7
- package/src/db/engineDrizzleSchema.ts +72 -0
- package/src/db/engineMigrator.ts +105 -0
- package/src/db/enginePrismaSchema.ts +147 -0
- package/src/db/engineSql.spec.ts +129 -0
- package/src/db/engineSql.ts +48 -0
- package/src/db/engineSqlMigrator.ts +116 -0
- package/src/db/fixedMigratorShared.ts +1 -20
- package/src/db/index.spec.ts +116 -829
- package/src/db/index.ts +43 -14
- package/src/db/pluginTables.spec.ts +218 -0
- package/src/db/releaseCatalog.ts +48 -90
- package/src/db/schemaSettings.ts +104 -0
- package/src/db/settingsMigrator.spec.ts +65 -0
- package/src/db/settingsMigrator.ts +152 -0
- package/src/db/types.ts +25 -171
- package/src/db/updateArtifacts.spec.ts +170 -294
- package/src/db/updateArtifacts.ts +74 -142
- package/src/db/updateCheck.bench.ts +47 -0
- package/src/handler-standalone.integration.spec.ts +204 -410
- package/src/handler.spec.ts +134 -51
- package/src/handler.testFixtures.ts +14 -19
- package/src/handler.ts +163 -75
- package/src/handlerAdminRoutes.spec.ts +183 -0
- package/src/handlerAdminRoutes.ts +536 -0
- package/src/handlerAuth.integration.spec.ts +3 -3
- package/src/handlerManagement.spec.ts +136 -196
- package/src/handlerReleaseCatalogRoutes.ts +15 -17
- package/src/handlerTypes.ts +3 -80
- package/src/handlerVersionRoutes.ts +20 -7
- package/src/index.ts +5 -12
- package/src/insights/domain.ts +12 -0
- package/src/insights/errors.spec.ts +48 -0
- package/src/insights/errors.ts +51 -0
- package/src/insights/eventInput.ts +27 -24
- package/src/insights/insights.integration.spec.ts +228 -78
- package/src/insights/provider.spec.ts +203 -25
- package/src/insights/provider.ts +88 -32
- package/src/insights/routes.ts +95 -20
- package/src/lintRules.spec.ts +76 -0
- package/src/plugins/api-keys/apiKeysPlugin.spec.ts +172 -0
- package/src/plugins/api-keys/index.ts +66 -0
- package/src/plugins/api-keys/model.ts +69 -0
- package/src/plugins/api-keys/schema.ts +22 -0
- package/src/plugins/builtIn.ts +5 -0
- package/src/plugins/definePlugin.ts +100 -0
- package/src/plugins/index.ts +55 -0
- package/src/plugins/insights/index.ts +86 -0
- package/src/plugins/insights/insightsRollout.integration.spec.ts +187 -0
- package/src/plugins/insights/model.spec.ts +66 -0
- package/src/plugins/insights/model.ts +19 -0
- package/src/plugins/insights/reads.spec.ts +725 -0
- package/src/plugins/insights/reads.ts +707 -0
- package/src/plugins/insights/recordEvent.spec.ts +443 -0
- package/src/plugins/insights/recordEvent.ts +238 -0
- package/src/plugins/insights/schema.ts +229 -0
- package/src/plugins/pluginTestHarness.spec.ts +56 -0
- package/src/readBudgets.testFixtures.ts +15 -0
- package/src/releaseCatalogRoutes.integration.spec.ts +47 -92
- package/src/runtime.spec.ts +91 -77
- package/src/runtime.testFixtures.ts +28 -44
- package/src/runtimeBundles.integration.spec.ts +112 -0
- package/src/testUtilsMutation.integration.spec.ts +215 -0
- package/src/testUtilsPackage.integration.spec.ts +176 -0
- package/src/types/index.ts +0 -32
- package/dist/adapters/databaseConstraintErrors.cjs +0 -22
- package/dist/adapters/databaseConstraintErrors.mjs +0 -21
- package/dist/adapters/databasePluginUtils.cjs +0 -130
- package/dist/adapters/databasePluginUtils.mjs +0 -118
- package/dist/adapters/drizzleCrud.cjs +0 -340
- package/dist/adapters/drizzleCrud.mjs +0 -337
- package/dist/adapters/drizzleLazyDB.cjs +0 -103
- package/dist/adapters/drizzleLazyDB.mjs +0 -103
- package/dist/adapters/drizzleQuery.cjs +0 -69
- package/dist/adapters/drizzleQuery.mjs +0 -69
- package/dist/adapters/kyselyCrud.cjs +0 -293
- package/dist/adapters/kyselyCrud.mjs +0 -292
- package/dist/adapters/kyselyQuery.cjs +0 -56
- package/dist/adapters/kyselyQuery.mjs +0 -56
- package/dist/adapters/mongodbCollections.cjs +0 -38
- package/dist/adapters/mongodbCollections.mjs +0 -32
- package/dist/adapters/mongodbQuery.cjs +0 -90
- package/dist/adapters/mongodbQuery.mjs +0 -83
- package/dist/adapters/mongodbReads.cjs +0 -206
- package/dist/adapters/mongodbReads.mjs +0 -206
- package/dist/adapters/mongodbWrites.cjs +0 -133
- package/dist/adapters/mongodbWrites.mjs +0 -133
- package/dist/adapters/prismaInsights.cjs +0 -99
- package/dist/adapters/prismaInsights.mjs +0 -97
- package/dist/adapters/prismaQuery.cjs +0 -57
- package/dist/adapters/prismaQuery.mjs +0 -56
- package/dist/adapters/prismaRows.cjs +0 -197
- package/dist/adapters/prismaRows.mjs +0 -188
- package/dist/db/databasePluginCore.cjs +0 -137
- package/dist/db/databasePluginCore.mjs +0 -137
- package/dist/db/fixedMigrator.cjs +0 -79
- package/dist/db/fixedMigrator.mjs +0 -78
- package/dist/db/fixedMigratorMongo.cjs +0 -321
- package/dist/db/fixedMigratorMongo.mjs +0 -321
- package/dist/db/mongoMigrationExecution.cjs +0 -11
- package/dist/db/mongoMigrationExecution.mjs +0 -11
- package/dist/db/schema/insightsCollation.cjs +0 -14
- package/dist/db/schema/insightsCollation.mjs +0 -14
- package/dist/db/schema/mongodb.cjs +0 -25
- package/dist/db/schema/mongodb.mjs +0 -25
- package/dist/db/schema/registry.cjs +0 -21
- package/dist/db/schema/registry.mjs +0 -17
- package/dist/db/schema/sql.cjs +0 -105
- package/dist/db/schema/sql.mjs +0 -97
- package/dist/db/schema/sqlOperations.cjs +0 -27
- package/dist/db/schema/sqlOperations.mjs +0 -26
- package/dist/db/schemaGenerators.cjs +0 -276
- package/dist/db/schemaGenerators.mjs +0 -274
- package/dist/db/schemaReadiness.cjs +0 -26
- package/dist/db/schemaReadiness.d.cts +0 -9
- package/dist/db/schemaReadiness.d.mts +0 -9
- package/dist/db/schemaReadiness.mjs +0 -25
- package/dist/db/sqlMigrationExecution.cjs +0 -67
- package/dist/db/sqlMigrationExecution.mjs +0 -67
- package/dist/handlerBundleRoutes.cjs +0 -155
- package/dist/handlerBundleRoutes.mjs +0 -155
- package/dist/handlerReleaseManagementRoutes.cjs +0 -124
- package/dist/handlerReleaseManagementRoutes.mjs +0 -124
- package/dist/schema/dsl.cjs +0 -103
- package/dist/schema/dsl.mjs +0 -87
- package/dist/schema/index.cjs +0 -20
- package/dist/schema/index.mjs +0 -7
- package/dist/schema/settings.cjs +0 -9
- package/dist/schema/settings.mjs +0 -9
- package/dist/schema/types.cjs +0 -8
- package/dist/schema/types.mjs +0 -6
- package/dist/schema/v1_0_0.cjs +0 -393
- package/dist/schema/v1_0_0.mjs +0 -385
- package/src/adapters/databaseConstraintErrors.ts +0 -25
- package/src/adapters/databasePluginTestDatabase.ts +0 -116
- package/src/adapters/databasePluginUtils.spec.ts +0 -29
- package/src/adapters/databasePluginUtils.ts +0 -238
- package/src/adapters/drizzleCrud.ts +0 -610
- package/src/adapters/drizzleLazyDB.ts +0 -284
- package/src/adapters/drizzleQuery.ts +0 -164
- package/src/adapters/kyselyCrud.ts +0 -696
- package/src/adapters/kyselyInsights.spec.ts +0 -168
- package/src/adapters/kyselyQuery.ts +0 -128
- package/src/adapters/mongodb.spec.ts +0 -230
- package/src/adapters/mongodbCollections.ts +0 -78
- package/src/adapters/mongodbQuery.ts +0 -245
- package/src/adapters/mongodbReads.ts +0 -387
- package/src/adapters/mongodbRows.spec.ts +0 -64
- package/src/adapters/mongodbRows.ts +0 -118
- package/src/adapters/mongodbTestClient.ts +0 -443
- package/src/adapters/mongodbTestFilter.ts +0 -170
- package/src/adapters/mongodbWrites.ts +0 -261
- package/src/adapters/prismaInsights.ts +0 -190
- package/src/adapters/prismaQuery.ts +0 -129
- package/src/adapters/prismaRows.spec.ts +0 -98
- package/src/adapters/prismaRows.ts +0 -308
- package/src/adapters/prismaTestClient.ts +0 -478
- package/src/calculatePagination.ts +0 -34
- package/src/db/databasePluginCore.spec.ts +0 -62
- package/src/db/databasePluginCore.testFixtures.ts +0 -76
- package/src/db/databasePluginCore.ts +0 -185
- package/src/db/databasePluginUpdateCheck.bench.ts +0 -117
- package/src/db/fixedMigrator.spec.ts +0 -294
- package/src/db/fixedMigrator.ts +0 -147
- package/src/db/fixedMigratorMongo.spec.ts +0 -253
- package/src/db/fixedMigratorMongo.ts +0 -425
- package/src/db/hotUpdaterSchema.ts +0 -5
- package/src/db/mongoMigrationExecution.ts +0 -23
- package/src/db/schema/definitions.ts +0 -1
- package/src/db/schema/insightsCollation.ts +0 -21
- package/src/db/schema/mongodb.ts +0 -40
- package/src/db/schema/registry.ts +0 -55
- package/src/db/schema/sql.ts +0 -244
- package/src/db/schema/sqlOperations.ts +0 -63
- package/src/db/schema/types.ts +0 -1
- package/src/db/schemaGenerators.ts +0 -495
- package/src/db/schemaReadiness.ts +0 -34
- package/src/db/sqlMigrationExecution.spec.ts +0 -65
- package/src/db/sqlMigrationExecution.ts +0 -108
- package/src/db/types.spec.ts +0 -63
- package/src/handlerBundleRoutes.ts +0 -259
- package/src/handlerReleaseManagementRoutes.ts +0 -213
- package/src/schema/dsl-all-versions.spec.ts +0 -35
- package/src/schema/dsl.ts +0 -187
- package/src/schema/index.ts +0 -10
- package/src/schema/settings.ts +0 -15
- package/src/schema/types.ts +0 -79
- package/src/schema/v1_0_0.spec.ts +0 -220
- package/src/schema/v1_0_0.ts +0 -511
|
@@ -7,6 +7,16 @@ import { createInsightsProvider } from "./provider";
|
|
|
7
7
|
const eventId = (index: number) =>
|
|
8
8
|
`00000000-0000-7000-8000-${String(index).padStart(12, "0")}`;
|
|
9
9
|
|
|
10
|
+
const HOUR = 3_600_000;
|
|
11
|
+
const DAY = 86_400_000;
|
|
12
|
+
const cutoff = Date.UTC(2026, 8, 20, 9, 30);
|
|
13
|
+
const bundle = {
|
|
14
|
+
platform: "ios",
|
|
15
|
+
channel: "production",
|
|
16
|
+
bundleId: "bundle-after",
|
|
17
|
+
outcome: "applied",
|
|
18
|
+
} as const;
|
|
19
|
+
|
|
10
20
|
type TransitionEventRow = Extract<
|
|
11
21
|
BundleEventRow,
|
|
12
22
|
{ readonly type: "UPDATE_APPLIED" | "RECOVERED" }
|
|
@@ -67,6 +77,23 @@ const createModel = () => {
|
|
|
67
77
|
findLatestEvents: vi.fn<InsightsModel["findLatestEvents"]>(async () => []),
|
|
68
78
|
countLatestEvents: vi.fn<InsightsModel["countLatestEvents"]>(async () => 0),
|
|
69
79
|
countEvents: vi.fn<InsightsModel["countEvents"]>(async () => 0),
|
|
80
|
+
getReleaseActivity: vi.fn<InsightsModel["getReleaseActivity"]>(
|
|
81
|
+
async () => ({
|
|
82
|
+
coverage: { kind: "complete", sinceMs: 0 },
|
|
83
|
+
data: [],
|
|
84
|
+
measuredAtMs: 0,
|
|
85
|
+
}),
|
|
86
|
+
),
|
|
87
|
+
getAppUsage: vi.fn<InsightsModel["getAppUsage"]>(async () => ({
|
|
88
|
+
coverage: { kind: "complete", sinceMs: 0 },
|
|
89
|
+
activeInstallations: 0,
|
|
90
|
+
points: [],
|
|
91
|
+
appVersions: [],
|
|
92
|
+
versions: [],
|
|
93
|
+
platforms: [],
|
|
94
|
+
bundleDistribution: [],
|
|
95
|
+
measuredAtMs: 0,
|
|
96
|
+
})),
|
|
70
97
|
} satisfies InsightsModel;
|
|
71
98
|
return { ...model, model };
|
|
72
99
|
};
|
|
@@ -84,7 +111,6 @@ describe("createInsightsProvider", () => {
|
|
|
84
111
|
.mockResolvedValueOnce([
|
|
85
112
|
eventRow(eventId(3), 999),
|
|
86
113
|
eventRow(eventId(2), 900),
|
|
87
|
-
eventRow(eventId(1), 800),
|
|
88
114
|
])
|
|
89
115
|
.mockResolvedValueOnce([eventRow(eventId(1), 800)]);
|
|
90
116
|
const provider = createInsightsProvider(fixture.model);
|
|
@@ -96,7 +122,7 @@ describe("createInsightsProvider", () => {
|
|
|
96
122
|
expect(first.nextCursor).not.toBeNull();
|
|
97
123
|
expect(fixture.listEvents).toHaveBeenNthCalledWith(1, {
|
|
98
124
|
beforeReceivedAtMs: 1_000,
|
|
99
|
-
limit:
|
|
125
|
+
limit: 2,
|
|
100
126
|
sinceMs: 0,
|
|
101
127
|
filter: { kind: "all" },
|
|
102
128
|
});
|
|
@@ -111,20 +137,160 @@ describe("createInsightsProvider", () => {
|
|
|
111
137
|
expect(fixture.listEvents).toHaveBeenNthCalledWith(2, {
|
|
112
138
|
after: { id: eventId(2), receivedAtMs: 900 },
|
|
113
139
|
beforeReceivedAtMs: 1_000,
|
|
114
|
-
limit:
|
|
140
|
+
limit: 2,
|
|
115
141
|
sinceMs: 0,
|
|
116
142
|
filter: { kind: "all" },
|
|
117
143
|
});
|
|
118
144
|
});
|
|
119
145
|
|
|
120
|
-
it("
|
|
146
|
+
it("reads only `limit` rows, so a full last page is followed by an empty one", async () => {
|
|
121
147
|
vi.useFakeTimers();
|
|
122
148
|
vi.setSystemTime(1_000);
|
|
123
149
|
const fixture = createModel();
|
|
124
|
-
fixture.listEvents
|
|
125
|
-
|
|
126
|
-
|
|
150
|
+
fixture.listEvents
|
|
151
|
+
.mockResolvedValueOnce([
|
|
152
|
+
eventRow(eventId(2), 900),
|
|
153
|
+
eventRow(eventId(1), 800),
|
|
154
|
+
])
|
|
155
|
+
.mockResolvedValueOnce([]);
|
|
156
|
+
const provider = createInsightsProvider(fixture.model);
|
|
157
|
+
|
|
158
|
+
const first = await provider.listEvents({ limit: 2 });
|
|
159
|
+
const second = await provider.listEvents({
|
|
160
|
+
cursor: first.nextCursor ?? undefined,
|
|
161
|
+
limit: 2,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
expect(first.nextCursor).not.toBeNull();
|
|
165
|
+
expect(second).toMatchObject({ data: [], nextCursor: null });
|
|
166
|
+
expect(fixture.listEvents.mock.calls.map(([input]) => input.limit)).toEqual(
|
|
167
|
+
[2, 2],
|
|
168
|
+
);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("lists the 90 days before the cutoff without a start, and all of an installation's history", async () => {
|
|
172
|
+
vi.useFakeTimers();
|
|
173
|
+
vi.setSystemTime(cutoff);
|
|
174
|
+
const fixture = createModel();
|
|
175
|
+
const provider = createInsightsProvider(fixture.model);
|
|
176
|
+
|
|
177
|
+
await provider.listEvents({});
|
|
178
|
+
await provider.listEvents({ beforeReceivedAtMs: cutoff - DAY, bundle });
|
|
179
|
+
await provider.listInstallationEvents({ installId: "install-1" });
|
|
180
|
+
|
|
181
|
+
expect(
|
|
182
|
+
fixture.listEvents.mock.calls.map(([input]) => [
|
|
183
|
+
input.sinceMs,
|
|
184
|
+
input.beforeReceivedAtMs,
|
|
185
|
+
]),
|
|
186
|
+
).toEqual([
|
|
187
|
+
[cutoff - 90 * DAY, cutoff],
|
|
188
|
+
[cutoff - 91 * DAY, cutoff - DAY],
|
|
189
|
+
[0, cutoff],
|
|
127
190
|
]);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("answers 400 for a global or bundle range over 90 days, before the database", async () => {
|
|
194
|
+
const fixture = createModel();
|
|
195
|
+
const provider = createInsightsProvider(fixture.model);
|
|
196
|
+
const range = { beforeReceivedAtMs: cutoff, sinceMs: cutoff - 90 * DAY };
|
|
197
|
+
|
|
198
|
+
for (const input of [{}, { bundle }]) {
|
|
199
|
+
await expect(
|
|
200
|
+
provider.listEvents({ ...input, ...range, sinceMs: range.sinceMs - 1 }),
|
|
201
|
+
).rejects.toThrow(
|
|
202
|
+
new InsightsBadRequestError(
|
|
203
|
+
"Insights event lists cover at most 90 days: send a sinceMs no more than 90 days before beforeReceivedAtMs.",
|
|
204
|
+
),
|
|
205
|
+
);
|
|
206
|
+
await provider.listEvents({ ...input, ...range });
|
|
207
|
+
}
|
|
208
|
+
await provider.listInstallationEvents({
|
|
209
|
+
installId: "install-1",
|
|
210
|
+
beforeReceivedAtMs: cutoff,
|
|
211
|
+
sinceMs: 0,
|
|
212
|
+
});
|
|
213
|
+
expect(
|
|
214
|
+
fixture.listEvents.mock.calls.map(([input]) => input.sinceMs),
|
|
215
|
+
).toEqual([range.sinceMs, range.sinceMs, 0]);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("returns a cursor only after a full page, and keeps the first page's range", async () => {
|
|
219
|
+
vi.useFakeTimers();
|
|
220
|
+
vi.setSystemTime(cutoff);
|
|
221
|
+
const fixture = createModel();
|
|
222
|
+
fixture.listEvents
|
|
223
|
+
.mockResolvedValueOnce([
|
|
224
|
+
eventRow(eventId(3), cutoff - HOUR),
|
|
225
|
+
eventRow(eventId(2), cutoff - 30 * DAY),
|
|
226
|
+
])
|
|
227
|
+
.mockResolvedValueOnce([eventRow(eventId(1), cutoff - 60 * DAY)]);
|
|
228
|
+
const provider = createInsightsProvider(fixture.model);
|
|
229
|
+
|
|
230
|
+
const first = await provider.listEvents({ limit: 2 });
|
|
231
|
+
// Days later, the next page still reads the first page's range.
|
|
232
|
+
vi.setSystemTime(cutoff + 3 * DAY);
|
|
233
|
+
const second = await provider.listEvents({
|
|
234
|
+
cursor: first.nextCursor ?? undefined,
|
|
235
|
+
limit: 2,
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
expect(
|
|
239
|
+
JSON.parse(Buffer.from(first.nextCursor!, "base64url").toString("utf8")),
|
|
240
|
+
).toEqual({
|
|
241
|
+
after: { id: eventId(2), receivedAtMs: cutoff - 30 * DAY },
|
|
242
|
+
beforeReceivedAtMs: cutoff,
|
|
243
|
+
sinceMs: cutoff - 90 * DAY,
|
|
244
|
+
kind: "events",
|
|
245
|
+
filter: { kind: "all" },
|
|
246
|
+
version: 2,
|
|
247
|
+
});
|
|
248
|
+
expect(second).toMatchObject({
|
|
249
|
+
beforeReceivedAtMs: cutoff,
|
|
250
|
+
nextCursor: null,
|
|
251
|
+
});
|
|
252
|
+
expect(fixture.listEvents).toHaveBeenLastCalledWith({
|
|
253
|
+
after: { id: eventId(2), receivedAtMs: cutoff - 30 * DAY },
|
|
254
|
+
beforeReceivedAtMs: cutoff,
|
|
255
|
+
sinceMs: cutoff - 90 * DAY,
|
|
256
|
+
filter: { kind: "all" },
|
|
257
|
+
limit: 2,
|
|
258
|
+
});
|
|
259
|
+
// A short page and an empty one end the list.
|
|
260
|
+
await expect(provider.listEvents({ limit: 2 })).resolves.toMatchObject({
|
|
261
|
+
data: [],
|
|
262
|
+
nextCursor: null,
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it("rejects the cursors that continued below a window of days", async () => {
|
|
267
|
+
const fixture = createModel();
|
|
268
|
+
const provider = createInsightsProvider(fixture.model);
|
|
269
|
+
const window = {
|
|
270
|
+
beforeReceivedAtMs: cutoff,
|
|
271
|
+
kind: "events",
|
|
272
|
+
filter: { kind: "all" },
|
|
273
|
+
version: 2,
|
|
274
|
+
};
|
|
275
|
+
for (const position of [
|
|
276
|
+
{ olderThanMs: cutoff - 90 * DAY, sinceMs: cutoff - 150 * DAY },
|
|
277
|
+
{ olderThanMs: cutoff - 10 * DAY, sinceMs: cutoff - 30 * DAY },
|
|
278
|
+
]) {
|
|
279
|
+
const cursor = Buffer.from(
|
|
280
|
+
JSON.stringify({ ...window, ...position }),
|
|
281
|
+
).toString("base64url");
|
|
282
|
+
await expect(provider.listEvents({ cursor })).rejects.toThrow(
|
|
283
|
+
new InsightsBadRequestError("Invalid Insights cursor."),
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
expect(fixture.listEvents).not.toHaveBeenCalled();
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it("binds event cursors to their filter", async () => {
|
|
290
|
+
vi.useFakeTimers();
|
|
291
|
+
vi.setSystemTime(1_000);
|
|
292
|
+
const fixture = createModel();
|
|
293
|
+
fixture.listEvents.mockResolvedValue([eventRow(eventId(2), 900)]);
|
|
128
294
|
const provider = createInsightsProvider(fixture.model);
|
|
129
295
|
const allEvents = await provider.listEvents({ limit: 1 });
|
|
130
296
|
|
|
@@ -181,7 +347,7 @@ describe("createInsightsProvider", () => {
|
|
|
181
347
|
]);
|
|
182
348
|
expect(fixture.listEvents).toHaveBeenCalledWith({
|
|
183
349
|
beforeReceivedAtMs: 1_000,
|
|
184
|
-
limit:
|
|
350
|
+
limit: 10,
|
|
185
351
|
sinceMs: 0,
|
|
186
352
|
filter: { kind: "installationMovement", installId: "install-2" },
|
|
187
353
|
});
|
|
@@ -192,7 +358,6 @@ describe("createInsightsProvider", () => {
|
|
|
192
358
|
fixture.findLatestEvents.mockResolvedValue([
|
|
193
359
|
installationRow("install-a"),
|
|
194
360
|
installationRow("install-b"),
|
|
195
|
-
installationRow("install-c"),
|
|
196
361
|
]);
|
|
197
362
|
const provider = createInsightsProvider(fixture.model);
|
|
198
363
|
|
|
@@ -206,7 +371,7 @@ describe("createInsightsProvider", () => {
|
|
|
206
371
|
"install-b",
|
|
207
372
|
]);
|
|
208
373
|
expect(fixture.findLatestEvents).toHaveBeenCalledWith({
|
|
209
|
-
limit:
|
|
374
|
+
limit: 2,
|
|
210
375
|
userId: "user-1",
|
|
211
376
|
});
|
|
212
377
|
await expect(
|
|
@@ -243,6 +408,31 @@ describe("createInsightsProvider", () => {
|
|
|
243
408
|
expect(fixture.countEvents).not.toHaveBeenCalled();
|
|
244
409
|
});
|
|
245
410
|
|
|
411
|
+
it("counts whole hours that end with the current one", async () => {
|
|
412
|
+
vi.useFakeTimers();
|
|
413
|
+
vi.setSystemTime(new Date("2026-08-12T10:25:00.000Z"));
|
|
414
|
+
const fixture = createModel();
|
|
415
|
+
fixture.countLatestEvents.mockResolvedValue(4);
|
|
416
|
+
const provider = createInsightsProvider(fixture.model);
|
|
417
|
+
|
|
418
|
+
const overview = await provider.getReportingOverview({
|
|
419
|
+
window: "24h",
|
|
420
|
+
platform: "ios",
|
|
421
|
+
channel: "production",
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
const end = Date.parse("2026-08-12T11:00:00.000Z");
|
|
425
|
+
expect(overview).toMatchObject({
|
|
426
|
+
beforeReceivedAtMs: end,
|
|
427
|
+
sinceMs: end - 24 * 60 * 60 * 1_000,
|
|
428
|
+
});
|
|
429
|
+
expect(fixture.countLatestEvents).toHaveBeenCalledWith({
|
|
430
|
+
platform: "ios",
|
|
431
|
+
channel: "production",
|
|
432
|
+
sinceMs: end - 24 * 60 * 60 * 1_000,
|
|
433
|
+
});
|
|
434
|
+
});
|
|
435
|
+
|
|
246
436
|
it("attributes recovery to the source bundle and reuses its count predicate for drill-down", async () => {
|
|
247
437
|
vi.useFakeTimers();
|
|
248
438
|
vi.setSystemTime(new Date("2026-08-12T00:00:00.000Z"));
|
|
@@ -297,23 +487,14 @@ describe("createInsightsProvider", () => {
|
|
|
297
487
|
expect(fixture.listEvents).toHaveBeenCalledWith({
|
|
298
488
|
...counted,
|
|
299
489
|
filter: { kind: "bundle", ...counted.filter },
|
|
300
|
-
limit:
|
|
490
|
+
limit: 50,
|
|
301
491
|
});
|
|
302
492
|
});
|
|
303
493
|
|
|
304
494
|
it("binds bundle cursors to the outcome, scope, bundle, and both time bounds", async () => {
|
|
305
495
|
const fixture = createModel();
|
|
306
|
-
fixture.listEvents.mockResolvedValue([
|
|
307
|
-
eventRow(eventId(2), 900),
|
|
308
|
-
eventRow(eventId(1), 800),
|
|
309
|
-
]);
|
|
496
|
+
fixture.listEvents.mockResolvedValue([eventRow(eventId(2), 900)]);
|
|
310
497
|
const provider = createInsightsProvider(fixture.model);
|
|
311
|
-
const bundle = {
|
|
312
|
-
platform: "ios",
|
|
313
|
-
channel: "production",
|
|
314
|
-
bundleId: "bundle-after",
|
|
315
|
-
outcome: "applied",
|
|
316
|
-
} as const;
|
|
317
498
|
const first = await provider.listEvents({
|
|
318
499
|
bundle,
|
|
319
500
|
sinceMs: 100,
|
|
@@ -337,10 +518,7 @@ describe("createInsightsProvider", () => {
|
|
|
337
518
|
|
|
338
519
|
it("rejects forged event keys before they reach the database boundary", async () => {
|
|
339
520
|
const fixture = createModel();
|
|
340
|
-
fixture.listEvents.mockResolvedValue([
|
|
341
|
-
eventRow(eventId(2), 900),
|
|
342
|
-
eventRow(eventId(1), 800),
|
|
343
|
-
]);
|
|
521
|
+
fixture.listEvents.mockResolvedValue([eventRow(eventId(2), 900)]);
|
|
344
522
|
const provider = createInsightsProvider(fixture.model);
|
|
345
523
|
const first = await provider.listEvents({
|
|
346
524
|
sinceMs: 100,
|
package/src/insights/provider.ts
CHANGED
|
@@ -32,12 +32,24 @@ const MAX_EVENT_ID_LENGTH = 1_024;
|
|
|
32
32
|
const MAX_IDENTITY_LENGTH = 255;
|
|
33
33
|
const MAX_CURSOR_LENGTH = 8 * 1_024;
|
|
34
34
|
|
|
35
|
+
const HOUR_MS = 60 * 60 * 1_000;
|
|
36
|
+
|
|
35
37
|
const WINDOW_MS: Record<ActiveInstallationWindow, number> = {
|
|
36
38
|
"24h": 24 * 60 * 60 * 1_000,
|
|
37
39
|
"7d": 7 * 24 * 60 * 60 * 1_000,
|
|
38
40
|
"30d": 30 * 24 * 60 * 60 * 1_000,
|
|
39
41
|
};
|
|
40
42
|
|
|
43
|
+
/**
|
|
44
|
+
* The longest receipt range, `[sinceMs, beforeReceivedAtMs)`, of a global or
|
|
45
|
+
* bundle event list, and the range one covers without `sinceMs`: raw events
|
|
46
|
+
* are listed inside a time range, as analytics products list them. It is a
|
|
47
|
+
* duration, so a range that ends mid-day touches 91 UTC days. Installation
|
|
48
|
+
* history is one index range and reads back to `sinceMs`.
|
|
49
|
+
*/
|
|
50
|
+
export const EVENT_LIST_RANGE_MS = 90 * 24 * HOUR_MS;
|
|
51
|
+
|
|
52
|
+
/** A full page's last row, and the range every page of the list shares. */
|
|
41
53
|
type EventCursorPayload = {
|
|
42
54
|
readonly after: InsightsEventCursor;
|
|
43
55
|
readonly beforeReceivedAtMs: number;
|
|
@@ -188,6 +200,8 @@ const readEventCursor = (
|
|
|
188
200
|
filter: InsightsEventFilter,
|
|
189
201
|
): EventCursorPayload => {
|
|
190
202
|
const cursor = decodeCursor(value);
|
|
203
|
+
// A cursor names the row it continues after. One that only held a time
|
|
204
|
+
// (`olderThanMs`) resumed below a window of days, which lists no longer do.
|
|
191
205
|
if (
|
|
192
206
|
!isRecord(cursor) ||
|
|
193
207
|
cursor.version !== 2 ||
|
|
@@ -335,17 +349,34 @@ const toInstallationRow = (row: BundleEventRow): InstallationRow => ({
|
|
|
335
349
|
username: row.metadata.username,
|
|
336
350
|
});
|
|
337
351
|
|
|
338
|
-
|
|
339
|
-
|
|
352
|
+
interface EventBounds {
|
|
353
|
+
readonly beforeReceivedAtMs: number;
|
|
354
|
+
readonly sinceMs: number;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Where a list without `sinceMs` starts: 90 days before its cutoff for the
|
|
359
|
+
* global and bundle lists, at the first event for installation history.
|
|
360
|
+
*/
|
|
361
|
+
const defaultEventStart = (
|
|
362
|
+
filter: InsightsEventFilter,
|
|
363
|
+
beforeReceivedAtMs: number,
|
|
364
|
+
): number =>
|
|
365
|
+
filter.kind === "installationMovement"
|
|
366
|
+
? 0
|
|
367
|
+
: Math.max(0, beforeReceivedAtMs - EVENT_LIST_RANGE_MS);
|
|
368
|
+
|
|
369
|
+
/** Whether a cursor's row lies inside the list's range. */
|
|
370
|
+
const isInsideBounds = ({ after }: EventCursorPayload, bounds: EventBounds) =>
|
|
371
|
+
after.receivedAtMs >= bounds.sinceMs &&
|
|
372
|
+
after.receivedAtMs < bounds.beforeReceivedAtMs;
|
|
373
|
+
|
|
374
|
+
/** A page's range: its cursor's, which the request may repeat but not change. */
|
|
375
|
+
const readEventBounds = (
|
|
340
376
|
input: InsightsEventPageInput,
|
|
377
|
+
cursor: EventCursorPayload | undefined,
|
|
341
378
|
filter: InsightsEventFilter,
|
|
342
|
-
|
|
343
|
-
) => {
|
|
344
|
-
const limit = readLimit(input.limit);
|
|
345
|
-
const cursor =
|
|
346
|
-
input.cursor === undefined
|
|
347
|
-
? undefined
|
|
348
|
-
: readEventCursor(input.cursor, filter);
|
|
379
|
+
): EventBounds => {
|
|
349
380
|
const beforeReceivedAtMs =
|
|
350
381
|
cursor?.beforeReceivedAtMs ??
|
|
351
382
|
(input.beforeReceivedAtMs === undefined
|
|
@@ -363,41 +394,63 @@ const pageEventRows = async <T extends EventHistoryRow>(
|
|
|
363
394
|
const sinceMs =
|
|
364
395
|
cursor?.sinceMs ??
|
|
365
396
|
(input.sinceMs === undefined
|
|
366
|
-
?
|
|
397
|
+
? defaultEventStart(filter, beforeReceivedAtMs)
|
|
367
398
|
: requireTimestamp(input.sinceMs, "event start"));
|
|
399
|
+
const bounds = { beforeReceivedAtMs, sinceMs };
|
|
368
400
|
if (
|
|
369
401
|
sinceMs > beforeReceivedAtMs ||
|
|
370
402
|
(input.sinceMs !== undefined && input.sinceMs !== sinceMs) ||
|
|
371
|
-
(cursor !== undefined &&
|
|
372
|
-
(cursor.after.receivedAtMs < sinceMs ||
|
|
373
|
-
cursor.after.receivedAtMs >= beforeReceivedAtMs))
|
|
403
|
+
(cursor !== undefined && !isInsideBounds(cursor, bounds))
|
|
374
404
|
) {
|
|
375
405
|
throw new InsightsBadRequestError(
|
|
376
406
|
"Insights cursor or range does not match the requested event start.",
|
|
377
407
|
);
|
|
378
408
|
}
|
|
379
|
-
|
|
409
|
+
if (
|
|
410
|
+
filter.kind !== "installationMovement" &&
|
|
411
|
+
beforeReceivedAtMs - sinceMs > EVENT_LIST_RANGE_MS
|
|
412
|
+
) {
|
|
413
|
+
throw new InsightsBadRequestError(
|
|
414
|
+
"Insights event lists cover at most 90 days: send a sinceMs no more than 90 days before beforeReceivedAtMs.",
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
return bounds;
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
const pageEventRows = async <T extends EventHistoryRow>(
|
|
421
|
+
model: InsightsModel,
|
|
422
|
+
input: InsightsEventPageInput,
|
|
423
|
+
filter: InsightsEventFilter,
|
|
424
|
+
map: (row: BundleEventRow) => T,
|
|
425
|
+
) => {
|
|
426
|
+
const limit = readLimit(input.limit);
|
|
427
|
+
const cursor =
|
|
428
|
+
input.cursor === undefined
|
|
429
|
+
? undefined
|
|
430
|
+
: readEventCursor(input.cursor, filter);
|
|
431
|
+
const bounds = readEventBounds(input, cursor, filter);
|
|
432
|
+
const query = {
|
|
380
433
|
filter,
|
|
381
|
-
|
|
382
|
-
beforeReceivedAtMs,
|
|
434
|
+
...bounds,
|
|
383
435
|
...(cursor === undefined ? {} : { after: cursor.after }),
|
|
384
|
-
limit
|
|
436
|
+
limit,
|
|
385
437
|
};
|
|
386
|
-
const rows = await model.listEvents(
|
|
387
|
-
assertEventRows(rows,
|
|
388
|
-
const
|
|
389
|
-
const last = pageRows.at(-1);
|
|
438
|
+
const rows = await model.listEvents(query);
|
|
439
|
+
assertEventRows(rows, query);
|
|
440
|
+
const last = rows.at(-1);
|
|
390
441
|
return {
|
|
391
|
-
beforeReceivedAtMs,
|
|
392
|
-
data:
|
|
442
|
+
beforeReceivedAtMs: bounds.beforeReceivedAtMs,
|
|
443
|
+
data: rows.map(map),
|
|
444
|
+
// A short page ends the range, so only a full page carries a cursor,
|
|
445
|
+
// with the range every page shares: the last call may read an empty
|
|
446
|
+
// page, and no page ends before the range start while events remain.
|
|
393
447
|
nextCursor:
|
|
394
|
-
rows.length
|
|
448
|
+
rows.length === limit && last
|
|
395
449
|
? encodeCursor({
|
|
396
450
|
after: { id: last.id, receivedAtMs: last.received_at_ms },
|
|
397
|
-
|
|
451
|
+
...bounds,
|
|
398
452
|
kind: "events",
|
|
399
453
|
filter,
|
|
400
|
-
sinceMs,
|
|
401
454
|
version: 2,
|
|
402
455
|
})
|
|
403
456
|
: null,
|
|
@@ -502,16 +555,15 @@ export const createInsightsProvider = (
|
|
|
502
555
|
...(cursor === undefined
|
|
503
556
|
? {}
|
|
504
557
|
: { afterInstallId: cursor.afterInstallId }),
|
|
505
|
-
limit
|
|
558
|
+
limit,
|
|
506
559
|
};
|
|
507
560
|
const rows = await model.findLatestEvents(databaseInput);
|
|
508
561
|
assertInstallationRows(rows, databaseInput);
|
|
509
|
-
const
|
|
510
|
-
const last = pageRows.at(-1);
|
|
562
|
+
const last = rows.at(-1);
|
|
511
563
|
return {
|
|
512
|
-
data:
|
|
564
|
+
data: rows.map(toInstallationRow),
|
|
513
565
|
nextCursor:
|
|
514
|
-
rows.length
|
|
566
|
+
rows.length === limit && last
|
|
515
567
|
? encodeCursor({
|
|
516
568
|
afterInstallId: last.install_id,
|
|
517
569
|
kind: "user-installations",
|
|
@@ -529,7 +581,11 @@ export const createInsightsProvider = (
|
|
|
529
581
|
"Invalid reporting installation window.",
|
|
530
582
|
);
|
|
531
583
|
}
|
|
532
|
-
|
|
584
|
+
// Whole hours, ending with the current one: the counts read only the
|
|
585
|
+
// maintained hour and day rows, never raw events.
|
|
586
|
+
const now = Date.now();
|
|
587
|
+
const beforeReceivedAtMs =
|
|
588
|
+
now - (now % HOUR_MS) + (now % HOUR_MS === 0 ? 0 : HOUR_MS);
|
|
533
589
|
const sinceMs = Math.max(0, beforeReceivedAtMs - WINDOW_MS[window]);
|
|
534
590
|
const measure = async (count: Promise<number>) => {
|
|
535
591
|
const value = await count;
|
package/src/insights/routes.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { RouteHandler } from "../handlerTypes";
|
|
2
1
|
import {
|
|
3
2
|
InsightsBadRequestError,
|
|
4
3
|
InsightsPayloadTooLargeError,
|
|
4
|
+
isDatabaseBusyError,
|
|
5
5
|
} from "./errors";
|
|
6
6
|
import { parseBundleEventRequest } from "./eventInput";
|
|
7
7
|
import {
|
|
@@ -11,9 +11,16 @@ import {
|
|
|
11
11
|
} from "./queryInput";
|
|
12
12
|
import type { InsightsProvider } from "./types";
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
/** Seconds a client waits before it resends a request the busy database refused. */
|
|
15
|
+
const RETRY_AFTER_SECONDS = 5;
|
|
16
|
+
|
|
17
|
+
const json = (
|
|
18
|
+
body: unknown,
|
|
19
|
+
status: number,
|
|
20
|
+
headers: Readonly<Record<string, string>> = {},
|
|
21
|
+
): Response =>
|
|
15
22
|
Response.json(body, {
|
|
16
|
-
headers: { "cache-control": "private, no-store" },
|
|
23
|
+
headers: { "cache-control": "private, no-store", ...headers },
|
|
17
24
|
status,
|
|
18
25
|
});
|
|
19
26
|
|
|
@@ -42,6 +49,18 @@ const run = async (operation: () => Promise<Response>): Promise<Response> => {
|
|
|
42
49
|
if (error instanceof InsightsPayloadTooLargeError) {
|
|
43
50
|
return json({ error: error.message }, 413);
|
|
44
51
|
}
|
|
52
|
+
// Back-pressure: a busy database has not failed the request, so the
|
|
53
|
+
// client may send it again after Retry-After. A 500 would read as a fault.
|
|
54
|
+
// The warning keeps the overload visible to whoever runs the server.
|
|
55
|
+
if (isDatabaseBusyError(error)) {
|
|
56
|
+
console.warn(
|
|
57
|
+
"[hot-updater] Insights answered 503: the database is busy.",
|
|
58
|
+
error,
|
|
59
|
+
);
|
|
60
|
+
return json({ error: "Service unavailable" }, 503, {
|
|
61
|
+
"retry-after": String(RETRY_AFTER_SECONDS),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
45
64
|
throw error;
|
|
46
65
|
}
|
|
47
66
|
};
|
|
@@ -49,9 +68,81 @@ const run = async (operation: () => Promise<Response>): Promise<Response> => {
|
|
|
49
68
|
const query = (operation: () => Promise<unknown>): Promise<Response> =>
|
|
50
69
|
run(async () => json(await operation(), 200));
|
|
51
70
|
|
|
71
|
+
/** Every Insights route: where it mounts, and the handler it runs. */
|
|
72
|
+
export const INSIGHTS_ROUTES = [
|
|
73
|
+
{
|
|
74
|
+
access: "client",
|
|
75
|
+
method: "POST",
|
|
76
|
+
path: "/events",
|
|
77
|
+
handler: "appendBundleEvent",
|
|
78
|
+
},
|
|
79
|
+
{ access: "admin", method: "GET", path: "/events", handler: "listEvents" },
|
|
80
|
+
{
|
|
81
|
+
access: "admin",
|
|
82
|
+
method: "GET",
|
|
83
|
+
path: "/overview",
|
|
84
|
+
handler: "getReportingOverview",
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
access: "admin",
|
|
88
|
+
method: "GET",
|
|
89
|
+
path: "/installations",
|
|
90
|
+
handler: "pageInstallationsByCurrentUserId",
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
access: "admin",
|
|
94
|
+
method: "GET",
|
|
95
|
+
path: "/installations/:installId/events",
|
|
96
|
+
handler: "listInstallationEvents",
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
access: "admin",
|
|
100
|
+
method: "GET",
|
|
101
|
+
path: "/installations/:installId",
|
|
102
|
+
handler: "getInstallation",
|
|
103
|
+
},
|
|
104
|
+
] as const;
|
|
105
|
+
|
|
106
|
+
export type InsightsRouteName = (typeof INSIGHTS_ROUTES)[number]["handler"];
|
|
107
|
+
|
|
108
|
+
export type InsightsRouteHandler = (
|
|
109
|
+
params: Readonly<Record<string, string>>,
|
|
110
|
+
request: Request,
|
|
111
|
+
) => Promise<Response>;
|
|
112
|
+
|
|
113
|
+
/** Without the insights plugin, its routes answer 204 and say Insights is off. */
|
|
114
|
+
export const insightsDisabled: InsightsRouteHandler = async () =>
|
|
115
|
+
new Response(null, {
|
|
116
|
+
headers: {
|
|
117
|
+
"cache-control": "private, no-store",
|
|
118
|
+
"x-hot-updater-insights": "disabled",
|
|
119
|
+
},
|
|
120
|
+
status: 204,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
/** What a server without the insights plugin logs on the first event it drops. */
|
|
124
|
+
export const INSIGHTS_OFF_WARNING =
|
|
125
|
+
"[hot-updater] Insights is off, so POST /events answered 204 and stored nothing. Add insights() from @hot-updater/server/plugins/insights to plugins to record app events, or set insights: false in the app's HotUpdater.init to stop sending them. This warning prints once per server.";
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* The client's `POST /events` without the insights plugin. It answers as
|
|
129
|
+
* {@link insightsDisabled} does, so apps need no change, and warns on the
|
|
130
|
+
* first event it drops, so a server that left out `insights()` says so.
|
|
131
|
+
*/
|
|
132
|
+
export const createDroppedEventHandler = (): InsightsRouteHandler => {
|
|
133
|
+
let warned = false;
|
|
134
|
+
return (params, request) => {
|
|
135
|
+
if (!warned) {
|
|
136
|
+
warned = true;
|
|
137
|
+
console.warn(INSIGHTS_OFF_WARNING);
|
|
138
|
+
}
|
|
139
|
+
return insightsDisabled(params, request);
|
|
140
|
+
};
|
|
141
|
+
};
|
|
142
|
+
|
|
52
143
|
export const createInsightsRouteHandlers = (
|
|
53
144
|
provider: InsightsProvider,
|
|
54
|
-
): Record<
|
|
145
|
+
): Record<InsightsRouteName, InsightsRouteHandler> => ({
|
|
55
146
|
appendBundleEvent: async (_params, request) =>
|
|
56
147
|
run(async () => {
|
|
57
148
|
await provider.appendBundleEvent(await parseBundleEventRequest(request));
|
|
@@ -86,19 +177,3 @@ export const createInsightsRouteHandlers = (
|
|
|
86
177
|
),
|
|
87
178
|
),
|
|
88
179
|
});
|
|
89
|
-
|
|
90
|
-
export const registerInsightsClientRoutes = (
|
|
91
|
-
add: (method: string, path: string, handler: string) => void,
|
|
92
|
-
): void => {
|
|
93
|
-
add("POST", "/events", "appendBundleEvent");
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
export const registerInsightsAdminRoutes = (
|
|
97
|
-
add: (method: string, path: string, handler: string) => void,
|
|
98
|
-
): void => {
|
|
99
|
-
add("GET", "/events", "listEvents");
|
|
100
|
-
add("GET", "/overview", "getReportingOverview");
|
|
101
|
-
add("GET", "/installations", "pageInstallationsByCurrentUserId");
|
|
102
|
-
add("GET", "/installations/:installId/events", "listInstallationEvents");
|
|
103
|
-
add("GET", "/installations/:installId", "getInstallation");
|
|
104
|
-
};
|