@tailor-platform/erp-kit 0.1.1 → 0.2.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 +12 -0
- package/README.md +158 -62
- package/dist/cli.js +1010 -270
- package/package.json +11 -8
- package/schemas/module/command.yml +1 -0
- package/schemas/module/model.yml +14 -0
- package/schemas/module/query.yml +53 -0
- package/skills/{app-compose-1-requirement-analysis → erp-kit-app-1-requirements}/SKILL.md +2 -2
- package/skills/{app-compose-2-requirements-breakdown → erp-kit-app-2-breakdown}/SKILL.md +3 -3
- package/skills/{app-compose-3-doc-review → erp-kit-app-3-doc-review}/SKILL.md +2 -2
- package/skills/{app-compose-4-design-mock → erp-kit-app-4-design}/SKILL.md +3 -3
- package/skills/{app-compose-5-design-mock-review → erp-kit-app-5-design-review}/SKILL.md +4 -4
- package/skills/{app-compose-6-implementation-spec → erp-kit-app-6-impl-spec}/SKILL.md +3 -3
- package/skills/{mock-scenario → erp-kit-mock-scenario}/SKILL.md +1 -1
- package/skills/{1-module-docs → erp-kit-module-1-docs}/SKILL.md +2 -2
- package/skills/{2-module-feature-breakdown → erp-kit-module-2-feature-breakdown}/SKILL.md +13 -9
- package/skills/erp-kit-module-2-feature-breakdown/references/naming.md +59 -0
- package/skills/{3-module-doc-review → erp-kit-module-3-doc-review}/SKILL.md +83 -25
- package/skills/erp-kit-module-4-tdd/SKILL.md +94 -0
- package/skills/erp-kit-module-4-tdd/references/cross-module-dependency.md +133 -0
- package/skills/{4-module-tdd-implementation → erp-kit-module-4-tdd}/references/db-relations.md +5 -1
- package/skills/{4-module-tdd-implementation → erp-kit-module-4-tdd}/references/exports.md +1 -1
- package/skills/erp-kit-module-4-tdd/references/generated-code.md +32 -0
- package/skills/{5-module-implementation-review → erp-kit-module-5-impl-review}/SKILL.md +46 -44
- package/skills/erp-kit-module-5-impl-review/references/commands.md +62 -0
- package/skills/erp-kit-module-5-impl-review/references/errors.md +10 -0
- package/skills/{5-module-implementation-review → erp-kit-module-5-impl-review}/references/testing.md +1 -1
- package/skills/erp-kit-module-shared/SKILL.md +16 -0
- package/skills/erp-kit-module-shared/references/commands.md +203 -0
- package/skills/erp-kit-module-shared/references/errors.md +35 -0
- package/skills/erp-kit-module-shared/references/queries.md +168 -0
- package/skills/erp-kit-module-shared/references/structure.md +36 -0
- package/skills/{3-module-doc-review → erp-kit-module-shared}/references/testing.md +4 -3
- package/skills/erp-kit-update/SKILL.md +64 -0
- package/src/cli.doc.test.ts +65 -0
- package/src/cli.ts +3 -117
- package/src/commands/app/index.ts +74 -0
- package/src/commands/check.test.ts +3 -2
- package/src/commands/check.ts +3 -2
- package/src/commands/index.ts +73 -0
- package/src/commands/init.test.ts +22 -5
- package/src/commands/init.ts +25 -16
- package/src/commands/license.ts +193 -0
- package/src/commands/mock/index.ts +2 -2
- package/src/commands/mock/start.ts +1 -1
- package/src/commands/mock/validate.test.ts +1 -1
- package/src/commands/module/generate.ts +35 -0
- package/src/commands/module/index.ts +87 -0
- package/src/commands/module/list.test.ts +57 -0
- package/src/commands/module/list.ts +64 -0
- package/src/commands/scaffold-templates.ts +65 -0
- package/src/commands/scaffold.test.ts +97 -2
- package/src/commands/scaffold.ts +24 -3
- package/src/commands/sync-check.test.ts +88 -1
- package/src/commands/sync-check.ts +41 -2
- package/src/generator/generate-code.test.ts +200 -0
- package/src/generator/generate-code.ts +260 -0
- package/src/generator/parse-command-doc.test.ts +159 -0
- package/src/generator/parse-command-doc.ts +116 -0
- package/src/integration.test.ts +6 -8
- package/src/module.ts +10 -9
- package/src/modules/item-management/README.md +38 -0
- package/src/modules/item-management/command/activateItem.generated.ts +6 -0
- package/src/modules/item-management/command/activateItem.test.ts +76 -0
- package/src/modules/item-management/command/activateItem.ts +42 -0
- package/src/modules/item-management/command/assignItemToTaxonomy.generated.ts +6 -0
- package/src/modules/item-management/command/assignItemToTaxonomy.test.ts +88 -0
- package/src/modules/item-management/command/assignItemToTaxonomy.ts +63 -0
- package/src/modules/item-management/command/createItem.generated.ts +6 -0
- package/src/modules/item-management/command/createItem.test.ts +152 -0
- package/src/modules/item-management/command/createItem.ts +72 -0
- package/src/modules/item-management/command/createTaxonomyNode.generated.ts +6 -0
- package/src/modules/item-management/command/createTaxonomyNode.test.ts +126 -0
- package/src/modules/item-management/command/createTaxonomyNode.ts +70 -0
- package/src/modules/item-management/command/deactivateItem.generated.ts +6 -0
- package/src/modules/item-management/command/deactivateItem.test.ts +76 -0
- package/src/modules/item-management/command/deactivateItem.ts +42 -0
- package/src/modules/item-management/command/deleteItem.generated.ts +6 -0
- package/src/modules/item-management/command/deleteItem.test.ts +61 -0
- package/src/modules/item-management/command/deleteItem.ts +38 -0
- package/src/modules/item-management/command/deleteTaxonomyNode.generated.ts +6 -0
- package/src/modules/item-management/command/deleteTaxonomyNode.test.ts +73 -0
- package/src/modules/item-management/command/deleteTaxonomyNode.ts +50 -0
- package/src/modules/item-management/command/moveTaxonomyNode.generated.ts +6 -0
- package/src/modules/item-management/command/moveTaxonomyNode.test.ts +136 -0
- package/src/modules/item-management/command/moveTaxonomyNode.ts +85 -0
- package/src/modules/item-management/command/reactivateItem.generated.ts +6 -0
- package/src/modules/item-management/command/reactivateItem.test.ts +76 -0
- package/src/modules/item-management/command/reactivateItem.ts +42 -0
- package/src/modules/item-management/command/removeItemFromTaxonomy.generated.ts +6 -0
- package/src/modules/item-management/command/removeItemFromTaxonomy.test.ts +43 -0
- package/src/modules/item-management/command/removeItemFromTaxonomy.ts +30 -0
- package/src/modules/item-management/command/updateItem.generated.ts +6 -0
- package/src/modules/item-management/command/updateItem.test.ts +178 -0
- package/src/modules/item-management/command/updateItem.ts +103 -0
- package/src/modules/item-management/command/updateTaxonomyNode.generated.ts +6 -0
- package/src/modules/item-management/command/updateTaxonomyNode.test.ts +88 -0
- package/src/modules/item-management/command/updateTaxonomyNode.ts +62 -0
- package/src/modules/item-management/db/item.ts +47 -0
- package/src/modules/item-management/db/itemTaxonomyAssignment.ts +49 -0
- package/src/modules/item-management/db/taxonomyNode.ts +34 -0
- package/src/modules/item-management/docs/commands/ActivateItem.md +32 -0
- package/src/modules/item-management/docs/commands/AssignItemToTaxonomy.md +38 -0
- package/src/modules/item-management/docs/commands/CreateItem.md +44 -0
- package/src/modules/item-management/docs/commands/CreateTaxonomyNode.md +44 -0
- package/src/modules/item-management/docs/commands/DeactivateItem.md +34 -0
- package/src/modules/item-management/docs/commands/DeleteItem.md +35 -0
- package/src/modules/item-management/docs/commands/DeleteTaxonomyNode.md +39 -0
- package/src/modules/item-management/docs/commands/MoveTaxonomyNode.md +45 -0
- package/src/modules/item-management/docs/commands/ReactivateItem.md +34 -0
- package/src/modules/item-management/docs/commands/RemoveItemFromTaxonomy.md +30 -0
- package/src/modules/item-management/docs/commands/UpdateItem.md +55 -0
- package/src/modules/item-management/docs/commands/UpdateTaxonomyNode.md +36 -0
- package/src/modules/item-management/docs/features/item-lifecycle.md +60 -0
- package/src/modules/item-management/docs/features/item-taxonomy.md +65 -0
- package/src/modules/item-management/docs/models/ItemTaxonomyAssignment.md +36 -0
- package/src/modules/item-management/docs/models/TaxonomyNode.md +47 -0
- package/src/modules/item-management/docs/models/item.md +59 -0
- package/src/modules/item-management/docs/queries/CalculateNodeDepth.md +36 -0
- package/src/modules/item-management/docs/queries/CalculateSubtreeDepth.md +40 -0
- package/src/modules/item-management/docs/queries/DetectCircularReference.md +41 -0
- package/src/modules/item-management/docs/queries/GetItem.md +38 -0
- package/src/modules/item-management/docs/queries/GetItemTaxonomyAssignment.md +29 -0
- package/src/modules/item-management/docs/queries/GetTaxonomyNode.md +35 -0
- package/src/modules/item-management/docs/queries/GetTaxonomyNodeAssignments.md +29 -0
- package/src/modules/item-management/docs/queries/GetTaxonomyNodeChildren.md +29 -0
- package/src/modules/item-management/generated/enums.ts +9 -0
- package/src/modules/item-management/generated/kysely-tailordb.ts +62 -0
- package/src/modules/item-management/index.ts +53 -0
- package/src/modules/item-management/lib/_db_deps.ts +13 -0
- package/src/modules/item-management/lib/errors.generated.ts +117 -0
- package/src/modules/item-management/lib/permissions.generated.ts +17 -0
- package/src/modules/item-management/lib/types.ts +19 -0
- package/src/modules/item-management/module.ts +97 -0
- package/src/modules/item-management/query/calculateNodeDepth.generated.ts +5 -0
- package/src/modules/item-management/query/calculateNodeDepth.test.ts +56 -0
- package/src/modules/item-management/query/calculateNodeDepth.ts +28 -0
- package/src/modules/item-management/query/calculateSubtreeDepth.generated.ts +5 -0
- package/src/modules/item-management/query/calculateSubtreeDepth.test.ts +75 -0
- package/src/modules/item-management/query/calculateSubtreeDepth.ts +29 -0
- package/src/modules/item-management/query/detectCircularReference.generated.ts +5 -0
- package/src/modules/item-management/query/detectCircularReference.test.ts +61 -0
- package/src/modules/item-management/query/detectCircularReference.ts +32 -0
- package/src/modules/item-management/query/getItem.generated.ts +5 -0
- package/src/modules/item-management/query/getItem.test.ts +67 -0
- package/src/modules/item-management/query/getItem.ts +20 -0
- package/src/modules/item-management/query/getItemTaxonomyAssignment.generated.ts +5 -0
- package/src/modules/item-management/query/getItemTaxonomyAssignment.test.ts +25 -0
- package/src/modules/item-management/query/getItemTaxonomyAssignment.ts +18 -0
- package/src/modules/item-management/query/getTaxonomyNode.generated.ts +5 -0
- package/src/modules/item-management/query/getTaxonomyNode.test.ts +47 -0
- package/src/modules/item-management/query/getTaxonomyNode.ts +18 -0
- package/src/modules/item-management/query/getTaxonomyNodeAssignments.generated.ts +5 -0
- package/src/modules/item-management/query/getTaxonomyNodeAssignments.test.ts +25 -0
- package/src/modules/item-management/query/getTaxonomyNodeAssignments.ts +16 -0
- package/src/modules/item-management/query/getTaxonomyNodeChildren.generated.ts +5 -0
- package/src/modules/item-management/query/getTaxonomyNodeChildren.test.ts +34 -0
- package/src/modules/item-management/query/getTaxonomyNodeChildren.ts +16 -0
- package/src/modules/item-management/tailor.config.ts +11 -0
- package/src/modules/item-management/testing/fixtures.ts +81 -0
- package/src/modules/primitives/command/activateCategory.generated.ts +6 -0
- package/src/modules/primitives/command/activateCategory.test.ts +11 -29
- package/src/modules/primitives/command/activateCategory.ts +27 -34
- package/src/modules/primitives/command/activateCurrency.generated.ts +6 -0
- package/src/modules/primitives/command/activateCurrency.test.ts +11 -29
- package/src/modules/primitives/command/activateCurrency.ts +27 -34
- package/src/modules/primitives/command/activateUnit.generated.ts +6 -0
- package/src/modules/primitives/command/activateUnit.test.ts +11 -15
- package/src/modules/primitives/command/activateUnit.ts +27 -34
- package/src/modules/primitives/command/createCategory.generated.ts +6 -0
- package/src/modules/primitives/command/createCategory.test.ts +27 -39
- package/src/modules/primitives/command/createCategory.ts +53 -62
- package/src/modules/primitives/command/createCurrency.generated.ts +6 -0
- package/src/modules/primitives/command/createCurrency.test.ts +78 -71
- package/src/modules/primitives/command/createCurrency.ts +43 -48
- package/src/modules/primitives/command/createExchangeRate.generated.ts +6 -0
- package/src/modules/primitives/command/createExchangeRate.test.ts +101 -100
- package/src/modules/primitives/command/createExchangeRate.ts +50 -59
- package/src/modules/primitives/command/createUnit.generated.ts +6 -0
- package/src/modules/primitives/command/createUnit.test.ts +92 -95
- package/src/modules/primitives/command/createUnit.ts +54 -57
- package/src/modules/primitives/command/deactivateCategory.generated.ts +6 -0
- package/src/modules/primitives/command/deactivateCategory.test.ts +27 -28
- package/src/modules/primitives/command/deactivateCategory.ts +43 -50
- package/src/modules/primitives/command/deactivateCurrency.generated.ts +6 -0
- package/src/modules/primitives/command/deactivateCurrency.test.ts +23 -38
- package/src/modules/primitives/command/deactivateCurrency.ts +31 -38
- package/src/modules/primitives/command/deactivateUnit.generated.ts +6 -0
- package/src/modules/primitives/command/deactivateUnit.test.ts +27 -23
- package/src/modules/primitives/command/deactivateUnit.ts +39 -49
- package/src/modules/primitives/command/setBaseCurrency.generated.ts +6 -0
- package/src/modules/primitives/command/setBaseCurrency.test.ts +40 -33
- package/src/modules/primitives/command/setBaseCurrency.ts +43 -50
- package/src/modules/primitives/command/setReferenceUnit.generated.ts +6 -0
- package/src/modules/primitives/command/setReferenceUnit.test.ts +39 -35
- package/src/modules/primitives/command/setReferenceUnit.ts +46 -59
- package/src/modules/primitives/db/unit.ts +13 -3
- package/src/modules/primitives/docs/commands/ActivateCategory.md +1 -2
- package/src/modules/primitives/docs/commands/ActivateCurrency.md +1 -2
- package/src/modules/primitives/docs/commands/ActivateUnit.md +1 -2
- package/src/modules/primitives/docs/commands/CreateCategory.md +1 -4
- package/src/modules/primitives/docs/commands/CreateCurrency.md +3 -4
- package/src/modules/primitives/docs/commands/CreateExchangeRate.md +4 -5
- package/src/modules/primitives/docs/commands/CreateUnit.md +5 -5
- package/src/modules/primitives/docs/commands/DeactivateCategory.md +2 -3
- package/src/modules/primitives/docs/commands/DeactivateCurrency.md +2 -3
- package/src/modules/primitives/docs/commands/DeactivateUnit.md +2 -3
- package/src/modules/primitives/docs/commands/SetBaseCurrency.md +2 -3
- package/src/modules/primitives/docs/commands/SetReferenceUnit.md +2 -3
- package/src/modules/primitives/docs/models/Currency.md +4 -0
- package/src/modules/primitives/docs/models/ExchangeRate.md +4 -1
- package/src/modules/primitives/docs/models/Unit.md +4 -1
- package/src/modules/primitives/docs/models/UoMCategory.md +2 -0
- package/src/modules/primitives/docs/{commands → queries}/ConvertAmount.md +3 -5
- package/src/modules/primitives/docs/{commands → queries}/ConvertQuantity.md +3 -5
- package/src/modules/primitives/docs/queries/GetBaseCurrency.md +32 -0
- package/src/modules/primitives/docs/queries/GetCurrency.md +36 -0
- package/src/modules/primitives/docs/queries/GetUnit.md +36 -0
- package/src/modules/primitives/docs/queries/GetUoMCategory.md +36 -0
- package/src/modules/primitives/docs/queries/ListUnitsByCategory.md +26 -0
- package/src/modules/primitives/generated/kysely-tailordb.ts +24 -45
- package/src/modules/primitives/index.ts +17 -6
- package/src/modules/primitives/lib/errors.generated.ts +112 -0
- package/src/modules/primitives/{permissions.ts → lib/permissions.generated.ts} +9 -10
- package/src/modules/primitives/module.ts +39 -27
- package/src/modules/primitives/query/convertAmount.generated.ts +5 -0
- package/src/modules/primitives/{command → query}/convertAmount.test.ts +4 -21
- package/src/modules/primitives/query/convertAmount.ts +121 -0
- package/src/modules/primitives/query/convertQuantity.generated.ts +5 -0
- package/src/modules/primitives/{command → query}/convertQuantity.test.ts +8 -15
- package/src/modules/primitives/query/convertQuantity.ts +63 -0
- package/src/modules/primitives/query/getBaseCurrency.generated.ts +5 -0
- package/src/modules/primitives/query/getBaseCurrency.test.ts +28 -0
- package/src/modules/primitives/query/getBaseCurrency.ts +16 -0
- package/src/modules/primitives/query/getCurrency.generated.ts +5 -0
- package/src/modules/primitives/query/getCurrency.test.ts +47 -0
- package/src/modules/primitives/query/getCurrency.ts +18 -0
- package/src/modules/primitives/query/getUnit.generated.ts +5 -0
- package/src/modules/primitives/query/getUnit.test.ts +47 -0
- package/src/modules/primitives/query/getUnit.ts +18 -0
- package/src/modules/primitives/query/getUoMCategory.generated.ts +5 -0
- package/src/modules/primitives/query/getUoMCategory.test.ts +47 -0
- package/src/modules/primitives/query/getUoMCategory.ts +18 -0
- package/src/modules/primitives/query/listUnitsByCategory.generated.ts +5 -0
- package/src/modules/primitives/query/listUnitsByCategory.ts +16 -0
- package/src/modules/primitives/tailor.config.ts +3 -3
- package/src/modules/shared/defineCommand.test.ts +23 -10
- package/src/modules/shared/defineCommand.ts +23 -10
- package/src/modules/shared/defineQuery.test.ts +28 -0
- package/src/modules/shared/defineQuery.ts +16 -0
- package/src/modules/shared/internal.ts +3 -1
- package/src/modules/shared/requirePermission.test.ts +22 -21
- package/src/modules/shared/requirePermission.ts +8 -2
- package/src/modules/shared/result.ts +12 -0
- package/src/modules/shared/types.ts +8 -0
- package/src/modules/testing/index.ts +36 -11
- package/src/modules/user-management/command/activateUser.generated.ts +6 -0
- package/src/modules/user-management/command/activateUser.test.ts +27 -27
- package/src/modules/user-management/command/activateUser.ts +40 -48
- package/src/modules/user-management/command/assignPermissionToRole.generated.ts +6 -0
- package/src/modules/user-management/command/assignPermissionToRole.test.ts +42 -43
- package/src/modules/user-management/command/assignPermissionToRole.ts +59 -62
- package/src/modules/user-management/command/assignRoleToUser.generated.ts +6 -0
- package/src/modules/user-management/command/assignRoleToUser.test.ts +70 -63
- package/src/modules/user-management/command/assignRoleToUser.ts +63 -66
- package/src/modules/user-management/command/createPermission.generated.ts +6 -0
- package/src/modules/user-management/command/createPermission.test.ts +45 -38
- package/src/modules/user-management/command/createPermission.ts +42 -46
- package/src/modules/user-management/command/createRole.generated.ts +6 -0
- package/src/modules/user-management/command/createRole.test.ts +30 -29
- package/src/modules/user-management/command/createRole.ts +33 -33
- package/src/modules/user-management/command/createUser.generated.ts +6 -0
- package/src/modules/user-management/command/createUser.test.ts +64 -42
- package/src/modules/user-management/command/createUser.ts +54 -56
- package/src/modules/user-management/command/deactivateUser.generated.ts +6 -0
- package/src/modules/user-management/command/deactivateUser.test.ts +27 -27
- package/src/modules/user-management/command/deactivateUser.ts +40 -48
- package/src/modules/user-management/command/logAuditEvent.generated.ts +6 -0
- package/src/modules/user-management/command/logAuditEvent.test.ts +50 -42
- package/src/modules/user-management/command/logAuditEvent.ts +25 -28
- package/src/modules/user-management/command/reactivateUser.generated.ts +6 -0
- package/src/modules/user-management/command/reactivateUser.test.ts +31 -27
- package/src/modules/user-management/command/reactivateUser.ts +40 -48
- package/src/modules/user-management/command/revokePermissionFromRole.generated.ts +6 -0
- package/src/modules/user-management/command/revokePermissionFromRole.test.ts +52 -51
- package/src/modules/user-management/command/revokePermissionFromRole.ts +60 -57
- package/src/modules/user-management/command/revokeRoleFromUser.generated.ts +6 -0
- package/src/modules/user-management/command/revokeRoleFromUser.test.ts +53 -48
- package/src/modules/user-management/command/revokeRoleFromUser.ts +58 -57
- package/src/modules/user-management/docs/commands/CreatePermission.md +2 -2
- package/src/modules/user-management/docs/commands/CreateRole.md +1 -1
- package/src/modules/user-management/docs/models/AuditEvent.md +2 -0
- package/src/modules/user-management/docs/models/Permission.md +2 -0
- package/src/modules/user-management/docs/models/Role.md +2 -0
- package/src/modules/user-management/docs/models/RolePermission.md +2 -0
- package/src/modules/user-management/docs/models/User.md +2 -0
- package/src/modules/user-management/docs/models/UserRole.md +2 -0
- package/src/modules/user-management/generated/enums.ts +11 -11
- package/src/modules/user-management/generated/kysely-tailordb.ts +27 -56
- package/src/modules/user-management/index.ts +2 -2
- package/src/modules/user-management/lib/errors.generated.ts +67 -0
- package/src/modules/user-management/{permissions.ts → lib/permissions.generated.ts} +8 -7
- package/src/modules/user-management/module.ts +22 -22
- package/src/modules/user-management/tailor.config.ts +3 -3
- package/src/schemas.ts +2 -1
- package/skills/1-module-docs/references/structure.md +0 -22
- package/skills/2-module-feature-breakdown/references/commands.md +0 -48
- package/skills/2-module-feature-breakdown/references/structure.md +0 -22
- package/skills/3-module-doc-review/references/commands.md +0 -54
- package/skills/3-module-doc-review/references/models.md +0 -29
- package/skills/4-module-tdd-implementation/SKILL.md +0 -74
- package/skills/4-module-tdd-implementation/references/commands.md +0 -45
- package/skills/4-module-tdd-implementation/references/errors.md +0 -7
- package/skills/4-module-tdd-implementation/references/models.md +0 -30
- package/skills/4-module-tdd-implementation/references/structure.md +0 -22
- package/skills/4-module-tdd-implementation/references/testing.md +0 -37
- package/skills/5-module-implementation-review/references/commands.md +0 -45
- package/skills/5-module-implementation-review/references/errors.md +0 -7
- package/skills/5-module-implementation-review/references/exports.md +0 -8
- package/skills/5-module-implementation-review/references/models.md +0 -30
- package/src/modules/primitives/command/convertAmount.ts +0 -126
- package/src/modules/primitives/command/convertQuantity.ts +0 -73
- package/src/modules/primitives/lib/errors.ts +0 -138
- package/src/modules/user-management/lib/errors.ts +0 -81
- /package/skills/{app-compose-1-requirement-analysis → erp-kit-app-1-requirements}/references/structure.md +0 -0
- /package/skills/{app-compose-2-requirements-breakdown → erp-kit-app-2-breakdown}/references/screen-detailview.md +0 -0
- /package/skills/{app-compose-2-requirements-breakdown → erp-kit-app-2-breakdown}/references/screen-form.md +0 -0
- /package/skills/{app-compose-2-requirements-breakdown → erp-kit-app-2-breakdown}/references/screen-listview.md +0 -0
- /package/skills/{app-compose-2-requirements-breakdown → erp-kit-app-2-breakdown}/references/structure.md +0 -0
- /package/skills/{app-compose-3-doc-review → erp-kit-app-3-doc-review}/references/structure.md +0 -0
- /package/skills/{app-compose-4-design-mock → erp-kit-app-4-design}/references/component.md +0 -0
- /package/skills/{app-compose-4-design-mock → erp-kit-app-4-design}/references/screen-detailview.md +0 -0
- /package/skills/{app-compose-4-design-mock → erp-kit-app-4-design}/references/screen-form.md +0 -0
- /package/skills/{app-compose-4-design-mock → erp-kit-app-4-design}/references/screen-listview.md +0 -0
- /package/skills/{app-compose-4-design-mock → erp-kit-app-4-design}/references/structure.md +0 -0
- /package/skills/{app-compose-5-design-mock-review → erp-kit-app-5-design-review}/references/component.md +0 -0
- /package/skills/{app-compose-5-design-mock-review → erp-kit-app-5-design-review}/references/screen-detailview.md +0 -0
- /package/skills/{app-compose-5-design-mock-review → erp-kit-app-5-design-review}/references/screen-form.md +0 -0
- /package/skills/{app-compose-5-design-mock-review → erp-kit-app-5-design-review}/references/screen-listview.md +0 -0
- /package/skills/{app-compose-6-implementation-spec → erp-kit-app-6-impl-spec}/references/auth.md +0 -0
- /package/skills/{app-compose-6-implementation-spec → erp-kit-app-6-impl-spec}/references/structure.md +0 -0
- /package/skills/{2-module-feature-breakdown → erp-kit-module-4-tdd}/references/models.md +0 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# Command Implementation
|
|
2
|
+
|
|
3
|
+
## Unified Pattern: `run` + generated shell
|
|
4
|
+
|
|
5
|
+
All commands follow the same pattern — export a `run` function, and the generated shell wraps it with `defineCommand`:
|
|
6
|
+
|
|
7
|
+
**Implementation file** (`command/myCommand.ts`):
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
export async function run(db: DB, input: MyCommandInput, ctx: CommandContext) {
|
|
11
|
+
// validate → query → mutate
|
|
12
|
+
return ok({ entity });
|
|
13
|
+
}
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
**Generated shell** (`command/myCommand.generated.ts`):
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
export const myCommand = defineCommand(permissions.myCommand, run);
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Custom Fields (generic CF)
|
|
23
|
+
|
|
24
|
+
Commands that **write** (insert or update) into a table with user-extensible fields make `run` generic:
|
|
25
|
+
|
|
26
|
+
- Generic `CF extends Record<string, unknown>` on the `run` function
|
|
27
|
+
- Input type: `CreateXInput & CF` or `UpdateXInput & Partial<CF>`
|
|
28
|
+
- Destructure known fields, rest-spread custom fields
|
|
29
|
+
- Cast custom fields: `...(customFields as Record<string, unknown>)`
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
export async function run<CF extends Record<string, unknown>>(
|
|
33
|
+
db: DB,
|
|
34
|
+
input: CreateXInput & CF,
|
|
35
|
+
ctx: CommandContext,
|
|
36
|
+
) {
|
|
37
|
+
const { name, ...customFields } = input;
|
|
38
|
+
await db
|
|
39
|
+
.insertInto("X")
|
|
40
|
+
.values({ ...(customFields as Record<string, unknown>), name })
|
|
41
|
+
.execute();
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### module.ts wiring
|
|
46
|
+
|
|
47
|
+
Use instantiation expressions to lock `CF` to the module's custom field type:
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
const createXTyped = createX<FieldsToInsertable<F>>;
|
|
51
|
+
return { commands: { createX: createXTyped } };
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Rule: when to use generic CF
|
|
55
|
+
|
|
56
|
+
> If a model uses `fields?: F` for custom fields, **every command that writes those fields** must have a generic `CF`. This includes create, update, and any other write command. The determining factor is whether the command writes to a table with custom fields, not just whether it inserts new rows.
|
|
57
|
+
|
|
58
|
+
## Config-Taking Commands
|
|
59
|
+
|
|
60
|
+
Commands that need external config (cross-module queries, settings) accept config as leading params before `(db, input, ctx)`:
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
export async function run<CF extends Record<string, unknown>>(
|
|
64
|
+
primitivesQueries: Pick<PrimitivesQueries, "getUnit">,
|
|
65
|
+
db: DB, input: CreateItemInput & CF, ctx: CommandContext,
|
|
66
|
+
) { ... }
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
module.ts wires with `.bind()` after instantiation:
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
const createItemTyped = createItem<FieldsToInsertable<IF>>;
|
|
73
|
+
return { commands: { createItem: createItemTyped.bind(null, queries) } };
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Command-Side Reads (CQRS Separation)
|
|
77
|
+
|
|
78
|
+
Following CQRS principles, commands own their reads for same-module data. Cross-module reads use injected query functions to preserve module boundaries.
|
|
79
|
+
|
|
80
|
+
### Why
|
|
81
|
+
|
|
82
|
+
- **Query functions** use `ReadonlyDB` — shaped for API consumers, can't `forUpdate()`
|
|
83
|
+
- **Command reads** are shaped for enforcing invariants — need locking, may need different filters or joins
|
|
84
|
+
- **Independence** — query-side changes (pagination, field selection) must not affect command behavior
|
|
85
|
+
- **Module composition** — modules nest at multiple depths; parent modules inject child queries to maintain explicit dependency contracts
|
|
86
|
+
|
|
87
|
+
### Rule
|
|
88
|
+
|
|
89
|
+
> **Same-module reads**: Commands inline their own `selectFrom()` calls using `db` (full `DB`). Never import or call query functions from `query/` within the same module — the command owns its reads and can apply locking.
|
|
90
|
+
>
|
|
91
|
+
> **Cross-module reads**: Commands receive query functions from other modules via dependency injection (leading params). This keeps module boundaries explicit and supports nested module composition.
|
|
92
|
+
>
|
|
93
|
+
> **Status preconditions**: Commands enforce status checks inline (e.g., "only DRAFT items can be deleted", "only ACTIVE users can be assigned roles"). Status filtering belongs to the command's invariant logic, not to query-side defaults. See [Status-Aware Query Rules](queries.md#status-aware-query-rules) for the read-side conventions.
|
|
94
|
+
|
|
95
|
+
### Examples
|
|
96
|
+
|
|
97
|
+
**Correct** — same-module read inlined with locking:
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
// command/deactivateUnit.ts
|
|
101
|
+
const unit = await db
|
|
102
|
+
.selectFrom("Unit")
|
|
103
|
+
.selectAll()
|
|
104
|
+
.where("id", "=", input.unitId)
|
|
105
|
+
.forUpdate()
|
|
106
|
+
.executeTakeFirst();
|
|
107
|
+
if (!unit) return err(new UnitNotFoundError(input.unitId));
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Correct** — cross-module read via injected query:
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
// command/createItem.ts (item-management validating a primitives entity)
|
|
114
|
+
export async function run<CF extends Record<string, unknown>>(
|
|
115
|
+
primitivesQueries: Pick<PrimitivesQueries, "getUnit">,
|
|
116
|
+
db: DB,
|
|
117
|
+
input: CreateItemInput & CF,
|
|
118
|
+
ctx: CommandContext,
|
|
119
|
+
) {
|
|
120
|
+
const { unit } = await primitivesQueries.getUnit(db, { id: input.unitId }, ctx);
|
|
121
|
+
if (!unit?.isActive) return err(new UnitNotFoundError(input.unitId));
|
|
122
|
+
// ...
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Wrong** — calling same-module query function:
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
// command/deactivateUnit.ts
|
|
130
|
+
import { getUnit } from "../query/getUnit"; // ← don't do this
|
|
131
|
+
const unit = await getUnit(db, { id: input.unitId }, ctx);
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Select Locking
|
|
135
|
+
|
|
136
|
+
Commands that read then write must lock rows with `forUpdate()` to prevent concurrent requests from causing race conditions.
|
|
137
|
+
|
|
138
|
+
### When to lock
|
|
139
|
+
|
|
140
|
+
| Pattern | Lock? | Example |
|
|
141
|
+
| ----------------------------------------------------------- | ----- | ------------------------------------------------------------------------------------- |
|
|
142
|
+
| Read a record, then update/delete it | Yes | `deactivateUnit`: reads unit, then sets `isActive = false` |
|
|
143
|
+
| Read a record to check uniqueness before insert | Yes | `createUnit`: checks symbol uniqueness, then inserts |
|
|
144
|
+
| Read multiple related records, then update them | Yes | `setReferenceUnit`: reads all units in category, recalculates factors |
|
|
145
|
+
| Read a record only for validation (no write to that record) | No | Reading a category to verify it exists before inserting a unit into a different table |
|
|
146
|
+
| Insert-only with no prior read | No | `logAuditEvent`: pure insert |
|
|
147
|
+
|
|
148
|
+
### Rule
|
|
149
|
+
|
|
150
|
+
> Lock a `SELECT` when the command **writes to the same record it reads**, or when it **reads to enforce a uniqueness constraint** before inserting. The lock scope should be the narrowest set of rows needed — lock only the rows that participate in the read-then-write cycle.
|
|
151
|
+
|
|
152
|
+
### Code pattern
|
|
153
|
+
|
|
154
|
+
Chain `.forUpdate()` on the select query:
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
const unit = await db
|
|
158
|
+
.selectFrom("Unit")
|
|
159
|
+
.selectAll()
|
|
160
|
+
.where("id", "=", input.unitId)
|
|
161
|
+
.forUpdate()
|
|
162
|
+
.executeTakeFirst();
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Batch locking
|
|
166
|
+
|
|
167
|
+
When a command reads multiple rows then updates them (e.g., `setReferenceUnit`), lock the entire set:
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
const units = await db
|
|
171
|
+
.selectFrom("Unit")
|
|
172
|
+
.selectAll()
|
|
173
|
+
.where("categoryId", "=", input.categoryId)
|
|
174
|
+
.forUpdate()
|
|
175
|
+
.execute();
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Implementation Considerations
|
|
179
|
+
|
|
180
|
+
- **Error handling**: Use `ok()` / `err()` from `shared/internal` — do not throw
|
|
181
|
+
- **Validation**: Check referenced entities exist before operating, return `err()` if not found
|
|
182
|
+
|
|
183
|
+
## Conventions
|
|
184
|
+
|
|
185
|
+
- Input types: exported interfaces (`export interface MyFunctionInput`)
|
|
186
|
+
- Use `.executeTakeFirst()` for single results
|
|
187
|
+
- Include JSDoc: `/** Function: name \n Description */`
|
|
188
|
+
|
|
189
|
+
## State Transitions
|
|
190
|
+
|
|
191
|
+
For commands that transition between statuses, accept `from?: string[]` with a default:
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
from?: string[]; // Default: ["ACTIVE"]
|
|
195
|
+
|
|
196
|
+
const validFromStatuses = input.from ?? ["ACTIVE"];
|
|
197
|
+
if (!validFromStatuses.includes(user.status)) {
|
|
198
|
+
return err(new InvalidStatusTransitionError(user.status, targetStatus));
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
- Default `from` contains the base valid source status
|
|
203
|
+
- Parent modules can override to allow transitions from additional statuses
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Error Classes
|
|
2
|
+
|
|
3
|
+
Errors are **generated** from command/query documentation. Do not write `lib/errors.ts` manually.
|
|
4
|
+
|
|
5
|
+
## How it works
|
|
6
|
+
|
|
7
|
+
1. Write error scenarios in `docs/commands/*.md` as `CODE: Description` pairs
|
|
8
|
+
2. Run `erp-kit module generate code` to produce `lib/errors.generated.ts`
|
|
9
|
+
3. Import generated errors in your command implementations
|
|
10
|
+
|
|
11
|
+
## Naming convention (in docs)
|
|
12
|
+
|
|
13
|
+
- Error code: `SCREAMING_SNAKE_CASE` (e.g., `DUPLICATE_SKU`)
|
|
14
|
+
- Generated class name: PascalCase + `Error` suffix (e.g., `DuplicateSkuError`)
|
|
15
|
+
- Generated via `createDomainError()` with contextual message
|
|
16
|
+
|
|
17
|
+
## Error category naming rules
|
|
18
|
+
|
|
19
|
+
Use the **entity name** (not business-domain abbreviations) in error codes. Codes should be self-describing and consistent across modules.
|
|
20
|
+
|
|
21
|
+
| Category | Pattern | Example |
|
|
22
|
+
| ----------------- | --------------------------------------- | ------------------------------------ |
|
|
23
|
+
| Not found | `{ENTITY}_NOT_FOUND` | `UNIT_NOT_FOUND`, `ITEM_NOT_FOUND` |
|
|
24
|
+
| Duplicate | `DUPLICATE_{FIELD}` | `DUPLICATE_SKU`, `DUPLICATE_BARCODE` |
|
|
25
|
+
| Inactive / locked | `{FIELD}_LOCKED` or `{ENTITY}_INACTIVE` | `UOM_LOCKED` |
|
|
26
|
+
| Invalid state | `INVALID_{WHAT}` | `INVALID_STATE_TRANSITION` |
|
|
27
|
+
| No-op | `NO_FIELDS_TO_UPDATE` | `NO_FIELDS_TO_UPDATE` |
|
|
28
|
+
|
|
29
|
+
### Cross-module foreign key errors
|
|
30
|
+
|
|
31
|
+
When a command validates an entity from another module (e.g., item-management checking that a Unit from primitives exists), use `{ENTITY}_NOT_FOUND` — the same pattern as same-module not-found errors. The error code reflects what's missing, not where it lives.
|
|
32
|
+
|
|
33
|
+
### Idempotent error policy
|
|
34
|
+
|
|
35
|
+
If a command's outcome is already the current state (e.g., deactivating an already-inactive entity), return `ok()` — not an error. Only return errors for genuinely invalid operations.
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# Query Implementation
|
|
2
|
+
|
|
3
|
+
## Generated Query Shells
|
|
4
|
+
|
|
5
|
+
Run `erp-kit module generate code` to generate query shells from `docs/queries/*.md`. Each generated shell wraps your `run` function with `defineQuery`.
|
|
6
|
+
|
|
7
|
+
### Generated shell example
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
// @generated — do not edit
|
|
11
|
+
import { defineQuery } from "../../shared/internal";
|
|
12
|
+
import { run } from "./getItem";
|
|
13
|
+
|
|
14
|
+
export const getItem = defineQuery(run);
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### Hand-written implementation
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import type { ReadonlyDB } from "../../shared/internal";
|
|
21
|
+
import type { DB } from "../generated/kysely-tailordb";
|
|
22
|
+
|
|
23
|
+
export type GetItemInput = { id: string } | { sku: string };
|
|
24
|
+
|
|
25
|
+
export async function run(db: ReadonlyDB<DB>, input: GetItemInput) {
|
|
26
|
+
let query = db.selectFrom("Item").selectAll();
|
|
27
|
+
|
|
28
|
+
if ("id" in input) {
|
|
29
|
+
query = query.where("id", "=", input.id);
|
|
30
|
+
} else {
|
|
31
|
+
query = query.where("sku", "=", input.sku);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const item = await query.executeTakeFirst();
|
|
35
|
+
return { item: item ?? null };
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Custom Queries
|
|
40
|
+
|
|
41
|
+
For queries beyond simple lookups (joins, aggregations, complex filters), write them by hand in `query/*.ts`:
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import type { ReadonlyDB } from "../../shared/internal";
|
|
45
|
+
import type { DB } from "../generated/kysely-tailordb";
|
|
46
|
+
|
|
47
|
+
export interface ListActiveItemsByCategoryInput {
|
|
48
|
+
categoryId: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function run(db: ReadonlyDB<DB>, input: ListActiveItemsByCategoryInput) {
|
|
52
|
+
const items = await db
|
|
53
|
+
.selectFrom("Item")
|
|
54
|
+
.selectAll()
|
|
55
|
+
.where("categoryId", "=", input.categoryId)
|
|
56
|
+
.where("status", "=", "ACTIVE")
|
|
57
|
+
.execute();
|
|
58
|
+
|
|
59
|
+
return { items };
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Conventions
|
|
64
|
+
|
|
65
|
+
- `defineQuery` wraps all queries via generated shells
|
|
66
|
+
- `ReadonlyDB<DB>` ensures read-only access
|
|
67
|
+
- Get queries return `{ entity: T | null }` using `executeTakeFirst()`
|
|
68
|
+
- List queries return `{ entities: T[] }` using `execute()`
|
|
69
|
+
- Input types: `type` for union inputs (get), `interface` for single-shape inputs (list)
|
|
70
|
+
- `.generated.ts` files are always overwritten — never edit them
|
|
71
|
+
|
|
72
|
+
## Status-Aware Query Rules
|
|
73
|
+
|
|
74
|
+
Models with a status lifecycle (Stateful models with `status` enum, or simple models with `isActive` bool) follow specific naming and filtering conventions. The core principle: **if a status filter is a business use case, put it in the query name. If it's a search parameter, put it in the input.**
|
|
75
|
+
|
|
76
|
+
### Naming Convention
|
|
77
|
+
|
|
78
|
+
| Query type | Pattern | Returns |
|
|
79
|
+
| ------------------------------ | ------------------------------------ | ------------------------------------------- |
|
|
80
|
+
| **Get (single lookup)** | `get{Entity}` | Any status — caller already has a reference |
|
|
81
|
+
| **List (status-filtered)** | `list{Status}{Entities}` | Only records matching the named status |
|
|
82
|
+
| **List (unfiltered)** | `list{Entities}` | All records, all statuses |
|
|
83
|
+
| **Search (admin/exploratory)** | `search{Entities}({ status?, ... })` | Ad-hoc filtering via input params |
|
|
84
|
+
|
|
85
|
+
### Examples
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
// Business use case — status baked into the name
|
|
89
|
+
export async function run(db: ReadonlyDB<DB>, input: ListActiveItemsByCategoryInput) {
|
|
90
|
+
const items = await db
|
|
91
|
+
.selectFrom("Item")
|
|
92
|
+
.selectAll()
|
|
93
|
+
.where("categoryId", "=", input.categoryId)
|
|
94
|
+
.where("status", "=", "ACTIVE")
|
|
95
|
+
.execute();
|
|
96
|
+
return { items };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Unfiltered — no status filter, returns everything
|
|
100
|
+
export async function run(db: ReadonlyDB<DB>, input: ListItemsByCategoryInput) {
|
|
101
|
+
const items = await db
|
|
102
|
+
.selectFrom("Item")
|
|
103
|
+
.selectAll()
|
|
104
|
+
.where("categoryId", "=", input.categoryId)
|
|
105
|
+
.execute();
|
|
106
|
+
return { items };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Single lookup — status-unaware, returns any status
|
|
110
|
+
export async function run(db: ReadonlyDB<DB>, input: GetItemInput) {
|
|
111
|
+
const item = await db
|
|
112
|
+
.selectFrom("Item")
|
|
113
|
+
.selectAll()
|
|
114
|
+
.where("id", "=", input.id)
|
|
115
|
+
.executeTakeFirst();
|
|
116
|
+
return { item: item ?? null };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Admin/exploratory — parametric status filter
|
|
120
|
+
export interface SearchItemsInput {
|
|
121
|
+
status?: string;
|
|
122
|
+
categoryId?: string;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export async function run(db: ReadonlyDB<DB>, input: SearchItemsInput) {
|
|
126
|
+
let query = db.selectFrom("Item").selectAll();
|
|
127
|
+
if (input.status) {
|
|
128
|
+
query = query.where("status", "=", input.status);
|
|
129
|
+
}
|
|
130
|
+
if (input.categoryId) {
|
|
131
|
+
query = query.where("categoryId", "=", input.categoryId);
|
|
132
|
+
}
|
|
133
|
+
const items = await query.execute();
|
|
134
|
+
return { items };
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### `isActive` (bool) Models
|
|
139
|
+
|
|
140
|
+
The same naming rules apply. For models using `isActive` instead of a `status` enum (e.g., Currency, Unit, UomCategory):
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
// Business use case — active units only
|
|
144
|
+
// listActiveUnitsByCategory
|
|
145
|
+
export async function run(db: ReadonlyDB<DB>, input: ListActiveUnitsByCategoryInput) {
|
|
146
|
+
const units = await db
|
|
147
|
+
.selectFrom("Unit")
|
|
148
|
+
.selectAll()
|
|
149
|
+
.where("categoryId", "=", input.categoryId)
|
|
150
|
+
.where("isActive", "=", true)
|
|
151
|
+
.execute();
|
|
152
|
+
return { units };
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Anti-patterns
|
|
157
|
+
|
|
158
|
+
- **Silent defaults**: `listItems()` returning only ACTIVE without the name saying so — hidden behavior violates the principle of least surprise
|
|
159
|
+
- **"listAll" prefix**: `listAllItems()` implies the unfiltered variant is the exception — use `listItems()` for unfiltered instead
|
|
160
|
+
- **Fat queries with many optional filters for business use cases**: prefer purpose-built queries with intent in the name over swiss-army-knife queries
|
|
161
|
+
|
|
162
|
+
### Status preconditions in commands
|
|
163
|
+
|
|
164
|
+
Status checks that guard mutations (e.g., "only DRAFT items can be deleted") belong in commands, not queries. See [CQRS command-side read rule](commands.md#command-side-reads-cqrs-separation).
|
|
165
|
+
|
|
166
|
+
## See Also
|
|
167
|
+
|
|
168
|
+
- [CQRS command-side read rule](commands.md#command-side-reads-cqrs-separation) — queries are read-side only; commands inline own reads
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Module Directory Structure
|
|
2
|
+
|
|
3
|
+
This structure is automatically scaffolded by `erp-kit module scaffold module <name>`.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
{module}/
|
|
7
|
+
├── db/ # Database models (one file per model)
|
|
8
|
+
├── executor/ # Async executors (record triggers, job functions)
|
|
9
|
+
├── command/ # Domain commands + tests (*.test.ts co-located)
|
|
10
|
+
│ ├── *.ts # Hand-written business logic (run function)
|
|
11
|
+
│ ├── *.generated.ts # Generated command shells (do not edit)
|
|
12
|
+
│ └── *.test.ts # Tests
|
|
13
|
+
├── query/ # Read-only query handlers
|
|
14
|
+
│ ├── *.ts # Hand-written custom queries
|
|
15
|
+
│ └── *.generated.ts # Generated get/list queries (do not edit)
|
|
16
|
+
├── lib/
|
|
17
|
+
│ ├── errors.generated.ts # Generated error classes (do not edit)
|
|
18
|
+
│ ├── permissions.generated.ts # Generated permissions (do not edit)
|
|
19
|
+
│ └── types.ts # Hand-written types
|
|
20
|
+
├── testing/ # Test fixtures and helpers
|
|
21
|
+
├── generated/ # Auto-generated kysely types (do not edit)
|
|
22
|
+
├── index.ts # Public exports
|
|
23
|
+
├── tailor.config.ts # Module config and generators
|
|
24
|
+
└── module.ts # Module definition
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Rules
|
|
28
|
+
|
|
29
|
+
- `db/`: Only documentable model definitions, no helpers
|
|
30
|
+
- `executor/`: Async executors as factory functions (see executors.md)
|
|
31
|
+
- `command/`: Domain commands + co-located tests, no utilities
|
|
32
|
+
- `query/`: Read-only query handlers
|
|
33
|
+
- `lib/`: Generated errors/permissions + hand-written types
|
|
34
|
+
- `testing/`: Fixtures for tests only
|
|
35
|
+
- `.generated.ts` files are always overwritten — never edit them
|
|
36
|
+
- Run `pnpm generate` after modifying `db/` models
|
|
@@ -22,13 +22,14 @@ spies.select.mockReturnValueOnce(first).mockReturnValueOnce(second);
|
|
|
22
22
|
|
|
23
23
|
## Custom Fields Passthrough
|
|
24
24
|
|
|
25
|
-
For
|
|
25
|
+
For commands with generic `CF`, add one test verifying custom fields reach `.values()`:
|
|
26
26
|
|
|
27
|
-
-
|
|
27
|
+
- Import from `./createX.generated` (the generated shell that wires permissions)
|
|
28
|
+
- Pass extra fields in the input: `await createX(db, { name: "Test", myField: "value" }, ctx)`
|
|
28
29
|
- Assert with `spies.values`: `expect(spies.values).toHaveBeenNthCalledWith(1, expect.objectContaining({ myField: "value" }))`
|
|
29
30
|
- Use `toHaveBeenNthCalledWith(n, ...)` when multiple inserts occur (e.g., audit events)
|
|
30
31
|
|
|
31
|
-
## Fixtures (`
|
|
32
|
+
## Fixtures (`testing/fixtures.ts`)
|
|
32
33
|
|
|
33
34
|
- Import `Schema` from `lib/types` (not `Namespace` from generated code)
|
|
34
35
|
- Pattern: `export const baseEntity = { ... } as const satisfies Entity<Schema>`
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: erp-kit-update
|
|
3
|
+
description: Route requirements changes to the correct erp-kit skill. Use when requirements change midway — adding features, modifying business flows, updating screens, changing module specs, or when unsure which erp-kit skill to re-run after a change.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Requirements Update Router
|
|
7
|
+
|
|
8
|
+
Route mid-workflow changes to the correct erp-kit skill. Dynamically discovers available skills — no hardcoded routing tables.
|
|
9
|
+
|
|
10
|
+
## Workflow
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
IDENTIFY → DISCOVER → ROUTE → CASCADE → INVOKE
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Phase 1: Identify
|
|
17
|
+
|
|
18
|
+
Ask the user:
|
|
19
|
+
|
|
20
|
+
1. **What changed?** (free-text description of the change)
|
|
21
|
+
2. **Where?** Determine domain from the target:
|
|
22
|
+
- `examples/<app>/` → **App workflow** (skills matching `erp-kit-app-*`)
|
|
23
|
+
- `modules/<module>/` → **Module workflow** (skills matching `erp-kit-module-*`)
|
|
24
|
+
|
|
25
|
+
If the user names a specific app or module, confirm the path. If ambiguous, check both directories.
|
|
26
|
+
|
|
27
|
+
## Phase 2: Discover
|
|
28
|
+
|
|
29
|
+
Dynamically discover available erp-kit skills:
|
|
30
|
+
|
|
31
|
+
1. Glob for `erp-kit-app-*/SKILL.md` or `erp-kit-module-*/SKILL.md` files (based on domain from Phase 1) in the skills directory
|
|
32
|
+
2. Read each SKILL.md's frontmatter `name` and `description`
|
|
33
|
+
3. Skip `erp-kit-module-shared` (not directly invocable)
|
|
34
|
+
4. Sort skills by their numeric step (extract number from name, e.g. `erp-kit-app-2-breakdown` → step 2)
|
|
35
|
+
|
|
36
|
+
This builds the routing table at runtime — new or renamed skills are picked up automatically.
|
|
37
|
+
|
|
38
|
+
## Phase 3: Route
|
|
39
|
+
|
|
40
|
+
Match the user's change description against each discovered skill's `description` field. Select the **earliest** skill whose description matches the type of change.
|
|
41
|
+
|
|
42
|
+
**When ambiguous:** Route to the earliest possible skill in the chain. Earlier skills cascade forward naturally.
|
|
43
|
+
|
|
44
|
+
## Phase 4: Cascade
|
|
45
|
+
|
|
46
|
+
After identifying the entry point, warn about downstream skills that need re-running. Use the sorted skill list from Phase 2 — all skills with a higher step number than the routed skill are potential downstream candidates.
|
|
47
|
+
|
|
48
|
+
Show: "Starting from `<routed-skill>`. After this completes, you'll likely need to re-run: `<downstream-skills>`."
|
|
49
|
+
|
|
50
|
+
Only list downstream skills that are plausibly affected by the change.
|
|
51
|
+
|
|
52
|
+
## Phase 5: Invoke
|
|
53
|
+
|
|
54
|
+
Use the `Skill` tool to invoke the routed skill. Pass the app or module name as the argument.
|
|
55
|
+
|
|
56
|
+
Example: `Skill: erp-kit-app-2-breakdown, args: "inventory-management"`
|
|
57
|
+
|
|
58
|
+
## Multiple Changes
|
|
59
|
+
|
|
60
|
+
If the user describes multiple changes spanning different skills or domains:
|
|
61
|
+
|
|
62
|
+
1. Group by domain (app vs module)
|
|
63
|
+
2. Within each domain, route to the **earliest** affected skill
|
|
64
|
+
3. The cascade will naturally cover later skills
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { describe, test, beforeAll } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
assertDocMatch,
|
|
4
|
+
initDocFile,
|
|
5
|
+
collectAllCommands,
|
|
6
|
+
type GenerateDocConfig,
|
|
7
|
+
type FileMapping,
|
|
8
|
+
} from "politty/docs";
|
|
9
|
+
import { mainCommand } from "./commands/index";
|
|
10
|
+
|
|
11
|
+
function capitalize(s: string): string {
|
|
12
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function buildFilesFromCommand(): Promise<FileMapping> {
|
|
16
|
+
const allCommands = await collectAllCommands(mainCommand);
|
|
17
|
+
const files: FileMapping = {};
|
|
18
|
+
|
|
19
|
+
// Single-file full reference
|
|
20
|
+
files["docs/cli.md"] = { commands: [""], title: "CLI Reference" };
|
|
21
|
+
|
|
22
|
+
// Per-command sub-files
|
|
23
|
+
for (const [path, info] of allCommands) {
|
|
24
|
+
if (info.depth !== 2) continue;
|
|
25
|
+
|
|
26
|
+
const title =
|
|
27
|
+
info.subCommands.length > 0 ? `${capitalize(path)} Commands` : `${capitalize(path)} Command`;
|
|
28
|
+
|
|
29
|
+
files[`docs/cli/${path}.md`] = { commands: [path], title };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return files;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const files = await buildFilesFromCommand();
|
|
36
|
+
const targetCommands = Object.values(files).flatMap((f) =>
|
|
37
|
+
typeof f === "object" && "commands" in f ? f.commands : f,
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const config: GenerateDocConfig = {
|
|
41
|
+
command: mainCommand,
|
|
42
|
+
files,
|
|
43
|
+
format: {
|
|
44
|
+
headingLevel: 2,
|
|
45
|
+
optionStyle: "table",
|
|
46
|
+
generateAnchors: true,
|
|
47
|
+
includeSubcommandDetails: true,
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
describe("CLI documentation", () => {
|
|
52
|
+
beforeAll(() => {
|
|
53
|
+
initDocFile(config);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
for (const cmd of targetCommands) {
|
|
57
|
+
test(`${cmd || "root"}`, async () => {
|
|
58
|
+
await assertDocMatch({ ...config, targetCommands: [cmd] });
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
test("all", async () => {
|
|
63
|
+
await assertDocMatch(config);
|
|
64
|
+
});
|
|
65
|
+
});
|