canopycms 0.0.0 → 0.0.2
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/dist/auth/plugin.d.ts +8 -0
- package/dist/auth/plugin.d.ts.map +1 -1
- package/dist/build-mode.d.ts +15 -5
- package/dist/build-mode.d.ts.map +1 -1
- package/dist/build-mode.js +18 -8
- package/dist/build-mode.js.map +1 -1
- package/dist/cli/init.d.ts +2 -2
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +37 -36
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/template-files/ai-config.ts.template +21 -0
- package/dist/cli/template-files/ai-route.ts.template +10 -0
- package/dist/cli/template-files/canopy.ts.template +24 -0
- package/dist/cli/templates.d.ts +5 -1
- package/dist/cli/templates.d.ts.map +1 -1
- package/dist/cli/templates.js +9 -2
- package/dist/cli/templates.js.map +1 -1
- package/dist/config/schemas/config.d.ts +4 -0
- package/dist/config/schemas/config.d.ts.map +1 -1
- package/dist/config/schemas/config.js +2 -0
- package/dist/config/schemas/config.js.map +1 -1
- package/dist/config/types.d.ts +5 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/content-reader.js +2 -2
- package/dist/content-reader.js.map +1 -1
- package/dist/context.js +5 -5
- package/dist/context.js.map +1 -1
- package/dist/operating-mode/client-unsafe-strategy.d.ts.map +1 -1
- package/dist/operating-mode/client-unsafe-strategy.js +15 -18
- package/dist/operating-mode/client-unsafe-strategy.js.map +1 -1
- package/dist/operating-mode/types.d.ts +8 -0
- package/dist/operating-mode/types.d.ts.map +1 -1
- package/dist/server.d.ts +2 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +2 -0
- package/dist/server.js.map +1 -1
- package/package.json +5 -4
- package/src/cli/init.ts +43 -38
- package/dist/__integration__/fixtures/content-seeds.d.ts +0 -43
- package/dist/__integration__/fixtures/content-seeds.d.ts.map +0 -1
- package/dist/__integration__/fixtures/content-seeds.js +0 -99
- package/dist/__integration__/fixtures/content-seeds.js.map +0 -1
- package/dist/__integration__/fixtures/schemas.d.ts +0 -12
- package/dist/__integration__/fixtures/schemas.d.ts.map +0 -1
- package/dist/__integration__/fixtures/schemas.js +0 -65
- package/dist/__integration__/fixtures/schemas.js.map +0 -1
- package/dist/__integration__/test-utils/api-client.d.ts +0 -123
- package/dist/__integration__/test-utils/api-client.d.ts.map +0 -1
- package/dist/__integration__/test-utils/api-client.js +0 -118
- package/dist/__integration__/test-utils/api-client.js.map +0 -1
- package/dist/__integration__/test-utils/multi-user.d.ts +0 -25
- package/dist/__integration__/test-utils/multi-user.d.ts.map +0 -1
- package/dist/__integration__/test-utils/multi-user.js +0 -105
- package/dist/__integration__/test-utils/multi-user.js.map +0 -1
- package/dist/__integration__/test-utils/test-workspace.d.ts +0 -25
- package/dist/__integration__/test-utils/test-workspace.d.ts.map +0 -1
- package/dist/__integration__/test-utils/test-workspace.js +0 -102
- package/dist/__integration__/test-utils/test-workspace.js.map +0 -1
- package/dist/editor/BranchManager.stories.d.ts +0 -8
- package/dist/editor/BranchManager.stories.d.ts.map +0 -1
- package/dist/editor/BranchManager.stories.js +0 -74
- package/dist/editor/BranchManager.stories.js.map +0 -1
- package/dist/editor/CanopyEditor.stories.d.ts +0 -7
- package/dist/editor/CanopyEditor.stories.d.ts.map +0 -1
- package/dist/editor/CanopyEditor.stories.js +0 -99
- package/dist/editor/CanopyEditor.stories.js.map +0 -1
- package/dist/editor/CommentsPanel.stories.d.ts +0 -10
- package/dist/editor/CommentsPanel.stories.d.ts.map +0 -1
- package/dist/editor/CommentsPanel.stories.js +0 -175
- package/dist/editor/CommentsPanel.stories.js.map +0 -1
- package/dist/editor/Editor.stories.d.ts +0 -7
- package/dist/editor/Editor.stories.d.ts.map +0 -1
- package/dist/editor/Editor.stories.js +0 -95
- package/dist/editor/Editor.stories.js.map +0 -1
- package/dist/editor/EditorPanes.stories.d.ts +0 -7
- package/dist/editor/EditorPanes.stories.d.ts.map +0 -1
- package/dist/editor/EditorPanes.stories.js +0 -116
- package/dist/editor/EditorPanes.stories.js.map +0 -1
- package/dist/editor/EntryNavigator.stories.d.ts +0 -8
- package/dist/editor/EntryNavigator.stories.d.ts.map +0 -1
- package/dist/editor/EntryNavigator.stories.js +0 -42
- package/dist/editor/EntryNavigator.stories.js.map +0 -1
- package/dist/editor/FormRenderer.stories.d.ts +0 -7
- package/dist/editor/FormRenderer.stories.d.ts.map +0 -1
- package/dist/editor/FormRenderer.stories.js +0 -115
- package/dist/editor/FormRenderer.stories.js.map +0 -1
- package/dist/editor/GroupManager.stories.d.ts +0 -19
- package/dist/editor/GroupManager.stories.d.ts.map +0 -1
- package/dist/editor/GroupManager.stories.js +0 -265
- package/dist/editor/GroupManager.stories.js.map +0 -1
- package/dist/editor/PermissionManager.stories.d.ts +0 -20
- package/dist/editor/PermissionManager.stories.d.ts.map +0 -1
- package/dist/editor/PermissionManager.stories.js +0 -506
- package/dist/editor/PermissionManager.stories.js.map +0 -1
- package/dist/editor/comments/FieldWrapper.stories.d.ts +0 -10
- package/dist/editor/comments/FieldWrapper.stories.d.ts.map +0 -1
- package/dist/editor/comments/FieldWrapper.stories.js +0 -173
- package/dist/editor/comments/FieldWrapper.stories.js.map +0 -1
- package/dist/editor/fields/BlockField.stories.d.ts +0 -7
- package/dist/editor/fields/BlockField.stories.d.ts.map +0 -1
- package/dist/editor/fields/BlockField.stories.js +0 -50
- package/dist/editor/fields/BlockField.stories.js.map +0 -1
- package/dist/editor/fields/fields.stories.d.ts +0 -8
- package/dist/editor/fields/fields.stories.d.ts.map +0 -1
- package/dist/editor/fields/fields.stories.js +0 -34
- package/dist/editor/fields/fields.stories.js.map +0 -1
- package/dist/test-utils/api-test-helpers.d.ts +0 -238
- package/dist/test-utils/api-test-helpers.d.ts.map +0 -1
- package/dist/test-utils/api-test-helpers.js +0 -347
- package/dist/test-utils/api-test-helpers.js.map +0 -1
- package/dist/test-utils/console-spy.d.ts +0 -56
- package/dist/test-utils/console-spy.d.ts.map +0 -1
- package/dist/test-utils/console-spy.js +0 -81
- package/dist/test-utils/console-spy.js.map +0 -1
- package/dist/test-utils/git-helpers.d.ts +0 -21
- package/dist/test-utils/git-helpers.d.ts.map +0 -1
- package/dist/test-utils/git-helpers.js +0 -23
- package/dist/test-utils/git-helpers.js.map +0 -1
- package/dist/test-utils/index.d.ts +0 -5
- package/dist/test-utils/index.d.ts.map +0 -1
- package/dist/test-utils/index.js +0 -4
- package/dist/test-utils/index.js.map +0 -1
- package/src/__integration__/errors/invalid-content.test.ts +0 -238
- package/src/__integration__/errors/permission-denied.test.ts +0 -220
- package/src/__integration__/fixtures/content-seeds.ts +0 -105
- package/src/__integration__/fixtures/schemas.ts +0 -67
- package/src/__integration__/initialization/prod-sim-init.test.ts +0 -139
- package/src/__integration__/permissions/path-permissions.test.ts +0 -314
- package/src/__integration__/permissions/role-permissions.test.ts +0 -354
- package/src/__integration__/permissions/settings-branch-isolation.test.ts +0 -317
- package/src/__integration__/settings/groups-api.test.ts +0 -403
- package/src/__integration__/test-utils/api-client.ts +0 -167
- package/src/__integration__/test-utils/multi-user.ts +0 -129
- package/src/__integration__/test-utils/test-workspace.ts +0 -130
- package/src/__integration__/user/user-context.test.ts +0 -174
- package/src/__integration__/validation/input-validation.test.ts +0 -166
- package/src/__integration__/workflows/api-editing-workflow.test.ts +0 -244
- package/src/__integration__/workflows/conflict-resolution.test.ts +0 -259
- package/src/__integration__/workflows/editing-workflow.test.ts +0 -205
- package/src/__integration__/workflows/review-workflow.test.ts +0 -260
- package/src/ai/__tests__/build.integration.test.ts +0 -224
- package/src/ai/__tests__/generate.integration.test.ts +0 -495
- package/src/ai/__tests__/handler.integration.test.ts +0 -212
- package/src/ai/__tests__/json-to-markdown.test.ts +0 -553
- package/src/ai/generate.ts +0 -410
- package/src/ai/handler.ts +0 -123
- package/src/ai/index.ts +0 -26
- package/src/ai/json-to-markdown.ts +0 -424
- package/src/ai/resolve-branch.ts +0 -34
- package/src/ai/types.ts +0 -160
- package/src/api/AGENTS.md +0 -81
- package/src/api/__test__/mock-client.ts +0 -404
- package/src/api/assets.test.ts +0 -140
- package/src/api/assets.ts +0 -154
- package/src/api/branch-merge.test.ts +0 -163
- package/src/api/branch-merge.ts +0 -113
- package/src/api/branch-review.test.ts +0 -297
- package/src/api/branch-review.ts +0 -136
- package/src/api/branch-status.test.ts +0 -85
- package/src/api/branch-status.ts +0 -153
- package/src/api/branch-withdraw.test.ts +0 -146
- package/src/api/branch-withdraw.ts +0 -81
- package/src/api/branch-workflow.integration.test.ts +0 -578
- package/src/api/branch.test.ts +0 -620
- package/src/api/branch.ts +0 -492
- package/src/api/client.test.ts +0 -349
- package/src/api/client.ts +0 -506
- package/src/api/comments.test.ts +0 -285
- package/src/api/comments.ts +0 -210
- package/src/api/content.test.ts +0 -345
- package/src/api/content.ts +0 -454
- package/src/api/entries.test.ts +0 -1339
- package/src/api/entries.ts +0 -650
- package/src/api/github-sync.ts +0 -144
- package/src/api/groups.test.ts +0 -1013
- package/src/api/groups.ts +0 -375
- package/src/api/guards.test.ts +0 -533
- package/src/api/guards.ts +0 -271
- package/src/api/index.ts +0 -87
- package/src/api/permissions.test.ts +0 -766
- package/src/api/permissions.ts +0 -334
- package/src/api/reference-options.ts +0 -118
- package/src/api/resolve-references.ts +0 -107
- package/src/api/route-builder.ts +0 -289
- package/src/api/schema.test.ts +0 -840
- package/src/api/schema.ts +0 -936
- package/src/api/security.test.ts +0 -233
- package/src/api/settings-helpers.ts +0 -84
- package/src/api/types.ts +0 -40
- package/src/api/user.test.ts +0 -127
- package/src/api/user.ts +0 -42
- package/src/api/validators.test.ts +0 -275
- package/src/api/validators.ts +0 -176
- package/src/asset-store.test.ts +0 -37
- package/src/asset-store.ts +0 -110
- package/src/auth/cache.ts +0 -7
- package/src/auth/caching-auth-plugin.test.ts +0 -154
- package/src/auth/caching-auth-plugin.ts +0 -109
- package/src/auth/context-helpers.ts +0 -75
- package/src/auth/file-based-auth-cache.test.ts +0 -257
- package/src/auth/file-based-auth-cache.ts +0 -279
- package/src/auth/index.ts +0 -12
- package/src/auth/plugin.ts +0 -51
- package/src/auth/types.ts +0 -38
- package/src/authorization/__tests__/branch.test.ts +0 -260
- package/src/authorization/__tests__/content.test.ts +0 -142
- package/src/authorization/__tests__/path.test.ts +0 -133
- package/src/authorization/__tests__/permissions-loader.test.ts +0 -200
- package/src/authorization/branch.ts +0 -94
- package/src/authorization/content.ts +0 -93
- package/src/authorization/groups/index.ts +0 -11
- package/src/authorization/groups/loader.ts +0 -127
- package/src/authorization/groups/schema.ts +0 -48
- package/src/authorization/helpers.ts +0 -48
- package/src/authorization/index.ts +0 -84
- package/src/authorization/path.ts +0 -112
- package/src/authorization/permissions/index.ts +0 -11
- package/src/authorization/permissions/loader.ts +0 -116
- package/src/authorization/permissions/schema.ts +0 -66
- package/src/authorization/test-utils.ts +0 -15
- package/src/authorization/types.ts +0 -66
- package/src/authorization/validation.test.ts +0 -100
- package/src/authorization/validation.ts +0 -62
- package/src/branch-metadata.test.ts +0 -168
- package/src/branch-metadata.ts +0 -166
- package/src/branch-registry.test.ts +0 -248
- package/src/branch-registry.ts +0 -152
- package/src/branch-schema-cache.test.ts +0 -275
- package/src/branch-schema-cache.ts +0 -189
- package/src/branch-workspace.test.ts +0 -183
- package/src/branch-workspace.ts +0 -124
- package/src/build/generate-ai-content.ts +0 -78
- package/src/build/index.ts +0 -8
- package/src/build-mode.ts +0 -27
- package/src/cli/generate-ai-content.ts +0 -100
- package/src/cli/init.test.ts +0 -240
- package/src/cli/templates/canopy.ts.template +0 -55
- package/src/cli/templates.ts +0 -47
- package/src/client.ts +0 -12
- package/src/comment-store.test.ts +0 -442
- package/src/comment-store.ts +0 -301
- package/src/config/__tests__/config.test.ts +0 -513
- package/src/config/flatten.ts +0 -174
- package/src/config/helpers.ts +0 -167
- package/src/config/index.ts +0 -86
- package/src/config/schemas/collection.ts +0 -67
- package/src/config/schemas/config.ts +0 -77
- package/src/config/schemas/field.ts +0 -108
- package/src/config/schemas/media.ts +0 -27
- package/src/config/schemas/permissions.ts +0 -21
- package/src/config/types.ts +0 -321
- package/src/config/validation.ts +0 -70
- package/src/config-test.ts +0 -65
- package/src/config.ts +0 -11
- package/src/content-id-index.test.ts +0 -512
- package/src/content-id-index.ts +0 -479
- package/src/content-reader.test.ts +0 -478
- package/src/content-reader.ts +0 -214
- package/src/content-store.test.ts +0 -1126
- package/src/content-store.ts +0 -793
- package/src/context.ts +0 -111
- package/src/editor/BranchManager.stories.tsx +0 -80
- package/src/editor/BranchManager.test.tsx +0 -324
- package/src/editor/BranchManager.tsx +0 -461
- package/src/editor/CanopyEditor.stories.tsx +0 -128
- package/src/editor/CanopyEditor.test.tsx +0 -81
- package/src/editor/CanopyEditor.tsx +0 -73
- package/src/editor/CanopyEditorPage.test.tsx +0 -59
- package/src/editor/CanopyEditorPage.tsx +0 -25
- package/src/editor/CommentsPanel.stories.tsx +0 -184
- package/src/editor/CommentsPanel.tsx +0 -338
- package/src/editor/Editor.integration.test.tsx +0 -227
- package/src/editor/Editor.stories.tsx +0 -119
- package/src/editor/Editor.tsx +0 -1221
- package/src/editor/EditorPanes.stories.tsx +0 -256
- package/src/editor/EditorPanes.test.tsx +0 -77
- package/src/editor/EditorPanes.tsx +0 -180
- package/src/editor/EntryNavigator.stories.tsx +0 -65
- package/src/editor/EntryNavigator.test.tsx +0 -598
- package/src/editor/EntryNavigator.tsx +0 -665
- package/src/editor/FormRenderer.stories.tsx +0 -212
- package/src/editor/FormRenderer.test.tsx +0 -194
- package/src/editor/FormRenderer.tsx +0 -432
- package/src/editor/GroupManager.stories.tsx +0 -301
- package/src/editor/GroupManager.test.tsx +0 -682
- package/src/editor/GroupManager.tsx +0 -9
- package/src/editor/PermissionManager.stories.tsx +0 -539
- package/src/editor/PermissionManager.test.tsx +0 -864
- package/src/editor/PermissionManager.tsx +0 -12
- package/src/editor/canopy-path.test.ts +0 -23
- package/src/editor/canopy-path.ts +0 -52
- package/src/editor/client-reference-resolver.ts +0 -118
- package/src/editor/comments/BranchComments.tsx +0 -93
- package/src/editor/comments/EntryComments.tsx +0 -94
- package/src/editor/comments/FieldWrapper.stories.tsx +0 -210
- package/src/editor/comments/FieldWrapper.tsx +0 -129
- package/src/editor/comments/InlineCommentThread.test.tsx +0 -384
- package/src/editor/comments/InlineCommentThread.tsx +0 -246
- package/src/editor/comments/ThreadCarousel.test.tsx +0 -393
- package/src/editor/comments/ThreadCarousel.tsx +0 -525
- package/src/editor/components/ConfirmDeleteModal.tsx +0 -49
- package/src/editor/components/EditorContext.tsx +0 -49
- package/src/editor/components/EditorFooter.tsx +0 -47
- package/src/editor/components/EditorHeader.tsx +0 -492
- package/src/editor/components/EditorSidebar.tsx +0 -193
- package/src/editor/components/EntryCreateModal.tsx +0 -193
- package/src/editor/components/RenameEntryModal.tsx +0 -152
- package/src/editor/components/UserBadge.test.tsx +0 -274
- package/src/editor/components/UserBadge.tsx +0 -240
- package/src/editor/components/index.ts +0 -6
- package/src/editor/context/ApiClientContext.tsx +0 -56
- package/src/editor/context/EditorStateContext.tsx +0 -221
- package/src/editor/context/index.ts +0 -40
- package/src/editor/editor-config.test.ts +0 -385
- package/src/editor/editor-config.ts +0 -94
- package/src/editor/editor-utils.test.ts +0 -772
- package/src/editor/editor-utils.ts +0 -303
- package/src/editor/env.ts +0 -4
- package/src/editor/fields/BlockField.stories.tsx +0 -79
- package/src/editor/fields/BlockField.tsx +0 -267
- package/src/editor/fields/CodeField.tsx +0 -41
- package/src/editor/fields/MarkdownField.tsx +0 -205
- package/src/editor/fields/ObjectField.tsx +0 -71
- package/src/editor/fields/ReferenceField.tsx +0 -138
- package/src/editor/fields/SelectField.tsx +0 -76
- package/src/editor/fields/TextField.tsx +0 -35
- package/src/editor/fields/ToggleField.tsx +0 -37
- package/src/editor/fields/fields.stories.tsx +0 -40
- package/src/editor/group-manager/ExternalGroupsTab.tsx +0 -114
- package/src/editor/group-manager/GroupCard.tsx +0 -102
- package/src/editor/group-manager/GroupForm.tsx +0 -66
- package/src/editor/group-manager/InternalGroupsTab.tsx +0 -147
- package/src/editor/group-manager/MemberList.tsx +0 -184
- package/src/editor/group-manager/hooks/useExternalGroupSearch.ts +0 -63
- package/src/editor/group-manager/hooks/useGroupState.ts +0 -134
- package/src/editor/group-manager/hooks/useUserSearch.ts +0 -84
- package/src/editor/group-manager/index.tsx +0 -210
- package/src/editor/group-manager/types.ts +0 -28
- package/src/editor/hooks/README.md +0 -26
- package/src/editor/hooks/__test__/test-utils.tsx +0 -183
- package/src/editor/hooks/index.ts +0 -23
- package/src/editor/hooks/useBranchActions.test.tsx +0 -267
- package/src/editor/hooks/useBranchActions.tsx +0 -121
- package/src/editor/hooks/useBranchManager.test.tsx +0 -391
- package/src/editor/hooks/useBranchManager.tsx +0 -326
- package/src/editor/hooks/useCommentSystem.test.ts +0 -615
- package/src/editor/hooks/useCommentSystem.ts +0 -347
- package/src/editor/hooks/useDraftManager.test.ts +0 -375
- package/src/editor/hooks/useDraftManager.ts +0 -259
- package/src/editor/hooks/useEditorLayout.test.ts +0 -147
- package/src/editor/hooks/useEditorLayout.ts +0 -67
- package/src/editor/hooks/useEntryManager.test.ts +0 -588
- package/src/editor/hooks/useEntryManager.ts +0 -387
- package/src/editor/hooks/useGroupManager.test.ts +0 -277
- package/src/editor/hooks/useGroupManager.ts +0 -139
- package/src/editor/hooks/usePermissionManager.test.ts +0 -211
- package/src/editor/hooks/usePermissionManager.ts +0 -113
- package/src/editor/hooks/useReferenceResolution.ts +0 -248
- package/src/editor/hooks/useSchemaManager.test.ts +0 -370
- package/src/editor/hooks/useSchemaManager.ts +0 -310
- package/src/editor/hooks/useUserContext.tsx +0 -57
- package/src/editor/hooks/useUserMetadata.test.ts +0 -191
- package/src/editor/hooks/useUserMetadata.ts +0 -71
- package/src/editor/permission-manager/GroupSelector.tsx +0 -73
- package/src/editor/permission-manager/PermissionEditor.tsx +0 -321
- package/src/editor/permission-manager/PermissionLevelBadge.tsx +0 -53
- package/src/editor/permission-manager/PermissionTree.tsx +0 -237
- package/src/editor/permission-manager/UserSelector.tsx +0 -95
- package/src/editor/permission-manager/constants.tsx +0 -18
- package/src/editor/permission-manager/hooks/useGroupsAndUsers.ts +0 -153
- package/src/editor/permission-manager/hooks/usePermissionTree.ts +0 -200
- package/src/editor/permission-manager/index.tsx +0 -294
- package/src/editor/permission-manager/types.ts +0 -58
- package/src/editor/permission-manager/utils.ts +0 -179
- package/src/editor/preview-bridge.test.tsx +0 -50
- package/src/editor/preview-bridge.tsx +0 -294
- package/src/editor/schema-editor/CollectionEditor.test.tsx +0 -238
- package/src/editor/schema-editor/CollectionEditor.tsx +0 -520
- package/src/editor/schema-editor/EntryTypeEditor.test.tsx +0 -215
- package/src/editor/schema-editor/EntryTypeEditor.tsx +0 -367
- package/src/editor/schema-editor/index.ts +0 -19
- package/src/editor/setup-test-dom.ts +0 -10
- package/src/editor/test-setup.ts +0 -33
- package/src/editor/theme.tsx +0 -119
- package/src/editor/utils/env.ts +0 -39
- package/src/entry-schema-registry.test.ts +0 -281
- package/src/entry-schema-registry.ts +0 -121
- package/src/entry-schema.ts +0 -84
- package/src/git-manager.test.ts +0 -552
- package/src/git-manager.ts +0 -667
- package/src/github-service.test.ts +0 -312
- package/src/github-service.ts +0 -295
- package/src/http/handler.test.ts +0 -275
- package/src/http/handler.ts +0 -280
- package/src/http/index.ts +0 -11
- package/src/http/router.ts +0 -164
- package/src/http/types.ts +0 -44
- package/src/id.test.ts +0 -48
- package/src/id.ts +0 -22
- package/src/index.ts +0 -26
- package/src/operating-mode/__tests__/strategies.test.ts +0 -511
- package/src/operating-mode/client-safe-strategy.ts +0 -184
- package/src/operating-mode/client-unsafe-strategy.ts +0 -303
- package/src/operating-mode/client.ts +0 -13
- package/src/operating-mode/index.ts +0 -34
- package/src/operating-mode/types.ts +0 -186
- package/src/paths/__tests__/branch.test.ts +0 -53
- package/src/paths/__tests__/normalize.test.ts +0 -141
- package/src/paths/__tests__/resolve.test.ts +0 -207
- package/src/paths/__tests__/validation.test.ts +0 -61
- package/src/paths/branch.ts +0 -115
- package/src/paths/index.ts +0 -73
- package/src/paths/normalize-server.ts +0 -40
- package/src/paths/normalize.ts +0 -107
- package/src/paths/resolve.ts +0 -61
- package/src/paths/test-utils.ts +0 -37
- package/src/paths/types.ts +0 -68
- package/src/paths/validation.test.ts +0 -480
- package/src/paths/validation.ts +0 -391
- package/src/reference-resolver.test.ts +0 -107
- package/src/reference-resolver.ts +0 -157
- package/src/schema/index.ts +0 -29
- package/src/schema/meta-loader.ts +0 -366
- package/src/schema/resolver.ts +0 -83
- package/src/schema/schema-store-types.ts +0 -56
- package/src/schema/schema-store.test.ts +0 -816
- package/src/schema/schema-store.ts +0 -795
- package/src/schema/types.ts +0 -33
- package/src/schema-meta-loader.test.ts +0 -447
- package/src/server.ts +0 -15
- package/src/services.test.ts +0 -559
- package/src/services.ts +0 -373
- package/src/settings-branch-utils.ts +0 -53
- package/src/settings-workspace.ts +0 -156
- package/src/task-queue/README.md +0 -144
- package/src/task-queue/index.ts +0 -14
- package/src/task-queue/task-queue.test.ts +0 -524
- package/src/task-queue/task-queue.ts +0 -514
- package/src/task-queue/types.ts +0 -41
- package/src/test-utils/api-test-helpers.ts +0 -445
- package/src/test-utils/console-spy.test.ts +0 -14
- package/src/test-utils/console-spy.ts +0 -125
- package/src/test-utils/git-helpers.ts +0 -31
- package/src/test-utils/index.ts +0 -4
- package/src/types.ts +0 -54
- package/src/user.ts +0 -118
- package/src/utils/debug.test.ts +0 -114
- package/src/utils/debug.ts +0 -127
- package/src/utils/error.test.ts +0 -92
- package/src/utils/error.ts +0 -83
- package/src/utils/format.ts +0 -12
- package/src/validation/__tests__/field-traversal.test.ts +0 -263
- package/src/validation/deletion-checker.ts +0 -234
- package/src/validation/field-traversal.ts +0 -146
- package/src/validation/reference-validator.ts +0 -168
- package/src/worker/cms-worker-rebase.test.ts +0 -473
- package/src/worker/cms-worker.ts +0 -777
- package/src/worker/integration.test.ts +0 -289
- package/src/worker/task-queue-config.ts +0 -25
- package/src/worker/task-queue.test.ts +0 -452
- package/src/worker/task-queue.ts +0 -58
- /package/{src/cli/templates → dist/cli/template-files}/Dockerfile.cms.template +0 -0
- /package/{src/cli/templates → dist/cli/template-files}/canopycms.config.ts.template +0 -0
- /package/{src/cli/templates → dist/cli/template-files}/deploy-cms.yml.template +0 -0
- /package/{src/cli/templates → dist/cli/template-files}/edit-page.tsx.template +0 -0
- /package/{src/cli/templates → dist/cli/template-files}/route.ts.template +0 -0
- /package/{src/cli/templates → dist/cli/template-files}/schemas.ts.template +0 -0
|
@@ -1,514 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* File-based persistent task queue.
|
|
3
|
-
*
|
|
4
|
-
* Tasks are JSON files organized in subdirectories by status:
|
|
5
|
-
* pending/ — ready to be picked up
|
|
6
|
-
* processing/ — currently being executed
|
|
7
|
-
* completed/ — finished successfully
|
|
8
|
-
* failed/ — permanently failed (exhausted retries)
|
|
9
|
-
* corrupt/ — unreadable files moved here for inspection
|
|
10
|
-
*
|
|
11
|
-
* Designed for shared filesystems (EFS/NFS) where one process enqueues
|
|
12
|
-
* and another dequeues. No external dependencies — only Node.js stdlib.
|
|
13
|
-
*
|
|
14
|
-
* IMPORTANT: Single-consumer only. The dequeue operation is not atomic across
|
|
15
|
-
* processes — the worker lock (acquireLock) ensures only one consumer runs at
|
|
16
|
-
* a time. Do not run multiple dequeue consumers concurrently.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import fs from 'node:fs/promises'
|
|
20
|
-
import path from 'node:path'
|
|
21
|
-
import crypto from 'node:crypto'
|
|
22
|
-
import type { Task, TaskStatus, QueueStats, TaskQueueLogger } from './types'
|
|
23
|
-
|
|
24
|
-
const DEFAULT_MAX_RETRIES = 3
|
|
25
|
-
|
|
26
|
-
// Silent no-op logger
|
|
27
|
-
const nullLogger: TaskQueueLogger = { debug: () => {} }
|
|
28
|
-
|
|
29
|
-
// Intentionally local — the task-queue module has zero Canopy dependencies
|
|
30
|
-
// to support eventual extraction as a standalone package.
|
|
31
|
-
function isNotFoundError(err: unknown): boolean {
|
|
32
|
-
return err instanceof Error && 'code' in err && (err as NodeJS.ErrnoException).code === 'ENOENT'
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// ============================================================================
|
|
36
|
-
// Core queue operations
|
|
37
|
-
// ============================================================================
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Enqueue a task. Writes a JSON file to pending/{id}.json.
|
|
41
|
-
* Returns the generated task ID.
|
|
42
|
-
*/
|
|
43
|
-
export async function enqueueTask(
|
|
44
|
-
taskDir: string,
|
|
45
|
-
task: {
|
|
46
|
-
action: string
|
|
47
|
-
payload: Record<string, unknown>
|
|
48
|
-
maxRetries?: number
|
|
49
|
-
},
|
|
50
|
-
logger: TaskQueueLogger = nullLogger,
|
|
51
|
-
): Promise<string> {
|
|
52
|
-
const id = crypto.randomUUID()
|
|
53
|
-
const pendingDir = path.join(taskDir, 'pending')
|
|
54
|
-
await fs.mkdir(pendingDir, { recursive: true })
|
|
55
|
-
|
|
56
|
-
const queuedTask: Task = {
|
|
57
|
-
id,
|
|
58
|
-
action: task.action,
|
|
59
|
-
payload: task.payload,
|
|
60
|
-
status: 'pending',
|
|
61
|
-
createdAt: new Date().toISOString(),
|
|
62
|
-
retryCount: 0,
|
|
63
|
-
maxRetries: task.maxRetries ?? DEFAULT_MAX_RETRIES,
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const filePath = path.join(pendingDir, `${id}.json`)
|
|
67
|
-
await fs.writeFile(filePath, JSON.stringify(queuedTask, null, 2), 'utf-8')
|
|
68
|
-
logger.debug('Enqueued task', { id, action: task.action })
|
|
69
|
-
return id
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Dequeue the next pending task (oldest first).
|
|
74
|
-
* Moves the task file from pending/ to processing/.
|
|
75
|
-
* Returns null if no tasks are ready.
|
|
76
|
-
*
|
|
77
|
-
* - Skips tasks whose `retryAfter` is in the future.
|
|
78
|
-
* - Moves corrupt JSON files to corrupt/.
|
|
79
|
-
* - Skips tasks that already exist in completed/ or failed/ (crash dedup).
|
|
80
|
-
*/
|
|
81
|
-
export async function dequeueTask(
|
|
82
|
-
taskDir: string,
|
|
83
|
-
logger: TaskQueueLogger = nullLogger,
|
|
84
|
-
): Promise<Task | null> {
|
|
85
|
-
const pendingDir = path.join(taskDir, 'pending')
|
|
86
|
-
const processingDir = path.join(taskDir, 'processing')
|
|
87
|
-
|
|
88
|
-
let files: string[]
|
|
89
|
-
try {
|
|
90
|
-
files = await fs.readdir(pendingDir)
|
|
91
|
-
} catch {
|
|
92
|
-
return null
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const jsonFiles = files.filter((f) => f.endsWith('.json'))
|
|
96
|
-
if (jsonFiles.length === 0) return null
|
|
97
|
-
|
|
98
|
-
const now = Date.now()
|
|
99
|
-
|
|
100
|
-
const tasks: { fileName: string; task: Task }[] = []
|
|
101
|
-
for (const fileName of jsonFiles) {
|
|
102
|
-
try {
|
|
103
|
-
const content = await fs.readFile(path.join(pendingDir, fileName), 'utf-8')
|
|
104
|
-
const task = parseTaskJson(content)
|
|
105
|
-
if (!task) {
|
|
106
|
-
await moveToCorrupt(taskDir, pendingDir, fileName, 'Invalid JSON', logger)
|
|
107
|
-
continue
|
|
108
|
-
}
|
|
109
|
-
if (task.retryAfter && new Date(task.retryAfter).getTime() > now) {
|
|
110
|
-
continue
|
|
111
|
-
}
|
|
112
|
-
tasks.push({ fileName, task })
|
|
113
|
-
} catch (err) {
|
|
114
|
-
if (isNotFoundError(err)) continue
|
|
115
|
-
throw err
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
if (tasks.length === 0) return null
|
|
119
|
-
|
|
120
|
-
// FIFO with stable tiebreaker (task ID) to prevent indeterminate order
|
|
121
|
-
// when tasks share the same millisecond timestamp
|
|
122
|
-
tasks.sort(
|
|
123
|
-
(a, b) =>
|
|
124
|
-
a.task.createdAt.localeCompare(b.task.createdAt) || a.task.id.localeCompare(b.task.id),
|
|
125
|
-
)
|
|
126
|
-
const { fileName, task } = tasks[0]
|
|
127
|
-
|
|
128
|
-
// Dedup: if this task already finished (crash between write-result and unlink-processing),
|
|
129
|
-
// just clean up the stale pending copy
|
|
130
|
-
if (await taskExistsIn(taskDir, task.id, ['completed', 'failed'])) {
|
|
131
|
-
await fs.unlink(path.join(pendingDir, fileName)).catch(() => {})
|
|
132
|
-
logger.debug('Skipped already-finished task', { id: task.id })
|
|
133
|
-
return null
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const sourcePath = path.join(pendingDir, fileName)
|
|
137
|
-
const destPath = path.join(processingDir, fileName)
|
|
138
|
-
|
|
139
|
-
try {
|
|
140
|
-
task.status = 'processing'
|
|
141
|
-
await fs.mkdir(processingDir, { recursive: true })
|
|
142
|
-
await fs.writeFile(destPath, JSON.stringify(task, null, 2), 'utf-8')
|
|
143
|
-
await fs.unlink(sourcePath)
|
|
144
|
-
logger.debug('Dequeued task', { id: task.id, action: task.action })
|
|
145
|
-
return task
|
|
146
|
-
} catch (err) {
|
|
147
|
-
if (isNotFoundError(err)) return null
|
|
148
|
-
throw err
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Mark a task as completed. Moves from processing/ to completed/.
|
|
154
|
-
*/
|
|
155
|
-
export async function completeTask(
|
|
156
|
-
taskDir: string,
|
|
157
|
-
taskId: string,
|
|
158
|
-
result: Record<string, unknown>,
|
|
159
|
-
logger: TaskQueueLogger = nullLogger,
|
|
160
|
-
): Promise<void> {
|
|
161
|
-
const processingPath = path.join(taskDir, 'processing', `${taskId}.json`)
|
|
162
|
-
const completedDir = path.join(taskDir, 'completed')
|
|
163
|
-
const completedPath = path.join(completedDir, `${taskId}.json`)
|
|
164
|
-
|
|
165
|
-
let task: Task
|
|
166
|
-
try {
|
|
167
|
-
const content = await fs.readFile(processingPath, 'utf-8')
|
|
168
|
-
const parsed = parseTaskJson(content)
|
|
169
|
-
if (!parsed) {
|
|
170
|
-
logger.debug('Corrupt task file in processing, removing', { id: taskId })
|
|
171
|
-
await fs.unlink(processingPath).catch(() => {})
|
|
172
|
-
return
|
|
173
|
-
}
|
|
174
|
-
task = parsed
|
|
175
|
-
} catch (err) {
|
|
176
|
-
if (isNotFoundError(err)) return
|
|
177
|
-
throw err
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
task.status = 'completed'
|
|
181
|
-
task.completedAt = new Date().toISOString()
|
|
182
|
-
task.result = result
|
|
183
|
-
|
|
184
|
-
await fs.mkdir(completedDir, { recursive: true })
|
|
185
|
-
await fs.writeFile(completedPath, JSON.stringify(task, null, 2), 'utf-8')
|
|
186
|
-
await fs.unlink(processingPath).catch(() => {})
|
|
187
|
-
logger.debug('Completed task', { id: taskId })
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Mark a task as permanently failed. Moves from processing/ to failed/.
|
|
192
|
-
*/
|
|
193
|
-
export async function failTask(
|
|
194
|
-
taskDir: string,
|
|
195
|
-
taskId: string,
|
|
196
|
-
error: string,
|
|
197
|
-
logger: TaskQueueLogger = nullLogger,
|
|
198
|
-
): Promise<void> {
|
|
199
|
-
const processingPath = path.join(taskDir, 'processing', `${taskId}.json`)
|
|
200
|
-
const failedDir = path.join(taskDir, 'failed')
|
|
201
|
-
const failedPath = path.join(failedDir, `${taskId}.json`)
|
|
202
|
-
|
|
203
|
-
let task: Task
|
|
204
|
-
try {
|
|
205
|
-
const content = await fs.readFile(processingPath, 'utf-8')
|
|
206
|
-
const parsed = parseTaskJson(content)
|
|
207
|
-
if (!parsed) {
|
|
208
|
-
logger.debug('Corrupt task file in processing, removing', { id: taskId })
|
|
209
|
-
await fs.unlink(processingPath).catch(() => {})
|
|
210
|
-
return
|
|
211
|
-
}
|
|
212
|
-
task = parsed
|
|
213
|
-
} catch (err) {
|
|
214
|
-
if (isNotFoundError(err)) return
|
|
215
|
-
throw err
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
task.status = 'failed'
|
|
219
|
-
task.completedAt = new Date().toISOString()
|
|
220
|
-
task.error = error
|
|
221
|
-
|
|
222
|
-
await fs.mkdir(failedDir, { recursive: true })
|
|
223
|
-
await fs.writeFile(failedPath, JSON.stringify(task, null, 2), 'utf-8')
|
|
224
|
-
await fs.unlink(processingPath).catch(() => {})
|
|
225
|
-
logger.debug('Failed task', { id: taskId, error })
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Retry a task with exponential backoff.
|
|
230
|
-
* Moves from processing/ back to pending/ with incremented retryCount
|
|
231
|
-
* and a retryAfter timestamp. Backoff: 5s → 10s → 20s → 40s → 60s cap.
|
|
232
|
-
*/
|
|
233
|
-
export async function retryTask(
|
|
234
|
-
taskDir: string,
|
|
235
|
-
taskId: string,
|
|
236
|
-
error: string,
|
|
237
|
-
logger: TaskQueueLogger = nullLogger,
|
|
238
|
-
): Promise<void> {
|
|
239
|
-
const processingPath = path.join(taskDir, 'processing', `${taskId}.json`)
|
|
240
|
-
const pendingDir = path.join(taskDir, 'pending')
|
|
241
|
-
const pendingPath = path.join(pendingDir, `${taskId}.json`)
|
|
242
|
-
|
|
243
|
-
let task: Task
|
|
244
|
-
try {
|
|
245
|
-
const content = await fs.readFile(processingPath, 'utf-8')
|
|
246
|
-
const parsed = parseTaskJson(content)
|
|
247
|
-
if (!parsed) {
|
|
248
|
-
logger.debug('Corrupt task file in processing, removing', { id: taskId })
|
|
249
|
-
await fs.unlink(processingPath).catch(() => {})
|
|
250
|
-
return
|
|
251
|
-
}
|
|
252
|
-
task = parsed
|
|
253
|
-
} catch (err) {
|
|
254
|
-
if (isNotFoundError(err)) return
|
|
255
|
-
throw err
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
const retryCount = (task.retryCount ?? 0) + 1
|
|
259
|
-
const backoffMs = Math.min(5000 * Math.pow(2, retryCount - 1), 60_000)
|
|
260
|
-
|
|
261
|
-
task.status = 'pending'
|
|
262
|
-
task.retryCount = retryCount
|
|
263
|
-
task.retryAfter = new Date(Date.now() + backoffMs).toISOString()
|
|
264
|
-
task.error = error
|
|
265
|
-
|
|
266
|
-
await fs.mkdir(pendingDir, { recursive: true })
|
|
267
|
-
await fs.writeFile(pendingPath, JSON.stringify(task, null, 2), 'utf-8')
|
|
268
|
-
await fs.unlink(processingPath).catch(() => {})
|
|
269
|
-
logger.debug('Retrying task', { id: taskId, retryCount, backoffMs })
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// ============================================================================
|
|
273
|
-
// Recovery & maintenance
|
|
274
|
-
// ============================================================================
|
|
275
|
-
|
|
276
|
-
/**
|
|
277
|
-
* Recover orphaned tasks stuck in processing/.
|
|
278
|
-
* Moves tasks whose file mtime is older than maxAgeMs back to pending/.
|
|
279
|
-
* Call on worker startup to handle crash recovery.
|
|
280
|
-
*
|
|
281
|
-
* Skips tasks that already exist in completed/ or failed/ — these are
|
|
282
|
-
* leftovers from a crash between writing the result and unlinking the
|
|
283
|
-
* processing copy.
|
|
284
|
-
*/
|
|
285
|
-
export async function recoverOrphanedTasks(
|
|
286
|
-
taskDir: string,
|
|
287
|
-
maxAgeMs = 5 * 60_000,
|
|
288
|
-
logger: TaskQueueLogger = nullLogger,
|
|
289
|
-
): Promise<number> {
|
|
290
|
-
const processingDir = path.join(taskDir, 'processing')
|
|
291
|
-
const pendingDir = path.join(taskDir, 'pending')
|
|
292
|
-
|
|
293
|
-
let files: string[]
|
|
294
|
-
try {
|
|
295
|
-
files = await fs.readdir(processingDir)
|
|
296
|
-
} catch {
|
|
297
|
-
return 0
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
const now = Date.now()
|
|
301
|
-
let recovered = 0
|
|
302
|
-
|
|
303
|
-
for (const fileName of files.filter((f) => f.endsWith('.json'))) {
|
|
304
|
-
const filePath = path.join(processingDir, fileName)
|
|
305
|
-
try {
|
|
306
|
-
const stat = await fs.stat(filePath)
|
|
307
|
-
if (now - stat.mtimeMs >= maxAgeMs) {
|
|
308
|
-
const content = await fs.readFile(filePath, 'utf-8')
|
|
309
|
-
const task = parseTaskJson(content)
|
|
310
|
-
if (!task) {
|
|
311
|
-
await moveToCorrupt(
|
|
312
|
-
taskDir,
|
|
313
|
-
processingDir,
|
|
314
|
-
fileName,
|
|
315
|
-
'Invalid JSON during recovery',
|
|
316
|
-
logger,
|
|
317
|
-
)
|
|
318
|
-
continue
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
if (await taskExistsIn(taskDir, task.id, ['completed', 'failed'])) {
|
|
322
|
-
await fs.unlink(filePath).catch(() => {})
|
|
323
|
-
logger.debug('Cleaned up orphaned task (already finished)', {
|
|
324
|
-
id: task.id,
|
|
325
|
-
})
|
|
326
|
-
continue
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
task.status = 'pending'
|
|
330
|
-
await fs.mkdir(pendingDir, { recursive: true })
|
|
331
|
-
await fs.writeFile(path.join(pendingDir, fileName), JSON.stringify(task, null, 2), 'utf-8')
|
|
332
|
-
await fs.unlink(filePath)
|
|
333
|
-
logger.debug('Recovered orphaned task', {
|
|
334
|
-
id: task.id,
|
|
335
|
-
action: task.action,
|
|
336
|
-
})
|
|
337
|
-
recovered++
|
|
338
|
-
}
|
|
339
|
-
} catch (err) {
|
|
340
|
-
if (isNotFoundError(err)) continue
|
|
341
|
-
logger.debug('Failed to recover task', { fileName })
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
return recovered
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
/**
|
|
349
|
-
* Delete old task files from completed/ and failed/.
|
|
350
|
-
* Default retention: 30 days.
|
|
351
|
-
*/
|
|
352
|
-
export async function cleanupOldTasks(
|
|
353
|
-
taskDir: string,
|
|
354
|
-
maxAgeMs = 30 * 24 * 60 * 60_000,
|
|
355
|
-
logger: TaskQueueLogger = nullLogger,
|
|
356
|
-
): Promise<number> {
|
|
357
|
-
const now = Date.now()
|
|
358
|
-
let cleaned = 0
|
|
359
|
-
|
|
360
|
-
for (const subdir of ['completed', 'failed']) {
|
|
361
|
-
const dir = path.join(taskDir, subdir)
|
|
362
|
-
let files: string[]
|
|
363
|
-
try {
|
|
364
|
-
files = await fs.readdir(dir)
|
|
365
|
-
} catch {
|
|
366
|
-
continue
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
for (const fileName of files.filter((f) => f.endsWith('.json'))) {
|
|
370
|
-
try {
|
|
371
|
-
const filePath = path.join(dir, fileName)
|
|
372
|
-
const stat = await fs.stat(filePath)
|
|
373
|
-
if (now - stat.mtimeMs >= maxAgeMs) {
|
|
374
|
-
await fs.unlink(filePath)
|
|
375
|
-
cleaned++
|
|
376
|
-
}
|
|
377
|
-
} catch {
|
|
378
|
-
// File already gone or unreadable
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
if (cleaned > 0) {
|
|
384
|
-
logger.debug('Cleaned up old tasks', { cleaned })
|
|
385
|
-
}
|
|
386
|
-
return cleaned
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
// ============================================================================
|
|
390
|
-
// Query operations (for status UIs, monitoring)
|
|
391
|
-
// ============================================================================
|
|
392
|
-
|
|
393
|
-
/**
|
|
394
|
-
* Get a specific task by ID. Searches all status directories.
|
|
395
|
-
*/
|
|
396
|
-
export async function getTask(taskDir: string, taskId: string): Promise<Task | null> {
|
|
397
|
-
for (const subdir of ['completed', 'failed', 'processing', 'pending']) {
|
|
398
|
-
const filePath = path.join(taskDir, subdir, `${taskId}.json`)
|
|
399
|
-
try {
|
|
400
|
-
const content = await fs.readFile(filePath, 'utf-8')
|
|
401
|
-
return parseTaskJson(content)
|
|
402
|
-
} catch {
|
|
403
|
-
continue
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
return null
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
/**
|
|
410
|
-
* List tasks in a specific status directory.
|
|
411
|
-
* Returns tasks sorted by createdAt (oldest first).
|
|
412
|
-
* Use `limit` to cap the number of results.
|
|
413
|
-
*/
|
|
414
|
-
export async function listTasks(
|
|
415
|
-
taskDir: string,
|
|
416
|
-
status: TaskStatus | 'corrupt',
|
|
417
|
-
limit = 100,
|
|
418
|
-
): Promise<Task[]> {
|
|
419
|
-
const dir = path.join(taskDir, status)
|
|
420
|
-
let files: string[]
|
|
421
|
-
try {
|
|
422
|
-
files = await fs.readdir(dir)
|
|
423
|
-
} catch {
|
|
424
|
-
return []
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
const tasks: Task[] = []
|
|
428
|
-
for (const fileName of files.filter((f) => f.endsWith('.json'))) {
|
|
429
|
-
if (tasks.length >= limit) break
|
|
430
|
-
try {
|
|
431
|
-
const content = await fs.readFile(path.join(dir, fileName), 'utf-8')
|
|
432
|
-
const task = parseTaskJson(content)
|
|
433
|
-
if (task) tasks.push(task)
|
|
434
|
-
} catch {
|
|
435
|
-
continue
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
tasks.sort((a, b) => a.createdAt.localeCompare(b.createdAt))
|
|
440
|
-
return tasks
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
/**
|
|
444
|
-
* Get counts of tasks in each status directory.
|
|
445
|
-
*/
|
|
446
|
-
export async function getQueueStats(taskDir: string): Promise<QueueStats> {
|
|
447
|
-
const stats: QueueStats = {
|
|
448
|
-
pending: 0,
|
|
449
|
-
processing: 0,
|
|
450
|
-
completed: 0,
|
|
451
|
-
failed: 0,
|
|
452
|
-
corrupt: 0,
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
for (const status of ['pending', 'processing', 'completed', 'failed', 'corrupt'] as const) {
|
|
456
|
-
const dir = path.join(taskDir, status)
|
|
457
|
-
try {
|
|
458
|
-
const files = await fs.readdir(dir)
|
|
459
|
-
stats[status] = files.filter((f) => f.endsWith('.json')).length
|
|
460
|
-
} catch {
|
|
461
|
-
// Directory doesn't exist — count stays 0
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
return stats
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
// ============================================================================
|
|
469
|
-
// Internal helpers
|
|
470
|
-
// ============================================================================
|
|
471
|
-
|
|
472
|
-
/** Parse JSON into a Task, returning null if invalid. */
|
|
473
|
-
function parseTaskJson(content: string): Task | null {
|
|
474
|
-
try {
|
|
475
|
-
const parsed = JSON.parse(content) as Task
|
|
476
|
-
if (typeof parsed.id !== 'string' || typeof parsed.action !== 'string') {
|
|
477
|
-
return null
|
|
478
|
-
}
|
|
479
|
-
return parsed
|
|
480
|
-
} catch {
|
|
481
|
-
return null
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
/** Check if a task file exists in any of the specified subdirectories. */
|
|
486
|
-
async function taskExistsIn(taskDir: string, taskId: string, subdirs: string[]): Promise<boolean> {
|
|
487
|
-
for (const subdir of subdirs) {
|
|
488
|
-
try {
|
|
489
|
-
await fs.stat(path.join(taskDir, subdir, `${taskId}.json`))
|
|
490
|
-
return true
|
|
491
|
-
} catch {
|
|
492
|
-
// Not in this subdir
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
return false
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
/** Move a corrupt file to corrupt/ for inspection. */
|
|
499
|
-
async function moveToCorrupt(
|
|
500
|
-
taskDir: string,
|
|
501
|
-
sourceDir: string,
|
|
502
|
-
fileName: string,
|
|
503
|
-
reason: string,
|
|
504
|
-
logger: TaskQueueLogger,
|
|
505
|
-
): Promise<void> {
|
|
506
|
-
const corruptDir = path.join(taskDir, 'corrupt')
|
|
507
|
-
try {
|
|
508
|
-
await fs.mkdir(corruptDir, { recursive: true })
|
|
509
|
-
await fs.rename(path.join(sourceDir, fileName), path.join(corruptDir, fileName))
|
|
510
|
-
logger.debug('Moved corrupt task file', { fileName, reason })
|
|
511
|
-
} catch {
|
|
512
|
-
await fs.unlink(path.join(sourceDir, fileName)).catch(() => {})
|
|
513
|
-
}
|
|
514
|
-
}
|
package/src/task-queue/types.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A task in the file-based queue.
|
|
3
|
-
*
|
|
4
|
-
* The `action` field is an arbitrary string — the queue does not interpret it.
|
|
5
|
-
* Consumers define their own action vocabularies.
|
|
6
|
-
*/
|
|
7
|
-
export interface Task {
|
|
8
|
-
id: string
|
|
9
|
-
action: string
|
|
10
|
-
payload: Record<string, unknown>
|
|
11
|
-
status: TaskStatus
|
|
12
|
-
createdAt: string
|
|
13
|
-
completedAt?: string
|
|
14
|
-
result?: Record<string, unknown>
|
|
15
|
-
error?: string
|
|
16
|
-
/** Number of times this task has been retried (default: 0) */
|
|
17
|
-
retryCount?: number
|
|
18
|
-
/** Maximum retries before permanent failure (default: 3) */
|
|
19
|
-
maxRetries?: number
|
|
20
|
-
/** ISO timestamp — task should not be dequeued before this time */
|
|
21
|
-
retryAfter?: string
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export type TaskStatus = 'pending' | 'processing' | 'completed' | 'failed'
|
|
25
|
-
|
|
26
|
-
/** Summary counts for each task status. */
|
|
27
|
-
export interface QueueStats {
|
|
28
|
-
pending: number
|
|
29
|
-
processing: number
|
|
30
|
-
completed: number
|
|
31
|
-
failed: number
|
|
32
|
-
corrupt: number
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Optional logger interface.
|
|
37
|
-
* Pass your own logger to `createTaskQueue()`, or omit for silent operation.
|
|
38
|
-
*/
|
|
39
|
-
export interface TaskQueueLogger {
|
|
40
|
-
debug(message: string, data?: Record<string, unknown>): void
|
|
41
|
-
}
|