@flowdular/sdk 0.2.4 → 0.3.1
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/.ai/agents/sandbox/business-manager.md +5 -1
- package/.ai/blueprints/author-spec/README.md +1 -1
- package/.ai/blueprints/author-spec/spec-requirements.yaml +44 -0
- package/.ai/blueprints/author-spec/steps.yaml +5 -5
- package/.ai/blueprints/author-spec/templates/module.yaml +99 -3
- package/.ai/blueprints/edit-module/gates.yaml +4 -0
- package/.ai/blueprints/edit-module/required-files.yaml +9 -0
- package/.ai/blueprints/new-module/gates.yaml +4 -0
- package/.ai/blueprints/new-module/spec-requirements.yaml +2 -2
- package/.ai/blueprints/release/gates.yaml +4 -0
- package/.ai/platform-capabilities.md +128 -0
- package/.ai/policies/capabilities.yaml +130 -3
- package/.ai/policies/path-ownership.yaml +5 -2
- package/.ai/policies/task-budgets.yaml +5 -3
- package/.ai/references/catalog/module.json +4 -4
- package/.ai/references/catalog/package.json +2 -2
- package/.ai/references/catalog/spec/module.yaml +5 -3
- package/.ai/references/catalog/src/platform.ts +2 -0
- package/.ai/references/catalog/src/services/catalog-service.ts +89 -1
- package/.ai/references/catalog/src/services/data-classes.ts +47 -0
- package/.ai/references/catalog/src/services/database-repository.ts +98 -1
- package/.ai/references/catalog/src/services/repository.ts +22 -1
- package/.ai/references/catalog/tests/data-classes.test.ts +157 -0
- package/.ai/references/catalog.provenance.json +12 -10
- package/.ai/skills/README.md +10 -0
- package/.ai/skills/agent-tool-design/SKILL.md +1 -2
- package/.ai/skills/auth-security-review/SKILL.md +1 -1
- package/.ai/skills/business-agent-design/SKILL.md +0 -1
- package/.ai/skills/cli-extension/SKILL.md +1 -1
- package/.ai/skills/deploy-operate/SKILL.md +119 -0
- package/.ai/skills/module-new/SKILL.md +29 -3
- package/.ai/skills/module-update/SKILL.md +9 -3
- package/.ai/skills/perf-audit/SKILL.md +0 -1
- package/.ai/skills/release-eject-pr/SKILL.md +0 -1
- package/.ai/skills/spec-interview/SKILL.md +120 -0
- package/.ai/skills/test-hardening/SKILL.md +1 -0
- package/.ai/skills/ux-design/SKILL.md +34 -3
- package/.ai/skills/variables/SKILL.md +0 -2
- package/.ai/skills/workflow-development/SKILL.md +0 -1
- package/AGENTS.md +4 -0
- package/docs/agent-contract.md +2 -2
- package/docs/design-system.md +185 -31
- package/modules/access/migrations/0001_access_core.down.sql +1 -0
- package/modules/access/migrations/0001_access_core.up.sql +28 -0
- package/modules/access/migrations/README.md +3 -0
- package/modules/access/module.json +33 -0
- package/modules/access/package.json +43 -0
- package/modules/access/spec/module.yaml +225 -0
- package/modules/access/src/acl/permissions.ts +6 -0
- package/modules/access/src/api/endpoints.ts +244 -0
- package/modules/access/src/client/ActivityView.tsrx +245 -0
- package/modules/access/src/client/AttestationsView.tsrx +176 -0
- package/modules/access/src/client/ReviewView.tsrx +586 -0
- package/modules/access/src/client/api.ts +132 -0
- package/modules/access/src/client/contribution.tsrx +41 -0
- package/modules/access/src/client/index.ts +41 -0
- package/modules/access/src/client/navigation.ts +57 -0
- package/modules/access/src/client/presentation.ts +69 -0
- package/modules/access/src/client/state.ts +72 -0
- package/modules/access/src/domain/types.ts +189 -0
- package/modules/access/src/index.ts +38 -0
- package/modules/access/src/platform.ts +60 -0
- package/modules/access/src/server/index.ts +42 -0
- package/modules/access/src/server/runtime.ts +105 -0
- package/modules/access/src/services/access-service.ts +298 -0
- package/modules/access/src/services/audit-window.ts +78 -0
- package/modules/access/src/services/auth-directory.ts +72 -0
- package/modules/access/src/services/changes.ts +91 -0
- package/modules/access/src/services/data-classes.ts +28 -0
- package/modules/access/src/services/database-repository.ts +185 -0
- package/modules/access/src/services/directory.ts +102 -0
- package/modules/access/src/services/index.ts +33 -0
- package/modules/access/src/services/list-exports.ts +150 -0
- package/modules/access/src/services/migration.ts +54 -0
- package/modules/access/src/services/repository.ts +30 -0
- package/modules/access/src/services/review.ts +178 -0
- package/modules/access/translations/en.json +155 -0
- package/modules/access/translations/pl.json +155 -0
- package/modules/agents/migrations/0022_credential_rotation_inventory.down.sql +2 -0
- package/modules/agents/migrations/0022_credential_rotation_inventory.up.sql +7 -0
- package/modules/agents/migrations/0023_agents_retention_indexes.down.sql +2 -0
- package/modules/agents/migrations/0023_agents_retention_indexes.up.sql +9 -0
- package/modules/agents/migrations/0024_agent_meter_refusals.down.sql +3 -0
- package/modules/agents/migrations/0024_agent_meter_refusals.up.sql +22 -0
- package/modules/agents/migrations/0025_agent_list_indexes.down.sql +3 -0
- package/modules/agents/migrations/0025_agent_list_indexes.up.sql +11 -0
- package/modules/agents/module.json +30 -4
- package/modules/agents/package.json +2 -1
- package/modules/agents/spec/module.yaml +63 -5
- package/modules/agents/src/api/endpoints.ts +291 -33
- package/modules/agents/src/cli/commands.json +11 -0
- package/modules/agents/src/cli/index.ts +45 -0
- package/modules/agents/src/client/AgentDashboardWidget.tsrx +19 -11
- package/modules/agents/src/client/AgentDefinitionList.tsrx +53 -10
- package/modules/agents/src/client/AgentPlaygroundView.tsrx +4 -4
- package/modules/agents/src/client/AgentProceduresView.tsrx +2 -2
- package/modules/agents/src/client/AgentRunsView.tsrx +143 -40
- package/modules/agents/src/client/AgentsView.tsrx +109 -33
- package/modules/agents/src/client/api.ts +99 -21
- package/modules/agents/src/client/state.ts +97 -0
- package/modules/agents/src/domain/types.ts +44 -4
- package/modules/agents/src/platform.ts +42 -0
- package/modules/agents/src/server/action-execution.ts +201 -106
- package/modules/agents/src/server/runtime.ts +32 -0
- package/modules/agents/src/services/agent-service.ts +158 -30
- package/modules/agents/src/services/credential-rotation.ts +185 -0
- package/modules/agents/src/services/credential-vault.ts +71 -46
- package/modules/agents/src/services/data-classes.ts +176 -0
- package/modules/agents/src/services/database-repository.ts +393 -101
- package/modules/agents/src/services/metering.ts +111 -0
- package/modules/agents/src/services/migration.ts +109 -0
- package/modules/agents/src/services/module-agent-preflight.ts +17 -9
- package/modules/agents/src/services/notifications.ts +98 -0
- package/modules/agents/src/services/provider-repository.ts +9 -18
- package/modules/agents/src/services/provider-service.ts +1 -9
- package/modules/agents/src/services/reports.ts +107 -0
- package/modules/agents/src/services/repository.ts +86 -6
- package/modules/agents/src/services/usage-service.ts +2 -0
- package/modules/agents/src/services/worker.ts +114 -4
- package/modules/agents/src/settings.ts +14 -3
- package/modules/agents/translations/en.json +14 -4
- package/modules/agents/translations/pl.json +14 -4
- package/modules/approvals/migrations/0001_approvals_core.down.sql +3 -0
- package/modules/approvals/migrations/0001_approvals_core.up.sql +78 -0
- package/modules/approvals/migrations/0002_approvals_expiry_routing_role.down.sql +2 -0
- package/modules/approvals/migrations/0002_approvals_expiry_routing_role.up.sql +19 -0
- package/modules/approvals/migrations/0003_approvals_retention_indexes.down.sql +6 -0
- package/modules/approvals/migrations/0003_approvals_retention_indexes.up.sql +14 -0
- package/modules/approvals/migrations/0004_approvals_inbox_keyset_indexes.down.sql +5 -0
- package/modules/approvals/migrations/0004_approvals_inbox_keyset_indexes.up.sql +12 -0
- package/modules/approvals/migrations/0005_approvals_grant_audit.down.sql +4 -0
- package/modules/approvals/migrations/0005_approvals_grant_audit.up.sql +20 -0
- package/modules/approvals/migrations/README.md +3 -0
- package/modules/approvals/module.json +34 -0
- package/modules/approvals/package.json +43 -0
- package/modules/approvals/spec/module.yaml +327 -0
- package/modules/approvals/src/acl/permissions.ts +7 -0
- package/modules/approvals/src/api/endpoints.ts +457 -0
- package/modules/approvals/src/client/ApprovalRequestDrawer.tsrx +138 -0
- package/modules/approvals/src/client/ApprovalsView.tsrx +562 -0
- package/modules/approvals/src/client/PendingCountWidget.tsrx +52 -0
- package/modules/approvals/src/client/api.ts +203 -0
- package/modules/approvals/src/client/approvals.css +51 -0
- package/modules/approvals/src/client/contribution.tsrx +44 -0
- package/modules/approvals/src/client/index.ts +26 -0
- package/modules/approvals/src/client/navigation.ts +39 -0
- package/modules/approvals/src/client/pending-count.ts +26 -0
- package/modules/approvals/src/client/presentation.ts +52 -0
- package/modules/approvals/src/client/state.ts +141 -0
- package/modules/approvals/src/domain/capability.ts +107 -0
- package/modules/approvals/src/domain/grant.ts +69 -0
- package/modules/approvals/src/domain/types.ts +168 -0
- package/modules/approvals/src/index.ts +61 -0
- package/modules/approvals/src/platform.ts +136 -0
- package/modules/approvals/src/server/index.ts +42 -0
- package/modules/approvals/src/server/runtime.ts +185 -0
- package/modules/approvals/src/services/approvals-service.ts +637 -0
- package/modules/approvals/src/services/callbacks.ts +75 -0
- package/modules/approvals/src/services/data-classes.ts +167 -0
- package/modules/approvals/src/services/database-repository.ts +783 -0
- package/modules/approvals/src/services/eligibility.ts +121 -0
- package/modules/approvals/src/services/expiry-runner.ts +81 -0
- package/modules/approvals/src/services/index.ts +31 -0
- package/modules/approvals/src/services/migration.ts +256 -0
- package/modules/approvals/src/services/notifications.ts +95 -0
- package/modules/approvals/src/services/repository.ts +169 -0
- package/modules/approvals/src/services/service-error.ts +70 -0
- package/modules/approvals/src/settings.ts +102 -0
- package/modules/approvals/translations/en.json +104 -0
- package/modules/approvals/translations/pl.json +104 -0
- package/modules/audit/migrations/0001_audit_core.down.sql +8 -0
- package/modules/audit/migrations/0001_audit_core.up.sql +107 -0
- package/modules/audit/migrations/0002_audit_sweep_routing_role.down.sql +2 -0
- package/modules/audit/migrations/0002_audit_sweep_routing_role.up.sql +21 -0
- package/modules/audit/migrations/0003_audit_export_request.down.sql +12 -0
- package/modules/audit/migrations/0003_audit_export_request.up.sql +40 -0
- package/modules/audit/migrations/0004_audit_hold_seal_erasure.down.sql +17 -0
- package/modules/audit/migrations/0004_audit_hold_seal_erasure.up.sql +192 -0
- package/modules/audit/migrations/0005_audit_event_format.down.sql +17 -0
- package/modules/audit/migrations/0005_audit_event_format.up.sql +60 -0
- package/modules/audit/migrations/0006_audit_own_class_walks.down.sql +3 -0
- package/modules/audit/migrations/0006_audit_own_class_walks.up.sql +14 -0
- package/modules/audit/migrations/README.md +3 -0
- package/modules/audit/module.json +32 -0
- package/modules/audit/package.json +49 -0
- package/modules/audit/spec/module.yaml +809 -0
- package/modules/audit/src/acl/permissions.ts +7 -0
- package/modules/audit/src/api/endpoints.ts +315 -0
- package/modules/audit/src/cli/commands.json +95 -0
- package/modules/audit/src/cli/index.ts +658 -0
- package/modules/audit/src/client/DataClassesView.tsrx +420 -0
- package/modules/audit/src/client/ExportsView.tsrx +256 -0
- package/modules/audit/src/client/HoldsView.tsrx +604 -0
- package/modules/audit/src/client/SweepsView.tsrx +251 -0
- package/modules/audit/src/client/api.ts +185 -0
- package/modules/audit/src/client/contribution.tsrx +52 -0
- package/modules/audit/src/client/index.ts +23 -0
- package/modules/audit/src/client/navigation.ts +74 -0
- package/modules/audit/src/client/presentation.ts +105 -0
- package/modules/audit/src/client/state.ts +86 -0
- package/modules/audit/src/domain/data-classes.ts +16 -0
- package/modules/audit/src/domain/types.ts +339 -0
- package/modules/audit/src/index.ts +76 -0
- package/modules/audit/src/platform.ts +44 -0
- package/modules/audit/src/server/index.ts +177 -0
- package/modules/audit/src/server/runtime.ts +303 -0
- package/modules/audit/src/services/anchor-key.ts +143 -0
- package/modules/audit/src/services/anchor-rotation.ts +147 -0
- package/modules/audit/src/services/audit-runners.ts +278 -0
- package/modules/audit/src/services/backup-guard.ts +95 -0
- package/modules/audit/src/services/database-repository.ts +1946 -0
- package/modules/audit/src/services/declared-classes.ts +64 -0
- package/modules/audit/src/services/erasure-port.ts +126 -0
- package/modules/audit/src/services/erasure-service.ts +770 -0
- package/modules/audit/src/services/export-directory.ts +106 -0
- package/modules/audit/src/services/export-service.ts +714 -0
- package/modules/audit/src/services/hold-service.ts +295 -0
- package/modules/audit/src/services/index.ts +17 -0
- package/modules/audit/src/services/migration.ts +658 -0
- package/modules/audit/src/services/own-classes.ts +318 -0
- package/modules/audit/src/services/platform-version.ts +23 -0
- package/modules/audit/src/services/repository.ts +495 -0
- package/modules/audit/src/services/retention-service.ts +191 -0
- package/modules/audit/src/services/seal-service.ts +769 -0
- package/modules/audit/src/services/service-error.ts +61 -0
- package/modules/audit/src/services/subject-keys.ts +90 -0
- package/modules/audit/src/services/sweep-service.ts +298 -0
- package/modules/audit/src/services/zip.ts +217 -0
- package/modules/audit/src/settings.ts +69 -0
- package/modules/audit/translations/en.json +210 -0
- package/modules/audit/translations/pl.json +210 -0
- package/modules/auth/README.md +77 -29
- package/modules/auth/migrations/0016_external_identities.up.sql +16 -0
- package/modules/auth/migrations/0017_mfa_key_id.up.sql +8 -0
- package/modules/auth/migrations/0018_retire_bundled_module_scopes.up.sql +29 -0
- package/modules/auth/migrations/0019_notifications_member_scopes.up.sql +44 -0
- package/modules/auth/migrations/0020_identity_providers.up.sql +61 -0
- package/modules/auth/migrations/0021_membership_status.up.sql +19 -0
- package/modules/auth/migrations/0022_auth_provider_scopes.up.sql +45 -0
- package/modules/auth/migrations/0023_enterprise_module_scopes.down.sql +19 -0
- package/modules/auth/migrations/0023_enterprise_module_scopes.up.sql +71 -0
- package/modules/auth/migrations/0024_approvals_member_scopes.down.sql +22 -0
- package/modules/auth/migrations/0024_approvals_member_scopes.up.sql +48 -0
- package/modules/auth/migrations/0025_audit_holds_owner_scope.down.sql +21 -0
- package/modules/auth/migrations/0025_audit_holds_owner_scope.up.sql +47 -0
- package/modules/auth/migrations/0026_auth_export_keyset_indexes.down.sql +6 -0
- package/modules/auth/migrations/0026_auth_export_keyset_indexes.up.sql +15 -0
- package/modules/auth/migrations/0027_workflow_automation_profile_scopes.down.sql +34 -0
- package/modules/auth/migrations/0027_workflow_automation_profile_scopes.up.sql +72 -0
- package/modules/auth/migrations/0028_auth_member_search_prefix_indexes.down.sql +6 -0
- package/modules/auth/migrations/0028_auth_member_search_prefix_indexes.up.sql +21 -0
- package/modules/auth/migrations/0029_reports_exports_access_scopes.down.sql +22 -0
- package/modules/auth/migrations/0029_reports_exports_access_scopes.up.sql +56 -0
- package/modules/auth/migrations/0030_auth_membership_keyset_index.down.sql +4 -0
- package/modules/auth/migrations/0030_auth_membership_keyset_index.up.sql +10 -0
- package/modules/auth/migrations/0031_membership_scope_backfills_under_rls.down.sql +4 -0
- package/modules/auth/migrations/0031_membership_scope_backfills_under_rls.up.sql +31 -0
- package/modules/auth/migrations/0032_audit_actor_service.down.sql +9 -0
- package/modules/auth/migrations/0032_audit_actor_service.up.sql +10 -0
- package/modules/auth/migrations/0033_auth_account_keyset_indexes.down.sql +5 -0
- package/modules/auth/migrations/0033_auth_account_keyset_indexes.up.sql +23 -0
- package/modules/auth/module.json +3 -3
- package/modules/auth/package.json +2 -1
- package/modules/auth/spec/module.yaml +372 -3
- package/modules/auth/src/acl/scopes.ts +50 -6
- package/modules/auth/src/cli/commands.json +11 -0
- package/modules/auth/src/cli/greenfield.ts +2 -2
- package/modules/auth/src/cli/index.ts +14 -0
- package/modules/auth/src/cli/secrets.ts +114 -0
- package/modules/auth/src/client/AuthenticationCore.tsrx +61 -8
- package/modules/auth/src/client/api.ts +72 -6
- package/modules/auth/src/client/contribution.tsrx +23 -0
- package/modules/auth/src/client/index.ts +6 -0
- package/modules/auth/src/client/providers/IdentityProviderForm.tsrx +191 -0
- package/modules/auth/src/client/providers/IdentityProvidersView.tsrx +358 -0
- package/modules/auth/src/client/providers/api.ts +128 -0
- package/modules/auth/src/client/providers/provider-columns.tsrx +60 -0
- package/modules/auth/src/client/providers/state.ts +35 -0
- package/modules/auth/src/client/security/SecurityView.tsrx +9 -1
- package/modules/auth/src/client/state.ts +15 -1
- package/modules/auth/src/domain/types.ts +55 -0
- package/modules/auth/src/index.ts +11 -1
- package/modules/auth/src/middleware/authentication.ts +4 -0
- package/modules/auth/src/server/endpoints.ts +467 -171
- package/modules/auth/src/server/http.ts +3 -2
- package/modules/auth/src/server/index.ts +22 -1
- package/modules/auth/src/server/membership-endpoints.ts +62 -0
- package/modules/auth/src/server/mfa-enforcement.ts +156 -0
- package/modules/auth/src/server/oidc.ts +389 -0
- package/modules/auth/src/server/provider-endpoints.ts +246 -0
- package/modules/auth/src/server/provider-host.ts +77 -0
- package/modules/auth/src/server/runtime.ts +231 -55
- package/modules/auth/src/services/auth-service-error.ts +7 -2
- package/modules/auth/src/services/auth-service.ts +948 -51
- package/modules/auth/src/services/data-classes.ts +236 -0
- package/modules/auth/src/services/database-repository.ts +853 -71
- package/modules/auth/src/services/identity-provider-service.ts +571 -0
- package/modules/auth/src/services/mail-delivery.ts +16 -16
- package/modules/auth/src/services/mail-message.ts +54 -0
- package/modules/auth/src/services/mail-port.ts +24 -0
- package/modules/auth/src/services/mail-smtp.ts +85 -0
- package/modules/auth/src/services/mfa-rotation.ts +144 -0
- package/modules/auth/src/services/migration.ts +810 -0
- package/modules/auth/src/services/password.ts +14 -0
- package/modules/auth/src/services/provider-secret-rotation.ts +169 -0
- package/modules/auth/src/services/provider-secrets.ts +136 -0
- package/modules/auth/src/services/repository.ts +321 -5
- package/modules/auth/src/services/session-sweep-runner.ts +73 -0
- package/modules/auth/src/services/settings-store.ts +10 -83
- package/modules/auth/src/services/totp.ts +88 -62
- package/modules/auth/src/services/validation.ts +194 -7
- package/modules/auth/src/settings.ts +18 -0
- package/modules/auth/translations/en.json +84 -1
- package/modules/auth/translations/pl.json +84 -1
- package/modules/automations/migrations/0005_secret_rotation_inventory.down.sql +1 -0
- package/modules/automations/migrations/0005_secret_rotation_inventory.up.sql +6 -0
- package/modules/automations/migrations/0006_automations_list_sort_indexes.down.sql +4 -0
- package/modules/automations/migrations/0006_automations_list_sort_indexes.up.sql +11 -0
- package/modules/automations/module.json +24 -6
- package/modules/automations/package.json +4 -1
- package/modules/automations/spec/module.yaml +81 -7
- package/modules/automations/src/api/endpoints.ts +202 -28
- package/modules/automations/src/cli/commands.json +17 -0
- package/modules/automations/src/cli/index.ts +114 -0
- package/modules/automations/src/client/AutomationScheduleForm.tsrx +77 -19
- package/modules/automations/src/client/AutomationSchedulesView.tsrx +144 -40
- package/modules/automations/src/client/AutomationTriggersView.tsrx +131 -32
- package/modules/automations/src/client/api.ts +62 -19
- package/modules/automations/src/client/presentation.ts +20 -2
- package/modules/automations/src/client/state.ts +84 -0
- package/modules/automations/src/domain/cadence.ts +99 -3
- package/modules/automations/src/domain/cron.ts +366 -0
- package/modules/automations/src/domain/time-zone.ts +61 -0
- package/modules/automations/src/platform.ts +38 -4
- package/modules/automations/src/server/index.ts +11 -0
- package/modules/automations/src/server/runtime.ts +46 -22
- package/modules/{automations-workflows-integration/src/server/adapter.ts → automations/src/server/workflow-target.ts} +13 -14
- package/modules/{automations-workflows-integration/src/server/tools.ts → automations/src/server/workflow-tools.ts} +0 -0
- package/modules/automations/src/services/data-classes.ts +62 -0
- package/modules/automations/src/services/database-repository.ts +152 -38
- package/modules/automations/src/services/migration.ts +119 -9
- package/modules/automations/src/services/repository.ts +52 -1
- package/modules/automations/src/services/schedule-runner.ts +84 -0
- package/modules/automations/src/services/schedule-service.ts +152 -26
- package/modules/automations/src/services/secret-rotation.ts +176 -0
- package/modules/automations/src/services/secret-vault.ts +68 -41
- package/modules/automations/src/services/trigger-service.ts +13 -10
- package/modules/automations/translations/en.json +15 -2
- package/modules/automations/translations/pl.json +15 -2
- package/modules/connectors/migrations/0001_connectors_core.down.sql +3 -0
- package/modules/connectors/migrations/0001_connectors_core.up.sql +72 -0
- package/modules/connectors/migrations/0002_connectors_call_keys.down.sql +3 -0
- package/modules/connectors/migrations/0002_connectors_call_keys.up.sql +18 -0
- package/modules/connectors/migrations/0003_connectors_rotation_inventory.down.sql +2 -0
- package/modules/connectors/migrations/0003_connectors_rotation_inventory.up.sql +19 -0
- package/modules/connectors/migrations/README.md +3 -0
- package/modules/connectors/module.json +32 -0
- package/modules/{automations-workflows-integration → connectors}/package.json +28 -26
- package/modules/connectors/spec/module.yaml +448 -0
- package/modules/connectors/src/acl/permissions.ts +6 -0
- package/modules/connectors/src/agent/tools.ts +180 -0
- package/modules/connectors/src/api/endpoints.ts +572 -0
- package/modules/connectors/src/cli/commands.json +17 -0
- package/modules/connectors/src/cli/index.ts +126 -0
- package/modules/connectors/src/client/ConnectorCallsView.tsrx +358 -0
- package/modules/connectors/src/client/ConnectorForm.tsrx +424 -0
- package/modules/connectors/src/client/ConnectorsView.tsrx +711 -0
- package/modules/connectors/src/client/api.ts +281 -0
- package/modules/connectors/src/client/consent.ts +48 -0
- package/modules/connectors/src/client/contribution.tsrx +37 -0
- package/modules/connectors/src/client/index.ts +22 -0
- package/modules/connectors/src/client/navigation.ts +42 -0
- package/modules/connectors/src/client/presentation.ts +38 -0
- package/modules/connectors/src/client/state.ts +175 -0
- package/modules/connectors/src/domain/calls.ts +70 -0
- package/modules/connectors/src/domain/definitions.ts +190 -0
- package/modules/connectors/src/domain/http-json.ts +68 -0
- package/modules/connectors/src/domain/types.ts +185 -0
- package/modules/connectors/src/index.ts +64 -0
- package/modules/connectors/src/platform.ts +56 -0
- package/modules/connectors/src/server/index.ts +66 -0
- package/modules/connectors/src/server/runtime.ts +163 -0
- package/modules/connectors/src/services/call-service.ts +903 -0
- package/modules/connectors/src/services/connectors-service.ts +653 -0
- package/modules/connectors/src/services/credential-rotation.ts +221 -0
- package/modules/connectors/src/services/credential-vault.ts +194 -0
- package/modules/connectors/src/services/data-classes.ts +158 -0
- package/modules/connectors/src/services/database-repository.ts +897 -0
- package/modules/connectors/src/services/egress.ts +261 -0
- package/modules/connectors/src/services/index.ts +19 -0
- package/modules/connectors/src/services/migration.ts +202 -0
- package/modules/connectors/src/services/repository.ts +175 -0
- package/modules/connectors/src/services/service-error.ts +46 -0
- package/modules/connectors/src/settings.ts +69 -0
- package/modules/connectors/translations/en.json +191 -0
- package/modules/connectors/translations/pl.json +191 -0
- package/modules/directory/migrations/0001_directory_core.down.sql +5 -0
- package/modules/directory/migrations/0001_directory_core.up.sql +111 -0
- package/modules/directory/migrations/README.md +3 -0
- package/modules/directory/module.json +27 -0
- package/modules/directory/package.json +46 -0
- package/modules/directory/spec/module.yaml +351 -0
- package/modules/directory/src/acl/permissions.ts +7 -0
- package/modules/directory/src/api/endpoints.ts +478 -0
- package/modules/directory/src/api/scim-endpoints.ts +516 -0
- package/modules/directory/src/client/GroupMappingsView.tsrx +430 -0
- package/modules/directory/src/client/ProvisioningLogView.tsrx +248 -0
- package/modules/directory/src/client/ScimTokensView.tsrx +587 -0
- package/modules/directory/src/client/api.ts +222 -0
- package/modules/directory/src/client/contribution.tsrx +46 -0
- package/modules/directory/src/client/index.ts +22 -0
- package/modules/directory/src/client/navigation.ts +57 -0
- package/modules/directory/src/client/presentation.ts +143 -0
- package/modules/directory/src/client/state.ts +173 -0
- package/modules/directory/src/domain/scim.ts +346 -0
- package/modules/directory/src/domain/types.ts +166 -0
- package/modules/directory/src/index.ts +44 -0
- package/modules/directory/src/platform.ts +43 -0
- package/modules/directory/src/server/index.ts +17 -0
- package/modules/directory/src/server/runtime.ts +124 -0
- package/modules/directory/src/services/auth-port.ts +127 -0
- package/modules/directory/src/services/data-classes.ts +41 -0
- package/modules/directory/src/services/database-repository.ts +1067 -0
- package/modules/directory/src/services/directory-service.ts +311 -0
- package/modules/directory/src/services/index.ts +43 -0
- package/modules/directory/src/services/migration.ts +146 -0
- package/modules/directory/src/services/provisioning-service.ts +1420 -0
- package/modules/directory/src/services/rate-limiter.ts +103 -0
- package/modules/directory/src/services/repository.ts +208 -0
- package/modules/directory/src/services/service-error.ts +42 -0
- package/modules/directory/src/services/token-service.ts +215 -0
- package/modules/directory/src/settings.ts +86 -0
- package/modules/directory/translations/en.json +185 -0
- package/modules/directory/translations/pl.json +185 -0
- package/modules/documents/migrations/0001_documents_core.down.sql +1 -0
- package/modules/documents/migrations/0001_documents_core.up.sql +35 -0
- package/modules/documents/migrations/0002_documents_files_page.down.sql +1 -0
- package/modules/documents/migrations/0002_documents_files_page.up.sql +6 -0
- package/modules/documents/migrations/0003_documents_rotation_inventory.down.sql +2 -0
- package/modules/documents/migrations/0003_documents_rotation_inventory.up.sql +18 -0
- package/modules/documents/migrations/README.md +3 -0
- package/modules/documents/module.json +32 -0
- package/modules/documents/package.json +50 -0
- package/modules/documents/spec/module.yaml +248 -0
- package/modules/documents/src/acl/permissions.ts +6 -0
- package/modules/documents/src/api/endpoints.ts +401 -0
- package/modules/documents/src/cli/commands.json +17 -0
- package/modules/documents/src/cli/index.ts +145 -0
- package/modules/documents/src/client/DocumentUploader.tsrx +148 -0
- package/modules/documents/src/client/DocumentsView.tsrx +540 -0
- package/modules/documents/src/client/api.ts +209 -0
- package/modules/documents/src/client/contribution.tsrx +29 -0
- package/modules/documents/src/client/download.ts +70 -0
- package/modules/documents/src/client/index.ts +22 -0
- package/modules/documents/src/client/navigation.ts +25 -0
- package/modules/documents/src/client/presentation.ts +110 -0
- package/modules/documents/src/client/state.ts +117 -0
- package/modules/documents/src/domain/attachments.ts +84 -0
- package/modules/documents/src/domain/types.ts +80 -0
- package/modules/documents/src/index.ts +46 -0
- package/modules/documents/src/platform.ts +61 -0
- package/modules/documents/src/server/index.ts +30 -0
- package/modules/documents/src/server/runtime.ts +107 -0
- package/modules/documents/src/services/attachments.ts +39 -0
- package/modules/documents/src/services/data-classes.ts +25 -0
- package/modules/documents/src/services/database-repository.ts +270 -0
- package/modules/documents/src/services/documents-service.ts +590 -0
- package/modules/documents/src/services/index.ts +18 -0
- package/modules/documents/src/services/migration.ts +117 -0
- package/modules/documents/src/services/repository.ts +58 -0
- package/modules/documents/src/services/storage-rotation.ts +157 -0
- package/modules/documents/src/settings.ts +107 -0
- package/modules/documents/translations/en.json +111 -0
- package/modules/documents/translations/pl.json +111 -0
- package/modules/exports/migrations/0001_exports_core.down.sql +2 -0
- package/modules/exports/migrations/0001_exports_core.up.sql +28 -0
- package/modules/exports/migrations/0002_exports_job_routing_role.down.sql +4 -0
- package/modules/exports/migrations/0002_exports_job_routing_role.up.sql +21 -0
- package/modules/exports/migrations/0003_exports_rotation_inventory.down.sql +1 -0
- package/modules/exports/migrations/0003_exports_rotation_inventory.up.sql +9 -0
- package/modules/exports/migrations/README.md +3 -0
- package/modules/exports/module.json +32 -0
- package/modules/exports/package.json +45 -0
- package/modules/exports/spec/module.yaml +230 -0
- package/modules/exports/src/acl/permissions.ts +6 -0
- package/modules/exports/src/api/endpoints.ts +211 -0
- package/modules/exports/src/cli/commands.json +17 -0
- package/modules/exports/src/cli/index.ts +145 -0
- package/modules/exports/src/client/ExportsView.tsrx +327 -0
- package/modules/exports/src/client/api.ts +133 -0
- package/modules/exports/src/client/contribution.tsrx +31 -0
- package/modules/exports/src/client/index.ts +20 -0
- package/modules/exports/src/client/navigation.ts +25 -0
- package/modules/exports/src/client/presentation.ts +53 -0
- package/modules/exports/src/client/state.ts +56 -0
- package/modules/exports/src/domain/lists.ts +29 -0
- package/modules/exports/src/domain/types.ts +120 -0
- package/modules/exports/src/index.ts +41 -0
- package/modules/exports/src/platform.ts +80 -0
- package/modules/exports/src/server/index.ts +50 -0
- package/modules/exports/src/server/runtime.ts +160 -0
- package/modules/exports/src/services/data-classes.ts +59 -0
- package/modules/exports/src/services/database-repository.ts +403 -0
- package/modules/exports/src/services/export-runner.ts +123 -0
- package/modules/exports/src/services/export-service.ts +356 -0
- package/modules/exports/src/services/index.ts +6 -0
- package/modules/exports/src/services/list-registry.ts +105 -0
- package/modules/exports/src/services/migration.ts +116 -0
- package/modules/exports/src/services/repository.ts +96 -0
- package/modules/exports/src/services/storage-rotation.ts +138 -0
- package/modules/exports/src/settings.ts +70 -0
- package/modules/exports/translations/en.json +69 -0
- package/modules/exports/translations/pl.json +69 -0
- package/modules/import/migrations/0001_import_core.down.sql +6 -0
- package/modules/import/migrations/0001_import_core.up.sql +61 -0
- package/modules/import/migrations/0002_import_job_routing_role.down.sql +4 -0
- package/modules/import/migrations/0002_import_job_routing_role.up.sql +20 -0
- package/modules/import/migrations/0003_import_jobs_export_index.down.sql +1 -0
- package/modules/import/migrations/0003_import_jobs_export_index.up.sql +7 -0
- package/modules/import/migrations/0004_import_jobs_traceparent.down.sql +1 -0
- package/modules/import/migrations/0004_import_jobs_traceparent.up.sql +7 -0
- package/modules/import/migrations/README.md +3 -0
- package/modules/import/module.json +37 -0
- package/modules/import/package.json +50 -0
- package/modules/import/spec/module.yaml +335 -0
- package/modules/import/src/acl/permissions.ts +6 -0
- package/modules/import/src/api/endpoints.ts +400 -0
- package/modules/import/src/client/ImportJobDrawer.tsrx +229 -0
- package/modules/import/src/client/ImportsView.tsrx +455 -0
- package/modules/import/src/client/NewImportForm.tsrx +274 -0
- package/modules/import/src/client/api.ts +243 -0
- package/modules/import/src/client/contribution.tsrx +29 -0
- package/modules/import/src/client/index.ts +23 -0
- package/modules/import/src/client/navigation.ts +25 -0
- package/modules/import/src/client/presentation.ts +59 -0
- package/modules/import/src/client/state.ts +43 -0
- package/modules/import/src/domain/csv.ts +258 -0
- package/modules/import/src/domain/ports.ts +157 -0
- package/modules/import/src/domain/types.ts +179 -0
- package/modules/import/src/index.ts +58 -0
- package/modules/import/src/platform.ts +60 -0
- package/modules/import/src/server/index.ts +49 -0
- package/modules/import/src/server/runtime.ts +158 -0
- package/modules/import/src/services/csv-source.ts +121 -0
- package/modules/import/src/services/data-classes.ts +63 -0
- package/modules/import/src/services/database-repository.ts +780 -0
- package/modules/import/src/services/import-runner.ts +127 -0
- package/modules/import/src/services/import-service.ts +872 -0
- package/modules/import/src/services/index.ts +6 -0
- package/modules/import/src/services/migration.ts +156 -0
- package/modules/import/src/services/port-registry.ts +158 -0
- package/modules/import/src/services/repository.ts +147 -0
- package/modules/import/src/settings.ts +70 -0
- package/modules/import/translations/en.json +175 -0
- package/modules/import/translations/pl.json +175 -0
- package/modules/metering/migrations/0001_metering_core.down.sql +6 -0
- package/modules/metering/migrations/0001_metering_core.up.sql +119 -0
- package/modules/metering/migrations/0002_metering_bucket_export.down.sql +3 -0
- package/modules/metering/migrations/0002_metering_bucket_export.up.sql +5 -0
- package/modules/metering/migrations/README.md +3 -0
- package/modules/metering/module.json +46 -0
- package/modules/metering/package.json +50 -0
- package/modules/metering/spec/module.yaml +269 -0
- package/modules/metering/src/acl/permissions.ts +5 -0
- package/modules/metering/src/api/endpoints.ts +104 -0
- package/modules/metering/src/cli/commands.json +28 -0
- package/modules/metering/src/cli/index.ts +246 -0
- package/modules/metering/src/client/LimitsView.tsrx +170 -0
- package/modules/metering/src/client/UsageSummaryWidget.tsrx +63 -0
- package/modules/metering/src/client/UsageView.tsrx +205 -0
- package/modules/metering/src/client/api.ts +91 -0
- package/modules/metering/src/client/contribution.tsrx +42 -0
- package/modules/metering/src/client/index.ts +26 -0
- package/modules/metering/src/client/navigation.ts +54 -0
- package/modules/metering/src/client/presentation.ts +63 -0
- package/modules/metering/src/client/state.ts +31 -0
- package/modules/metering/src/domain/meters.ts +100 -0
- package/modules/metering/src/domain/types.ts +71 -0
- package/modules/metering/src/index.ts +51 -0
- package/modules/metering/src/platform.ts +84 -0
- package/modules/metering/src/server/index.ts +40 -0
- package/modules/metering/src/server/runtime.ts +165 -0
- package/modules/metering/src/services/data-classes.ts +34 -0
- package/modules/metering/src/services/database-repository.ts +574 -0
- package/modules/metering/src/services/index.ts +9 -0
- package/modules/metering/src/services/meter-registry.ts +145 -0
- package/modules/metering/src/services/metering-service.ts +441 -0
- package/modules/metering/src/services/migration.ts +183 -0
- package/modules/metering/src/services/notifications.ts +75 -0
- package/modules/metering/src/services/reports.ts +105 -0
- package/modules/metering/src/services/repository.ts +132 -0
- package/modules/metering/src/services/service-error.ts +46 -0
- package/modules/metering/src/settings.ts +47 -0
- package/modules/metering/translations/en.json +68 -0
- package/modules/metering/translations/pl.json +68 -0
- package/modules/notifications/migrations/0001_notifications_core.down.sql +1 -0
- package/modules/notifications/migrations/0001_notifications_core.up.sql +26 -0
- package/modules/notifications/migrations/0002_notifications_preferences.down.sql +1 -0
- package/modules/notifications/migrations/0002_notifications_preferences.up.sql +17 -0
- package/modules/notifications/migrations/0003_notifications_webhook_subscriptions.down.sql +1 -0
- package/modules/notifications/migrations/0003_notifications_webhook_subscriptions.up.sql +30 -0
- package/modules/notifications/migrations/0004_notifications_deliveries.down.sql +1 -0
- package/modules/notifications/migrations/0004_notifications_deliveries.up.sql +35 -0
- package/modules/notifications/migrations/0005_notifications_delivery_routing_role.down.sql +2 -0
- package/modules/notifications/migrations/0005_notifications_delivery_routing_role.up.sql +17 -0
- package/modules/notifications/migrations/0006_notifications_delivery_title.down.sql +1 -0
- package/modules/notifications/migrations/0006_notifications_delivery_title.up.sql +11 -0
- package/modules/notifications/migrations/0007_notifications_secret_rotation_inventory.down.sql +2 -0
- package/modules/notifications/migrations/0007_notifications_secret_rotation_inventory.up.sql +19 -0
- package/modules/notifications/migrations/0008_notifications_delivery_claim.down.sql +8 -0
- package/modules/notifications/migrations/0008_notifications_delivery_claim.up.sql +19 -0
- package/modules/notifications/migrations/0009_notifications_kind_approvals.down.sql +16 -0
- package/modules/notifications/migrations/0009_notifications_kind_approvals.up.sql +18 -0
- package/modules/notifications/migrations/0010_notifications_kind_meter_threshold.down.sql +16 -0
- package/modules/notifications/migrations/0010_notifications_kind_meter_threshold.up.sql +18 -0
- package/modules/notifications/migrations/0011_notifications_retention_keyset.down.sql +5 -0
- package/modules/notifications/migrations/0011_notifications_retention_keyset.up.sql +15 -0
- package/modules/notifications/migrations/0012_notifications_email_channel.down.sql +12 -0
- package/modules/notifications/migrations/0012_notifications_email_channel.up.sql +38 -0
- package/modules/notifications/migrations/0013_notifications_list_pages.down.sql +5 -0
- package/modules/notifications/migrations/0013_notifications_list_pages.up.sql +13 -0
- package/modules/notifications/migrations/README.md +3 -0
- package/modules/notifications/module.json +32 -0
- package/modules/notifications/package.json +49 -0
- package/modules/notifications/spec/module.yaml +718 -0
- package/modules/notifications/src/acl/permissions.ts +11 -0
- package/modules/notifications/src/api/endpoints.ts +814 -0
- package/modules/notifications/src/cli/commands.json +17 -0
- package/modules/notifications/src/cli/index.ts +126 -0
- package/modules/notifications/src/client/DeliveriesView.tsrx +483 -0
- package/modules/notifications/src/client/FactList.tsrx +24 -0
- package/modules/notifications/src/client/NotificationPreferencesView.tsrx +240 -0
- package/modules/notifications/src/client/NotificationsInboxView.tsrx +592 -0
- package/modules/notifications/src/client/UnreadCountWidget.tsrx +62 -0
- package/modules/notifications/src/client/WebhookForm.tsrx +297 -0
- package/modules/notifications/src/client/WebhooksView.tsrx +637 -0
- package/modules/notifications/src/client/api.ts +382 -0
- package/modules/notifications/src/client/contribution.tsrx +83 -0
- package/modules/notifications/src/client/inbox.ts +41 -0
- package/modules/notifications/src/client/index.ts +29 -0
- package/modules/notifications/src/client/navigation.ts +92 -0
- package/modules/notifications/src/client/notifications.css +70 -0
- package/modules/notifications/src/client/presentation.ts +81 -0
- package/modules/notifications/src/client/state.ts +246 -0
- package/modules/notifications/src/client/unread-count.ts +26 -0
- package/modules/notifications/src/domain/locale.ts +47 -0
- package/modules/notifications/src/domain/publish.ts +46 -0
- package/modules/notifications/src/domain/types.ts +186 -0
- package/modules/notifications/src/index.ts +62 -0
- package/modules/notifications/src/platform.ts +85 -0
- package/modules/notifications/src/server/index.ts +82 -0
- package/modules/notifications/src/server/runtime.ts +251 -0
- package/modules/notifications/src/services/data-classes.ts +139 -0
- package/modules/notifications/src/services/database-repository.ts +1334 -0
- package/modules/notifications/src/services/delivery-payload.ts +93 -0
- package/modules/notifications/src/services/delivery-runner.ts +170 -0
- package/modules/notifications/src/services/delivery-service.ts +811 -0
- package/modules/notifications/src/services/egress.ts +239 -0
- package/modules/notifications/src/services/email-channel.ts +93 -0
- package/modules/notifications/src/services/index.ts +23 -0
- package/modules/notifications/src/services/migration.ts +572 -0
- package/modules/notifications/src/services/notifications-service.ts +297 -0
- package/modules/notifications/src/services/paging.ts +60 -0
- package/modules/notifications/src/services/publish-service.ts +133 -0
- package/modules/notifications/src/services/repository.ts +342 -0
- package/modules/notifications/src/services/secret-rotation.ts +177 -0
- package/modules/notifications/src/services/secret-vault.ts +168 -0
- package/modules/notifications/src/services/service-error.ts +67 -0
- package/modules/notifications/src/services/signature.ts +81 -0
- package/modules/notifications/src/services/webhook-service.ts +307 -0
- package/modules/notifications/src/settings.ts +130 -0
- package/modules/notifications/translations/en.json +255 -0
- package/modules/notifications/translations/pl.json +255 -0
- package/modules/profile/module.json +3 -3
- package/modules/profile/package.json +1 -1
- package/modules/profile/spec/module.yaml +3 -3
- package/modules/profile/src/services/database-repository.ts +3 -13
- package/modules/reports/module.json +28 -0
- package/modules/reports/package.json +42 -0
- package/modules/reports/spec/module.yaml +231 -0
- package/modules/reports/src/acl/permissions.ts +5 -0
- package/modules/reports/src/api/endpoints.ts +72 -0
- package/modules/reports/src/client/ReportsSummaryWidget.tsrx +52 -0
- package/modules/reports/src/client/ReportsView.tsrx +246 -0
- package/modules/reports/src/client/api.ts +65 -0
- package/modules/reports/src/client/contribution.tsrx +36 -0
- package/modules/reports/src/client/index.ts +18 -0
- package/modules/reports/src/client/navigation.ts +39 -0
- package/modules/reports/src/client/presentation.ts +123 -0
- package/modules/reports/src/client/state.ts +28 -0
- package/modules/reports/src/domain/providers.ts +115 -0
- package/modules/reports/src/domain/types.ts +44 -0
- package/modules/reports/src/index.ts +47 -0
- package/modules/reports/src/platform.ts +37 -0
- package/modules/reports/src/server/index.ts +21 -0
- package/modules/reports/src/server/runtime.ts +40 -0
- package/modules/reports/src/services/index.ts +17 -0
- package/modules/reports/src/services/provider-registry.ts +144 -0
- package/modules/reports/src/services/range.ts +65 -0
- package/modules/reports/src/services/reports-service.ts +316 -0
- package/modules/reports/src/services/service-error.ts +26 -0
- package/modules/reports/src/settings.ts +49 -0
- package/modules/reports/translations/en.json +35 -0
- package/modules/reports/translations/pl.json +35 -0
- package/modules/sandbox/module.json +4 -4
- package/modules/sandbox/package.json +1 -1
- package/modules/sandbox/spec/module.yaml +3 -3
- package/modules/sandbox/src/services/database-repository.ts +14 -23
- package/modules/sandbox/src/services/directory.ts +16 -0
- package/modules/sandbox/src/services/sandbox-service.ts +13 -11
- package/modules/search/migrations/0001_search_core.down.sql +1 -0
- package/modules/search/migrations/0001_search_core.up.sql +22 -0
- package/modules/search/migrations/0002_search_recent_export.down.sql +3 -0
- package/modules/search/migrations/0002_search_recent_export.up.sql +5 -0
- package/modules/search/migrations/README.md +3 -0
- package/modules/search/module.json +28 -0
- package/modules/search/package.json +43 -0
- package/modules/search/spec/module.yaml +177 -0
- package/modules/search/src/acl/permissions.ts +5 -0
- package/modules/search/src/api/endpoints.ts +221 -0
- package/modules/search/src/client/SearchView.tsrx +346 -0
- package/modules/search/src/client/api.ts +157 -0
- package/modules/search/src/client/contribution.tsrx +31 -0
- package/modules/search/src/client/index.ts +17 -0
- package/modules/search/src/client/navigation.ts +97 -0
- package/modules/search/src/client/search.css +56 -0
- package/modules/search/src/client/state.ts +113 -0
- package/modules/search/src/domain/data-classes.ts +73 -0
- package/modules/search/src/domain/providers.ts +88 -0
- package/modules/search/src/domain/types.ts +56 -0
- package/modules/search/src/index.ts +46 -0
- package/modules/search/src/platform.ts +42 -0
- package/modules/search/src/server/index.ts +25 -0
- package/modules/search/src/server/runtime.ts +123 -0
- package/modules/search/src/services/database-repository.ts +163 -0
- package/modules/search/src/services/index.ts +21 -0
- package/modules/search/src/services/migration.ts +65 -0
- package/modules/search/src/services/provider-registry.ts +137 -0
- package/modules/search/src/services/repository.ts +43 -0
- package/modules/search/src/services/search-service.ts +442 -0
- package/modules/search/src/services/service-error.ts +35 -0
- package/modules/search/src/settings.ts +68 -0
- package/modules/search/translations/en.json +45 -0
- package/modules/search/translations/pl.json +45 -0
- package/modules/system/module.json +2 -2
- package/modules/system/package.json +1 -1
- package/modules/system/spec/module.yaml +30 -3
- package/modules/system/src/client/FlagsPanel.tsrx +127 -0
- package/modules/system/src/client/ModuleSettingRow.tsrx +17 -7
- package/modules/system/src/client/ModuleSettingsSection.tsrx +10 -71
- package/modules/system/src/client/ModulesView.tsrx +91 -65
- package/modules/system/src/client/flags.ts +35 -0
- package/modules/system/src/client/index.ts +8 -0
- package/modules/system/src/client/navigation.ts +8 -0
- package/modules/system/src/client/settings-state.ts +159 -0
- package/modules/system/src/client/state.ts +2 -0
- package/modules/system/src/domain/time-zone.ts +105 -0
- package/modules/system/src/index.ts +12 -0
- package/modules/system/src/platform.ts +5 -1
- package/modules/system/src/server/endpoints.ts +72 -21
- package/modules/system/src/settings.ts +28 -0
- package/modules/system/translations/en.json +17 -0
- package/modules/system/translations/pl.json +17 -0
- package/modules/users/module.json +30 -4
- package/modules/users/package.json +4 -1
- package/modules/users/spec/module.yaml +290 -7
- package/modules/users/src/api/endpoints.ts +228 -15
- package/modules/users/src/client/InviteMemberForm.tsrx +2 -2
- package/modules/users/src/client/MemberDrawer.tsrx +142 -26
- package/modules/users/src/client/UserAccountForm.tsrx +2 -2
- package/modules/users/src/client/UsersView.tsrx +432 -71
- package/modules/users/src/client/api.ts +106 -12
- package/modules/users/src/client/contribution.tsrx +5 -0
- package/modules/users/src/client/member-columns.tsrx +22 -9
- package/modules/users/src/client/state.ts +172 -7
- package/modules/users/src/domain/lists.ts +9 -0
- package/modules/users/src/index.ts +11 -0
- package/modules/users/src/platform.ts +50 -1
- package/modules/users/src/services/member-export.ts +51 -0
- package/modules/users/src/services/member-import.ts +249 -0
- package/modules/users/src/services/member-search.ts +145 -0
- package/modules/users/src/services/users-service.ts +117 -4
- package/modules/users/translations/en.json +57 -8
- package/modules/users/translations/pl.json +57 -8
- package/modules/workflows/migrations/0004_payload_rotation_inventory.up.sql +17 -0
- package/modules/workflows/migrations/0005_workflows_human_approval.up.sql +28 -0
- package/modules/workflows/migrations/0006_workflows_retention_indexes.up.sql +11 -0
- package/modules/workflows/migrations/0007_workflows_erasure_subject_indexes.up.sql +17 -0
- package/modules/workflows/migrations/0008_workflows_subject_account.up.sql +24 -0
- package/modules/workflows/migrations/0009_workflows_definition_list_indexes.up.sql +9 -0
- package/modules/workflows/module.json +34 -6
- package/modules/workflows/package.json +2 -1
- package/modules/workflows/spec/module.yaml +26 -9
- package/modules/workflows/src/api/endpoints.ts +131 -25
- package/modules/workflows/src/cli/commands.json +17 -0
- package/modules/workflows/src/cli/index.ts +114 -0
- package/modules/workflows/src/client/WorkflowExecutionHistory.tsrx +36 -27
- package/modules/workflows/src/client/WorkflowInspector.tsrx +116 -0
- package/modules/workflows/src/client/WorkflowNodePalette.tsrx +1 -0
- package/modules/workflows/src/client/WorkflowsView.tsrx +236 -99
- package/modules/workflows/src/client/api.ts +54 -34
- package/modules/workflows/src/client/canvas-model.ts +12 -0
- package/modules/workflows/src/client/state.ts +95 -0
- package/modules/workflows/src/domain/events.ts +25 -11
- package/modules/workflows/src/domain/graph.ts +135 -1
- package/modules/workflows/src/domain/types.ts +91 -4
- package/modules/workflows/src/platform.ts +12 -0
- package/modules/workflows/src/server/runtime.ts +78 -5
- package/modules/workflows/src/services/approvals.ts +62 -0
- package/modules/workflows/src/services/cursors.ts +37 -0
- package/modules/workflows/src/services/data-classes.ts +232 -0
- package/modules/workflows/src/services/database-repository.ts +660 -171
- package/modules/workflows/src/services/migration.ts +240 -0
- package/modules/workflows/src/services/notifications.ts +98 -0
- package/modules/workflows/src/services/payload-codec.ts +51 -28
- package/modules/workflows/src/services/payload-rotation.ts +171 -0
- package/modules/workflows/src/services/repository.ts +123 -8
- package/modules/workflows/src/services/simulation.ts +7 -0
- package/modules/workflows/src/services/worker.ts +446 -25
- package/modules/workflows/src/services/workflows-service.ts +264 -103
- package/modules/workflows/translations/en.json +12 -4
- package/modules/workflows/translations/pl.json +12 -4
- package/modules.json +48 -4
- package/package.json +64 -6
- package/packages/client/package.json +1 -0
- package/packages/client/src/ApplicationShell.tsrx +43 -0
- package/packages/client/src/contributions.ts +55 -0
- package/packages/client/src/i18n/locales/en.json +5 -0
- package/packages/client/src/i18n/locales/pl.json +5 -0
- package/packages/client/src/index.ts +13 -1
- package/packages/client/src/routing.ts +22 -0
- package/packages/client/src/shell/CommandPalette.tsrx +223 -11
- package/packages/client/src/shell/command-search.ts +126 -0
- package/packages/client/src/shell/command.ts +30 -0
- package/packages/client/src/shell/navigation.ts +12 -0
- package/packages/client/src/shell/palette-keys.ts +27 -0
- package/packages/client/src/shell/shell.css +7 -0
- package/packages/contracts/schemas/module-spec.schema.json +287 -1
- package/packages/contracts/schemas/module.schema.json +25 -0
- package/packages/contracts/src/index.ts +144 -1
- package/packages/database/src/backup.ts +180 -0
- package/packages/database/src/decoders.ts +25 -0
- package/packages/database/src/index.ts +17 -0
- package/packages/database/src/migrations.ts +7 -0
- package/packages/database/src/record-history.ts +5 -14
- package/packages/harness/src/index.ts +2 -0
- package/packages/harness/src/runtime.ts +378 -2
- package/packages/harness/src/tool-adapters.ts +20 -9
- package/packages/kernel/src/acl.ts +179 -0
- package/packages/kernel/src/approval-grant.ts +310 -0
- package/packages/kernel/src/capability-registry.ts +69 -2
- package/packages/kernel/src/data-class-registry.ts +354 -0
- package/packages/kernel/src/index.ts +85 -4
- package/packages/kernel/src/keyring.ts +208 -0
- package/packages/kernel/src/module-compatibility.ts +35 -4
- package/packages/kernel/src/module-registry.ts +49 -2
- package/packages/kernel/src/module-settings.ts +189 -27
- package/packages/server/package.json +2 -1
- package/packages/server/src/composition.ts +38 -0
- package/packages/server/src/endpoint.ts +104 -47
- package/packages/server/src/export/csv.ts +34 -0
- package/packages/server/src/export/definition.ts +346 -0
- package/packages/server/src/export/index.ts +61 -0
- package/packages/server/src/export/run.ts +116 -0
- package/packages/server/src/index.ts +155 -0
- package/packages/server/src/jobs/index.ts +93 -0
- package/packages/server/src/jobs/runner.ts +486 -0
- package/packages/server/src/log.ts +291 -0
- package/packages/server/src/mail/config.ts +184 -0
- package/packages/server/src/mail/contracts.ts +251 -0
- package/packages/server/src/mail/index.ts +31 -0
- package/packages/server/src/mail/port.ts +101 -0
- package/packages/server/src/mail/smtp.ts +191 -0
- package/packages/server/src/mail/template.ts +105 -0
- package/packages/server/src/metrics.ts +447 -0
- package/packages/server/src/pagination.ts +246 -0
- package/packages/server/src/trace/context.ts +103 -0
- package/packages/server/src/trace/egress.ts +66 -0
- package/packages/server/src/trace/error-sink.ts +296 -0
- package/packages/server/src/trace/exporter.ts +295 -0
- package/packages/server/src/trace/index.ts +60 -0
- package/packages/server/src/trace/job-sink.ts +172 -0
- package/packages/server/src/trace/tracer.ts +338 -0
- package/packages/server/src/web.ts +5 -1
- package/packages/storage/package.json +40 -0
- package/packages/storage/src/config.ts +189 -0
- package/packages/storage/src/content-type.ts +124 -0
- package/packages/storage/src/contracts.ts +112 -0
- package/packages/storage/src/envelope.ts +238 -0
- package/packages/storage/src/index.ts +49 -0
- package/packages/storage/src/local.ts +75 -0
- package/packages/storage/src/port.ts +262 -0
- package/packages/storage/src/read-token.ts +130 -0
- package/packages/storage/src/reseal.ts +128 -0
- package/packages/storage/src/s3.ts +144 -0
- package/packages/storage/src/scanner.ts +22 -0
- package/packages/storage/src/sigv4.ts +104 -0
- package/packages/storage/src/store.ts +13 -0
- package/packages/ui/package.json +1 -0
- package/packages/ui/src/components/Button.tsrx +3 -0
- package/packages/ui/src/components/ConfirmDialog.tsrx +13 -2
- package/packages/ui/src/components/DateField.tsrx +92 -0
- package/packages/ui/src/components/DatePicker.tsrx +525 -0
- package/packages/ui/src/components/DateRangeField.tsrx +93 -0
- package/packages/ui/src/components/Drawer.tsrx +10 -1
- package/packages/ui/src/components/FileUpload.tsrx +158 -0
- package/packages/ui/src/components/Pagination.tsrx +130 -0
- package/packages/ui/src/components/Select.tsrx +78 -0
- package/packages/ui/src/components/Table.tsrx +498 -81
- package/packages/ui/src/components/Tabs.tsrx +53 -0
- package/packages/ui/src/components/ToastHost.tsrx +48 -0
- package/packages/ui/src/components/calendar.ts +243 -0
- package/packages/ui/src/components/date-field.ts +62 -0
- package/packages/ui/src/components/file-upload.ts +49 -0
- package/packages/ui/src/components/focus-trap.ts +62 -0
- package/packages/ui/src/components/pagination.ts +68 -0
- package/packages/ui/src/components/table-sorting.ts +49 -0
- package/packages/ui/src/components/tabs.ts +61 -0
- package/packages/ui/src/components/toast-store.ts +116 -0
- package/packages/ui/src/icons/Icon.tsrx +5 -0
- package/packages/ui/src/index.ts +45 -0
- package/packages/ui/src/styles/components.css +598 -0
- package/modules/automations-workflows-integration/module.json +0 -27
- package/modules/automations-workflows-integration/spec/module.yaml +0 -93
- package/modules/automations-workflows-integration/src/index.ts +0 -14
- package/modules/automations-workflows-integration/src/platform.ts +0 -24
- package/modules/automations-workflows-integration/src/server/index.ts +0 -11
- package/modules/automations-workflows-integration/translations/en.json +0 -21
- package/modules/automations-workflows-integration/translations/pl.json +0 -21
- package/modules/workflows/src/services/cursor-codec.ts +0 -62
|
@@ -0,0 +1,1334 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DatabaseHandle,
|
|
3
|
+
DatabaseParameter,
|
|
4
|
+
DatabaseRow,
|
|
5
|
+
DatabaseStatement,
|
|
6
|
+
DatabaseTransaction,
|
|
7
|
+
} from '@flowdular/sdk/database';
|
|
8
|
+
import { integer, runDatabaseMigrations } from '@flowdular/sdk/database';
|
|
9
|
+
import { keysetWhere } from '@flowdular/sdk/server';
|
|
10
|
+
import type {
|
|
11
|
+
DeliveryAttempt,
|
|
12
|
+
DeliveryChannel,
|
|
13
|
+
DeliveryRouting,
|
|
14
|
+
DeliveryStatus,
|
|
15
|
+
MemberNotificationSettings,
|
|
16
|
+
NotificationKind,
|
|
17
|
+
NotificationPreference,
|
|
18
|
+
NotificationsInbox,
|
|
19
|
+
NotificationsInboxStatus,
|
|
20
|
+
WebhookSubscription,
|
|
21
|
+
WebhookSubscriptionStatus,
|
|
22
|
+
} from '../domain/types.ts';
|
|
23
|
+
import { databaseMigrations } from './migration.ts';
|
|
24
|
+
import type {
|
|
25
|
+
ClaimDeliveryInput,
|
|
26
|
+
CompleteDeliveryInput,
|
|
27
|
+
DeliveryFilters,
|
|
28
|
+
ExportCursor,
|
|
29
|
+
InboxFilters,
|
|
30
|
+
ListPage,
|
|
31
|
+
NotificationsRepository,
|
|
32
|
+
PagedRows,
|
|
33
|
+
PublishEventInput,
|
|
34
|
+
PublishEventResult,
|
|
35
|
+
StoredWebhookSubscription,
|
|
36
|
+
SubscriptionFilters,
|
|
37
|
+
} from './repository.ts';
|
|
38
|
+
import type { EncryptedSecret } from './secret-vault.ts';
|
|
39
|
+
|
|
40
|
+
interface InboxRow {
|
|
41
|
+
id: string;
|
|
42
|
+
tenant_id: string;
|
|
43
|
+
recipient_account_id: string;
|
|
44
|
+
kind: NotificationKind;
|
|
45
|
+
title: string;
|
|
46
|
+
body: string | null;
|
|
47
|
+
source_module: string;
|
|
48
|
+
source_ref: string;
|
|
49
|
+
status: NotificationsInboxStatus;
|
|
50
|
+
read_at: Date | string | null;
|
|
51
|
+
created_at: number | bigint | string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface PreferenceRow {
|
|
55
|
+
id: string;
|
|
56
|
+
tenant_id: string;
|
|
57
|
+
recipient_account_id: string;
|
|
58
|
+
kind: NotificationKind;
|
|
59
|
+
enabled: number | bigint | string;
|
|
60
|
+
created_at: number | bigint | string;
|
|
61
|
+
updated_at: number | bigint | string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
interface MemberPreferenceRow {
|
|
65
|
+
tenant_id: string;
|
|
66
|
+
recipient_account_id: string;
|
|
67
|
+
email_delivery: number | bigint | string;
|
|
68
|
+
created_at: number | bigint | string;
|
|
69
|
+
updated_at: number | bigint | string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
interface SubscriptionRow {
|
|
73
|
+
id: string;
|
|
74
|
+
tenant_id: string;
|
|
75
|
+
name: string;
|
|
76
|
+
url: string;
|
|
77
|
+
events_json: string;
|
|
78
|
+
secret_key_id: string;
|
|
79
|
+
secret_iv: string;
|
|
80
|
+
secret_tag: string;
|
|
81
|
+
secret_ciphertext: string;
|
|
82
|
+
secret_fingerprint: string;
|
|
83
|
+
secret_revision: number | bigint | string;
|
|
84
|
+
status: WebhookSubscriptionStatus;
|
|
85
|
+
description: string | null;
|
|
86
|
+
last_delivery_at: number | bigint | string | null;
|
|
87
|
+
created_at: number | bigint | string;
|
|
88
|
+
updated_at: number | bigint | string;
|
|
89
|
+
created_by: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The claim the poll loop takes before a request leaves. It is a lease on a
|
|
94
|
+
* pending attempt, not one of the four states the ledger publishes: it counts
|
|
95
|
+
* no attempt, records no outcome, and every read path answers `pending` for it.
|
|
96
|
+
*/
|
|
97
|
+
type ClaimedDeliveryStatus = 'sending';
|
|
98
|
+
|
|
99
|
+
interface DeliveryRow {
|
|
100
|
+
id: string;
|
|
101
|
+
tenant_id: string;
|
|
102
|
+
channel: DeliveryChannel;
|
|
103
|
+
subscription_id: string | null;
|
|
104
|
+
recipient_account_id: string | null;
|
|
105
|
+
kind: NotificationKind;
|
|
106
|
+
source_module: string;
|
|
107
|
+
source_ref: string;
|
|
108
|
+
title: string;
|
|
109
|
+
sequence: number | bigint | string;
|
|
110
|
+
attempt_number: number | bigint | string;
|
|
111
|
+
status: DeliveryStatus | ClaimedDeliveryStatus;
|
|
112
|
+
scheduled_for: number | bigint | string;
|
|
113
|
+
claimed_at: number | bigint | string | null;
|
|
114
|
+
completed_at: number | bigint | string | null;
|
|
115
|
+
response_status: number | bigint | string | null;
|
|
116
|
+
error_class: string | null;
|
|
117
|
+
payload_digest: string;
|
|
118
|
+
payload_bytes: number | bigint | string;
|
|
119
|
+
occurred_at: number | bigint | string;
|
|
120
|
+
created_at: number | bigint | string;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const SUBSCRIPTION_COLUMNS = `id, tenant_id, name, url, events_json,
|
|
124
|
+
secret_key_id, secret_iv, secret_tag, secret_ciphertext, secret_fingerprint,
|
|
125
|
+
secret_revision, status, description, last_delivery_at, created_at,
|
|
126
|
+
updated_at, created_by`;
|
|
127
|
+
|
|
128
|
+
const DELIVERY_COLUMNS = `id, tenant_id, channel, subscription_id,
|
|
129
|
+
recipient_account_id, kind, source_module, source_ref, title, sequence,
|
|
130
|
+
attempt_number, status, scheduled_for, completed_at, response_status,
|
|
131
|
+
error_class, payload_digest, payload_bytes, occurred_at, created_at`;
|
|
132
|
+
|
|
133
|
+
/* The three list screens page by keyset. Each list has one filter predicate
|
|
134
|
+
and one page index, and the page read is composed from them at call time so
|
|
135
|
+
the direction, the keyset and the limit stay one decision. */
|
|
136
|
+
const INBOX_LIST = `tenant_id = $1 AND recipient_account_id = $2
|
|
137
|
+
AND (($3::text IS NULL AND status <> 'archived') OR status = $3)
|
|
138
|
+
AND ($4::text IS NULL OR kind = $4)`;
|
|
139
|
+
|
|
140
|
+
/* The subscription page orders by lower(name), which keysetWhere cannot name
|
|
141
|
+
as a column, so the source names it once and the row carries it back as the
|
|
142
|
+
key the next page resumes from. */
|
|
143
|
+
const SUBSCRIPTION_PAGE_SOURCE = `SELECT ${SUBSCRIPTION_COLUMNS}, name_key
|
|
144
|
+
FROM (SELECT ${SUBSCRIPTION_COLUMNS}, lower(name) AS name_key
|
|
145
|
+
FROM notifications_webhook_subscriptions) AS subscriptions`;
|
|
146
|
+
const SUBSCRIPTION_LIST = `tenant_id = $1
|
|
147
|
+
AND ($2::text IS NULL OR status = $2)
|
|
148
|
+
AND ($3::text IS NULL OR name ILIKE $3 OR url ILIKE $3)`;
|
|
149
|
+
|
|
150
|
+
const DELIVERY_LIST = `tenant_id = $1
|
|
151
|
+
AND ($2::text IS NULL OR status = $2)
|
|
152
|
+
AND ($3::text IS NULL OR subscription_id = $3)
|
|
153
|
+
AND ($4::text IS NULL OR source_ref ILIKE $4 OR source_module ILIKE $4)`;
|
|
154
|
+
|
|
155
|
+
/* Queries stay explicit. Values always travel in the adapter's parameter
|
|
156
|
+
channel; nothing from a request is concatenated into SQL. */
|
|
157
|
+
const SQL = {
|
|
158
|
+
inboxBySource: `SELECT id FROM notifications_inbox
|
|
159
|
+
WHERE tenant_id = $1 AND kind = $2 AND source_ref = $3
|
|
160
|
+
ORDER BY id`,
|
|
161
|
+
deliveriesBySource: `SELECT id FROM notifications_deliveries
|
|
162
|
+
WHERE tenant_id = $1 AND kind = $2 AND source_ref = $3
|
|
163
|
+
ORDER BY id`,
|
|
164
|
+
insertInbox: `INSERT INTO notifications_inbox
|
|
165
|
+
(id, tenant_id, recipient_account_id, kind, title, body, source_module,
|
|
166
|
+
source_ref, status, read_at, created_at)
|
|
167
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
|
168
|
+
ON CONFLICT DO NOTHING
|
|
169
|
+
RETURNING id`,
|
|
170
|
+
countUnread: `SELECT count(*) AS total FROM notifications_inbox
|
|
171
|
+
WHERE tenant_id = $1 AND recipient_account_id = $2 AND status = 'unread'`,
|
|
172
|
+
getInboxItem: `SELECT * FROM notifications_inbox
|
|
173
|
+
WHERE tenant_id = $1 AND recipient_account_id = $2 AND id = $3`,
|
|
174
|
+
/* The unique source index answers this; it is the key a publication is
|
|
175
|
+
idempotent on, so one member holds at most one item per event. */
|
|
176
|
+
findInboxItem: `SELECT * FROM notifications_inbox
|
|
177
|
+
WHERE tenant_id = $1 AND recipient_account_id = $2 AND kind = $3
|
|
178
|
+
AND source_ref = $4`,
|
|
179
|
+
setInboxStatus: `UPDATE notifications_inbox SET status = $4, read_at = $5
|
|
180
|
+
WHERE tenant_id = $1 AND recipient_account_id = $2 AND id = $3
|
|
181
|
+
RETURNING *`,
|
|
182
|
+
/* Both walks run on notifications_inbox_retention_idx, so a page is a range
|
|
183
|
+
read of the batch rather than a sort of the workspace's history. */
|
|
184
|
+
exportInbox: `SELECT * FROM notifications_inbox
|
|
185
|
+
WHERE tenant_id = $1
|
|
186
|
+
AND ($2::bigint IS NULL OR (created_at, id) > ($2::bigint, $3::text))
|
|
187
|
+
ORDER BY created_at, id
|
|
188
|
+
LIMIT $4`,
|
|
189
|
+
deleteInboxBefore: `DELETE FROM notifications_inbox
|
|
190
|
+
WHERE id IN (
|
|
191
|
+
SELECT id FROM notifications_inbox
|
|
192
|
+
WHERE tenant_id = $1 AND created_at < $2
|
|
193
|
+
ORDER BY created_at, id LIMIT $3
|
|
194
|
+
)`,
|
|
195
|
+
|
|
196
|
+
disabledRecipients: `SELECT recipient_account_id FROM notifications_preferences
|
|
197
|
+
WHERE tenant_id = $1 AND kind = $2 AND enabled = 0`,
|
|
198
|
+
listPreferences: `SELECT * FROM notifications_preferences
|
|
199
|
+
WHERE tenant_id = $1 AND recipient_account_id = $2
|
|
200
|
+
ORDER BY kind`,
|
|
201
|
+
savePreference: `INSERT INTO notifications_preferences
|
|
202
|
+
(id, tenant_id, recipient_account_id, kind, enabled, created_at, updated_at)
|
|
203
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
204
|
+
ON CONFLICT (tenant_id, recipient_account_id, kind) DO UPDATE SET
|
|
205
|
+
enabled = EXCLUDED.enabled, updated_at = EXCLUDED.updated_at
|
|
206
|
+
RETURNING *`,
|
|
207
|
+
|
|
208
|
+
/* An absent row means the defaults, so only the members who asked for mail
|
|
209
|
+
are read back; the set bounds the e-mail rows one publication writes. */
|
|
210
|
+
emailRecipients: `SELECT recipient_account_id
|
|
211
|
+
FROM notifications_member_preferences
|
|
212
|
+
WHERE tenant_id = $1 AND email_delivery = 1`,
|
|
213
|
+
getMemberSettings: `SELECT * FROM notifications_member_preferences
|
|
214
|
+
WHERE tenant_id = $1 AND recipient_account_id = $2`,
|
|
215
|
+
saveMemberSettings: `INSERT INTO notifications_member_preferences
|
|
216
|
+
(tenant_id, recipient_account_id, email_delivery, created_at, updated_at)
|
|
217
|
+
VALUES ($1, $2, $3, $4, $5)
|
|
218
|
+
ON CONFLICT (tenant_id, recipient_account_id) DO UPDATE SET
|
|
219
|
+
email_delivery = EXCLUDED.email_delivery, updated_at = EXCLUDED.updated_at
|
|
220
|
+
RETURNING *`,
|
|
221
|
+
|
|
222
|
+
activeSubscriptionsForKind: `SELECT ${SUBSCRIPTION_COLUMNS}
|
|
223
|
+
FROM notifications_webhook_subscriptions
|
|
224
|
+
WHERE tenant_id = $1 AND status = 'active'
|
|
225
|
+
AND events_json::jsonb @> to_jsonb($2::text)
|
|
226
|
+
ORDER BY lower(name), id`,
|
|
227
|
+
getSubscription: `SELECT ${SUBSCRIPTION_COLUMNS}
|
|
228
|
+
FROM notifications_webhook_subscriptions
|
|
229
|
+
WHERE tenant_id = $1 AND id = $2`,
|
|
230
|
+
createSubscription: `INSERT INTO notifications_webhook_subscriptions
|
|
231
|
+
(${SUBSCRIPTION_COLUMNS})
|
|
232
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17)
|
|
233
|
+
ON CONFLICT DO NOTHING
|
|
234
|
+
RETURNING id`,
|
|
235
|
+
updateSubscription: `UPDATE notifications_webhook_subscriptions
|
|
236
|
+
SET name = $3, url = $4, events_json = $5, description = $6, updated_at = $7
|
|
237
|
+
WHERE tenant_id = $1 AND id = $2`,
|
|
238
|
+
setSubscriptionStatus: `UPDATE notifications_webhook_subscriptions
|
|
239
|
+
SET status = $3, updated_at = $4
|
|
240
|
+
WHERE tenant_id = $1 AND id = $2`,
|
|
241
|
+
disableSubscription: `UPDATE notifications_webhook_subscriptions
|
|
242
|
+
SET status = 'disabled', updated_at = $3
|
|
243
|
+
WHERE tenant_id = $1 AND id = $2`,
|
|
244
|
+
rotateSubscriptionSecret: `UPDATE notifications_webhook_subscriptions
|
|
245
|
+
SET secret_key_id = $3, secret_iv = $4, secret_tag = $5,
|
|
246
|
+
secret_ciphertext = $6, secret_fingerprint = $7,
|
|
247
|
+
secret_revision = secret_revision + 1, updated_at = $8
|
|
248
|
+
WHERE tenant_id = $1 AND id = $2`,
|
|
249
|
+
deleteSubscription: `DELETE FROM notifications_webhook_subscriptions
|
|
250
|
+
WHERE tenant_id = $1 AND id = $2`,
|
|
251
|
+
deletePendingDeliveries: `DELETE FROM notifications_deliveries
|
|
252
|
+
WHERE tenant_id = $1 AND subscription_id = $2 AND status = 'pending'`,
|
|
253
|
+
stampSubscriptionDelivery: `UPDATE notifications_webhook_subscriptions
|
|
254
|
+
SET last_delivery_at = $3 WHERE tenant_id = $1 AND id = $2`,
|
|
255
|
+
|
|
256
|
+
insertDelivery: `INSERT INTO notifications_deliveries (${DELIVERY_COLUMNS})
|
|
257
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20)
|
|
258
|
+
ON CONFLICT DO NOTHING
|
|
259
|
+
RETURNING id`,
|
|
260
|
+
getDelivery: `SELECT * FROM notifications_deliveries
|
|
261
|
+
WHERE tenant_id = $1 AND id = $2`,
|
|
262
|
+
/* One statement does the read back under the tenant, the due check and the
|
|
263
|
+
claim, so nothing can change between deciding to send and sending. */
|
|
264
|
+
claimDelivery: `UPDATE notifications_deliveries
|
|
265
|
+
SET status = 'sending', claimed_at = $3
|
|
266
|
+
WHERE tenant_id = $1 AND id = $2 AND scheduled_for <= $3
|
|
267
|
+
AND (status = 'pending' OR (status = 'sending' AND claimed_at <= $4))
|
|
268
|
+
RETURNING *`,
|
|
269
|
+
releaseDelivery: `UPDATE notifications_deliveries
|
|
270
|
+
SET status = 'pending', claimed_at = NULL, scheduled_for = $3
|
|
271
|
+
WHERE tenant_id = $1 AND id = $2 AND status = 'sending'`,
|
|
272
|
+
completeDelivery: `UPDATE notifications_deliveries
|
|
273
|
+
SET status = $3, completed_at = $4, response_status = $5, error_class = $6
|
|
274
|
+
WHERE tenant_id = $1 AND id = $2 AND status IN ('pending', 'sending')`,
|
|
275
|
+
latestSequence: `SELECT coalesce(max(sequence), 0) AS sequence
|
|
276
|
+
FROM notifications_deliveries
|
|
277
|
+
WHERE tenant_id = $1 AND channel = $2
|
|
278
|
+
AND coalesce(subscription_id, recipient_account_id) = $3 AND kind = $4
|
|
279
|
+
AND source_ref = $5`,
|
|
280
|
+
/* Read through the cross-tenant background lease; the attempt is read again
|
|
281
|
+
under the tenant this returned before anything leaves the process. */
|
|
282
|
+
listDueDeliveries: `SELECT tenant_id, id, scheduled_for, status
|
|
283
|
+
FROM notifications_deliveries
|
|
284
|
+
WHERE status = 'pending' AND scheduled_for <= $1
|
|
285
|
+
ORDER BY scheduled_for, id LIMIT $2`,
|
|
286
|
+
/* Kept apart from the due read so that one stays an ordered walk of the
|
|
287
|
+
routing index: a claim is a small partition, and folding it into the due
|
|
288
|
+
query with an OR would cost a sort of every due row instead. */
|
|
289
|
+
listStrandedDeliveries: `SELECT tenant_id, id, scheduled_for, status
|
|
290
|
+
FROM notifications_deliveries
|
|
291
|
+
WHERE status = 'sending' AND claimed_at <= $1
|
|
292
|
+
ORDER BY claimed_at, id LIMIT $2`,
|
|
293
|
+
listDeliveryTenants: `SELECT DISTINCT tenant_id FROM notifications_deliveries
|
|
294
|
+
WHERE tenant_id > $2 ORDER BY tenant_id LIMIT $1`,
|
|
295
|
+
exportDeliveries: `SELECT * FROM notifications_deliveries
|
|
296
|
+
WHERE tenant_id = $1
|
|
297
|
+
AND ($2::bigint IS NULL OR (created_at, id) > ($2::bigint, $3::text))
|
|
298
|
+
ORDER BY created_at, id
|
|
299
|
+
LIMIT $4`,
|
|
300
|
+
deleteCompletedBefore: `DELETE FROM notifications_deliveries
|
|
301
|
+
WHERE id IN (
|
|
302
|
+
SELECT id FROM notifications_deliveries
|
|
303
|
+
WHERE tenant_id = $1 AND status <> 'pending' AND completed_at IS NOT NULL
|
|
304
|
+
AND completed_at < $2
|
|
305
|
+
ORDER BY completed_at LIMIT $3
|
|
306
|
+
)`,
|
|
307
|
+
} as const;
|
|
308
|
+
|
|
309
|
+
/* 23505 is the SQLSTATE for a unique violation; the index name keeps a
|
|
310
|
+
different unique index on the table from being mistaken for the name. The
|
|
311
|
+
driver text is never surfaced, only the stable domain error. */
|
|
312
|
+
function isDuplicateName(error: unknown): boolean {
|
|
313
|
+
const cause = error as { code?: unknown; constraint?: unknown };
|
|
314
|
+
const text = String(error);
|
|
315
|
+
return (
|
|
316
|
+
(cause?.code === '23505' || text.includes('23505')) &&
|
|
317
|
+
(String(cause?.constraint ?? '').includes(
|
|
318
|
+
'notifications_webhook_subscriptions_name_idx',
|
|
319
|
+
) ||
|
|
320
|
+
text.includes('notifications_webhook_subscriptions_name_idx'))
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function optionalInteger(
|
|
325
|
+
value: number | bigint | string | null,
|
|
326
|
+
field: string,
|
|
327
|
+
): number | null {
|
|
328
|
+
return value === null ? null : integer(value, field);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/* The driver returns a timestamp as a Date; the domain keeps ISO text. */
|
|
332
|
+
function isoText(value: Date | string): string {
|
|
333
|
+
return value instanceof Date ? value.toISOString() : value;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/* The term is matched as a substring, so the three characters LIKE reads as
|
|
337
|
+
syntax are escaped with the backslash PostgreSQL takes as the default escape
|
|
338
|
+
character. The match runs after the tenant predicate, over that workspace's
|
|
339
|
+
rows alone. */
|
|
340
|
+
function likePattern(term: string | undefined): string | null {
|
|
341
|
+
if (term === undefined) return null;
|
|
342
|
+
return '%' + term.replace(/[\\%_]/g, (character) => '\\' + character) + '%';
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* One page read. The keyset and the ORDER BY name the same two columns in the
|
|
347
|
+
* same direction, which is the one order the list's page index carries either
|
|
348
|
+
* way round; the column names are constants here, never request input.
|
|
349
|
+
*/
|
|
350
|
+
function pageStatement(
|
|
351
|
+
source: string,
|
|
352
|
+
filters: string,
|
|
353
|
+
parameters: readonly DatabaseParameter[],
|
|
354
|
+
columns: readonly [string, string],
|
|
355
|
+
page: ListPage<string | number>,
|
|
356
|
+
): DatabaseStatement {
|
|
357
|
+
const keyset =
|
|
358
|
+
page.after === null
|
|
359
|
+
? null
|
|
360
|
+
: keysetWhere(columns, [page.after.key, page.after.id], {
|
|
361
|
+
direction: page.direction,
|
|
362
|
+
parameterOffset: parameters.length,
|
|
363
|
+
});
|
|
364
|
+
const bound = [...parameters, ...(keyset?.parameters ?? [])];
|
|
365
|
+
const order = page.direction === 'asc' ? 'ASC' : 'DESC';
|
|
366
|
+
return {
|
|
367
|
+
text: `${source}
|
|
368
|
+
WHERE ${filters}${
|
|
369
|
+
keyset === null
|
|
370
|
+
? ''
|
|
371
|
+
: `
|
|
372
|
+
AND ${keyset.text}`
|
|
373
|
+
}
|
|
374
|
+
ORDER BY ${columns[0]} ${order}, ${columns[1]} ${order}
|
|
375
|
+
LIMIT $${bound.length + 1}`,
|
|
376
|
+
parameters: [...bound, page.limit],
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function paged<
|
|
381
|
+
Row extends { readonly id: string },
|
|
382
|
+
Key extends string | number,
|
|
383
|
+
>(
|
|
384
|
+
rows: readonly Row[],
|
|
385
|
+
limit: number,
|
|
386
|
+
key: (row: Row) => Key,
|
|
387
|
+
): PagedRows<Row, Key> {
|
|
388
|
+
const last = rows.at(-1);
|
|
389
|
+
return {
|
|
390
|
+
rows,
|
|
391
|
+
next:
|
|
392
|
+
last !== undefined && rows.length === limit
|
|
393
|
+
? { key: key(last), id: last.id }
|
|
394
|
+
: null,
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function inboxFromRow(row: InboxRow): NotificationsInbox {
|
|
399
|
+
return {
|
|
400
|
+
id: row.id,
|
|
401
|
+
tenantId: row.tenant_id,
|
|
402
|
+
recipientAccountId: row.recipient_account_id,
|
|
403
|
+
kind: row.kind,
|
|
404
|
+
title: row.title,
|
|
405
|
+
body: row.body,
|
|
406
|
+
sourceModule: row.source_module,
|
|
407
|
+
sourceRef: row.source_ref,
|
|
408
|
+
status: row.status,
|
|
409
|
+
readAt: row.read_at === null ? null : isoText(row.read_at),
|
|
410
|
+
createdAt: integer(row.created_at, 'timestamp'),
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function memberSettingsFromRow(
|
|
415
|
+
row: MemberPreferenceRow,
|
|
416
|
+
): MemberNotificationSettings {
|
|
417
|
+
return {
|
|
418
|
+
tenantId: row.tenant_id,
|
|
419
|
+
recipientAccountId: row.recipient_account_id,
|
|
420
|
+
emailDelivery: integer(row.email_delivery, 'preference flag') === 1,
|
|
421
|
+
createdAt: integer(row.created_at, 'timestamp'),
|
|
422
|
+
updatedAt: integer(row.updated_at, 'timestamp'),
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function preferenceFromRow(row: PreferenceRow): NotificationPreference {
|
|
427
|
+
return {
|
|
428
|
+
id: row.id,
|
|
429
|
+
tenantId: row.tenant_id,
|
|
430
|
+
recipientAccountId: row.recipient_account_id,
|
|
431
|
+
kind: row.kind,
|
|
432
|
+
enabled: integer(row.enabled, 'preference flag') === 1,
|
|
433
|
+
createdAt: integer(row.created_at, 'timestamp'),
|
|
434
|
+
updatedAt: integer(row.updated_at, 'timestamp'),
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function subscriptionEvents(value: string): readonly NotificationKind[] {
|
|
439
|
+
try {
|
|
440
|
+
const parsed = JSON.parse(value) as unknown;
|
|
441
|
+
if (!Array.isArray(parsed)) return [];
|
|
442
|
+
return parsed.filter(
|
|
443
|
+
(entry): entry is NotificationKind => typeof entry === 'string',
|
|
444
|
+
);
|
|
445
|
+
} catch {
|
|
446
|
+
/* A row edited outside the module subscribes to nothing rather than to
|
|
447
|
+
everything; delivery fails closed. */
|
|
448
|
+
return [];
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function subscriptionFromRow(row: SubscriptionRow): StoredWebhookSubscription {
|
|
453
|
+
return {
|
|
454
|
+
id: row.id,
|
|
455
|
+
tenantId: row.tenant_id,
|
|
456
|
+
name: row.name,
|
|
457
|
+
url: row.url,
|
|
458
|
+
events: subscriptionEvents(row.events_json),
|
|
459
|
+
secretFingerprint: row.secret_fingerprint,
|
|
460
|
+
secretRevision: integer(row.secret_revision, 'secret revision'),
|
|
461
|
+
status: row.status,
|
|
462
|
+
description: row.description,
|
|
463
|
+
lastDeliveryAt: optionalInteger(row.last_delivery_at, 'timestamp'),
|
|
464
|
+
createdAt: integer(row.created_at, 'timestamp'),
|
|
465
|
+
updatedAt: integer(row.updated_at, 'timestamp'),
|
|
466
|
+
createdBy: row.created_by,
|
|
467
|
+
secret: {
|
|
468
|
+
keyId: row.secret_key_id,
|
|
469
|
+
iv: row.secret_iv,
|
|
470
|
+
tag: row.secret_tag,
|
|
471
|
+
ciphertext: row.secret_ciphertext,
|
|
472
|
+
},
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/* A claimed attempt has not completed, so the ledger keeps calling it pending:
|
|
477
|
+
the four states the specification defines are the only ones any read path,
|
|
478
|
+
screen or filter ever sees. */
|
|
479
|
+
function deliveryStatus(
|
|
480
|
+
status: DeliveryStatus | ClaimedDeliveryStatus,
|
|
481
|
+
): DeliveryStatus {
|
|
482
|
+
return status === 'sending' ? 'pending' : status;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function deliveryFromRow(row: DeliveryRow): DeliveryAttempt {
|
|
486
|
+
return {
|
|
487
|
+
id: row.id,
|
|
488
|
+
tenantId: row.tenant_id,
|
|
489
|
+
channel: row.channel,
|
|
490
|
+
subscriptionId: row.subscription_id,
|
|
491
|
+
recipientAccountId: row.recipient_account_id,
|
|
492
|
+
kind: row.kind,
|
|
493
|
+
sourceModule: row.source_module,
|
|
494
|
+
sourceRef: row.source_ref,
|
|
495
|
+
title: row.title,
|
|
496
|
+
sequence: integer(row.sequence, 'sequence'),
|
|
497
|
+
attemptNumber: integer(row.attempt_number, 'attempt number'),
|
|
498
|
+
status: deliveryStatus(row.status),
|
|
499
|
+
scheduledFor: integer(row.scheduled_for, 'timestamp'),
|
|
500
|
+
completedAt: optionalInteger(row.completed_at, 'timestamp'),
|
|
501
|
+
responseStatus: optionalInteger(row.response_status, 'response status'),
|
|
502
|
+
errorClass: row.error_class,
|
|
503
|
+
payloadDigest: row.payload_digest,
|
|
504
|
+
payloadBytes: integer(row.payload_bytes, 'payload size'),
|
|
505
|
+
occurredAt: integer(row.occurred_at, 'timestamp'),
|
|
506
|
+
createdAt: integer(row.created_at, 'timestamp'),
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function inboxParameters(
|
|
511
|
+
record: NotificationsInbox,
|
|
512
|
+
): readonly DatabaseParameter[] {
|
|
513
|
+
return [
|
|
514
|
+
record.id,
|
|
515
|
+
record.tenantId,
|
|
516
|
+
record.recipientAccountId,
|
|
517
|
+
record.kind,
|
|
518
|
+
record.title,
|
|
519
|
+
record.body,
|
|
520
|
+
record.sourceModule,
|
|
521
|
+
record.sourceRef,
|
|
522
|
+
record.status,
|
|
523
|
+
record.readAt,
|
|
524
|
+
record.createdAt,
|
|
525
|
+
];
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function deliveryParameters(
|
|
529
|
+
record: DeliveryAttempt,
|
|
530
|
+
): readonly DatabaseParameter[] {
|
|
531
|
+
return [
|
|
532
|
+
record.id,
|
|
533
|
+
record.tenantId,
|
|
534
|
+
record.channel,
|
|
535
|
+
record.subscriptionId,
|
|
536
|
+
record.recipientAccountId,
|
|
537
|
+
record.kind,
|
|
538
|
+
record.sourceModule,
|
|
539
|
+
record.sourceRef,
|
|
540
|
+
record.title,
|
|
541
|
+
record.sequence,
|
|
542
|
+
record.attemptNumber,
|
|
543
|
+
record.status,
|
|
544
|
+
record.scheduledFor,
|
|
545
|
+
record.completedAt,
|
|
546
|
+
record.responseStatus,
|
|
547
|
+
record.errorClass,
|
|
548
|
+
record.payloadDigest,
|
|
549
|
+
record.payloadBytes,
|
|
550
|
+
record.occurredAt,
|
|
551
|
+
record.createdAt,
|
|
552
|
+
];
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/** A publication that addresses nobody reads no preferences. */
|
|
556
|
+
const NO_RECIPIENTS: ReadonlySet<string> = new Set();
|
|
557
|
+
|
|
558
|
+
export interface NotificationsDatabaseHandles {
|
|
559
|
+
readonly runtime: DatabaseHandle;
|
|
560
|
+
/** Cross-tenant, read only, granted the delivery routing columns alone. */
|
|
561
|
+
readonly background: DatabaseHandle;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/** A repository over platform-owned PostgreSQL leases. */
|
|
565
|
+
export class DatabaseNotificationsRepository
|
|
566
|
+
implements NotificationsRepository
|
|
567
|
+
{
|
|
568
|
+
constructor(private readonly handles: NotificationsDatabaseHandles) {}
|
|
569
|
+
|
|
570
|
+
async #read<Row extends DatabaseRow>(
|
|
571
|
+
tenantId: string,
|
|
572
|
+
statement: DatabaseStatement,
|
|
573
|
+
): Promise<readonly Row[]> {
|
|
574
|
+
const result = await this.handles.runtime.transaction(
|
|
575
|
+
(transaction) => transaction.query<Row>(statement),
|
|
576
|
+
{ access: 'read', tenantId },
|
|
577
|
+
);
|
|
578
|
+
return result.rows;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
async #write(
|
|
582
|
+
tenantId: string,
|
|
583
|
+
statement: DatabaseStatement,
|
|
584
|
+
): Promise<number> {
|
|
585
|
+
const result = await this.handles.runtime.transaction(
|
|
586
|
+
(transaction) => transaction.execute(statement),
|
|
587
|
+
{ access: 'write', tenantId },
|
|
588
|
+
);
|
|
589
|
+
return result.affectedRows;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
async publish(input: PublishEventInput): Promise<PublishEventResult> {
|
|
593
|
+
return this.handles.runtime.transaction(
|
|
594
|
+
async (transaction) => {
|
|
595
|
+
const existing = await this.#existingEvent(transaction, input);
|
|
596
|
+
if (existing) return existing;
|
|
597
|
+
const disabled =
|
|
598
|
+
input.recipients.length === 0
|
|
599
|
+
? NO_RECIPIENTS
|
|
600
|
+
: await this.#disabledRecipients(
|
|
601
|
+
transaction,
|
|
602
|
+
input.tenantId,
|
|
603
|
+
input.kind,
|
|
604
|
+
);
|
|
605
|
+
const mailed =
|
|
606
|
+
input.recipients.length === 0
|
|
607
|
+
? NO_RECIPIENTS
|
|
608
|
+
: await this.#emailRecipients(transaction, input.tenantId);
|
|
609
|
+
const inboxItemIds: string[] = [];
|
|
610
|
+
const deliveryIds: string[] = [];
|
|
611
|
+
let addressed = 0;
|
|
612
|
+
let queued = 0;
|
|
613
|
+
for (const recipient of input.recipients) {
|
|
614
|
+
if (disabled.has(recipient)) continue;
|
|
615
|
+
addressed += 1;
|
|
616
|
+
const item = input.inboxItem(recipient);
|
|
617
|
+
const written = await transaction.query<{ id: string }>({
|
|
618
|
+
text: SQL.insertInbox,
|
|
619
|
+
parameters: [...inboxParameters(item)],
|
|
620
|
+
});
|
|
621
|
+
const id = written.rows[0]?.id;
|
|
622
|
+
if (id !== undefined) inboxItemIds.push(id);
|
|
623
|
+
/* The kind switch decided there is an item at all; this decides
|
|
624
|
+
whether the item that exists is also mailed. */
|
|
625
|
+
if (!mailed.has(recipient)) continue;
|
|
626
|
+
queued += 1;
|
|
627
|
+
const mail = await transaction.query<{ id: string }>({
|
|
628
|
+
text: SQL.insertDelivery,
|
|
629
|
+
parameters: [...deliveryParameters(input.emailDelivery(item))],
|
|
630
|
+
});
|
|
631
|
+
const mailId = mail.rows[0]?.id;
|
|
632
|
+
if (mailId !== undefined) deliveryIds.push(mailId);
|
|
633
|
+
}
|
|
634
|
+
const subscriptions = await transaction.query<SubscriptionRow>({
|
|
635
|
+
text: SQL.activeSubscriptionsForKind,
|
|
636
|
+
parameters: [input.tenantId, input.kind],
|
|
637
|
+
});
|
|
638
|
+
for (const row of subscriptions.rows) {
|
|
639
|
+
const written = await transaction.query<{ id: string }>({
|
|
640
|
+
text: SQL.insertDelivery,
|
|
641
|
+
parameters: [
|
|
642
|
+
...deliveryParameters(input.delivery(subscriptionFromRow(row))),
|
|
643
|
+
],
|
|
644
|
+
});
|
|
645
|
+
const id = written.rows[0]?.id;
|
|
646
|
+
if (id !== undefined) deliveryIds.push(id);
|
|
647
|
+
}
|
|
648
|
+
/* Fewer rows than the inserts carried means a concurrent publication
|
|
649
|
+
of the same event committed after the idempotency read took its
|
|
650
|
+
snapshot: its rows hold the unique indexes. They are this event's
|
|
651
|
+
rows, so they are read back rather than answered with the empty
|
|
652
|
+
result of an insert that conflicted. */
|
|
653
|
+
if (
|
|
654
|
+
inboxItemIds.length < addressed ||
|
|
655
|
+
deliveryIds.length < subscriptions.rows.length + queued
|
|
656
|
+
) {
|
|
657
|
+
const written = await this.#existingEvent(transaction, input);
|
|
658
|
+
if (written) return written;
|
|
659
|
+
}
|
|
660
|
+
/* Sorted, because the repeat path reads them back ordered by id: a
|
|
661
|
+
caller comparing the two results must see the same arrays. */
|
|
662
|
+
return {
|
|
663
|
+
inboxItemIds: inboxItemIds.sort(),
|
|
664
|
+
deliveryIds: deliveryIds.sort(),
|
|
665
|
+
};
|
|
666
|
+
},
|
|
667
|
+
{ access: 'write', tenantId: input.tenantId },
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/* The idempotency read and every insert share one transaction, so a repeat
|
|
672
|
+
cannot interleave between the check and the writes it guards. */
|
|
673
|
+
async #existingEvent(
|
|
674
|
+
transaction: DatabaseTransaction,
|
|
675
|
+
input: PublishEventInput,
|
|
676
|
+
): Promise<PublishEventResult | null> {
|
|
677
|
+
const parameters = [input.tenantId, input.kind, input.sourceRef];
|
|
678
|
+
const inbox = await transaction.query<{ id: string }>({
|
|
679
|
+
text: SQL.inboxBySource,
|
|
680
|
+
parameters,
|
|
681
|
+
});
|
|
682
|
+
const deliveries = await transaction.query<{ id: string }>({
|
|
683
|
+
text: SQL.deliveriesBySource,
|
|
684
|
+
parameters,
|
|
685
|
+
});
|
|
686
|
+
if (inbox.rows.length === 0 && deliveries.rows.length === 0) return null;
|
|
687
|
+
return {
|
|
688
|
+
inboxItemIds: inbox.rows.map((row) => row.id),
|
|
689
|
+
deliveryIds: deliveries.rows.map((row) => row.id),
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/* An absent row means the member never asked for mail, so only the ones who
|
|
694
|
+
did are read back. Read once per publication, not once per recipient. */
|
|
695
|
+
async #emailRecipients(
|
|
696
|
+
transaction: DatabaseTransaction,
|
|
697
|
+
tenantId: string,
|
|
698
|
+
): Promise<ReadonlySet<string>> {
|
|
699
|
+
const result = await transaction.query<{ recipient_account_id: string }>({
|
|
700
|
+
text: SQL.emailRecipients,
|
|
701
|
+
parameters: [tenantId],
|
|
702
|
+
});
|
|
703
|
+
return new Set(result.rows.map((row) => row.recipient_account_id));
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
/* An absent preference row means enabled, so only the members who switched
|
|
707
|
+
the kind off are read back. Every path that writes an inbox item crosses
|
|
708
|
+
this, a publication and a fan-out alike, so no caller can hand a member an
|
|
709
|
+
item of a kind they disabled. */
|
|
710
|
+
async #disabledRecipients(
|
|
711
|
+
transaction: DatabaseTransaction,
|
|
712
|
+
tenantId: string,
|
|
713
|
+
kind: NotificationKind,
|
|
714
|
+
): Promise<ReadonlySet<string>> {
|
|
715
|
+
const result = await transaction.query<{ recipient_account_id: string }>({
|
|
716
|
+
text: SQL.disabledRecipients,
|
|
717
|
+
parameters: [tenantId, kind],
|
|
718
|
+
});
|
|
719
|
+
return new Set(result.rows.map((row) => row.recipient_account_id));
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
async listInbox(
|
|
723
|
+
tenantId: string,
|
|
724
|
+
recipientAccountId: string,
|
|
725
|
+
filters: InboxFilters,
|
|
726
|
+
page: ListPage<number>,
|
|
727
|
+
): Promise<PagedRows<NotificationsInbox, number>> {
|
|
728
|
+
const rows = await this.#read<InboxRow>(
|
|
729
|
+
tenantId,
|
|
730
|
+
pageStatement(
|
|
731
|
+
'SELECT * FROM notifications_inbox',
|
|
732
|
+
INBOX_LIST,
|
|
733
|
+
[
|
|
734
|
+
tenantId,
|
|
735
|
+
recipientAccountId,
|
|
736
|
+
filters.status ?? null,
|
|
737
|
+
filters.kind ?? null,
|
|
738
|
+
],
|
|
739
|
+
['created_at', 'id'],
|
|
740
|
+
page,
|
|
741
|
+
),
|
|
742
|
+
);
|
|
743
|
+
return paged(rows.map(inboxFromRow), page.limit, (row) => row.createdAt);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
async exportInboxPage(
|
|
747
|
+
tenantId: string,
|
|
748
|
+
after: ExportCursor | null,
|
|
749
|
+
limit: number,
|
|
750
|
+
): Promise<readonly NotificationsInbox[]> {
|
|
751
|
+
const rows = await this.#read<InboxRow>(tenantId, {
|
|
752
|
+
text: SQL.exportInbox,
|
|
753
|
+
parameters: [
|
|
754
|
+
tenantId,
|
|
755
|
+
after?.createdAt ?? null,
|
|
756
|
+
after?.id ?? null,
|
|
757
|
+
limit,
|
|
758
|
+
],
|
|
759
|
+
});
|
|
760
|
+
return rows.map(inboxFromRow);
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
async deleteInboxItemsBefore(
|
|
764
|
+
tenantId: string,
|
|
765
|
+
before: number,
|
|
766
|
+
limit: number,
|
|
767
|
+
): Promise<number> {
|
|
768
|
+
return this.#write(tenantId, {
|
|
769
|
+
text: SQL.deleteInboxBefore,
|
|
770
|
+
parameters: [tenantId, before, limit],
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
async countUnreadInbox(
|
|
775
|
+
tenantId: string,
|
|
776
|
+
recipientAccountId: string,
|
|
777
|
+
): Promise<number> {
|
|
778
|
+
const rows = await this.#read<{ total: number | bigint | string }>(
|
|
779
|
+
tenantId,
|
|
780
|
+
{ text: SQL.countUnread, parameters: [tenantId, recipientAccountId] },
|
|
781
|
+
);
|
|
782
|
+
return integer(rows[0]?.total ?? 0, 'unread count');
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
async getInboxItem(
|
|
786
|
+
tenantId: string,
|
|
787
|
+
recipientAccountId: string,
|
|
788
|
+
id: string,
|
|
789
|
+
): Promise<NotificationsInbox | null> {
|
|
790
|
+
const rows = await this.#read<InboxRow>(tenantId, {
|
|
791
|
+
text: SQL.getInboxItem,
|
|
792
|
+
parameters: [tenantId, recipientAccountId, id],
|
|
793
|
+
});
|
|
794
|
+
return rows[0] ? inboxFromRow(rows[0]) : null;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
async setInboxStatus(
|
|
798
|
+
tenantId: string,
|
|
799
|
+
recipientAccountId: string,
|
|
800
|
+
id: string,
|
|
801
|
+
status: NotificationsInboxStatus,
|
|
802
|
+
readAt: string | null,
|
|
803
|
+
): Promise<NotificationsInbox | null> {
|
|
804
|
+
const result = await this.handles.runtime.transaction(
|
|
805
|
+
(transaction) =>
|
|
806
|
+
transaction.query<InboxRow>({
|
|
807
|
+
text: SQL.setInboxStatus,
|
|
808
|
+
parameters: [tenantId, recipientAccountId, id, status, readAt],
|
|
809
|
+
}),
|
|
810
|
+
{ access: 'write', tenantId },
|
|
811
|
+
);
|
|
812
|
+
const row = result.rows[0];
|
|
813
|
+
return row ? inboxFromRow(row) : null;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
async appendInboxItems(
|
|
817
|
+
tenantId: string,
|
|
818
|
+
records: readonly NotificationsInbox[],
|
|
819
|
+
emailDelivery: (item: NotificationsInbox) => DeliveryAttempt,
|
|
820
|
+
): Promise<readonly string[]> {
|
|
821
|
+
if (records.length === 0) return [];
|
|
822
|
+
return this.handles.runtime.transaction(
|
|
823
|
+
async (transaction) => {
|
|
824
|
+
const disabled = new Map<NotificationKind, ReadonlySet<string>>();
|
|
825
|
+
const mailed = await this.#emailRecipients(transaction, tenantId);
|
|
826
|
+
const written: string[] = [];
|
|
827
|
+
for (const record of records) {
|
|
828
|
+
let skipped = disabled.get(record.kind);
|
|
829
|
+
if (!skipped) {
|
|
830
|
+
skipped = await this.#disabledRecipients(
|
|
831
|
+
transaction,
|
|
832
|
+
tenantId,
|
|
833
|
+
record.kind,
|
|
834
|
+
);
|
|
835
|
+
disabled.set(record.kind, skipped);
|
|
836
|
+
}
|
|
837
|
+
if (skipped.has(record.recipientAccountId)) continue;
|
|
838
|
+
const result = await transaction.query<{ id: string }>({
|
|
839
|
+
text: SQL.insertInbox,
|
|
840
|
+
parameters: [...inboxParameters(record)],
|
|
841
|
+
});
|
|
842
|
+
const id = result.rows[0]?.id;
|
|
843
|
+
if (id === undefined) continue;
|
|
844
|
+
written.push(id);
|
|
845
|
+
/* Only a fresh item is mailed: a repeat wrote nothing, and the
|
|
846
|
+
member was already told. */
|
|
847
|
+
if (!mailed.has(record.recipientAccountId)) continue;
|
|
848
|
+
await transaction.query<{ id: string }>({
|
|
849
|
+
text: SQL.insertDelivery,
|
|
850
|
+
parameters: [...deliveryParameters(emailDelivery(record))],
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
return written;
|
|
854
|
+
},
|
|
855
|
+
{ access: 'write', tenantId },
|
|
856
|
+
);
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
async findInboxItem(
|
|
860
|
+
tenantId: string,
|
|
861
|
+
recipientAccountId: string,
|
|
862
|
+
kind: NotificationKind,
|
|
863
|
+
sourceRef: string,
|
|
864
|
+
): Promise<NotificationsInbox | null> {
|
|
865
|
+
const rows = await this.#read<InboxRow>(tenantId, {
|
|
866
|
+
text: SQL.findInboxItem,
|
|
867
|
+
parameters: [tenantId, recipientAccountId, kind, sourceRef],
|
|
868
|
+
});
|
|
869
|
+
return rows[0] ? inboxFromRow(rows[0]) : null;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
async getMemberSettings(
|
|
873
|
+
tenantId: string,
|
|
874
|
+
recipientAccountId: string,
|
|
875
|
+
): Promise<MemberNotificationSettings | null> {
|
|
876
|
+
const rows = await this.#read<MemberPreferenceRow>(tenantId, {
|
|
877
|
+
text: SQL.getMemberSettings,
|
|
878
|
+
parameters: [tenantId, recipientAccountId],
|
|
879
|
+
});
|
|
880
|
+
return rows[0] ? memberSettingsFromRow(rows[0]) : null;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
async saveMemberSettings(
|
|
884
|
+
record: MemberNotificationSettings,
|
|
885
|
+
): Promise<MemberNotificationSettings> {
|
|
886
|
+
const result = await this.handles.runtime.transaction(
|
|
887
|
+
(transaction) =>
|
|
888
|
+
transaction.query<MemberPreferenceRow>({
|
|
889
|
+
text: SQL.saveMemberSettings,
|
|
890
|
+
parameters: [
|
|
891
|
+
record.tenantId,
|
|
892
|
+
record.recipientAccountId,
|
|
893
|
+
record.emailDelivery ? 1 : 0,
|
|
894
|
+
record.createdAt,
|
|
895
|
+
record.updatedAt,
|
|
896
|
+
],
|
|
897
|
+
}),
|
|
898
|
+
{ access: 'write', tenantId: record.tenantId },
|
|
899
|
+
);
|
|
900
|
+
return memberSettingsFromRow(result.rows[0]!);
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
async listPreferences(
|
|
904
|
+
tenantId: string,
|
|
905
|
+
recipientAccountId: string,
|
|
906
|
+
): Promise<readonly NotificationPreference[]> {
|
|
907
|
+
const rows = await this.#read<PreferenceRow>(tenantId, {
|
|
908
|
+
text: SQL.listPreferences,
|
|
909
|
+
parameters: [tenantId, recipientAccountId],
|
|
910
|
+
});
|
|
911
|
+
return rows.map(preferenceFromRow);
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
async savePreference(
|
|
915
|
+
record: NotificationPreference,
|
|
916
|
+
): Promise<NotificationPreference> {
|
|
917
|
+
const result = await this.handles.runtime.transaction(
|
|
918
|
+
(transaction) =>
|
|
919
|
+
transaction.query<PreferenceRow>({
|
|
920
|
+
text: SQL.savePreference,
|
|
921
|
+
parameters: [
|
|
922
|
+
record.id,
|
|
923
|
+
record.tenantId,
|
|
924
|
+
record.recipientAccountId,
|
|
925
|
+
record.kind,
|
|
926
|
+
record.enabled ? 1 : 0,
|
|
927
|
+
record.createdAt,
|
|
928
|
+
record.updatedAt,
|
|
929
|
+
],
|
|
930
|
+
}),
|
|
931
|
+
{ access: 'write', tenantId: record.tenantId },
|
|
932
|
+
);
|
|
933
|
+
return preferenceFromRow(result.rows[0]!);
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
async listSubscriptions(
|
|
937
|
+
tenantId: string,
|
|
938
|
+
filters: SubscriptionFilters,
|
|
939
|
+
page: ListPage<string>,
|
|
940
|
+
): Promise<PagedRows<StoredWebhookSubscription, string>> {
|
|
941
|
+
const rows = await this.#read<SubscriptionRow & { name_key: string }>(
|
|
942
|
+
tenantId,
|
|
943
|
+
pageStatement(
|
|
944
|
+
SUBSCRIPTION_PAGE_SOURCE,
|
|
945
|
+
SUBSCRIPTION_LIST,
|
|
946
|
+
[tenantId, filters.status ?? null, likePattern(filters.search)],
|
|
947
|
+
['name_key', 'id'],
|
|
948
|
+
page,
|
|
949
|
+
),
|
|
950
|
+
);
|
|
951
|
+
const keys = new Map(rows.map((row) => [row.id, row.name_key]));
|
|
952
|
+
return paged(
|
|
953
|
+
rows.map(subscriptionFromRow),
|
|
954
|
+
page.limit,
|
|
955
|
+
(row) => keys.get(row.id)!,
|
|
956
|
+
);
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
async getSubscription(
|
|
960
|
+
tenantId: string,
|
|
961
|
+
id: string,
|
|
962
|
+
): Promise<StoredWebhookSubscription | null> {
|
|
963
|
+
const rows = await this.#read<SubscriptionRow>(tenantId, {
|
|
964
|
+
text: SQL.getSubscription,
|
|
965
|
+
parameters: [tenantId, id],
|
|
966
|
+
});
|
|
967
|
+
return rows[0] ? subscriptionFromRow(rows[0]) : null;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
async createSubscription(
|
|
971
|
+
record: StoredWebhookSubscription,
|
|
972
|
+
): Promise<StoredWebhookSubscription | null> {
|
|
973
|
+
const result = await this.handles.runtime.transaction(
|
|
974
|
+
(transaction) =>
|
|
975
|
+
transaction.query<{ id: string }>({
|
|
976
|
+
text: SQL.createSubscription,
|
|
977
|
+
parameters: [
|
|
978
|
+
record.id,
|
|
979
|
+
record.tenantId,
|
|
980
|
+
record.name,
|
|
981
|
+
record.url,
|
|
982
|
+
JSON.stringify(record.events),
|
|
983
|
+
record.secret.keyId,
|
|
984
|
+
record.secret.iv,
|
|
985
|
+
record.secret.tag,
|
|
986
|
+
record.secret.ciphertext,
|
|
987
|
+
record.secretFingerprint,
|
|
988
|
+
record.secretRevision,
|
|
989
|
+
record.status,
|
|
990
|
+
record.description,
|
|
991
|
+
record.lastDeliveryAt,
|
|
992
|
+
record.createdAt,
|
|
993
|
+
record.updatedAt,
|
|
994
|
+
record.createdBy,
|
|
995
|
+
],
|
|
996
|
+
}),
|
|
997
|
+
{ access: 'write', tenantId: record.tenantId },
|
|
998
|
+
);
|
|
999
|
+
return result.rows.length === 1 ? record : null;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
async updateSubscription(
|
|
1003
|
+
record: Pick<
|
|
1004
|
+
WebhookSubscription,
|
|
1005
|
+
| 'tenantId'
|
|
1006
|
+
| 'id'
|
|
1007
|
+
| 'name'
|
|
1008
|
+
| 'url'
|
|
1009
|
+
| 'events'
|
|
1010
|
+
| 'description'
|
|
1011
|
+
| 'updatedAt'
|
|
1012
|
+
>,
|
|
1013
|
+
): Promise<StoredWebhookSubscription | null | 'conflict'> {
|
|
1014
|
+
let affected: number;
|
|
1015
|
+
try {
|
|
1016
|
+
affected = await this.#write(record.tenantId, {
|
|
1017
|
+
text: SQL.updateSubscription,
|
|
1018
|
+
parameters: [
|
|
1019
|
+
record.tenantId,
|
|
1020
|
+
record.id,
|
|
1021
|
+
record.name,
|
|
1022
|
+
record.url,
|
|
1023
|
+
JSON.stringify(record.events),
|
|
1024
|
+
record.description,
|
|
1025
|
+
record.updatedAt,
|
|
1026
|
+
],
|
|
1027
|
+
});
|
|
1028
|
+
} catch (error) {
|
|
1029
|
+
if (isDuplicateName(error)) return 'conflict';
|
|
1030
|
+
throw error;
|
|
1031
|
+
}
|
|
1032
|
+
return affected === 1
|
|
1033
|
+
? await this.getSubscription(record.tenantId, record.id)
|
|
1034
|
+
: null;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
async setSubscriptionStatus(
|
|
1038
|
+
tenantId: string,
|
|
1039
|
+
id: string,
|
|
1040
|
+
status: WebhookSubscriptionStatus,
|
|
1041
|
+
updatedAt: number,
|
|
1042
|
+
): Promise<StoredWebhookSubscription | null> {
|
|
1043
|
+
const affected = await this.#write(tenantId, {
|
|
1044
|
+
text: SQL.setSubscriptionStatus,
|
|
1045
|
+
parameters: [tenantId, id, status, updatedAt],
|
|
1046
|
+
});
|
|
1047
|
+
return affected === 1 ? await this.getSubscription(tenantId, id) : null;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
/* The status change and the queue drop share one transaction, so the poll
|
|
1051
|
+
loop can never read a still-active row whose pending attempts are already
|
|
1052
|
+
gone, or send an attempt the operator has just dropped. */
|
|
1053
|
+
async disableSubscription(
|
|
1054
|
+
tenantId: string,
|
|
1055
|
+
id: string,
|
|
1056
|
+
updatedAt: number,
|
|
1057
|
+
): Promise<StoredWebhookSubscription | null> {
|
|
1058
|
+
const disabled = await this.handles.runtime.transaction(
|
|
1059
|
+
async (transaction) => {
|
|
1060
|
+
const updated = await transaction.execute({
|
|
1061
|
+
text: SQL.disableSubscription,
|
|
1062
|
+
parameters: [tenantId, id, updatedAt],
|
|
1063
|
+
});
|
|
1064
|
+
if (updated.affectedRows !== 1) return false;
|
|
1065
|
+
await transaction.execute({
|
|
1066
|
+
text: SQL.deletePendingDeliveries,
|
|
1067
|
+
parameters: [tenantId, id],
|
|
1068
|
+
});
|
|
1069
|
+
return true;
|
|
1070
|
+
},
|
|
1071
|
+
{ access: 'write', tenantId },
|
|
1072
|
+
);
|
|
1073
|
+
return disabled ? await this.getSubscription(tenantId, id) : null;
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
async rotateSubscriptionSecret(
|
|
1077
|
+
tenantId: string,
|
|
1078
|
+
id: string,
|
|
1079
|
+
secret: EncryptedSecret,
|
|
1080
|
+
fingerprint: string,
|
|
1081
|
+
updatedAt: number,
|
|
1082
|
+
): Promise<StoredWebhookSubscription | null> {
|
|
1083
|
+
const affected = await this.#write(tenantId, {
|
|
1084
|
+
text: SQL.rotateSubscriptionSecret,
|
|
1085
|
+
parameters: [
|
|
1086
|
+
tenantId,
|
|
1087
|
+
id,
|
|
1088
|
+
secret.keyId,
|
|
1089
|
+
secret.iv,
|
|
1090
|
+
secret.tag,
|
|
1091
|
+
secret.ciphertext,
|
|
1092
|
+
fingerprint,
|
|
1093
|
+
updatedAt,
|
|
1094
|
+
],
|
|
1095
|
+
});
|
|
1096
|
+
return affected === 1 ? await this.getSubscription(tenantId, id) : null;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
async deleteSubscription(tenantId: string, id: string): Promise<boolean> {
|
|
1100
|
+
return this.handles.runtime.transaction(
|
|
1101
|
+
async (transaction) => {
|
|
1102
|
+
await transaction.execute({
|
|
1103
|
+
text: SQL.deletePendingDeliveries,
|
|
1104
|
+
parameters: [tenantId, id],
|
|
1105
|
+
});
|
|
1106
|
+
const removed = await transaction.execute({
|
|
1107
|
+
text: SQL.deleteSubscription,
|
|
1108
|
+
parameters: [tenantId, id],
|
|
1109
|
+
});
|
|
1110
|
+
return removed.affectedRows === 1;
|
|
1111
|
+
},
|
|
1112
|
+
{ access: 'write', tenantId },
|
|
1113
|
+
);
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
async stampSubscriptionDelivery(
|
|
1117
|
+
tenantId: string,
|
|
1118
|
+
id: string,
|
|
1119
|
+
at: number,
|
|
1120
|
+
): Promise<void> {
|
|
1121
|
+
await this.#write(tenantId, {
|
|
1122
|
+
text: SQL.stampSubscriptionDelivery,
|
|
1123
|
+
parameters: [tenantId, id, at],
|
|
1124
|
+
});
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
async listDeliveries(
|
|
1128
|
+
tenantId: string,
|
|
1129
|
+
filters: DeliveryFilters,
|
|
1130
|
+
page: ListPage<number>,
|
|
1131
|
+
): Promise<PagedRows<DeliveryAttempt, number>> {
|
|
1132
|
+
const rows = await this.#read<DeliveryRow>(
|
|
1133
|
+
tenantId,
|
|
1134
|
+
pageStatement(
|
|
1135
|
+
'SELECT * FROM notifications_deliveries',
|
|
1136
|
+
DELIVERY_LIST,
|
|
1137
|
+
[
|
|
1138
|
+
tenantId,
|
|
1139
|
+
filters.status ?? null,
|
|
1140
|
+
filters.subscriptionId ?? null,
|
|
1141
|
+
likePattern(filters.search),
|
|
1142
|
+
],
|
|
1143
|
+
['scheduled_for', 'id'],
|
|
1144
|
+
page,
|
|
1145
|
+
),
|
|
1146
|
+
);
|
|
1147
|
+
return paged(
|
|
1148
|
+
rows.map(deliveryFromRow),
|
|
1149
|
+
page.limit,
|
|
1150
|
+
(row) => row.scheduledFor,
|
|
1151
|
+
);
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
async getDelivery(
|
|
1155
|
+
tenantId: string,
|
|
1156
|
+
id: string,
|
|
1157
|
+
): Promise<DeliveryAttempt | null> {
|
|
1158
|
+
const rows = await this.#read<DeliveryRow>(tenantId, {
|
|
1159
|
+
text: SQL.getDelivery,
|
|
1160
|
+
parameters: [tenantId, id],
|
|
1161
|
+
});
|
|
1162
|
+
return rows[0] ? deliveryFromRow(rows[0]) : null;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
async appendDelivery(
|
|
1166
|
+
record: DeliveryAttempt,
|
|
1167
|
+
): Promise<DeliveryAttempt | null> {
|
|
1168
|
+
const result = await this.handles.runtime.transaction(
|
|
1169
|
+
(transaction) =>
|
|
1170
|
+
transaction.query<{ id: string }>({
|
|
1171
|
+
text: SQL.insertDelivery,
|
|
1172
|
+
parameters: [...deliveryParameters(record)],
|
|
1173
|
+
}),
|
|
1174
|
+
{ access: 'write', tenantId: record.tenantId },
|
|
1175
|
+
);
|
|
1176
|
+
return result.rows.length === 1 ? record : null;
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
async claimDelivery(
|
|
1180
|
+
input: ClaimDeliveryInput,
|
|
1181
|
+
): Promise<DeliveryAttempt | null> {
|
|
1182
|
+
const result = await this.handles.runtime.transaction(
|
|
1183
|
+
(transaction) =>
|
|
1184
|
+
transaction.query<DeliveryRow>({
|
|
1185
|
+
text: SQL.claimDelivery,
|
|
1186
|
+
parameters: [
|
|
1187
|
+
input.tenantId,
|
|
1188
|
+
input.id,
|
|
1189
|
+
input.now,
|
|
1190
|
+
input.strandedBefore,
|
|
1191
|
+
],
|
|
1192
|
+
}),
|
|
1193
|
+
{ access: 'write', tenantId: input.tenantId },
|
|
1194
|
+
);
|
|
1195
|
+
const row = result.rows[0];
|
|
1196
|
+
return row ? deliveryFromRow(row) : null;
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
async releaseDelivery(
|
|
1200
|
+
tenantId: string,
|
|
1201
|
+
id: string,
|
|
1202
|
+
scheduledFor: number,
|
|
1203
|
+
): Promise<boolean> {
|
|
1204
|
+
const affected = await this.#write(tenantId, {
|
|
1205
|
+
text: SQL.releaseDelivery,
|
|
1206
|
+
parameters: [tenantId, id, scheduledFor],
|
|
1207
|
+
});
|
|
1208
|
+
return affected === 1;
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
async completeDelivery(input: CompleteDeliveryInput): Promise<boolean> {
|
|
1212
|
+
const affected = await this.#write(input.tenantId, {
|
|
1213
|
+
text: SQL.completeDelivery,
|
|
1214
|
+
parameters: [
|
|
1215
|
+
input.tenantId,
|
|
1216
|
+
input.id,
|
|
1217
|
+
input.status,
|
|
1218
|
+
input.completedAt,
|
|
1219
|
+
input.responseStatus,
|
|
1220
|
+
input.errorClass,
|
|
1221
|
+
],
|
|
1222
|
+
});
|
|
1223
|
+
return affected === 1;
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
async latestDeliverySequence(
|
|
1227
|
+
attempt: Pick<
|
|
1228
|
+
DeliveryAttempt,
|
|
1229
|
+
| 'tenantId'
|
|
1230
|
+
| 'channel'
|
|
1231
|
+
| 'subscriptionId'
|
|
1232
|
+
| 'recipientAccountId'
|
|
1233
|
+
| 'kind'
|
|
1234
|
+
| 'sourceRef'
|
|
1235
|
+
>,
|
|
1236
|
+
): Promise<number> {
|
|
1237
|
+
const rows = await this.#read<{ sequence: number | bigint | string }>(
|
|
1238
|
+
attempt.tenantId,
|
|
1239
|
+
{
|
|
1240
|
+
text: SQL.latestSequence,
|
|
1241
|
+
parameters: [
|
|
1242
|
+
attempt.tenantId,
|
|
1243
|
+
attempt.channel,
|
|
1244
|
+
attempt.subscriptionId ?? attempt.recipientAccountId,
|
|
1245
|
+
attempt.kind,
|
|
1246
|
+
attempt.sourceRef,
|
|
1247
|
+
],
|
|
1248
|
+
},
|
|
1249
|
+
);
|
|
1250
|
+
return integer(rows[0]?.sequence ?? 0, 'sequence');
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
async listDueDeliveries(
|
|
1254
|
+
now: number,
|
|
1255
|
+
limit: number,
|
|
1256
|
+
): Promise<readonly DeliveryRouting[]> {
|
|
1257
|
+
return this.#route(SQL.listDueDeliveries, now, limit);
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
async listStrandedDeliveries(
|
|
1261
|
+
before: number,
|
|
1262
|
+
limit: number,
|
|
1263
|
+
): Promise<readonly DeliveryRouting[]> {
|
|
1264
|
+
return this.#route(SQL.listStrandedDeliveries, before, limit);
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
async #route(
|
|
1268
|
+
text: string,
|
|
1269
|
+
bound: number,
|
|
1270
|
+
limit: number,
|
|
1271
|
+
): Promise<readonly DeliveryRouting[]> {
|
|
1272
|
+
const result = await this.handles.background.query<{
|
|
1273
|
+
tenant_id: string;
|
|
1274
|
+
id: string;
|
|
1275
|
+
scheduled_for: number | bigint | string;
|
|
1276
|
+
status: DeliveryStatus | ClaimedDeliveryStatus;
|
|
1277
|
+
}>({ text, parameters: [bound, limit] });
|
|
1278
|
+
return result.rows.map((row) => ({
|
|
1279
|
+
tenantId: row.tenant_id,
|
|
1280
|
+
id: row.id,
|
|
1281
|
+
scheduledFor: integer(row.scheduled_for, 'timestamp'),
|
|
1282
|
+
status: deliveryStatus(row.status),
|
|
1283
|
+
}));
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
async listDeliveryTenants(
|
|
1287
|
+
limit: number,
|
|
1288
|
+
after: string,
|
|
1289
|
+
): Promise<readonly string[]> {
|
|
1290
|
+
const result = await this.handles.background.query<{ tenant_id: string }>({
|
|
1291
|
+
text: SQL.listDeliveryTenants,
|
|
1292
|
+
parameters: [limit, after],
|
|
1293
|
+
});
|
|
1294
|
+
return result.rows.map((row) => row.tenant_id);
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
async deleteCompletedDeliveriesBefore(
|
|
1298
|
+
tenantId: string,
|
|
1299
|
+
before: number,
|
|
1300
|
+
limit: number,
|
|
1301
|
+
): Promise<number> {
|
|
1302
|
+
return this.#write(tenantId, {
|
|
1303
|
+
text: SQL.deleteCompletedBefore,
|
|
1304
|
+
parameters: [tenantId, before, limit],
|
|
1305
|
+
});
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
async exportDeliveriesPage(
|
|
1309
|
+
tenantId: string,
|
|
1310
|
+
after: ExportCursor | null,
|
|
1311
|
+
limit: number,
|
|
1312
|
+
): Promise<readonly DeliveryAttempt[]> {
|
|
1313
|
+
const rows = await this.#read<DeliveryRow>(tenantId, {
|
|
1314
|
+
text: SQL.exportDeliveries,
|
|
1315
|
+
parameters: [
|
|
1316
|
+
tenantId,
|
|
1317
|
+
after?.createdAt ?? null,
|
|
1318
|
+
after?.id ?? null,
|
|
1319
|
+
limit,
|
|
1320
|
+
],
|
|
1321
|
+
});
|
|
1322
|
+
return rows.map(deliveryFromRow);
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
export async function migrateNotificationsDatabase(
|
|
1327
|
+
database: DatabaseHandle,
|
|
1328
|
+
): Promise<void> {
|
|
1329
|
+
await runDatabaseMigrations(
|
|
1330
|
+
database,
|
|
1331
|
+
'notifications.core',
|
|
1332
|
+
databaseMigrations,
|
|
1333
|
+
);
|
|
1334
|
+
}
|