@friggframework/core 2.0.0-next.11 → 2.0.0-next.110
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/CLAUDE.md +702 -0
- package/README.md +999 -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 +336 -0
- package/application/commands/integration-commands.js +356 -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 +700 -0
- package/core/Worker.js +60 -24
- package/core/create-handler.js +189 -5
- package/core/parameters-to-env.js +257 -0
- 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 +154 -0
- package/database/documentdb-encryption-service.js +330 -0
- package/database/documentdb-utils.js +192 -0
- package/database/encryption/README.md +839 -0
- package/database/encryption/documentdb-encryption-service.md +3575 -0
- package/database/encryption/encryption-schema-registry.js +401 -0
- package/database/encryption/field-encryption-service.js +254 -0
- package/database/encryption/logger.js +79 -0
- package/database/encryption/prisma-encryption-extension.js +230 -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 +15 -7
- package/errors/index.js +2 -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 +653 -0
- package/handlers/app-definition-loader.js +67 -0
- package/handlers/app-handler-helpers.js +3 -3
- package/handlers/backend-utils.js +271 -42
- package/handlers/database-migration-handler.js +227 -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 +80 -0
- package/handlers/routers/user.js +27 -5
- package/handlers/routers/websocket.js +5 -3
- 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 +427 -0
- package/handlers/workers/dlq-processor.js +63 -0
- package/handlers/workers/integration-defined-workers.js +13 -7
- package/index.js +102 -46
- package/infrastructure/scheduler/eventbridge-scheduler-adapter.js +184 -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 +730 -74
- package/integrations/integration-router.js +398 -180
- package/integrations/options.js +1 -1
- package/integrations/repositories/config-patch-shared.js +43 -0
- package/integrations/repositories/integration-mapping-repository-documentdb.js +303 -0
- package/integrations/repositories/integration-mapping-repository-factory.js +57 -0
- package/integrations/repositories/integration-mapping-repository-interface.js +120 -0
- package/integrations/repositories/integration-mapping-repository-mongo.js +183 -0
- package/integrations/repositories/integration-mapping-repository-postgres.js +255 -0
- package/integrations/repositories/integration-mapping-repository.js +156 -0
- package/integrations/repositories/integration-repository-documentdb.js +322 -0
- package/integrations/repositories/integration-repository-factory.js +51 -0
- package/integrations/repositories/integration-repository-interface.js +189 -0
- package/integrations/repositories/integration-repository-mongo.js +450 -0
- package/integrations/repositories/integration-repository-postgres.js +492 -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/create-integration.js +215 -0
- package/integrations/use-cases/create-process.js +130 -0
- package/integrations/use-cases/delete-integration-for-user.js +119 -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/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/map-integration-dto.js +37 -0
- package/jest-global-setup-noop.js +3 -0
- package/jest-global-teardown-noop.js +3 -0
- package/logs/logger.js +0 -4
- package/{module-plugin → modules}/index.js +0 -10
- package/modules/module-factory.js +56 -0
- package/modules/module.js +307 -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 +555 -0
- package/modules/requester/requester.js +542 -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 +243 -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 +18 -0
- package/package.json +92 -50
- 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/queuer-util.js +103 -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 +73 -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 +87 -0
- package/telemetry/no-op-telemetry.js +67 -0
- package/telemetry/north-star.js +103 -0
- package/telemetry/otel-telemetry.js +213 -0
- package/telemetry/plugin-subscribers.js +77 -0
- package/telemetry/telemetry-config.js +120 -0
- package/telemetry/telemetry-context.js +40 -0
- package/telemetry/telemetry-event-bus.js +58 -0
- package/telemetry/telemetry-runtime.js +147 -0
- package/telemetry/telemetry-service.js +51 -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/integrations/index.d.ts +3 -8
- 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 +458 -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 +127 -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 +77 -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
|
@@ -1,75 +1,26 @@
|
|
|
1
1
|
declare module "@friggframework/module-plugin" {
|
|
2
|
-
import { Model } from "mongoose";
|
|
3
2
|
import { Delegate, IFriggDelegate } from "@friggframework/core";
|
|
4
3
|
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
externalId
|
|
4
|
+
export interface Credential {
|
|
5
|
+
id?: string;
|
|
6
|
+
userId?: string;
|
|
7
|
+
authIsValid?: boolean;
|
|
8
|
+
externalId?: string;
|
|
9
|
+
data?: any;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
export
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
static getEntityManagerInstanceFromEntityId(
|
|
21
|
-
entityId: string,
|
|
22
|
-
userId: string
|
|
23
|
-
): Promise<any>;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
interface IFriggEntityManager {}
|
|
27
|
-
|
|
28
|
-
export class Entity extends Model {
|
|
29
|
-
credentialId: string;
|
|
30
|
-
subType: string;
|
|
31
|
-
userId: string;
|
|
32
|
-
name: string;
|
|
33
|
-
externalId: string;
|
|
12
|
+
export interface Entity {
|
|
13
|
+
id?: string;
|
|
14
|
+
credentialId?: string;
|
|
15
|
+
userId?: string;
|
|
16
|
+
name?: string;
|
|
17
|
+
moduleName?: string;
|
|
18
|
+
externalId?: string;
|
|
19
|
+
data?: any;
|
|
34
20
|
}
|
|
35
21
|
|
|
36
22
|
export type MappedEntity = Entity & { id: string; type: any };
|
|
37
|
-
export class ModuleManager extends Delegate implements IFriggModuleManager {
|
|
38
|
-
static Entity: Entity;
|
|
39
|
-
static Credential: Credential;
|
|
40
23
|
|
|
41
|
-
constructor(params: { userId: string });
|
|
42
|
-
|
|
43
|
-
static getName(): any;
|
|
44
|
-
static getInstance(params: any): Promise<any>;
|
|
45
|
-
static getEntitiesForUserId(userId: string): Promise<MappedEntity[]>;
|
|
46
|
-
|
|
47
|
-
batchCreateSyncObjects(syncObjects: any, syncManager: any): Promise<any>;
|
|
48
|
-
batchUpdateSyncObjects(syncObjects: any, syncManager: any): Promise<any>;
|
|
49
|
-
findOrCreateEntity(params: any): Promise<any>;
|
|
50
|
-
getAllSyncObjects(SyncClass: any): Promise<any>;
|
|
51
|
-
getAuthorizationRequirements(params: any): Promise<any>;
|
|
52
|
-
getEntityId(): Promise<string>;
|
|
53
|
-
getEntityOptions(): Promise<any>;
|
|
54
|
-
markCredentialsInvalid(): Promise<Credential>;
|
|
55
|
-
processAuthorizationCallback(params: any): Promise<any>;
|
|
56
|
-
testAuth(params: any): Promise<any>;
|
|
57
|
-
validateAuthorizationRequirements(): Promise<boolean>;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
interface IFriggModuleManager extends IFriggDelegate {
|
|
61
|
-
getEntityId(): Promise<string>;
|
|
62
|
-
validateAuthorizationRequirements(): Promise<boolean>;
|
|
63
|
-
getAuthorizationRequirements(params: any): Promise<any>;
|
|
64
|
-
testAuth(params: any): Promise<any>;
|
|
65
|
-
processAuthorizationCallback(params: any): Promise<any>;
|
|
66
|
-
getEntityOptions(): Promise<any>;
|
|
67
|
-
findOrCreateEntity(params: any): Promise<any>;
|
|
68
|
-
getAllSyncObjects(SyncClass: any): Promise<any>;
|
|
69
|
-
batchCreateSyncObjects(syncObjects: any, syncManager: any): Promise<any>;
|
|
70
|
-
batchUpdateSyncObjects(syncObjects: any, syncManager: any): Promise<any>;
|
|
71
|
-
markCredentialsInvalid(): Promise<Credential>;
|
|
72
|
-
}
|
|
73
24
|
|
|
74
25
|
export class Requester implements IFriggRequester {
|
|
75
26
|
DLGT_INVALID_AUTH: string;
|
|
@@ -90,6 +41,7 @@ declare module "@friggframework/module-plugin" {
|
|
|
90
41
|
): Promise<any>;
|
|
91
42
|
parseBody(response: any): Promise<any>;
|
|
92
43
|
refreshAuth(): Promise<any>;
|
|
44
|
+
_adoptNewerCredential(): Promise<boolean>;
|
|
93
45
|
|
|
94
46
|
delegate: any;
|
|
95
47
|
delegateTypes: any[];
|
|
@@ -121,6 +73,7 @@ declare module "@friggframework/module-plugin" {
|
|
|
121
73
|
_put(options: RequestOptions): Promise<any>;
|
|
122
74
|
_delete(options: RequestOptions): Promise<any>;
|
|
123
75
|
refreshAuth(): Promise<any>;
|
|
76
|
+
_adoptNewerCredential(): Promise<boolean>;
|
|
124
77
|
}
|
|
125
78
|
|
|
126
79
|
type RequestOptions = {
|
|
@@ -138,8 +91,7 @@ declare module "@friggframework/module-plugin" {
|
|
|
138
91
|
|
|
139
92
|
export class ApiKeyRequester
|
|
140
93
|
extends Requester
|
|
141
|
-
implements IFriggApiKeyRequester
|
|
142
|
-
{
|
|
94
|
+
implements IFriggApiKeyRequester {
|
|
143
95
|
API_KEY_NAME: string;
|
|
144
96
|
API_KEY_VALUE: any;
|
|
145
97
|
|
|
@@ -160,8 +112,7 @@ declare module "@friggframework/module-plugin" {
|
|
|
160
112
|
|
|
161
113
|
export class BasicAuthRequester
|
|
162
114
|
extends Requester
|
|
163
|
-
implements IFriggBasicAuthRequester
|
|
164
|
-
{
|
|
115
|
+
implements IFriggBasicAuthRequester {
|
|
165
116
|
password: string;
|
|
166
117
|
username: string;
|
|
167
118
|
|
|
@@ -189,8 +140,7 @@ declare module "@friggframework/module-plugin" {
|
|
|
189
140
|
|
|
190
141
|
export class OAuth2Requester
|
|
191
142
|
extends Requester
|
|
192
|
-
implements IFriggOAuth2Requester
|
|
193
|
-
{
|
|
143
|
+
implements IFriggOAuth2Requester {
|
|
194
144
|
DLGT_TOKEN_DEAUTHORIZED: string;
|
|
195
145
|
DLGT_TOKEN_UPDATE: string;
|
|
196
146
|
accessTokenExpire: any;
|
|
@@ -253,6 +203,7 @@ declare module "@friggframework/module-plugin" {
|
|
|
253
203
|
addAuthHeaders(headers: object): Promise<object>;
|
|
254
204
|
isAuthenticated(): boolean;
|
|
255
205
|
refreshAuth(): Promise<void>;
|
|
206
|
+
_adoptNewerCredential(): Promise<boolean>;
|
|
256
207
|
getTokenFromUsernamePassword(): Promise<Token>;
|
|
257
208
|
getTokenFromClientCredentials(): Promise<Token>;
|
|
258
209
|
}
|
package/types/syncs/index.d.ts
CHANGED
|
@@ -1,18 +1,3 @@
|
|
|
1
|
-
declare module "@friggframework/syncs/model" {
|
|
2
|
-
import { Model } from "mongoose";
|
|
3
|
-
|
|
4
|
-
export class Sync extends Model {
|
|
5
|
-
entities: any[];
|
|
6
|
-
hash: string;
|
|
7
|
-
name: string;
|
|
8
|
-
dataIdentifiers: {
|
|
9
|
-
entity: any;
|
|
10
|
-
id: object;
|
|
11
|
-
hash: string;
|
|
12
|
-
}[];
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
1
|
declare module "@friggframework/syncs/manager" {
|
|
17
2
|
import Sync from "@friggframework/syncs/sync";
|
|
18
3
|
|
|
@@ -28,7 +13,6 @@ declare module "@friggframework/syncs/manager" {
|
|
|
28
13
|
confirmCreate(
|
|
29
14
|
syncObj: Sync,
|
|
30
15
|
createdId: string,
|
|
31
|
-
moduleManager: any
|
|
32
16
|
): Promise<any>;
|
|
33
17
|
confirmUpdate(syncObj: Sync): Promise<any>;
|
|
34
18
|
createSyncDBObject(objArr: any[], entities: any[]): Promise<any>;
|
|
@@ -50,7 +34,6 @@ declare module "@friggframework/syncs/manager" {
|
|
|
50
34
|
confirmCreate(
|
|
51
35
|
syncObj: Sync,
|
|
52
36
|
createdId: string,
|
|
53
|
-
moduleManager: any
|
|
54
37
|
): Promise<any>;
|
|
55
38
|
confirmUpdate(syncObj: Sync): Promise<any>;
|
|
56
39
|
}
|
package/usage/README.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Usage Store
|
|
2
|
+
|
|
3
|
+
The durable, Frigg-owned store behind ADR-011 feature-usage tracking. Holds
|
|
4
|
+
per-integration counters that the reporting endpoint reads for cross-integration
|
|
5
|
+
comparison and trend series.
|
|
6
|
+
|
|
7
|
+
Populated by the telemetry usage rollup and read via `frigg.usage.*` — see
|
|
8
|
+
[`telemetry/README.md`](../telemetry/README.md) for the full guide, config, and
|
|
9
|
+
usage examples. This README covers the store internals only.
|
|
10
|
+
|
|
11
|
+
## Fact row
|
|
12
|
+
|
|
13
|
+
`UsageCounter { integrationId, integrationType, metric, window, value, updatedAt }`,
|
|
14
|
+
uniquely keyed by `(integrationId, integrationType, metric, window)`. `window` is
|
|
15
|
+
`day:YYYY-MM-DD` or `hour:YYYY-MM-DDTHH` (UTC).
|
|
16
|
+
|
|
17
|
+
## Isolation (ADR-010 Decision 3)
|
|
18
|
+
|
|
19
|
+
The store is deliberately isolated from user/integration-scoped data:
|
|
20
|
+
|
|
21
|
+
- **No `userId`** and **no foreign key** to `Integration` — a user-scoped query
|
|
22
|
+
can never return a usage row, and usage history survives integration deletion.
|
|
23
|
+
- **Not** in the encryption registry — dimensions are bounded and non-sensitive.
|
|
24
|
+
|
|
25
|
+
## Repository triad
|
|
26
|
+
|
|
27
|
+
Mirrors the reporting/process pattern — an interface plus PostgreSQL, MongoDB and
|
|
28
|
+
DocumentDB adapters selected by `DB_TYPE`:
|
|
29
|
+
|
|
30
|
+
```js
|
|
31
|
+
const { createUsageRepository } = require('@friggframework/core'); // usage-repository-factory
|
|
32
|
+
|
|
33
|
+
class UsageRepositoryInterface {
|
|
34
|
+
async increment({ integrationId, integrationType, metric, window, value }) {} // atomic upsert
|
|
35
|
+
async getTotalsByDimension({ metric, groupBy, since, bucket }) {} // comparison (one window granularity)
|
|
36
|
+
async getTimeSeries({ metric, integrationType, from, to, bucket }) {} // trend (aggregated across instances)
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
- **`increment`** is atomic: PostgreSQL uses Prisma `upsert` with
|
|
41
|
+
`{ value: { increment } }`; a concurrent first-insert race (`P2002`) retries
|
|
42
|
+
once onto the atomic update path.
|
|
43
|
+
- **`getTotalsByDimension`** filters to a single window granularity (default `day`) so day and
|
|
44
|
+
hour rows are never double-summed. Requires a `metric`; `groupBy` is
|
|
45
|
+
allow-listed to `integrationType` / `metric`.
|
|
46
|
+
- **`getTimeSeries`** aggregates across integration instances (`groupBy(window) + sum`)
|
|
47
|
+
and range-filters on the window key. Requires an `integrationType`.
|
|
48
|
+
|
|
49
|
+
> **DocumentDB:** the adapter overrides increment/getTotalsByDimension/getTimeSeries with raw commands
|
|
50
|
+
> (`$runCommandRaw`: a `$inc` upsert via `documentdb-utils.updateOne`, and a
|
|
51
|
+
> cursor-drained `$aggregate` `$group/$sum`) — matching every other DocumentDB
|
|
52
|
+
> adapter, since Prisma's Mongo engine emits upsert/groupBy shapes DocumentDB
|
|
53
|
+
> rejects and cursor reads truncate at ~101 docs. Command shapes are unit-tested;
|
|
54
|
+
> run an end-to-end check against a real cluster before GA.
|
package/usage/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const {
|
|
2
|
+
createUsageRepository,
|
|
3
|
+
UsageRepositoryPrisma,
|
|
4
|
+
UsageRepositoryDocumentDB,
|
|
5
|
+
} = require('./repositories/usage-repository-factory');
|
|
6
|
+
const {
|
|
7
|
+
UsageRepositoryInterface,
|
|
8
|
+
} = require('./repositories/usage-repository-interface');
|
|
9
|
+
const { computeTrackedMetrics } = require('./tracked-metrics');
|
|
10
|
+
|
|
11
|
+
module.exports = {
|
|
12
|
+
createUsageRepository,
|
|
13
|
+
computeTrackedMetrics,
|
|
14
|
+
UsageRepositoryInterface,
|
|
15
|
+
UsageRepositoryPrisma,
|
|
16
|
+
UsageRepositoryDocumentDB,
|
|
17
|
+
};
|
|
@@ -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 };
|