@agilesyndrome/cf-genai-base 5.0.2 → 6.0.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/CHANGELOG.md +99 -0
- package/CLI.md +9 -13
- package/CONTRACT.md +32 -15
- package/README.md +95 -28
- package/bin/cf-genai.js +24 -8
- package/dist/admin/auth.d.ts +12 -0
- package/dist/admin/auth.d.ts.map +1 -0
- package/dist/admin/auth.js +103 -0
- package/dist/admin/auth.js.map +1 -0
- package/dist/admin/index.d.ts +2 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +2 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/router.d.ts +4 -0
- package/dist/admin/router.d.ts.map +1 -0
- package/dist/admin/router.js +24 -0
- package/dist/admin/router.js.map +1 -0
- package/dist/admin/types.d.ts +43 -0
- package/dist/admin/types.d.ts.map +1 -0
- package/dist/admin/types.js +2 -0
- package/dist/admin/types.js.map +1 -0
- package/dist/api/client.d.ts +34 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/client.js +59 -0
- package/dist/api/client.js.map +1 -0
- package/dist/api/contracts.d.ts +31 -0
- package/dist/api/contracts.d.ts.map +1 -0
- package/dist/api/contracts.js +101 -0
- package/dist/api/contracts.js.map +1 -0
- package/dist/api/http.d.ts +12 -0
- package/dist/api/http.d.ts.map +1 -0
- package/dist/api/http.js +36 -0
- package/dist/api/http.js.map +1 -0
- package/dist/api/index.d.ts +14 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +11 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/testing.d.ts +10 -0
- package/dist/api/testing.d.ts.map +1 -0
- package/dist/api/testing.js +26 -0
- package/dist/api/testing.js.map +1 -0
- package/dist/app.d.ts +21 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +23 -0
- package/dist/app.js.map +1 -0
- package/dist/auth/constants.d.ts +9 -0
- package/dist/auth/constants.d.ts.map +1 -0
- package/{src → dist}/auth/constants.js +2 -2
- package/dist/auth/constants.js.map +1 -0
- package/dist/auth/domains.d.ts +3 -0
- package/dist/auth/domains.d.ts.map +1 -0
- package/dist/auth/domains.js +8 -0
- package/dist/auth/domains.js.map +1 -0
- package/dist/auth/encoding.d.ts +7 -0
- package/dist/auth/encoding.d.ts.map +1 -0
- package/dist/auth/encoding.js +41 -0
- package/dist/auth/encoding.js.map +1 -0
- package/dist/auth/groups/d1.d.ts +16 -0
- package/dist/auth/groups/d1.d.ts.map +1 -0
- package/dist/auth/groups/d1.js +66 -0
- package/dist/auth/groups/d1.js.map +1 -0
- package/dist/auth/groups/domain.d.ts +8 -0
- package/dist/auth/groups/domain.d.ts.map +1 -0
- package/dist/auth/groups/domain.js +99 -0
- package/dist/auth/groups/domain.js.map +1 -0
- package/dist/auth/groups/index.d.ts +7 -0
- package/dist/auth/groups/index.d.ts.map +1 -0
- package/dist/auth/groups/index.js +7 -0
- package/dist/auth/groups/index.js.map +1 -0
- package/dist/auth/groups/model.d.ts +38 -0
- package/dist/auth/groups/model.d.ts.map +1 -0
- package/dist/auth/groups/model.js +2 -0
- package/dist/auth/groups/model.js.map +1 -0
- package/dist/auth/groups/policy.d.ts +5 -0
- package/dist/auth/groups/policy.d.ts.map +1 -0
- package/dist/auth/groups/policy.js +8 -0
- package/dist/auth/groups/policy.js.map +1 -0
- package/dist/auth/groups/service.d.ts +20 -0
- package/dist/auth/groups/service.d.ts.map +1 -0
- package/dist/auth/groups/service.js +40 -0
- package/dist/auth/groups/service.js.map +1 -0
- package/dist/auth/groups/validation.d.ts +5 -0
- package/dist/auth/groups/validation.d.ts.map +1 -0
- package/dist/auth/groups/validation.js +22 -0
- package/dist/auth/groups/validation.js.map +1 -0
- package/dist/auth/groups/views/list.d.ts +10 -0
- package/dist/auth/groups/views/list.d.ts.map +1 -0
- package/dist/auth/groups/views/list.js +4 -0
- package/dist/auth/groups/views/list.js.map +1 -0
- package/dist/auth/identity/index.d.ts +58 -0
- package/dist/auth/identity/index.d.ts.map +1 -0
- package/dist/auth/identity/index.js +58 -0
- package/dist/auth/identity/index.js.map +1 -0
- package/dist/auth/impersonation/constants.d.ts +2 -0
- package/dist/auth/impersonation/constants.d.ts.map +1 -0
- package/dist/auth/impersonation/constants.js +2 -0
- package/dist/auth/impersonation/constants.js.map +1 -0
- package/dist/auth/impersonation/domain.d.ts +6 -0
- package/dist/auth/impersonation/domain.d.ts.map +1 -0
- package/dist/auth/impersonation/domain.js +16 -0
- package/dist/auth/impersonation/domain.js.map +1 -0
- package/dist/auth/impersonation/index.d.ts +6 -0
- package/dist/auth/impersonation/index.d.ts.map +1 -0
- package/dist/auth/impersonation/index.js +6 -0
- package/dist/auth/impersonation/index.js.map +1 -0
- package/dist/auth/impersonation/model.d.ts +12 -0
- package/dist/auth/impersonation/model.d.ts.map +1 -0
- package/dist/auth/impersonation/model.js +2 -0
- package/dist/auth/impersonation/model.js.map +1 -0
- package/dist/auth/impersonation/service.d.ts +4 -0
- package/dist/auth/impersonation/service.d.ts.map +1 -0
- package/dist/auth/impersonation/service.js +32 -0
- package/dist/auth/impersonation/service.js.map +1 -0
- package/dist/auth/impersonation/validation.d.ts +4 -0
- package/dist/auth/impersonation/validation.d.ts.map +1 -0
- package/dist/auth/impersonation/validation.js +14 -0
- package/dist/auth/impersonation/validation.js.map +1 -0
- package/dist/auth/index.d.ts +13 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +13 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/oauth-single/constants.d.ts +4 -0
- package/dist/auth/oauth-single/constants.d.ts.map +1 -0
- package/dist/auth/oauth-single/constants.js +5 -0
- package/dist/auth/oauth-single/constants.js.map +1 -0
- package/dist/auth/oauth-single/cookies.d.ts +10 -0
- package/dist/auth/oauth-single/cookies.d.ts.map +1 -0
- package/dist/auth/oauth-single/cookies.js +59 -0
- package/dist/auth/oauth-single/cookies.js.map +1 -0
- package/dist/auth/oauth-single/domain.d.ts +14 -0
- package/dist/auth/oauth-single/domain.d.ts.map +1 -0
- package/dist/auth/oauth-single/domain.js +12 -0
- package/dist/auth/oauth-single/domain.js.map +1 -0
- package/dist/auth/oauth-single/index.d.ts +31 -0
- package/dist/auth/oauth-single/index.d.ts.map +1 -0
- package/dist/auth/oauth-single/index.js +215 -0
- package/dist/auth/oauth-single/index.js.map +1 -0
- package/dist/auth/oauth-single/model.d.ts +75 -0
- package/dist/auth/oauth-single/model.d.ts.map +1 -0
- package/dist/auth/oauth-single/model.js +2 -0
- package/dist/auth/oauth-single/model.js.map +1 -0
- package/dist/auth/oauth-single/protocol.d.ts +9 -0
- package/dist/auth/oauth-single/protocol.d.ts.map +1 -0
- package/dist/auth/oauth-single/protocol.js +190 -0
- package/dist/auth/oauth-single/protocol.js.map +1 -0
- package/dist/auth/oauth-single/session.d.ts +7 -0
- package/dist/auth/oauth-single/session.d.ts.map +1 -0
- package/dist/auth/oauth-single/session.js +78 -0
- package/dist/auth/oauth-single/session.js.map +1 -0
- package/dist/auth/repositories.d.ts +58 -0
- package/dist/auth/repositories.d.ts.map +1 -0
- package/dist/auth/repositories.js +31 -0
- package/dist/auth/repositories.js.map +1 -0
- package/dist/auth/scopes/d1.d.ts +17 -0
- package/dist/auth/scopes/d1.d.ts.map +1 -0
- package/dist/auth/scopes/d1.js +54 -0
- package/dist/auth/scopes/d1.js.map +1 -0
- package/dist/auth/scopes/domain.d.ts +7 -0
- package/dist/auth/scopes/domain.d.ts.map +1 -0
- package/dist/auth/scopes/domain.js +25 -0
- package/dist/auth/scopes/domain.js.map +1 -0
- package/dist/auth/scopes/index.d.ts +4 -0
- package/dist/auth/scopes/index.d.ts.map +1 -0
- package/dist/auth/scopes/index.js +4 -0
- package/dist/auth/scopes/index.js.map +1 -0
- package/dist/auth/scopes/model.d.ts +26 -0
- package/dist/auth/scopes/model.d.ts.map +1 -0
- package/dist/auth/scopes/model.js +2 -0
- package/dist/auth/scopes/model.js.map +1 -0
- package/dist/auth/scopes/policy.d.ts +3 -0
- package/dist/auth/scopes/policy.d.ts.map +1 -0
- package/dist/auth/scopes/policy.js +4 -0
- package/dist/auth/scopes/policy.js.map +1 -0
- package/dist/auth/scopes/service.d.ts +4 -0
- package/dist/auth/scopes/service.d.ts.map +1 -0
- package/dist/auth/scopes/service.js +4 -0
- package/dist/auth/scopes/service.js.map +1 -0
- package/dist/auth/scopes/validation.d.ts +6 -0
- package/dist/auth/scopes/validation.d.ts.map +1 -0
- package/dist/auth/scopes/validation.js +33 -0
- package/dist/auth/scopes/validation.js.map +1 -0
- package/dist/auth/scopes/views/list.d.ts +9 -0
- package/dist/auth/scopes/views/list.d.ts.map +1 -0
- package/dist/auth/scopes/views/list.js +4 -0
- package/dist/auth/scopes/views/list.js.map +1 -0
- package/dist/auth/subscriptions/d1.d.ts +10 -0
- package/dist/auth/subscriptions/d1.d.ts.map +1 -0
- package/dist/auth/subscriptions/d1.js +86 -0
- package/dist/auth/subscriptions/d1.js.map +1 -0
- package/dist/auth/subscriptions/domain.d.ts +10 -0
- package/dist/auth/subscriptions/domain.d.ts.map +1 -0
- package/dist/auth/subscriptions/domain.js +33 -0
- package/dist/auth/subscriptions/domain.js.map +1 -0
- package/dist/auth/subscriptions/index.d.ts +6 -0
- package/dist/auth/subscriptions/index.d.ts.map +1 -0
- package/dist/auth/subscriptions/index.js +6 -0
- package/dist/auth/subscriptions/index.js.map +1 -0
- package/dist/auth/subscriptions/model.d.ts +20 -0
- package/dist/auth/subscriptions/model.d.ts.map +1 -0
- package/dist/auth/subscriptions/model.js +2 -0
- package/dist/auth/subscriptions/model.js.map +1 -0
- package/dist/auth/subscriptions/service.d.ts +12 -0
- package/dist/auth/subscriptions/service.d.ts.map +1 -0
- package/dist/auth/subscriptions/service.js +71 -0
- package/dist/auth/subscriptions/service.js.map +1 -0
- package/dist/auth/subscriptions/validation.d.ts +3 -0
- package/dist/auth/subscriptions/validation.d.ts.map +1 -0
- package/dist/auth/subscriptions/validation.js +21 -0
- package/dist/auth/subscriptions/validation.js.map +1 -0
- package/dist/auth/tenants/d1.d.ts +12 -0
- package/dist/auth/tenants/d1.d.ts.map +1 -0
- package/dist/auth/tenants/d1.js +40 -0
- package/dist/auth/tenants/d1.js.map +1 -0
- package/dist/auth/tenants/delete.d.ts +3 -0
- package/dist/auth/tenants/delete.d.ts.map +1 -0
- package/dist/auth/tenants/delete.js +28 -0
- package/dist/auth/tenants/delete.js.map +1 -0
- package/dist/auth/tenants/domain.d.ts +8 -0
- package/dist/auth/tenants/domain.d.ts.map +1 -0
- package/dist/auth/tenants/domain.js +64 -0
- package/dist/auth/tenants/domain.js.map +1 -0
- package/dist/auth/tenants/index.d.ts +7 -0
- package/dist/auth/tenants/index.d.ts.map +1 -0
- package/dist/auth/tenants/index.js +7 -0
- package/dist/auth/tenants/index.js.map +1 -0
- package/dist/auth/tenants/model.d.ts +28 -0
- package/dist/auth/tenants/model.d.ts.map +1 -0
- package/dist/auth/tenants/model.js +2 -0
- package/dist/auth/tenants/model.js.map +1 -0
- package/dist/auth/tenants/policy.d.ts +3 -0
- package/dist/auth/tenants/policy.d.ts.map +1 -0
- package/dist/auth/tenants/policy.js +4 -0
- package/dist/auth/tenants/policy.js.map +1 -0
- package/dist/auth/tenants/service.d.ts +14 -0
- package/dist/auth/tenants/service.d.ts.map +1 -0
- package/dist/auth/tenants/service.js +37 -0
- package/dist/auth/tenants/service.js.map +1 -0
- package/dist/auth/tenants/validation.d.ts +5 -0
- package/dist/auth/tenants/validation.d.ts.map +1 -0
- package/dist/auth/tenants/validation.js +21 -0
- package/dist/auth/tenants/validation.js.map +1 -0
- package/dist/auth/tenants/views/list.d.ts +8 -0
- package/dist/auth/tenants/views/list.d.ts.map +1 -0
- package/dist/auth/tenants/views/list.js +3 -0
- package/dist/auth/tenants/views/list.js.map +1 -0
- package/dist/auth/users/access.d.ts +9 -0
- package/dist/auth/users/access.d.ts.map +1 -0
- package/dist/auth/users/access.js +53 -0
- package/dist/auth/users/access.js.map +1 -0
- package/dist/auth/users/d1.d.ts +19 -0
- package/dist/auth/users/d1.d.ts.map +1 -0
- package/dist/auth/users/d1.js +68 -0
- package/dist/auth/users/d1.js.map +1 -0
- package/dist/auth/users/domain.d.ts +13 -0
- package/dist/auth/users/domain.d.ts.map +1 -0
- package/dist/auth/users/domain.js +129 -0
- package/dist/auth/users/domain.js.map +1 -0
- package/dist/auth/users/index.d.ts +7 -0
- package/dist/auth/users/index.d.ts.map +1 -0
- package/dist/auth/users/index.js +7 -0
- package/dist/auth/users/index.js.map +1 -0
- package/dist/auth/users/model.d.ts +44 -0
- package/dist/auth/users/model.d.ts.map +1 -0
- package/dist/auth/users/model.js +2 -0
- package/dist/auth/users/model.js.map +1 -0
- package/dist/auth/users/service.d.ts +8 -0
- package/dist/auth/users/service.d.ts.map +1 -0
- package/dist/auth/users/service.js +62 -0
- package/dist/auth/users/service.js.map +1 -0
- package/dist/auth/users/validation.d.ts +4 -0
- package/dist/auth/users/validation.d.ts.map +1 -0
- package/dist/auth/users/validation.js +23 -0
- package/dist/auth/users/validation.js.map +1 -0
- package/dist/cli/cli.d.ts +9 -0
- package/dist/cli/cli.d.ts.map +1 -0
- package/dist/cli/cli.js +201 -0
- package/dist/cli/cli.js.map +1 -0
- package/dist/cli/d1.d.ts +24 -0
- package/dist/cli/d1.d.ts.map +1 -0
- package/dist/cli/d1.js +175 -0
- package/dist/cli/d1.js.map +1 -0
- package/dist/cli/operations.d.ts +3 -0
- package/dist/cli/operations.d.ts.map +1 -0
- package/dist/cli/operations.js +63 -0
- package/dist/cli/operations.js.map +1 -0
- package/dist/cli/project.d.ts +136 -0
- package/dist/cli/project.d.ts.map +1 -0
- package/dist/cli/project.js +738 -0
- package/dist/cli/project.js.map +1 -0
- package/dist/cli/status.d.ts +24 -0
- package/dist/cli/status.d.ts.map +1 -0
- package/dist/cli/status.js +55 -0
- package/dist/cli/status.js.map +1 -0
- package/dist/cli/tenants.d.ts +9 -0
- package/dist/cli/tenants.d.ts.map +1 -0
- package/dist/cli/tenants.js +32 -0
- package/dist/cli/tenants.js.map +1 -0
- package/dist/cli/types.d.ts +43 -0
- package/dist/cli/types.d.ts.map +1 -0
- package/dist/cli/types.js +2 -0
- package/dist/cli/types.js.map +1 -0
- package/dist/cli/users.d.ts +8 -0
- package/dist/cli/users.d.ts.map +1 -0
- package/dist/cli/users.js +74 -0
- package/dist/cli/users.js.map +1 -0
- package/dist/cli/version.d.ts +12 -0
- package/dist/cli/version.d.ts.map +1 -0
- package/dist/cli/version.js +60 -0
- package/dist/cli/version.js.map +1 -0
- package/dist/core/circuits/availability.d.ts +48 -0
- package/dist/core/circuits/availability.d.ts.map +1 -0
- package/dist/core/circuits/availability.js +87 -0
- package/dist/core/circuits/availability.js.map +1 -0
- package/dist/core/circuits/d1.d.ts +11 -0
- package/dist/core/circuits/d1.d.ts.map +1 -0
- package/dist/core/circuits/d1.js +83 -0
- package/dist/core/circuits/d1.js.map +1 -0
- package/dist/core/circuits/domain.d.ts +7 -0
- package/dist/core/circuits/domain.d.ts.map +1 -0
- package/dist/core/circuits/domain.js +36 -0
- package/dist/core/circuits/domain.js.map +1 -0
- package/dist/core/circuits/index.d.ts +6 -0
- package/dist/core/circuits/index.d.ts.map +1 -0
- package/dist/core/circuits/index.js +6 -0
- package/dist/core/circuits/index.js.map +1 -0
- package/dist/core/circuits/model.d.ts +50 -0
- package/dist/core/circuits/model.d.ts.map +1 -0
- package/dist/core/circuits/model.js +2 -0
- package/dist/core/circuits/model.js.map +1 -0
- package/dist/core/circuits/service.d.ts +9 -0
- package/dist/core/circuits/service.d.ts.map +1 -0
- package/dist/core/circuits/service.js +63 -0
- package/dist/core/circuits/service.js.map +1 -0
- package/dist/core/circuits/validation.d.ts +4 -0
- package/dist/core/circuits/validation.d.ts.map +1 -0
- package/dist/core/circuits/validation.js +35 -0
- package/dist/core/circuits/validation.js.map +1 -0
- package/dist/core/constants.d.ts +9 -0
- package/dist/core/constants.d.ts.map +1 -0
- package/{src → dist}/core/constants.js +2 -1
- package/dist/core/constants.js.map +1 -0
- package/dist/core/database/index.d.ts +14 -0
- package/dist/core/database/index.d.ts.map +1 -0
- package/dist/core/database/index.js +65 -0
- package/dist/core/database/index.js.map +1 -0
- package/dist/core/domains.d.ts +2 -0
- package/dist/core/domains.d.ts.map +1 -0
- package/dist/core/domains.js +7 -0
- package/dist/core/domains.js.map +1 -0
- package/dist/core/events/audit.d.ts +8 -0
- package/dist/core/events/audit.d.ts.map +1 -0
- package/dist/core/events/audit.js +27 -0
- package/dist/core/events/audit.js.map +1 -0
- package/dist/core/events/domain.d.ts +19 -0
- package/dist/core/events/domain.d.ts.map +1 -0
- package/dist/core/events/domain.js +39 -0
- package/dist/core/events/domain.js.map +1 -0
- package/dist/core/events/hub.d.ts +10 -0
- package/dist/core/events/hub.d.ts.map +1 -0
- package/dist/core/events/hub.js +56 -0
- package/dist/core/events/hub.js.map +1 -0
- package/dist/core/events/index.d.ts +57 -0
- package/dist/core/events/index.d.ts.map +1 -0
- package/dist/core/events/index.js +170 -0
- package/dist/core/events/index.js.map +1 -0
- package/dist/core/healthchecks/d1.d.ts +7 -0
- package/dist/core/healthchecks/d1.d.ts.map +1 -0
- package/dist/core/healthchecks/d1.js +43 -0
- package/dist/core/healthchecks/d1.js.map +1 -0
- package/dist/core/healthchecks/domain.d.ts +7 -0
- package/dist/core/healthchecks/domain.d.ts.map +1 -0
- package/dist/core/healthchecks/domain.js +51 -0
- package/dist/core/healthchecks/domain.js.map +1 -0
- package/dist/core/healthchecks/features.d.ts +19 -0
- package/dist/core/healthchecks/features.d.ts.map +1 -0
- package/dist/core/healthchecks/features.js +100 -0
- package/dist/core/healthchecks/features.js.map +1 -0
- package/dist/core/healthchecks/index.d.ts +6 -0
- package/dist/core/healthchecks/index.d.ts.map +1 -0
- package/dist/core/healthchecks/index.js +6 -0
- package/dist/core/healthchecks/index.js.map +1 -0
- package/dist/core/healthchecks/model.d.ts +64 -0
- package/dist/core/healthchecks/model.d.ts.map +1 -0
- package/dist/core/healthchecks/model.js +2 -0
- package/dist/core/healthchecks/model.js.map +1 -0
- package/dist/core/healthchecks/service.d.ts +8 -0
- package/dist/core/healthchecks/service.d.ts.map +1 -0
- package/dist/core/healthchecks/service.js +29 -0
- package/dist/core/healthchecks/service.js.map +1 -0
- package/dist/core/healthchecks/validation.d.ts +5 -0
- package/dist/core/healthchecks/validation.d.ts.map +1 -0
- package/dist/core/healthchecks/validation.js +51 -0
- package/dist/core/healthchecks/validation.js.map +1 -0
- package/dist/core/index.d.ts +9 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +9 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/jobs/d1.d.ts +19 -0
- package/dist/core/jobs/d1.d.ts.map +1 -0
- package/dist/core/jobs/d1.js +139 -0
- package/dist/core/jobs/d1.js.map +1 -0
- package/dist/core/jobs/domain.d.ts +8 -0
- package/dist/core/jobs/domain.d.ts.map +1 -0
- package/dist/core/jobs/domain.js +59 -0
- package/dist/core/jobs/domain.js.map +1 -0
- package/dist/core/jobs/events.d.ts +3 -0
- package/dist/core/jobs/events.d.ts.map +1 -0
- package/dist/core/jobs/events.js +25 -0
- package/dist/core/jobs/events.js.map +1 -0
- package/dist/core/jobs/index.d.ts +7 -0
- package/dist/core/jobs/index.d.ts.map +1 -0
- package/dist/core/jobs/index.js +7 -0
- package/dist/core/jobs/index.js.map +1 -0
- package/dist/core/jobs/model.d.ts +86 -0
- package/dist/core/jobs/model.d.ts.map +1 -0
- package/dist/core/jobs/model.js +2 -0
- package/dist/core/jobs/model.js.map +1 -0
- package/dist/core/jobs/service.d.ts +39 -0
- package/dist/core/jobs/service.d.ts.map +1 -0
- package/dist/core/jobs/service.js +84 -0
- package/dist/core/jobs/service.js.map +1 -0
- package/dist/core/jobs/validation.d.ts +8 -0
- package/dist/core/jobs/validation.d.ts.map +1 -0
- package/dist/core/jobs/validation.js +39 -0
- package/dist/core/jobs/validation.js.map +1 -0
- package/dist/core/security/index.d.ts +30 -0
- package/dist/core/security/index.d.ts.map +1 -0
- package/dist/core/security/index.js +138 -0
- package/dist/core/security/index.js.map +1 -0
- package/dist/data/context.d.ts +20 -0
- package/dist/data/context.d.ts.map +1 -0
- package/dist/data/context.js +65 -0
- package/dist/data/context.js.map +1 -0
- package/dist/data/index.d.ts +5 -0
- package/dist/data/index.d.ts.map +1 -0
- package/{src → dist}/data/index.js +2 -0
- package/dist/data/index.js.map +1 -0
- package/dist/data/json.d.ts +3 -0
- package/dist/data/json.d.ts.map +1 -0
- package/dist/data/json.js +14 -0
- package/dist/data/json.js.map +1 -0
- package/dist/data/model.d.ts +96 -0
- package/dist/data/model.d.ts.map +1 -0
- package/dist/data/model.js +3 -0
- package/dist/data/model.js.map +1 -0
- package/dist/data/policy.d.ts +14 -0
- package/dist/data/policy.d.ts.map +1 -0
- package/dist/data/policy.js +89 -0
- package/dist/data/policy.js.map +1 -0
- package/dist/data/reader.d.ts +9 -0
- package/dist/data/reader.d.ts.map +1 -0
- package/dist/data/reader.js +203 -0
- package/dist/data/reader.js.map +1 -0
- package/dist/data/resources.d.ts +9 -0
- package/dist/data/resources.d.ts.map +1 -0
- package/dist/data/resources.js +129 -0
- package/dist/data/resources.js.map +1 -0
- package/dist/domain/app-domain.d.ts +36 -0
- package/dist/domain/app-domain.d.ts.map +1 -0
- package/dist/domain/app-domain.js +79 -0
- package/dist/domain/app-domain.js.map +1 -0
- package/dist/domain/index.d.ts +3 -0
- package/dist/domain/index.d.ts.map +1 -0
- package/dist/domain/index.js +3 -0
- package/dist/domain/index.js.map +1 -0
- package/dist/domain/model.d.ts +37 -0
- package/dist/domain/model.d.ts.map +1 -0
- package/dist/domain/model.js +2 -0
- package/dist/domain/model.js.map +1 -0
- package/dist/features/index.d.ts +25 -0
- package/dist/features/index.d.ts.map +1 -0
- package/dist/features/index.js +61 -0
- package/dist/features/index.js.map +1 -0
- package/dist/features/llm/client.d.ts +3 -0
- package/dist/features/llm/client.d.ts.map +1 -0
- package/dist/features/llm/client.js +327 -0
- package/dist/features/llm/client.js.map +1 -0
- package/dist/features/llm/config.d.ts +7 -0
- package/dist/features/llm/config.d.ts.map +1 -0
- package/dist/features/llm/config.js +93 -0
- package/dist/features/llm/config.js.map +1 -0
- package/dist/features/llm/constants.d.ts +5 -0
- package/dist/features/llm/constants.d.ts.map +1 -0
- package/dist/features/llm/constants.js +6 -0
- package/dist/features/llm/constants.js.map +1 -0
- package/dist/features/llm/errors.d.ts +12 -0
- package/dist/features/llm/errors.d.ts.map +1 -0
- package/dist/features/llm/errors.js +19 -0
- package/dist/features/llm/errors.js.map +1 -0
- package/dist/features/llm/feature.d.ts +6 -0
- package/dist/features/llm/feature.d.ts.map +1 -0
- package/dist/features/llm/feature.js +37 -0
- package/dist/features/llm/feature.js.map +1 -0
- package/dist/features/llm/index.d.ts +9 -0
- package/dist/features/llm/index.d.ts.map +1 -0
- package/dist/features/llm/index.js +6 -0
- package/dist/features/llm/index.js.map +1 -0
- package/dist/features/llm/model.d.ts +171 -0
- package/dist/features/llm/model.d.ts.map +1 -0
- package/dist/features/llm/model.js +2 -0
- package/dist/features/llm/model.js.map +1 -0
- package/dist/features/llm/schema.d.ts +4 -0
- package/dist/features/llm/schema.d.ts.map +1 -0
- package/dist/features/llm/schema.js +82 -0
- package/dist/features/llm/schema.js.map +1 -0
- package/dist/features/llm/service.d.ts +29 -0
- package/dist/features/llm/service.d.ts.map +1 -0
- package/dist/features/llm/service.js +55 -0
- package/dist/features/llm/service.js.map +1 -0
- package/dist/features/messaging/constants.d.ts +3 -0
- package/dist/features/messaging/constants.d.ts.map +1 -0
- package/dist/features/messaging/constants.js +4 -0
- package/dist/features/messaging/constants.js.map +1 -0
- package/dist/features/messaging/domain.d.ts +9 -0
- package/dist/features/messaging/domain.d.ts.map +1 -0
- package/dist/features/messaging/domain.js +14 -0
- package/dist/features/messaging/domain.js.map +1 -0
- package/dist/features/messaging/feature.d.ts +7 -0
- package/dist/features/messaging/feature.d.ts.map +1 -0
- package/dist/features/messaging/feature.js +23 -0
- package/dist/features/messaging/feature.js.map +1 -0
- package/dist/features/messaging/index.d.ts +12 -0
- package/dist/features/messaging/index.d.ts.map +1 -0
- package/dist/features/messaging/index.js +8 -0
- package/dist/features/messaging/index.js.map +1 -0
- package/dist/features/messaging/jobs.d.ts +38 -0
- package/dist/features/messaging/jobs.d.ts.map +1 -0
- package/dist/features/messaging/jobs.js +40 -0
- package/dist/features/messaging/jobs.js.map +1 -0
- package/dist/features/messaging/models.d.ts +65 -0
- package/dist/features/messaging/models.d.ts.map +1 -0
- package/dist/features/messaging/models.js +97 -0
- package/dist/features/messaging/models.js.map +1 -0
- package/dist/features/messaging/resources.d.ts +8 -0
- package/dist/features/messaging/resources.d.ts.map +1 -0
- package/dist/features/messaging/resources.js +11 -0
- package/dist/features/messaging/resources.js.map +1 -0
- package/dist/features/messaging/store.d.ts +46 -0
- package/dist/features/messaging/store.d.ts.map +1 -0
- package/dist/features/messaging/store.js +208 -0
- package/dist/features/messaging/store.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/{src → dist}/index.js +7 -2
- package/dist/index.js.map +1 -0
- package/dist/input.d.ts +18 -0
- package/dist/input.d.ts.map +1 -0
- package/dist/input.js +49 -0
- package/dist/input.js.map +1 -0
- package/dist/repository/model.d.ts +61 -0
- package/dist/repository/model.d.ts.map +1 -0
- package/dist/repository/model.js +2 -0
- package/dist/repository/model.js.map +1 -0
- package/dist/repository.d.ts +11 -0
- package/dist/repository.d.ts.map +1 -0
- package/dist/repository.js +118 -0
- package/dist/repository.js.map +1 -0
- package/dist/runtime/domains.d.ts +14 -0
- package/dist/runtime/domains.d.ts.map +1 -0
- package/dist/runtime/domains.js +42 -0
- package/dist/runtime/domains.js.map +1 -0
- package/dist/runtime/features.d.ts +10 -0
- package/dist/runtime/features.d.ts.map +1 -0
- package/dist/runtime/features.js +37 -0
- package/dist/runtime/features.js.map +1 -0
- package/dist/runtime/health.d.ts +6 -0
- package/dist/runtime/health.d.ts.map +1 -0
- package/dist/runtime/health.js +28 -0
- package/dist/runtime/health.js.map +1 -0
- package/dist/runtime/hono.d.ts +30 -0
- package/dist/runtime/hono.d.ts.map +1 -0
- package/dist/runtime/hono.js +50 -0
- package/dist/runtime/hono.js.map +1 -0
- package/dist/runtime/model.d.ts +133 -0
- package/dist/runtime/model.d.ts.map +1 -0
- package/dist/runtime/model.js +2 -0
- package/dist/runtime/model.js.map +1 -0
- package/dist/runtime/responses.d.ts +9 -0
- package/dist/runtime/responses.d.ts.map +1 -0
- package/dist/runtime/responses.js +14 -0
- package/dist/runtime/responses.js.map +1 -0
- package/dist/runtime/worker.d.ts +10 -0
- package/dist/runtime/worker.d.ts.map +1 -0
- package/dist/runtime/worker.js +216 -0
- package/dist/runtime/worker.js.map +1 -0
- package/dist/ui/index.d.ts +3 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +3 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/react/admin-access.d.ts +2 -0
- package/dist/ui/react/admin-access.d.ts.map +1 -0
- package/dist/ui/react/admin-access.js +47 -0
- package/dist/ui/react/admin-access.js.map +1 -0
- package/dist/ui/react/admin-audit.d.ts +2 -0
- package/dist/ui/react/admin-audit.d.ts.map +1 -0
- package/dist/ui/react/admin-audit.js +13 -0
- package/dist/ui/react/admin-audit.js.map +1 -0
- package/dist/ui/react/admin-catalogs.d.ts +6 -0
- package/dist/ui/react/admin-catalogs.d.ts.map +1 -0
- package/dist/ui/react/admin-catalogs.js +37 -0
- package/dist/ui/react/admin-catalogs.js.map +1 -0
- package/dist/ui/react/admin-dashboard.d.ts +39 -0
- package/dist/ui/react/admin-dashboard.d.ts.map +1 -0
- package/dist/ui/react/admin-dashboard.js +135 -0
- package/dist/ui/react/admin-dashboard.js.map +1 -0
- package/dist/ui/react/admin-details.d.ts +27 -0
- package/dist/ui/react/admin-details.d.ts.map +1 -0
- package/dist/ui/react/admin-details.js +82 -0
- package/dist/ui/react/admin-details.js.map +1 -0
- package/dist/ui/react/admin-shell.d.ts +20 -0
- package/dist/ui/react/admin-shell.d.ts.map +1 -0
- package/dist/ui/react/admin-shell.js +24 -0
- package/dist/ui/react/admin-shell.js.map +1 -0
- package/dist/ui/react/bootstrap.d.ts +9 -0
- package/dist/ui/react/bootstrap.d.ts.map +1 -0
- package/dist/ui/react/bootstrap.js +45 -0
- package/dist/ui/react/bootstrap.js.map +1 -0
- package/dist/ui/react/foundation.d.ts +35 -0
- package/dist/ui/react/foundation.d.ts.map +1 -0
- package/dist/ui/react/foundation.js +62 -0
- package/dist/ui/react/foundation.js.map +1 -0
- package/dist/ui/react/index.d.ts +12 -0
- package/dist/ui/react/index.d.ts.map +1 -0
- package/dist/ui/react/index.js +12 -0
- package/dist/ui/react/index.js.map +1 -0
- package/dist/ui/react/jobs.d.ts +62 -0
- package/dist/ui/react/jobs.d.ts.map +1 -0
- package/dist/ui/react/jobs.js +114 -0
- package/dist/ui/react/jobs.js.map +1 -0
- package/dist/ui/react/live-events.d.ts +18 -0
- package/dist/ui/react/live-events.d.ts.map +1 -0
- package/dist/ui/react/live-events.js +94 -0
- package/dist/ui/react/live-events.js.map +1 -0
- package/dist/ui/react/types.d.ts +79 -0
- package/dist/ui/react/types.d.ts.map +1 -0
- package/dist/ui/react/types.js +32 -0
- package/dist/ui/react/types.js.map +1 -0
- package/dist/ui/ssr.d.ts +55 -0
- package/dist/ui/ssr.d.ts.map +1 -0
- package/dist/ui/ssr.js +30 -0
- package/dist/ui/ssr.js.map +1 -0
- package/{src → dist}/ui/styles.css +22 -1
- package/migrations/0007_messaging.sql +52 -0
- package/migrations/0008_messaging_tenant_integrity.sql +44 -0
- package/migrations/0009_healthcheck_measurements.sql +3 -0
- package/migrations/0010_admin_dashboard_refinements.sql +24 -0
- package/package.json +162 -21
- package/changelog.md +0 -25
- package/src/admin/index.js +0 -1
- package/src/admin/routes.js +0 -64
- package/src/api/client.js +0 -8
- package/src/api/contracts.js +0 -24
- package/src/api/index.js +0 -7
- package/src/api/jobs.js +0 -37
- package/src/api/testing.js +0 -13
- package/src/app.js +0 -5
- package/src/auth/encoding.js +0 -6
- package/src/auth/groups.js +0 -5
- package/src/auth/impersonation.js +0 -15
- package/src/auth/index.js +0 -8
- package/src/auth/oauth-single.js +0 -204
- package/src/auth/scopes.js +0 -32
- package/src/auth/subscriptions.js +0 -42
- package/src/auth/tenants.js +0 -34
- package/src/auth/users.js +0 -40
- package/src/cli/cli.js +0 -133
- package/src/cli/d1.js +0 -165
- package/src/cli/operations.js +0 -34
- package/src/cli/project.js +0 -560
- package/src/cli/status.js +0 -46
- package/src/cli/tenants.js +0 -16
- package/src/cli/users.js +0 -34
- package/src/cli/version.js +0 -54
- package/src/core/circuits.js +0 -134
- package/src/core/d1.js +0 -54
- package/src/core/event-hub.js +0 -29
- package/src/core/events.js +0 -63
- package/src/core/identity.js +0 -39
- package/src/core/index.js +0 -7
- package/src/core/jobs.js +0 -185
- package/src/core/security.js +0 -88
- package/src/data/context.js +0 -16
- package/src/data/reader.js +0 -63
- package/src/data/resources.js +0 -42
- package/src/repository.js +0 -63
- package/src/runtime/features.js +0 -17
- package/src/runtime/health.js +0 -17
- package/src/runtime/worker.js +0 -58
- package/src/ui/index.js +0 -1
- package/src/ui/react/admin-access.jsx +0 -29
- package/src/ui/react/admin-catalogs.jsx +0 -39
- package/src/ui/react/admin-shell.jsx +0 -17
- package/src/ui/react/foundation.jsx +0 -40
- package/src/ui/react/index.jsx +0 -6
- package/src/ui/react/jobs.jsx +0 -63
- package/src/ui/react/live-events.jsx +0 -55
package/src/core/security.js
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
export function sameOrigin(request, allowedOrigins = []) {
|
|
2
|
-
const origin = request?.headers?.get("Origin");
|
|
3
|
-
if (!origin) return false;
|
|
4
|
-
const requestOrigin = (() => { try { return new URL(request.url).origin; } catch { return ""; } })();
|
|
5
|
-
const allowed = new Set((Array.isArray(allowedOrigins) ? allowedOrigins : [allowedOrigins]).filter(Boolean).map(String));
|
|
6
|
-
if (!allowed.size && requestOrigin) allowed.add(requestOrigin);
|
|
7
|
-
const fetchSite = request.headers.get("Sec-Fetch-Site");
|
|
8
|
-
return allowed.has(origin) && (!fetchSite || fetchSite === "same-origin");
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export async function readJsonClone(request, maxBytes = 64 * 1024) {
|
|
12
|
-
if (!(request.headers.get("Content-Type") || "").toLowerCase().startsWith("application/json")) return { error: secureJson({ error: "JSON request required" }, 415) };
|
|
13
|
-
const declared = Number(request.headers.get("Content-Length"));
|
|
14
|
-
if (Number.isFinite(declared) && declared > maxBytes) return { error: secureJson({ error: "Request body is too large" }, 413) };
|
|
15
|
-
try {
|
|
16
|
-
const value = await readBoundedJson(request, maxBytes);
|
|
17
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Object required");
|
|
18
|
-
return { value };
|
|
19
|
-
} catch (error) {
|
|
20
|
-
if (error?.status === 413) return { error: secureJson({ error: "Request body is too large" }, 413) };
|
|
21
|
-
return { error: secureJson({ error: "Invalid JSON object request" }, 400) };
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export async function readJson(request, maxBytes = 64 * 1024) {
|
|
26
|
-
if (!(request.headers.get("Content-Type") || "").toLowerCase().startsWith("application/json")) throw Object.assign(new Error("JSON request required"), { status: 415 });
|
|
27
|
-
const declared = Number(request.headers.get("Content-Length"));
|
|
28
|
-
if (Number.isFinite(declared) && declared > maxBytes) throw Object.assign(new Error("Request body is too large"), { status: 413 });
|
|
29
|
-
if (!request.body) throw Object.assign(new Error("Request body required"), { status: 400 });
|
|
30
|
-
try {
|
|
31
|
-
const value = await readBoundedJson(request, maxBytes);
|
|
32
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Object required");
|
|
33
|
-
return value;
|
|
34
|
-
} catch (error) {
|
|
35
|
-
if (error?.status) throw error;
|
|
36
|
-
throw Object.assign(new Error("Invalid JSON object request"), { status: 400 });
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
async function readBoundedJson(request, maxBytes) {
|
|
41
|
-
const reader = request.clone().body?.getReader();
|
|
42
|
-
if (!reader) throw new Error("Request body required");
|
|
43
|
-
const chunks = [];
|
|
44
|
-
let total = 0;
|
|
45
|
-
while (true) {
|
|
46
|
-
const { done, value } = await reader.read();
|
|
47
|
-
if (done) break;
|
|
48
|
-
total += value.byteLength;
|
|
49
|
-
if (total > maxBytes) { void reader.cancel(); throw Object.assign(new Error("Request body is too large"), { status: 413 }); }
|
|
50
|
-
chunks.push(value);
|
|
51
|
-
}
|
|
52
|
-
const body = new Uint8Array(total);
|
|
53
|
-
let offset = 0;
|
|
54
|
-
for (const chunk of chunks) { body.set(chunk, offset); offset += chunk.byteLength; }
|
|
55
|
-
return JSON.parse(new TextDecoder().decode(body));
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function secureJson(payload, status = 200, headers = {}) {
|
|
59
|
-
return secureResponse(Response.json(payload, { status, headers: { "Cache-Control": "no-store", ...headers } }));
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function json(payload, status = 200, cache = "no-store") {
|
|
63
|
-
return secureJson(payload, status, { "Cache-Control": cache });
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function secureText(text, status = 200, cache = "no-store") {
|
|
67
|
-
return secureResponse(new Response(text, { status, headers: { "Content-Type": "text/plain; charset=utf-8", "Cache-Control": cache } }));
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export function secureResponse(response) {
|
|
71
|
-
const headers = new Headers(response.headers);
|
|
72
|
-
headers.set("Content-Security-Policy", "default-src 'self'; script-src 'self'; worker-src 'self' blob: data:; style-src 'self' 'unsafe-inline'; img-src 'self' https: data:; connect-src 'self'; font-src 'self'; object-src 'none'; base-uri 'none'; form-action 'self'; frame-ancestors 'none'; upgrade-insecure-requests");
|
|
73
|
-
headers.set("X-Content-Type-Options", "nosniff");
|
|
74
|
-
headers.set("X-Frame-Options", "DENY");
|
|
75
|
-
headers.set("Referrer-Policy", "strict-origin-when-cross-origin");
|
|
76
|
-
headers.set("Permissions-Policy", "camera=(), microphone=(), geolocation=(), payment=(), usb=()");
|
|
77
|
-
headers.set("Cross-Origin-Opener-Policy", "same-origin");
|
|
78
|
-
headers.set("Strict-Transport-Security", "max-age=31536000");
|
|
79
|
-
return new Response(response.body, { status: response.status, statusText: response.statusText, headers });
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export function requireUser(identity, response = secureJson({ error: "Authentication is required." }, 401)) {
|
|
83
|
-
return identity ? null : response;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export function requireAdmin(identity, response = secureJson({ error: "Administrator access is required." }, 403)) {
|
|
87
|
-
return identity?.is_admin || identity?.isAdmin ? null : response;
|
|
88
|
-
}
|
package/src/data/context.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { ensureUser, getAuthorizationUser, listUserGrants, listUserTenants, verifyImpersonationToken } from "../auth/index.js";
|
|
2
|
-
|
|
3
|
-
export async function requestDataContext(env, { state = {}, request, publicTenantId = null } = {}) {
|
|
4
|
-
let authUser = state.authUser || (state.user ? await ensureUser(env, state.user, { who: `user:${state.user.sub || "unknown"}` }) : null);
|
|
5
|
-
const system = Boolean(state.user?.auth_strategy === "http_basic" || (authUser && authUser.is_admin));
|
|
6
|
-
if (!authUser) return { userId: null, tenantId: publicTenantId, publicTenantId, public: Boolean(publicTenantId), system: false };
|
|
7
|
-
const impersonation = system ? await verifyImpersonationToken(request?.headers?.get("X-CF-GenAI-Impersonation") || readCookie(request, "__Host-cfgenai_impersonation"), env) : null;
|
|
8
|
-
if (impersonation) { const targetUser = await getAuthorizationUser(env, impersonation.targetUserId, { who: `user:${impersonation.adminUserId}` }); if (targetUser) authUser = targetUser; }
|
|
9
|
-
const who = `user:${authUser.id}`;
|
|
10
|
-
const [tenants, grants] = await Promise.all([listUserTenants(env, authUser.id, { who }), listUserGrants(env, authUser.id, { who })]);
|
|
11
|
-
const requestedTenant = state.tenantId || request?.headers?.get("X-Tenant-ID") || null;
|
|
12
|
-
const tenant = requestedTenant ? tenants.find((item) => item.id === requestedTenant) : tenants.length === 1 ? tenants[0] : null;
|
|
13
|
-
return { userId: authUser.id, tenantId: tenant?.id || null, publicTenantId, public: false, system, scopes: grants.map((grant) => grant.scope_name), tenants, invalidTenant: Boolean(requestedTenant && !tenant), impersonated: Boolean(impersonation), impersonatedBy: impersonation?.adminUserId || null };
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function readCookie(request, name) { const value = request?.headers?.get("Cookie") || ""; return value.split(";").map((part) => part.trim()).find((part) => part.startsWith(`${name}=`))?.slice(name.length + 1) || ""; }
|
package/src/data/reader.js
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { auditLog, createD1 } from "../core/index.js";
|
|
2
|
-
import { DataScopeError, normalizeDataResources } from "./resources.js";
|
|
3
|
-
|
|
4
|
-
export function createDataReader(env, { resources = [], context } = {}) {
|
|
5
|
-
const registry = new Map(normalizeDataResources(resources).map((resource) => [resource.name, resource]));
|
|
6
|
-
const getContext = typeof context === "function" ? context : async () => context || {};
|
|
7
|
-
const scope = (requestedScope) => ({
|
|
8
|
-
list: (name, options) => readList(name, requestedScope, options), page: (name, options) => readPage(name, requestedScope, options), count: (name, options) => readCount(name, requestedScope, options),
|
|
9
|
-
get: async (name, id) => (await readList(name, requestedScope, { where: { [registry.get(name)?.idColumn || "id"]: id }, limit: 1 }))[0] || null,
|
|
10
|
-
insert: (name, values) => writeInsert(name, requestedScope, values), update: (name, id, changes) => writeUpdate(name, requestedScope, id, changes), updateWhere: (name, where, changes) => writeUpdateWhere(name, requestedScope, where, changes), delete: (name, id) => writeDelete(name, requestedScope, id), deleteWhere: (name, where) => writeDeleteWhere(name, requestedScope, where),
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
async function readList(name, requestedScope, { where = {}, limit = 100, orderBy, offset = 0 } = {}) { return (await readPage(name, requestedScope, { where, limit, orderBy, offset })).rows; }
|
|
14
|
-
|
|
15
|
-
async function readPage(name, requestedScope, { where = {}, limit = 100, orderBy, cursor, offset = 0 } = {}) {
|
|
16
|
-
const resource = getResource(name); const actor = await getContext(); if (!isAllowed(resource, requestedScope, actor, "read")) return { rows: [], nextCursor: null };
|
|
17
|
-
const predicates = []; const bindings = []; addScopePredicate(resource, requestedScope, actor, predicates, bindings); addFilters(resource, where, predicates, bindings);
|
|
18
|
-
if (cursor !== undefined && cursor !== null) { predicates.push(`${quote(resource.idColumn)}>?`); bindings.push(cursor); }
|
|
19
|
-
const safeLimit = Math.min(Math.max(Number(limit) || 100, 1), 1000); const safeOffset = Math.max(Number(offset) || 0, 0);
|
|
20
|
-
let sql = `SELECT ${resource.readableColumns.map(quote).join(",")} FROM ${quote(resource.table)}${predicates.length ? ` WHERE ${predicates.join(" AND ")}` : ""} LIMIT ${safeLimit} OFFSET ${safeOffset}`;
|
|
21
|
-
if (orderBy) { const [column, direction = "ASC"] = String(orderBy).split(/\s+/, 2); if (!resource.orderableColumns.includes(column)) throw new TypeError(`Column ${column} cannot order ${resource.name}`); sql = sql.replace(` LIMIT ${safeLimit}`, ` ORDER BY ${quote(column)} ${direction.toUpperCase() === "DESC" ? "DESC" : "ASC"} LIMIT ${safeLimit}`); }
|
|
22
|
-
const result = await createD1(env, { who: actorLabel(actor) }).prepare(sql).bind(...bindings).all(); const rows = result.results || [];
|
|
23
|
-
return { rows, nextCursor: rows.length === safeLimit ? rows[rows.length - 1][resource.idColumn] : null };
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
async function readCount(name, requestedScope, { where = {} } = {}) {
|
|
27
|
-
const resource = getResource(name); const actor = await getContext(); if (!isAllowed(resource, requestedScope, actor, "read")) return 0;
|
|
28
|
-
const predicates = []; const bindings = []; addScopePredicate(resource, requestedScope, actor, predicates, bindings); addFilters(resource, where, predicates, bindings);
|
|
29
|
-
const result = await createD1(env, { who: actorLabel(actor) }).prepare(`SELECT COUNT(*) AS count FROM ${quote(resource.table)}${predicates.length ? ` WHERE ${predicates.join(" AND ")}` : ""}`).bind(...bindings).first(); return Number(result?.count || 0);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async function writeInsert(name, requestedScope, values = {}) {
|
|
33
|
-
const resource = getResource(name); const actor = await getContext(); assertWritable(resource, requestedScope, actor, "create"); const data = cleanWritableValues(resource, values, { includeId: true, includeOwnership: requestedScope === "system" }); assertColumnScopes(resource, actor, Object.keys(data)); addOwnedValue(resource, requestedScope, actor, data); const columns = Object.keys(data); if (!columns.length) throw new TypeError(`No writable values supplied for ${resource.name}`);
|
|
34
|
-
const result = await createD1(env, { who: actorLabel(actor) }).prepare(`INSERT INTO ${quote(resource.table)} (${columns.map(quote).join(",")}) VALUES (${columns.map(() => "?").join(",")})`).bind(...columns.map((column) => data[column])).run(); const insertedId = data[resource.idColumn] === undefined ? result?.meta?.last_row_id : data[resource.idColumn]; return insertedId === undefined ? data : (await readList(name, requestedScope, { where: { [resource.idColumn]: insertedId }, limit: 1 }))[0] || { ...data, [resource.idColumn]: insertedId };
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
async function writeUpdate(name, requestedScope, id, changes = {}) {
|
|
38
|
-
const resource = getResource(name); const actor = await getContext(); assertWritable(resource, requestedScope, actor, "update"); const data = cleanWritableValues(resource, changes, { includeOwnership: requestedScope === "system" }); assertColumnScopes(resource, actor, Object.keys(data)); const columns = Object.keys(data); if (!columns.length) throw new TypeError(`No writable values supplied for ${resource.name}`);
|
|
39
|
-
const predicates = [`${quote(resource.idColumn)}=?`]; const bindings = [id]; addScopePredicate(resource, requestedScope, actor, predicates, bindings); await createD1(env, { who: actorLabel(actor) }).prepare(`UPDATE ${quote(resource.table)} SET ${columns.map((column) => `${quote(column)}=?`).join(",")} WHERE ${predicates.join(" AND ")}`).bind(...columns.map((column) => data[column]), ...bindings).run(); return (await readList(name, requestedScope, { where: { [resource.idColumn]: id }, limit: 1 }))[0] || null;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async function writeDelete(name, requestedScope, id) { const resource = getResource(name); const actor = await getContext(); assertWritable(resource, requestedScope, actor, "delete"); const predicates = [`${quote(resource.idColumn)}=?`]; const bindings = [id]; addScopePredicate(resource, requestedScope, actor, predicates, bindings); return createD1(env, { who: actorLabel(actor) }).prepare(`DELETE FROM ${quote(resource.table)} WHERE ${predicates.join(" AND ")}`).bind(...bindings).run(); }
|
|
43
|
-
|
|
44
|
-
async function writeUpdateWhere(name, requestedScope, where, changes) { const resource = getResource(name); const actor = await getContext(); assertWritable(resource, requestedScope, actor, "update"); const data = cleanWritableValues(resource, changes, { includeOwnership: requestedScope === "system" }); assertColumnScopes(resource, actor, Object.keys(data)); const columns = Object.keys(data); if (!columns.length) throw new TypeError(`No writable values supplied for ${resource.name}`); const predicates = []; const bindings = []; addScopePredicate(resource, requestedScope, actor, predicates, bindings); addFilters(resource, where, predicates, bindings); await createD1(env, { who: actorLabel(actor) }).prepare(`UPDATE ${quote(resource.table)} SET ${columns.map((column) => `${quote(column)}=?`).join(",")} WHERE ${predicates.join(" AND ")}`).bind(...columns.map((column) => data[column]), ...bindings).run(); return readCount(name, requestedScope, { where }); }
|
|
45
|
-
|
|
46
|
-
async function writeDeleteWhere(name, requestedScope, where) { const resource = getResource(name); const actor = await getContext(); assertWritable(resource, requestedScope, actor, "delete"); const predicates = []; const bindings = []; addScopePredicate(resource, requestedScope, actor, predicates, bindings); addFilters(resource, where, predicates, bindings); return createD1(env, { who: actorLabel(actor) }).prepare(`DELETE FROM ${quote(resource.table)} WHERE ${predicates.join(" AND ")}`).bind(...bindings).run(); }
|
|
47
|
-
function getResource(name) { const resource = registry.get(String(name)); if (!resource) throw new TypeError(`Unknown data resource: ${name}`); return resource; }
|
|
48
|
-
return { user: scope("user"), tenant: scope("tenant"), public: scope("public"), system: scope("system"), context: getContext, resources: [...registry.values()] };
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function isAllowed(resource, requestedScope, actor, operation) {
|
|
52
|
-
const allowed = requestedScope === "system" ? Boolean(actor.system) : requestedScope === resource.scope && (requestedScope === "user" ? Boolean(actor.userId) : requestedScope === "public" ? Boolean(actor.publicTenantId && operation === "read") : Boolean(actor.tenantId && (actor.userId || (actor.public && resource.publicRead))));
|
|
53
|
-
if (!allowed || !resource.operations.includes(operation)) { auditLog({ who: actorLabel(actor), operation: "deny", resource: `data:${resource.name}:${requestedScope}:${operation}` }); return false; }
|
|
54
|
-
return true;
|
|
55
|
-
}
|
|
56
|
-
function assertWritable(resource, requestedScope, actor, operation) { if (!isAllowed(resource, requestedScope, actor, operation)) throw new DataScopeError(`Data scope ${requestedScope} cannot ${operation} resource ${resource.name}`); }
|
|
57
|
-
function assertColumnScopes(resource, actor, columns) { if (actor.system) return; for (const column of columns) { const required = resource.columnScopes[column] || []; if (required.some((scope) => !actor.scopes?.includes(scope))) throw new DataScopeError(`Missing scope to write ${resource.name}.${column}`); } }
|
|
58
|
-
function addScopePredicate(resource, requestedScope, actor, predicates, bindings) { if (requestedScope === "user") { predicates.push(`${quote(resource.ownerColumn)}=?`); bindings.push(actor.userId); } if (requestedScope === "public") { predicates.push(`${quote(resource.tenantColumn)}=?`); bindings.push(actor.publicTenantId); } if (requestedScope === "tenant") { predicates.push(`${quote(resource.tenantColumn)}=?`); bindings.push(actor.tenantId); if (actor.public && resource.publicRead && typeof resource.publicRead === "object") { predicates.push(`${quote(resource.publicRead.column)}=?`); bindings.push(resource.publicRead.value); } } }
|
|
59
|
-
function addFilters(resource, where, predicates, bindings) { for (const [column, value] of Object.entries(where || {})) { if (!resource.filterableColumns.includes(column)) throw new TypeError(`Column ${column} cannot filter ${resource.name}`); if (value === null) predicates.push(`${quote(column)} IS NULL`); else { predicates.push(`${quote(column)}=?`); bindings.push(value); } } }
|
|
60
|
-
function cleanWritableValues(resource, values, { includeId = false, includeOwnership = false } = {}) { return Object.fromEntries(Object.entries(values || {}).filter(([column]) => (resource.writableColumns.includes(column) || (includeOwnership && [resource.ownerColumn, resource.tenantColumn].includes(column))) && (includeId || column !== resource.idColumn))); }
|
|
61
|
-
function addOwnedValue(resource, requestedScope, actor, data) { if (requestedScope === "user") data[resource.ownerColumn] = actor.userId; if (requestedScope === "tenant") data[resource.tenantColumn] = actor.tenantId; }
|
|
62
|
-
function actorLabel(actor) { return actor.system ? "system:data" : `user:${actor.userId || "unknown"}`; }
|
|
63
|
-
function quote(identifier) { return `"${identifier}"`; }
|
package/src/data/resources.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
export const DATA_SCOPES = ["user", "tenant", "public", "system"];
|
|
2
|
-
export const DATA_OPERATIONS = ["read", "create", "update", "delete"];
|
|
3
|
-
|
|
4
|
-
export class DataScopeError extends Error {
|
|
5
|
-
constructor(message) { super(message); this.name = "DataScopeError"; }
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function normalizeDataResources(resources = []) {
|
|
9
|
-
const names = new Set();
|
|
10
|
-
return resources.map((resource) => {
|
|
11
|
-
if (!resource || !/^[a-z][a-z0-9_-]*$/.test(String(resource.name || ""))) throw new TypeError("Data resources require a safe name");
|
|
12
|
-
if (!/^[a-z][a-z0-9_]*$/.test(String(resource.table || ""))) throw new TypeError(`Data resource ${resource.name} requires a safe table name`);
|
|
13
|
-
const name = String(resource.name);
|
|
14
|
-
if (names.has(name)) throw new TypeError(`Duplicate data resource: ${name}`);
|
|
15
|
-
names.add(name);
|
|
16
|
-
const scope = String(resource.scope || "").toLowerCase();
|
|
17
|
-
if (!DATA_SCOPES.includes(scope)) throw new TypeError(`Data resource ${resource.name} requires scope user, tenant, public, or system`);
|
|
18
|
-
const columns = [...new Set((resource.columns || []).map(String))];
|
|
19
|
-
if (!columns.length || columns.some((column) => !/^[a-z][a-z0-9_]*$/.test(column))) throw new TypeError(`Data resource ${resource.name} requires safe columns`);
|
|
20
|
-
const idColumn = String(resource.idColumn || "id");
|
|
21
|
-
if (!columns.includes(idColumn)) throw new TypeError(`Data resource ${resource.name} must include ${idColumn}`);
|
|
22
|
-
const ownerColumn = resource.ownerColumn ? String(resource.ownerColumn) : "user_id";
|
|
23
|
-
const tenantColumn = resource.tenantColumn ? String(resource.tenantColumn) : "tenant_id";
|
|
24
|
-
if (scope === "user" && !columns.includes(ownerColumn)) throw new TypeError(`User resource ${resource.name} must include ${ownerColumn}`);
|
|
25
|
-
if (["tenant", "public"].includes(scope) && !columns.includes(tenantColumn)) throw new TypeError(`${scope} resource ${resource.name} must include ${tenantColumn}`);
|
|
26
|
-
const filterableColumns = [...new Set((resource.filterableColumns || columns).map(String))];
|
|
27
|
-
const orderableColumns = [...new Set((resource.orderableColumns || columns).map(String))];
|
|
28
|
-
for (const column of [...filterableColumns, ...orderableColumns]) if (!columns.includes(column)) throw new TypeError(`Data resource ${resource.name} references an unselected column`);
|
|
29
|
-
const writableColumns = [...new Set((resource.writableColumns || []).map(String))].filter((column) => column !== ownerColumn && column !== tenantColumn);
|
|
30
|
-
if (writableColumns.some((column) => !columns.includes(column))) throw new TypeError(`Data resource ${resource.name} references an unwritable column`);
|
|
31
|
-
const columnScopes = Object.fromEntries(Object.entries(resource.columnScopes || {}).map(([column, scopes]) => [String(column), Array.isArray(scopes) ? scopes.map(String) : [String(scopes)]]));
|
|
32
|
-
if (Object.keys(columnScopes).some((column) => !columns.includes(column) || columnScopes[column].some((scope) => !/^[a-z0-9]+(?::[a-z0-9-]+)+$/.test(scope)))) throw new TypeError(`Data resource ${resource.name} has invalid column scopes`);
|
|
33
|
-
if (!Array.isArray(resource.readableColumns) || !resource.readableColumns.length) throw new TypeError(`Data resource ${resource.name} requires explicit readableColumns`);
|
|
34
|
-
const readableColumns = [...new Set(resource.readableColumns.map(String))];
|
|
35
|
-
if (readableColumns.some((column) => !columns.includes(column))) throw new TypeError(`Data resource ${resource.name} references an unreadable column`);
|
|
36
|
-
const operations = [...new Set((resource.operations || ["read", ...(writableColumns.length ? ["create", "update", "delete"] : [])]).map((operation) => String(operation).toLowerCase()))];
|
|
37
|
-
if (!operations.length || operations.some((operation) => !DATA_OPERATIONS.includes(operation)) || !operations.includes("read")) throw new TypeError(`Data resource ${name} has invalid operations`);
|
|
38
|
-
const publicRead = resource.publicRead && typeof resource.publicRead === "object" ? { column: String(resource.publicRead.column || ""), value: resource.publicRead.value } : Boolean(resource.publicRead);
|
|
39
|
-
if (publicRead && typeof publicRead === "object" && (!/^[a-z][a-z0-9_]*$/.test(publicRead.column) || !columns.includes(publicRead.column))) throw new TypeError(`Public resource ${resource.name} requires a selected visibility column`);
|
|
40
|
-
return { ...resource, name, table: String(resource.table), scope, columns, readableColumns, idColumn, ownerColumn, tenantColumn, filterableColumns, orderableColumns, writableColumns, columnScopes, operations, publicRead };
|
|
41
|
-
});
|
|
42
|
-
}
|
package/src/repository.js
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { auditLog } from "./core/events.js";
|
|
2
|
-
|
|
3
|
-
export class RepositoryError extends Error {
|
|
4
|
-
constructor(message, status = 500) { super(message); this.name = "RepositoryError"; this.status = status; }
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export function defineRepository({ name, resource, scope = "tenant", relations = {}, hooks = {}, resourceDefinition = null } = {}) {
|
|
8
|
-
if (!/^[a-z][a-z0-9_-]*$/.test(String(name || ""))) throw new TypeError("Repositories require a safe name");
|
|
9
|
-
if (!resource) throw new TypeError(`Repository ${name} requires a data resource`);
|
|
10
|
-
return { name: String(name), resource: String(resource), scope, relations: { ...relations }, hooks: { ...hooks }, resourceDefinition };
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function createRepositories(env, definitions = [], { context } = {}) {
|
|
14
|
-
const specs = new Map(definitions.map((definition) => {
|
|
15
|
-
const spec = defineRepository(definition);
|
|
16
|
-
return [spec.name, spec];
|
|
17
|
-
}));
|
|
18
|
-
const repositories = new Map();
|
|
19
|
-
const get = (name) => {
|
|
20
|
-
const repository = repositories.get(String(name));
|
|
21
|
-
if (!repository) throw new RepositoryError(`Unknown repository: ${name}`, 500);
|
|
22
|
-
return repository;
|
|
23
|
-
};
|
|
24
|
-
for (const spec of specs.values()) repositories.set(spec.name, repository(spec));
|
|
25
|
-
return Object.fromEntries([...repositories].map(([name, value]) => [name, value]));
|
|
26
|
-
|
|
27
|
-
function repository(spec) {
|
|
28
|
-
const reader = () => {
|
|
29
|
-
const data = env?.data;
|
|
30
|
-
if (!data?.[spec.scope]) throw new RepositoryError(`Data scope ${spec.scope} is unavailable`, 500);
|
|
31
|
-
return data[spec.scope];
|
|
32
|
-
};
|
|
33
|
-
const invoke = async (operation, value) => {
|
|
34
|
-
const hook = spec.hooks[operation];
|
|
35
|
-
return typeof hook === "function" ? hook(value, { env, context, repository: api }) : value;
|
|
36
|
-
};
|
|
37
|
-
const api = {
|
|
38
|
-
name: spec.name,
|
|
39
|
-
resource: spec.resource,
|
|
40
|
-
async list(options) { return invoke("list", await reader().list(spec.resource, options)); },
|
|
41
|
-
async page(options) { return invoke("page", await reader().page(spec.resource, options)); },
|
|
42
|
-
async count(options) { return reader().count(spec.resource, options); },
|
|
43
|
-
async find(id) { return invoke("find", await reader().get(spec.resource, id)); },
|
|
44
|
-
async findOne(where, options = {}) { return invoke("findOne", (await reader().list(spec.resource, { ...options, where, limit: 1 }))[0] || null); },
|
|
45
|
-
async insert(values) { const result = await reader().insert(spec.resource, await invoke("beforeInsert", values)); auditLog({ who: "repository", operation: "create", resource: spec.name }); return invoke("afterInsert", result); },
|
|
46
|
-
async update(id, changes) { const result = await reader().update(spec.resource, id, await invoke("beforeUpdate", changes)); auditLog({ who: "repository", operation: "update", resource: spec.name }); return invoke("afterUpdate", result); },
|
|
47
|
-
async updateWhere(where, changes) { return reader().updateWhere(spec.resource, where, await invoke("beforeUpdate", changes)); },
|
|
48
|
-
async delete(id) { const result = await reader().delete(spec.resource, id); auditLog({ who: "repository", operation: "delete", resource: spec.name }); return result; },
|
|
49
|
-
async deleteWhere(where) { return reader().deleteWhere(spec.resource, where); },
|
|
50
|
-
async link(relation, record) {
|
|
51
|
-
const relationSpec = spec.relations[relation];
|
|
52
|
-
if (!relationSpec) throw new RepositoryError(`Unknown relation ${spec.name}.${relation}`, 500);
|
|
53
|
-
const target = get(relationSpec.repository);
|
|
54
|
-
const localKey = relationSpec.localKey || "id";
|
|
55
|
-
const foreignKey = relationSpec.foreignKey || `${spec.name}_id`;
|
|
56
|
-
const value = record?.[localKey];
|
|
57
|
-
if (value === undefined || value === null) return relationSpec.many === false ? null : [];
|
|
58
|
-
return relationSpec.many === false ? target.findOne({ [foreignKey]: value }) : target.list({ where: { [foreignKey]: value, ...(relationSpec.where || {}) }, ...(relationSpec.options || {}) });
|
|
59
|
-
},
|
|
60
|
-
};
|
|
61
|
-
return api;
|
|
62
|
-
}
|
|
63
|
-
}
|
package/src/runtime/features.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { evaluateCircuitBreaker, listCircuitBreakers, registerFeatureManifests } from "../core/circuits.js";
|
|
2
|
-
|
|
3
|
-
const featureRegistrationPromises = new WeakMap();
|
|
4
|
-
|
|
5
|
-
export function ensureFeatureManifests(env, features = [], { who = "system:update" } = {}) {
|
|
6
|
-
if (!env || typeof env !== "object" || !env.DB) return Promise.resolve();
|
|
7
|
-
const key = features.map((feature) => String(feature?.name || feature?.id || "feature")).join("|");
|
|
8
|
-
let registrations = featureRegistrationPromises.get(env.DB);
|
|
9
|
-
if (!registrations) { registrations = new Map(); featureRegistrationPromises.set(env.DB, registrations); }
|
|
10
|
-
let promise = registrations.get(key);
|
|
11
|
-
if (!promise) {
|
|
12
|
-
promise = (async () => { await registerFeatureManifests(env, features, { who }); const breakers = await listCircuitBreakers(env, { who }); await Promise.all(breakers.filter(Boolean).map((breaker) => evaluateCircuitBreaker(env, breaker.id, { who }))); })();
|
|
13
|
-
registrations.set(key, promise);
|
|
14
|
-
promise.catch(() => registrations.delete(key));
|
|
15
|
-
}
|
|
16
|
-
return promise;
|
|
17
|
-
}
|
package/src/runtime/health.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
export function validateBoot(env, { bindings = [], required = [] } = {}) {
|
|
2
|
-
const missingBindings = bindings.filter((name) => !env?.[name]);
|
|
3
|
-
const missingValues = required.filter((name) => !env?.[name] || String(env[name]).startsWith("replace-with-"));
|
|
4
|
-
return { ok: missingBindings.length === 0 && missingValues.length === 0, missingBindings, missingValues };
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export function assertBoot(env, spec = {}) {
|
|
8
|
-
const result = validateBoot(env, spec);
|
|
9
|
-
if (!result.ok) throw new Error(`Worker boot validation failed: ${[...result.missingBindings, ...result.missingValues].join(", ")}`);
|
|
10
|
-
return result;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function methodNotAllowed(allow = "GET") { return new Response("Method Not Allowed", { status: 405, headers: { Allow: allow } }); }
|
|
14
|
-
|
|
15
|
-
export function healthResponse(env, details = {}) {
|
|
16
|
-
return Response.json({ ok: true, version: String(env.BUILD_SHA || "unknown").slice(0, 7), build_number: env.BUILD_NUMBER ? String(env.BUILD_NUMBER) : null, ...details }, { headers: { "Cache-Control": "no-store" } });
|
|
17
|
-
}
|
package/src/runtime/worker.js
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { ensureScopes, ensureSubscriptionManifest, SubscriptionError } from "../auth/index.js";
|
|
2
|
-
import { auditedD1, createEventHandler, listFeatureHealth, requestActor, requestContext, secureResponse } from "../core/index.js";
|
|
3
|
-
import { jobResponse, liveEventsResponse } from "../api/jobs.js";
|
|
4
|
-
import { createRepositories } from "../repository.js";
|
|
5
|
-
import { dispatchRoutes } from "../api/contracts.js";
|
|
6
|
-
import { defineApp } from "../app.js";
|
|
7
|
-
import { createDataReader } from "../data/reader.js";
|
|
8
|
-
import { DataScopeError, normalizeDataResources } from "../data/resources.js";
|
|
9
|
-
import { requestDataContext } from "../data/context.js";
|
|
10
|
-
import { adminBoundary } from "../admin/routes.js";
|
|
11
|
-
import { ensureFeatureManifests } from "./features.js";
|
|
12
|
-
import { healthResponse } from "./health.js";
|
|
13
|
-
|
|
14
|
-
const scopeManifestPromises = new WeakMap();
|
|
15
|
-
|
|
16
|
-
export function createWorker({ fetch, scheduled, app = { name: "worker" }, auth, authorize, scopes = [], subscriptionManifest = [], scopeRoutes = [], apiRoutes = [], middleware = [], features = [], dataResources = [], repositories = [], publicTenantId = null, health, boot, security = true, eventHubBinding = "EVENT_HUB" }) {
|
|
17
|
-
if (typeof fetch !== "function") throw new TypeError("createWorker requires a fetch handler");
|
|
18
|
-
const application = defineApp(app); const provider = auth || features.find((feature) => typeof feature?.getUser === "function"); const eventHandler = createEventHandler(features, { eventHubBinding });
|
|
19
|
-
const repositoryDefinitions = [...repositories, ...features.flatMap((feature) => Array.isArray(feature?.repositories) ? feature.repositories : [])];
|
|
20
|
-
const registeredDataResources = normalizeDataResources([...dataResources, ...features.flatMap((feature) => Array.isArray(feature?.dataResources) ? feature.dataResources : []), ...repositoryDefinitions.filter((definition) => definition.resourceDefinition).map((definition) => definition.resourceDefinition)]);
|
|
21
|
-
const featureRoutes = features.flatMap((feature) => Array.isArray(feature?.routes) ? [async (request, env, ctx, next, state) => { for (const route of feature.routes) { const matches = typeof route?.match === "function" ? await route.match(request, env, state) : route?.path === new URL(request.url).pathname; if (matches && typeof route.handle === "function") return route.handle({ request, env, ctx, state, next }); } return next(); }] : []);
|
|
22
|
-
const chain = [(request, env, ctx, next, state) => adminBoundary(request, env, ctx, next, state, { provider, authorize, scopes: [], scopeRoutes, features }), ...(apiRoutes.length ? [(request, env, ctx, next, state) => dispatchRoutes(request, env, ctx, state, apiRoutes).then((response) => response || next(request))] : []), ...features.flatMap((feature) => feature?.middleware ? [feature.middleware.bind(feature)] : []), ...featureRoutes, ...middleware, ...(auth ? [(request, env, ctx, next) => auth(request, env, ctx, next)] : [])].filter(Boolean);
|
|
23
|
-
return {
|
|
24
|
-
async fetch(request, env, ctx) {
|
|
25
|
-
try {
|
|
26
|
-
if (boot) await boot(env, { request, ctx });
|
|
27
|
-
if (subscriptionManifest.length) await ensureSubscriptionManifest(env, subscriptionManifest, { who: "system:update" });
|
|
28
|
-
const url = new URL(request.url); const state = Object.create(null); state.requestId = crypto.randomUUID();
|
|
29
|
-
if (env?.DB && scopes.length && isAdminPath(url.pathname)) await ensureScopeManifest(env, scopes);
|
|
30
|
-
if (provider?.getUser) state.user = await provider.getUser(request, env).catch(() => null); if (state.user?.authUser) state.authUser = state.user.authUser;
|
|
31
|
-
let dataContext;
|
|
32
|
-
state.data = createDataReader(env, { resources: registeredDataResources, context: () => dataContext ||= requestDataContext(env, { state, request, publicTenantId }) });
|
|
33
|
-
const requestEnv = Object.create(env || null);
|
|
34
|
-
Object.assign(requestEnv, { app: application, DB: env?.DB ? auditedD1(env.DB, requestActor(state)) : env?.DB, data: state.data, repositories: createRepositories(requestEnv, repositoryDefinitions), user: state.user || null, authUser: state.authUser || null, userId: state.authUser?.id || null, eventHandler: (event, eventEnv, eventCtx) => eventHandler(event, eventEnv || requestEnv, eventCtx || ctx) });
|
|
35
|
-
requestEnv.requestId = state.requestId; state.context = requestContext({ request, env: requestEnv, ctx, state, data: state.data }); requestEnv.context = state.context; requestEnv.event = state.context.event;
|
|
36
|
-
if (env?.DB && features.some((feature) => typeof feature?.healthcheck === "function" || feature?.healthchecks?.length || feature?.healthChecks?.length || feature?.circuitBreakers?.length || feature?.circuit_breakers?.length)) ctx?.waitUntil?.(ensureFeatureManifests(env, features).catch((error) => console.error("[EventLog] feature manifest registration failed", error)));
|
|
37
|
-
const dispatch = async (index, currentRequest = request) => { const layer = chain[index]; if (!layer) { if (url.pathname === "/health" || url.pathname === "/api/health") { const details = health ? await health(requestEnv, { request: currentRequest, ctx, state }) : {}; const featureHealth = requestEnv?.DB ? await listFeatureHealth(requestEnv, { who: "system:read" }).catch(() => []) : []; return healthResponse(requestEnv, featureHealth.length ? { ...details, features: featureHealth } : details); } if (url.pathname === "/api/tenant" && currentRequest.method === "GET") { const context = await state.data.context(); if (!context.userId) return Response.json({ error: "Authentication is required." }, { status: 401 }); if (context.invalidTenant) return Response.json({ error: "The requested tenant is not available." }, { status: 400 }); return Response.json({ tenant: context.tenantId ? { id: context.tenantId, name: context.tenants?.find((tenant) => tenant.id === context.tenantId)?.name || null } : null, tenants: context.tenants || [] }); } return fetch(currentRequest, requestEnv, ctx, state); } if (typeof layer !== "function") throw new TypeError("Worker middleware must be a function"); return layer(currentRequest, requestEnv, ctx, (nextRequest = currentRequest) => dispatch(index + 1, nextRequest), state); };
|
|
38
|
-
const liveResponse = await liveEventsResponse(request, requestEnv, state, eventHubBinding);
|
|
39
|
-
if (liveResponse) return liveResponse;
|
|
40
|
-
const response = await jobResponse(request, requestEnv, state) || await dispatch(0); return withRequestId(security ? secureResponse(response) : response, state.requestId);
|
|
41
|
-
} catch (error) {
|
|
42
|
-
console.error("[worker] request failed", error); const errorResponse = error instanceof DataScopeError || error instanceof SubscriptionError ? Response.json({ error: error.message }, { status: 403, headers: { "Cache-Control": "no-store" } }) : Response.json({ error: "Internal server error" }, { status: 500, headers: { "Cache-Control": "no-store" } }); return withRequestId(security ? secureResponse(errorResponse) : errorResponse, error?.requestId || crypto.randomUUID());
|
|
43
|
-
}
|
|
44
|
-
},
|
|
45
|
-
...(scheduled ? { scheduled } : {}),
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function withRequestId(response, requestId) { const headers = new Headers(response.headers); headers.set("X-Request-ID", String(requestId)); return new Response(response.body, { status: response.status, statusText: response.statusText, headers }); }
|
|
50
|
-
function isAdminPath(pathname) { return pathname === "/admin" || pathname.startsWith("/admin/") || pathname === "/api/admin" || pathname.startsWith("/api/admin/"); }
|
|
51
|
-
function ensureScopeManifest(env, scopes) {
|
|
52
|
-
let registrations = scopeManifestPromises.get(env.DB);
|
|
53
|
-
if (!registrations) { registrations = new Map(); scopeManifestPromises.set(env.DB, registrations); }
|
|
54
|
-
const key = JSON.stringify(scopes);
|
|
55
|
-
let promise = registrations.get(key);
|
|
56
|
-
if (!promise) { promise = ensureScopes(env, scopes, { who: "system:update" }); registrations.set(key, promise); promise.catch(() => registrations.delete(key)); }
|
|
57
|
-
return promise;
|
|
58
|
-
}
|
package/src/ui/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./react/index.jsx";
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { useState } from "react";
|
|
2
|
-
import { apiJson } from "../../api/client.js";
|
|
3
|
-
import { ResourceState, useApiResource } from "./foundation.jsx";
|
|
4
|
-
|
|
5
|
-
export function UserManagement() {
|
|
6
|
-
const users = useApiResource("/api/admin/users");
|
|
7
|
-
const scopes = useApiResource("/api/admin/scopes");
|
|
8
|
-
const tenants = useApiResource("/api/admin/tenants");
|
|
9
|
-
const reload = () => Promise.all([users.reload(), scopes.reload(), tenants.reload()]);
|
|
10
|
-
const options = { scopes: scopes.value?.scopes || [], tenants: tenants.value?.tenants || [] };
|
|
11
|
-
return <section className="cf-ui-card"><h1>User access</h1><ResourceState loading={users.loading || scopes.loading || tenants.loading} error={users.error || scopes.error || tenants.error} empty="No users registered.">{(users.value?.users || []).length ? <div className="cf-ui-table-wrap"><table><thead><tr><th>User</th><th>Tenants</th><th>Scopes</th><th>Save</th></tr></thead><tbody>{users.value.users.map((user) => <UserRow key={user.id} user={user} options={options} onSaved={reload} />)}</tbody></table></div> : null}</ResourceState></section>;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function UserRow({ user, options, onSaved }) {
|
|
15
|
-
const [selectedScopes, setSelectedScopes] = useStateSet(user.scopes);
|
|
16
|
-
const [selectedTenants, setSelectedTenants] = useStateSet((user.tenants || []).map((tenant) => tenant.id));
|
|
17
|
-
const [saving, setSaving] = useState(false);
|
|
18
|
-
const save = async () => { setSaving(true); try { await Promise.all([apiJson(`/api/admin/users/${encodeURIComponent(user.id)}/scopes`, { scopes: [...selectedScopes] }, { method: "PUT" }), apiJson(`/api/admin/users/${encodeURIComponent(user.id)}/tenants`, { tenants: [...selectedTenants] }, { method: "PUT" })]); await onSaved(); } finally { setSaving(false); } };
|
|
19
|
-
return <tr><th scope="row">{user.display_name || user.email || "Unnamed user"}<small>{user.email || "No email"}</small></th><td><CheckList items={options.tenants} selected={selectedTenants} onChange={setSelectedTenants} valueKey="id" labelKey="name" /></td><td><CheckList items={options.scopes} selected={selectedScopes} onChange={setSelectedScopes} valueKey="name" labelKey="label" /></td><td><button type="button" disabled={saving} onClick={save}>{saving ? "Saving…" : "Save"}</button></td></tr>;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function CheckList({ items, selected, onChange, valueKey, labelKey }) {
|
|
23
|
-
return <div className="cf-ui-check-list">{items.map((item) => { const value = item[valueKey]; return <label key={value}><input type="checkbox" checked={selected.has(value)} onChange={(event) => { const next = new Set(selected); if (event.target.checked) next.add(value); else next.delete(value); onChange(next); }} />{item[labelKey] || value}</label>; })}</div>;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function useStateSet(initial = []) {
|
|
27
|
-
const [value, setValue] = useState(() => new Set(initial || []));
|
|
28
|
-
return [value, setValue];
|
|
29
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { apiJson } from "../../api/client.js";
|
|
2
|
-
import { useLiveEvent } from "./live-events.jsx";
|
|
3
|
-
import { ResourceState, StatusBadge, useApiResource, useMutation } from "./foundation.jsx";
|
|
4
|
-
|
|
5
|
-
export function FeatureCatalog() {
|
|
6
|
-
const resource = useApiResource("/api/admin/features");
|
|
7
|
-
useLiveEvent((event) => ["healthcheck", "circuit"].some((word) => String(event.what || "").includes(word)), resource.reload);
|
|
8
|
-
const features = resource.value?.features || [];
|
|
9
|
-
return <section className="cf-ui-card"><header><h1>Installed features</h1><p>Runtime modules, package versions, healthchecks, and circuit breakers.</p></header><ResourceState {...resource} empty="No features registered.">{features.length ? <div className="cf-ui-table-wrap"><table><thead><tr><th>Feature</th><th>Package/version</th><th>Health</th><th>Roll-up</th><th>Healthchecks</th><th>Breakers</th></tr></thead><tbody>{features.map((item) => <tr key={item.feature}><th scope="row">{item.display_name || item.feature}<small>{item.feature}</small></th><td>{item.package_name || "Unknown package"}<br />{item.version || "Unknown version"}</td><td><StatusBadge state={item.health} /></td><td>{item.circuit_breaker ? <StatusBadge state={item.circuit_breaker.state} /> : "None"}</td><td><CatalogList items={item.healthchecks} /></td><td><CatalogList items={item.circuit_breakers} /></td></tr>)}</tbody></table></div> : null}</ResourceState></section>;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function HealthcheckCatalog() {
|
|
13
|
-
const resource = useApiResource("/api/admin/healthchecks");
|
|
14
|
-
const items = resource.value?.healthchecks || [];
|
|
15
|
-
return <section className="cf-ui-card"><h1>Healthchecks</h1><ResourceState {...resource} empty="No healthchecks registered.">{items.length ? <div className="cf-ui-card-grid">{items.map((item) => <article className="cf-ui-card" key={item.id}><h2>{item.display_name}</h2><StatusBadge state={item.state} /><p>{item.feature} / {item.component}</p></article>)}</div> : null}</ResourceState></section>;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function CircuitBreakerCatalog() {
|
|
19
|
-
const resource = useApiResource("/api/admin/circuit-breakers");
|
|
20
|
-
const items = resource.value?.circuit_breakers || [];
|
|
21
|
-
const update = async (item, state) => { await apiJson(`/api/admin/circuit-breakers/${encodeURIComponent(item.id)}`, { state }, { method: "PUT" }); await resource.reload(); };
|
|
22
|
-
return <section className="cf-ui-card"><h1>Circuit breakers</h1><ResourceState {...resource} empty="No circuit breakers registered.">{items.length ? <div className="cf-ui-list">{items.map((item) => <article className="cf-ui-breaker" key={item.id}><div><h2>{item.display_name}</h2><small>{item.feature}/{item.name}</small></div><StatusBadge state={item.state} /><div className="cf-ui-actions">{["off", "tripped", "on"].map((state) => <button type="button" key={state} disabled={item.state === state} onClick={() => update(item, state)}>{state}</button>)}</div></article>)}</div> : null}</ResourceState></section>;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function ScopeCatalog() {
|
|
26
|
-
const resource = useApiResource("/api/admin/scopes");
|
|
27
|
-
const scopes = resource.value?.scopes || [];
|
|
28
|
-
return <section className="cf-ui-card"><h1>Scopes</h1><ResourceState {...resource} empty="No scopes registered.">{scopes.length ? <ul className="cf-ui-list">{scopes.map((scope) => <li key={scope.name}><strong>{scope.label || scope.name}</strong><small>{scope.name}</small><p>{scope.description}</p></li>)}</ul> : null}</ResourceState></section>;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function GroupCatalog() {
|
|
32
|
-
const resource = useApiResource("/api/admin/groups");
|
|
33
|
-
const groups = resource.value?.groups || [];
|
|
34
|
-
return <section className="cf-ui-card"><h1>Groups</h1><ResourceState {...resource} empty="No groups registered.">{groups.length ? <ul className="cf-ui-list">{groups.map((group) => <li key={group.id || group.name}><strong>{group.display_name || group.name}</strong><small>{group.name}</small></li>)}</ul> : null}</ResourceState></section>;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function CatalogList({ items = [] }) {
|
|
38
|
-
return items.length ? <ul className="cf-ui-inline-list">{items.map((item) => <li key={item.id || item.name}><strong>{item.display_name || item.name}</strong>: <StatusBadge state={item.state} /></li>)}</ul> : <span>None registered</span>;
|
|
39
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
const SYSTEM_LINKS = [
|
|
2
|
-
["Overview", "/admin", "home"],
|
|
3
|
-
["Users", "/admin/users", "users"],
|
|
4
|
-
["Scopes", "/admin/scopes", "scopes"],
|
|
5
|
-
["Groups", "/admin/groups", "groups"],
|
|
6
|
-
["Features", "/admin/features", "features"],
|
|
7
|
-
["Healthchecks", "/admin/healthchecks", "healthchecks"],
|
|
8
|
-
["Circuit breakers", "/admin/circuit-breakers", "circuit-breakers"],
|
|
9
|
-
];
|
|
10
|
-
|
|
11
|
-
export function AdminShell({ children, active = "", applicationLinks = [], title = "Administration" }) {
|
|
12
|
-
return <div className="cf-ui-admin-shell"><aside className="cf-ui-admin-nav"><p className="cf-ui-admin-title">{title}</p><nav aria-label="Administration"><section><p className="cf-ui-nav-label">Application</p>{applicationLinks.map((link) => <AdminLink key={link.href} link={link} active={active} />)}</section><section><p className="cf-ui-nav-label">System</p>{SYSTEM_LINKS.map(([label, href, key]) => <AdminLink key={href} link={{ label, href, key }} active={active} />)}</section></nav></aside><main className="cf-ui-admin-content">{children}</main></div>;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function AdminLink({ link, active }) {
|
|
16
|
-
return <a href={link.href} aria-current={link.key === active ? "page" : undefined}>{link.label}</a>;
|
|
17
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { useCallback, useEffect, useState } from "react";
|
|
2
|
-
import { apiFetch, apiJson } from "../../api/client.js";
|
|
3
|
-
|
|
4
|
-
export function useApiResource(path, { enabled = true, initialValue = null } = {}) {
|
|
5
|
-
const [value, setValue] = useState(initialValue);
|
|
6
|
-
const [loading, setLoading] = useState(Boolean(enabled));
|
|
7
|
-
const [error, setError] = useState(null);
|
|
8
|
-
const reload = useCallback(async () => {
|
|
9
|
-
if (!enabled) return null;
|
|
10
|
-
setLoading(true);
|
|
11
|
-
try { const next = await apiFetch(path); setValue(next); setError(null); return next; }
|
|
12
|
-
catch (cause) { setError(cause); throw cause; }
|
|
13
|
-
finally { setLoading(false); }
|
|
14
|
-
}, [enabled, path]);
|
|
15
|
-
useEffect(() => { reload().catch(() => {}); }, [reload]);
|
|
16
|
-
return { value, setValue, loading, error, reload };
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function useMutation(path, { method = "POST" } = {}) {
|
|
20
|
-
const [loading, setLoading] = useState(false);
|
|
21
|
-
const [error, setError] = useState(null);
|
|
22
|
-
const mutate = useCallback(async (payload = {}) => {
|
|
23
|
-
setLoading(true);
|
|
24
|
-
try { const result = await apiJson(path, payload, { method }); setError(null); return result; }
|
|
25
|
-
catch (cause) { setError(cause); throw cause; }
|
|
26
|
-
finally { setLoading(false); }
|
|
27
|
-
}, [method, path]);
|
|
28
|
-
return { mutate, loading, error };
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function ResourceState({ loading, error, empty = "Nothing to show.", children }) {
|
|
32
|
-
if (loading) return <p className="cf-ui-status" role="status">Loading…</p>;
|
|
33
|
-
if (error) return <p className="cf-ui-error" role="alert">{error.message || "Unable to load data."}</p>;
|
|
34
|
-
return children || <p className="cf-ui-status">{empty}</p>;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function StatusBadge({ state }) {
|
|
38
|
-
const value = String(state || "unknown").toLowerCase();
|
|
39
|
-
return <span className={`cf-ui-status-badge cf-ui-status-${value}`}>{value}</span>;
|
|
40
|
-
}
|
package/src/ui/react/index.jsx
DELETED
package/src/ui/react/jobs.jsx
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
|
-
import { apiFetch } from "../../api/client.js";
|
|
3
|
-
import { useLiveEvent, useLiveEvents } from "./live-events.jsx";
|
|
4
|
-
|
|
5
|
-
function jobQuery(filters) {
|
|
6
|
-
const query = new URLSearchParams();
|
|
7
|
-
for (const [key, value] of Object.entries(filters || {})) if (value !== undefined && value !== null && value !== "") query.set(key, value);
|
|
8
|
-
return query.toString() ? `/api/jobs?${query}` : "/api/jobs";
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function useJobs(filters = {}) {
|
|
12
|
-
const key = useMemo(() => JSON.stringify(filters), [filters]);
|
|
13
|
-
const path = useMemo(() => jobQuery(JSON.parse(key)), [key]);
|
|
14
|
-
const [jobs, setJobs] = useState([]);
|
|
15
|
-
const [loading, setLoading] = useState(true);
|
|
16
|
-
const [error, setError] = useState(null);
|
|
17
|
-
const { connection } = useLiveEvents();
|
|
18
|
-
const reload = useCallback(async () => {
|
|
19
|
-
setLoading(true);
|
|
20
|
-
try { const result = await apiFetch(path); setJobs(result.jobs || []); setError(null); }
|
|
21
|
-
catch (cause) { setError(cause); }
|
|
22
|
-
finally { setLoading(false); }
|
|
23
|
-
}, [path]);
|
|
24
|
-
useEffect(() => { reload(); }, [reload]);
|
|
25
|
-
useEffect(() => { if (connection === "open") reload(); }, [connection, reload]);
|
|
26
|
-
useLiveEvent((event) => String(event.what || "").startsWith("job."), reload);
|
|
27
|
-
return { jobs, loading, error, reload };
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function useJob(jobId) {
|
|
31
|
-
const [job, setJob] = useState(null);
|
|
32
|
-
const [loading, setLoading] = useState(Boolean(jobId));
|
|
33
|
-
const [error, setError] = useState(null);
|
|
34
|
-
const reload = useCallback(async () => {
|
|
35
|
-
if (!jobId) return null;
|
|
36
|
-
setLoading(true);
|
|
37
|
-
try { const result = await apiFetch(`/api/jobs/${encodeURIComponent(jobId)}`); setJob(result.job); setError(null); return result.job; }
|
|
38
|
-
catch (cause) { setError(cause); throw cause; }
|
|
39
|
-
finally { setLoading(false); }
|
|
40
|
-
}, [jobId]);
|
|
41
|
-
useEffect(() => { reload().catch(() => {}); }, [reload]);
|
|
42
|
-
useLiveEvent((event) => event.details?.jobId === jobId, () => { reload().catch(() => {}); });
|
|
43
|
-
return { job, loading, error, reload };
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export function useJobNotifications({ limit = 20 } = {}) {
|
|
47
|
-
const { jobs, loading, error, reload } = useJobs({ limit });
|
|
48
|
-
const [dismissed, setDismissed] = useState(() => new Set());
|
|
49
|
-
const notifications = jobs.filter((job) => ["succeeded", "failed", "cancelled"].includes(job.status) && !dismissed.has(job.id));
|
|
50
|
-
const dismiss = useCallback((jobId) => setDismissed((current) => new Set([...current, jobId])), []);
|
|
51
|
-
return { notifications, loading, error, dismiss, reload };
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function JobNotificationBanner({ job, onDismiss }) {
|
|
55
|
-
const failed = job.status === "failed";
|
|
56
|
-
const cancelled = job.status === "cancelled";
|
|
57
|
-
const message = failed ? `${job.type} failed.` : cancelled ? `${job.type} was cancelled.` : `${job.type} finished.`;
|
|
58
|
-
return <aside className={`cf-ui-job-banner cf-ui-job-${job.status}`} role="status" aria-live="polite"><span>{message}</span><button type="button" onClick={() => onDismiss?.(job.id)} aria-label="Dismiss notification">Dismiss</button></aside>;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export function JobNotificationList({ notifications, onDismiss }) {
|
|
62
|
-
return <>{notifications.map((job) => <JobNotificationBanner key={job.id} job={job} onDismiss={onDismiss} />)}</>;
|
|
63
|
-
}
|