@m6d/cortex-server 1.8.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -3
- package/contracts/README.md +23 -0
- package/contracts/graph/embed.ts +50 -0
- package/{src/lib → contracts}/graph/helpers.ts +4 -10
- package/{src/lib → contracts}/graph/neo4j.ts +14 -27
- package/contracts/graph/schema.ts +65 -0
- package/{src/lib → contracts}/graph/types.ts +2 -29
- package/contracts/graph.ts +36 -0
- package/contracts/rich-text.ts +207 -0
- package/contracts/runtime.ts +243 -0
- package/contracts/wire.ts +143 -0
- package/dist/contracts/graph/embed.d.ts +22 -0
- package/dist/{src/lib → contracts}/graph/helpers.d.ts +16 -14
- package/dist/{src/lib → contracts}/graph/neo4j.d.ts +5 -5
- package/dist/contracts/graph/schema.d.ts +62 -0
- package/dist/{src/lib → contracts}/graph/types.d.ts +1 -1
- package/dist/contracts/graph.d.ts +14 -0
- package/dist/contracts/rich-text.d.ts +32 -0
- package/dist/contracts/runtime.d.ts +310 -0
- package/dist/contracts/wire.d.ts +120 -0
- package/dist/src/lib/adapters/database/index.d.ts +61 -0
- package/dist/src/lib/adapters/database/message-content.d.ts +30 -0
- package/dist/src/lib/adapters/database/mssql/attachments.d.ts +100 -0
- package/dist/src/lib/adapters/database/mssql/client.d.ts +8 -0
- package/dist/src/lib/adapters/database/mssql/index.d.ts +194 -0
- package/dist/src/lib/adapters/database/mssql/llm-requests.d.ts +17 -0
- package/dist/src/lib/adapters/database/mssql/messages.d.ts +37 -0
- package/dist/src/lib/adapters/database/mssql/threads.d.ts +50 -0
- package/dist/src/lib/adapters/database/postgres/attachments.d.ts +113 -0
- package/dist/src/lib/adapters/database/postgres/client.d.ts +8 -0
- package/dist/src/lib/adapters/database/postgres/index.d.ts +207 -0
- package/dist/src/lib/adapters/database/postgres/llm-requests.d.ts +17 -0
- package/dist/src/lib/adapters/database/postgres/messages.d.ts +37 -0
- package/dist/src/lib/adapters/database/postgres/threads.d.ts +50 -0
- package/{src/lib/adapters/storage.ts → dist/src/lib/adapters/storage/index.d.ts} +1 -1
- package/dist/src/lib/adapters/{minio.d.ts → storage/minio.d.ts} +1 -1
- package/dist/src/lib/ai/active-runs.d.ts +63 -0
- package/dist/src/lib/ai/attachments.d.ts +30 -0
- package/dist/src/lib/ai/backend-url.d.ts +16 -0
- package/dist/src/lib/ai/cc-runtime.d.ts +61 -0
- package/dist/src/lib/ai/commit-gate.d.ts +21 -0
- package/dist/src/lib/ai/context/builder.d.ts +6 -7
- package/dist/src/lib/ai/context/compression-middleware.d.ts +15 -0
- package/dist/src/lib/ai/context/compressor.d.ts +2 -13
- package/dist/src/lib/ai/context/intra-turn-compressor.d.ts +9 -9
- package/dist/src/lib/ai/context/optimize-thread-context.d.ts +9 -0
- package/dist/src/lib/ai/context/summarizer.d.ts +3 -3
- package/dist/src/lib/ai/context/token-estimator.d.ts +5 -8
- package/dist/src/lib/ai/fetch.d.ts +1 -1
- package/dist/src/lib/ai/finish-turn.d.ts +23 -0
- package/dist/src/lib/ai/helpers.d.ts +37 -4
- package/dist/src/lib/ai/index.d.ts +6 -3
- package/dist/src/lib/ai/inspector.d.ts +41 -0
- package/dist/src/lib/ai/interceptors/request-interceptor.d.ts +6 -5
- package/dist/src/lib/ai/prompt.d.ts +5 -4
- package/dist/src/lib/ai/redis-runs.d.ts +41 -0
- package/dist/src/lib/ai/redis-stream-log.d.ts +39 -0
- package/dist/src/lib/ai/tools/execute-code.tool.d.ts +18 -4
- package/dist/src/lib/ai/tools/query-graph.tool.d.ts +13 -5
- package/dist/src/lib/ai/tools/read-attachment.tool.d.ts +21 -0
- package/dist/src/lib/ai/tools/search-common.d.ts +17 -0
- package/dist/src/lib/ai/tools/search-knowledge.tool.d.ts +15 -0
- package/dist/src/lib/ai/tools/search-services.tool.d.ts +12 -0
- package/dist/src/lib/ai/tools/search-tools.tool.d.ts +12 -0
- package/dist/src/lib/ai/turn-tools.d.ts +39 -0
- package/dist/src/lib/ai/vision/rasterize-pdf.d.ts +26 -0
- package/dist/src/lib/ai/vision/rasterize-pdf.worker.d.ts +1 -0
- package/dist/src/lib/ai/vision/vision-reader.d.ts +45 -0
- package/dist/src/lib/cc/client.d.ts +183 -0
- package/dist/src/lib/cc/config-cache.d.ts +20 -0
- package/dist/src/lib/cc/format.d.ts +23 -0
- package/dist/src/lib/cc/registry.d.ts +34 -0
- package/dist/src/lib/cc/types.d.ts +27 -0
- package/dist/src/lib/config.d.ts +81 -64
- package/dist/src/lib/db/drizzle.config.mssql.d.ts +2 -0
- package/dist/src/lib/db/drizzle.config.pg.d.ts +2 -0
- package/dist/src/lib/db/migrate.d.ts +7 -1
- package/dist/src/lib/db/{schema.d.ts → schema.mssql.d.ts} +138 -29
- package/dist/src/lib/db/schema.pg.d.ts +546 -0
- package/dist/src/lib/factory.d.ts +2 -12
- package/dist/src/lib/graph/index.d.ts +10 -11
- package/dist/src/lib/graph/resolver.d.ts +2 -3
- package/dist/src/lib/index.d.ts +3 -3
- package/dist/src/lib/redis.d.ts +19 -0
- package/dist/src/lib/routes/chat.d.ts +1 -1
- package/dist/src/lib/routes/files.d.ts +1 -1
- package/dist/src/lib/routes/owned-thread.d.ts +17 -0
- package/dist/src/lib/routes/threads.d.ts +1 -1
- package/dist/src/lib/routes/ws.d.ts +1 -1
- package/dist/src/lib/types.d.ts +20 -37
- package/dist/src/lib/ws/connections.d.ts +2 -1
- package/package.json +33 -18
- package/src/lib/adapters/database/index.ts +78 -0
- package/src/lib/adapters/database/message-content.ts +39 -0
- package/src/lib/adapters/database/mssql/attachments.ts +90 -0
- package/src/lib/adapters/database/mssql/client.ts +11 -0
- package/src/lib/adapters/database/mssql/index.ts +23 -0
- package/src/lib/adapters/database/mssql/llm-requests.ts +43 -0
- package/src/lib/adapters/database/mssql/messages.ts +92 -0
- package/src/lib/adapters/database/mssql/threads.ts +83 -0
- package/src/lib/adapters/database/postgres/attachments.ts +88 -0
- package/src/lib/adapters/database/postgres/client.ts +11 -0
- package/src/lib/adapters/database/postgres/index.ts +23 -0
- package/src/lib/adapters/database/postgres/llm-requests.ts +45 -0
- package/src/lib/adapters/database/postgres/messages.ts +92 -0
- package/src/lib/adapters/database/postgres/threads.ts +84 -0
- package/{dist/src/lib/adapters/storage.d.ts → src/lib/adapters/storage/index.ts} +1 -1
- package/src/lib/adapters/{minio.ts → storage/minio.ts} +5 -5
- package/src/lib/ai/active-runs.ts +157 -0
- package/src/lib/ai/attachments.ts +144 -0
- package/src/lib/ai/backend-url.ts +27 -0
- package/src/lib/ai/cc-runtime.ts +63 -0
- package/src/lib/ai/commit-gate.ts +116 -0
- package/src/lib/ai/context/builder.ts +12 -14
- package/src/lib/ai/context/compression-middleware.ts +44 -0
- package/src/lib/ai/context/compressor.ts +67 -59
- package/src/lib/ai/context/intra-turn-compressor.ts +121 -204
- package/src/lib/ai/context/optimize-thread-context.ts +50 -0
- package/src/lib/ai/context/summarizer.ts +28 -30
- package/src/lib/ai/context/token-estimator.ts +35 -30
- package/src/lib/ai/fetch.ts +10 -5
- package/src/lib/ai/finish-turn.ts +75 -0
- package/src/lib/ai/helpers.ts +83 -15
- package/src/lib/ai/index.ts +366 -329
- package/src/lib/ai/inspector.ts +112 -0
- package/src/lib/ai/interceptors/request-interceptor.ts +70 -42
- package/src/lib/ai/prompt.ts +126 -125
- package/src/lib/ai/redis-runs.ts +167 -0
- package/src/lib/ai/redis-stream-log.ts +219 -0
- package/src/lib/ai/tools/execute-code.tool.ts +178 -67
- package/src/lib/ai/tools/query-graph.tool.ts +7 -8
- package/src/lib/ai/tools/read-attachment.tool.ts +59 -0
- package/src/lib/ai/tools/search-common.ts +15 -0
- package/src/lib/ai/tools/search-knowledge.tool.ts +33 -0
- package/src/lib/ai/tools/search-services.tool.ts +31 -0
- package/src/lib/ai/tools/search-tools.tool.ts +29 -0
- package/src/lib/ai/turn-tools.ts +154 -0
- package/src/lib/ai/vision/rasterize-pdf.ts +69 -0
- package/src/lib/ai/vision/rasterize-pdf.worker.ts +81 -0
- package/src/lib/ai/vision/vision-reader.ts +130 -0
- package/src/lib/auth/middleware.ts +5 -3
- package/src/lib/cc/client.ts +273 -0
- package/src/lib/cc/config-cache.ts +32 -0
- package/src/lib/cc/format.ts +92 -0
- package/src/lib/cc/registry.ts +62 -0
- package/src/lib/cc/types.ts +44 -0
- package/src/lib/config.ts +102 -71
- package/src/lib/db/drizzle.config.mssql.ts +9 -0
- package/src/lib/db/drizzle.config.pg.ts +9 -0
- package/src/lib/db/migrate.ts +45 -10
- package/src/lib/db/migrations/mssql/20260729092726_talented_wilson_fisk/migration.sql +20 -0
- package/src/lib/db/migrations/mssql/20260729092726_talented_wilson_fisk/snapshot.json +497 -0
- package/src/lib/db/migrations/mssql/20260801003652_perpetual_blue_shield/migration.sql +1 -0
- package/src/lib/db/migrations/mssql/20260801003652_perpetual_blue_shield/snapshot.json +516 -0
- package/src/lib/db/migrations/mssql/20260812174642_user_id_text/migration.sql +2 -0
- package/src/lib/db/migrations/mssql/20260812174642_user_id_text/snapshot.json +516 -0
- package/src/lib/db/migrations/pg/20260801084117_thick_ben_grimm/migration.sql +53 -0
- package/src/lib/db/migrations/pg/20260801084117_thick_ben_grimm/snapshot.json +584 -0
- package/src/lib/db/migrations/pg/20260812174622_user_id_text/migration.sql +2 -0
- package/src/lib/db/migrations/pg/20260812174622_user_id_text/snapshot.json +584 -0
- package/src/lib/db/{schema.ts → schema.mssql.ts} +29 -18
- package/src/lib/db/schema.pg.ts +95 -0
- package/src/lib/factory.ts +44 -89
- package/src/lib/graph/index.ts +12 -40
- package/src/lib/graph/resolver.ts +146 -116
- package/src/lib/index.ts +11 -4
- package/src/lib/redis.ts +44 -0
- package/src/lib/routes/chat.ts +91 -62
- package/src/lib/routes/files.ts +105 -83
- package/src/lib/routes/owned-thread.ts +17 -0
- package/src/lib/routes/threads.ts +14 -30
- package/src/lib/routes/ws.ts +9 -3
- package/src/lib/types.ts +37 -36
- package/src/lib/ws/connections.ts +9 -1
- package/tsconfig.json +48 -0
- package/dist/src/lib/adapters/database.d.ts +0 -45
- package/dist/src/lib/adapters/mssql.d.ts +0 -3
- package/dist/src/lib/ai/active-streams.d.ts +0 -14
- package/dist/src/lib/ai/context/index.d.ts +0 -16
- package/dist/src/lib/ai/tools/capture-files.tool.d.ts +0 -6
- package/dist/src/lib/cli/extract-endpoints.d.ts +0 -6
- package/dist/src/lib/graph/expand-domains.d.ts +0 -2
- package/dist/src/lib/graph/generate-cypher.d.ts +0 -22
- package/dist/src/lib/graph/seed.d.ts +0 -19
- package/dist/src/lib/graph/validate.d.ts +0 -2
- package/dist/src/lib/routes/index.d.ts +0 -4
- package/dist/src/lib/ws/events.d.ts +0 -39
- package/dist/src/lib/ws/index.d.ts +0 -3
- package/dist/src/lib/ws/notify.d.ts +0 -2
- package/src/lib/adapters/database.ts +0 -48
- package/src/lib/adapters/mssql.ts +0 -231
- package/src/lib/ai/active-streams.ts +0 -123
- package/src/lib/ai/context/index.ts +0 -80
- package/src/lib/ai/tools/capture-files.tool.ts +0 -20
- package/src/lib/cli/extract-endpoints.ts +0 -550
- package/src/lib/graph/expand-domains.ts +0 -276
- package/src/lib/graph/generate-cypher.ts +0 -192
- package/src/lib/graph/seed.ts +0 -162
- package/src/lib/graph/validate.ts +0 -78
- package/src/lib/routes/index.ts +0 -4
- package/src/lib/ws/events.ts +0 -39
- package/src/lib/ws/index.ts +0 -11
- package/src/lib/ws/notify.ts +0 -9
- package/src/sample/db/client.ts +0 -9
- package/src/sample/db/migrations/20260411023125_rich_purple_man/migration.sql +0 -5
- package/src/sample/db/migrations/20260411023125_rich_purple_man/snapshot.json +0 -50
- package/src/sample/db/schema.ts +0 -10
- package/src/sample/domains/account/concepts/session.concept.ts +0 -17
- package/src/sample/domains/account/endpoints/listCurrentSessions.endpoint.ts +0 -31
- package/src/sample/domains/account/endpoints/revokeAllOtherSessions.endpoint.ts +0 -22
- package/src/sample/domains/account/endpoints/revokeCurrentSession.endpoint.ts +0 -33
- package/src/sample/domains/account/index.ts +0 -18
- package/src/sample/domains/appraisals/concepts/appraisalCycle.concept.ts +0 -15
- package/src/sample/domains/appraisals/concepts/appraisalRecord.concept.ts +0 -20
- package/src/sample/domains/appraisals/concepts/appraisalRecordCompetency.concept.ts +0 -10
- package/src/sample/domains/appraisals/concepts/appraisalRecordFunctionalRequirement.concept.ts +0 -10
- package/src/sample/domains/appraisals/concepts/appraisalRecordGoal.concept.ts +0 -17
- package/src/sample/domains/appraisals/endpoints/getAppraisalRecordById.endpoint.ts +0 -321
- package/src/sample/domains/appraisals/endpoints/getAppraisalRecordCompetencies.endpoint.ts +0 -120
- package/src/sample/domains/appraisals/endpoints/getAppraisalRecordFunctionalRequirements.endpoint.ts +0 -126
- package/src/sample/domains/appraisals/endpoints/getAppraisalRecordGoals.endpoint.ts +0 -104
- package/src/sample/domains/appraisals/endpoints/getAppraisalRecordScore.endpoint.ts +0 -58
- package/src/sample/domains/appraisals/endpoints/listAppraisalCyclesSimple.endpoint.ts +0 -52
- package/src/sample/domains/appraisals/endpoints/listAppraisalRecords.endpoint.ts +0 -245
- package/src/sample/domains/appraisals/endpoints/listAppraisalTemplatesSimple.endpoint.ts +0 -52
- package/src/sample/domains/appraisals/index.ts +0 -40
- package/src/sample/domains/appraisals/rules/appraisalRecordScoping.rule.ts +0 -7
- package/src/sample/domains/attendance/concepts/attendancePermission.concept.ts +0 -17
- package/src/sample/domains/attendance/concepts/attendanceTransaction.concept.ts +0 -17
- package/src/sample/domains/attendance/concepts/publicHoliday.concept.ts +0 -10
- package/src/sample/domains/attendance/concepts/punch.concept.ts +0 -19
- package/src/sample/domains/attendance/endpoints/getAttendanceTransactionById.endpoint.ts +0 -444
- package/src/sample/domains/attendance/endpoints/getCurrentAttendanceProfile.endpoint.ts +0 -886
- package/src/sample/domains/attendance/endpoints/getCurrentAttendanceStatistics.endpoint.ts +0 -49
- package/src/sample/domains/attendance/endpoints/listAttendancePermissions.endpoint.ts +0 -176
- package/src/sample/domains/attendance/endpoints/listAttendanceTransactionStates.endpoint.ts +0 -26
- package/src/sample/domains/attendance/endpoints/listAttendanceTransactions.endpoint.ts +0 -373
- package/src/sample/domains/attendance/endpoints/listPublicHolidaysSimple.endpoint.ts +0 -52
- package/src/sample/domains/attendance/endpoints/listPunchTypes.endpoint.ts +0 -25
- package/src/sample/domains/attendance/endpoints/listPunches.endpoint.ts +0 -178
- package/src/sample/domains/attendance/index.ts +0 -41
- package/src/sample/domains/attendance/services/attendancePermissions.service.ts +0 -15
- package/src/sample/domains/employees/concepts/employee.concept.ts +0 -20
- package/src/sample/domains/employees/concepts/employeeBankAccount.concept.ts +0 -19
- package/src/sample/domains/employees/concepts/employeeChild.concept.ts +0 -19
- package/src/sample/domains/employees/concepts/employeeDocument.concept.ts +0 -21
- package/src/sample/domains/employees/concepts/employeeEvent.concept.ts +0 -21
- package/src/sample/domains/employees/concepts/employeeExperience.concept.ts +0 -18
- package/src/sample/domains/employees/concepts/employeeJobLevelChange.concept.ts +0 -21
- package/src/sample/domains/employees/concepts/employeeMedal.concept.ts +0 -20
- package/src/sample/domains/employees/concepts/employeeQualification.concept.ts +0 -20
- package/src/sample/domains/employees/concepts/employeeSpouse.concept.ts +0 -21
- package/src/sample/domains/employees/concepts/employeeViolation.concept.ts +0 -19
- package/src/sample/domains/employees/endpoints/getCurrentEmployee.endpoint.ts +0 -1651
- package/src/sample/domains/employees/endpoints/getCurrentIdentity.endpoint.ts +0 -197
- package/src/sample/domains/employees/endpoints/getCurrentManagers.endpoint.ts +0 -142
- package/src/sample/domains/employees/endpoints/getEmployeeById.endpoint.ts +0 -1659
- package/src/sample/domains/employees/endpoints/getEmployeeStatistics.endpoint.ts +0 -43
- package/src/sample/domains/employees/endpoints/listBankAccounts.endpoint.ts +0 -223
- package/src/sample/domains/employees/endpoints/listChildren.endpoint.ts +0 -211
- package/src/sample/domains/employees/endpoints/listDocuments.endpoint.ts +0 -222
- package/src/sample/domains/employees/endpoints/listEmployeesSimple.endpoint.ts +0 -156
- package/src/sample/domains/employees/endpoints/listEvents.endpoint.ts +0 -120
- package/src/sample/domains/employees/endpoints/listExperiences.endpoint.ts +0 -188
- package/src/sample/domains/employees/endpoints/listJobLevelChanges.endpoint.ts +0 -240
- package/src/sample/domains/employees/endpoints/listMedals.endpoint.ts +0 -266
- package/src/sample/domains/employees/endpoints/listQualifications.endpoint.ts +0 -265
- package/src/sample/domains/employees/endpoints/listSpouses.endpoint.ts +0 -278
- package/src/sample/domains/employees/endpoints/listViolations.endpoint.ts +0 -245
- package/src/sample/domains/employees/index.ts +0 -75
- package/src/sample/domains/employees/services/employees.service.ts +0 -181
- package/src/sample/domains/index.ts +0 -10
- package/src/sample/domains/leaves/concepts/leave.concept.ts +0 -21
- package/src/sample/domains/leaves/concepts/leaveBalance.concept.ts +0 -17
- package/src/sample/domains/leaves/concepts/leaveBalanceTransaction.concept.ts +0 -16
- package/src/sample/domains/leaves/concepts/leaveType.concept.ts +0 -13
- package/src/sample/domains/leaves/concepts/leaveTypes.concept.ts +0 -205
- package/src/sample/domains/leaves/endpoints/getBalance.endpoint.ts +0 -53
- package/src/sample/domains/leaves/endpoints/getLeaveById.endpoint.ts +0 -53
- package/src/sample/domains/leaves/endpoints/listBalanceTransactions.endpoint.ts +0 -196
- package/src/sample/domains/leaves/endpoints/listBalanceTypes.endpoint.ts +0 -26
- package/src/sample/domains/leaves/endpoints/listBalances.endpoint.ts +0 -201
- package/src/sample/domains/leaves/endpoints/listLeaveTypes.endpoint.ts +0 -28
- package/src/sample/domains/leaves/endpoints/listLeaves.endpoint.ts +0 -234
- package/src/sample/domains/leaves/index.ts +0 -38
- package/src/sample/domains/leaves/services/leaveRequests.service.ts +0 -232
- package/src/sample/domains/notifications/concepts/notification.concept.ts +0 -19
- package/src/sample/domains/notifications/endpoints/countMyNotifications.endpoint.ts +0 -21
- package/src/sample/domains/notifications/endpoints/getMyNotification.endpoint.ts +0 -57
- package/src/sample/domains/notifications/endpoints/listMyNotifications.endpoint.ts +0 -59
- package/src/sample/domains/notifications/endpoints/markMyNotificationAsRead.endpoint.ts +0 -32
- package/src/sample/domains/notifications/index.ts +0 -20
- package/src/sample/domains/payroll/concepts/payslip.concept.ts +0 -18
- package/src/sample/domains/payroll/concepts/salaryCertificate.concept.ts +0 -17
- package/src/sample/domains/payroll/concepts/salaryCertificateRequestingEntity.concept.ts +0 -8
- package/src/sample/domains/payroll/endpoints/getPayslipById.endpoint.ts +0 -238
- package/src/sample/domains/payroll/endpoints/listPayslips.endpoint.ts +0 -216
- package/src/sample/domains/payroll/endpoints/listSalaryCertificateLanguages.endpoint.ts +0 -25
- package/src/sample/domains/payroll/endpoints/listSalaryCertificateRequestingEntities.endpoint.ts +0 -62
- package/src/sample/domains/payroll/endpoints/listSalaryCertificateTypes.endpoint.ts +0 -24
- package/src/sample/domains/payroll/endpoints/listSalaryCertificates.endpoint.ts +0 -122
- package/src/sample/domains/payroll/endpoints/previewSalaryCertificate.endpoint.ts +0 -45
- package/src/sample/domains/payroll/index.ts +0 -32
- package/src/sample/domains/payroll/rules/payrollEmployeeContext.rule.ts +0 -7
- package/src/sample/domains/payroll/services/salaryCertificates.service.ts +0 -15
- package/src/sample/domains/servicing/concepts/service.concept.ts +0 -6
- package/src/sample/domains/servicing/concepts/serviceRequest.concept.ts +0 -12
- package/src/sample/domains/servicing/concepts/serviceRequestApproval.concept.ts +0 -9
- package/src/sample/domains/servicing/endpoints/cancelServiceRequest.endpoint.ts +0 -21
- package/src/sample/domains/servicing/endpoints/createServiceRequest.endpoint.ts +0 -511
- package/src/sample/domains/servicing/endpoints/getServiceRequest.endpoint.ts +0 -352
- package/src/sample/domains/servicing/endpoints/getServiceRequestFormItems.endpoint.ts +0 -219
- package/src/sample/domains/servicing/endpoints/getServiceRequestTransactionFormItems.endpoint.ts +0 -216
- package/src/sample/domains/servicing/endpoints/getServiceStates.endpoint.ts +0 -47
- package/src/sample/domains/servicing/endpoints/invokeServiceRequestAction.endpoint.ts +0 -139
- package/src/sample/domains/servicing/endpoints/listServiceRequests.endpoint.ts +0 -292
- package/src/sample/domains/servicing/endpoints/listServices.endpoint.ts +0 -108
- package/src/sample/domains/servicing/endpoints/updateServiceRequest.endpoint.ts +0 -355
- package/src/sample/domains/servicing/index.ts +0 -44
- package/src/sample/domains/servicing/rules/checkValidActions.rule.ts +0 -7
- package/src/sample/domains/servicing/rules/createServiceRequest.rule.ts +0 -7
- package/src/sample/domains/servicing/rules/filterServiceRequestsByState.rule.ts +0 -7
- package/src/sample/domains/servicing/rules/passingOptionFields.rule.ts +0 -7
- package/src/sample/domains/shared/rules/pagination.rule.ts +0 -7
- package/src/sample/domains/suggestions/concepts/suggestion.concept.ts +0 -19
- package/src/sample/domains/suggestions/concepts/suggestionCategory.concept.ts +0 -7
- package/src/sample/domains/suggestions/concepts/suggestionImpactLevel.concept.ts +0 -7
- package/src/sample/domains/suggestions/endpoints/createSuggestion.endpoint.ts +0 -705
- package/src/sample/domains/suggestions/endpoints/deleteSuggestion.endpoint.ts +0 -32
- package/src/sample/domains/suggestions/endpoints/getSuggestionById.endpoint.ts +0 -635
- package/src/sample/domains/suggestions/endpoints/listSuggestionCategories.endpoint.ts +0 -62
- package/src/sample/domains/suggestions/endpoints/listSuggestionImpactLevels.endpoint.ts +0 -62
- package/src/sample/domains/suggestions/endpoints/listSuggestionStates.endpoint.ts +0 -24
- package/src/sample/domains/suggestions/endpoints/listSuggestions.endpoint.ts +0 -132
- package/src/sample/domains/suggestions/endpoints/updateSuggestion.endpoint.ts +0 -690
- package/src/sample/domains/suggestions/endpoints/updateSuggestionState.endpoint.ts +0 -39
- package/src/sample/domains/suggestions/index.ts +0 -34
- package/src/sample/domains/suggestions/rules/suggestionScope.rule.ts +0 -7
- package/src/sample/domains/surveys/concepts/survey.concept.ts +0 -10
- package/src/sample/domains/surveys/concepts/surveyResponse.concept.ts +0 -14
- package/src/sample/domains/surveys/endpoints/getSurveyResponseById.endpoint.ts +0 -131
- package/src/sample/domains/surveys/endpoints/getTargetedSurvey.endpoint.ts +0 -129
- package/src/sample/domains/surveys/endpoints/listTargetedSurveys.endpoint.ts +0 -160
- package/src/sample/domains/surveys/endpoints/respondToSurvey.endpoint.ts +0 -144
- package/src/sample/domains/surveys/index.ts +0 -21
- package/src/sample/drizzle.config.ts +0 -11
- package/src/sample/index.bak.ts +0 -341
- package/src/sample/index.ts +0 -318
- package/src/sample/official-statements/defaults.ts +0 -57
- package/src/sample/official-statements/routes.ts +0 -137
- package/src/sample/official-statements/service.ts +0 -148
- package/src/sample/official-statements/types.ts +0 -23
- /package/src/lib/db/migrations/{20260326231647_nice_speedball → mssql/20260326231647_nice_speedball}/migration.sql +0 -0
- /package/src/lib/db/migrations/{20260326231647_nice_speedball → mssql/20260326231647_nice_speedball}/snapshot.json +0 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import type { RedisConnections } from "@/redis";
|
|
2
|
+
import type { ActiveRun, RunCoordinator } from "./active-runs";
|
|
3
|
+
import { JOIN_FIRST_CHUNK_TIMEOUT_MS } from "./active-runs";
|
|
4
|
+
import { offsetBelongsToRun, redisStreamLog } from "./redis-stream-log";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The claim is the producer's liveness signal: a heartbeat refreshes its short
|
|
8
|
+
* TTL while the run is live, so a claim that expired means the producer died —
|
|
9
|
+
* that is how joiners on an orphaned run learn to stop tailing, and how a
|
|
10
|
+
* crashed instance's threads free up within a minute instead of fifteen. The
|
|
11
|
+
* stream TTL governs how long a run's chunk log stays replayable.
|
|
12
|
+
*/
|
|
13
|
+
const CLAIM_TTL_SECONDS = 60;
|
|
14
|
+
const CLAIM_HEARTBEAT_MS = 20_000;
|
|
15
|
+
const DEFAULT_STREAM_TTL_SECONDS = 24 * 60 * 60;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Cross-instance abort: the producer's AbortController lives in-process on
|
|
19
|
+
* whichever instance runs the turn, but a stop may land on any instance — so
|
|
20
|
+
* abort publishes the run id and every instance listens, aborting the run if
|
|
21
|
+
* it owns the controller. Single-instance works too: the publisher's own
|
|
22
|
+
* subscriber receives the message.
|
|
23
|
+
*/
|
|
24
|
+
const ABORT_CHANNEL = "cortex:chat:abort";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The claim doubles as mutex and discovery pointer: a joiner that only knows
|
|
28
|
+
* the thread id finds the live run here.
|
|
29
|
+
*/
|
|
30
|
+
function claimKey(threadId: string) {
|
|
31
|
+
return `cortex:chat:active:${threadId}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Atomically take the claim and learn who held it. Two concurrent starts on
|
|
36
|
+
* one thread each see the other exactly once, so the loser is always aborted —
|
|
37
|
+
* a GET-then-SET pair could interleave and leave both producers running.
|
|
38
|
+
*/
|
|
39
|
+
const CLAIM_SWAP = `local previous = redis.call("get", KEYS[1])
|
|
40
|
+
redis.call("set", KEYS[1], ARGV[1], "EX", ARGV[2])
|
|
41
|
+
return previous`;
|
|
42
|
+
|
|
43
|
+
/** Release only if still held by this run — a superseded run finishing late
|
|
44
|
+
* must not evict its replacement. GET-then-DEL would race; the script cannot. */
|
|
45
|
+
const RELEASE_IF_HELD = `if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end`;
|
|
46
|
+
|
|
47
|
+
/** The heartbeat must never resurrect a claim that abort or supersede released. */
|
|
48
|
+
const REFRESH_IF_HELD = `if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("expire", KEYS[1], ARGV[2]) else return 0 end`;
|
|
49
|
+
|
|
50
|
+
type RedisRunOptions = {
|
|
51
|
+
streamTtlSeconds?: number;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export function createRedisRuns(connections: RedisConnections, options: RedisRunOptions = {}) {
|
|
55
|
+
const { publisher, subscriber } = connections;
|
|
56
|
+
const streamTtlMs = (options.streamTtlSeconds ?? DEFAULT_STREAM_TTL_SECONDS) * 1000;
|
|
57
|
+
const logOptions = { streamTtlMs, firstChunkDeadlineMs: JOIN_FIRST_CHUNK_TIMEOUT_MS };
|
|
58
|
+
const aborters = new Map<string, AbortController>();
|
|
59
|
+
const heartbeats = new Map<string, ReturnType<typeof setInterval>>();
|
|
60
|
+
|
|
61
|
+
subscriber.on("message", function (channel: string, runId: string) {
|
|
62
|
+
if (channel === ABORT_CHANNEL) aborters.get(runId)?.abort();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Lazy and retried, never memoized-rejected: caching one boot-time
|
|
67
|
+
* subscribe promise would leave every future startRun rethrowing a single
|
|
68
|
+
* transient outage forever. Once it resolves, ioredis re-subscribes by
|
|
69
|
+
* itself across reconnects.
|
|
70
|
+
*/
|
|
71
|
+
let abortSubscription: Promise<unknown> | undefined;
|
|
72
|
+
function ensureAbortSubscription() {
|
|
73
|
+
abortSubscription ??= subscriber.subscribe(ABORT_CHANNEL).catch((error: unknown) => {
|
|
74
|
+
abortSubscription = undefined;
|
|
75
|
+
throw error;
|
|
76
|
+
});
|
|
77
|
+
return abortSubscription;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function abortRun(threadId: string) {
|
|
81
|
+
const runId = await publisher.get(claimKey(threadId));
|
|
82
|
+
if (!runId) return false;
|
|
83
|
+
// The local abort is immediate; the publish reaches the other instances.
|
|
84
|
+
aborters.get(runId)?.abort();
|
|
85
|
+
await publisher.publish(ABORT_CHANNEL, runId);
|
|
86
|
+
await publisher.eval(RELEASE_IF_HELD, 1, claimKey(threadId), runId);
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function startHeartbeat(threadId: string, runId: string) {
|
|
91
|
+
const timer = setInterval(function () {
|
|
92
|
+
void publisher
|
|
93
|
+
.eval(REFRESH_IF_HELD, 1, claimKey(threadId), runId, String(CLAIM_TTL_SECONDS))
|
|
94
|
+
.catch((error: unknown) =>
|
|
95
|
+
console.error("[cortex-server] redis claim heartbeat:", error),
|
|
96
|
+
);
|
|
97
|
+
}, CLAIM_HEARTBEAT_MS);
|
|
98
|
+
heartbeats.set(runId, timer);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function stopHeartbeat(runId: string) {
|
|
102
|
+
const timer = heartbeats.get(runId);
|
|
103
|
+
if (timer !== undefined) clearInterval(timer);
|
|
104
|
+
heartbeats.delete(runId);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
async startRun(threadId) {
|
|
109
|
+
// A producer must be able to hear aborts before it may exist, or a
|
|
110
|
+
// stop from another instance could slip by while we boot.
|
|
111
|
+
await ensureAbortSubscription();
|
|
112
|
+
const run: ActiveRun = {
|
|
113
|
+
runId: crypto.randomUUID(),
|
|
114
|
+
abortController: new AbortController(),
|
|
115
|
+
};
|
|
116
|
+
// Registered before the claim exists, so an abort cannot land in
|
|
117
|
+
// between; unregistered again if the claim never materializes.
|
|
118
|
+
aborters.set(run.runId, run.abortController);
|
|
119
|
+
try {
|
|
120
|
+
const superseded = (await publisher.eval(
|
|
121
|
+
CLAIM_SWAP,
|
|
122
|
+
1,
|
|
123
|
+
claimKey(threadId),
|
|
124
|
+
run.runId,
|
|
125
|
+
String(CLAIM_TTL_SECONDS),
|
|
126
|
+
)) as string | null;
|
|
127
|
+
if (superseded !== null) {
|
|
128
|
+
aborters.get(superseded)?.abort();
|
|
129
|
+
await publisher.publish(ABORT_CHANNEL, superseded);
|
|
130
|
+
}
|
|
131
|
+
} catch (error) {
|
|
132
|
+
aborters.delete(run.runId);
|
|
133
|
+
throw error;
|
|
134
|
+
}
|
|
135
|
+
startHeartbeat(threadId, run.runId);
|
|
136
|
+
return run;
|
|
137
|
+
},
|
|
138
|
+
async getRunId(threadId) {
|
|
139
|
+
return (await publisher.get(claimKey(threadId))) ?? undefined;
|
|
140
|
+
},
|
|
141
|
+
async runningThreadIds(threadIds) {
|
|
142
|
+
if (threadIds.length === 0) return new Set<string>();
|
|
143
|
+
const claims = await publisher.mget(threadIds.map(claimKey));
|
|
144
|
+
return new Set(threadIds.filter((_, index) => claims[index] !== null));
|
|
145
|
+
},
|
|
146
|
+
abortRun,
|
|
147
|
+
async endRun(threadId, runId) {
|
|
148
|
+
stopHeartbeat(runId);
|
|
149
|
+
aborters.delete(runId);
|
|
150
|
+
await publisher.eval(RELEASE_IF_HELD, 1, claimKey(threadId), runId);
|
|
151
|
+
},
|
|
152
|
+
producerLog(runId) {
|
|
153
|
+
return redisStreamLog(publisher, runId, { resumeOffset: null, ...logOptions });
|
|
154
|
+
},
|
|
155
|
+
joinLog(threadId, runId, offset = "-1") {
|
|
156
|
+
return redisStreamLog(publisher, runId, {
|
|
157
|
+
resumeOffset: offset,
|
|
158
|
+
// The claim is the producer's pulse: while this run holds it the
|
|
159
|
+
// reader may park between chunks indefinitely, and once it is
|
|
160
|
+
// gone with the log still unclosed, the producer died.
|
|
161
|
+
isLive: async () => (await publisher.get(claimKey(threadId))) === runId,
|
|
162
|
+
...logOptions,
|
|
163
|
+
});
|
|
164
|
+
},
|
|
165
|
+
offsetBelongsToRun,
|
|
166
|
+
} satisfies RunCoordinator;
|
|
167
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import type { Redis } from "ioredis";
|
|
2
|
+
import type { StreamChunk, StreamDurability } from "@tanstack/ai";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A `StreamDurability` adapter over a Redis Stream, one stream per run. The
|
|
6
|
+
* entry id is naturally ordered, so — wrapped in a run-scoped prefix — it is
|
|
7
|
+
* the resumable offset. Redis holds the actual chunk log rather than acting as
|
|
8
|
+
* a pub/sub rendezvous, so resume survives a producer restart and works from
|
|
9
|
+
* any instance.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const OFFSET_PREFIX = "redis:v1:";
|
|
13
|
+
|
|
14
|
+
/** How long a blocked read waits before re-checking its deadline and abort signal. */
|
|
15
|
+
const READ_BLOCK_MS = 5_000;
|
|
16
|
+
|
|
17
|
+
function streamKey(runId: string) {
|
|
18
|
+
return `cortex:stream:${runId}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function runPrefix(runId: string) {
|
|
22
|
+
return `${OFFSET_PREFIX}${encodeURIComponent(runId)}:`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Whether a client-supplied resume offset belongs to `runId`. The offset names
|
|
27
|
+
* the log it replays, so an offset naming another run would replay that run's
|
|
28
|
+
* log to whoever presents it — matching the prefix this adapter encodes is what
|
|
29
|
+
* binds a resume to the thread the caller owns.
|
|
30
|
+
*/
|
|
31
|
+
export function offsetBelongsToRun(offset: string, runId: string) {
|
|
32
|
+
return offset.startsWith(runPrefix(runId));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function encodeOffset(runId: string, entryId: string) {
|
|
36
|
+
return `${runPrefix(runId)}${entryId}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function decodeOffset(offset: string, runId: string) {
|
|
40
|
+
const entryId = offset.slice(runPrefix(runId).length);
|
|
41
|
+
if (!offsetBelongsToRun(offset, runId) || !/^\d+-\d+$/.test(entryId)) {
|
|
42
|
+
throw new Error(`Invalid redis stream offset for run ${JSON.stringify(runId)}: ${offset}`);
|
|
43
|
+
}
|
|
44
|
+
return entryId;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** An entry is either `chunk <json>` or the `end` marker `close()` writes. */
|
|
48
|
+
function fieldValue(fields: string[], name: string) {
|
|
49
|
+
for (let index = 0; index < fields.length; index += 2) {
|
|
50
|
+
if (fields[index] === name) return fields[index + 1];
|
|
51
|
+
}
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Where a read starts: from the beginning, or the entry id a concrete offset
|
|
57
|
+
* names. The contract's "now" sentinel never reaches this adapter — cortex's
|
|
58
|
+
* join paths always pass "-1" or a validated concrete offset — so it falls
|
|
59
|
+
* through to decodeOffset's rejection instead of carrying dead code.
|
|
60
|
+
*/
|
|
61
|
+
function resolveStartId(runId: string, offset: string) {
|
|
62
|
+
return offset === "-1" ? "0" : decodeOffset(offset, runId);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const READ_ABORTED = Symbol("read aborted");
|
|
66
|
+
|
|
67
|
+
/** What an idle (null) reply means: keep parking, or the run will never end. */
|
|
68
|
+
function idlePolicy(runId: string, options: RedisStreamLogOptions) {
|
|
69
|
+
const deadline = Date.now() + options.firstChunkDeadlineMs;
|
|
70
|
+
let producerGoneTicks = 0;
|
|
71
|
+
return {
|
|
72
|
+
sawData() {
|
|
73
|
+
producerGoneTicks = 0;
|
|
74
|
+
},
|
|
75
|
+
async onIdle(logHasData: boolean) {
|
|
76
|
+
if (options.isLive && !(await options.isLive())) {
|
|
77
|
+
// One more blocking read before failing: an abort releases the
|
|
78
|
+
// claim moments before the terminal marker lands, and that
|
|
79
|
+
// close must win the race.
|
|
80
|
+
producerGoneTicks += 1;
|
|
81
|
+
if (producerGoneTicks > 1) {
|
|
82
|
+
throw new Error(
|
|
83
|
+
`Redis stream run was abandoned by its producer: ${JSON.stringify(runId)}`,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (!logHasData && Date.now() >= deadline) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
`Redis stream run produced no data within ${options.firstChunkDeadlineMs}ms: ${JSON.stringify(runId)}`,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function blockingRead(reader: Redis, key: string, lastId: string, signal?: AbortSignal) {
|
|
98
|
+
try {
|
|
99
|
+
return await reader.xread("BLOCK", READ_BLOCK_MS, "STREAMS", key, lastId);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
// disconnect() rejects the in-flight read; that is the abort path.
|
|
102
|
+
if (signal?.aborted) return READ_ABORTED;
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function parseBatch(runId: string, entries: [id: string, fields: string[]][], lastId: string) {
|
|
108
|
+
const items: { offset: string; chunk: StreamChunk }[] = [];
|
|
109
|
+
let closed = false;
|
|
110
|
+
for (const [entryId, fields] of entries) {
|
|
111
|
+
lastId = entryId;
|
|
112
|
+
if (fieldValue(fields, "end") !== undefined) {
|
|
113
|
+
closed = true;
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
const chunk = fieldValue(fields, "chunk");
|
|
117
|
+
if (chunk !== undefined) {
|
|
118
|
+
items.push({
|
|
119
|
+
offset: encodeOffset(runId, entryId),
|
|
120
|
+
chunk: JSON.parse(chunk) as StreamChunk,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return { items, closed, lastId };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
type RedisStreamLogOptions = {
|
|
128
|
+
/** `null` builds a producer (appends flow into the log); an offset builds a reader. */
|
|
129
|
+
resumeOffset: string | null;
|
|
130
|
+
/** How long the log stays replayable after its last append or `close()`. */
|
|
131
|
+
streamTtlMs: number;
|
|
132
|
+
/** How long a reader waits for the run's first entry before failing. */
|
|
133
|
+
firstChunkDeadlineMs: number;
|
|
134
|
+
/**
|
|
135
|
+
* Whether the run's producer is still alive. While it is, a caught-up
|
|
136
|
+
* reader parks indefinitely between chunks — the producer owns termination.
|
|
137
|
+
* Once it reports dead with the log still unclosed, the run was orphaned
|
|
138
|
+
* (producer crashed before `close()`), and the read fails so the joiner
|
|
139
|
+
* gets a terminal error instead of tailing forever.
|
|
140
|
+
*/
|
|
141
|
+
isLive?: () => Promise<boolean>;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
export function redisStreamLog(redis: Redis, runId: string, options: RedisStreamLogOptions) {
|
|
145
|
+
const key = streamKey(runId);
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
resumeFrom: () => options.resumeOffset,
|
|
149
|
+
|
|
150
|
+
append: async (chunks) => {
|
|
151
|
+
if (chunks.length === 0) return [];
|
|
152
|
+
const pipeline = redis.pipeline();
|
|
153
|
+
for (const chunk of chunks) pipeline.xadd(key, "*", "chunk", JSON.stringify(chunk));
|
|
154
|
+
// Refreshed on every append so a crashed producer's log — which
|
|
155
|
+
// never reaches close() — still expires instead of leaking forever.
|
|
156
|
+
pipeline.pexpire(key, options.streamTtlMs);
|
|
157
|
+
const results = (await pipeline.exec()) ?? [];
|
|
158
|
+
return results.slice(0, chunks.length).map(function ([error, entryId]) {
|
|
159
|
+
if (error) throw error;
|
|
160
|
+
return encodeOffset(runId, entryId as string);
|
|
161
|
+
});
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
snapshot: async () => {
|
|
165
|
+
// XRANGE on a missing key returns [], satisfying the contract's
|
|
166
|
+
// empty-run case without the existence probe read() needs.
|
|
167
|
+
const entries = await redis.xrange(key, "-", "+");
|
|
168
|
+
return parseBatch(runId, entries, "0").items;
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
close: async () => {
|
|
172
|
+
// The marker is what ends a tailing read — a terminal chunk cannot,
|
|
173
|
+
// since the agent loop emits one per iteration. The TTL then bounds
|
|
174
|
+
// how long the finished log lingers for late replays.
|
|
175
|
+
await redis.xadd(key, "*", "end", "1");
|
|
176
|
+
await redis.pexpire(key, options.streamTtlMs);
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
read: async function* (offset: string, signal?: AbortSignal) {
|
|
180
|
+
let lastId = resolveStartId(runId, offset);
|
|
181
|
+
// Armed while the log is empty, matching memoryStream: once a run
|
|
182
|
+
// has produced anything the producer owns termination, and a
|
|
183
|
+
// caught-up reader may legitimately park between chunks — a slow
|
|
184
|
+
// tool call must not read as "no data".
|
|
185
|
+
let logHasData = (await redis.exists(key)) === 1;
|
|
186
|
+
if (offset !== "-1" && !logHasData) {
|
|
187
|
+
// A concrete offset names entries that existed, so a missing
|
|
188
|
+
// key means the log expired — fail rather than park forever.
|
|
189
|
+
throw new Error(`Unknown or expired redis stream run: ${JSON.stringify(runId)}`);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// A blocked XREAD stalls every command queued behind it, so each
|
|
193
|
+
// reader tails on a connection of its own for as long as it lives.
|
|
194
|
+
const reader = redis.duplicate();
|
|
195
|
+
const onAbort = () => reader.disconnect();
|
|
196
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
197
|
+
const idle = idlePolicy(runId, options);
|
|
198
|
+
try {
|
|
199
|
+
while (!signal?.aborted) {
|
|
200
|
+
const reply = await blockingRead(reader, key, lastId, signal);
|
|
201
|
+
if (reply === READ_ABORTED) return;
|
|
202
|
+
if (reply === null) {
|
|
203
|
+
await idle.onIdle(logHasData);
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
idle.sawData();
|
|
207
|
+
logHasData = true;
|
|
208
|
+
const batch = parseBatch(runId, reply[0]?.[1] ?? [], lastId);
|
|
209
|
+
lastId = batch.lastId;
|
|
210
|
+
for (const item of batch.items) yield item;
|
|
211
|
+
if (batch.closed) return;
|
|
212
|
+
}
|
|
213
|
+
} finally {
|
|
214
|
+
signal?.removeEventListener("abort", onAbort);
|
|
215
|
+
reader.disconnect();
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
} satisfies StreamDurability;
|
|
219
|
+
}
|