@de-otio/trellis 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/dist/db.js +10 -18
- package/dist/db.js.map +1 -1
- package/dist/env.d.ts +66 -6
- package/dist/env.d.ts.map +1 -1
- package/dist/env.js +89 -70
- package/dist/env.js.map +1 -1
- package/dist/extensions.js +3 -8
- package/dist/extensions.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -9
- package/dist/index.js.map +1 -1
- package/dist/lambda/cleanup-cron.d.ts.map +1 -1
- package/dist/lambda/cleanup-cron.js +20 -24
- package/dist/lambda/cleanup-cron.js.map +1 -1
- package/dist/lambda/create-auth-challenge.d.ts.map +1 -1
- package/dist/lambda/create-auth-challenge.js +17 -19
- package/dist/lambda/create-auth-challenge.js.map +1 -1
- package/dist/lambda/custom-message.js +1 -5
- package/dist/lambda/custom-message.js.map +1 -1
- package/dist/lambda/define-auth-challenge.js +1 -5
- package/dist/lambda/define-auth-challenge.js.map +1 -1
- package/dist/lambda/delete-account-worker.d.ts.map +1 -1
- package/dist/lambda/delete-account-worker.js +25 -58
- package/dist/lambda/delete-account-worker.js.map +1 -1
- package/dist/lambda/diagnostics-proxy.d.ts.map +1 -1
- package/dist/lambda/diagnostics-proxy.js +14 -49
- package/dist/lambda/diagnostics-proxy.js.map +1 -1
- package/dist/lambda/e2e-sweeper.d.ts.map +1 -1
- package/dist/lambda/e2e-sweeper.js +30 -38
- package/dist/lambda/e2e-sweeper.js.map +1 -1
- package/dist/lambda/federation-outbox-worker.d.ts.map +1 -1
- package/dist/lambda/federation-outbox-worker.js +4 -6
- package/dist/lambda/federation-outbox-worker.js.map +1 -1
- package/dist/lambda/followers-events-worker.d.ts.map +1 -1
- package/dist/lambda/followers-events-worker.js +4 -6
- package/dist/lambda/followers-events-worker.js.map +1 -1
- package/dist/lambda/hourly-cron.d.ts.map +1 -1
- package/dist/lambda/hourly-cron.js +100 -32
- package/dist/lambda/hourly-cron.js.map +1 -1
- package/dist/lambda/link-check-worker.d.ts.map +1 -1
- package/dist/lambda/link-check-worker.js +4 -6
- package/dist/lambda/link-check-worker.js.map +1 -1
- package/dist/lambda/maintenance-cron.d.ts.map +1 -1
- package/dist/lambda/maintenance-cron.js +30 -63
- package/dist/lambda/maintenance-cron.js.map +1 -1
- package/dist/lambda/media-processing-worker.d.ts.map +1 -1
- package/dist/lambda/media-processing-worker.js +11 -46
- package/dist/lambda/media-processing-worker.js.map +1 -1
- package/dist/lambda/media-reconciliation-worker.d.ts.map +1 -1
- package/dist/lambda/media-reconciliation-worker.js +4 -6
- package/dist/lambda/media-reconciliation-worker.js.map +1 -1
- package/dist/lambda/nightly-cron.d.ts.map +1 -1
- package/dist/lambda/nightly-cron.js +67 -112
- package/dist/lambda/nightly-cron.js.map +1 -1
- package/dist/lambda/post-confirmation.d.ts.map +1 -1
- package/dist/lambda/post-confirmation.js +147 -45
- package/dist/lambda/post-confirmation.js.map +1 -1
- package/dist/lambda/pre-signup.js +7 -11
- package/dist/lambda/pre-signup.js.map +1 -1
- package/dist/lambda/pre-token-generation.d.ts.map +1 -1
- package/dist/lambda/pre-token-generation.js +27 -35
- package/dist/lambda/pre-token-generation.js.map +1 -1
- package/dist/lambda/tools/check-health.js +1 -5
- package/dist/lambda/tools/check-health.js.map +1 -1
- package/dist/lambda/tools/describe-services.js +4 -8
- package/dist/lambda/tools/describe-services.js.map +1 -1
- package/dist/lambda/tools/get-cost-report.js +4 -8
- package/dist/lambda/tools/get-cost-report.js.map +1 -1
- package/dist/lambda/tools/get-errors.js +5 -9
- package/dist/lambda/tools/get-errors.js.map +1 -1
- package/dist/lambda/tools/get-feature-flags.js +4 -8
- package/dist/lambda/tools/get-feature-flags.js.map +1 -1
- package/dist/lambda/tools/get-queue-status.js +5 -9
- package/dist/lambda/tools/get-queue-status.js.map +1 -1
- package/dist/lambda/tools/search-logs.js +5 -9
- package/dist/lambda/tools/search-logs.js.map +1 -1
- package/dist/lambda/tools/send-alert.js +4 -8
- package/dist/lambda/tools/send-alert.js.map +1 -1
- package/dist/lambda/verify-auth-challenge.d.ts.map +1 -1
- package/dist/lambda/verify-auth-challenge.js +10 -12
- package/dist/lambda/verify-auth-challenge.js.map +1 -1
- package/dist/lib/abuse-metrics.d.ts.map +1 -1
- package/dist/lib/abuse-metrics.js +10 -13
- package/dist/lib/abuse-metrics.js.map +1 -1
- package/dist/lib/activitypub/activity-processor.d.ts +1 -1
- package/dist/lib/activitypub/activity-processor.d.ts.map +1 -1
- package/dist/lib/activitypub/activity-processor.js +9 -43
- package/dist/lib/activitypub/activity-processor.js.map +1 -1
- package/dist/lib/activitypub/activity-service.js +1 -5
- package/dist/lib/activitypub/activity-service.js.map +1 -1
- package/dist/lib/activitypub/actor.d.ts +1 -1
- package/dist/lib/activitypub/actor.d.ts.map +1 -1
- package/dist/lib/activitypub/actor.js +1 -5
- package/dist/lib/activitypub/actor.js.map +1 -1
- package/dist/lib/activitypub/audience-service.d.ts +2 -2
- package/dist/lib/activitypub/audience-service.d.ts.map +1 -1
- package/dist/lib/activitypub/audience-service.js +8 -12
- package/dist/lib/activitypub/audience-service.js.map +1 -1
- package/dist/lib/activitypub/crypto.d.ts +1 -1
- package/dist/lib/activitypub/crypto.d.ts.map +1 -1
- package/dist/lib/activitypub/crypto.js +3 -41
- package/dist/lib/activitypub/crypto.js.map +1 -1
- package/dist/lib/activitypub/delivery-service.d.ts +5 -5
- package/dist/lib/activitypub/delivery-service.d.ts.map +1 -1
- package/dist/lib/activitypub/delivery-service.js +10 -47
- package/dist/lib/activitypub/delivery-service.js.map +1 -1
- package/dist/lib/activitypub/dispatchers/entity-actor.d.ts +3 -2
- package/dist/lib/activitypub/dispatchers/entity-actor.d.ts.map +1 -1
- package/dist/lib/activitypub/dispatchers/entity-actor.js +19 -23
- package/dist/lib/activitypub/dispatchers/entity-actor.js.map +1 -1
- package/dist/lib/activitypub/dispatchers/group-actor.d.ts +3 -2
- package/dist/lib/activitypub/dispatchers/group-actor.d.ts.map +1 -1
- package/dist/lib/activitypub/dispatchers/group-actor.js +19 -23
- package/dist/lib/activitypub/dispatchers/group-actor.js.map +1 -1
- package/dist/lib/activitypub/dispatchers/user-actor.d.ts +3 -2
- package/dist/lib/activitypub/dispatchers/user-actor.d.ts.map +1 -1
- package/dist/lib/activitypub/dispatchers/user-actor.js +16 -20
- package/dist/lib/activitypub/dispatchers/user-actor.js.map +1 -1
- package/dist/lib/activitypub/dm-service.js +1 -5
- package/dist/lib/activitypub/dm-service.js.map +1 -1
- package/dist/lib/activitypub/entity-profile-service.d.ts +1 -1
- package/dist/lib/activitypub/entity-profile-service.d.ts.map +1 -1
- package/dist/lib/activitypub/entity-profile-service.js +6 -10
- package/dist/lib/activitypub/entity-profile-service.js.map +1 -1
- package/dist/lib/activitypub/fedify/config.d.ts +3 -3
- package/dist/lib/activitypub/fedify/config.d.ts.map +1 -1
- package/dist/lib/activitypub/fedify/config.js +5 -8
- package/dist/lib/activitypub/fedify/config.js.map +1 -1
- package/dist/lib/activitypub/fedify/context.d.ts +1 -1
- package/dist/lib/activitypub/fedify/context.d.ts.map +1 -1
- package/dist/lib/activitypub/fedify/context.js +8 -12
- package/dist/lib/activitypub/fedify/context.js.map +1 -1
- package/dist/lib/activitypub/fedify/runtime.d.ts +1 -1
- package/dist/lib/activitypub/fedify/runtime.d.ts.map +1 -1
- package/dist/lib/activitypub/fedify/runtime.js +3 -6
- package/dist/lib/activitypub/fedify/runtime.js.map +1 -1
- package/dist/lib/activitypub/friendship-service.js +1 -5
- package/dist/lib/activitypub/friendship-service.js.map +1 -1
- package/dist/lib/activitypub/group-service.d.ts +1 -1
- package/dist/lib/activitypub/group-service.d.ts.map +1 -1
- package/dist/lib/activitypub/group-service.js +9 -46
- package/dist/lib/activitypub/group-service.js.map +1 -1
- package/dist/lib/activitypub/http-signatures.js +8 -45
- package/dist/lib/activitypub/http-signatures.js.map +1 -1
- package/dist/lib/activitypub/jsonld.d.ts +1 -1
- package/dist/lib/activitypub/jsonld.d.ts.map +1 -1
- package/dist/lib/activitypub/jsonld.js +1 -5
- package/dist/lib/activitypub/jsonld.js.map +1 -1
- package/dist/lib/activitypub/listeners/friends-collection.d.ts +1 -1
- package/dist/lib/activitypub/listeners/friends-collection.d.ts.map +1 -1
- package/dist/lib/activitypub/listeners/friends-collection.js +17 -20
- package/dist/lib/activitypub/listeners/friends-collection.js.map +1 -1
- package/dist/lib/activitypub/listeners/http-signatures.d.ts +1 -1
- package/dist/lib/activitypub/listeners/http-signatures.d.ts.map +1 -1
- package/dist/lib/activitypub/listeners/http-signatures.js +9 -46
- package/dist/lib/activitypub/listeners/http-signatures.js.map +1 -1
- package/dist/lib/activitypub/listeners/inbox.d.ts +2 -2
- package/dist/lib/activitypub/listeners/inbox.d.ts.map +1 -1
- package/dist/lib/activitypub/listeners/inbox.js +31 -35
- package/dist/lib/activitypub/listeners/inbox.js.map +1 -1
- package/dist/lib/activitypub/listeners/outbox.d.ts +1 -1
- package/dist/lib/activitypub/listeners/outbox.d.ts.map +1 -1
- package/dist/lib/activitypub/listeners/outbox.js +17 -20
- package/dist/lib/activitypub/listeners/outbox.js.map +1 -1
- package/dist/lib/activitypub/remote-fetch-service.d.ts +6 -6
- package/dist/lib/activitypub/remote-fetch-service.d.ts.map +1 -1
- package/dist/lib/activitypub/remote-fetch-service.js +6 -10
- package/dist/lib/activitypub/remote-fetch-service.js.map +1 -1
- package/dist/lib/activitypub/services/abuse-prevention.d.ts +1 -1
- package/dist/lib/activitypub/services/abuse-prevention.d.ts.map +1 -1
- package/dist/lib/activitypub/services/abuse-prevention.js +11 -17
- package/dist/lib/activitypub/services/abuse-prevention.js.map +1 -1
- package/dist/lib/activitypub/services/dm-service-fedify.d.ts +4 -4
- package/dist/lib/activitypub/services/dm-service-fedify.d.ts.map +1 -1
- package/dist/lib/activitypub/services/dm-service-fedify.js +24 -59
- package/dist/lib/activitypub/services/dm-service-fedify.js.map +1 -1
- package/dist/lib/activitypub/services/fedify-converters.d.ts +2 -2
- package/dist/lib/activitypub/services/fedify-converters.d.ts.map +1 -1
- package/dist/lib/activitypub/services/fedify-converters.js +3 -8
- package/dist/lib/activitypub/services/fedify-converters.js.map +1 -1
- package/dist/lib/activitypub/services/fedify-delivery.d.ts +2 -2
- package/dist/lib/activitypub/services/fedify-delivery.d.ts.map +1 -1
- package/dist/lib/activitypub/services/fedify-delivery.js +19 -56
- package/dist/lib/activitypub/services/fedify-delivery.js.map +1 -1
- package/dist/lib/activitypub/services/follow-activity-service.d.ts +2 -2
- package/dist/lib/activitypub/services/follow-activity-service.d.ts.map +1 -1
- package/dist/lib/activitypub/services/follow-activity-service.js +8 -12
- package/dist/lib/activitypub/services/follow-activity-service.js.map +1 -1
- package/dist/lib/activitypub/services/post-service-fedify.d.ts +2 -2
- package/dist/lib/activitypub/services/post-service-fedify.d.ts.map +1 -1
- package/dist/lib/activitypub/services/post-service-fedify.js +33 -65
- package/dist/lib/activitypub/services/post-service-fedify.js.map +1 -1
- package/dist/lib/activitypub/services/remote-activity-handler.d.ts +2 -2
- package/dist/lib/activitypub/services/remote-activity-handler.d.ts.map +1 -1
- package/dist/lib/activitypub/services/remote-activity-handler.js +25 -28
- package/dist/lib/activitypub/services/remote-activity-handler.js.map +1 -1
- package/dist/lib/activitypub/standalone-mode.d.ts +1 -1
- package/dist/lib/activitypub/standalone-mode.d.ts.map +1 -1
- package/dist/lib/activitypub/standalone-mode.js +13 -50
- package/dist/lib/activitypub/standalone-mode.js.map +1 -1
- package/dist/lib/activitypub/webfinger/server.d.ts +1 -1
- package/dist/lib/activitypub/webfinger/server.d.ts.map +1 -1
- package/dist/lib/activitypub/webfinger/server.js +18 -54
- package/dist/lib/activitypub/webfinger/server.js.map +1 -1
- package/dist/lib/age-gate-middleware.d.ts +4 -4
- package/dist/lib/age-gate-middleware.d.ts.map +1 -1
- package/dist/lib/age-gate-middleware.js +3 -6
- package/dist/lib/age-gate-middleware.js.map +1 -1
- package/dist/lib/age-gate.js +3 -8
- package/dist/lib/age-gate.js.map +1 -1
- package/dist/lib/age-tier-transition.d.ts +1 -1
- package/dist/lib/age-tier-transition.d.ts.map +1 -1
- package/dist/lib/age-tier-transition.js +7 -44
- package/dist/lib/age-tier-transition.js.map +1 -1
- package/dist/lib/app.d.ts +76 -0
- package/dist/lib/app.d.ts.map +1 -0
- package/dist/lib/app.js +400 -0
- package/dist/lib/app.js.map +1 -0
- package/dist/lib/audit/csv-export.js +6 -13
- package/dist/lib/audit/csv-export.js.map +1 -1
- package/dist/lib/audit/pii-filter.d.ts +9 -0
- package/dist/lib/audit/pii-filter.d.ts.map +1 -1
- package/dist/lib/audit/pii-filter.js +57 -7
- package/dist/lib/audit/pii-filter.js.map +1 -1
- package/dist/lib/audit-actions.d.ts +94 -0
- package/dist/lib/audit-actions.d.ts.map +1 -0
- package/dist/lib/audit-actions.js +107 -0
- package/dist/lib/audit-actions.js.map +1 -0
- package/dist/lib/audit-composer.d.ts +174 -0
- package/dist/lib/audit-composer.d.ts.map +1 -0
- package/dist/lib/audit-composer.js +421 -0
- package/dist/lib/audit-composer.js.map +1 -0
- package/dist/lib/auth/auth-context.d.ts +1 -1
- package/dist/lib/auth/auth-context.js +1 -2
- package/dist/lib/auth/auth-context.js.map +1 -1
- package/dist/lib/auth/auth-middleware.d.ts +16 -2
- package/dist/lib/auth/auth-middleware.d.ts.map +1 -1
- package/dist/lib/auth/auth-middleware.js +36 -45
- package/dist/lib/auth/auth-middleware.js.map +1 -1
- package/dist/lib/auth/capabilities.js +2 -5
- package/dist/lib/auth/capabilities.js.map +1 -1
- package/dist/lib/auth/claims-cache.d.ts +2 -2
- package/dist/lib/auth/claims-cache.js +19 -24
- package/dist/lib/auth/claims-cache.js.map +1 -1
- package/dist/lib/auth/cognito-jwt.d.ts +20 -2
- package/dist/lib/auth/cognito-jwt.d.ts.map +1 -1
- package/dist/lib/auth/cognito-jwt.js +83 -23
- package/dist/lib/auth/cognito-jwt.js.map +1 -1
- package/dist/lib/auth/idp-redirect-builder.d.ts +1 -1
- package/dist/lib/auth/idp-redirect-builder.d.ts.map +1 -1
- package/dist/lib/auth/idp-redirect-builder.js +4 -10
- package/dist/lib/auth/idp-redirect-builder.js.map +1 -1
- package/dist/lib/auth/require.d.ts +4 -4
- package/dist/lib/auth/require.d.ts.map +1 -1
- package/dist/lib/auth/require.js +11 -18
- package/dist/lib/auth/require.js.map +1 -1
- package/dist/lib/auth/role-grants.d.ts +1 -1
- package/dist/lib/auth/role-grants.d.ts.map +1 -1
- package/dist/lib/auth/role-grants.js +28 -31
- package/dist/lib/auth/role-grants.js.map +1 -1
- package/dist/lib/auth-context-manager.js +1 -5
- package/dist/lib/auth-context-manager.js.map +1 -1
- package/dist/lib/auth-handler.d.ts +5 -5
- package/dist/lib/auth-handler.d.ts.map +1 -1
- package/dist/lib/auth-handler.js +5 -9
- package/dist/lib/auth-handler.js.map +1 -1
- package/dist/lib/badge-handler.d.ts +1 -1
- package/dist/lib/badge-handler.d.ts.map +1 -1
- package/dist/lib/badge-handler.js +14 -52
- package/dist/lib/badge-handler.js.map +1 -1
- package/dist/lib/circle-handler.d.ts +10 -10
- package/dist/lib/circle-handler.d.ts.map +1 -1
- package/dist/lib/circle-handler.js +10 -47
- package/dist/lib/circle-handler.js.map +1 -1
- package/dist/lib/cognito/idp-sdk.js +11 -18
- package/dist/lib/cognito/idp-sdk.js.map +1 -1
- package/dist/lib/cognito/issuer-probe.js +9 -14
- package/dist/lib/cognito/issuer-probe.js.map +1 -1
- package/dist/lib/comment-handler.d.ts +10 -10
- package/dist/lib/comment-handler.d.ts.map +1 -1
- package/dist/lib/comment-handler.js +61 -97
- package/dist/lib/comment-handler.js.map +1 -1
- package/dist/lib/compliance/baseline.d.ts +2 -2
- package/dist/lib/compliance/baseline.d.ts.map +1 -1
- package/dist/lib/compliance/baseline.js +15 -18
- package/dist/lib/compliance/baseline.js.map +1 -1
- package/dist/lib/compliance/tenant-merge.d.ts +1 -1
- package/dist/lib/compliance/tenant-merge.d.ts.map +1 -1
- package/dist/lib/compliance/tenant-merge.js +1 -4
- package/dist/lib/compliance/tenant-merge.js.map +1 -1
- package/dist/lib/compliance/types.d.ts +1 -1
- package/dist/lib/compliance/types.js +2 -3
- package/dist/lib/compliance/types.js.map +1 -1
- package/dist/lib/connection-code-handler.d.ts +7 -7
- package/dist/lib/connection-code-handler.d.ts.map +1 -1
- package/dist/lib/connection-code-handler.js +13 -50
- package/dist/lib/connection-code-handler.js.map +1 -1
- package/dist/lib/content-discovery.d.ts +1 -1
- package/dist/lib/content-discovery.d.ts.map +1 -1
- package/dist/lib/content-discovery.js +15 -52
- package/dist/lib/content-discovery.js.map +1 -1
- package/dist/lib/context-aware-data-access.d.ts +1 -1
- package/dist/lib/context-aware-data-access.d.ts.map +1 -1
- package/dist/lib/context-aware-data-access.js +1 -5
- package/dist/lib/context-aware-data-access.js.map +1 -1
- package/dist/lib/cors-handler.d.ts +1 -1
- package/dist/lib/cors-handler.d.ts.map +1 -1
- package/dist/lib/cors-handler.js +13 -17
- package/dist/lib/cors-handler.js.map +1 -1
- package/dist/lib/cost-accumulator.d.ts.map +1 -1
- package/dist/lib/cost-accumulator.js +7 -11
- package/dist/lib/cost-accumulator.js.map +1 -1
- package/dist/lib/crypto/voting/elgamal-encryption.js +1 -5
- package/dist/lib/crypto/voting/elgamal-encryption.js.map +1 -1
- package/dist/lib/crypto/voting/encryption-scheme.js +1 -2
- package/dist/lib/crypto/voting/encryption-scheme.js.map +1 -1
- package/dist/lib/crypto/voting/hash-utils.js +6 -12
- package/dist/lib/crypto/voting/hash-utils.js.map +1 -1
- package/dist/lib/crypto/voting/hybrid-encryption.js +5 -9
- package/dist/lib/crypto/voting/hybrid-encryption.js.map +1 -1
- package/dist/lib/crypto/voting/index.js +4 -14
- package/dist/lib/crypto/voting/index.js.map +1 -1
- package/dist/lib/crypto/voting/post-quantum-encryption.js +1 -5
- package/dist/lib/crypto/voting/post-quantum-encryption.js.map +1 -1
- package/dist/lib/csrf.d.ts +2 -2
- package/dist/lib/csrf.d.ts.map +1 -1
- package/dist/lib/csrf.js +1 -5
- package/dist/lib/csrf.js.map +1 -1
- package/dist/lib/data-router.d.ts +5 -4
- package/dist/lib/data-router.d.ts.map +1 -1
- package/dist/lib/data-router.js +60 -90
- package/dist/lib/data-router.js.map +1 -1
- package/dist/lib/database-circuit-breaker.d.ts +61 -34
- package/dist/lib/database-circuit-breaker.d.ts.map +1 -1
- package/dist/lib/database-circuit-breaker.js +102 -109
- package/dist/lib/database-circuit-breaker.js.map +1 -1
- package/dist/lib/database-config.js +1 -4
- package/dist/lib/database-config.js.map +1 -1
- package/dist/lib/database-connection-manager.d.ts +42 -2
- package/dist/lib/database-connection-manager.d.ts.map +1 -1
- package/dist/lib/database-connection-manager.js +178 -74
- package/dist/lib/database-connection-manager.js.map +1 -1
- package/dist/lib/database-monitor.d.ts +1 -1
- package/dist/lib/database-monitor.d.ts.map +1 -1
- package/dist/lib/database-monitor.js +5 -9
- package/dist/lib/database-monitor.js.map +1 -1
- package/dist/lib/database-rate-limiter.d.ts +1 -1
- package/dist/lib/database-rate-limiter.d.ts.map +1 -1
- package/dist/lib/database-rate-limiter.js +3 -7
- package/dist/lib/database-rate-limiter.js.map +1 -1
- package/dist/lib/database-wrapper-helper.d.ts +2 -2
- package/dist/lib/database-wrapper-helper.d.ts.map +1 -1
- package/dist/lib/database-wrapper-helper.js +7 -11
- package/dist/lib/database-wrapper-helper.js.map +1 -1
- package/dist/lib/database-wrapper.d.ts +1 -1
- package/dist/lib/database-wrapper.d.ts.map +1 -1
- package/dist/lib/database-wrapper.js +5 -9
- package/dist/lib/database-wrapper.js.map +1 -1
- package/dist/lib/db-query-helper.d.ts +3 -3
- package/dist/lib/db-query-helper.d.ts.map +1 -1
- package/dist/lib/db-query-helper.js +4 -9
- package/dist/lib/db-query-helper.js.map +1 -1
- package/dist/lib/discovery-exposure.d.ts +42 -0
- package/dist/lib/discovery-exposure.d.ts.map +1 -0
- package/dist/lib/discovery-exposure.js +89 -0
- package/dist/lib/discovery-exposure.js.map +1 -0
- package/dist/lib/discovery-handler.d.ts +6 -6
- package/dist/lib/discovery-handler.d.ts.map +1 -1
- package/dist/lib/discovery-handler.js +10 -43
- package/dist/lib/discovery-handler.js.map +1 -1
- package/dist/lib/domain-reputation-service.d.ts +1 -1
- package/dist/lib/domain-reputation-service.d.ts.map +1 -1
- package/dist/lib/domain-reputation-service.js +12 -15
- package/dist/lib/domain-reputation-service.js.map +1 -1
- package/dist/lib/email-privacy.js +4 -8
- package/dist/lib/email-privacy.js.map +1 -1
- package/dist/lib/email-provider.d.ts +2 -2
- package/dist/lib/email-provider.d.ts.map +1 -1
- package/dist/lib/email-provider.js +8 -16
- package/dist/lib/email-provider.js.map +1 -1
- package/dist/lib/entity-handler.d.ts +5 -6
- package/dist/lib/entity-handler.d.ts.map +1 -1
- package/dist/lib/entity-handler.js +45 -80
- package/dist/lib/entity-handler.js.map +1 -1
- package/dist/lib/entity-relationship-handler.d.ts +9 -9
- package/dist/lib/entity-relationship-handler.d.ts.map +1 -1
- package/dist/lib/entity-relationship-handler.js +14 -51
- package/dist/lib/entity-relationship-handler.js.map +1 -1
- package/dist/lib/entity-tagging-errors.js +4 -11
- package/dist/lib/entity-tagging-errors.js.map +1 -1
- package/dist/lib/entity-tagging-validator.d.ts +3 -3
- package/dist/lib/entity-tagging-validator.d.ts.map +1 -1
- package/dist/lib/entity-tagging-validator.js +6 -11
- package/dist/lib/entity-tagging-validator.js.map +1 -1
- package/dist/lib/exif-stripper.js +1 -4
- package/dist/lib/exif-stripper.js.map +1 -1
- package/dist/lib/extension-context.d.ts +2 -2
- package/dist/lib/extension-context.d.ts.map +1 -1
- package/dist/lib/extension-context.js +1 -4
- package/dist/lib/extension-context.js.map +1 -1
- package/dist/lib/extension-route-wrapper.d.ts +1 -1
- package/dist/lib/extension-route-wrapper.d.ts.map +1 -1
- package/dist/lib/extension-route-wrapper.js +17 -55
- package/dist/lib/extension-route-wrapper.js.map +1 -1
- package/dist/lib/extension-validator.js +3 -6
- package/dist/lib/extension-validator.js.map +1 -1
- package/dist/lib/feature-flags.d.ts +5 -2
- package/dist/lib/feature-flags.d.ts.map +1 -1
- package/dist/lib/feature-flags.js +15 -48
- package/dist/lib/feature-flags.js.map +1 -1
- package/dist/lib/feature-toggle-global-client.d.ts +6 -0
- package/dist/lib/feature-toggle-global-client.d.ts.map +1 -0
- package/dist/lib/feature-toggle-global-client.js +73 -0
- package/dist/lib/feature-toggle-global-client.js.map +1 -0
- package/dist/lib/feature-toggle-service.d.ts +137 -27
- package/dist/lib/feature-toggle-service.d.ts.map +1 -1
- package/dist/lib/feature-toggle-service.js +302 -119
- package/dist/lib/feature-toggle-service.js.map +1 -1
- package/dist/lib/feed-handler.d.ts +8 -8
- package/dist/lib/feed-handler.d.ts.map +1 -1
- package/dist/lib/feed-handler.js +33 -62
- package/dist/lib/feed-handler.js.map +1 -1
- package/dist/lib/feed-pagination.d.ts +26 -0
- package/dist/lib/feed-pagination.d.ts.map +1 -1
- package/dist/lib/feed-pagination.js +31 -11
- package/dist/lib/feed-pagination.js.map +1 -1
- package/dist/lib/feed-personalization.d.ts +1 -1
- package/dist/lib/feed-personalization.d.ts.map +1 -1
- package/dist/lib/feed-personalization.js +6 -43
- package/dist/lib/feed-personalization.js.map +1 -1
- package/dist/lib/followers-events.js +8 -13
- package/dist/lib/followers-events.js.map +1 -1
- package/dist/lib/friends-handler.d.ts +2 -2
- package/dist/lib/friends-handler.d.ts.map +1 -1
- package/dist/lib/friends-handler.js +9 -46
- package/dist/lib/friends-handler.js.map +1 -1
- package/dist/lib/geo/entity-geo-repository.d.ts +67 -0
- package/dist/lib/geo/entity-geo-repository.d.ts.map +1 -0
- package/dist/lib/geo/entity-geo-repository.js +91 -0
- package/dist/lib/geo/entity-geo-repository.js.map +1 -0
- package/dist/lib/graph/errors.d.ts.map +1 -1
- package/dist/lib/graph/errors.js +13 -18
- package/dist/lib/graph/errors.js.map +1 -1
- package/dist/lib/graph/graph-factory.d.ts +12 -53
- package/dist/lib/graph/graph-factory.d.ts.map +1 -1
- package/dist/lib/graph/graph-factory.js +67 -162
- package/dist/lib/graph/graph-factory.js.map +1 -1
- package/dist/lib/graph/graph-service.d.ts +1 -1
- package/dist/lib/graph/graph-service.d.ts.map +1 -1
- package/dist/lib/graph/graph-service.js +1 -2
- package/dist/lib/graph/graph-service.js.map +1 -1
- package/dist/lib/graph/index.d.ts +10 -14
- package/dist/lib/graph/index.d.ts.map +1 -1
- package/dist/lib/graph/index.js +12 -46
- package/dist/lib/graph/index.js.map +1 -1
- package/dist/lib/graph/postgres/_shared.d.ts +18 -0
- package/dist/lib/graph/postgres/_shared.d.ts.map +1 -0
- package/dist/lib/graph/postgres/_shared.js +24 -0
- package/dist/lib/graph/postgres/_shared.js.map +1 -0
- package/dist/lib/graph/postgres/circles.d.ts +66 -0
- package/dist/lib/graph/postgres/circles.d.ts.map +1 -0
- package/dist/lib/graph/postgres/circles.js +513 -0
- package/dist/lib/graph/postgres/circles.js.map +1 -0
- package/dist/lib/graph/postgres/discovery.d.ts +165 -0
- package/dist/lib/graph/postgres/discovery.d.ts.map +1 -0
- package/dist/lib/graph/postgres/discovery.js +579 -0
- package/dist/lib/graph/postgres/discovery.js.map +1 -0
- package/dist/lib/graph/postgres/entity-relationships.d.ts +53 -0
- package/dist/lib/graph/postgres/entity-relationships.d.ts.map +1 -0
- package/dist/lib/graph/postgres/entity-relationships.js +304 -0
- package/dist/lib/graph/postgres/entity-relationships.js.map +1 -0
- package/dist/lib/graph/postgres/interaction-events.d.ts +106 -0
- package/dist/lib/graph/postgres/interaction-events.d.ts.map +1 -0
- package/dist/lib/graph/postgres/interaction-events.js +162 -0
- package/dist/lib/graph/postgres/interaction-events.js.map +1 -0
- package/dist/lib/graph/postgres/postgres-graph-service.d.ts +74 -0
- package/dist/lib/graph/postgres/postgres-graph-service.d.ts.map +1 -0
- package/dist/lib/graph/postgres/postgres-graph-service.js +167 -0
- package/dist/lib/graph/postgres/postgres-graph-service.js.map +1 -0
- package/dist/lib/graph/postgres/relationships.d.ts +58 -0
- package/dist/lib/graph/postgres/relationships.d.ts.map +1 -0
- package/dist/lib/graph/postgres/relationships.js +314 -0
- package/dist/lib/graph/postgres/relationships.js.map +1 -0
- package/dist/lib/graph/postgres/scoring.d.ts +74 -0
- package/dist/lib/graph/postgres/scoring.d.ts.map +1 -0
- package/dist/lib/graph/postgres/scoring.js +297 -0
- package/dist/lib/graph/postgres/scoring.js.map +1 -0
- package/dist/lib/graph/postgres/sync.d.ts +149 -0
- package/dist/lib/graph/postgres/sync.d.ts.map +1 -0
- package/dist/lib/graph/postgres/sync.js +269 -0
- package/dist/lib/graph/postgres/sync.js.map +1 -0
- package/dist/lib/graph/scoring-engine.d.ts +7 -1
- package/dist/lib/graph/scoring-engine.d.ts.map +1 -1
- package/dist/lib/graph/scoring-engine.js +29 -35
- package/dist/lib/graph/scoring-engine.js.map +1 -1
- package/dist/lib/graph/types.d.ts +18 -1
- package/dist/lib/graph/types.d.ts.map +1 -1
- package/dist/lib/graph/types.js +1 -2
- package/dist/lib/graph/types.js.map +1 -1
- package/dist/lib/hook-dispatcher.d.ts +1 -1
- package/dist/lib/hook-dispatcher.d.ts.map +1 -1
- package/dist/lib/hook-dispatcher.js +8 -12
- package/dist/lib/hook-dispatcher.js.map +1 -1
- package/dist/lib/input-sanitizer.js +1 -5
- package/dist/lib/input-sanitizer.js.map +1 -1
- package/dist/lib/internal-docs-handler.d.ts +2 -2
- package/dist/lib/internal-docs-handler.d.ts.map +1 -1
- package/dist/lib/internal-docs-handler.js +20 -28
- package/dist/lib/internal-docs-handler.js.map +1 -1
- package/dist/lib/internal-docs-navigation.js +2 -6
- package/dist/lib/internal-docs-navigation.js.map +1 -1
- package/dist/lib/invitation-handler.d.ts +2 -2
- package/dist/lib/invitation-handler.d.ts.map +1 -1
- package/dist/lib/invitation-handler.js +41 -82
- package/dist/lib/invitation-handler.js.map +1 -1
- package/dist/lib/ip-scrubber.js +3 -8
- package/dist/lib/ip-scrubber.js.map +1 -1
- package/dist/lib/link-security-handler.d.ts +3 -2
- package/dist/lib/link-security-handler.d.ts.map +1 -1
- package/dist/lib/link-security-handler.js +8 -44
- package/dist/lib/link-security-handler.js.map +1 -1
- package/dist/lib/logger.d.ts +31 -82
- package/dist/lib/logger.d.ts.map +1 -1
- package/dist/lib/logger.js +43 -185
- package/dist/lib/logger.js.map +1 -1
- package/dist/lib/media-cleanup-handler.d.ts +2 -2
- package/dist/lib/media-cleanup-handler.d.ts.map +1 -1
- package/dist/lib/media-cleanup-handler.js +7 -11
- package/dist/lib/media-cleanup-handler.js.map +1 -1
- package/dist/lib/media-handler.d.ts +1 -1
- package/dist/lib/media-handler.d.ts.map +1 -1
- package/dist/lib/media-handler.js +36 -73
- package/dist/lib/media-handler.js.map +1 -1
- package/dist/lib/media-metadata-extractor.d.ts +1 -1
- package/dist/lib/media-metadata-extractor.d.ts.map +1 -1
- package/dist/lib/media-metadata-extractor.js +3 -7
- package/dist/lib/media-metadata-extractor.js.map +1 -1
- package/dist/lib/media-metrics.d.ts +2 -2
- package/dist/lib/media-metrics.d.ts.map +1 -1
- package/dist/lib/media-metrics.js +3 -7
- package/dist/lib/media-metrics.js.map +1 -1
- package/dist/lib/metadata/index.d.ts +5 -5
- package/dist/lib/metadata/index.d.ts.map +1 -1
- package/dist/lib/metadata/index.js +5 -21
- package/dist/lib/metadata/index.js.map +1 -1
- package/dist/lib/metadata/metadata-config.js +2 -5
- package/dist/lib/metadata/metadata-config.js.map +1 -1
- package/dist/lib/metadata/metadata-errors.js +2 -7
- package/dist/lib/metadata/metadata-errors.js.map +1 -1
- package/dist/lib/metadata/metadata-extractor.d.ts +1 -1
- package/dist/lib/metadata/metadata-extractor.d.ts.map +1 -1
- package/dist/lib/metadata/metadata-extractor.js +42 -82
- package/dist/lib/metadata/metadata-extractor.js.map +1 -1
- package/dist/lib/metadata/metadata-sanitizer.js +17 -24
- package/dist/lib/metadata/metadata-sanitizer.js.map +1 -1
- package/dist/lib/metadata/metadata-schemas.d.ts +16 -100
- package/dist/lib/metadata/metadata-schemas.d.ts.map +1 -1
- package/dist/lib/metadata/metadata-schemas.js +31 -34
- package/dist/lib/metadata/metadata-schemas.js.map +1 -1
- package/dist/lib/mfa/mfa-handler.d.ts +1 -1
- package/dist/lib/mfa/mfa-handler.d.ts.map +1 -1
- package/dist/lib/mfa/mfa-handler.js +13 -17
- package/dist/lib/mfa/mfa-handler.js.map +1 -1
- package/dist/lib/mfa/totp-service.js +8 -18
- package/dist/lib/mfa/totp-service.js.map +1 -1
- package/dist/lib/middleware/comment-rate-limit.d.ts +1 -1
- package/dist/lib/middleware/comment-rate-limit.d.ts.map +1 -1
- package/dist/lib/middleware/comment-rate-limit.js +7 -10
- package/dist/lib/middleware/comment-rate-limit.js.map +1 -1
- package/dist/lib/middleware/feature-toggle-rate-limit.d.ts +1 -1
- package/dist/lib/middleware/feature-toggle-rate-limit.d.ts.map +1 -1
- package/dist/lib/middleware/feature-toggle-rate-limit.js +8 -13
- package/dist/lib/middleware/feature-toggle-rate-limit.js.map +1 -1
- package/dist/lib/middleware/idempotency-store.js +20 -26
- package/dist/lib/middleware/idempotency-store.js.map +1 -1
- package/dist/lib/middleware/idempotency.d.ts +2 -2
- package/dist/lib/middleware/idempotency.d.ts.map +1 -1
- package/dist/lib/middleware/idempotency.js +12 -50
- package/dist/lib/middleware/idempotency.js.map +1 -1
- package/dist/lib/middleware.d.ts +22 -9
- package/dist/lib/middleware.d.ts.map +1 -1
- package/dist/lib/middleware.js +72 -153
- package/dist/lib/middleware.js.map +1 -1
- package/dist/lib/moderation-handler.d.ts +1 -1
- package/dist/lib/moderation-handler.d.ts.map +1 -1
- package/dist/lib/moderation-handler.js +15 -54
- package/dist/lib/moderation-handler.js.map +1 -1
- package/dist/lib/net/trusted-client-ip.d.ts +8 -30
- package/dist/lib/net/trusted-client-ip.d.ts.map +1 -1
- package/dist/lib/net/trusted-client-ip.js +13 -94
- package/dist/lib/net/trusted-client-ip.js.map +1 -1
- package/dist/lib/notification-handler.d.ts +1 -1
- package/dist/lib/notification-handler.d.ts.map +1 -1
- package/dist/lib/notification-handler.js +10 -15
- package/dist/lib/notification-handler.js.map +1 -1
- package/dist/lib/notification-preferences-handler.d.ts +1 -1
- package/dist/lib/notification-preferences-handler.d.ts.map +1 -1
- package/dist/lib/notification-preferences-handler.js +7 -11
- package/dist/lib/notification-preferences-handler.js.map +1 -1
- package/dist/lib/oauth/cognito-issuer.d.ts +1 -1
- package/dist/lib/oauth/cognito-issuer.d.ts.map +1 -1
- package/dist/lib/oauth/cognito-issuer.js +5 -10
- package/dist/lib/oauth/cognito-issuer.js.map +1 -1
- package/dist/lib/oauth/device-authorization.d.ts +1 -1
- package/dist/lib/oauth/device-authorization.d.ts.map +1 -1
- package/dist/lib/oauth/device-authorization.js +62 -77
- package/dist/lib/oauth/device-authorization.js.map +1 -1
- package/dist/lib/oauth/envelope-crypto.d.ts +2 -2
- package/dist/lib/oauth/envelope-crypto.js +22 -34
- package/dist/lib/oauth/envelope-crypto.js.map +1 -1
- package/dist/lib/oauth/refresh-detection.js +42 -52
- package/dist/lib/oauth/refresh-detection.js.map +1 -1
- package/dist/lib/openai-budget.d.ts.map +1 -1
- package/dist/lib/openai-budget.js +7 -44
- package/dist/lib/openai-budget.js.map +1 -1
- package/dist/lib/openapi/generator.d.ts +1 -1
- package/dist/lib/openapi/generator.d.ts.map +1 -1
- package/dist/lib/openapi/generator.js +2 -6
- package/dist/lib/openapi/generator.js.map +1 -1
- package/dist/lib/orphaned-media-handler.d.ts +1 -1
- package/dist/lib/orphaned-media-handler.d.ts.map +1 -1
- package/dist/lib/orphaned-media-handler.js +9 -46
- package/dist/lib/orphaned-media-handler.js.map +1 -1
- package/dist/lib/parental-control-handler.d.ts +2 -2
- package/dist/lib/parental-control-handler.d.ts.map +1 -1
- package/dist/lib/parental-control-handler.js +18 -55
- package/dist/lib/parental-control-handler.js.map +1 -1
- package/dist/lib/parental-link-handler.d.ts +8 -8
- package/dist/lib/parental-link-handler.d.ts.map +1 -1
- package/dist/lib/parental-link-handler.js +10 -14
- package/dist/lib/parental-link-handler.js.map +1 -1
- package/dist/lib/performance-metrics.d.ts +1 -1
- package/dist/lib/performance-metrics.d.ts.map +1 -1
- package/dist/lib/performance-metrics.js +3 -6
- package/dist/lib/performance-metrics.js.map +1 -1
- package/dist/lib/post-handler.d.ts +9 -9
- package/dist/lib/post-handler.d.ts.map +1 -1
- package/dist/lib/post-handler.js +67 -101
- package/dist/lib/post-handler.js.map +1 -1
- package/dist/lib/privacy-defaults.js +3 -8
- package/dist/lib/privacy-defaults.js.map +1 -1
- package/dist/lib/privacy-handler.d.ts +2 -2
- package/dist/lib/privacy-handler.d.ts.map +1 -1
- package/dist/lib/privacy-handler.js +6 -10
- package/dist/lib/privacy-handler.js.map +1 -1
- package/dist/lib/pseudonym.d.ts +56 -0
- package/dist/lib/pseudonym.d.ts.map +1 -0
- package/dist/lib/pseudonym.js +85 -0
- package/dist/lib/pseudonym.js.map +1 -0
- package/dist/lib/queue-consumers/media-reconciliation-consumer.d.ts +2 -2
- package/dist/lib/queue-consumers/media-reconciliation-consumer.d.ts.map +1 -1
- package/dist/lib/queue-consumers/media-reconciliation-consumer.js +5 -8
- package/dist/lib/queue-consumers/media-reconciliation-consumer.js.map +1 -1
- package/dist/lib/quiet-hours.js +2 -6
- package/dist/lib/quiet-hours.js.map +1 -1
- package/dist/lib/rate-limit.d.ts +58 -47
- package/dist/lib/rate-limit.d.ts.map +1 -1
- package/dist/lib/rate-limit.js +168 -157
- package/dist/lib/rate-limit.js.map +1 -1
- package/dist/lib/reaction-handler.d.ts +10 -10
- package/dist/lib/reaction-handler.d.ts.map +1 -1
- package/dist/lib/reaction-handler.js +44 -80
- package/dist/lib/reaction-handler.js.map +1 -1
- package/dist/lib/recaptcha.js +6 -9
- package/dist/lib/recaptcha.js.map +1 -1
- package/dist/lib/redirect-resolver.d.ts +2 -2
- package/dist/lib/redirect-resolver.d.ts.map +1 -1
- package/dist/lib/redirect-resolver.js +5 -9
- package/dist/lib/redirect-resolver.js.map +1 -1
- package/dist/lib/region-config.d.ts +3 -3
- package/dist/lib/region-config.d.ts.map +1 -1
- package/dist/lib/region-config.js +15 -58
- package/dist/lib/region-config.js.map +1 -1
- package/dist/lib/region-detection.d.ts +55 -24
- package/dist/lib/region-detection.d.ts.map +1 -1
- package/dist/lib/region-detection.js +140 -199
- package/dist/lib/region-detection.js.map +1 -1
- package/dist/lib/region-registry.d.ts +49 -0
- package/dist/lib/region-registry.d.ts.map +1 -0
- package/dist/lib/region-registry.js +112 -0
- package/dist/lib/region-registry.js.map +1 -0
- package/dist/lib/relationship-handler.d.ts +9 -9
- package/dist/lib/relationship-handler.d.ts.map +1 -1
- package/dist/lib/relationship-handler.js +12 -49
- package/dist/lib/relationship-handler.js.map +1 -1
- package/dist/lib/request-context.d.ts +16 -16
- package/dist/lib/request-context.d.ts.map +1 -1
- package/dist/lib/request-context.js +14 -22
- package/dist/lib/request-context.js.map +1 -1
- package/dist/lib/route-helpers.d.ts +3 -4
- package/dist/lib/route-helpers.d.ts.map +1 -1
- package/dist/lib/route-helpers.js +20 -75
- package/dist/lib/route-helpers.js.map +1 -1
- package/dist/lib/routes/activitypub/actor.d.ts +1 -1
- package/dist/lib/routes/activitypub/actor.d.ts.map +1 -1
- package/dist/lib/routes/activitypub/actor.js +20 -23
- package/dist/lib/routes/activitypub/actor.js.map +1 -1
- package/dist/lib/routes/activitypub/audiences.d.ts +1 -1
- package/dist/lib/routes/activitypub/audiences.d.ts.map +1 -1
- package/dist/lib/routes/activitypub/audiences.js +76 -80
- package/dist/lib/routes/activitypub/audiences.js.map +1 -1
- package/dist/lib/routes/activitypub/collections.d.ts +1 -1
- package/dist/lib/routes/activitypub/collections.d.ts.map +1 -1
- package/dist/lib/routes/activitypub/collections.js +24 -26
- package/dist/lib/routes/activitypub/collections.js.map +1 -1
- package/dist/lib/routes/activitypub/entity-profile.d.ts +1 -1
- package/dist/lib/routes/activitypub/entity-profile.d.ts.map +1 -1
- package/dist/lib/routes/activitypub/entity-profile.js +36 -39
- package/dist/lib/routes/activitypub/entity-profile.js.map +1 -1
- package/dist/lib/routes/activitypub/friends.d.ts +1 -1
- package/dist/lib/routes/activitypub/friends.d.ts.map +1 -1
- package/dist/lib/routes/activitypub/friends.js +9 -12
- package/dist/lib/routes/activitypub/friends.js.map +1 -1
- package/dist/lib/routes/activitypub/group.d.ts +1 -1
- package/dist/lib/routes/activitypub/group.d.ts.map +1 -1
- package/dist/lib/routes/activitypub/group.js +91 -94
- package/dist/lib/routes/activitypub/group.js.map +1 -1
- package/dist/lib/routes/activitypub/inbox.d.ts +1 -1
- package/dist/lib/routes/activitypub/inbox.d.ts.map +1 -1
- package/dist/lib/routes/activitypub/inbox.js +30 -33
- package/dist/lib/routes/activitypub/inbox.js.map +1 -1
- package/dist/lib/routes/activitypub/messages.d.ts +1 -1
- package/dist/lib/routes/activitypub/messages.d.ts.map +1 -1
- package/dist/lib/routes/activitypub/messages.js +79 -83
- package/dist/lib/routes/activitypub/messages.js.map +1 -1
- package/dist/lib/routes/activitypub/outbox.d.ts +1 -1
- package/dist/lib/routes/activitypub/outbox.d.ts.map +1 -1
- package/dist/lib/routes/activitypub/outbox.js +9 -12
- package/dist/lib/routes/activitypub/outbox.js.map +1 -1
- package/dist/lib/routes/activitypub/post.d.ts +1 -1
- package/dist/lib/routes/activitypub/post.d.ts.map +1 -1
- package/dist/lib/routes/activitypub/post.js +32 -35
- package/dist/lib/routes/activitypub/post.js.map +1 -1
- package/dist/lib/routes/activitypub/webfinger.d.ts +1 -1
- package/dist/lib/routes/activitypub/webfinger.d.ts.map +1 -1
- package/dist/lib/routes/activitypub/webfinger.js +5 -8
- package/dist/lib/routes/activitypub/webfinger.js.map +1 -1
- package/dist/lib/routes/admin-costs.d.ts +1 -1
- package/dist/lib/routes/admin-costs.d.ts.map +1 -1
- package/dist/lib/routes/admin-costs.js +22 -26
- package/dist/lib/routes/admin-costs.js.map +1 -1
- package/dist/lib/routes/admin.d.ts +1 -1
- package/dist/lib/routes/admin.d.ts.map +1 -1
- package/dist/lib/routes/admin.js +290 -269
- package/dist/lib/routes/admin.js.map +1 -1
- package/dist/lib/routes/agent-authorize.d.ts +5 -5
- package/dist/lib/routes/agent-authorize.d.ts.map +1 -1
- package/dist/lib/routes/agent-authorize.js +68 -74
- package/dist/lib/routes/agent-authorize.js.map +1 -1
- package/dist/lib/routes/agent-sessions.d.ts +4 -4
- package/dist/lib/routes/agent-sessions.d.ts.map +1 -1
- package/dist/lib/routes/agent-sessions.js +30 -35
- package/dist/lib/routes/agent-sessions.js.map +1 -1
- package/dist/lib/routes/agent-surface.d.ts +2 -2
- package/dist/lib/routes/agent-surface.d.ts.map +1 -1
- package/dist/lib/routes/agent-surface.js +20 -24
- package/dist/lib/routes/agent-surface.js.map +1 -1
- package/dist/lib/routes/auth-discover.d.ts +1 -1
- package/dist/lib/routes/auth-discover.d.ts.map +1 -1
- package/dist/lib/routes/auth-discover.js +20 -56
- package/dist/lib/routes/auth-discover.js.map +1 -1
- package/dist/lib/routes/auth.d.ts +1 -1
- package/dist/lib/routes/auth.d.ts.map +1 -1
- package/dist/lib/routes/auth.js +13 -16
- package/dist/lib/routes/auth.js.map +1 -1
- package/dist/lib/routes/badges.d.ts +1 -1
- package/dist/lib/routes/badges.d.ts.map +1 -1
- package/dist/lib/routes/badges.js +20 -23
- package/dist/lib/routes/badges.js.map +1 -1
- package/dist/lib/routes/circles.d.ts +1 -1
- package/dist/lib/routes/circles.d.ts.map +1 -1
- package/dist/lib/routes/circles.js +40 -44
- package/dist/lib/routes/circles.js.map +1 -1
- package/dist/lib/routes/comments.d.ts +1 -1
- package/dist/lib/routes/comments.d.ts.map +1 -1
- package/dist/lib/routes/comments.js +67 -71
- package/dist/lib/routes/comments.js.map +1 -1
- package/dist/lib/routes/connection-codes.d.ts +1 -1
- package/dist/lib/routes/connection-codes.d.ts.map +1 -1
- package/dist/lib/routes/connection-codes.js +30 -34
- package/dist/lib/routes/connection-codes.js.map +1 -1
- package/dist/lib/routes/content-discovery.d.ts +1 -1
- package/dist/lib/routes/content-discovery.d.ts.map +1 -1
- package/dist/lib/routes/content-discovery.js +31 -34
- package/dist/lib/routes/content-discovery.js.map +1 -1
- package/dist/lib/routes/dashboard.d.ts +1 -1
- package/dist/lib/routes/dashboard.d.ts.map +1 -1
- package/dist/lib/routes/dashboard.js +251 -288
- package/dist/lib/routes/dashboard.js.map +1 -1
- package/dist/lib/routes/deletion.d.ts +1 -1
- package/dist/lib/routes/deletion.d.ts.map +1 -1
- package/dist/lib/routes/deletion.js +37 -74
- package/dist/lib/routes/deletion.js.map +1 -1
- package/dist/lib/routes/discovery.d.ts +1 -1
- package/dist/lib/routes/discovery.d.ts.map +1 -1
- package/dist/lib/routes/discovery.js +20 -24
- package/dist/lib/routes/discovery.js.map +1 -1
- package/dist/lib/routes/employees.d.ts +1 -1
- package/dist/lib/routes/employees.d.ts.map +1 -1
- package/dist/lib/routes/employees.js +15 -52
- package/dist/lib/routes/employees.js.map +1 -1
- package/dist/lib/routes/entities.d.ts +1 -1
- package/dist/lib/routes/entities.d.ts.map +1 -1
- package/dist/lib/routes/entities.js +133 -137
- package/dist/lib/routes/entities.js.map +1 -1
- package/dist/lib/routes/entity-relationships.d.ts +1 -1
- package/dist/lib/routes/entity-relationships.d.ts.map +1 -1
- package/dist/lib/routes/entity-relationships.js +35 -39
- package/dist/lib/routes/entity-relationships.js.map +1 -1
- package/dist/lib/routes/errors.d.ts +1 -1
- package/dist/lib/routes/errors.d.ts.map +1 -1
- package/dist/lib/routes/errors.js +4 -10
- package/dist/lib/routes/errors.js.map +1 -1
- package/dist/lib/routes/export.d.ts +1 -1
- package/dist/lib/routes/export.d.ts.map +1 -1
- package/dist/lib/routes/export.js +31 -35
- package/dist/lib/routes/export.js.map +1 -1
- package/dist/lib/routes/feature-flags.d.ts +1 -1
- package/dist/lib/routes/feature-flags.d.ts.map +1 -1
- package/dist/lib/routes/feature-flags.js +20 -23
- package/dist/lib/routes/feature-flags.js.map +1 -1
- package/dist/lib/routes/feeds.d.ts +1 -1
- package/dist/lib/routes/feeds.d.ts.map +1 -1
- package/dist/lib/routes/feeds.js +42 -46
- package/dist/lib/routes/feeds.js.map +1 -1
- package/dist/lib/routes/friends.d.ts +1 -1
- package/dist/lib/routes/friends.d.ts.map +1 -1
- package/dist/lib/routes/friends.js +35 -39
- package/dist/lib/routes/friends.js.map +1 -1
- package/dist/lib/routes/health.d.ts +1 -1
- package/dist/lib/routes/health.d.ts.map +1 -1
- package/dist/lib/routes/health.js +23 -27
- package/dist/lib/routes/health.js.map +1 -1
- package/dist/lib/routes/index.d.ts +2 -7
- package/dist/lib/routes/index.d.ts.map +1 -1
- package/dist/lib/routes/index.js +137 -158
- package/dist/lib/routes/index.js.map +1 -1
- package/dist/lib/routes/internal-docs.d.ts +1 -1
- package/dist/lib/routes/internal-docs.d.ts.map +1 -1
- package/dist/lib/routes/internal-docs.js +13 -16
- package/dist/lib/routes/internal-docs.js.map +1 -1
- package/dist/lib/routes/invitations.d.ts +1 -1
- package/dist/lib/routes/invitations.d.ts.map +1 -1
- package/dist/lib/routes/invitations.js +19 -22
- package/dist/lib/routes/invitations.js.map +1 -1
- package/dist/lib/routes/link-reports.d.ts +2 -2
- package/dist/lib/routes/link-reports.d.ts.map +1 -1
- package/dist/lib/routes/link-reports.js +86 -48
- package/dist/lib/routes/link-reports.js.map +1 -1
- package/dist/lib/routes/map.d.ts +1 -1
- package/dist/lib/routes/map.d.ts.map +1 -1
- package/dist/lib/routes/map.js +5 -8
- package/dist/lib/routes/map.js.map +1 -1
- package/dist/lib/routes/media-metadata-visibility.d.ts +1 -1
- package/dist/lib/routes/media-metadata-visibility.d.ts.map +1 -1
- package/dist/lib/routes/media-metadata-visibility.js +30 -67
- package/dist/lib/routes/media-metadata-visibility.js.map +1 -1
- package/dist/lib/routes/media.d.ts +1 -1
- package/dist/lib/routes/media.d.ts.map +1 -1
- package/dist/lib/routes/media.js +156 -193
- package/dist/lib/routes/media.js.map +1 -1
- package/dist/lib/routes/mfa.d.ts +1 -1
- package/dist/lib/routes/mfa.d.ts.map +1 -1
- package/dist/lib/routes/mfa.js +60 -64
- package/dist/lib/routes/mfa.js.map +1 -1
- package/dist/lib/routes/notifications.d.ts +1 -1
- package/dist/lib/routes/notifications.d.ts.map +1 -1
- package/dist/lib/routes/notifications.js +68 -72
- package/dist/lib/routes/notifications.js.map +1 -1
- package/dist/lib/routes/oauth.d.ts +1 -1
- package/dist/lib/routes/oauth.d.ts.map +1 -1
- package/dist/lib/routes/oauth.js +20 -23
- package/dist/lib/routes/oauth.js.map +1 -1
- package/dist/lib/routes/orphaned-media-health.d.ts +1 -1
- package/dist/lib/routes/orphaned-media-health.d.ts.map +1 -1
- package/dist/lib/routes/orphaned-media-health.js +10 -13
- package/dist/lib/routes/orphaned-media-health.js.map +1 -1
- package/dist/lib/routes/orphaned-media.d.ts +1 -1
- package/dist/lib/routes/orphaned-media.d.ts.map +1 -1
- package/dist/lib/routes/orphaned-media.js +20 -57
- package/dist/lib/routes/orphaned-media.js.map +1 -1
- package/dist/lib/routes/out.d.ts +1 -1
- package/dist/lib/routes/out.d.ts.map +1 -1
- package/dist/lib/routes/out.js +21 -24
- package/dist/lib/routes/out.js.map +1 -1
- package/dist/lib/routes/parental-controls.d.ts +1 -1
- package/dist/lib/routes/parental-controls.d.ts.map +1 -1
- package/dist/lib/routes/parental-controls.js +91 -95
- package/dist/lib/routes/parental-controls.js.map +1 -1
- package/dist/lib/routes/posts.d.ts +1 -1
- package/dist/lib/routes/posts.d.ts.map +1 -1
- package/dist/lib/routes/posts.js +101 -105
- package/dist/lib/routes/posts.js.map +1 -1
- package/dist/lib/routes/privacy.d.ts +1 -1
- package/dist/lib/routes/privacy.d.ts.map +1 -1
- package/dist/lib/routes/privacy.js +21 -25
- package/dist/lib/routes/privacy.js.map +1 -1
- package/dist/lib/routes/products.d.ts +1 -1
- package/dist/lib/routes/products.d.ts.map +1 -1
- package/dist/lib/routes/products.js +44 -48
- package/dist/lib/routes/products.js.map +1 -1
- package/dist/lib/routes/relationships.d.ts +1 -1
- package/dist/lib/routes/relationships.d.ts.map +1 -1
- package/dist/lib/routes/relationships.js +35 -39
- package/dist/lib/routes/relationships.js.map +1 -1
- package/dist/lib/routes/sentiments.d.ts +1 -1
- package/dist/lib/routes/sentiments.d.ts.map +1 -1
- package/dist/lib/routes/sentiments.js +71 -75
- package/dist/lib/routes/sentiments.js.map +1 -1
- package/dist/lib/routes/setup-status.d.ts +1 -1
- package/dist/lib/routes/setup-status.d.ts.map +1 -1
- package/dist/lib/routes/setup-status.js +17 -20
- package/dist/lib/routes/setup-status.js.map +1 -1
- package/dist/lib/routes/taxonomy-analytics.d.ts +1 -1
- package/dist/lib/routes/taxonomy-analytics.d.ts.map +1 -1
- package/dist/lib/routes/taxonomy-analytics.js +29 -33
- package/dist/lib/routes/taxonomy-analytics.js.map +1 -1
- package/dist/lib/routes/taxonomy.d.ts +1 -1
- package/dist/lib/routes/taxonomy.d.ts.map +1 -1
- package/dist/lib/routes/taxonomy.js +48 -51
- package/dist/lib/routes/taxonomy.js.map +1 -1
- package/dist/lib/routes/tenant-audit.d.ts +1 -1
- package/dist/lib/routes/tenant-audit.d.ts.map +1 -1
- package/dist/lib/routes/tenant-audit.js +35 -92
- package/dist/lib/routes/tenant-audit.js.map +1 -1
- package/dist/lib/routes/tenant-compliance.d.ts +1 -1
- package/dist/lib/routes/tenant-compliance.d.ts.map +1 -1
- package/dist/lib/routes/tenant-compliance.js +16 -52
- package/dist/lib/routes/tenant-compliance.js.map +1 -1
- package/dist/lib/routes/tenant-domains.d.ts +1 -1
- package/dist/lib/routes/tenant-domains.d.ts.map +1 -1
- package/dist/lib/routes/tenant-domains.js +27 -30
- package/dist/lib/routes/tenant-domains.js.map +1 -1
- package/dist/lib/routes/tenant-idp.d.ts +1 -1
- package/dist/lib/routes/tenant-idp.d.ts.map +1 -1
- package/dist/lib/routes/tenant-idp.js +27 -30
- package/dist/lib/routes/tenant-idp.js.map +1 -1
- package/dist/lib/routes/tenant-members.d.ts +1 -1
- package/dist/lib/routes/tenant-members.d.ts.map +1 -1
- package/dist/lib/routes/tenant-members.js +21 -24
- package/dist/lib/routes/tenant-members.js.map +1 -1
- package/dist/lib/routes/tenant-role-mappings.d.ts +1 -1
- package/dist/lib/routes/tenant-role-mappings.d.ts.map +1 -1
- package/dist/lib/routes/tenant-role-mappings.js +27 -30
- package/dist/lib/routes/tenant-role-mappings.js.map +1 -1
- package/dist/lib/routes/tenants.d.ts +1 -1
- package/dist/lib/routes/tenants.d.ts.map +1 -1
- package/dist/lib/routes/tenants.js +37 -40
- package/dist/lib/routes/tenants.js.map +1 -1
- package/dist/lib/routes/types.d.ts +10 -5
- package/dist/lib/routes/types.d.ts.map +1 -1
- package/dist/lib/routes/types.js +1 -2
- package/dist/lib/routes/types.js.map +1 -1
- package/dist/lib/routes/upload-sessions.d.ts +1 -1
- package/dist/lib/routes/upload-sessions.d.ts.map +1 -1
- package/dist/lib/routes/upload-sessions.js +57 -94
- package/dist/lib/routes/upload-sessions.js.map +1 -1
- package/dist/lib/routes/user.d.ts +1 -1
- package/dist/lib/routes/user.d.ts.map +1 -1
- package/dist/lib/routes/user.js +137 -85
- package/dist/lib/routes/user.js.map +1 -1
- package/dist/lib/routes.d.ts +2 -2
- package/dist/lib/routes.d.ts.map +1 -1
- package/dist/lib/routes.js +2 -7
- package/dist/lib/routes.js.map +1 -1
- package/dist/lib/scaling-health.d.ts.map +1 -1
- package/dist/lib/scaling-health.js +6 -9
- package/dist/lib/scaling-health.js.map +1 -1
- package/dist/lib/scheduled/media-stale-cleanup.js +5 -8
- package/dist/lib/scheduled/media-stale-cleanup.js.map +1 -1
- package/dist/lib/scheduled/orphaned-media-monitor.d.ts +1 -1
- package/dist/lib/scheduled/orphaned-media-monitor.d.ts.map +1 -1
- package/dist/lib/scheduled/orphaned-media-monitor.js +5 -42
- package/dist/lib/scheduled/orphaned-media-monitor.js.map +1 -1
- package/dist/lib/schemas.d.ts +85 -204
- package/dist/lib/schemas.d.ts.map +1 -1
- package/dist/lib/schemas.js +71 -74
- package/dist/lib/schemas.js.map +1 -1
- package/dist/lib/secrets/idp-secrets.d.ts +1 -1
- package/dist/lib/secrets/idp-secrets.js +13 -19
- package/dist/lib/secrets/idp-secrets.js.map +1 -1
- package/dist/lib/security-event-cleaner.js +1 -5
- package/dist/lib/security-event-cleaner.js.map +1 -1
- package/dist/lib/security-headers.js +1 -5
- package/dist/lib/security-headers.js.map +1 -1
- package/dist/lib/security-monitor.d.ts +4 -2
- package/dist/lib/security-monitor.d.ts.map +1 -1
- package/dist/lib/security-monitor.js +16 -18
- package/dist/lib/security-monitor.js.map +1 -1
- package/dist/lib/sentiment-digest.d.ts +1 -1
- package/dist/lib/sentiment-digest.d.ts.map +1 -1
- package/dist/lib/sentiment-digest.js +5 -8
- package/dist/lib/sentiment-digest.js.map +1 -1
- package/dist/lib/sentiment-display.js +3 -7
- package/dist/lib/sentiment-display.js.map +1 -1
- package/dist/lib/services/image-normalizer.js +1 -5
- package/dist/lib/services/image-normalizer.js.map +1 -1
- package/dist/lib/services/media-reconciliation-service.d.ts +1 -1
- package/dist/lib/services/media-reconciliation-service.d.ts.map +1 -1
- package/dist/lib/services/media-reconciliation-service.js +7 -11
- package/dist/lib/services/media-reconciliation-service.js.map +1 -1
- package/dist/lib/services/media-upload-service.d.ts +1 -1
- package/dist/lib/services/media-upload-service.d.ts.map +1 -1
- package/dist/lib/services/media-upload-service.js +4 -8
- package/dist/lib/services/media-upload-service.js.map +1 -1
- package/dist/lib/services/user-data-deletion.d.ts +45 -2
- package/dist/lib/services/user-data-deletion.d.ts.map +1 -1
- package/dist/lib/services/user-data-deletion.js +87 -9
- package/dist/lib/services/user-data-deletion.js.map +1 -1
- package/dist/lib/session-awareness.js +2 -6
- package/dist/lib/session-awareness.js.map +1 -1
- package/dist/lib/session-config.js +8 -17
- package/dist/lib/session-config.js.map +1 -1
- package/dist/lib/{session-manager.d.ts → session-cookie.d.ts} +58 -15
- package/dist/lib/session-cookie.d.ts.map +1 -0
- package/dist/lib/session-cookie.js +0 -0
- package/dist/lib/session-cookie.js.map +1 -0
- package/dist/lib/signup-metadata.d.ts +129 -0
- package/dist/lib/signup-metadata.d.ts.map +1 -0
- package/dist/lib/signup-metadata.js +127 -0
- package/dist/lib/signup-metadata.js.map +1 -0
- package/dist/lib/sso-auth-handler.js +1 -5
- package/dist/lib/sso-auth-handler.js.map +1 -1
- package/dist/lib/tag-suggestions-handler.d.ts +1 -1
- package/dist/lib/tag-suggestions-handler.d.ts.map +1 -1
- package/dist/lib/tag-suggestions-handler.js +1 -5
- package/dist/lib/tag-suggestions-handler.js.map +1 -1
- package/dist/lib/taxonomy-handler-factory.d.ts +2 -2
- package/dist/lib/taxonomy-handler-factory.d.ts.map +1 -1
- package/dist/lib/taxonomy-handler-factory.js +7 -10
- package/dist/lib/taxonomy-handler-factory.js.map +1 -1
- package/dist/lib/taxonomy-handler.d.ts +2 -2
- package/dist/lib/taxonomy-handler.d.ts.map +1 -1
- package/dist/lib/taxonomy-handler.js +8 -8
- package/dist/lib/taxonomy-handler.js.map +1 -1
- package/dist/lib/taxonomy-metrics.js +5 -9
- package/dist/lib/taxonomy-metrics.js.map +1 -1
- package/dist/lib/taxonomy-search-metrics.d.ts +2 -2
- package/dist/lib/taxonomy-search-metrics.d.ts.map +1 -1
- package/dist/lib/taxonomy-search-metrics.js +3 -7
- package/dist/lib/taxonomy-search-metrics.js.map +1 -1
- package/dist/lib/tenant/audit-emit.d.ts +18 -8
- package/dist/lib/tenant/audit-emit.d.ts.map +1 -1
- package/dist/lib/tenant/audit-emit.js +50 -11
- package/dist/lib/tenant/audit-emit.js.map +1 -1
- package/dist/lib/tenant/derive-domain.js +1 -4
- package/dist/lib/tenant/derive-domain.js.map +1 -1
- package/dist/lib/tenant/domain-handler.d.ts +2 -2
- package/dist/lib/tenant/domain-handler.d.ts.map +1 -1
- package/dist/lib/tenant/domain-handler.js +50 -62
- package/dist/lib/tenant/domain-handler.js.map +1 -1
- package/dist/lib/tenant/domain-validator.d.ts +1 -1
- package/dist/lib/tenant/domain-validator.js +10 -13
- package/dist/lib/tenant/domain-validator.js.map +1 -1
- package/dist/lib/tenant/domain-verifier.d.ts +3 -3
- package/dist/lib/tenant/domain-verifier.js +8 -11
- package/dist/lib/tenant/domain-verifier.js.map +1 -1
- package/dist/lib/tenant/idp-handler.d.ts +4 -4
- package/dist/lib/tenant/idp-handler.d.ts.map +1 -1
- package/dist/lib/tenant/idp-handler.js +45 -82
- package/dist/lib/tenant/idp-handler.js.map +1 -1
- package/dist/lib/tenant/idp-name.js +1 -4
- package/dist/lib/tenant/idp-name.js.map +1 -1
- package/dist/lib/tenant/member-handler.d.ts +2 -2
- package/dist/lib/tenant/member-handler.d.ts.map +1 -1
- package/dist/lib/tenant/member-handler.js +30 -67
- package/dist/lib/tenant/member-handler.js.map +1 -1
- package/dist/lib/tenant/reserved-slugs.d.ts +1 -1
- package/dist/lib/tenant/reserved-slugs.d.ts.map +1 -1
- package/dist/lib/tenant/reserved-slugs.js +8 -14
- package/dist/lib/tenant/reserved-slugs.js.map +1 -1
- package/dist/lib/tenant/resolve-role.js +1 -4
- package/dist/lib/tenant/resolve-role.js.map +1 -1
- package/dist/lib/tenant/role-mapping-handler.d.ts +2 -2
- package/dist/lib/tenant/role-mapping-handler.d.ts.map +1 -1
- package/dist/lib/tenant/role-mapping-handler.js +24 -61
- package/dist/lib/tenant/role-mapping-handler.js.map +1 -1
- package/dist/lib/tenant/setup-status.d.ts +1 -1
- package/dist/lib/tenant/setup-status.d.ts.map +1 -1
- package/dist/lib/tenant/setup-status.js +3 -40
- package/dist/lib/tenant/setup-status.js.map +1 -1
- package/dist/lib/tenant/slug-validator.js +3 -6
- package/dist/lib/tenant/slug-validator.js.map +1 -1
- package/dist/lib/tenant/tenant-handler.d.ts +2 -2
- package/dist/lib/tenant/tenant-handler.d.ts.map +1 -1
- package/dist/lib/tenant/tenant-handler.js +31 -68
- package/dist/lib/tenant/tenant-handler.js.map +1 -1
- package/dist/lib/tenant/transfer-ownership.js +2 -6
- package/dist/lib/tenant/transfer-ownership.js.map +1 -1
- package/dist/lib/tenant-scope.d.ts +97 -0
- package/dist/lib/tenant-scope.d.ts.map +1 -0
- package/dist/lib/tenant-scope.js +270 -0
- package/dist/lib/tenant-scope.js.map +1 -0
- package/dist/lib/terminology.d.ts.map +1 -1
- package/dist/lib/terminology.js +7 -9
- package/dist/lib/terminology.js.map +1 -1
- package/dist/lib/theme.js +2 -6
- package/dist/lib/theme.js.map +1 -1
- package/dist/lib/threat-intel-service.d.ts +2 -2
- package/dist/lib/threat-intel-service.d.ts.map +1 -1
- package/dist/lib/threat-intel-service.js +3 -7
- package/dist/lib/threat-intel-service.js.map +1 -1
- package/dist/lib/types/media-reconciliation.js +1 -2
- package/dist/lib/types/media-reconciliation.js.map +1 -1
- package/dist/lib/upload-session-handler.d.ts +1 -1
- package/dist/lib/upload-session-handler.d.ts.map +1 -1
- package/dist/lib/upload-session-handler.js +13 -50
- package/dist/lib/upload-session-handler.js.map +1 -1
- package/dist/lib/user/derive-handle.js +2 -6
- package/dist/lib/user/derive-handle.js.map +1 -1
- package/dist/lib/user-badge.js +6 -14
- package/dist/lib/user-badge.js.map +1 -1
- package/dist/lib/user-deletion-handler-enhanced.d.ts +2 -2
- package/dist/lib/user-deletion-handler-enhanced.d.ts.map +1 -1
- package/dist/lib/user-deletion-handler-enhanced.js +16 -53
- package/dist/lib/user-deletion-handler-enhanced.js.map +1 -1
- package/dist/lib/user-deprovisioning.d.ts +1 -1
- package/dist/lib/user-deprovisioning.d.ts.map +1 -1
- package/dist/lib/user-deprovisioning.js +16 -20
- package/dist/lib/user-deprovisioning.js.map +1 -1
- package/dist/lib/user-export-handler.d.ts +4 -4
- package/dist/lib/user-export-handler.d.ts.map +1 -1
- package/dist/lib/user-export-handler.js +11 -15
- package/dist/lib/user-export-handler.js.map +1 -1
- package/dist/lib/validate-request.js +8 -13
- package/dist/lib/validate-request.js.map +1 -1
- package/dist/lib/validation/feature-toggle-schemas.d.ts +130 -249
- package/dist/lib/validation/feature-toggle-schemas.d.ts.map +1 -1
- package/dist/lib/validation/feature-toggle-schemas.js +50 -59
- package/dist/lib/validation/feature-toggle-schemas.js.map +1 -1
- package/dist/lib/validation/validate-request.d.ts.map +1 -1
- package/dist/lib/validation/validate-request.js +12 -23
- package/dist/lib/validation/validate-request.js.map +1 -1
- package/dist/lib/validation.js +1 -5
- package/dist/lib/validation.js.map +1 -1
- package/dist/lib/version.js +3 -8
- package/dist/lib/version.js.map +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +29 -69
- package/dist/server.js.map +1 -1
- package/dist/types/cloudflare-compat.d.ts +3 -93
- package/dist/types/cloudflare-compat.d.ts.map +1 -1
- package/dist/types/cloudflare-compat.js +1 -2
- package/dist/types/cloudflare-compat.js.map +1 -1
- package/dist/worker.d.ts +6 -6
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +6 -13
- package/dist/worker.js.map +1 -1
- package/package.json +30 -17
- package/prisma/migrations/20260602054730_add_entity_geo_and_pending_schema/migration.sql +113 -0
- package/prisma/migrations/20260602162901_research_foundations/migration.sql +65 -0
- package/prisma/migrations/20260604130000_surveillance_phase0_enablers/migration.sql +107 -0
- package/prisma/migrations/20260604140000_fold_link_reports_into_reports/migration.sql +23 -0
- package/prisma/migrations/20260604140000_fold_link_reports_into_reports/rollback.reference.sql +31 -0
- package/prisma/schema.prisma +419 -68
- package/src/lambda/cleanup-cron.ts +10 -7
- package/src/lambda/create-auth-challenge.ts +6 -3
- package/src/lambda/delete-account-worker.ts +17 -12
- package/src/lambda/diagnostics-proxy.ts +9 -6
- package/src/lambda/e2e-sweeper.ts +17 -23
- package/src/lambda/federation-outbox-worker.ts +4 -1
- package/src/lambda/followers-events-worker.ts +4 -1
- package/src/lambda/hourly-cron.ts +112 -20
- package/src/lambda/link-check-worker.ts +4 -1
- package/src/lambda/maintenance-cron.ts +24 -13
- package/src/lambda/media-processing-worker.ts +5 -2
- package/src/lambda/media-reconciliation-worker.ts +4 -1
- package/src/lambda/nightly-cron.ts +53 -54
- package/src/lambda/post-confirmation.ts +188 -62
- package/src/lambda/pre-token-generation.ts +39 -44
- package/src/lambda/verify-auth-challenge.ts +4 -1
- package/dist/lib/audit/emit.d.ts +0 -56
- package/dist/lib/audit/emit.d.ts.map +0 -1
- package/dist/lib/audit/emit.js +0 -124
- package/dist/lib/audit/emit.js.map +0 -1
- package/dist/lib/audit/event-types.d.ts +0 -36
- package/dist/lib/audit/event-types.d.ts.map +0 -1
- package/dist/lib/audit/event-types.js +0 -69
- package/dist/lib/audit/event-types.js.map +0 -1
- package/dist/lib/audit-logger.d.ts +0 -142
- package/dist/lib/audit-logger.d.ts.map +0 -1
- package/dist/lib/audit-logger.js +0 -326
- package/dist/lib/audit-logger.js.map +0 -1
- package/dist/lib/circuit-breaker.d.ts +0 -27
- package/dist/lib/circuit-breaker.d.ts.map +0 -1
- package/dist/lib/circuit-breaker.js +0 -63
- package/dist/lib/circuit-breaker.js.map +0 -1
- package/dist/lib/graph/dual-write-service.d.ts +0 -116
- package/dist/lib/graph/dual-write-service.d.ts.map +0 -1
- package/dist/lib/graph/dual-write-service.js +0 -332
- package/dist/lib/graph/dual-write-service.js.map +0 -1
- package/dist/lib/graph/dual-write.d.ts +0 -396
- package/dist/lib/graph/dual-write.d.ts.map +0 -1
- package/dist/lib/graph/dual-write.js +0 -53
- package/dist/lib/graph/dual-write.js.map +0 -1
- package/dist/lib/graph/graph-schema-init.d.ts +0 -31
- package/dist/lib/graph/graph-schema-init.d.ts.map +0 -1
- package/dist/lib/graph/graph-schema-init.js +0 -105
- package/dist/lib/graph/graph-schema-init.js.map +0 -1
- package/dist/lib/graph/neo4j-graph-service.d.ts +0 -186
- package/dist/lib/graph/neo4j-graph-service.d.ts.map +0 -1
- package/dist/lib/graph/neo4j-graph-service.js +0 -1625
- package/dist/lib/graph/neo4j-graph-service.js.map +0 -1
- package/dist/lib/graph/reconciliation-service.d.ts +0 -113
- package/dist/lib/graph/reconciliation-service.d.ts.map +0 -1
- package/dist/lib/graph/reconciliation-service.js +0 -533
- package/dist/lib/graph/reconciliation-service.js.map +0 -1
- package/dist/lib/id-generator.d.ts +0 -29
- package/dist/lib/id-generator.d.ts.map +0 -1
- package/dist/lib/id-generator.js +0 -51
- package/dist/lib/id-generator.js.map +0 -1
- package/dist/lib/kv/dynamodb-kv.d.ts +0 -39
- package/dist/lib/kv/dynamodb-kv.d.ts.map +0 -1
- package/dist/lib/kv/dynamodb-kv.js +0 -239
- package/dist/lib/kv/dynamodb-kv.js.map +0 -1
- package/dist/lib/queue/sqs-queue.d.ts +0 -16
- package/dist/lib/queue/sqs-queue.d.ts.map +0 -1
- package/dist/lib/queue/sqs-queue.js +0 -39
- package/dist/lib/queue/sqs-queue.js.map +0 -1
- package/dist/lib/route-matcher.d.ts +0 -24
- package/dist/lib/route-matcher.d.ts.map +0 -1
- package/dist/lib/route-matcher.js +0 -96
- package/dist/lib/route-matcher.js.map +0 -1
- package/dist/lib/router.d.ts +0 -26
- package/dist/lib/router.d.ts.map +0 -1
- package/dist/lib/router.js +0 -90
- package/dist/lib/router.js.map +0 -1
- package/dist/lib/routes-all.d.ts +0 -9
- package/dist/lib/routes-all.d.ts.map +0 -1
- package/dist/lib/routes-all.js +0 -170
- package/dist/lib/routes-all.js.map +0 -1
- package/dist/lib/secret-resolver.d.ts +0 -88
- package/dist/lib/secret-resolver.d.ts.map +0 -1
- package/dist/lib/secret-resolver.js +0 -183
- package/dist/lib/secret-resolver.js.map +0 -1
- package/dist/lib/session-manager.d.ts.map +0 -1
- package/dist/lib/session-manager.js +0 -492
- package/dist/lib/session-manager.js.map +0 -1
- package/dist/lib/storage/s3-storage.d.ts +0 -29
- package/dist/lib/storage/s3-storage.d.ts.map +0 -1
- package/dist/lib/storage/s3-storage.js +0 -135
- package/dist/lib/storage/s3-storage.js.map +0 -1
- package/dist/lib/tenant-context.d.ts +0 -35
- package/dist/lib/tenant-context.d.ts.map +0 -1
- package/dist/lib/tenant-context.js +0 -54
- package/dist/lib/tenant-context.js.map +0 -1
|
@@ -1,1625 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Neo4j GraphService Implementation
|
|
4
|
-
*
|
|
5
|
-
* Implements the GraphService interface using the Neo4j JavaScript driver.
|
|
6
|
-
* Used for local development and the AuraDB production/dev instances —
|
|
7
|
-
* same Cypher dialect.
|
|
8
|
-
*
|
|
9
|
-
* Connection management, schema initialization, and health checks are fully
|
|
10
|
-
* implemented. Business methods are stubbed (Phase 2).
|
|
11
|
-
*
|
|
12
|
-
* SECURITY: All queries use parameterized queries ($paramName) — never
|
|
13
|
-
* string concatenation.
|
|
14
|
-
*
|
|
15
|
-
* @see /analysis/redesign/07-graph-database/04-graph-schema.md
|
|
16
|
-
*/
|
|
17
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
18
|
-
if (k2 === undefined) k2 = k;
|
|
19
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
20
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
21
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
22
|
-
}
|
|
23
|
-
Object.defineProperty(o, k2, desc);
|
|
24
|
-
}) : (function(o, m, k, k2) {
|
|
25
|
-
if (k2 === undefined) k2 = k;
|
|
26
|
-
o[k2] = m[k];
|
|
27
|
-
}));
|
|
28
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
29
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
30
|
-
}) : function(o, v) {
|
|
31
|
-
o["default"] = v;
|
|
32
|
-
});
|
|
33
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
34
|
-
var ownKeys = function(o) {
|
|
35
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
36
|
-
var ar = [];
|
|
37
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
38
|
-
return ar;
|
|
39
|
-
};
|
|
40
|
-
return ownKeys(o);
|
|
41
|
-
};
|
|
42
|
-
return function (mod) {
|
|
43
|
-
if (mod && mod.__esModule) return mod;
|
|
44
|
-
var result = {};
|
|
45
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
46
|
-
__setModuleDefault(result, mod);
|
|
47
|
-
return result;
|
|
48
|
-
};
|
|
49
|
-
})();
|
|
50
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
51
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
52
|
-
};
|
|
53
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
54
|
-
exports.Neo4jGraphService = void 0;
|
|
55
|
-
const neo4j_driver_1 = __importDefault(require("neo4j-driver"));
|
|
56
|
-
const errors_1 = require("./errors");
|
|
57
|
-
const graph_schema_init_1 = require("./graph-schema-init");
|
|
58
|
-
const scoring_engine_1 = require("./scoring-engine");
|
|
59
|
-
// ---------------------------------------------------------------------------
|
|
60
|
-
// Implementation
|
|
61
|
-
// ---------------------------------------------------------------------------
|
|
62
|
-
class Neo4jGraphService {
|
|
63
|
-
driver = null;
|
|
64
|
-
connected = false;
|
|
65
|
-
schemaInitialized = false;
|
|
66
|
-
// =========================================================================
|
|
67
|
-
// Connection Management (GraphConnection)
|
|
68
|
-
// =========================================================================
|
|
69
|
-
async connect(config) {
|
|
70
|
-
if (this.connected && this.driver) {
|
|
71
|
-
return; // Idempotent
|
|
72
|
-
}
|
|
73
|
-
try {
|
|
74
|
-
// Resolve authentication
|
|
75
|
-
let auth;
|
|
76
|
-
if (config.auth.type === "basic") {
|
|
77
|
-
auth = neo4j_driver_1.default.auth.basic(config.auth.username, config.auth.password);
|
|
78
|
-
}
|
|
79
|
-
// "none" auth type: no auth object passed to driver
|
|
80
|
-
// Create driver with pool configuration
|
|
81
|
-
this.driver = neo4j_driver_1.default.driver(config.endpoint, auth, {
|
|
82
|
-
maxConnectionPoolSize: config.pool?.maxConnectionPoolSize ?? 100,
|
|
83
|
-
connectionAcquisitionTimeout: config.pool?.connectionAcquisitionTimeout ?? 60_000,
|
|
84
|
-
maxConnectionLifetime: config.pool?.maxConnectionLifetime ?? 3_600_000,
|
|
85
|
-
...(config.pool?.connectionLivenessCheckTimeout !== undefined && {
|
|
86
|
-
connectionLivenessCheckTimeout: config.pool.connectionLivenessCheckTimeout,
|
|
87
|
-
}),
|
|
88
|
-
disableLosslessIntegers: true, // Return native JS numbers instead of Neo4j Integer
|
|
89
|
-
});
|
|
90
|
-
// Verify connectivity
|
|
91
|
-
await this.driver.verifyConnectivity();
|
|
92
|
-
this.connected = true;
|
|
93
|
-
// Initialize schema (constraints + indexes) on first connect
|
|
94
|
-
if (!this.schemaInitialized) {
|
|
95
|
-
await this.initializeSchema();
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
catch (error) {
|
|
99
|
-
this.connected = false;
|
|
100
|
-
this.driver = null;
|
|
101
|
-
throw new errors_1.GraphConnectionError(`Failed to connect to Neo4j at ${config.endpoint}: ${error instanceof Error ? error.message : String(error)}`, { cause: error instanceof Error ? error : undefined });
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
async close() {
|
|
105
|
-
if (this.driver) {
|
|
106
|
-
await this.driver.close();
|
|
107
|
-
this.driver = null;
|
|
108
|
-
this.connected = false;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
isConnected() {
|
|
112
|
-
return this.connected;
|
|
113
|
-
}
|
|
114
|
-
// =========================================================================
|
|
115
|
-
// Health Check
|
|
116
|
-
// =========================================================================
|
|
117
|
-
async healthCheck() {
|
|
118
|
-
if (!this.driver || !this.connected) {
|
|
119
|
-
return {
|
|
120
|
-
healthy: false,
|
|
121
|
-
latencyMs: 0,
|
|
122
|
-
error: "Not connected",
|
|
123
|
-
backend: "neo4j",
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
const session = this.driver.session();
|
|
127
|
-
const start = Date.now();
|
|
128
|
-
try {
|
|
129
|
-
await session.run("RETURN 1 AS health");
|
|
130
|
-
const latencyMs = Date.now() - start;
|
|
131
|
-
return {
|
|
132
|
-
healthy: true,
|
|
133
|
-
latencyMs,
|
|
134
|
-
backend: "neo4j",
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
catch (error) {
|
|
138
|
-
const latencyMs = Date.now() - start;
|
|
139
|
-
this.connected = false;
|
|
140
|
-
return {
|
|
141
|
-
healthy: false,
|
|
142
|
-
latencyMs,
|
|
143
|
-
error: error instanceof Error ? error.message : String(error),
|
|
144
|
-
backend: "neo4j",
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
finally {
|
|
148
|
-
await session.close();
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
// =========================================================================
|
|
152
|
-
// Relationships
|
|
153
|
-
// =========================================================================
|
|
154
|
-
async createRelationship(input) {
|
|
155
|
-
const { userId, targetType, targetId, connectionMethod = "discovery" } = input;
|
|
156
|
-
// Initial score by connection method (from scoring engine constants)
|
|
157
|
-
const initialScore = scoring_engine_1.CONNECTION_BONUSES[connectionMethod] ?? 0.3;
|
|
158
|
-
const tier = (0, scoring_engine_1.scoreToTier)(initialScore);
|
|
159
|
-
const now = new Date().toISOString();
|
|
160
|
-
// Target label is capitalized: "user" -> "User", "entity" -> "Entity"
|
|
161
|
-
const targetLabel = targetType === "user" ? "User" : "Entity";
|
|
162
|
-
// MERGE the relationship (idempotent on the edge; we want conflict detection so
|
|
163
|
-
// we use MATCH first and fail if it already exists, then CREATE).
|
|
164
|
-
// We verify the target node exists, then create the edge.
|
|
165
|
-
const result = await this.executeQuery(`
|
|
166
|
-
MATCH (src:User {id: $userId})
|
|
167
|
-
MATCH (tgt:${targetLabel} {id: $targetId})
|
|
168
|
-
MERGE (src)-[r:RELATES_TO]->(tgt)
|
|
169
|
-
ON CREATE SET
|
|
170
|
-
r.computedScore = $initialScore,
|
|
171
|
-
r.manualScore = null,
|
|
172
|
-
r.score = $initialScore,
|
|
173
|
-
r.tier = $tier,
|
|
174
|
-
r.interactionCount = 0,
|
|
175
|
-
r.lastInteractionAt = null,
|
|
176
|
-
r.connectionMethod = $connectionMethod,
|
|
177
|
-
r.reciprocated = false,
|
|
178
|
-
r.createdAt = $now
|
|
179
|
-
ON MATCH SET
|
|
180
|
-
r._alreadyExisted = true
|
|
181
|
-
WITH src, tgt, r
|
|
182
|
-
// Handle reciprocity for user->user edges
|
|
183
|
-
OPTIONAL MATCH (tgt)-[rev:RELATES_TO]->(src)
|
|
184
|
-
WITH r, rev, (tgt:User) AS isUserTarget
|
|
185
|
-
SET r.reciprocated = (isUserTarget AND rev IS NOT NULL)
|
|
186
|
-
WITH r, rev, isUserTarget
|
|
187
|
-
// Mark the reverse edge as reciprocated if it exists
|
|
188
|
-
FOREACH (_ IN CASE WHEN isUserTarget AND rev IS NOT NULL THEN [1] ELSE [] END |
|
|
189
|
-
SET rev.reciprocated = true
|
|
190
|
-
)
|
|
191
|
-
RETURN r
|
|
192
|
-
`, { userId, targetId, initialScore, tier, connectionMethod, now });
|
|
193
|
-
if (result.records.length === 0) {
|
|
194
|
-
throw new errors_1.GraphNotFoundError(`Source user ${userId} or target ${targetType} ${targetId} not found in graph`);
|
|
195
|
-
}
|
|
196
|
-
const rel = result.records[0].get("r").properties;
|
|
197
|
-
return recordToRelationship(rel, userId, targetType, targetId);
|
|
198
|
-
}
|
|
199
|
-
async removeRelationship(userId, targetType, targetId) {
|
|
200
|
-
const targetLabel = targetType === "user" ? "User" : "Entity";
|
|
201
|
-
const result = await this.executeQuery(`
|
|
202
|
-
OPTIONAL MATCH (src:User {id: $userId})-[r:RELATES_TO]->(tgt:${targetLabel} {id: $targetId})
|
|
203
|
-
WITH r, src, tgt, (tgt:User) AS isUserTarget, (r IS NOT NULL) AS found
|
|
204
|
-
// If reciprocated user-user edge, clear the reverse reciprocated flag
|
|
205
|
-
OPTIONAL MATCH (tgt)-[rev:RELATES_TO]->(src)
|
|
206
|
-
WITH r, rev, isUserTarget, found
|
|
207
|
-
FOREACH (_ IN CASE WHEN isUserTarget AND rev IS NOT NULL THEN [1] ELSE [] END |
|
|
208
|
-
SET rev.reciprocated = false
|
|
209
|
-
)
|
|
210
|
-
WITH r, found
|
|
211
|
-
FOREACH (_ IN CASE WHEN r IS NOT NULL THEN [1] ELSE [] END | DELETE r)
|
|
212
|
-
RETURN found
|
|
213
|
-
`, { userId, targetId });
|
|
214
|
-
// If no relationship was matched and deleted, throw not found
|
|
215
|
-
const found = result.records[0]?.get("found") ?? false;
|
|
216
|
-
if (!found) {
|
|
217
|
-
throw new errors_1.GraphNotFoundError(`Relationship from user ${userId} to ${targetType} ${targetId} not found`, "relationship", `${userId}->${targetId}`);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
async updateRelationshipScore(input) {
|
|
221
|
-
const { userId, targetType, targetId, manualScore } = input;
|
|
222
|
-
const targetLabel = targetType === "user" ? "User" : "Entity";
|
|
223
|
-
const result = await this.executeQuery(`
|
|
224
|
-
MATCH (src:User {id: $userId})-[r:RELATES_TO]->(tgt:${targetLabel} {id: $targetId})
|
|
225
|
-
SET r.manualScore = $manualScore,
|
|
226
|
-
r.score = CASE WHEN $manualScore IS NOT NULL THEN $manualScore ELSE r.computedScore END
|
|
227
|
-
RETURN r
|
|
228
|
-
`, {
|
|
229
|
-
userId,
|
|
230
|
-
targetId,
|
|
231
|
-
manualScore: manualScore ?? null,
|
|
232
|
-
});
|
|
233
|
-
if (result.records.length === 0) {
|
|
234
|
-
throw new errors_1.GraphNotFoundError(`Relationship from user ${userId} to ${targetType} ${targetId} not found`, "relationship", `${userId}->${targetId}`);
|
|
235
|
-
}
|
|
236
|
-
// The query updates r.score in the DB; derive tier from the effective score.
|
|
237
|
-
const rel = result.records[0].get("r").properties;
|
|
238
|
-
const effectiveScore = typeof rel.score === "number" ? rel.score : 0;
|
|
239
|
-
rel.tier = (0, scoring_engine_1.scoreToTier)(effectiveScore);
|
|
240
|
-
return recordToRelationship(rel, userId, targetType, targetId);
|
|
241
|
-
}
|
|
242
|
-
async getRelationship(userId, targetType, targetId) {
|
|
243
|
-
const targetLabel = targetType === "user" ? "User" : "Entity";
|
|
244
|
-
const result = await this.executeQuery(`
|
|
245
|
-
MATCH (src:User {id: $userId})-[r:RELATES_TO]->(tgt:${targetLabel} {id: $targetId})
|
|
246
|
-
RETURN r
|
|
247
|
-
`, { userId, targetId });
|
|
248
|
-
if (result.records.length === 0) {
|
|
249
|
-
return null;
|
|
250
|
-
}
|
|
251
|
-
const rel = result.records[0].get("r").properties;
|
|
252
|
-
return recordToRelationship(rel, userId, targetType, targetId);
|
|
253
|
-
}
|
|
254
|
-
async getRelationships(userId, options) {
|
|
255
|
-
const limit = options?.pagination?.limit ?? 50;
|
|
256
|
-
const cursor = options?.pagination?.cursor ?? null;
|
|
257
|
-
// Build tier filter
|
|
258
|
-
const tierFilter = options?.tier !== undefined ? "AND r.tier = $tier" : "";
|
|
259
|
-
// Build target label constraint
|
|
260
|
-
let targetMatch = "(tgt)";
|
|
261
|
-
if (options?.targetType === "user") {
|
|
262
|
-
targetMatch = "(tgt:User)";
|
|
263
|
-
}
|
|
264
|
-
else if (options?.targetType === "entity") {
|
|
265
|
-
targetMatch = "(tgt:Entity)";
|
|
266
|
-
}
|
|
267
|
-
// Cursor encodes the score of the last item (score-based cursor pagination)
|
|
268
|
-
const cursorScore = cursor !== null ? decodeCursor(cursor) : null;
|
|
269
|
-
const cursorFilter = cursorScore !== null ? "AND r.score < $cursorScore" : "";
|
|
270
|
-
const result = await this.executeQuery(`
|
|
271
|
-
MATCH (src:User {id: $userId})-[r:RELATES_TO]->${targetMatch}
|
|
272
|
-
WHERE 1=1 ${tierFilter} ${cursorFilter}
|
|
273
|
-
RETURN r, tgt.id AS targetId,
|
|
274
|
-
CASE WHEN tgt:User THEN 'user' ELSE 'entity' END AS targetType
|
|
275
|
-
ORDER BY r.score DESC
|
|
276
|
-
LIMIT $fetchLimit
|
|
277
|
-
`, {
|
|
278
|
-
userId,
|
|
279
|
-
tier: options?.tier ?? null,
|
|
280
|
-
cursorScore: cursorScore ?? null,
|
|
281
|
-
fetchLimit: limit + 1, // fetch one extra to detect hasMore
|
|
282
|
-
});
|
|
283
|
-
const records = result.records;
|
|
284
|
-
const hasMore = records.length > limit;
|
|
285
|
-
const items = (hasMore ? records.slice(0, limit) : records).map((rec) => {
|
|
286
|
-
const rel = rec.get("r").properties;
|
|
287
|
-
const tId = rec.get("targetId");
|
|
288
|
-
const tType = rec.get("targetType");
|
|
289
|
-
return recordToRelationship(rel, userId, tType, tId);
|
|
290
|
-
});
|
|
291
|
-
const lastItem = items[items.length - 1];
|
|
292
|
-
const nextCursor = hasMore && lastItem ? encodeCursor(lastItem.score) : null;
|
|
293
|
-
return { items, cursor: nextCursor, hasMore };
|
|
294
|
-
}
|
|
295
|
-
async getRelationshipGraph(userId) {
|
|
296
|
-
const result = await this.executeQuery(`
|
|
297
|
-
MATCH (viewer:User {id: $userId})-[r:RELATES_TO]->(tgt)
|
|
298
|
-
RETURN tgt.id AS targetId,
|
|
299
|
-
CASE WHEN tgt:User THEN 'user' ELSE 'entity' END AS targetType,
|
|
300
|
-
tgt.name AS name,
|
|
301
|
-
r.score AS score,
|
|
302
|
-
r.tier AS tier
|
|
303
|
-
ORDER BY r.score DESC
|
|
304
|
-
`, { userId });
|
|
305
|
-
const nodes = result.records.map((rec) => {
|
|
306
|
-
const rawScore = rec.get("score");
|
|
307
|
-
const tier = rec.get("tier");
|
|
308
|
-
return {
|
|
309
|
-
id: rec.get("targetId"),
|
|
310
|
-
type: rec.get("targetType"),
|
|
311
|
-
name: rec.get("name") ?? "",
|
|
312
|
-
// Coarsen to nearest 10 (0-100), no raw score exposed
|
|
313
|
-
closeness: Math.round(rawScore * 10) * 10,
|
|
314
|
-
tier,
|
|
315
|
-
};
|
|
316
|
-
});
|
|
317
|
-
// Build tier summaries
|
|
318
|
-
const counts = { inner: 0, closeFriends: 0, community: 0, ambient: 0 };
|
|
319
|
-
for (const node of nodes) {
|
|
320
|
-
const tierName = tierToName(node.tier);
|
|
321
|
-
counts[tierName]++;
|
|
322
|
-
}
|
|
323
|
-
// Use canonical thresholds from the scoring engine
|
|
324
|
-
const thresholdFor = (tier) => scoring_engine_1.TIER_THRESHOLDS.find((t) => t.tier === tier)?.minScore ?? 0;
|
|
325
|
-
const tiers = {
|
|
326
|
-
inner: { threshold: thresholdFor(0), count: counts.inner },
|
|
327
|
-
closeFriends: { threshold: thresholdFor(1), count: counts.closeFriends },
|
|
328
|
-
community: { threshold: thresholdFor(2), count: counts.community },
|
|
329
|
-
ambient: { threshold: thresholdFor(3), count: counts.ambient },
|
|
330
|
-
};
|
|
331
|
-
return { nodes, tiers };
|
|
332
|
-
}
|
|
333
|
-
// =========================================================================
|
|
334
|
-
// Circles (Stubbed — Phase 2)
|
|
335
|
-
// =========================================================================
|
|
336
|
-
// --- Circle Resolution helpers (P2.2) ---
|
|
337
|
-
/** Default circle config thresholds from circle-queries.md. */
|
|
338
|
-
static CIRCLE_THRESHOLDS = {
|
|
339
|
-
innerThreshold: 0.8,
|
|
340
|
-
closeFriendThreshold: 0.5,
|
|
341
|
-
communityThreshold: 0.2,
|
|
342
|
-
};
|
|
343
|
-
static TIER_NAMES = { 0: "inner", 1: "closeFriends", 2: "community", 3: "ambient" };
|
|
344
|
-
/** Score threshold bounds for a tier (from circle-queries.md). */
|
|
345
|
-
getCircleTierBounds(tier, thresholds = Neo4jGraphService.CIRCLE_THRESHOLDS) {
|
|
346
|
-
switch (tier) {
|
|
347
|
-
case 0: return { lower: thresholds.innerThreshold, upper: Infinity };
|
|
348
|
-
case 1: return { lower: thresholds.closeFriendThreshold, upper: thresholds.innerThreshold };
|
|
349
|
-
case 2: return { lower: thresholds.communityThreshold, upper: thresholds.closeFriendThreshold };
|
|
350
|
-
case 3: return { lower: 0.001, upper: thresholds.communityThreshold };
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
decodeCircleCursor(cursor) {
|
|
354
|
-
if (!cursor)
|
|
355
|
-
return null;
|
|
356
|
-
try {
|
|
357
|
-
const d = JSON.parse(Buffer.from(cursor, "base64").toString("utf8"));
|
|
358
|
-
if (d.createdAt && d.postId)
|
|
359
|
-
return d;
|
|
360
|
-
return null;
|
|
361
|
-
}
|
|
362
|
-
catch {
|
|
363
|
-
return null;
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
encodeCircleCursor(createdAt, postId) {
|
|
367
|
-
return Buffer.from(JSON.stringify({ createdAt, postId })).toString("base64");
|
|
368
|
-
}
|
|
369
|
-
// --- Circle Resolution methods (P2.2) ---
|
|
370
|
-
async getCircleMembers(userId, tier) {
|
|
371
|
-
const bounds = this.getCircleTierBounds(tier);
|
|
372
|
-
const result = await this.executeQuery(`MATCH (viewer:User {id: $viewerId})-[r:RELATES_TO]->(target)
|
|
373
|
-
WHERE r.score >= $lowerThreshold AND r.score < $upperThreshold
|
|
374
|
-
RETURN target.id AS id, labels(target)[0] AS type, target.name AS name, r.score AS score
|
|
375
|
-
ORDER BY r.score DESC`, { viewerId: userId, lowerThreshold: bounds.lower, upperThreshold: bounds.upper === Infinity ? 1e9 : bounds.upper });
|
|
376
|
-
return result.records.map((rec) => ({
|
|
377
|
-
id: rec.get("id"),
|
|
378
|
-
type: rec.get("type").toLowerCase() === "entity" ? "entity" : "user",
|
|
379
|
-
name: rec.get("name"),
|
|
380
|
-
score: rec.get("score"),
|
|
381
|
-
tier,
|
|
382
|
-
}));
|
|
383
|
-
}
|
|
384
|
-
async getVisiblePostIds(userId, tier, since, pagination) {
|
|
385
|
-
const bounds = this.getCircleTierBounds(tier);
|
|
386
|
-
const t = Neo4jGraphService.CIRCLE_THRESHOLDS;
|
|
387
|
-
const cursor = this.decodeCircleCursor(pagination.cursor ?? undefined);
|
|
388
|
-
const cursorClause = cursor
|
|
389
|
-
? `AND (post.createdAt < datetime($cursorCreatedAt) OR (post.createdAt = datetime($cursorCreatedAt) AND post.id < $cursorPostId))`
|
|
390
|
-
: "";
|
|
391
|
-
const query = `
|
|
392
|
-
MATCH (viewer:User {id: $viewerId})-[r:RELATES_TO]->(entity:Entity)
|
|
393
|
-
WHERE r.score >= $lowerThreshold AND r.score < $upperThreshold
|
|
394
|
-
WITH entity, r.score AS relScore
|
|
395
|
-
MATCH (post:Post)-[:ABOUT]->(entity)
|
|
396
|
-
WHERE post.createdAt > datetime($since) AND post.radiusInt >= $tierInt ${cursorClause}
|
|
397
|
-
WITH post, MIN(CASE WHEN relScore >= $innerThreshold THEN 0 WHEN relScore >= $closeFriendThreshold THEN 1 WHEN relScore >= $communityThreshold THEN 2 ELSE 3 END) AS resolvedTier
|
|
398
|
-
RETURN post.id AS postId, post.createdAt AS createdAt, resolvedTier
|
|
399
|
-
UNION
|
|
400
|
-
MATCH (viewer:User {id: $viewerId})-[r:RELATES_TO]->(author:User)
|
|
401
|
-
WHERE r.score >= $lowerThreshold AND r.score < $upperThreshold
|
|
402
|
-
WITH author, r.score AS relScore
|
|
403
|
-
MATCH (post:Post)
|
|
404
|
-
WHERE post.authorId = author.id AND post.createdAt > datetime($since) AND post.radiusInt >= $tierInt ${cursorClause}
|
|
405
|
-
WITH post, MIN(CASE WHEN relScore >= $innerThreshold THEN 0 WHEN relScore >= $closeFriendThreshold THEN 1 WHEN relScore >= $communityThreshold THEN 2 ELSE 3 END) AS resolvedTier
|
|
406
|
-
RETURN post.id AS postId, post.createdAt AS createdAt, resolvedTier
|
|
407
|
-
ORDER BY createdAt DESC
|
|
408
|
-
LIMIT $limit`;
|
|
409
|
-
const params = {
|
|
410
|
-
viewerId: userId, lowerThreshold: bounds.lower,
|
|
411
|
-
upperThreshold: bounds.upper === Infinity ? 1e9 : bounds.upper,
|
|
412
|
-
since: since.toISOString(), tierInt: tier,
|
|
413
|
-
innerThreshold: t.innerThreshold, closeFriendThreshold: t.closeFriendThreshold,
|
|
414
|
-
communityThreshold: t.communityThreshold, limit: pagination.limit + 1,
|
|
415
|
-
};
|
|
416
|
-
if (cursor) {
|
|
417
|
-
params.cursorCreatedAt = cursor.createdAt;
|
|
418
|
-
params.cursorPostId = cursor.postId;
|
|
419
|
-
}
|
|
420
|
-
const result = await this.executeQuery(query, params);
|
|
421
|
-
const items = result.records.slice(0, pagination.limit).map((rec) => {
|
|
422
|
-
const ca = rec.get("createdAt");
|
|
423
|
-
return { postId: rec.get("postId"), createdAt: ca instanceof Date ? ca : new Date(String(ca)), resolvedTier: rec.get("resolvedTier") };
|
|
424
|
-
});
|
|
425
|
-
const hasMore = result.records.length > pagination.limit;
|
|
426
|
-
const last = items[items.length - 1];
|
|
427
|
-
const nextCursor = hasMore && last ? this.encodeCircleCursor(last.createdAt.toISOString(), last.postId) : null;
|
|
428
|
-
return { items, cursor: nextCursor, hasMore };
|
|
429
|
-
}
|
|
430
|
-
async getGlanceItems(userId, tier, limit) {
|
|
431
|
-
const bounds = this.getCircleTierBounds(tier);
|
|
432
|
-
// Step 1: get tier members
|
|
433
|
-
const membersResult = await this.executeQuery(`MATCH (viewer:User {id: $viewerId})-[r:RELATES_TO]->(target)
|
|
434
|
-
WHERE r.score >= $lowerThreshold AND r.score < $upperThreshold
|
|
435
|
-
RETURN target.id AS targetId, labels(target)[0] AS targetType, target.name AS targetName, r.score AS score
|
|
436
|
-
ORDER BY r.score DESC`, { viewerId: userId, lowerThreshold: bounds.lower, upperThreshold: bounds.upper === Infinity ? 1e9 : bounds.upper });
|
|
437
|
-
const entityIds = [];
|
|
438
|
-
const userIds = [];
|
|
439
|
-
const memberMap = new Map();
|
|
440
|
-
for (const rec of membersResult.records) {
|
|
441
|
-
const tid = rec.get("targetId");
|
|
442
|
-
const rawType = rec.get("targetType").toLowerCase();
|
|
443
|
-
const tt = rawType === "entity" ? "entity" : "user";
|
|
444
|
-
memberMap.set(tid, { targetType: tt, targetName: rec.get("targetName") });
|
|
445
|
-
if (tt === "entity")
|
|
446
|
-
entityIds.push(tid);
|
|
447
|
-
else
|
|
448
|
-
userIds.push(tid);
|
|
449
|
-
}
|
|
450
|
-
const glanceItems = [];
|
|
451
|
-
// Step 2a: entity posts
|
|
452
|
-
if (entityIds.length > 0) {
|
|
453
|
-
const er = await this.executeQuery(`UNWIND $entityIds AS entityId
|
|
454
|
-
MATCH (entity:Entity {id: entityId})<-[:ABOUT]-(post:Post)
|
|
455
|
-
WHERE post.radiusInt >= $tierInt
|
|
456
|
-
WITH entityId, post ORDER BY post.createdAt DESC
|
|
457
|
-
WITH entityId, COLLECT(post)[0] AS latestPost
|
|
458
|
-
WHERE latestPost IS NOT NULL
|
|
459
|
-
RETURN entityId AS targetId, latestPost.id AS postId, latestPost.createdAt AS postCreatedAt`, { entityIds, tierInt: tier });
|
|
460
|
-
for (const rec of er.records) {
|
|
461
|
-
const tid = rec.get("targetId");
|
|
462
|
-
const m = memberMap.get(tid);
|
|
463
|
-
if (!m)
|
|
464
|
-
continue;
|
|
465
|
-
const pca = rec.get("postCreatedAt");
|
|
466
|
-
glanceItems.push({ targetId: tid, targetType: "entity", targetName: m.targetName, postId: rec.get("postId"), postCreatedAt: pca instanceof Date ? pca : new Date(String(pca)) });
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
// Step 2b: user posts
|
|
470
|
-
if (userIds.length > 0) {
|
|
471
|
-
const ur = await this.executeQuery(`UNWIND $userIds AS uId
|
|
472
|
-
MATCH (post:Post) WHERE post.authorId = uId AND post.radiusInt >= $tierInt
|
|
473
|
-
WITH uId, post ORDER BY post.createdAt DESC
|
|
474
|
-
With uId, COLLECT(post)[0] AS latestPost
|
|
475
|
-
WHERE latestPost IS NOT NULL
|
|
476
|
-
RETURN uId AS targetId, latestPost.id AS postId, latestPost.createdAt AS postCreatedAt`, { userIds, tierInt: tier });
|
|
477
|
-
for (const rec of ur.records) {
|
|
478
|
-
const tid = rec.get("targetId");
|
|
479
|
-
const m = memberMap.get(tid);
|
|
480
|
-
if (!m)
|
|
481
|
-
continue;
|
|
482
|
-
const pca = rec.get("postCreatedAt");
|
|
483
|
-
glanceItems.push({ targetId: tid, targetType: "user", targetName: m.targetName, postId: rec.get("postId"), postCreatedAt: pca instanceof Date ? pca : new Date(String(pca)) });
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
glanceItems.sort((a, b) => b.postCreatedAt.getTime() - a.postCreatedAt.getTime());
|
|
487
|
-
return glanceItems.slice(0, limit);
|
|
488
|
-
}
|
|
489
|
-
async getDepthPostIds(userId, targetType, targetId, since, limit) {
|
|
490
|
-
const t = Neo4jGraphService.CIRCLE_THRESHOLDS;
|
|
491
|
-
const commonParams = {
|
|
492
|
-
viewerId: userId, targetId, since: since.toISOString(),
|
|
493
|
-
innerThreshold: t.innerThreshold, closeFriendThreshold: t.closeFriendThreshold,
|
|
494
|
-
communityThreshold: t.communityThreshold, limit,
|
|
495
|
-
};
|
|
496
|
-
if (targetType === "entity") {
|
|
497
|
-
const result = await this.executeQuery(`MATCH (viewer:User {id: $viewerId})-[r:RELATES_TO]->(entity:Entity {id: $targetId})
|
|
498
|
-
MATCH (post:Post)-[:ABOUT]->(entity)
|
|
499
|
-
WHERE post.createdAt > datetime($since)
|
|
500
|
-
AND post.radiusInt >= CASE WHEN r.score >= $innerThreshold THEN 0 WHEN r.score >= $closeFriendThreshold THEN 1 WHEN r.score >= $communityThreshold THEN 2 ELSE 3 END
|
|
501
|
-
RETURN post.id AS postId ORDER BY post.createdAt DESC LIMIT $limit`, commonParams);
|
|
502
|
-
return result.records.map((r) => r.get("postId"));
|
|
503
|
-
}
|
|
504
|
-
const result = await this.executeQuery(`MATCH (viewer:User {id: $viewerId})-[r:RELATES_TO]->(author:User {id: $targetId})
|
|
505
|
-
MATCH (post:Post)
|
|
506
|
-
WHERE post.authorId = author.id AND post.createdAt > datetime($since)
|
|
507
|
-
AND post.radiusInt >= CASE WHEN r.score >= $innerThreshold THEN 0 WHEN r.score >= $closeFriendThreshold THEN 1 WHEN r.score >= $communityThreshold THEN 2 ELSE 3 END
|
|
508
|
-
RETURN post.id AS postId ORDER BY post.createdAt DESC LIMIT $limit`, commonParams);
|
|
509
|
-
return result.records.map((r) => r.get("postId"));
|
|
510
|
-
}
|
|
511
|
-
/**
|
|
512
|
-
* Get circle status for all tiers. Uses four parallel count queries
|
|
513
|
-
* (recommended by circle-queries.md). lastReadTimestamps come from
|
|
514
|
-
* Postgres CircleReadState.
|
|
515
|
-
*/
|
|
516
|
-
async getCircleStatus(userId, lastReadTimestamps) {
|
|
517
|
-
const thresholds = Neo4jGraphService.CIRCLE_THRESHOLDS;
|
|
518
|
-
const defaultLR = new Date(0);
|
|
519
|
-
const lr = {
|
|
520
|
-
0: lastReadTimestamps?.[0] ?? defaultLR, 1: lastReadTimestamps?.[1] ?? defaultLR,
|
|
521
|
-
2: lastReadTimestamps?.[2] ?? defaultLR, 3: lastReadTimestamps?.[3] ?? defaultLR,
|
|
522
|
-
};
|
|
523
|
-
const tiers = [0, 1, 2, 3];
|
|
524
|
-
return Promise.all(tiers.map(async (tier) => {
|
|
525
|
-
const bounds = this.getCircleTierBounds(tier, thresholds);
|
|
526
|
-
const result = await this.executeQuery(`MATCH (viewer:User {id: $viewerId})-[r:RELATES_TO]->(target)
|
|
527
|
-
WHERE r.score >= $lowerThreshold AND r.score < $upperThreshold
|
|
528
|
-
MATCH (post:Post)
|
|
529
|
-
WHERE ((target:Entity AND EXISTS { MATCH (post)-[:ABOUT]->(target) }) OR (target:User AND post.authorId = target.id))
|
|
530
|
-
AND post.radiusInt >= $tierInt AND post.createdAt > datetime($lastReadAt)
|
|
531
|
-
RETURN COUNT(DISTINCT post.id) AS unseenCount`, { viewerId: userId, lowerThreshold: bounds.lower, upperThreshold: bounds.upper === Infinity ? 1e9 : bounds.upper, tierInt: tier, lastReadAt: lr[tier].toISOString() });
|
|
532
|
-
const unseenCount = result.records.length > 0 ? result.records[0].get("unseenCount") : 0;
|
|
533
|
-
return { tier, name: Neo4jGraphService.TIER_NAMES[tier], caughtUp: unseenCount === 0, unseenCount, lastReadAt: lastReadTimestamps?.[tier] ?? null };
|
|
534
|
-
}));
|
|
535
|
-
}
|
|
536
|
-
/**
|
|
537
|
-
* Get per-entity unseen counts within a tier. Uses circle-queries.md Query 5.
|
|
538
|
-
* lastReadAt comes from Postgres CircleReadState.
|
|
539
|
-
*/
|
|
540
|
-
async getCircleEntityStatus(userId, tier, lastReadAt) {
|
|
541
|
-
const bounds = this.getCircleTierBounds(tier);
|
|
542
|
-
const effectiveLR = lastReadAt ?? new Date(0);
|
|
543
|
-
const result = await this.executeQuery(`MATCH (viewer:User {id: $viewerId})-[r:RELATES_TO]->(entity:Entity)
|
|
544
|
-
WHERE r.score >= $lowerThreshold AND r.score < $upperThreshold
|
|
545
|
-
OPTIONAL MATCH (post:Post)-[:ABOUT]->(entity)
|
|
546
|
-
WHERE post.radiusInt >= $tierInt AND post.createdAt > datetime($lastReadAt)
|
|
547
|
-
WITH entity, COUNT(post) AS unseenCount, MAX(post.createdAt) AS latestPostAt
|
|
548
|
-
RETURN entity.id AS entityId, entity.name AS entityName, unseenCount = 0 AS caughtUp, unseenCount, latestPostAt
|
|
549
|
-
ORDER BY unseenCount DESC, latestPostAt DESC`, { viewerId: userId, lowerThreshold: bounds.lower, upperThreshold: bounds.upper === Infinity ? 1e9 : bounds.upper, tierInt: tier, lastReadAt: effectiveLR.toISOString() });
|
|
550
|
-
return result.records.map((rec) => {
|
|
551
|
-
const lpa = rec.get("latestPostAt");
|
|
552
|
-
return {
|
|
553
|
-
entityId: rec.get("entityId"), entityName: rec.get("entityName"),
|
|
554
|
-
caughtUp: rec.get("caughtUp"), unseenCount: rec.get("unseenCount"),
|
|
555
|
-
latestPostAt: lpa ? (lpa instanceof Date ? lpa : new Date(String(lpa))) : null,
|
|
556
|
-
};
|
|
557
|
-
});
|
|
558
|
-
}
|
|
559
|
-
/**
|
|
560
|
-
* Mark a circle tier as read. The canonical read state lives in Postgres
|
|
561
|
-
* (CircleReadState). This stores it on the User node as graph-side fallback.
|
|
562
|
-
*/
|
|
563
|
-
async markCircleRead(userId, tier, readAt) {
|
|
564
|
-
const timestamp = readAt ?? new Date();
|
|
565
|
-
const prop = `lastReadTier${tier}`;
|
|
566
|
-
await this.executeQuery(`MATCH (u:User {id: $userId}) SET u.${prop} = datetime($readAt)`, { userId, readAt: timestamp.toISOString() });
|
|
567
|
-
}
|
|
568
|
-
// =========================================================================
|
|
569
|
-
// Entity Relationships
|
|
570
|
-
// =========================================================================
|
|
571
|
-
async createEntityRelationship(input) {
|
|
572
|
-
const { entityId, relatedEntityId, type, proposedByUserId } = input;
|
|
573
|
-
// 1. Verify proposing user owns the source entity
|
|
574
|
-
const ownershipCheck = await this.executeQuery(`MATCH (u:User {id: $userId})-[o:OWNS]->(e:Entity {id: $entityId})
|
|
575
|
-
RETURN o.role AS role`, { userId: proposedByUserId, entityId });
|
|
576
|
-
if (ownershipCheck.records.length === 0) {
|
|
577
|
-
const { GraphAuthorizationError } = await Promise.resolve().then(() => __importStar(require("./errors")));
|
|
578
|
-
throw new GraphAuthorizationError(`User ${proposedByUserId} does not own entity ${entityId}`);
|
|
579
|
-
}
|
|
580
|
-
// 2. Verify both entity nodes exist
|
|
581
|
-
const entityCheck = await this.executeQuery(`MATCH (a:Entity {id: $entityId})
|
|
582
|
-
MATCH (b:Entity {id: $relatedEntityId})
|
|
583
|
-
RETURN a.id AS aId, b.id AS bId`, { entityId, relatedEntityId });
|
|
584
|
-
if (entityCheck.records.length === 0) {
|
|
585
|
-
const { GraphNotFoundError } = await Promise.resolve().then(() => __importStar(require("./errors")));
|
|
586
|
-
throw new GraphNotFoundError(`One or both entities not found: ${entityId}, ${relatedEntityId}`);
|
|
587
|
-
}
|
|
588
|
-
// 3. Check for existing relationship of this type
|
|
589
|
-
const existingCheck = await this.executeQuery(`MATCH (a:Entity {id: $entityId})-[r:ENTITY_RELATES {type: $type}]->(b:Entity {id: $relatedEntityId})
|
|
590
|
-
RETURN r.status AS status`, { entityId, relatedEntityId, type });
|
|
591
|
-
if (existingCheck.records.length > 0) {
|
|
592
|
-
const { GraphConflictError } = await Promise.resolve().then(() => __importStar(require("./errors")));
|
|
593
|
-
throw new GraphConflictError(`Entity relationship of type ${type} already exists between ${entityId} and ${relatedEntityId}`);
|
|
594
|
-
}
|
|
595
|
-
// 4. Determine if auto-confirm applies.
|
|
596
|
-
// PACK_MATE between entities where a single user holds PRIMARY_OWNER or CO_OWNER
|
|
597
|
-
// on BOTH entities auto-confirms. CARETAKER is excluded per security rules.
|
|
598
|
-
let status = "PENDING";
|
|
599
|
-
if (type === "PACK_MATE") {
|
|
600
|
-
const sharedOwnerCheck = await this.executeQuery(`MATCH (u:User)-[o1:OWNS]->(a:Entity {id: $entityId})
|
|
601
|
-
MATCH (u)-[o2:OWNS]->(b:Entity {id: $relatedEntityId})
|
|
602
|
-
WHERE o1.role IN ['PRIMARY_OWNER', 'CO_OWNER']
|
|
603
|
-
AND o2.role IN ['PRIMARY_OWNER', 'CO_OWNER']
|
|
604
|
-
RETURN u.id AS userId
|
|
605
|
-
LIMIT 1`, { entityId, relatedEntityId });
|
|
606
|
-
if (sharedOwnerCheck.records.length > 0) {
|
|
607
|
-
status = "CONFIRMED";
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
const now = new Date();
|
|
611
|
-
const since = now.toISOString();
|
|
612
|
-
if (status === "CONFIRMED") {
|
|
613
|
-
// Auto-confirmed PACK_MATE: create bidirectional edges immediately
|
|
614
|
-
await this.executeQuery(`MATCH (a:Entity {id: $entityId}), (b:Entity {id: $relatedEntityId})
|
|
615
|
-
CREATE (a)-[:ENTITY_RELATES {
|
|
616
|
-
type: $type,
|
|
617
|
-
status: 'CONFIRMED',
|
|
618
|
-
proposedByUserId: $proposedByUserId,
|
|
619
|
-
since: $since
|
|
620
|
-
}]->(b)
|
|
621
|
-
CREATE (b)-[:ENTITY_RELATES {
|
|
622
|
-
type: $type,
|
|
623
|
-
status: 'CONFIRMED',
|
|
624
|
-
proposedByUserId: $proposedByUserId,
|
|
625
|
-
since: $since
|
|
626
|
-
}]->(a)`, { entityId, relatedEntityId, type, proposedByUserId, since });
|
|
627
|
-
}
|
|
628
|
-
else {
|
|
629
|
-
// Create single PENDING edge from source to target
|
|
630
|
-
await this.executeQuery(`MATCH (a:Entity {id: $entityId}), (b:Entity {id: $relatedEntityId})
|
|
631
|
-
CREATE (a)-[:ENTITY_RELATES {
|
|
632
|
-
type: $type,
|
|
633
|
-
status: 'PENDING',
|
|
634
|
-
proposedByUserId: $proposedByUserId,
|
|
635
|
-
since: $since
|
|
636
|
-
}]->(b)`, { entityId, relatedEntityId, type, proposedByUserId, since });
|
|
637
|
-
}
|
|
638
|
-
return {
|
|
639
|
-
entityId,
|
|
640
|
-
relatedEntityId,
|
|
641
|
-
type,
|
|
642
|
-
status,
|
|
643
|
-
proposedByUserId,
|
|
644
|
-
since: now,
|
|
645
|
-
};
|
|
646
|
-
}
|
|
647
|
-
async confirmEntityRelationship(entityId, relatedEntityId, confirmingUserId) {
|
|
648
|
-
// 1. Verify confirming user owns the target (related) entity
|
|
649
|
-
const ownershipCheck = await this.executeQuery(`MATCH (u:User {id: $userId})-[o:OWNS]->(e:Entity {id: $relatedEntityId})
|
|
650
|
-
RETURN o.role AS role`, { userId: confirmingUserId, relatedEntityId });
|
|
651
|
-
if (ownershipCheck.records.length === 0) {
|
|
652
|
-
const { GraphAuthorizationError } = await Promise.resolve().then(() => __importStar(require("./errors")));
|
|
653
|
-
throw new GraphAuthorizationError(`User ${confirmingUserId} does not own entity ${relatedEntityId}`);
|
|
654
|
-
}
|
|
655
|
-
// 2. Find the pending relationship
|
|
656
|
-
const relCheck = await this.executeQuery(`MATCH (a:Entity {id: $entityId})-[r:ENTITY_RELATES]->(b:Entity {id: $relatedEntityId})
|
|
657
|
-
RETURN r.type AS type, r.status AS status, r.proposedByUserId AS proposedByUserId, r.since AS since`, { entityId, relatedEntityId });
|
|
658
|
-
if (relCheck.records.length === 0) {
|
|
659
|
-
const { GraphNotFoundError } = await Promise.resolve().then(() => __importStar(require("./errors")));
|
|
660
|
-
throw new GraphNotFoundError(`Entity relationship not found between ${entityId} and ${relatedEntityId}`);
|
|
661
|
-
}
|
|
662
|
-
const record = relCheck.records[0];
|
|
663
|
-
const currentStatus = record.get("status");
|
|
664
|
-
const relType = record.get("type");
|
|
665
|
-
const proposedByUserId = record.get("proposedByUserId");
|
|
666
|
-
const since = record.get("since");
|
|
667
|
-
if (currentStatus !== "PENDING") {
|
|
668
|
-
const { GraphConflictError } = await Promise.resolve().then(() => __importStar(require("./errors")));
|
|
669
|
-
throw new GraphConflictError(`Entity relationship is already ${currentStatus}`);
|
|
670
|
-
}
|
|
671
|
-
// 3. Update the existing edge to CONFIRMED
|
|
672
|
-
await this.executeQuery(`MATCH (a:Entity {id: $entityId})-[r:ENTITY_RELATES]->(b:Entity {id: $relatedEntityId})
|
|
673
|
-
SET r.status = 'CONFIRMED'`, { entityId, relatedEntityId });
|
|
674
|
-
// 4. Create or confirm reciprocal edge based on relationship symmetry
|
|
675
|
-
const SYMMETRIC_TYPES = new Set(["PACK_MATE", "SIBLING", "PLAYMATE", "WALK_BUDDY"]);
|
|
676
|
-
const ASYMMETRIC_PAIRS = {
|
|
677
|
-
PARENT: "OFFSPRING",
|
|
678
|
-
OFFSPRING: "PARENT",
|
|
679
|
-
};
|
|
680
|
-
if (SYMMETRIC_TYPES.has(relType)) {
|
|
681
|
-
// Symmetric: B→A edge gets the same type
|
|
682
|
-
const reverseCheck = await this.executeQuery(`MATCH (b:Entity {id: $relatedEntityId})-[r:ENTITY_RELATES {type: $type}]->(a:Entity {id: $entityId})
|
|
683
|
-
RETURN r.status AS status`, { entityId, relatedEntityId, type: relType });
|
|
684
|
-
if (reverseCheck.records.length === 0) {
|
|
685
|
-
await this.executeQuery(`MATCH (a:Entity {id: $entityId}), (b:Entity {id: $relatedEntityId})
|
|
686
|
-
CREATE (b)-[:ENTITY_RELATES {
|
|
687
|
-
type: $type,
|
|
688
|
-
status: 'CONFIRMED',
|
|
689
|
-
proposedByUserId: $proposedByUserId,
|
|
690
|
-
since: $since
|
|
691
|
-
}]->(a)`, { entityId, relatedEntityId, type: relType, proposedByUserId, since });
|
|
692
|
-
}
|
|
693
|
-
else {
|
|
694
|
-
await this.executeQuery(`MATCH (b:Entity {id: $relatedEntityId})-[r:ENTITY_RELATES {type: $type}]->(a:Entity {id: $entityId})
|
|
695
|
-
SET r.status = 'CONFIRMED'`, { entityId, relatedEntityId, type: relType });
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
else if (ASYMMETRIC_PAIRS[relType]) {
|
|
699
|
-
// Asymmetric: B→A edge gets the complementary type (PARENT↔OFFSPRING)
|
|
700
|
-
const inverseType = ASYMMETRIC_PAIRS[relType];
|
|
701
|
-
const reverseCheck = await this.executeQuery(`MATCH (b:Entity {id: $relatedEntityId})-[r:ENTITY_RELATES {type: $inverseType}]->(a:Entity {id: $entityId})
|
|
702
|
-
RETURN r.status AS status`, { entityId, relatedEntityId, inverseType });
|
|
703
|
-
if (reverseCheck.records.length === 0) {
|
|
704
|
-
await this.executeQuery(`MATCH (a:Entity {id: $entityId}), (b:Entity {id: $relatedEntityId})
|
|
705
|
-
CREATE (b)-[:ENTITY_RELATES {
|
|
706
|
-
type: $inverseType,
|
|
707
|
-
status: 'CONFIRMED',
|
|
708
|
-
proposedByUserId: $proposedByUserId,
|
|
709
|
-
since: $since
|
|
710
|
-
}]->(a)`, { entityId, relatedEntityId, inverseType, proposedByUserId, since });
|
|
711
|
-
}
|
|
712
|
-
else {
|
|
713
|
-
await this.executeQuery(`MATCH (b:Entity {id: $relatedEntityId})-[r:ENTITY_RELATES {type: $inverseType}]->(a:Entity {id: $entityId})
|
|
714
|
-
SET r.status = 'CONFIRMED'`, { entityId, relatedEntityId, inverseType });
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
return {
|
|
718
|
-
entityId,
|
|
719
|
-
relatedEntityId,
|
|
720
|
-
type: relType,
|
|
721
|
-
status: "CONFIRMED",
|
|
722
|
-
proposedByUserId,
|
|
723
|
-
since: new Date(since),
|
|
724
|
-
};
|
|
725
|
-
}
|
|
726
|
-
async rejectEntityRelationship(entityId, relatedEntityId, rejectingUserId) {
|
|
727
|
-
// 1. Verify rejecting user owns the target (related) entity
|
|
728
|
-
const ownershipCheck = await this.executeQuery(`MATCH (u:User {id: $userId})-[o:OWNS]->(e:Entity {id: $relatedEntityId})
|
|
729
|
-
RETURN o.role AS role`, { userId: rejectingUserId, relatedEntityId });
|
|
730
|
-
if (ownershipCheck.records.length === 0) {
|
|
731
|
-
const { GraphAuthorizationError } = await Promise.resolve().then(() => __importStar(require("./errors")));
|
|
732
|
-
throw new GraphAuthorizationError(`User ${rejectingUserId} does not own entity ${relatedEntityId}`);
|
|
733
|
-
}
|
|
734
|
-
// 2. Find the relationship
|
|
735
|
-
const relCheck = await this.executeQuery(`MATCH (a:Entity {id: $entityId})-[r:ENTITY_RELATES]->(b:Entity {id: $relatedEntityId})
|
|
736
|
-
RETURN r.status AS status`, { entityId, relatedEntityId });
|
|
737
|
-
if (relCheck.records.length === 0) {
|
|
738
|
-
const { GraphNotFoundError } = await Promise.resolve().then(() => __importStar(require("./errors")));
|
|
739
|
-
throw new GraphNotFoundError(`Entity relationship not found between ${entityId} and ${relatedEntityId}`);
|
|
740
|
-
}
|
|
741
|
-
// 3. Set status to REJECTED
|
|
742
|
-
await this.executeQuery(`MATCH (a:Entity {id: $entityId})-[r:ENTITY_RELATES]->(b:Entity {id: $relatedEntityId})
|
|
743
|
-
SET r.status = 'REJECTED'`, { entityId, relatedEntityId });
|
|
744
|
-
}
|
|
745
|
-
async removeEntityRelationship(entityId, relatedEntityId, removingUserId) {
|
|
746
|
-
// 1. Verify removing user owns either entity
|
|
747
|
-
const ownershipCheck = await this.executeQuery(`MATCH (u:User {id: $userId})-[o:OWNS]->(e:Entity)
|
|
748
|
-
WHERE e.id = $entityId OR e.id = $relatedEntityId
|
|
749
|
-
RETURN e.id AS ownedEntityId
|
|
750
|
-
LIMIT 1`, { userId: removingUserId, entityId, relatedEntityId });
|
|
751
|
-
if (ownershipCheck.records.length === 0) {
|
|
752
|
-
const { GraphAuthorizationError } = await Promise.resolve().then(() => __importStar(require("./errors")));
|
|
753
|
-
throw new GraphAuthorizationError(`User ${removingUserId} does not own either entity ${entityId} or ${relatedEntityId}`);
|
|
754
|
-
}
|
|
755
|
-
// 2. Check relationship exists (in the A→B direction)
|
|
756
|
-
const relCheck = await this.executeQuery(`MATCH (a:Entity {id: $entityId})-[r:ENTITY_RELATES]->(b:Entity {id: $relatedEntityId})
|
|
757
|
-
RETURN r.type AS type`, { entityId, relatedEntityId });
|
|
758
|
-
if (relCheck.records.length === 0) {
|
|
759
|
-
const { GraphNotFoundError } = await Promise.resolve().then(() => __importStar(require("./errors")));
|
|
760
|
-
throw new GraphNotFoundError(`Entity relationship not found between ${entityId} and ${relatedEntityId}`);
|
|
761
|
-
}
|
|
762
|
-
// 3. Delete A→B edge and reciprocal B→A edge if it exists
|
|
763
|
-
await this.executeQuery(`MATCH (a:Entity {id: $entityId})-[r:ENTITY_RELATES]->(b:Entity {id: $relatedEntityId})
|
|
764
|
-
DELETE r`, { entityId, relatedEntityId });
|
|
765
|
-
await this.executeQuery(`OPTIONAL MATCH (b:Entity {id: $relatedEntityId})-[r:ENTITY_RELATES]->(a:Entity {id: $entityId})
|
|
766
|
-
DELETE r`, { entityId, relatedEntityId });
|
|
767
|
-
}
|
|
768
|
-
async getEntityRelationships(entityId, options) {
|
|
769
|
-
let query = `MATCH (a:Entity {id: $entityId})-[r:ENTITY_RELATES]->(b:Entity)
|
|
770
|
-
WHERE 1=1`;
|
|
771
|
-
const params = { entityId };
|
|
772
|
-
if (options?.type) {
|
|
773
|
-
query += ` AND r.type = $type`;
|
|
774
|
-
params.type = options.type;
|
|
775
|
-
}
|
|
776
|
-
if (options?.status) {
|
|
777
|
-
query += ` AND r.status = $status`;
|
|
778
|
-
params.status = options.status;
|
|
779
|
-
}
|
|
780
|
-
query += ` RETURN b.id AS relatedEntityId, r.type AS type, r.status AS status,
|
|
781
|
-
r.proposedByUserId AS proposedByUserId, r.since AS since
|
|
782
|
-
ORDER BY r.since DESC`;
|
|
783
|
-
const result = await this.executeQuery(query, params);
|
|
784
|
-
return result.records.map((record) => ({
|
|
785
|
-
entityId,
|
|
786
|
-
relatedEntityId: record.get("relatedEntityId"),
|
|
787
|
-
type: record.get("type"),
|
|
788
|
-
status: record.get("status"),
|
|
789
|
-
proposedByUserId: record.get("proposedByUserId"),
|
|
790
|
-
since: new Date(record.get("since")),
|
|
791
|
-
}));
|
|
792
|
-
}
|
|
793
|
-
async getPendingEntityRelationships(userId) {
|
|
794
|
-
// Return relationships where the user owns the TARGET entity and the edge is PENDING.
|
|
795
|
-
// The relationship direction is A→B (source→target), and user owns B.
|
|
796
|
-
const result = await this.executeQuery(`MATCH (u:User {id: $userId})-[:OWNS]->(b:Entity)
|
|
797
|
-
MATCH (a:Entity)-[r:ENTITY_RELATES {status: 'PENDING'}]->(b)
|
|
798
|
-
RETURN a.id AS entityId, b.id AS relatedEntityId, r.type AS type,
|
|
799
|
-
r.proposedByUserId AS proposedByUserId, r.since AS since
|
|
800
|
-
ORDER BY r.since DESC`, { userId });
|
|
801
|
-
return result.records.map((record) => ({
|
|
802
|
-
entityId: record.get("entityId"),
|
|
803
|
-
relatedEntityId: record.get("relatedEntityId"),
|
|
804
|
-
type: record.get("type"),
|
|
805
|
-
status: "PENDING",
|
|
806
|
-
proposedByUserId: record.get("proposedByUserId"),
|
|
807
|
-
since: new Date(record.get("since")),
|
|
808
|
-
}));
|
|
809
|
-
}
|
|
810
|
-
// =========================================================================
|
|
811
|
-
// Discovery
|
|
812
|
-
//
|
|
813
|
-
// RATE LIMITING NOTE: All discovery endpoints should be rate-limited at
|
|
814
|
-
// 5 requests/minute/user. Enforcement is at the handler/route layer.
|
|
815
|
-
// Hop count is hard-capped at 2 in discoverByGraph.
|
|
816
|
-
// =========================================================================
|
|
817
|
-
/**
|
|
818
|
-
* Discover entities through multi-hop entity-to-entity relationship traversal.
|
|
819
|
-
*
|
|
820
|
-
* Traverses PLAYMATE|PACK_MATE|SIBLING|PARENT|OFFSPRING|WALK_BUDDY edges
|
|
821
|
-
* starting from the user's owned entities. Returns only entities the user
|
|
822
|
-
* does NOT already have a relationship with, and where discoverable != false.
|
|
823
|
-
*
|
|
824
|
-
* SECURITY: Hops are hard-capped at 2 regardless of input. 3-hop traversals
|
|
825
|
-
* can visit 100^3 nodes on popular entities (graph DoS vector). All query
|
|
826
|
-
* values are parameterized — the hop range is a safe numeric literal derived
|
|
827
|
-
* from server-side clamped input, never user-supplied string data.
|
|
828
|
-
*/
|
|
829
|
-
async discoverByGraph(userId, hops, filters) {
|
|
830
|
-
// Hard-cap at 2 regardless of what the caller passes
|
|
831
|
-
const safeHops = hops <= 1 ? 1 : 2;
|
|
832
|
-
const limit = filters?.limit ?? 20;
|
|
833
|
-
const filterClauses = [
|
|
834
|
-
"NOT EXISTS { MATCH (me)-[:RELATES_TO]->(discovered) }",
|
|
835
|
-
"(discovered.discoverable IS NULL OR discovered.discoverable = true)",
|
|
836
|
-
];
|
|
837
|
-
if (filters?.entityType)
|
|
838
|
-
filterClauses.push("discovered.entityType = $entityType");
|
|
839
|
-
if (filters?.breed)
|
|
840
|
-
filterClauses.push("discovered.breed = $breed");
|
|
841
|
-
if (filters?.lifeStage)
|
|
842
|
-
filterClauses.push("discovered.lifeStage = $lifeStage");
|
|
843
|
-
const whereClause = filterClauses.join("\n AND ");
|
|
844
|
-
// safeHops is 1 or 2 — a numeric literal in the template, not user input.
|
|
845
|
-
const hopRange = safeHops === 1 ? "1" : "1..2";
|
|
846
|
-
const query = `
|
|
847
|
-
MATCH (me:User {id: $userId})-[:OWNS]->(myEntity:Entity),
|
|
848
|
-
(myEntity)-[:PLAYMATE|PACK_MATE|SIBLING|PARENT|OFFSPRING|WALK_BUDDY*${hopRange}]-(discovered:Entity)
|
|
849
|
-
WHERE ${whereClause}
|
|
850
|
-
RETURN DISTINCT
|
|
851
|
-
discovered.id AS entityId,
|
|
852
|
-
discovered.name AS name,
|
|
853
|
-
discovered.entityType AS entityType,
|
|
854
|
-
discovered.breed AS breed,
|
|
855
|
-
${safeHops} AS hops
|
|
856
|
-
ORDER BY discovered.name ASC
|
|
857
|
-
LIMIT $limit
|
|
858
|
-
`;
|
|
859
|
-
const params = { userId, limit };
|
|
860
|
-
if (filters?.entityType)
|
|
861
|
-
params.entityType = filters.entityType;
|
|
862
|
-
if (filters?.breed)
|
|
863
|
-
params.breed = filters.breed;
|
|
864
|
-
if (filters?.lifeStage)
|
|
865
|
-
params.lifeStage = filters.lifeStage;
|
|
866
|
-
const result = await this.executeQuery(query, params);
|
|
867
|
-
return result.records.map((record) => {
|
|
868
|
-
const entityId = record.get("entityId");
|
|
869
|
-
const name = record.get("name");
|
|
870
|
-
const entityType = record.get("entityType");
|
|
871
|
-
const breed = record.get("breed");
|
|
872
|
-
const hopCount = record.get("hops");
|
|
873
|
-
const discovery = { entityId, name, entityType, hops: hopCount };
|
|
874
|
-
if (breed)
|
|
875
|
-
discovery.breed = breed;
|
|
876
|
-
return discovery;
|
|
877
|
-
});
|
|
878
|
-
}
|
|
879
|
-
/**
|
|
880
|
-
* Discover entities by geographic proximity using point.distance().
|
|
881
|
-
*
|
|
882
|
-
* Only returns entities where discoverable is not false, and entities the
|
|
883
|
-
* user does NOT already have a relationship with.
|
|
884
|
-
*
|
|
885
|
-
* SECURITY: Exact distances are withheld for all results — only a coarse
|
|
886
|
-
* distance band is returned to prevent location triangulation (security
|
|
887
|
-
* review Finding 15). Coordinates are pre-coarsened to ~1km precision at
|
|
888
|
-
* sync time for additional protection. Entities with existing relationships
|
|
889
|
-
* are excluded so exact distance is never needed here.
|
|
890
|
-
*/
|
|
891
|
-
async discoverNearby(userId, lat, lng, radiusMeters, filters) {
|
|
892
|
-
const limit = filters?.limit ?? 20;
|
|
893
|
-
// These clauses are safe because the filter values come from $params
|
|
894
|
-
const entityTypeFilter = filters?.entityType ? "AND entity.entityType = $entityType" : "";
|
|
895
|
-
const breedFilter = filters?.breed ? "AND entity.breed = $breed" : "";
|
|
896
|
-
const query = `
|
|
897
|
-
MATCH (entity:Entity)
|
|
898
|
-
WHERE (entity.discoverable IS NULL OR entity.discoverable = true)
|
|
899
|
-
AND entity.lat IS NOT NULL
|
|
900
|
-
AND entity.lng IS NOT NULL
|
|
901
|
-
${entityTypeFilter}
|
|
902
|
-
${breedFilter}
|
|
903
|
-
AND point.distance(
|
|
904
|
-
point({latitude: entity.lat, longitude: entity.lng}),
|
|
905
|
-
point({latitude: $lat, longitude: $lng})
|
|
906
|
-
) < $radiusMeters
|
|
907
|
-
WITH entity,
|
|
908
|
-
point.distance(
|
|
909
|
-
point({latitude: entity.lat, longitude: entity.lng}),
|
|
910
|
-
point({latitude: $lat, longitude: $lng})
|
|
911
|
-
) AS distanceMeters
|
|
912
|
-
OPTIONAL MATCH (me:User {id: $userId})-[rel:RELATES_TO]->(entity)
|
|
913
|
-
WITH entity, distanceMeters, rel
|
|
914
|
-
WHERE rel IS NULL
|
|
915
|
-
RETURN entity.id AS entityId,
|
|
916
|
-
entity.name AS name,
|
|
917
|
-
entity.entityType AS entityType,
|
|
918
|
-
entity.breed AS breed,
|
|
919
|
-
distanceMeters
|
|
920
|
-
ORDER BY distanceMeters ASC
|
|
921
|
-
LIMIT $limit
|
|
922
|
-
`;
|
|
923
|
-
const params = { userId, lat, lng, radiusMeters, limit };
|
|
924
|
-
if (filters?.entityType)
|
|
925
|
-
params.entityType = filters.entityType;
|
|
926
|
-
if (filters?.breed)
|
|
927
|
-
params.breed = filters.breed;
|
|
928
|
-
const result = await this.executeQuery(query, params);
|
|
929
|
-
return result.records.map((record) => {
|
|
930
|
-
const entityId = record.get("entityId");
|
|
931
|
-
const name = record.get("name");
|
|
932
|
-
const entityType = record.get("entityType");
|
|
933
|
-
const breed = record.get("breed");
|
|
934
|
-
const distMeters = record.get("distanceMeters");
|
|
935
|
-
const discovery = {
|
|
936
|
-
entityId,
|
|
937
|
-
name,
|
|
938
|
-
entityType,
|
|
939
|
-
// SECURITY: Coarse band only — never exact distance for unrelated entities
|
|
940
|
-
distanceBand: Neo4jGraphService.toDistanceBand(distMeters),
|
|
941
|
-
};
|
|
942
|
-
if (breed)
|
|
943
|
-
discovery.breed = breed;
|
|
944
|
-
return discovery;
|
|
945
|
-
});
|
|
946
|
-
}
|
|
947
|
-
/**
|
|
948
|
-
* Get entity recommendations based on shared connections, breed similarity,
|
|
949
|
-
* and geographic proximity.
|
|
950
|
-
*
|
|
951
|
-
* Runs three parallel queries (signals) and merges results, deduplicated by
|
|
952
|
-
* entity ID, keeping the highest-scoring reason per entity.
|
|
953
|
-
*
|
|
954
|
-
* SECURITY: `owner_proximity` is never exposed as a RecommendationReason.
|
|
955
|
-
* Exposing it would allow the viewer to infer a close relationship with the
|
|
956
|
-
* entity's owner from a visible recommendation, leaking graph topology.
|
|
957
|
-
* Owner proximity is used internally to boost shared-connection scoring but
|
|
958
|
-
* always surfaced as "shared_connections" in the response.
|
|
959
|
-
*/
|
|
960
|
-
async getRecommendations(userId, limit) {
|
|
961
|
-
const params = { userId, limit };
|
|
962
|
-
// Signal 1: Shared connections (also folds in owner proximity internally)
|
|
963
|
-
const sharedConnectionsQuery = `
|
|
964
|
-
MATCH (me:User {id: $userId})-[:OWNS|RELATES_TO]->(myEntity:Entity)
|
|
965
|
-
-[:PLAYMATE|PACK_MATE|SIBLING|PARENT|OFFSPRING|WALK_BUDDY*1..2]-(candidate:Entity)
|
|
966
|
-
WHERE NOT EXISTS { MATCH (me)-[:RELATES_TO]->(candidate) }
|
|
967
|
-
AND NOT EXISTS { MATCH (me)-[:OWNS]->(candidate) }
|
|
968
|
-
AND (candidate.discoverable IS NULL OR candidate.discoverable = true)
|
|
969
|
-
AND candidate.id <> myEntity.id
|
|
970
|
-
WITH candidate, count(DISTINCT myEntity) AS sharedCount
|
|
971
|
-
RETURN candidate.id AS entityId,
|
|
972
|
-
candidate.name AS name,
|
|
973
|
-
candidate.entityType AS entityType,
|
|
974
|
-
toFloat(sharedCount) / 10.0 AS score,
|
|
975
|
-
'shared_connections' AS reason
|
|
976
|
-
ORDER BY score DESC
|
|
977
|
-
LIMIT $limit
|
|
978
|
-
`;
|
|
979
|
-
// Signal 2: Same breed as user's owned entities
|
|
980
|
-
const sameBreedQuery = `
|
|
981
|
-
MATCH (me:User {id: $userId})-[:OWNS]->(myDog:Entity)
|
|
982
|
-
WHERE myDog.breed IS NOT NULL
|
|
983
|
-
WITH me, collect(DISTINCT myDog.breed) AS myBreeds
|
|
984
|
-
MATCH (candidate:Entity)
|
|
985
|
-
WHERE candidate.breed IN myBreeds
|
|
986
|
-
AND NOT EXISTS { MATCH (me)-[:RELATES_TO]->(candidate) }
|
|
987
|
-
AND NOT EXISTS { MATCH (me)-[:OWNS]->(candidate) }
|
|
988
|
-
AND (candidate.discoverable IS NULL OR candidate.discoverable = true)
|
|
989
|
-
RETURN candidate.id AS entityId,
|
|
990
|
-
candidate.name AS name,
|
|
991
|
-
candidate.entityType AS entityType,
|
|
992
|
-
0.6 AS score,
|
|
993
|
-
'same_breed' AS reason
|
|
994
|
-
LIMIT $limit
|
|
995
|
-
`;
|
|
996
|
-
// Signal 3: Geographic proximity to user's owned entities
|
|
997
|
-
const nearbyQuery = `
|
|
998
|
-
MATCH (me:User {id: $userId})-[:OWNS]->(myEntity:Entity)
|
|
999
|
-
WHERE myEntity.lat IS NOT NULL AND myEntity.lng IS NOT NULL
|
|
1000
|
-
WITH me, collect(myEntity) AS myEntities
|
|
1001
|
-
MATCH (candidate:Entity)
|
|
1002
|
-
WHERE candidate.lat IS NOT NULL
|
|
1003
|
-
AND candidate.lng IS NOT NULL
|
|
1004
|
-
AND NOT EXISTS { MATCH (me)-[:RELATES_TO]->(candidate) }
|
|
1005
|
-
AND NOT EXISTS { MATCH (me)-[:OWNS]->(candidate) }
|
|
1006
|
-
AND (candidate.discoverable IS NULL OR candidate.discoverable = true)
|
|
1007
|
-
WITH candidate, myEntities,
|
|
1008
|
-
reduce(minD = 999999999.0, e IN myEntities |
|
|
1009
|
-
CASE
|
|
1010
|
-
WHEN point.distance(
|
|
1011
|
-
point({latitude: candidate.lat, longitude: candidate.lng}),
|
|
1012
|
-
point({latitude: e.lat, longitude: e.lng})
|
|
1013
|
-
) < minD
|
|
1014
|
-
THEN point.distance(
|
|
1015
|
-
point({latitude: candidate.lat, longitude: candidate.lng}),
|
|
1016
|
-
point({latitude: e.lat, longitude: e.lng})
|
|
1017
|
-
)
|
|
1018
|
-
ELSE minD
|
|
1019
|
-
END
|
|
1020
|
-
) AS minDist
|
|
1021
|
-
WHERE minDist < 5000
|
|
1022
|
-
RETURN candidate.id AS entityId,
|
|
1023
|
-
candidate.name AS name,
|
|
1024
|
-
candidate.entityType AS entityType,
|
|
1025
|
-
(1.0 - (minDist / 10000.0)) * 0.5 AS score,
|
|
1026
|
-
'nearby' AS reason
|
|
1027
|
-
ORDER BY minDist ASC
|
|
1028
|
-
LIMIT $limit
|
|
1029
|
-
`;
|
|
1030
|
-
const [sharedResult, breedResult, nearbyResult] = await Promise.all([
|
|
1031
|
-
this.executeQuery(sharedConnectionsQuery, params),
|
|
1032
|
-
this.executeQuery(sameBreedQuery, params),
|
|
1033
|
-
this.executeQuery(nearbyQuery, params),
|
|
1034
|
-
]);
|
|
1035
|
-
// Merge and deduplicate: keep highest-scoring entry per entity
|
|
1036
|
-
const candidateMap = new Map();
|
|
1037
|
-
for (const queryResult of [sharedResult, breedResult, nearbyResult]) {
|
|
1038
|
-
for (const record of queryResult.records) {
|
|
1039
|
-
const entityId = record.get("entityId");
|
|
1040
|
-
const existing = candidateMap.get(entityId);
|
|
1041
|
-
const score = record.get("score");
|
|
1042
|
-
if (!existing || score > existing.score) {
|
|
1043
|
-
candidateMap.set(entityId, {
|
|
1044
|
-
entityId,
|
|
1045
|
-
name: record.get("name"),
|
|
1046
|
-
entityType: record.get("entityType"),
|
|
1047
|
-
score,
|
|
1048
|
-
reason: record.get("reason"),
|
|
1049
|
-
});
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
|
-
return Array.from(candidateMap.values())
|
|
1054
|
-
.sort((a, b) => b.score - a.score)
|
|
1055
|
-
.slice(0, limit)
|
|
1056
|
-
.map((c) => ({
|
|
1057
|
-
entityId: c.entityId,
|
|
1058
|
-
name: c.name,
|
|
1059
|
-
entityType: c.entityType,
|
|
1060
|
-
reason: c.reason,
|
|
1061
|
-
confidence: Math.min(1.0, Math.max(0.0, c.score)),
|
|
1062
|
-
}));
|
|
1063
|
-
}
|
|
1064
|
-
// =========================================================================
|
|
1065
|
-
// Private Static Helpers
|
|
1066
|
-
// =========================================================================
|
|
1067
|
-
/**
|
|
1068
|
-
* Convert an exact distance in meters to a coarse distance band string.
|
|
1069
|
-
*
|
|
1070
|
-
* SECURITY: Used by discoverNearby() to prevent location triangulation.
|
|
1071
|
-
* Values match the DiscoveryResult.distanceBand union type exactly.
|
|
1072
|
-
*/
|
|
1073
|
-
static toDistanceBand(meters) {
|
|
1074
|
-
if (meters < 500)
|
|
1075
|
-
return "< 500m";
|
|
1076
|
-
if (meters < 1000)
|
|
1077
|
-
return "500m-1km";
|
|
1078
|
-
if (meters < 2000)
|
|
1079
|
-
return "1-2km";
|
|
1080
|
-
if (meters < 5000)
|
|
1081
|
-
return "2-5km";
|
|
1082
|
-
return "> 5km";
|
|
1083
|
-
}
|
|
1084
|
-
// =========================================================================
|
|
1085
|
-
// Scoring (Phase 2 — P2.4)
|
|
1086
|
-
// =========================================================================
|
|
1087
|
-
/**
|
|
1088
|
-
* Record a user interaction on a RELATES_TO edge.
|
|
1089
|
-
*
|
|
1090
|
-
* Updates interaction count, last interaction timestamp, and per-type
|
|
1091
|
-
* interaction counters. No-op if the relationship does not exist.
|
|
1092
|
-
*
|
|
1093
|
-
* SECURITY: All queries parameterized.
|
|
1094
|
-
*/
|
|
1095
|
-
async recordInteraction(input) {
|
|
1096
|
-
const targetLabel = input.targetType === "user" ? "User" : "Entity";
|
|
1097
|
-
// Increment total count, set lastInteractionAt, and increment per-type counter.
|
|
1098
|
-
// The per-type counter property is named i_{type} (e.g., i_view, i_comment).
|
|
1099
|
-
// Note: property name is safe — interactionType comes from the InteractionType union.
|
|
1100
|
-
const counterProperty = `i_${input.interactionType}`;
|
|
1101
|
-
await this.executeQuery(`
|
|
1102
|
-
MATCH (u:User {id: $userId})-[r:RELATES_TO]->(t:${targetLabel} {id: $targetId})
|
|
1103
|
-
SET r.interactionCount = coalesce(r.interactionCount, 0) + 1,
|
|
1104
|
-
r.lastInteractionAt = datetime($now),
|
|
1105
|
-
r.${counterProperty} = coalesce(r.${counterProperty}, 0) + 1
|
|
1106
|
-
`, {
|
|
1107
|
-
userId: input.userId,
|
|
1108
|
-
targetId: input.targetId,
|
|
1109
|
-
now: new Date().toISOString(),
|
|
1110
|
-
});
|
|
1111
|
-
}
|
|
1112
|
-
/**
|
|
1113
|
-
* Recompute scores for ALL relationships of a user.
|
|
1114
|
-
*
|
|
1115
|
-
* Batch-processes all RELATES_TO edges in a single query pass per target type,
|
|
1116
|
-
* avoiding N+1 patterns. Returns only relationships where the tier changed.
|
|
1117
|
-
*
|
|
1118
|
-
* SECURITY: All queries parameterized.
|
|
1119
|
-
*/
|
|
1120
|
-
async recomputeScores(userId) {
|
|
1121
|
-
const { computeScore, scoreToTier } = await Promise.resolve().then(() => __importStar(require("./scoring-engine")));
|
|
1122
|
-
const tierChanges = [];
|
|
1123
|
-
const now = new Date();
|
|
1124
|
-
// --- User targets ---
|
|
1125
|
-
// Batch fetch all user relationships with their reciprocity status
|
|
1126
|
-
const userRels = await this.executeQuery(`
|
|
1127
|
-
MATCH (u:User {id: $userId})-[r:RELATES_TO]->(t:User)
|
|
1128
|
-
OPTIONAL MATCH (t)-[rev:RELATES_TO]->(u)
|
|
1129
|
-
RETURN t.id AS targetId,
|
|
1130
|
-
r.computedScore AS oldScore,
|
|
1131
|
-
r.tier AS oldTier,
|
|
1132
|
-
r.connectionMethod AS connectionMethod,
|
|
1133
|
-
r.interactionCount AS interactionCount,
|
|
1134
|
-
r.lastInteractionAt AS lastInteractionAt,
|
|
1135
|
-
r.createdAt AS createdAt,
|
|
1136
|
-
r.manualScore AS manualScore,
|
|
1137
|
-
coalesce(r.i_view, 0) AS i_view,
|
|
1138
|
-
coalesce(r.i_react, 0) AS i_react,
|
|
1139
|
-
coalesce(r.i_comment, 0) AS i_comment,
|
|
1140
|
-
coalesce(r.i_share, 0) AS i_share,
|
|
1141
|
-
coalesce(r.i_depth_mode, 0) AS i_depth_mode,
|
|
1142
|
-
coalesce(r.i_profile_visit, 0) AS i_profile_visit,
|
|
1143
|
-
coalesce(r.i_content_creation, 0) AS i_content_creation,
|
|
1144
|
-
rev IS NOT NULL AS reciprocated
|
|
1145
|
-
`, { userId });
|
|
1146
|
-
// Compute new scores and collect updates
|
|
1147
|
-
const userUpdates = [];
|
|
1148
|
-
for (const record of userRels.records) {
|
|
1149
|
-
const targetId = record.get("targetId");
|
|
1150
|
-
const oldScore = record.get("oldScore") ?? 0;
|
|
1151
|
-
const oldTier = record.get("oldTier") ?? 3;
|
|
1152
|
-
const lastInteractionRaw = record.get("lastInteractionAt");
|
|
1153
|
-
const createdAtRaw = record.get("createdAt");
|
|
1154
|
-
const newComputedScore = computeScore({
|
|
1155
|
-
targetType: "user",
|
|
1156
|
-
connectionMethod: record.get("connectionMethod") ?? "discovery",
|
|
1157
|
-
interactionCount: record.get("interactionCount") ?? 0,
|
|
1158
|
-
interactionsByType: {
|
|
1159
|
-
view: record.get("i_view"),
|
|
1160
|
-
react: record.get("i_react"),
|
|
1161
|
-
comment: record.get("i_comment"),
|
|
1162
|
-
share: record.get("i_share"),
|
|
1163
|
-
depth_mode: record.get("i_depth_mode"),
|
|
1164
|
-
profile_visit: record.get("i_profile_visit"),
|
|
1165
|
-
content_creation: record.get("i_content_creation"),
|
|
1166
|
-
},
|
|
1167
|
-
lastInteractionAt: lastInteractionRaw
|
|
1168
|
-
? new Date(lastInteractionRaw)
|
|
1169
|
-
: null,
|
|
1170
|
-
reciprocated: record.get("reciprocated"),
|
|
1171
|
-
createdAt: createdAtRaw ? new Date(createdAtRaw) : now,
|
|
1172
|
-
manualScore: record.get("manualScore") ?? null,
|
|
1173
|
-
isOwned: false,
|
|
1174
|
-
ownerScore: null,
|
|
1175
|
-
now,
|
|
1176
|
-
});
|
|
1177
|
-
const manualScore = record.get("manualScore") ?? null;
|
|
1178
|
-
const effectiveNewScore = manualScore ?? newComputedScore;
|
|
1179
|
-
const newTier = scoreToTier(effectiveNewScore);
|
|
1180
|
-
userUpdates.push({
|
|
1181
|
-
targetId,
|
|
1182
|
-
newScore: newComputedScore,
|
|
1183
|
-
newTier,
|
|
1184
|
-
});
|
|
1185
|
-
if (newTier !== oldTier) {
|
|
1186
|
-
tierChanges.push({
|
|
1187
|
-
userId,
|
|
1188
|
-
targetType: "user",
|
|
1189
|
-
targetId,
|
|
1190
|
-
previousScore: oldScore,
|
|
1191
|
-
newScore: newComputedScore,
|
|
1192
|
-
previousTier: oldTier,
|
|
1193
|
-
newTier,
|
|
1194
|
-
});
|
|
1195
|
-
}
|
|
1196
|
-
}
|
|
1197
|
-
// Batch-write user relationship scores
|
|
1198
|
-
if (userUpdates.length > 0) {
|
|
1199
|
-
await this.executeQuery(`
|
|
1200
|
-
UNWIND $updates AS upd
|
|
1201
|
-
MATCH (u:User {id: $userId})-[r:RELATES_TO]->(t:User {id: upd.targetId})
|
|
1202
|
-
SET r.computedScore = upd.newScore,
|
|
1203
|
-
r.tier = upd.newTier
|
|
1204
|
-
`, { userId, updates: userUpdates });
|
|
1205
|
-
}
|
|
1206
|
-
// --- Entity targets ---
|
|
1207
|
-
// Batch fetch all entity relationships with ownership and owner scores
|
|
1208
|
-
const entityRels = await this.executeQuery(`
|
|
1209
|
-
MATCH (u:User {id: $userId})-[r:RELATES_TO]->(t:Entity)
|
|
1210
|
-
OPTIONAL MATCH (u)-[:OWNS]->(t)
|
|
1211
|
-
WITH u, r, t, EXISTS((u)-[:OWNS]->(t)) AS isOwned
|
|
1212
|
-
OPTIONAL MATCH (owner:User)-[:OWNS]->(t)
|
|
1213
|
-
WHERE owner.id <> $userId
|
|
1214
|
-
OPTIONAL MATCH (u)-[ownerRel:RELATES_TO]->(owner)
|
|
1215
|
-
WITH u, r, t, isOwned,
|
|
1216
|
-
collect(ownerRel.computedScore) AS ownerScores
|
|
1217
|
-
RETURN t.id AS targetId,
|
|
1218
|
-
r.computedScore AS oldScore,
|
|
1219
|
-
r.tier AS oldTier,
|
|
1220
|
-
r.connectionMethod AS connectionMethod,
|
|
1221
|
-
r.interactionCount AS interactionCount,
|
|
1222
|
-
r.lastInteractionAt AS lastInteractionAt,
|
|
1223
|
-
r.createdAt AS createdAt,
|
|
1224
|
-
r.manualScore AS manualScore,
|
|
1225
|
-
coalesce(r.i_view, 0) AS i_view,
|
|
1226
|
-
coalesce(r.i_react, 0) AS i_react,
|
|
1227
|
-
coalesce(r.i_comment, 0) AS i_comment,
|
|
1228
|
-
coalesce(r.i_share, 0) AS i_share,
|
|
1229
|
-
coalesce(r.i_depth_mode, 0) AS i_depth_mode,
|
|
1230
|
-
coalesce(r.i_profile_visit, 0) AS i_profile_visit,
|
|
1231
|
-
coalesce(r.i_content_creation, 0) AS i_content_creation,
|
|
1232
|
-
isOwned,
|
|
1233
|
-
ownerScores
|
|
1234
|
-
`, { userId });
|
|
1235
|
-
const entityUpdates = [];
|
|
1236
|
-
for (const record of entityRels.records) {
|
|
1237
|
-
const targetId = record.get("targetId");
|
|
1238
|
-
const oldScore = record.get("oldScore") ?? 0;
|
|
1239
|
-
const oldTier = record.get("oldTier") ?? 3;
|
|
1240
|
-
const isOwned = record.get("isOwned");
|
|
1241
|
-
const lastInteractionRaw = record.get("lastInteractionAt");
|
|
1242
|
-
const createdAtRaw = record.get("createdAt");
|
|
1243
|
-
// Average the owner scores for the owner proximity signal
|
|
1244
|
-
const ownerScores = record.get("ownerScores") ?? [];
|
|
1245
|
-
const validOwnerScores = ownerScores.filter((s) => s !== null && s !== undefined);
|
|
1246
|
-
const avgOwnerScore = validOwnerScores.length > 0
|
|
1247
|
-
? validOwnerScores.reduce((a, b) => a + b, 0) /
|
|
1248
|
-
validOwnerScores.length
|
|
1249
|
-
: null;
|
|
1250
|
-
const newComputedScore = computeScore({
|
|
1251
|
-
targetType: "entity",
|
|
1252
|
-
connectionMethod: record.get("connectionMethod") ?? "discovery",
|
|
1253
|
-
interactionCount: record.get("interactionCount") ?? 0,
|
|
1254
|
-
interactionsByType: {
|
|
1255
|
-
view: record.get("i_view"),
|
|
1256
|
-
react: record.get("i_react"),
|
|
1257
|
-
comment: record.get("i_comment"),
|
|
1258
|
-
share: record.get("i_share"),
|
|
1259
|
-
depth_mode: record.get("i_depth_mode"),
|
|
1260
|
-
profile_visit: record.get("i_profile_visit"),
|
|
1261
|
-
content_creation: record.get("i_content_creation"),
|
|
1262
|
-
},
|
|
1263
|
-
lastInteractionAt: lastInteractionRaw
|
|
1264
|
-
? new Date(lastInteractionRaw)
|
|
1265
|
-
: null,
|
|
1266
|
-
reciprocated: false, // Not applicable for entities
|
|
1267
|
-
createdAt: createdAtRaw ? new Date(createdAtRaw) : now,
|
|
1268
|
-
manualScore: record.get("manualScore") ?? null,
|
|
1269
|
-
isOwned,
|
|
1270
|
-
ownerScore: avgOwnerScore,
|
|
1271
|
-
now,
|
|
1272
|
-
});
|
|
1273
|
-
const manualScore = record.get("manualScore") ?? null;
|
|
1274
|
-
const effectiveNewScore = isOwned
|
|
1275
|
-
? 1.0
|
|
1276
|
-
: (manualScore ?? newComputedScore);
|
|
1277
|
-
const newTier = scoreToTier(effectiveNewScore);
|
|
1278
|
-
entityUpdates.push({
|
|
1279
|
-
targetId,
|
|
1280
|
-
newScore: newComputedScore,
|
|
1281
|
-
newTier,
|
|
1282
|
-
});
|
|
1283
|
-
if (newTier !== oldTier) {
|
|
1284
|
-
tierChanges.push({
|
|
1285
|
-
userId,
|
|
1286
|
-
targetType: "entity",
|
|
1287
|
-
targetId,
|
|
1288
|
-
previousScore: oldScore,
|
|
1289
|
-
newScore: newComputedScore,
|
|
1290
|
-
previousTier: oldTier,
|
|
1291
|
-
newTier,
|
|
1292
|
-
});
|
|
1293
|
-
}
|
|
1294
|
-
}
|
|
1295
|
-
// Batch-write entity relationship scores
|
|
1296
|
-
if (entityUpdates.length > 0) {
|
|
1297
|
-
await this.executeQuery(`
|
|
1298
|
-
UNWIND $updates AS upd
|
|
1299
|
-
MATCH (u:User {id: $userId})-[r:RELATES_TO]->(t:Entity {id: upd.targetId})
|
|
1300
|
-
SET r.computedScore = upd.newScore,
|
|
1301
|
-
r.tier = upd.newTier
|
|
1302
|
-
`, { userId, updates: entityUpdates });
|
|
1303
|
-
}
|
|
1304
|
-
return tierChanges;
|
|
1305
|
-
}
|
|
1306
|
-
/**
|
|
1307
|
-
* Apply time-based decay to all of a user's relationship scores.
|
|
1308
|
-
*
|
|
1309
|
-
* - User->User: 50% decay after 60 days of no interaction
|
|
1310
|
-
* - User->Entity: 50% decay after 120 days of no interaction
|
|
1311
|
-
* - Owned entities (:OWNS edge) are exempt — always pinned at 1.0
|
|
1312
|
-
*
|
|
1313
|
-
* SECURITY: All queries parameterized.
|
|
1314
|
-
*/
|
|
1315
|
-
async applyDecay(userId) {
|
|
1316
|
-
const { computeDecay, scoreToTier, USER_DECAY_HALF_LIFE_DAYS, ENTITY_DECAY_HALF_LIFE_DAYS, } = await Promise.resolve().then(() => __importStar(require("./scoring-engine")));
|
|
1317
|
-
const tierChanges = [];
|
|
1318
|
-
const now = new Date();
|
|
1319
|
-
// --- User targets: apply decay ---
|
|
1320
|
-
const userRels = await this.executeQuery(`
|
|
1321
|
-
MATCH (u:User {id: $userId})-[r:RELATES_TO]->(t:User)
|
|
1322
|
-
WHERE r.lastInteractionAt IS NOT NULL
|
|
1323
|
-
RETURN t.id AS targetId,
|
|
1324
|
-
r.computedScore AS currentScore,
|
|
1325
|
-
r.tier AS currentTier,
|
|
1326
|
-
r.manualScore AS manualScore,
|
|
1327
|
-
r.lastInteractionAt AS lastInteractionAt
|
|
1328
|
-
`, { userId });
|
|
1329
|
-
const userDecayUpdates = [];
|
|
1330
|
-
for (const record of userRels.records) {
|
|
1331
|
-
const targetId = record.get("targetId");
|
|
1332
|
-
const currentScore = record.get("currentScore") ?? 0;
|
|
1333
|
-
const currentTier = record.get("currentTier") ?? 3;
|
|
1334
|
-
const manualScore = record.get("manualScore") ?? null;
|
|
1335
|
-
const lastInteractionAt = new Date(record.get("lastInteractionAt"));
|
|
1336
|
-
const decayFactor = computeDecay(lastInteractionAt, now, USER_DECAY_HALF_LIFE_DAYS);
|
|
1337
|
-
// Apply multiplicative decay: score * (1 - decayFactor)
|
|
1338
|
-
const decayedScore = Math.max(0, currentScore * (1 - decayFactor));
|
|
1339
|
-
const effective = manualScore ?? decayedScore;
|
|
1340
|
-
const newTier = scoreToTier(effective);
|
|
1341
|
-
userDecayUpdates.push({
|
|
1342
|
-
targetId,
|
|
1343
|
-
newScore: decayedScore,
|
|
1344
|
-
newTier,
|
|
1345
|
-
});
|
|
1346
|
-
if (newTier !== currentTier) {
|
|
1347
|
-
tierChanges.push({
|
|
1348
|
-
userId,
|
|
1349
|
-
targetType: "user",
|
|
1350
|
-
targetId,
|
|
1351
|
-
previousScore: currentScore,
|
|
1352
|
-
newScore: decayedScore,
|
|
1353
|
-
previousTier: currentTier,
|
|
1354
|
-
newTier,
|
|
1355
|
-
});
|
|
1356
|
-
}
|
|
1357
|
-
}
|
|
1358
|
-
if (userDecayUpdates.length > 0) {
|
|
1359
|
-
await this.executeQuery(`
|
|
1360
|
-
UNWIND $updates AS upd
|
|
1361
|
-
MATCH (u:User {id: $userId})-[r:RELATES_TO]->(t:User {id: upd.targetId})
|
|
1362
|
-
SET r.computedScore = upd.newScore,
|
|
1363
|
-
r.tier = upd.newTier
|
|
1364
|
-
`, { userId, updates: userDecayUpdates });
|
|
1365
|
-
}
|
|
1366
|
-
// --- Entity targets: apply decay (skip owned) ---
|
|
1367
|
-
const entityRels = await this.executeQuery(`
|
|
1368
|
-
MATCH (u:User {id: $userId})-[r:RELATES_TO]->(t:Entity)
|
|
1369
|
-
WHERE r.lastInteractionAt IS NOT NULL
|
|
1370
|
-
AND NOT EXISTS((u)-[:OWNS]->(t))
|
|
1371
|
-
RETURN t.id AS targetId,
|
|
1372
|
-
r.computedScore AS currentScore,
|
|
1373
|
-
r.tier AS currentTier,
|
|
1374
|
-
r.manualScore AS manualScore,
|
|
1375
|
-
r.lastInteractionAt AS lastInteractionAt
|
|
1376
|
-
`, { userId });
|
|
1377
|
-
const entityDecayUpdates = [];
|
|
1378
|
-
for (const record of entityRels.records) {
|
|
1379
|
-
const targetId = record.get("targetId");
|
|
1380
|
-
const currentScore = record.get("currentScore") ?? 0;
|
|
1381
|
-
const currentTier = record.get("currentTier") ?? 3;
|
|
1382
|
-
const manualScore = record.get("manualScore") ?? null;
|
|
1383
|
-
const lastInteractionAt = new Date(record.get("lastInteractionAt"));
|
|
1384
|
-
const decayFactor = computeDecay(lastInteractionAt, now, ENTITY_DECAY_HALF_LIFE_DAYS);
|
|
1385
|
-
const decayedScore = Math.max(0, currentScore * (1 - decayFactor));
|
|
1386
|
-
const effective = manualScore ?? decayedScore;
|
|
1387
|
-
const newTier = scoreToTier(effective);
|
|
1388
|
-
entityDecayUpdates.push({
|
|
1389
|
-
targetId,
|
|
1390
|
-
newScore: decayedScore,
|
|
1391
|
-
newTier,
|
|
1392
|
-
});
|
|
1393
|
-
if (newTier !== currentTier) {
|
|
1394
|
-
tierChanges.push({
|
|
1395
|
-
userId,
|
|
1396
|
-
targetType: "entity",
|
|
1397
|
-
targetId,
|
|
1398
|
-
previousScore: currentScore,
|
|
1399
|
-
newScore: decayedScore,
|
|
1400
|
-
previousTier: currentTier,
|
|
1401
|
-
newTier,
|
|
1402
|
-
});
|
|
1403
|
-
}
|
|
1404
|
-
}
|
|
1405
|
-
if (entityDecayUpdates.length > 0) {
|
|
1406
|
-
await this.executeQuery(`
|
|
1407
|
-
UNWIND $updates AS upd
|
|
1408
|
-
MATCH (u:User {id: $userId})-[r:RELATES_TO]->(t:Entity {id: upd.targetId})
|
|
1409
|
-
SET r.computedScore = upd.newScore,
|
|
1410
|
-
r.tier = upd.newTier
|
|
1411
|
-
`, { userId, updates: entityDecayUpdates });
|
|
1412
|
-
}
|
|
1413
|
-
return tierChanges;
|
|
1414
|
-
}
|
|
1415
|
-
// =========================================================================
|
|
1416
|
-
// Sync — Dual-Write from Postgres
|
|
1417
|
-
// =========================================================================
|
|
1418
|
-
async syncUser(input) {
|
|
1419
|
-
await this.executeQuery(`
|
|
1420
|
-
MERGE (u:User {id: $id})
|
|
1421
|
-
ON CREATE SET u.role = $role
|
|
1422
|
-
ON MATCH SET u.role = $role
|
|
1423
|
-
`, { id: input.id, role: input.role });
|
|
1424
|
-
}
|
|
1425
|
-
async removeUser(userId) {
|
|
1426
|
-
await this.executeQuery(`MATCH (u:User {id: $id}) DETACH DELETE u`, { id: userId });
|
|
1427
|
-
}
|
|
1428
|
-
async syncEntity(input) {
|
|
1429
|
-
await this.executeQuery(`
|
|
1430
|
-
MERGE (e:Entity {id: $id})
|
|
1431
|
-
ON CREATE SET
|
|
1432
|
-
e.entityType = $entityType,
|
|
1433
|
-
e.name = $name,
|
|
1434
|
-
e.breed = $breed,
|
|
1435
|
-
e.lifeStage = $lifeStage,
|
|
1436
|
-
e.lat = $lat,
|
|
1437
|
-
e.lng = $lng
|
|
1438
|
-
ON MATCH SET
|
|
1439
|
-
e.entityType = $entityType,
|
|
1440
|
-
e.name = $name,
|
|
1441
|
-
e.breed = $breed,
|
|
1442
|
-
e.lifeStage = $lifeStage,
|
|
1443
|
-
e.lat = $lat,
|
|
1444
|
-
e.lng = $lng
|
|
1445
|
-
`, {
|
|
1446
|
-
id: input.id,
|
|
1447
|
-
entityType: input.entityType,
|
|
1448
|
-
name: input.name,
|
|
1449
|
-
breed: input.breed ?? null,
|
|
1450
|
-
lifeStage: input.lifeStage ?? null,
|
|
1451
|
-
lat: input.lat ?? null,
|
|
1452
|
-
lng: input.lng ?? null,
|
|
1453
|
-
});
|
|
1454
|
-
}
|
|
1455
|
-
async removeEntity(entityId) {
|
|
1456
|
-
await this.executeQuery(`MATCH (e:Entity {id: $id}) DETACH DELETE e`, { id: entityId });
|
|
1457
|
-
}
|
|
1458
|
-
async syncPost(input) {
|
|
1459
|
-
await this.executeQuery(`
|
|
1460
|
-
MERGE (u:User {id: $authorId})
|
|
1461
|
-
MERGE (p:Post {id: $id})
|
|
1462
|
-
ON CREATE SET
|
|
1463
|
-
p.radius = $radius,
|
|
1464
|
-
p.createdAt = $createdAt
|
|
1465
|
-
ON MATCH SET
|
|
1466
|
-
p.radius = $radius,
|
|
1467
|
-
p.createdAt = $createdAt
|
|
1468
|
-
MERGE (u)-[:AUTHORED]->(p)
|
|
1469
|
-
`, {
|
|
1470
|
-
id: input.id,
|
|
1471
|
-
authorId: input.authorId,
|
|
1472
|
-
radius: input.radius,
|
|
1473
|
-
createdAt: input.createdAt.toISOString(),
|
|
1474
|
-
});
|
|
1475
|
-
}
|
|
1476
|
-
async removePost(postId) {
|
|
1477
|
-
await this.executeQuery(`MATCH (p:Post {id: $id}) DETACH DELETE p`, { id: postId });
|
|
1478
|
-
}
|
|
1479
|
-
async syncPostSubjects(input) {
|
|
1480
|
-
// Delete all existing ABOUT edges from the post, then recreate
|
|
1481
|
-
await this.executeQuery(`
|
|
1482
|
-
MATCH (p:Post {id: $postId})
|
|
1483
|
-
OPTIONAL MATCH (p)-[r:ABOUT]->()
|
|
1484
|
-
DELETE r
|
|
1485
|
-
WITH p
|
|
1486
|
-
UNWIND $entityIds AS eId
|
|
1487
|
-
MATCH (e:Entity {id: eId})
|
|
1488
|
-
MERGE (p)-[rel:ABOUT]->(e)
|
|
1489
|
-
SET rel.isPrimary = (eId = $primaryEntityId)
|
|
1490
|
-
`, {
|
|
1491
|
-
postId: input.postId,
|
|
1492
|
-
entityIds: input.entityIds,
|
|
1493
|
-
primaryEntityId: input.primaryEntityId ?? null,
|
|
1494
|
-
});
|
|
1495
|
-
}
|
|
1496
|
-
async syncOwnership(input) {
|
|
1497
|
-
await this.executeQuery(`
|
|
1498
|
-
MATCH (u:User {id: $userId})
|
|
1499
|
-
MATCH (e:Entity {id: $entityId})
|
|
1500
|
-
MERGE (u)-[r:OWNS]->(e)
|
|
1501
|
-
ON CREATE SET r.role = $role
|
|
1502
|
-
ON MATCH SET r.role = $role
|
|
1503
|
-
`, {
|
|
1504
|
-
userId: input.userId,
|
|
1505
|
-
entityId: input.entityId,
|
|
1506
|
-
role: input.role,
|
|
1507
|
-
});
|
|
1508
|
-
}
|
|
1509
|
-
async removeOwnership(entityId, userId) {
|
|
1510
|
-
await this.executeQuery(`
|
|
1511
|
-
MATCH (u:User {id: $userId})-[r:OWNS]->(e:Entity {id: $entityId})
|
|
1512
|
-
DELETE r
|
|
1513
|
-
`, { userId, entityId });
|
|
1514
|
-
}
|
|
1515
|
-
// =========================================================================
|
|
1516
|
-
// Internal Helpers
|
|
1517
|
-
// =========================================================================
|
|
1518
|
-
/**
|
|
1519
|
-
* Get a new session from the driver. Throws if not connected.
|
|
1520
|
-
* Callers are responsible for closing the session.
|
|
1521
|
-
*/
|
|
1522
|
-
getSession() {
|
|
1523
|
-
if (!this.driver || !this.connected) {
|
|
1524
|
-
throw new errors_1.GraphConnectionError("Not connected to Neo4j");
|
|
1525
|
-
}
|
|
1526
|
-
return this.driver.session();
|
|
1527
|
-
}
|
|
1528
|
-
/**
|
|
1529
|
-
* Execute a parameterized Cypher query and return the result.
|
|
1530
|
-
* Sessions are opened and closed automatically.
|
|
1531
|
-
*
|
|
1532
|
-
* SECURITY: Always use parameters — never interpolate values into the query.
|
|
1533
|
-
*/
|
|
1534
|
-
async executeQuery(query, parameters) {
|
|
1535
|
-
const session = this.getSession();
|
|
1536
|
-
try {
|
|
1537
|
-
return await session.run(query, parameters);
|
|
1538
|
-
}
|
|
1539
|
-
catch (error) {
|
|
1540
|
-
throw new errors_1.GraphQueryError(`Query failed: ${error instanceof Error ? error.message : String(error)}`, query, { cause: error instanceof Error ? error : undefined });
|
|
1541
|
-
}
|
|
1542
|
-
finally {
|
|
1543
|
-
await session.close();
|
|
1544
|
-
}
|
|
1545
|
-
}
|
|
1546
|
-
/**
|
|
1547
|
-
* Initialize the graph schema (constraints and indexes).
|
|
1548
|
-
* Called once on first connect.
|
|
1549
|
-
*/
|
|
1550
|
-
async initializeSchema() {
|
|
1551
|
-
const session = this.getSession();
|
|
1552
|
-
try {
|
|
1553
|
-
await (0, graph_schema_init_1.initGraphSchema)(session);
|
|
1554
|
-
this.schemaInitialized = true;
|
|
1555
|
-
}
|
|
1556
|
-
catch (error) {
|
|
1557
|
-
throw new errors_1.GraphConnectionError(`Schema initialization failed: ${error instanceof Error ? error.message : String(error)}`, { cause: error instanceof Error ? error : undefined });
|
|
1558
|
-
}
|
|
1559
|
-
finally {
|
|
1560
|
-
await session.close();
|
|
1561
|
-
}
|
|
1562
|
-
}
|
|
1563
|
-
}
|
|
1564
|
-
exports.Neo4jGraphService = Neo4jGraphService;
|
|
1565
|
-
// ---------------------------------------------------------------------------
|
|
1566
|
-
// Internal helpers (module-private)
|
|
1567
|
-
// ---------------------------------------------------------------------------
|
|
1568
|
-
/** Map a tier number to its TierSummary key. */
|
|
1569
|
-
function tierToName(tier) {
|
|
1570
|
-
switch (tier) {
|
|
1571
|
-
case 0: return "inner";
|
|
1572
|
-
case 1: return "closeFriends";
|
|
1573
|
-
case 2: return "community";
|
|
1574
|
-
default: return "ambient";
|
|
1575
|
-
}
|
|
1576
|
-
}
|
|
1577
|
-
/**
|
|
1578
|
-
* Encode a score-based pagination cursor.
|
|
1579
|
-
* The cursor is a base64-encoded JSON object with the score.
|
|
1580
|
-
*/
|
|
1581
|
-
function encodeCursor(score) {
|
|
1582
|
-
return Buffer.from(JSON.stringify({ score })).toString("base64");
|
|
1583
|
-
}
|
|
1584
|
-
/**
|
|
1585
|
-
* Decode a score-based pagination cursor.
|
|
1586
|
-
* Returns null if the cursor is invalid.
|
|
1587
|
-
*/
|
|
1588
|
-
function decodeCursor(cursor) {
|
|
1589
|
-
try {
|
|
1590
|
-
const parsed = JSON.parse(Buffer.from(cursor, "base64").toString("utf8"));
|
|
1591
|
-
if (parsed && typeof parsed === "object" && "score" in parsed) {
|
|
1592
|
-
const score = parsed.score;
|
|
1593
|
-
if (typeof score === "number")
|
|
1594
|
-
return score;
|
|
1595
|
-
}
|
|
1596
|
-
return null;
|
|
1597
|
-
}
|
|
1598
|
-
catch {
|
|
1599
|
-
return null;
|
|
1600
|
-
}
|
|
1601
|
-
}
|
|
1602
|
-
/**
|
|
1603
|
-
* Map a raw Neo4j relationship property map to a Relationship object.
|
|
1604
|
-
* All edge properties are stored as-is (numbers, strings, null).
|
|
1605
|
-
*/
|
|
1606
|
-
function recordToRelationship(rel, userId, targetType, targetId) {
|
|
1607
|
-
const computedScore = typeof rel.computedScore === "number" ? rel.computedScore : 0;
|
|
1608
|
-
const manualScore = typeof rel.manualScore === "number" ? rel.manualScore : null;
|
|
1609
|
-
const score = typeof rel.score === "number" ? rel.score : computedScore;
|
|
1610
|
-
return {
|
|
1611
|
-
userId,
|
|
1612
|
-
targetType,
|
|
1613
|
-
targetId,
|
|
1614
|
-
score,
|
|
1615
|
-
computedScore,
|
|
1616
|
-
manualScore,
|
|
1617
|
-
tier: (0, scoring_engine_1.scoreToTier)(score),
|
|
1618
|
-
interactionCount: typeof rel.interactionCount === "number" ? rel.interactionCount : 0,
|
|
1619
|
-
lastInteractionAt: rel.lastInteractionAt != null ? new Date(rel.lastInteractionAt) : null,
|
|
1620
|
-
connectionMethod: (rel.connectionMethod ?? "discovery"),
|
|
1621
|
-
reciprocated: rel.reciprocated === true,
|
|
1622
|
-
createdAt: rel.createdAt != null ? new Date(rel.createdAt) : new Date(),
|
|
1623
|
-
};
|
|
1624
|
-
}
|
|
1625
|
-
//# sourceMappingURL=neo4j-graph-service.js.map
|