@tailor-platform/erp-kit 0.1.0 → 0.1.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/CHANGELOG.md +13 -0
- package/LICENSE +21 -0
- package/README.md +77 -50
- package/dist/cli.js +693 -553
- package/package.json +4 -2
- package/schemas/module/command.yml +1 -0
- package/schemas/module/model.yml +9 -0
- package/schemas/module/query.yml +53 -0
- package/skills/1-module-docs/SKILL.md +4 -0
- package/{rules/module-development → skills/1-module-docs/references}/structure.md +2 -7
- package/skills/2-module-feature-breakdown/SKILL.md +6 -0
- package/{rules/module-development → skills/2-module-feature-breakdown/references}/commands.md +0 -6
- package/{rules/module-development → skills/2-module-feature-breakdown/references}/models.md +0 -5
- package/skills/2-module-feature-breakdown/references/structure.md +22 -0
- package/skills/3-module-doc-review/SKILL.md +6 -0
- package/skills/3-module-doc-review/references/commands.md +54 -0
- package/skills/3-module-doc-review/references/models.md +29 -0
- package/{rules/module-development → skills/3-module-doc-review/references}/testing.md +0 -6
- package/skills/4-module-tdd-implementation/SKILL.md +24 -6
- package/skills/4-module-tdd-implementation/references/commands.md +45 -0
- package/{rules/sdk-best-practices → skills/4-module-tdd-implementation/references}/db-relations.md +0 -5
- package/{rules/module-development → skills/4-module-tdd-implementation/references}/errors.md +0 -5
- package/{rules/module-development → skills/4-module-tdd-implementation/references}/exports.md +0 -5
- package/skills/4-module-tdd-implementation/references/models.md +30 -0
- package/skills/4-module-tdd-implementation/references/structure.md +22 -0
- package/skills/4-module-tdd-implementation/references/testing.md +37 -0
- package/skills/5-module-implementation-review/SKILL.md +8 -0
- package/skills/5-module-implementation-review/references/commands.md +45 -0
- package/skills/5-module-implementation-review/references/errors.md +7 -0
- package/skills/5-module-implementation-review/references/exports.md +8 -0
- package/skills/5-module-implementation-review/references/models.md +30 -0
- package/skills/5-module-implementation-review/references/testing.md +29 -0
- package/skills/app-compose-1-requirement-analysis/SKILL.md +4 -0
- package/{rules/app-compose → skills/app-compose-1-requirement-analysis/references}/structure.md +0 -5
- package/skills/app-compose-2-requirements-breakdown/SKILL.md +7 -0
- package/{rules/app-compose/frontend → skills/app-compose-2-requirements-breakdown/references}/screen-detailview.md +0 -6
- package/{rules/app-compose/frontend → skills/app-compose-2-requirements-breakdown/references}/screen-form.md +0 -6
- package/{rules/app-compose/frontend → skills/app-compose-2-requirements-breakdown/references}/screen-listview.md +0 -6
- package/skills/app-compose-2-requirements-breakdown/references/structure.md +27 -0
- package/skills/app-compose-3-doc-review/SKILL.md +4 -0
- package/skills/app-compose-3-doc-review/references/structure.md +27 -0
- package/skills/app-compose-4-design-mock/SKILL.md +8 -0
- package/{rules/app-compose/frontend → skills/app-compose-4-design-mock/references}/component.md +0 -5
- package/skills/app-compose-4-design-mock/references/screen-detailview.md +106 -0
- package/skills/app-compose-4-design-mock/references/screen-form.md +139 -0
- package/skills/app-compose-4-design-mock/references/screen-listview.md +153 -0
- package/skills/app-compose-4-design-mock/references/structure.md +27 -0
- package/skills/app-compose-5-design-mock-review/SKILL.md +7 -0
- package/skills/app-compose-5-design-mock-review/references/component.md +50 -0
- package/skills/app-compose-5-design-mock-review/references/screen-detailview.md +106 -0
- package/skills/app-compose-5-design-mock-review/references/screen-form.md +139 -0
- package/skills/app-compose-5-design-mock-review/references/screen-listview.md +153 -0
- package/skills/app-compose-6-implementation-spec/SKILL.md +5 -0
- package/{rules/app-compose/backend → skills/app-compose-6-implementation-spec/references}/auth.md +0 -6
- package/skills/app-compose-6-implementation-spec/references/structure.md +27 -0
- package/src/cli.ts +8 -90
- package/src/commands/app/index.ts +74 -0
- package/src/commands/check.test.ts +2 -1
- package/src/commands/check.ts +1 -0
- package/src/commands/init.test.ts +30 -19
- package/src/commands/init.ts +76 -43
- package/src/commands/module/index.ts +85 -0
- package/src/commands/module/list.test.ts +62 -0
- package/src/commands/module/list.ts +64 -0
- package/src/commands/scaffold.test.ts +5 -0
- package/src/commands/scaffold.ts +2 -1
- package/src/commands/sync-check.test.ts +28 -0
- package/src/commands/sync-check.ts +6 -0
- package/src/integration.test.ts +6 -8
- package/src/module.ts +4 -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/index.ts +2 -2
- package/src/modules/primitives/module.ts +5 -3
- package/src/modules/primitives/permissions.ts +0 -2
- package/src/modules/primitives/{command → query}/convertAmount.test.ts +2 -19
- package/src/modules/primitives/query/convertAmount.ts +122 -0
- package/src/modules/primitives/{command → query}/convertQuantity.test.ts +2 -13
- package/src/modules/primitives/{command → query}/convertQuantity.ts +4 -6
- package/src/modules/shared/defineQuery.test.ts +28 -0
- package/src/modules/shared/defineQuery.ts +16 -0
- package/src/modules/shared/internal.ts +2 -1
- package/src/modules/shared/types.ts +8 -0
- 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/schemas.ts +1 -0
- package/rules/app-compose/frontend/auth.md +0 -55
- package/rules/app-compose/frontend/page.md +0 -86
- package/rules/module-development/cross-module-type-injection.md +0 -28
- package/rules/module-development/dependency-modules.md +0 -24
- package/rules/module-development/executors.md +0 -67
- package/rules/module-development/sync-vs-async-operations.md +0 -83
- package/rules/sdk-best-practices/sdk-docs.md +0 -14
- package/src/modules/primitives/command/convertAmount.ts +0 -126
- /package/src/modules/primitives/docs/{commands → queries}/ConvertAmount.md +0 -0
- /package/src/modules/primitives/docs/{commands → queries}/ConvertQuantity.md +0 -0
package/package.json
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tailor-platform/erp-kit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Opinionated ERP toolkit for building business applications on Tailor Platform",
|
|
5
|
+
"license": "MIT",
|
|
5
6
|
"bin": {
|
|
6
7
|
"erp-kit": "./dist/cli.js"
|
|
7
8
|
},
|
|
8
9
|
"files": [
|
|
10
|
+
"CHANGELOG.md",
|
|
9
11
|
"dist",
|
|
10
|
-
"
|
|
12
|
+
"LICENSE",
|
|
11
13
|
"schemas",
|
|
12
14
|
"skills",
|
|
13
15
|
"src"
|
|
@@ -35,6 +35,7 @@ structure:
|
|
|
35
35
|
# Error handling - REQUIRED
|
|
36
36
|
# How errors are handled
|
|
37
37
|
- heading: "## Error Scenarios"
|
|
38
|
+
description: "Use UPPER_SNAKE_CASE error codes to identify each scenario.\ne.g. `- **ITEM_NOT_FOUND**: Specified item ID does not exist`"
|
|
38
39
|
lists:
|
|
39
40
|
- min: 0
|
|
40
41
|
type: unordered
|
package/schemas/module/model.yml
CHANGED
|
@@ -37,6 +37,15 @@ structure:
|
|
|
37
37
|
type: unordered
|
|
38
38
|
min_items: 0
|
|
39
39
|
|
|
40
|
+
- heading: "### Query Definitions"
|
|
41
|
+
description: |
|
|
42
|
+
Definitions of queries this domain model supports.
|
|
43
|
+
Should match with the queries defined in modules/**/docs/queries/*.md and link to them.
|
|
44
|
+
lists:
|
|
45
|
+
- min: 0
|
|
46
|
+
type: unordered
|
|
47
|
+
min_items: 0
|
|
48
|
+
|
|
40
49
|
- heading: "### Models"
|
|
41
50
|
description: |
|
|
42
51
|
Actual database models necessary for the domain model.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# yaml-language-server: $schema=https://raw.githubusercontent.com/jackchuka/mdschema/main/schema.json
|
|
2
|
+
|
|
3
|
+
structure:
|
|
4
|
+
- heading:
|
|
5
|
+
expr: "filename == heading"
|
|
6
|
+
children:
|
|
7
|
+
# What this query does - REQUIRED
|
|
8
|
+
- heading: "## Overview"
|
|
9
|
+
|
|
10
|
+
# Business rules - REQUIRED
|
|
11
|
+
# Rules, constraints, and validation logic
|
|
12
|
+
- heading: "## Business Rules"
|
|
13
|
+
allow_additional: true
|
|
14
|
+
lists:
|
|
15
|
+
- min: 0
|
|
16
|
+
type: unordered
|
|
17
|
+
min_items: 1
|
|
18
|
+
|
|
19
|
+
# How it works - REQUIRED
|
|
20
|
+
# Step-by-step flow or workflow
|
|
21
|
+
- heading: "## Process Flow"
|
|
22
|
+
code_blocks:
|
|
23
|
+
- lang: mermaid
|
|
24
|
+
min: 1
|
|
25
|
+
|
|
26
|
+
# External dependencies - OPTIONAL
|
|
27
|
+
# Queries from other modules that this query depends on
|
|
28
|
+
- heading: "## External Dependencies"
|
|
29
|
+
description: "e.g. `- [inventory::getStockLevel](../../inventory/queries/getStockLevel.md) - Get current stock level`"
|
|
30
|
+
lists:
|
|
31
|
+
- min: 0
|
|
32
|
+
type: unordered
|
|
33
|
+
min_items: 1
|
|
34
|
+
|
|
35
|
+
# Error handling - REQUIRED
|
|
36
|
+
# How errors are handled
|
|
37
|
+
- heading: "## Error Scenarios"
|
|
38
|
+
description: "Use UPPER_SNAKE_CASE error codes to identify each scenario.\ne.g. `- **ITEM_NOT_FOUND**: Specified item ID does not exist`"
|
|
39
|
+
lists:
|
|
40
|
+
- min: 0
|
|
41
|
+
type: unordered
|
|
42
|
+
min_items: 1
|
|
43
|
+
|
|
44
|
+
# Link validation
|
|
45
|
+
links:
|
|
46
|
+
validate_internal: true
|
|
47
|
+
validate_files: true
|
|
48
|
+
|
|
49
|
+
# Heading rules
|
|
50
|
+
heading_rules:
|
|
51
|
+
no_skip_levels: true
|
|
52
|
+
unique: true
|
|
53
|
+
max_depth: 4
|
|
@@ -1,15 +1,10 @@
|
|
|
1
|
-
---
|
|
2
|
-
paths:
|
|
3
|
-
- "modules/*/src/"
|
|
4
|
-
---
|
|
5
|
-
|
|
6
1
|
# Module Directory Structure
|
|
7
2
|
|
|
8
3
|
```
|
|
9
4
|
src/
|
|
10
5
|
├── db/ # Database models (one file per model)
|
|
11
6
|
├── executor/ # Async executors (record triggers, job functions)
|
|
12
|
-
├──
|
|
7
|
+
├── command/ # Domain commands + tests (*.test.ts co-located)
|
|
13
8
|
├── lib/ # Internal shared code (errors.ts, types.ts)
|
|
14
9
|
├── testing/ # Test fixtures and helpers
|
|
15
10
|
├── generated/ # Auto-generated code (do not edit)
|
|
@@ -21,7 +16,7 @@ src/
|
|
|
21
16
|
|
|
22
17
|
- `db/`: Only documentable model definitions, no helpers
|
|
23
18
|
- `executor/`: Async executors as factory functions (see executors.md)
|
|
24
|
-
- `
|
|
19
|
+
- `command/`: Domain commands + co-located tests, no utilities
|
|
25
20
|
- `lib/`: Internal errors and types (not documented)
|
|
26
21
|
- `testing/`: Fixtures for tests only
|
|
27
22
|
- Run `pnpm generate` after modifying `db/` models
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Module Directory Structure
|
|
2
|
+
|
|
3
|
+
```
|
|
4
|
+
src/
|
|
5
|
+
├── db/ # Database models (one file per model)
|
|
6
|
+
├── executor/ # Async executors (record triggers, job functions)
|
|
7
|
+
├── command/ # Domain commands + tests (*.test.ts co-located)
|
|
8
|
+
├── lib/ # Internal shared code (errors.ts, types.ts)
|
|
9
|
+
├── testing/ # Test fixtures and helpers
|
|
10
|
+
├── generated/ # Auto-generated code (do not edit)
|
|
11
|
+
├── index.ts # Public exports
|
|
12
|
+
└── module.ts # Module definition
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Rules
|
|
16
|
+
|
|
17
|
+
- `db/`: Only documentable model definitions, no helpers
|
|
18
|
+
- `executor/`: Async executors as factory functions (see executors.md)
|
|
19
|
+
- `command/`: Domain commands + co-located tests, no utilities
|
|
20
|
+
- `lib/`: Internal errors and types (not documented)
|
|
21
|
+
- `testing/`: Fixtures for tests only
|
|
22
|
+
- Run `pnpm generate` after modifying `db/` models
|
|
@@ -228,3 +228,9 @@ For each command doc:
|
|
|
228
228
|
| "Deactivate X" | deactivateX |
|
|
229
229
|
| "Assign X to Y" | assignXToY |
|
|
230
230
|
| "Remove X from Y" | removeXFromY |
|
|
231
|
+
|
|
232
|
+
## References
|
|
233
|
+
|
|
234
|
+
- [Model patterns](references/models.md)
|
|
235
|
+
- [Command patterns](references/commands.md)
|
|
236
|
+
- [Testing patterns](references/testing.md)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "packages/erp-kit/src/modules/*/command/*.ts"
|
|
4
|
+
- "!packages/erp-kit/src/modules/*/command/*.test.ts"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Command Implementation
|
|
8
|
+
|
|
9
|
+
## defineCommand Pattern
|
|
10
|
+
|
|
11
|
+
Commands that don't need custom fields use `defineCommand` directly:
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
export const myCommand = defineCommand(permissions.myCommand, async (db: DB, input: Input) => { ... });
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Factory Function Pattern (custom fields)
|
|
18
|
+
|
|
19
|
+
Commands that insert into a table with user-extensible fields use a `makeCreateX<CF>()` factory:
|
|
20
|
+
|
|
21
|
+
- Export only `makeCreateX` — no default instance
|
|
22
|
+
- Generic `CF extends Record<string, unknown>` for custom fields
|
|
23
|
+
- Input type: `CreateXInput & CF`
|
|
24
|
+
- Destructure known fields, rest-spread custom fields into `.values()` before known fields
|
|
25
|
+
- Cast custom fields: `...(customFields as Record<string, unknown>)`
|
|
26
|
+
- `module.ts` wires with `makeCreateX<FieldsToInsertable<F>>()`
|
|
27
|
+
|
|
28
|
+
## Implementation Considerations
|
|
29
|
+
|
|
30
|
+
- **Validation**: Check referenced entities exist before operating
|
|
31
|
+
- **Idempotency**: For assign/revoke, return existing instead of throwing
|
|
32
|
+
- **Return format**: Wrap in object `{ entity }` not just `entity`
|
|
33
|
+
|
|
34
|
+
## Conventions
|
|
35
|
+
|
|
36
|
+
- Input types: exported interfaces (`export interface MyFunctionInput`)
|
|
37
|
+
- Use `.executeTakeFirst()` for single results
|
|
38
|
+
- Include JSDoc: `/** Function: name \n Description */`
|
|
39
|
+
|
|
40
|
+
## State Transitions
|
|
41
|
+
|
|
42
|
+
For commands that transition between statuses, accept `from?: string[]` with a default:
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
from?: string[]; // Default: ["ACTIVE"]
|
|
46
|
+
|
|
47
|
+
const validFromStatuses = input.from ?? ["ACTIVE"];
|
|
48
|
+
if (!validFromStatuses.includes(user.status)) {
|
|
49
|
+
throw new InvalidStatusTransitionError(user.status, targetStatus);
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
- Default `from` contains the base valid source status
|
|
54
|
+
- Parent modules can override to allow transitions from additional statuses
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Database Models
|
|
2
|
+
|
|
3
|
+
## Factory Function Pattern
|
|
4
|
+
|
|
5
|
+
Each model is a `createXType<const F>(params)` factory:
|
|
6
|
+
|
|
7
|
+
- Params interface generic: `F extends Record<string, TailorAnyDBField>`
|
|
8
|
+
- `fields?: F` — optional custom fields from parent modules
|
|
9
|
+
- Spread as `...(params.fields ?? {}) as F` to preserve type information
|
|
10
|
+
- Include `...db.fields.timestamps()`
|
|
11
|
+
- Use `.description()` for field docs
|
|
12
|
+
- Apply permissions at model level
|
|
13
|
+
- Export a default instance: `export const x = createXType({})`
|
|
14
|
+
|
|
15
|
+
## Stateful Model Enums
|
|
16
|
+
|
|
17
|
+
Status enums are tied to the module's state machine. Base module defines core statuses; parent callers can only **extend, not replace**.
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
// Good: extension only
|
|
21
|
+
const BASE_STATUSES = ["PENDING", "ACTIVE", "INACTIVE"] as const;
|
|
22
|
+
const statuses = [...BASE_STATUSES, ...(params.additionalStatuses ?? [])];
|
|
23
|
+
|
|
24
|
+
// Bad: allows replacement
|
|
25
|
+
const statuses = params.statuses ?? ["PENDING", "ACTIVE", "INACTIVE"];
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
- Name parameter `additionalX` to signal extension-only
|
|
29
|
+
- Parent modules handle their additional status transitions
|
|
@@ -24,27 +24,35 @@ MODELS → ERRORS → FIXTURES → TESTS → IMPLEMENT → EXPORT → VERIFY
|
|
|
24
24
|
|
|
25
25
|
### 1. Database Models (`src/db/`)
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
**Read [models rules](references/models.md) and [db-relations rules](references/db-relations.md) before writing any model.**
|
|
28
|
+
|
|
29
|
+
Key patterns: factory function, `db.fields.timestamps()`, `.description()` on fields, `.relation()` for foreign keys.
|
|
28
30
|
|
|
29
31
|
### 2. Error Classes (`src/lib/errors.ts`)
|
|
30
32
|
|
|
31
|
-
|
|
33
|
+
**Read [errors rules](references/errors.md) before defining errors.**
|
|
34
|
+
|
|
35
|
+
Key patterns: `name` as const = class name, `code` as const = SCREAMING_SNAKE_CASE.
|
|
32
36
|
|
|
33
37
|
### 3. Test Fixtures (`src/testing/fixtures.ts`)
|
|
34
38
|
|
|
35
|
-
|
|
39
|
+
**Read [testing rules](references/testing.md) before writing fixtures or tests.**
|
|
36
40
|
|
|
37
41
|
### 4. Write Tests First (`src/command/*.test.ts`)
|
|
38
42
|
|
|
39
|
-
|
|
43
|
+
Same reference: [testing rules](references/testing.md).
|
|
44
|
+
|
|
45
|
+
Key patterns: `createMockDb<DB>()`, fixed IDs `"entity-1"`, cover all doc branches.
|
|
40
46
|
|
|
41
47
|
### 5. Implement Commands (`src/command/*.ts`)
|
|
42
48
|
|
|
43
|
-
|
|
49
|
+
**Read [commands rules](references/commands.md) before implementing.**
|
|
50
|
+
|
|
51
|
+
Key patterns: dual function (`_fn` internal + `fn<T>` public), validate → query → mutate.
|
|
44
52
|
|
|
45
53
|
### 6. Export (`src/index.ts`)
|
|
46
54
|
|
|
47
|
-
|
|
55
|
+
**Read [exports rules](references/exports.md) for export order.**
|
|
48
56
|
|
|
49
57
|
### 7. Verify
|
|
50
58
|
|
|
@@ -54,3 +62,13 @@ pnpm lint # Check code style
|
|
|
54
62
|
pnpm typecheck # Verify TypeScript types
|
|
55
63
|
pnpm test # Run all tests
|
|
56
64
|
```
|
|
65
|
+
|
|
66
|
+
## References
|
|
67
|
+
|
|
68
|
+
- [Module structure](references/structure.md)
|
|
69
|
+
- [Model patterns](references/models.md)
|
|
70
|
+
- [Database relations](references/db-relations.md)
|
|
71
|
+
- [Command patterns](references/commands.md)
|
|
72
|
+
- [Error patterns](references/errors.md)
|
|
73
|
+
- [Testing patterns](references/testing.md)
|
|
74
|
+
- [Export order](references/exports.md)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Command Implementation
|
|
2
|
+
|
|
3
|
+
## Dual Function Pattern
|
|
4
|
+
|
|
5
|
+
Two functions per operation: internal `_fn` and public `fn`.
|
|
6
|
+
|
|
7
|
+
**Internal function (`_myFunction`):**
|
|
8
|
+
|
|
9
|
+
- Takes concrete `DB` type from `generated/kysely-tailordb`
|
|
10
|
+
- Return type uses `Schema` from `lib/types`: `Promise<{ entity: Entity<Schema> }>`
|
|
11
|
+
- Contains actual implementation
|
|
12
|
+
|
|
13
|
+
**Public function (`myFunction`):**
|
|
14
|
+
|
|
15
|
+
- Generic signature: `<T extends { Entity: object }>(db: Kysely<T>, ...)`
|
|
16
|
+
- Return type uses generic `T`: `Promise<{ entity: Entity<T> }>`
|
|
17
|
+
- Delegates to internal with type casting: `_fn(db as unknown as DB, ...) as unknown as Result`
|
|
18
|
+
|
|
19
|
+
## Implementation Considerations
|
|
20
|
+
|
|
21
|
+
- **Validation**: Check referenced entities exist before operating
|
|
22
|
+
- **Idempotency**: For assign/revoke, return existing instead of throwing
|
|
23
|
+
- **Return format**: Wrap in object `{ entity }` not just `entity`
|
|
24
|
+
|
|
25
|
+
## Conventions
|
|
26
|
+
|
|
27
|
+
- Input types: exported interfaces (`export interface MyFunctionInput`)
|
|
28
|
+
- Use `.executeTakeFirst()` for single results
|
|
29
|
+
- Include JSDoc: `/** Function: name \n Description */`
|
|
30
|
+
|
|
31
|
+
## State Transitions
|
|
32
|
+
|
|
33
|
+
For commands that transition between statuses, accept `from?: string[]` with a default:
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
from?: string[]; // Default: ["ACTIVE"]
|
|
37
|
+
|
|
38
|
+
const validFromStatuses = input.from ?? ["ACTIVE"];
|
|
39
|
+
if (!validFromStatuses.includes(user.status)) {
|
|
40
|
+
throw new InvalidStatusTransitionError(user.status, targetStatus);
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
- Default `from` contains the base valid source status
|
|
45
|
+
- Parent modules can override to allow transitions from additional statuses
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Database Models
|
|
2
|
+
|
|
3
|
+
## Factory Function Pattern
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
export function createEntityType(params: {
|
|
7
|
+
fields?: Record<string, unknown>;
|
|
8
|
+
additionalStatuses?: string[];
|
|
9
|
+
});
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
- Include `...db.fields.timestamps()`
|
|
13
|
+
- Use `.description()` for field docs
|
|
14
|
+
- Apply permissions at model level
|
|
15
|
+
|
|
16
|
+
## Stateful Model Enums
|
|
17
|
+
|
|
18
|
+
Status enums are tied to the module's state machine. Base module defines core statuses; parent callers can only **extend, not replace**.
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
// Good: extension only
|
|
22
|
+
const BASE_STATUSES = ["PENDING", "ACTIVE", "INACTIVE"] as const;
|
|
23
|
+
const statuses = [...BASE_STATUSES, ...(params.additionalStatuses ?? [])];
|
|
24
|
+
|
|
25
|
+
// Bad: allows replacement
|
|
26
|
+
const statuses = params.statuses ?? ["PENDING", "ACTIVE", "INACTIVE"];
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
- Name parameter `additionalX` to signal extension-only
|
|
30
|
+
- Parent modules handle their additional status transitions
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Module Directory Structure
|
|
2
|
+
|
|
3
|
+
```
|
|
4
|
+
src/
|
|
5
|
+
├── db/ # Database models (one file per model)
|
|
6
|
+
├── executor/ # Async executors (record triggers, job functions)
|
|
7
|
+
├── command/ # Domain commands + tests (*.test.ts co-located)
|
|
8
|
+
├── lib/ # Internal shared code (errors.ts, types.ts)
|
|
9
|
+
├── testing/ # Test fixtures and helpers
|
|
10
|
+
├── generated/ # Auto-generated code (do not edit)
|
|
11
|
+
├── index.ts # Public exports
|
|
12
|
+
└── module.ts # Module definition
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Rules
|
|
16
|
+
|
|
17
|
+
- `db/`: Only documentable model definitions, no helpers
|
|
18
|
+
- `executor/`: Async executors as factory functions (see executors.md)
|
|
19
|
+
- `command/`: Domain commands + co-located tests, no utilities
|
|
20
|
+
- `lib/`: Internal errors and types (not documented)
|
|
21
|
+
- `testing/`: Fixtures for tests only
|
|
22
|
+
- Run `pnpm generate` after modifying `db/` models
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Testing Patterns
|
|
2
|
+
|
|
3
|
+
## Test Coverage Goal
|
|
4
|
+
|
|
5
|
+
Tests should cover all paths in the corresponding `docs/commands/*.md`:
|
|
6
|
+
|
|
7
|
+
- **Process Flow**: Each branch in the mermaid flowchart = one test case
|
|
8
|
+
- **Error Scenarios**: Each error code listed = one test case
|
|
9
|
+
- **Idempotent paths**: If flowchart shows "Already exists? → Return existing"
|
|
10
|
+
|
|
11
|
+
## Mock Database
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
const { db, spies } = createMockDb<DB>();
|
|
15
|
+
|
|
16
|
+
// Single return
|
|
17
|
+
spies.select.mockReturnValue(entity);
|
|
18
|
+
|
|
19
|
+
// Sequential returns (in query execution order)
|
|
20
|
+
spies.select.mockReturnValueOnce(first).mockReturnValueOnce(second);
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Custom Fields Passthrough
|
|
24
|
+
|
|
25
|
+
For `makeCreateX` factory commands, add one test verifying custom fields reach `.values()`:
|
|
26
|
+
|
|
27
|
+
- Instantiate with concrete type: `makeCreateX<{ myField: string }>()`
|
|
28
|
+
- Assert with `spies.values`: `expect(spies.values).toHaveBeenNthCalledWith(1, expect.objectContaining({ myField: "value" }))`
|
|
29
|
+
- Use `toHaveBeenNthCalledWith(n, ...)` when multiple inserts occur (e.g., audit events)
|
|
30
|
+
|
|
31
|
+
## Fixtures (`src/testing/fixtures.ts`)
|
|
32
|
+
|
|
33
|
+
- Import `Schema` from `lib/types` (not `Namespace` from generated code)
|
|
34
|
+
- Pattern: `export const baseEntity = { ... } as const satisfies Entity<Schema>`
|
|
35
|
+
- Fixed IDs for traceability: `"entity-1"`
|
|
36
|
+
- Consistent timestamp: `new Date("2024-01-01T00:00:00.000Z")`
|
|
37
|
+
- `updatedAt: null` for base fixtures
|
|
@@ -398,3 +398,11 @@ describe("myCommand", () => {
|
|
|
398
398
|
it("throws NOT_FOUND when missing"); // Check: error scenario
|
|
399
399
|
});
|
|
400
400
|
```
|
|
401
|
+
|
|
402
|
+
## References
|
|
403
|
+
|
|
404
|
+
- [Model patterns](references/models.md)
|
|
405
|
+
- [Command patterns](references/commands.md)
|
|
406
|
+
- [Error patterns](references/errors.md)
|
|
407
|
+
- [Testing patterns](references/testing.md)
|
|
408
|
+
- [Export order](references/exports.md)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Command Implementation
|
|
2
|
+
|
|
3
|
+
## Dual Function Pattern
|
|
4
|
+
|
|
5
|
+
Two functions per operation: internal `_fn` and public `fn`.
|
|
6
|
+
|
|
7
|
+
**Internal function (`_myFunction`):**
|
|
8
|
+
|
|
9
|
+
- Takes concrete `DB` type from `generated/kysely-tailordb`
|
|
10
|
+
- Return type uses `Schema` from `lib/types`: `Promise<{ entity: Entity<Schema> }>`
|
|
11
|
+
- Contains actual implementation
|
|
12
|
+
|
|
13
|
+
**Public function (`myFunction`):**
|
|
14
|
+
|
|
15
|
+
- Generic signature: `<T extends { Entity: object }>(db: Kysely<T>, ...)`
|
|
16
|
+
- Return type uses generic `T`: `Promise<{ entity: Entity<T> }>`
|
|
17
|
+
- Delegates to internal with type casting: `_fn(db as unknown as DB, ...) as unknown as Result`
|
|
18
|
+
|
|
19
|
+
## Implementation Considerations
|
|
20
|
+
|
|
21
|
+
- **Validation**: Check referenced entities exist before operating
|
|
22
|
+
- **Idempotency**: For assign/revoke, return existing instead of throwing
|
|
23
|
+
- **Return format**: Wrap in object `{ entity }` not just `entity`
|
|
24
|
+
|
|
25
|
+
## Conventions
|
|
26
|
+
|
|
27
|
+
- Input types: exported interfaces (`export interface MyFunctionInput`)
|
|
28
|
+
- Use `.executeTakeFirst()` for single results
|
|
29
|
+
- Include JSDoc: `/** Function: name \n Description */`
|
|
30
|
+
|
|
31
|
+
## State Transitions
|
|
32
|
+
|
|
33
|
+
For commands that transition between statuses, accept `from?: string[]` with a default:
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
from?: string[]; // Default: ["ACTIVE"]
|
|
37
|
+
|
|
38
|
+
const validFromStatuses = input.from ?? ["ACTIVE"];
|
|
39
|
+
if (!validFromStatuses.includes(user.status)) {
|
|
40
|
+
throw new InvalidStatusTransitionError(user.status, targetStatus);
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
- Default `from` contains the base valid source status
|
|
45
|
+
- Parent modules can override to allow transitions from additional statuses
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Database Models
|
|
2
|
+
|
|
3
|
+
## Factory Function Pattern
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
export function createEntityType(params: {
|
|
7
|
+
fields?: Record<string, unknown>;
|
|
8
|
+
additionalStatuses?: string[];
|
|
9
|
+
});
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
- Include `...db.fields.timestamps()`
|
|
13
|
+
- Use `.description()` for field docs
|
|
14
|
+
- Apply permissions at model level
|
|
15
|
+
|
|
16
|
+
## Stateful Model Enums
|
|
17
|
+
|
|
18
|
+
Status enums are tied to the module's state machine. Base module defines core statuses; parent callers can only **extend, not replace**.
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
// Good: extension only
|
|
22
|
+
const BASE_STATUSES = ["PENDING", "ACTIVE", "INACTIVE"] as const;
|
|
23
|
+
const statuses = [...BASE_STATUSES, ...(params.additionalStatuses ?? [])];
|
|
24
|
+
|
|
25
|
+
// Bad: allows replacement
|
|
26
|
+
const statuses = params.statuses ?? ["PENDING", "ACTIVE", "INACTIVE"];
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
- Name parameter `additionalX` to signal extension-only
|
|
30
|
+
- Parent modules handle their additional status transitions
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Testing Patterns
|
|
2
|
+
|
|
3
|
+
## Test Coverage Goal
|
|
4
|
+
|
|
5
|
+
Tests should cover all paths in the corresponding `docs/commands/*.md`:
|
|
6
|
+
|
|
7
|
+
- **Process Flow**: Each branch in the mermaid flowchart = one test case
|
|
8
|
+
- **Error Scenarios**: Each error code listed = one test case
|
|
9
|
+
- **Idempotent paths**: If flowchart shows "Already exists? → Return existing"
|
|
10
|
+
|
|
11
|
+
## Mock Database
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
const { db, spies } = createMockDb<DB>();
|
|
15
|
+
|
|
16
|
+
// Single return
|
|
17
|
+
spies.select.mockReturnValue(entity);
|
|
18
|
+
|
|
19
|
+
// Sequential returns (in query execution order)
|
|
20
|
+
spies.select.mockReturnValueOnce(first).mockReturnValueOnce(second);
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Fixtures (`src/testing/fixtures.ts`)
|
|
24
|
+
|
|
25
|
+
- Import `Schema` from `lib/types` (not `Namespace` from generated code)
|
|
26
|
+
- Pattern: `export const baseEntity = { ... } as const satisfies Entity<Schema>`
|
|
27
|
+
- Fixed IDs for traceability: `"entity-1"`
|
|
28
|
+
- Consistent timestamp: `new Date("2024-01-01T00:00:00.000Z")`
|
|
29
|
+
- `updatedAt: null` for base fixtures
|