@friggframework/core 2.0.0-next.12 → 2.0.0-next.121
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/.eslintrc.json +18 -1
- package/CLAUDE.md +830 -0
- package/README.md +996 -50
- package/admin-scripts/index.js +52 -0
- package/admin-scripts/repositories/admin-script-execution-repository-documentdb.js +21 -0
- package/admin-scripts/repositories/admin-script-execution-repository-factory.js +51 -0
- package/admin-scripts/repositories/admin-script-execution-repository-interface.js +171 -0
- package/admin-scripts/repositories/admin-script-execution-repository-mongo.js +238 -0
- package/admin-scripts/repositories/admin-script-execution-repository-postgres.js +278 -0
- package/admin-scripts/repositories/script-schedule-repository-documentdb.js +21 -0
- package/admin-scripts/repositories/script-schedule-repository-factory.js +51 -0
- package/admin-scripts/repositories/script-schedule-repository-interface.js +108 -0
- package/admin-scripts/repositories/script-schedule-repository-mongo.js +179 -0
- package/admin-scripts/repositories/script-schedule-repository-postgres.js +210 -0
- package/application/commands/README.md +451 -0
- package/application/commands/admin-script-commands.js +359 -0
- package/application/commands/credential-commands.js +262 -0
- package/application/commands/entity-commands.js +294 -0
- package/application/commands/integration-commands.js +419 -0
- package/application/commands/integration-mapping-commands.js +25 -0
- package/application/commands/process-commands.js +135 -0
- package/application/commands/report-commands.js +188 -0
- package/application/commands/scheduler-commands.js +263 -0
- package/application/commands/usage-commands.js +56 -0
- package/application/commands/user-commands.js +312 -0
- package/application/index.js +83 -0
- package/artifacts/repositories/artifact-repository-factory.js +19 -0
- package/artifacts/repositories/artifact-repository-interface.js +27 -0
- package/artifacts/repositories/artifact-repository-local.js +42 -0
- package/artifacts/repositories/artifact-repository-s3.js +61 -0
- package/assertions/index.js +0 -3
- package/core/CLAUDE.md +715 -0
- package/core/Worker.js +55 -24
- package/core/create-handler.js +121 -38
- package/core/index.js +8 -1
- package/core/invocation-scope.js +225 -0
- package/core/parameters-to-env.js +257 -0
- package/core/secrets-to-env.js +7 -6
- package/core/ssm-preload.mjs +34 -0
- package/credential/repositories/credential-active-type.js +32 -0
- package/credential/repositories/credential-repository-documentdb.js +355 -0
- package/credential/repositories/credential-repository-factory.js +54 -0
- package/credential/repositories/credential-repository-interface.js +113 -0
- package/credential/repositories/credential-repository-mongo.js +294 -0
- package/credential/repositories/credential-repository-postgres.js +312 -0
- package/credential/repositories/credential-repository.js +300 -0
- package/credential/use-cases/get-credential-for-user.js +25 -0
- package/credential/use-cases/update-authentication-status.js +15 -0
- package/database/MONGODB_TRANSACTION_FIX.md +198 -0
- package/database/adapters/lambda-invoker.js +97 -0
- package/database/config.js +147 -0
- package/database/documentdb-encryption-service.js +334 -0
- package/database/documentdb-utils.js +196 -0
- package/database/encryption/README.md +863 -0
- package/database/encryption/documentdb-encryption-service.md +3575 -0
- package/database/encryption/encryption-schema-registry.js +404 -0
- package/database/encryption/field-encryption-service.js +254 -0
- package/database/encryption/integration-mapping-encryption.js +97 -0
- package/database/encryption/logger.js +82 -0
- package/database/encryption/prisma-encryption-extension.js +241 -0
- package/database/index.js +21 -21
- package/database/prisma.js +182 -0
- package/database/repositories/health-check-repository-documentdb.js +138 -0
- package/database/repositories/health-check-repository-factory.js +48 -0
- package/database/repositories/health-check-repository-interface.js +82 -0
- package/database/repositories/health-check-repository-mongodb.js +89 -0
- package/database/repositories/health-check-repository-postgres.js +82 -0
- package/database/repositories/migration-status-repository-s3.js +137 -0
- package/database/use-cases/check-database-health-use-case.js +29 -0
- package/database/use-cases/check-database-state-use-case.js +81 -0
- package/database/use-cases/check-encryption-health-use-case.js +83 -0
- package/database/use-cases/get-database-state-via-worker-use-case.js +61 -0
- package/database/use-cases/get-migration-status-use-case.js +93 -0
- package/database/use-cases/resolve-migration-via-worker-use-case.js +49 -0
- package/database/use-cases/run-database-migration-use-case.js +139 -0
- package/database/use-cases/test-encryption-use-case.js +253 -0
- package/database/use-cases/trigger-database-migration-use-case.js +157 -0
- package/database/utils/mongodb-collection-utils.js +94 -0
- package/database/utils/mongodb-schema-init.js +108 -0
- package/database/utils/prisma-runner.js +491 -0
- package/database/utils/prisma-schema-parser.js +182 -0
- package/docs/PROCESS_MANAGEMENT_QUEUE_SPEC.md +517 -0
- package/encrypt/Cryptor.js +34 -168
- package/encrypt/index.js +1 -2
- package/errors/client-safe-error.js +26 -0
- package/errors/fetch-error.js +59 -57
- package/errors/index.js +6 -0
- package/errors/mapping-already-exists-error.js +14 -0
- package/generated/prisma-mongodb/client.d.ts +1 -0
- package/generated/prisma-mongodb/client.js +5 -0
- package/generated/prisma-mongodb/default.d.ts +1 -0
- package/generated/prisma-mongodb/default.js +5 -0
- package/generated/prisma-mongodb/edge.d.ts +1 -0
- package/generated/prisma-mongodb/edge.js +386 -0
- package/generated/prisma-mongodb/index-browser.js +368 -0
- package/generated/prisma-mongodb/index.d.ts +27065 -0
- package/generated/prisma-mongodb/index.js +411 -0
- package/generated/prisma-mongodb/libquery_engine-debian-openssl-3.0.x.so.node +0 -0
- package/generated/prisma-mongodb/libquery_engine-rhel-openssl-3.0.x.so.node +0 -0
- package/generated/prisma-mongodb/package.json +183 -0
- package/generated/prisma-mongodb/runtime/edge-esm.js +35 -0
- package/generated/prisma-mongodb/runtime/edge.js +35 -0
- package/generated/prisma-mongodb/runtime/index-browser.d.ts +370 -0
- package/generated/prisma-mongodb/runtime/index-browser.js +17 -0
- package/generated/prisma-mongodb/runtime/library.d.ts +3982 -0
- package/generated/prisma-mongodb/runtime/library.js +147 -0
- package/generated/prisma-mongodb/runtime/react-native.js +84 -0
- package/generated/prisma-mongodb/runtime/wasm-compiler-edge.js +85 -0
- package/generated/prisma-mongodb/runtime/wasm-engine-edge.js +38 -0
- package/generated/prisma-mongodb/schema.prisma +455 -0
- package/generated/prisma-mongodb/wasm-edge-light-loader.mjs +5 -0
- package/generated/prisma-mongodb/wasm-worker-loader.mjs +5 -0
- package/generated/prisma-mongodb/wasm.d.ts +1 -0
- package/generated/prisma-mongodb/wasm.js +393 -0
- package/generated/prisma-postgresql/client.d.ts +1 -0
- package/generated/prisma-postgresql/client.js +5 -0
- package/generated/prisma-postgresql/default.d.ts +1 -0
- package/generated/prisma-postgresql/default.js +5 -0
- package/generated/prisma-postgresql/edge.d.ts +1 -0
- package/generated/prisma-postgresql/edge.js +407 -0
- package/generated/prisma-postgresql/index-browser.js +389 -0
- package/generated/prisma-postgresql/index.d.ts +29904 -0
- package/generated/prisma-postgresql/index.js +432 -0
- package/generated/prisma-postgresql/libquery_engine-debian-openssl-3.0.x.so.node +0 -0
- package/generated/prisma-postgresql/libquery_engine-rhel-openssl-3.0.x.so.node +0 -0
- package/generated/prisma-postgresql/package.json +183 -0
- package/generated/prisma-postgresql/query_engine_bg.js +2 -0
- package/generated/prisma-postgresql/query_engine_bg.wasm +0 -0
- package/generated/prisma-postgresql/runtime/edge-esm.js +35 -0
- package/generated/prisma-postgresql/runtime/edge.js +35 -0
- package/generated/prisma-postgresql/runtime/index-browser.d.ts +370 -0
- package/generated/prisma-postgresql/runtime/index-browser.js +17 -0
- package/generated/prisma-postgresql/runtime/library.d.ts +3982 -0
- package/generated/prisma-postgresql/runtime/library.js +147 -0
- package/generated/prisma-postgresql/runtime/react-native.js +84 -0
- package/generated/prisma-postgresql/runtime/wasm-compiler-edge.js +85 -0
- package/generated/prisma-postgresql/runtime/wasm-engine-edge.js +38 -0
- package/generated/prisma-postgresql/schema.prisma +436 -0
- package/generated/prisma-postgresql/wasm-edge-light-loader.mjs +5 -0
- package/generated/prisma-postgresql/wasm-worker-loader.mjs +5 -0
- package/generated/prisma-postgresql/wasm.d.ts +1 -0
- package/generated/prisma-postgresql/wasm.js +414 -0
- package/handlers/WEBHOOKS.md +682 -0
- package/handlers/app-definition-loader.js +103 -0
- package/handlers/app-handler-helpers.js +20 -6
- package/handlers/backend-utils.js +272 -46
- package/handlers/database-migration-handler.js +236 -0
- package/handlers/integration-event-dispatcher.js +68 -0
- package/handlers/middleware/admin-auth.js +73 -0
- package/handlers/routers/HEALTHCHECK.md +342 -0
- package/handlers/routers/auth.js +4 -15
- package/handlers/routers/db-migration.handler.js +29 -0
- package/handlers/routers/db-migration.js +329 -0
- package/handlers/routers/health.js +518 -0
- package/handlers/routers/integration-defined-routers.js +85 -10
- package/handlers/routers/integration-webhook-routers.js +87 -0
- package/handlers/routers/user.js +27 -5
- package/handlers/routers/websocket.js +31 -9
- package/handlers/use-cases/check-external-apis-health-use-case.js +81 -0
- package/handlers/use-cases/check-integrations-health-use-case.js +44 -0
- package/handlers/workers/db-migration.js +438 -0
- package/handlers/workers/dlq-processor.js +89 -0
- package/handlers/workers/integration-defined-workers.js +13 -7
- package/index.js +123 -47
- package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +238 -0
- package/infrastructure/scheduler/index.js +33 -0
- package/infrastructure/scheduler/mock-scheduler-adapter.js +143 -0
- package/infrastructure/scheduler/scheduler-service-factory.js +73 -0
- package/infrastructure/scheduler/scheduler-service-interface.js +47 -0
- package/integrations/EXTENSIONS.md +240 -0
- package/integrations/WEBHOOK-QUICKSTART.md +151 -0
- package/integrations/extension.js +254 -0
- package/integrations/index.js +20 -10
- package/integrations/integration-base.js +777 -74
- package/integrations/integration-router.js +399 -180
- package/integrations/options.js +1 -1
- package/integrations/repositories/config-patch-shared.js +43 -0
- package/integrations/repositories/integration-mapping-query-pipeline.js +152 -0
- package/integrations/repositories/integration-mapping-query.js +193 -0
- package/integrations/repositories/integration-mapping-repository-documentdb.js +406 -0
- package/integrations/repositories/integration-mapping-repository-factory.js +57 -0
- package/integrations/repositories/integration-mapping-repository-interface.js +183 -0
- package/integrations/repositories/integration-mapping-repository-mongo.js +284 -0
- package/integrations/repositories/integration-mapping-repository-postgres.js +403 -0
- package/integrations/repositories/integration-mapping-repository.js +185 -0
- package/integrations/repositories/integration-repository-documentdb.js +354 -0
- package/integrations/repositories/integration-repository-factory.js +51 -0
- package/integrations/repositories/integration-repository-interface.js +219 -0
- package/integrations/repositories/integration-repository-mongo.js +502 -0
- package/integrations/repositories/integration-repository-postgres.js +550 -0
- package/integrations/repositories/process-repository-documentdb.js +311 -0
- package/integrations/repositories/process-repository-factory.js +53 -0
- package/integrations/repositories/process-repository-interface.js +136 -0
- package/integrations/repositories/process-repository-mongo.js +262 -0
- package/integrations/repositories/process-repository-postgres.js +380 -0
- package/integrations/repositories/process-update-ops-shared.js +112 -0
- package/integrations/repositories/report-id.js +13 -0
- package/integrations/tests/doubles/config-capturing-integration.js +81 -0
- package/integrations/tests/doubles/dummy-integration-class.js +113 -0
- package/integrations/tests/doubles/test-integration-repository.js +146 -0
- package/integrations/use-cases/add-entities-to-integration.js +92 -0
- package/integrations/use-cases/create-integration.js +253 -0
- package/integrations/use-cases/create-process.js +130 -0
- package/integrations/use-cases/delete-integration-for-user.js +130 -0
- package/integrations/use-cases/find-integration-by-entity-external-id.js +74 -0
- package/integrations/use-cases/find-integration-context-by-external-entity-id.js +76 -0
- package/integrations/use-cases/get-integration-for-user.js +78 -0
- package/integrations/use-cases/get-integration-instance-by-definition.js +67 -0
- package/integrations/use-cases/get-integration-instance.js +83 -0
- package/integrations/use-cases/get-integrations-for-user.js +88 -0
- package/integrations/use-cases/get-possible-integrations.js +27 -0
- package/integrations/use-cases/get-process.js +89 -0
- package/integrations/use-cases/index.js +19 -0
- package/integrations/use-cases/list-integrations-by-entity-external-id.js +46 -0
- package/integrations/use-cases/load-integration-context.js +71 -0
- package/integrations/use-cases/patch-integration-config.js +39 -0
- package/integrations/use-cases/process-errors.js +28 -0
- package/integrations/use-cases/remove-entities-from-integration.js +70 -0
- package/integrations/use-cases/update-integration-config.js +32 -0
- package/integrations/use-cases/update-integration-messages.js +44 -0
- package/integrations/use-cases/update-integration-status.js +32 -0
- package/integrations/use-cases/update-integration.js +92 -0
- package/integrations/use-cases/update-process-metrics.js +222 -0
- package/integrations/use-cases/update-process-state.js +163 -0
- package/integrations/utils/assert-entity-ids-input.js +42 -0
- package/integrations/utils/map-integration-dto.js +37 -0
- package/jest-global-setup-noop.js +3 -0
- package/jest-global-teardown-noop.js +3 -0
- package/jest.config.js +2 -0
- package/logs/.eslintrc.json +26 -1
- package/logs/__fixtures__/events.js +82 -0
- package/logs/__fixtures__/matchers.js +45 -0
- package/logs/__fixtures__/secrets.js +59 -0
- package/logs/__fixtures__/vectors.js +329 -0
- package/logs/__fixtures__/with-env.js +45 -0
- package/logs/context.js +109 -0
- package/logs/contract-keys.js +67 -0
- package/logs/debug-shims.js +46 -0
- package/logs/denied-keys.js +21 -0
- package/logs/index.js +14 -3
- package/logs/jest-logger-setup.js +25 -0
- package/logs/levels.js +71 -0
- package/logs/logger-runtime.js +137 -0
- package/logs/logger.js +134 -46
- package/logs/record.js +276 -0
- package/logs/redact.js +328 -0
- package/logs/serialize.js +329 -0
- package/logs/sinks.js +93 -0
- package/logs/summarize-event.js +163 -0
- package/{module-plugin → modules}/index.js +0 -10
- package/modules/module-factory.js +56 -0
- package/modules/module.js +320 -0
- package/modules/repositories/module-repository-documentdb.js +350 -0
- package/modules/repositories/module-repository-factory.js +40 -0
- package/modules/repositories/module-repository-interface.js +145 -0
- package/modules/repositories/module-repository-mongo.js +436 -0
- package/modules/repositories/module-repository-postgres.js +481 -0
- package/modules/repositories/module-repository.js +369 -0
- package/modules/requester/api-key.js +52 -0
- package/modules/requester/oauth-2.js +561 -0
- package/modules/requester/requester.js +605 -0
- package/{module-plugin → modules}/test/mock-api/api.js +8 -3
- package/{module-plugin → modules}/test/mock-api/definition.js +14 -10
- package/modules/tests/doubles/test-module-factory.js +16 -0
- package/modules/tests/doubles/test-module-repository.js +39 -0
- package/modules/use-cases/get-entities-for-user.js +32 -0
- package/modules/use-cases/get-entity-options-by-id.js +71 -0
- package/modules/use-cases/get-entity-options-by-type.js +34 -0
- package/modules/use-cases/get-module-instance-from-type.js +34 -0
- package/modules/use-cases/get-module.js +74 -0
- package/modules/use-cases/process-authorization-callback.js +294 -0
- package/modules/use-cases/refresh-entity-options.js +72 -0
- package/modules/use-cases/test-module-auth.js +72 -0
- package/modules/utils/map-module-dto.js +20 -0
- package/modules/utils/to-credential-id.js +14 -0
- package/package.json +92 -52
- package/prisma-mongodb/schema.prisma +455 -0
- package/prisma-postgresql/migrations/20250930193005_init/migration.sql +315 -0
- package/prisma-postgresql/migrations/20251006135218_init/migration.sql +9 -0
- package/prisma-postgresql/migrations/20251010000000_remove_unused_entity_reference_map/migration.sql +3 -0
- package/prisma-postgresql/migrations/20251112195422_update_user_unique_constraints/migration.sql +25 -0
- package/prisma-postgresql/migrations/20260422120000_add_entity_data_column/migration.sql +10 -0
- package/prisma-postgresql/migrations/20260422120001_create_process_table/migration.sql +48 -0
- package/prisma-postgresql/migrations/20260625000000_add_user_organization_id_index/migration.sql +2 -0
- package/prisma-postgresql/migrations/20260703000000_add_integration_status_in_creation_in_deletion/migration.sql +11 -0
- package/prisma-postgresql/migrations/20260703000001_integration_status_default_in_creation/migration.sql +8 -0
- package/prisma-postgresql/migrations/20260705000000_create_usage_counter/migration.sql +26 -0
- package/prisma-postgresql/migrations/20260706000000_add_admin_script_execution_and_schedule/migration.sql +61 -0
- package/prisma-postgresql/migrations/migration_lock.toml +3 -0
- package/prisma-postgresql/schema.prisma +436 -0
- package/queues/queue-delivery.js +41 -0
- package/queues/queuer-util.js +89 -21
- package/reporting/README.md +154 -0
- package/reporting/builtin-reports.js +6 -0
- package/reporting/index.js +13 -0
- package/reporting/report-base.js +49 -0
- package/reporting/reports/integrations-report.js +221 -0
- package/syncs/manager.js +468 -443
- package/syncs/repositories/sync-repository-documentdb.js +240 -0
- package/syncs/repositories/sync-repository-factory.js +43 -0
- package/syncs/repositories/sync-repository-interface.js +109 -0
- package/syncs/repositories/sync-repository-mongo.js +239 -0
- package/syncs/repositories/sync-repository-postgres.js +319 -0
- package/syncs/sync.js +0 -1
- package/telemetry/README.md +331 -0
- package/telemetry/bind-telemetry-context.js +77 -0
- package/telemetry/canonical-counters.js +52 -0
- package/telemetry/exporters.js +85 -0
- package/telemetry/index.js +26 -0
- package/telemetry/instrument-handler.js +104 -0
- package/telemetry/no-op-telemetry.js +71 -0
- package/telemetry/north-star.js +103 -0
- package/telemetry/otel-telemetry.js +232 -0
- package/telemetry/plugin-subscribers.js +77 -0
- package/telemetry/telemetry-config.js +120 -0
- package/telemetry/telemetry-context.js +51 -0
- package/telemetry/telemetry-event-bus.js +58 -0
- package/telemetry/telemetry-runtime.js +164 -0
- package/telemetry/telemetry-service.js +52 -0
- package/telemetry/usage-rollup-subscriber.js +116 -0
- package/token/repositories/token-repository-documentdb.js +137 -0
- package/token/repositories/token-repository-factory.js +40 -0
- package/token/repositories/token-repository-interface.js +131 -0
- package/token/repositories/token-repository-mongo.js +219 -0
- package/token/repositories/token-repository-postgres.js +264 -0
- package/token/repositories/token-repository.js +219 -0
- package/types/associations/index.d.ts +0 -17
- package/types/core/index.d.ts +12 -4
- package/types/database/index.d.ts +10 -2
- package/types/encrypt/index.d.ts +5 -3
- package/types/errors/index.d.ts +24 -10
- package/types/integrations/index.d.ts +3 -8
- package/types/logs/index.d.ts +61 -2
- package/types/module-plugin/index.d.ts +20 -69
- package/types/syncs/index.d.ts +0 -17
- package/usage/README.md +54 -0
- package/usage/index.js +17 -0
- package/usage/repositories/usage-repository-documentdb.js +194 -0
- package/usage/repositories/usage-repository-factory.js +25 -0
- package/usage/repositories/usage-repository-interface.js +37 -0
- package/usage/repositories/usage-repository-prisma.js +146 -0
- package/usage/tracked-metrics.js +38 -0
- package/usage/usage-windows.js +24 -0
- package/user/repositories/user-repository-documentdb.js +426 -0
- package/user/repositories/user-repository-factory.js +52 -0
- package/user/repositories/user-repository-interface.js +214 -0
- package/user/repositories/user-repository-mongo.js +323 -0
- package/user/repositories/user-repository-postgres.js +377 -0
- package/user/tests/doubles/test-user-repository.js +72 -0
- package/user/use-cases/authenticate-user.js +143 -0
- package/user/use-cases/authenticate-with-shared-secret.js +48 -0
- package/user/use-cases/create-individual-user.js +61 -0
- package/user/use-cases/create-organization-user.js +47 -0
- package/user/use-cases/create-token-for-user-id.js +30 -0
- package/user/use-cases/get-user-from-adopter-jwt.js +149 -0
- package/user/use-cases/get-user-from-bearer-token.js +82 -0
- package/user/use-cases/get-user-from-x-frigg-headers.js +132 -0
- package/user/use-cases/login-user.js +122 -0
- package/user/user.js +125 -0
- package/utils/backend-path.js +38 -0
- package/utils/index.js +6 -0
- package/websocket/repositories/websocket-connection-repository-documentdb.js +119 -0
- package/websocket/repositories/websocket-connection-repository-factory.js +44 -0
- package/websocket/repositories/websocket-connection-repository-interface.js +106 -0
- package/websocket/repositories/websocket-connection-repository-mongo.js +156 -0
- package/websocket/repositories/websocket-connection-repository-postgres.js +196 -0
- package/websocket/repositories/websocket-connection-repository.js +161 -0
- package/assertions/is-equal.js +0 -17
- package/associations/model.js +0 -54
- package/database/models/IndividualUser.js +0 -76
- package/database/models/OrganizationUser.js +0 -29
- package/database/models/State.js +0 -9
- package/database/models/Token.js +0 -70
- package/database/models/UserModel.js +0 -7
- package/database/models/WebsocketConnection.js +0 -49
- package/database/mongo.js +0 -45
- package/database/mongoose.js +0 -5
- package/encrypt/Cryptor.test.js +0 -32
- package/encrypt/encrypt.js +0 -132
- package/encrypt/encrypt.test.js +0 -1069
- package/encrypt/test-encrypt.js +0 -107
- package/errors/base-error.test.js +0 -32
- package/errors/fetch-error.test.js +0 -79
- package/errors/halt-error.test.js +0 -11
- package/errors/validation-errors.test.js +0 -120
- package/handlers/routers/middleware/loadUser.js +0 -15
- package/handlers/routers/middleware/requireLoggedInUser.js +0 -12
- package/integrations/create-frigg-backend.js +0 -31
- package/integrations/integration-factory.js +0 -251
- package/integrations/integration-mapping.js +0 -43
- package/integrations/integration-model.js +0 -46
- package/integrations/integration-user.js +0 -144
- package/integrations/test/integration-base.test.js +0 -144
- package/lambda/TimeoutCatcher.test.js +0 -68
- package/logs/logger.test.js +0 -76
- package/module-plugin/auther.js +0 -393
- package/module-plugin/credential.js +0 -22
- package/module-plugin/entity-manager.js +0 -70
- package/module-plugin/entity.js +0 -46
- package/module-plugin/manager.js +0 -169
- package/module-plugin/module-factory.js +0 -61
- package/module-plugin/requester/api-key.js +0 -36
- package/module-plugin/requester/oauth-2.js +0 -219
- package/module-plugin/requester/requester.js +0 -165
- package/module-plugin/requester/requester.test.js +0 -28
- package/module-plugin/test/auther.test.js +0 -97
- package/syncs/model.js +0 -62
- /package/{module-plugin → modules}/ModuleConstants.js +0 -0
- /package/{module-plugin → modules}/requester/basic.js +0 -0
- /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
const { prisma } = require('../../database/prisma');
|
|
2
|
+
const { updateOne } = require('../../database/documentdb-utils');
|
|
3
|
+
const { UsageRepositoryPrisma } = require('./usage-repository-prisma');
|
|
4
|
+
const { windowKey } = require('../usage-windows');
|
|
5
|
+
|
|
6
|
+
const COLLECTION = 'UsageCounter';
|
|
7
|
+
const DRAIN_BATCH_SIZE = 1000;
|
|
8
|
+
const MAX_BATCHES = 100000;
|
|
9
|
+
const VALID_GROUP_BY = new Set(['integrationType', 'metric']);
|
|
10
|
+
const VALID_BUCKETS = new Set(['day', 'hour']);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* DocumentDB usage store. Amazon DocumentDB does not accept the command shapes
|
|
14
|
+
* Prisma's Mongo engine emits for `upsert({ update: { value: { increment } } })`
|
|
15
|
+
* and `groupBy({ _sum })`, and cursor reads truncate at ~101 docs — so, like
|
|
16
|
+
* every other DocumentDB adapter in this repo, these operations are issued as
|
|
17
|
+
* raw commands (`$runCommandRaw`) via the validated documentdb-utils helpers and
|
|
18
|
+
* a drained aggregate cursor. No timestamps are managed: getTotalsByDimension/getTimeSeries filter on
|
|
19
|
+
* the window KEY (not write-time), so createdAt/updatedAt are unnecessary here.
|
|
20
|
+
*/
|
|
21
|
+
class UsageRepositoryDocumentDB extends UsageRepositoryPrisma {
|
|
22
|
+
constructor() {
|
|
23
|
+
super();
|
|
24
|
+
this.prisma = prisma;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async increment({ integrationId, integrationType, metric, window, value = 1 }) {
|
|
28
|
+
// Atomic upsert-increment: $inc creates the field at the increment value
|
|
29
|
+
// on insert; $setOnInsert stamps the identity on first write. The compound
|
|
30
|
+
// filter is the unique key, so concurrent writers converge on one row.
|
|
31
|
+
await updateOne(
|
|
32
|
+
this.prisma,
|
|
33
|
+
COLLECTION,
|
|
34
|
+
{ integrationId, integrationType, metric, window },
|
|
35
|
+
{
|
|
36
|
+
$inc: { value },
|
|
37
|
+
$setOnInsert: {
|
|
38
|
+
integrationId,
|
|
39
|
+
integrationType,
|
|
40
|
+
metric,
|
|
41
|
+
window,
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
{ upsert: true }
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async getTotalsByDimension({
|
|
49
|
+
metric,
|
|
50
|
+
groupBy = 'integrationType',
|
|
51
|
+
since,
|
|
52
|
+
bucket = 'day',
|
|
53
|
+
} = {}) {
|
|
54
|
+
if (!metric) {
|
|
55
|
+
throw new Error('getTotalsByDimension requires a metric (units are per-metric)');
|
|
56
|
+
}
|
|
57
|
+
assertGroupBy(groupBy);
|
|
58
|
+
assertBucket(bucket);
|
|
59
|
+
|
|
60
|
+
// Single window granularity (day: OR hour:) — never sum across both.
|
|
61
|
+
// `since` bounds on the window KEY (mirrors getTimeSeries / the Prisma adapter).
|
|
62
|
+
const rows = await this._aggregateDrained([
|
|
63
|
+
{
|
|
64
|
+
$match: {
|
|
65
|
+
metric,
|
|
66
|
+
window: windowMatch(bucket, {
|
|
67
|
+
gte: since ? windowKey(bucket, since) : undefined,
|
|
68
|
+
}),
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
{ $group: { _id: `$${groupBy}`, value: { $sum: '$value' } } },
|
|
72
|
+
]);
|
|
73
|
+
|
|
74
|
+
return rows.map((row) => ({
|
|
75
|
+
[groupBy]: row._id,
|
|
76
|
+
value: toNumber(row.value),
|
|
77
|
+
}));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async getTimeSeries({ metric, integrationType, from, to, bucket = 'day' } = {}) {
|
|
81
|
+
assertBucket(bucket);
|
|
82
|
+
if (!integrationType) {
|
|
83
|
+
throw new Error('getTimeSeries requires an integrationType');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const rows = await this._aggregateDrained([
|
|
87
|
+
{
|
|
88
|
+
$match: {
|
|
89
|
+
metric,
|
|
90
|
+
integrationType,
|
|
91
|
+
window: windowMatch(bucket, {
|
|
92
|
+
gte: from ? windowKey(bucket, from) : undefined,
|
|
93
|
+
lte: to ? windowKey(bucket, to) : undefined,
|
|
94
|
+
}),
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
{ $group: { _id: '$window', value: { $sum: '$value' } } },
|
|
98
|
+
{ $sort: { _id: 1 } },
|
|
99
|
+
]);
|
|
100
|
+
|
|
101
|
+
return rows.map((row) => ({
|
|
102
|
+
bucket: row._id,
|
|
103
|
+
value: toNumber(row.value),
|
|
104
|
+
}));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async _aggregateDrained(pipeline) {
|
|
108
|
+
const first = await this.prisma.$runCommandRaw({
|
|
109
|
+
aggregate: COLLECTION,
|
|
110
|
+
pipeline,
|
|
111
|
+
cursor: { batchSize: DRAIN_BATCH_SIZE },
|
|
112
|
+
});
|
|
113
|
+
return drainCursor(this.prisma, COLLECTION, first);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** A `$match` window clause: prefix by granularity, optionally range-bounded. */
|
|
118
|
+
function windowMatch(bucket, { gte, lte } = {}) {
|
|
119
|
+
const clause = { $regex: `^${bucket}:` };
|
|
120
|
+
if (gte) clause.$gte = gte;
|
|
121
|
+
if (lte) clause.$lte = lte;
|
|
122
|
+
return clause;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Coerce an aggregate `$sum` result to a JS Number. Prisma $runCommandRaw returns
|
|
127
|
+
* extended JSON, so a 64-bit sum can arrive as { $numberLong: "..." } (or
|
|
128
|
+
* $numberInt/$numberDouble); counts never approach 2^53.
|
|
129
|
+
*/
|
|
130
|
+
function toNumber(value) {
|
|
131
|
+
if (value === null || value === undefined) return 0;
|
|
132
|
+
if (typeof value === 'number') return value;
|
|
133
|
+
if (typeof value === 'bigint') return Number(value);
|
|
134
|
+
if (typeof value === 'object') {
|
|
135
|
+
const raw =
|
|
136
|
+
value.$numberLong ?? value.$numberInt ?? value.$numberDouble;
|
|
137
|
+
if (raw !== undefined) return Number(raw);
|
|
138
|
+
}
|
|
139
|
+
return Number(value) || 0;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function drainCursor(client, collection, firstResult) {
|
|
143
|
+
const cursor = firstResult?.cursor || {};
|
|
144
|
+
const docs = [...(cursor.firstBatch || [])];
|
|
145
|
+
let cursorId = cursor.id;
|
|
146
|
+
let batches = 0;
|
|
147
|
+
|
|
148
|
+
while (isCursorOpen(cursorId) && batches < MAX_BATCHES) {
|
|
149
|
+
batches += 1;
|
|
150
|
+
const next = await client.$runCommandRaw({
|
|
151
|
+
getMore: cursorId,
|
|
152
|
+
collection,
|
|
153
|
+
batchSize: DRAIN_BATCH_SIZE,
|
|
154
|
+
});
|
|
155
|
+
const nextCursor = next?.cursor || {};
|
|
156
|
+
const nextBatch = nextCursor.nextBatch || [];
|
|
157
|
+
docs.push(...nextBatch);
|
|
158
|
+
cursorId = nextCursor.id;
|
|
159
|
+
if (nextBatch.length === 0) break;
|
|
160
|
+
}
|
|
161
|
+
return docs;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function isCursorOpen(id) {
|
|
165
|
+
if (id === undefined || id === null) return false;
|
|
166
|
+
if (typeof id === 'number') return id !== 0;
|
|
167
|
+
if (typeof id === 'bigint') return id !== 0n;
|
|
168
|
+
if (typeof id === 'object' && id.$numberLong !== undefined) {
|
|
169
|
+
return id.$numberLong !== '0';
|
|
170
|
+
}
|
|
171
|
+
return String(id) !== '0';
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function assertGroupBy(groupBy) {
|
|
175
|
+
if (!VALID_GROUP_BY.has(groupBy)) {
|
|
176
|
+
throw new Error(
|
|
177
|
+
`Invalid groupBy "${groupBy}". Allowed: ${[...VALID_GROUP_BY].join(
|
|
178
|
+
', '
|
|
179
|
+
)}`
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function assertBucket(bucket) {
|
|
185
|
+
if (!VALID_BUCKETS.has(bucket)) {
|
|
186
|
+
throw new Error(
|
|
187
|
+
`Invalid bucket "${bucket}". Allowed: ${[...VALID_BUCKETS].join(
|
|
188
|
+
', '
|
|
189
|
+
)}`
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
module.exports = { UsageRepositoryDocumentDB };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const { UsageRepositoryPrisma } = require('./usage-repository-prisma');
|
|
2
|
+
const { UsageRepositoryDocumentDB } = require('./usage-repository-documentdb');
|
|
3
|
+
const config = require('../../database/config');
|
|
4
|
+
|
|
5
|
+
function createUsageRepository() {
|
|
6
|
+
const dbType = config.DB_TYPE;
|
|
7
|
+
|
|
8
|
+
switch (dbType) {
|
|
9
|
+
case 'mongodb':
|
|
10
|
+
case 'postgresql':
|
|
11
|
+
return new UsageRepositoryPrisma();
|
|
12
|
+
case 'documentdb':
|
|
13
|
+
return new UsageRepositoryDocumentDB();
|
|
14
|
+
default:
|
|
15
|
+
throw new Error(
|
|
16
|
+
`Unsupported database type: ${dbType}. Supported values: 'mongodb', 'documentdb', 'postgresql'`
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = {
|
|
22
|
+
createUsageRepository,
|
|
23
|
+
UsageRepositoryPrisma,
|
|
24
|
+
UsageRepositoryDocumentDB,
|
|
25
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Port for the durable usage-counter store.
|
|
3
|
+
*
|
|
4
|
+
* The store is deliberately isolated (ADR-010 Decision 3): its fact rows carry
|
|
5
|
+
* NO userId and NO foreign key to Integration, so a user-scoped query can never
|
|
6
|
+
* return a usage row and usage history survives integration deletion.
|
|
7
|
+
*
|
|
8
|
+
* Fact row: { integrationId, integrationType, metric, window, value, updatedAt }
|
|
9
|
+
* Dimensions must be bounded — high-cardinality ids stay on traces.
|
|
10
|
+
*/
|
|
11
|
+
class UsageRepositoryInterface {
|
|
12
|
+
/**
|
|
13
|
+
* Atomically add `value` to the counter for
|
|
14
|
+
* (integrationId, integrationType, metric, window), inserting if absent.
|
|
15
|
+
*/
|
|
16
|
+
async increment(/* { integrationId, integrationType, metric, window, value } */) {
|
|
17
|
+
throw new Error('increment must be implemented by subclass');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Sum a metric grouped by a bounded dimension since an optional timestamp.
|
|
22
|
+
* @returns {Promise<Array<{[groupBy]: string, value: number}>>}
|
|
23
|
+
*/
|
|
24
|
+
async getTotalsByDimension(/* { metric, groupBy, since } */) {
|
|
25
|
+
throw new Error('getTotalsByDimension must be implemented by subclass');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Time series of a metric for one integration type.
|
|
30
|
+
* @returns {Promise<Array<{bucket: string, value: number}>>}
|
|
31
|
+
*/
|
|
32
|
+
async getTimeSeries(/* { metric, integrationType, from, to, bucket } */) {
|
|
33
|
+
throw new Error('getTimeSeries must be implemented by subclass');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
module.exports = { UsageRepositoryInterface };
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
const { prisma } = require('../../database/prisma');
|
|
2
|
+
const { UsageRepositoryInterface } = require('./usage-repository-interface');
|
|
3
|
+
const { windowKey } = require('../usage-windows');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Prisma-backed usage store — the single implementation for both mongodb and
|
|
7
|
+
* postgresql (Prisma abstracts the store; integrationId is a plain string in
|
|
8
|
+
* both). DocumentDB extends this but overrides the write/read ops with raw
|
|
9
|
+
* commands. All queries touch only the isolated `UsageCounter` model — never
|
|
10
|
+
* user/integration-scoped tables.
|
|
11
|
+
*/
|
|
12
|
+
class UsageRepositoryPrisma extends UsageRepositoryInterface {
|
|
13
|
+
constructor() {
|
|
14
|
+
super();
|
|
15
|
+
this.prisma = prisma;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async increment({
|
|
19
|
+
integrationId,
|
|
20
|
+
integrationType,
|
|
21
|
+
metric,
|
|
22
|
+
window,
|
|
23
|
+
value = 1,
|
|
24
|
+
}) {
|
|
25
|
+
const where = {
|
|
26
|
+
integrationId_integrationType_metric_window: {
|
|
27
|
+
integrationId,
|
|
28
|
+
integrationType,
|
|
29
|
+
metric,
|
|
30
|
+
window,
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
const upsertArgs = {
|
|
34
|
+
where,
|
|
35
|
+
create: { integrationId, integrationType, metric, window, value },
|
|
36
|
+
update: { value: { increment: value } },
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// Concurrent first-insert race: two workers both INSERT and one hits the
|
|
40
|
+
// unique constraint (P2002). After the winner's insert the row exists, so
|
|
41
|
+
// a retry takes the atomic UPDATE (increment) path. Bounded so a
|
|
42
|
+
// pathological repeated race can't throw unexpectedly out of the public
|
|
43
|
+
// `recordUsageCounter` write; non-conflict errors surface immediately.
|
|
44
|
+
for (let attempt = 1; ; attempt++) {
|
|
45
|
+
try {
|
|
46
|
+
await this.prisma.usageCounter.upsert(upsertArgs);
|
|
47
|
+
return;
|
|
48
|
+
} catch (err) {
|
|
49
|
+
if (err && err.code === 'P2002' && attempt < MAX_UPSERT_ATTEMPTS) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
throw err;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async getTotalsByDimension({
|
|
58
|
+
metric,
|
|
59
|
+
groupBy = 'integrationType',
|
|
60
|
+
since,
|
|
61
|
+
bucket = 'day',
|
|
62
|
+
} = {}) {
|
|
63
|
+
if (!metric) {
|
|
64
|
+
throw new Error('getTotalsByDimension requires a metric (units are per-metric)');
|
|
65
|
+
}
|
|
66
|
+
assertGroupBy(groupBy);
|
|
67
|
+
assertBucket(bucket);
|
|
68
|
+
|
|
69
|
+
// Filter to ONE window granularity — every event is written to both a
|
|
70
|
+
// day: and an hour: row, so summing across granularities would double
|
|
71
|
+
// (or worse) the true count.
|
|
72
|
+
// Bound `since` on the WINDOW key (mirrors getTimeSeries) — write-time
|
|
73
|
+
// updatedAt would misplace a late/redelivered increment for an earlier
|
|
74
|
+
// window, over- or under-counting the time-bounded total.
|
|
75
|
+
const where = { metric, window: { startsWith: `${bucket}:` } };
|
|
76
|
+
if (since) where.window.gte = windowKey(bucket, since);
|
|
77
|
+
|
|
78
|
+
const groups = await this.prisma.usageCounter.groupBy({
|
|
79
|
+
by: [groupBy],
|
|
80
|
+
where,
|
|
81
|
+
_sum: { value: true },
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// value is a BigInt column — coerce the sum to a JSON-safe Number
|
|
85
|
+
// (JSON.stringify throws on BigInt; counts never approach 2^53).
|
|
86
|
+
return groups.map((group) => ({
|
|
87
|
+
[groupBy]: group[groupBy],
|
|
88
|
+
value: Number(group._sum?.value ?? 0),
|
|
89
|
+
}));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async getTimeSeries({ metric, integrationType, from, to, bucket = 'day' } = {}) {
|
|
93
|
+
assertBucket(bucket);
|
|
94
|
+
if (!integrationType) {
|
|
95
|
+
throw new Error('getTimeSeries requires an integrationType');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Range-filter on the WINDOW key (write-time `updatedAt` would misplace a
|
|
99
|
+
// late increment for an earlier window). Window keys are ISO-lexicographic
|
|
100
|
+
// within a granularity, so string gte/lte gives the correct range.
|
|
101
|
+
const window = { startsWith: `${bucket}:` };
|
|
102
|
+
if (from) window.gte = windowKey(bucket, from);
|
|
103
|
+
if (to) window.lte = windowKey(bucket, to);
|
|
104
|
+
|
|
105
|
+
// Aggregate ACROSS integration instances: there is one row per
|
|
106
|
+
// (integrationId, integrationType, metric, window), so a type with many
|
|
107
|
+
// instances has many rows per window — sum them into one point.
|
|
108
|
+
const groups = await this.prisma.usageCounter.groupBy({
|
|
109
|
+
by: ['window'],
|
|
110
|
+
where: { metric, integrationType, window },
|
|
111
|
+
_sum: { value: true },
|
|
112
|
+
orderBy: { window: 'asc' },
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
return groups.map((group) => ({
|
|
116
|
+
bucket: group.window,
|
|
117
|
+
value: Number(group._sum?.value ?? 0),
|
|
118
|
+
}));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const MAX_UPSERT_ATTEMPTS = 3;
|
|
123
|
+
const VALID_GROUP_BY = new Set(['integrationType', 'metric']);
|
|
124
|
+
const VALID_BUCKETS = new Set(['day', 'hour']);
|
|
125
|
+
|
|
126
|
+
function assertGroupBy(groupBy) {
|
|
127
|
+
if (!VALID_GROUP_BY.has(groupBy)) {
|
|
128
|
+
throw new Error(
|
|
129
|
+
`Invalid groupBy "${groupBy}". Allowed: ${[...VALID_GROUP_BY].join(
|
|
130
|
+
', '
|
|
131
|
+
)}`
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function assertBucket(bucket) {
|
|
137
|
+
if (!VALID_BUCKETS.has(bucket)) {
|
|
138
|
+
throw new Error(
|
|
139
|
+
`Invalid bucket "${bucket}". Allowed: ${[...VALID_BUCKETS].join(
|
|
140
|
+
', '
|
|
141
|
+
)}`
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
module.exports = { UsageRepositoryPrisma };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const { isCanonicalCounter } = require('../telemetry/canonical-counters');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Compute the set of usage-counter keys the rollup should persist, from each
|
|
5
|
+
* integration's `Definition.usage` opt-in.
|
|
6
|
+
* Declaring a canonical key opts into cross-type comparison + the rollup; custom
|
|
7
|
+
* keys are tracked per integration type. Unknown canonical keys are dropped with
|
|
8
|
+
* a warning (lazy validation, matching how Definition is treated elsewhere).
|
|
9
|
+
*/
|
|
10
|
+
function computeTrackedMetrics(integrationClasses = []) {
|
|
11
|
+
const tracked = new Set();
|
|
12
|
+
if (!Array.isArray(integrationClasses)) return tracked;
|
|
13
|
+
|
|
14
|
+
for (const IntegrationClass of integrationClasses) {
|
|
15
|
+
const usage = IntegrationClass?.Definition?.usage;
|
|
16
|
+
if (!usage) continue;
|
|
17
|
+
const name = IntegrationClass.Definition?.name || 'integration';
|
|
18
|
+
|
|
19
|
+
for (const key of usage.canonical || []) {
|
|
20
|
+
if (!isCanonicalCounter(key)) {
|
|
21
|
+
console.warn(
|
|
22
|
+
`[Frigg][usage] "${name}" declares unknown canonical counter "${key}" — ignored. ` +
|
|
23
|
+
`Use a key from CANONICAL_COUNTERS or declare it under usage.custom.`
|
|
24
|
+
);
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
tracked.add(key);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
for (const key of Object.keys(usage.custom || {})) {
|
|
31
|
+
tracked.add(key);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return tracked;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = { computeTrackedMetrics };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rollup window keys. A window key is `<granularity>:<UTC-truncated-timestamp>`
|
|
3
|
+
* (e.g. `day:2026-07-05`, `hour:2026-07-05T14`), prefixed by granularity so the
|
|
4
|
+
* store can filter a series by bucket (`window startsWith 'day:'`) and range on
|
|
5
|
+
* the ISO-lexicographic keys. Shared by the rollup writer and the read
|
|
6
|
+
* repositories so the write/read formats can never drift.
|
|
7
|
+
*/
|
|
8
|
+
function windowKey(bucket, date = new Date()) {
|
|
9
|
+
const iso = new Date(date).toISOString();
|
|
10
|
+
return `${bucket}:${
|
|
11
|
+
bucket === 'hour' ? iso.slice(0, 13) : iso.slice(0, 10)
|
|
12
|
+
}`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The window keys a usage event falls into — one per granularity.
|
|
17
|
+
* @param {Date} [date] Defaults to now.
|
|
18
|
+
* @returns {string[]} e.g. ['day:2026-07-05', 'hour:2026-07-05T14']
|
|
19
|
+
*/
|
|
20
|
+
function computeUsageWindows(date = new Date()) {
|
|
21
|
+
return [windowKey('day', date), windowKey('hour', date)];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = { computeUsageWindows, windowKey };
|