@tailor-platform/erp-kit 0.0.1 → 0.1.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.
Files changed (213) hide show
  1. package/README.md +196 -28
  2. package/dist/cli.js +894 -0
  3. package/package.json +65 -8
  4. package/rules/app-compose/backend/auth.md +78 -0
  5. package/rules/app-compose/frontend/auth.md +55 -0
  6. package/rules/app-compose/frontend/component.md +55 -0
  7. package/rules/app-compose/frontend/page.md +86 -0
  8. package/rules/app-compose/frontend/screen-detailview.md +112 -0
  9. package/rules/app-compose/frontend/screen-form.md +145 -0
  10. package/rules/app-compose/frontend/screen-listview.md +159 -0
  11. package/rules/app-compose/structure.md +32 -0
  12. package/rules/module-development/commands.md +54 -0
  13. package/rules/module-development/cross-module-type-injection.md +28 -0
  14. package/rules/module-development/dependency-modules.md +24 -0
  15. package/rules/module-development/errors.md +12 -0
  16. package/rules/module-development/executors.md +67 -0
  17. package/rules/module-development/exports.md +13 -0
  18. package/rules/module-development/models.md +34 -0
  19. package/rules/module-development/structure.md +27 -0
  20. package/rules/module-development/sync-vs-async-operations.md +83 -0
  21. package/rules/module-development/testing.md +43 -0
  22. package/rules/sdk-best-practices/db-relations.md +74 -0
  23. package/rules/sdk-best-practices/sdk-docs.md +14 -0
  24. package/schemas/app-compose/actors.yml +34 -0
  25. package/schemas/app-compose/business-flow.yml +50 -0
  26. package/schemas/app-compose/requirements.yml +33 -0
  27. package/schemas/app-compose/resolver.yml +47 -0
  28. package/schemas/app-compose/screen.yml +81 -0
  29. package/schemas/app-compose/story.yml +67 -0
  30. package/schemas/module/command.yml +52 -0
  31. package/schemas/module/feature.yml +58 -0
  32. package/schemas/module/model.yml +70 -0
  33. package/schemas/module/module.yml +50 -0
  34. package/skills/1-module-docs/SKILL.md +107 -0
  35. package/skills/2-module-feature-breakdown/SKILL.md +66 -0
  36. package/skills/3-module-doc-review/SKILL.md +230 -0
  37. package/skills/4-module-tdd-implementation/SKILL.md +56 -0
  38. package/skills/5-module-implementation-review/SKILL.md +400 -0
  39. package/skills/app-compose-1-requirement-analysis/SKILL.md +85 -0
  40. package/skills/app-compose-2-requirements-breakdown/SKILL.md +88 -0
  41. package/skills/app-compose-3-doc-review/SKILL.md +112 -0
  42. package/skills/app-compose-4-design-mock/SKILL.md +248 -0
  43. package/skills/app-compose-5-design-mock-review/SKILL.md +283 -0
  44. package/skills/app-compose-6-implementation-spec/SKILL.md +122 -0
  45. package/skills/mock-scenario/SKILL.md +118 -0
  46. package/src/app.ts +1 -0
  47. package/src/cli.ts +120 -0
  48. package/src/commands/check.test.ts +30 -0
  49. package/src/commands/check.ts +66 -0
  50. package/src/commands/init.test.ts +77 -0
  51. package/src/commands/init.ts +87 -0
  52. package/src/commands/mock/index.ts +53 -0
  53. package/src/commands/mock/start.ts +179 -0
  54. package/src/commands/mock/validate.test.ts +185 -0
  55. package/src/commands/mock/validate.ts +198 -0
  56. package/src/commands/scaffold.test.ts +76 -0
  57. package/src/commands/scaffold.ts +119 -0
  58. package/src/commands/sync-check.test.ts +125 -0
  59. package/src/commands/sync-check.ts +182 -0
  60. package/src/integration.test.ts +63 -0
  61. package/src/mdschema.ts +48 -0
  62. package/src/mockServer.ts +55 -0
  63. package/src/module.ts +86 -0
  64. package/src/modules/accounting/.gitkeep +0 -0
  65. package/src/modules/coa-management/.gitkeep +0 -0
  66. package/src/modules/inventory/.gitkeep +0 -0
  67. package/src/modules/manufacturing/.gitkeep +0 -0
  68. package/src/modules/primitives/README.md +39 -0
  69. package/src/modules/primitives/command/activateCategory.test.ts +75 -0
  70. package/src/modules/primitives/command/activateCategory.ts +50 -0
  71. package/src/modules/primitives/command/activateCurrency.test.ts +70 -0
  72. package/src/modules/primitives/command/activateCurrency.ts +50 -0
  73. package/src/modules/primitives/command/activateUnit.test.ts +53 -0
  74. package/src/modules/primitives/command/activateUnit.ts +50 -0
  75. package/src/modules/primitives/command/convertAmount.test.ts +275 -0
  76. package/src/modules/primitives/command/convertAmount.ts +126 -0
  77. package/src/modules/primitives/command/convertQuantity.test.ts +219 -0
  78. package/src/modules/primitives/command/convertQuantity.ts +73 -0
  79. package/src/modules/primitives/command/createCategory.test.ts +126 -0
  80. package/src/modules/primitives/command/createCategory.ts +89 -0
  81. package/src/modules/primitives/command/createCurrency.test.ts +191 -0
  82. package/src/modules/primitives/command/createCurrency.ts +77 -0
  83. package/src/modules/primitives/command/createExchangeRate.test.ts +216 -0
  84. package/src/modules/primitives/command/createExchangeRate.ts +91 -0
  85. package/src/modules/primitives/command/createUnit.test.ts +214 -0
  86. package/src/modules/primitives/command/createUnit.ts +88 -0
  87. package/src/modules/primitives/command/deactivateCategory.test.ts +97 -0
  88. package/src/modules/primitives/command/deactivateCategory.ts +62 -0
  89. package/src/modules/primitives/command/deactivateCurrency.test.ts +85 -0
  90. package/src/modules/primitives/command/deactivateCurrency.ts +55 -0
  91. package/src/modules/primitives/command/deactivateUnit.test.ts +78 -0
  92. package/src/modules/primitives/command/deactivateUnit.ts +62 -0
  93. package/src/modules/primitives/command/setBaseCurrency.test.ts +98 -0
  94. package/src/modules/primitives/command/setBaseCurrency.ts +74 -0
  95. package/src/modules/primitives/command/setReferenceUnit.test.ts +108 -0
  96. package/src/modules/primitives/command/setReferenceUnit.ts +84 -0
  97. package/src/modules/primitives/db/currency.ts +30 -0
  98. package/src/modules/primitives/db/exchangeRate.ts +28 -0
  99. package/src/modules/primitives/db/unit.ts +32 -0
  100. package/src/modules/primitives/db/uomCategory.ts +32 -0
  101. package/src/modules/primitives/docs/commands/ActivateCategory.md +34 -0
  102. package/src/modules/primitives/docs/commands/ActivateCurrency.md +33 -0
  103. package/src/modules/primitives/docs/commands/ActivateUnit.md +34 -0
  104. package/src/modules/primitives/docs/commands/ConvertAmount.md +50 -0
  105. package/src/modules/primitives/docs/commands/ConvertQuantity.md +43 -0
  106. package/src/modules/primitives/docs/commands/CreateCategory.md +44 -0
  107. package/src/modules/primitives/docs/commands/CreateCurrency.md +47 -0
  108. package/src/modules/primitives/docs/commands/CreateExchangeRate.md +48 -0
  109. package/src/modules/primitives/docs/commands/CreateUnit.md +48 -0
  110. package/src/modules/primitives/docs/commands/DeactivateCategory.md +38 -0
  111. package/src/modules/primitives/docs/commands/DeactivateCurrency.md +38 -0
  112. package/src/modules/primitives/docs/commands/DeactivateUnit.md +38 -0
  113. package/src/modules/primitives/docs/commands/SetBaseCurrency.md +39 -0
  114. package/src/modules/primitives/docs/commands/SetReferenceUnit.md +43 -0
  115. package/src/modules/primitives/docs/features/currency-definitions.md +55 -0
  116. package/src/modules/primitives/docs/features/exchange-rates.md +61 -0
  117. package/src/modules/primitives/docs/features/unit-conversion.md +66 -0
  118. package/src/modules/primitives/docs/features/uom-categories.md +52 -0
  119. package/src/modules/primitives/docs/models/Currency.md +45 -0
  120. package/src/modules/primitives/docs/models/ExchangeRate.md +33 -0
  121. package/src/modules/primitives/docs/models/Unit.md +46 -0
  122. package/src/modules/primitives/docs/models/UoMCategory.md +44 -0
  123. package/src/modules/primitives/generated/kysely-tailordb.ts +95 -0
  124. package/src/modules/primitives/index.ts +40 -0
  125. package/src/modules/primitives/lib/errors.ts +138 -0
  126. package/src/modules/primitives/lib/types.ts +20 -0
  127. package/src/modules/primitives/module.ts +66 -0
  128. package/src/modules/primitives/permissions.ts +18 -0
  129. package/src/modules/primitives/tailor.config.ts +11 -0
  130. package/src/modules/primitives/testing/fixtures.ts +161 -0
  131. package/src/modules/product-management/.gitkeep +0 -0
  132. package/src/modules/purchase/.gitkeep +0 -0
  133. package/src/modules/sales/.gitkeep +0 -0
  134. package/src/modules/shared/createContext.test.ts +39 -0
  135. package/src/modules/shared/createContext.ts +15 -0
  136. package/src/modules/shared/defineCommand.test.ts +42 -0
  137. package/src/modules/shared/defineCommand.ts +19 -0
  138. package/src/modules/shared/definePermissions.test.ts +146 -0
  139. package/src/modules/shared/definePermissions.ts +94 -0
  140. package/src/modules/shared/entityTypes.ts +15 -0
  141. package/src/modules/shared/errors.ts +22 -0
  142. package/src/modules/shared/index.ts +1 -0
  143. package/src/modules/shared/internal.ts +13 -0
  144. package/src/modules/shared/requirePermission.test.ts +47 -0
  145. package/src/modules/shared/requirePermission.ts +8 -0
  146. package/src/modules/shared/types.ts +4 -0
  147. package/src/modules/supplier-management/.gitkeep +0 -0
  148. package/src/modules/supplier-portal/.gitkeep +0 -0
  149. package/src/modules/testing/index.ts +120 -0
  150. package/src/modules/user-management/README.md +38 -0
  151. package/src/modules/user-management/command/activateUser.test.ts +112 -0
  152. package/src/modules/user-management/command/activateUser.ts +67 -0
  153. package/src/modules/user-management/command/assignPermissionToRole.test.ts +119 -0
  154. package/src/modules/user-management/command/assignPermissionToRole.ts +87 -0
  155. package/src/modules/user-management/command/assignRoleToUser.test.ts +162 -0
  156. package/src/modules/user-management/command/assignRoleToUser.ts +93 -0
  157. package/src/modules/user-management/command/createPermission.test.ts +143 -0
  158. package/src/modules/user-management/command/createPermission.ts +66 -0
  159. package/src/modules/user-management/command/createRole.test.ts +115 -0
  160. package/src/modules/user-management/command/createRole.ts +52 -0
  161. package/src/modules/user-management/command/createUser.test.ts +198 -0
  162. package/src/modules/user-management/command/createUser.ts +85 -0
  163. package/src/modules/user-management/command/deactivateUser.test.ts +112 -0
  164. package/src/modules/user-management/command/deactivateUser.ts +67 -0
  165. package/src/modules/user-management/command/logAuditEvent.test.ts +179 -0
  166. package/src/modules/user-management/command/logAuditEvent.ts +59 -0
  167. package/src/modules/user-management/command/reactivateUser.test.ts +115 -0
  168. package/src/modules/user-management/command/reactivateUser.ts +67 -0
  169. package/src/modules/user-management/command/revokePermissionFromRole.test.ts +112 -0
  170. package/src/modules/user-management/command/revokePermissionFromRole.ts +81 -0
  171. package/src/modules/user-management/command/revokeRoleFromUser.test.ts +112 -0
  172. package/src/modules/user-management/command/revokeRoleFromUser.ts +81 -0
  173. package/src/modules/user-management/db/auditEvent.ts +47 -0
  174. package/src/modules/user-management/db/permission.ts +31 -0
  175. package/src/modules/user-management/db/role.ts +28 -0
  176. package/src/modules/user-management/db/rolePermission.ts +44 -0
  177. package/src/modules/user-management/db/user.ts +38 -0
  178. package/src/modules/user-management/db/userRole.ts +44 -0
  179. package/src/modules/user-management/docs/commands/ActivateUser.md +36 -0
  180. package/src/modules/user-management/docs/commands/AssignPermissionToRole.md +39 -0
  181. package/src/modules/user-management/docs/commands/AssignRoleToUser.md +43 -0
  182. package/src/modules/user-management/docs/commands/CreatePermission.md +35 -0
  183. package/src/modules/user-management/docs/commands/CreateRole.md +35 -0
  184. package/src/modules/user-management/docs/commands/CreateUser.md +41 -0
  185. package/src/modules/user-management/docs/commands/DeactivateUser.md +38 -0
  186. package/src/modules/user-management/docs/commands/LogAuditEvent.md +37 -0
  187. package/src/modules/user-management/docs/commands/ReactivateUser.md +37 -0
  188. package/src/modules/user-management/docs/commands/RevokePermissionFromRole.md +40 -0
  189. package/src/modules/user-management/docs/commands/RevokeRoleFromUser.md +40 -0
  190. package/src/modules/user-management/docs/features/audit-trail.md +80 -0
  191. package/src/modules/user-management/docs/features/role-based-access-control.md +76 -0
  192. package/src/modules/user-management/docs/features/user-account-management.md +64 -0
  193. package/src/modules/user-management/docs/models/AuditEvent.md +34 -0
  194. package/src/modules/user-management/docs/models/Permission.md +31 -0
  195. package/src/modules/user-management/docs/models/Role.md +31 -0
  196. package/src/modules/user-management/docs/models/RolePermission.md +33 -0
  197. package/src/modules/user-management/docs/models/User.md +47 -0
  198. package/src/modules/user-management/docs/models/UserRole.md +34 -0
  199. package/src/modules/user-management/docs/plans/2026-01-30-flattened-permissions-design.md +52 -0
  200. package/src/modules/user-management/executor/recomputeOnRolePermissionChange.ts +61 -0
  201. package/src/modules/user-management/generated/enums.ts +24 -0
  202. package/src/modules/user-management/generated/kysely-tailordb.ts +112 -0
  203. package/src/modules/user-management/index.ts +32 -0
  204. package/src/modules/user-management/lib/errors.ts +81 -0
  205. package/src/modules/user-management/lib/recomputeUserPermissions.ts +53 -0
  206. package/src/modules/user-management/lib/types.ts +31 -0
  207. package/src/modules/user-management/module.ts +77 -0
  208. package/src/modules/user-management/permissions.ts +15 -0
  209. package/src/modules/user-management/tailor.config.ts +11 -0
  210. package/src/modules/user-management/testing/fixtures.ts +98 -0
  211. package/src/schemas.ts +25 -0
  212. package/src/testing.ts +10 -0
  213. package/src/util.ts +3 -0
@@ -0,0 +1,159 @@
1
+ ---
2
+ paths:
3
+ - "examples/**/src/pages/**/page.tsx"
4
+ - "examples/**/src/pages/**/components/*table*.tsx"
5
+ ---
6
+
7
+ # ListView Screen Implementation
8
+
9
+ Implementation pattern for screens with `Screen Type: ListView`.
10
+ Assumes `page.md` and `component.md` rules.
11
+
12
+ ## File Structure
13
+
14
+ ```
15
+ {screen-path}/
16
+ ├── components/
17
+ │ └── {screen-name}-table.tsx # Table component with fragments
18
+ └── page.tsx
19
+ ```
20
+
21
+ ## Page Component (page.tsx)
22
+
23
+ Data fetching and `Layout` must be co-located in the same page component.
24
+ Do NOT split into an inner Content component — `Layout` requires `Layout.Column` as direct children.
25
+
26
+ ```tsx
27
+ const ResourcesQuery = graphql(
28
+ `
29
+ query Resources {
30
+ resources {
31
+ ...ResourceTable
32
+ }
33
+ }
34
+ `,
35
+ [ResourceTableFragment],
36
+ );
37
+
38
+ const ResourcesPage = () => {
39
+ const [{ data, error, fetching }, reexecuteQuery] = useQuery({
40
+ query: ResourcesQuery,
41
+ });
42
+
43
+ if (fetching) return <Loading />;
44
+
45
+ if (error || !data) {
46
+ return (
47
+ <ErrorFallback
48
+ title="Failed to load resources"
49
+ message="An error occurred while fetching the list."
50
+ onReset={() => reexecuteQuery({ requestPolicy: "network-only" })}
51
+ />
52
+ );
53
+ }
54
+
55
+ return (
56
+ <Layout
57
+ columns={1}
58
+ title="Resources"
59
+ actions={[
60
+ <Button key="create" asChild>
61
+ <Link to="create">Create</Link>
62
+ </Button>,
63
+ ]}
64
+ >
65
+ <Layout.Column>
66
+ <ResourceTable data={data.resources} />
67
+ </Layout.Column>
68
+ </Layout>
69
+ );
70
+ };
71
+ ```
72
+
73
+ ## Table Component (components/{screen-name}-table.tsx)
74
+
75
+ ### Fragment Collocation
76
+
77
+ Define a row fragment and a table fragment wrapping the Connection type.
78
+
79
+ ```tsx
80
+ const ResourceRowFragment = graphql(`
81
+ fragment ResourceRow on Resource {
82
+ id
83
+ title
84
+ status
85
+ createdAt
86
+ }
87
+ `);
88
+
89
+ export const ResourceTableFragment = graphql(
90
+ `
91
+ fragment ResourceTable on ResourceConnection {
92
+ edges {
93
+ node {
94
+ ...ResourceRow
95
+ }
96
+ }
97
+ }
98
+ `,
99
+ [ResourceRowFragment],
100
+ );
101
+ ```
102
+
103
+ ### Row Component
104
+
105
+ ```tsx
106
+ const ResourceRow = ({ resource: resourceFragment }: ResourceRowProps) => {
107
+ const resource = readFragment(ResourceRowFragment, resourceFragment);
108
+ return (
109
+ <TableRow>
110
+ <TableCell>{resource.title}</TableCell>
111
+ <TableCell>
112
+ <Badge variant={resource.status === "ACTIVE" ? "default" : "secondary"}>
113
+ {resource.status}
114
+ </Badge>
115
+ </TableCell>
116
+ <TableCell>
117
+ <Button variant="ghost" size="sm" asChild>
118
+ <Link to={resource.id}>View</Link>
119
+ </Button>
120
+ </TableCell>
121
+ </TableRow>
122
+ );
123
+ };
124
+ ```
125
+
126
+ ### Empty State
127
+
128
+ ```tsx
129
+ if (connection.edges.length === 0) {
130
+ return (
131
+ <EmptyState
132
+ title="No resources"
133
+ message="Get started by creating a new resource."
134
+ action={
135
+ <Button asChild>
136
+ <Link to="create">Create</Link>
137
+ </Button>
138
+ }
139
+ />
140
+ );
141
+ }
142
+ ```
143
+
144
+ ## Column Property Mapping
145
+
146
+ | Property | Implementation |
147
+ | --------------- | ----------------------------------------------------------- |
148
+ | Hideable: Yes | Column visibility state to toggle show/hide |
149
+ | Sortable: Yes | Sort icon on `<TableHead>` + onClick to toggle query vars |
150
+ | Filterable: Yes | Filter UI above table (`<Select />`) |
151
+ | Searchable: Yes | Search input above table (`<Input placeholder="Search" />`) |
152
+
153
+ ## Key Points
154
+
155
+ - Use `<Badge />` for status columns
156
+ - Format dates with `toLocaleDateString()`
157
+ - Use `<EmptyState />` with Create action for empty lists
158
+ - Add a View button per row to navigate to the detail page
159
+ - Iterate data using Connection type `edges.node` pattern
@@ -0,0 +1,32 @@
1
+ ---
2
+ paths:
3
+ - "examples/**/src/"
4
+ ---
5
+
6
+ # Application Directory Structure
7
+
8
+ ```
9
+ {app_name}/
10
+ ├── backend/
11
+ │ ├── src/
12
+ │ │ ├── modules.ts # Declaring module usage
13
+ │ │ ├── modules/
14
+ │ │ │ └── {module-name}/ # Module-specific directory
15
+ │ │ │ ├── resolvers/ # API Definition to expose graphql apis
16
+ │ │ │ └── executors/ # PubSub Automation (one file per declaration)
17
+ │ │ └── generated/ # Auto-generated code (do not edit)
18
+ │ └── tailor.config.ts # tailor application config
19
+
20
+ └── frontend/
21
+ └── src/
22
+ ├── pages/ # File-based routing (auto-discovered by Vite plugin)
23
+ │ └── {page-path}/
24
+ │ ├── page.tsx
25
+ │ └── {page-path}/
26
+ │ ├── components/
27
+ │ └── page.tsx
28
+ ├── components/
29
+ │ └── ui/ # Generic UI components
30
+ ├── graphql/ # gql.tada settings
31
+ └── providers/ # react providers
32
+ ```
@@ -0,0 +1,54 @@
1
+ ---
2
+ paths:
3
+ - "modules/*/src/command/*.ts"
4
+ - "!modules/*/src/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,28 @@
1
+ ---
2
+ paths:
3
+ - "modules/*/src/db/*.ts"
4
+ - "modules/*/src/module.ts"
5
+ ---
6
+
7
+ # Cross-Module Type Injection
8
+
9
+ ## Typing External Module References
10
+
11
+ - Derive types from the source module's `defineModule` return type, never use `any`
12
+ - Use `import type` for type-only imports
13
+ - All modules live in the same package (`@tailor-platform/erp-kit`), so use relative paths
14
+ - Define a local type alias for readability: `type UnitType = ReturnType<typeof definePrimitivesModule>["unit"]`
15
+
16
+ ## DB Type Creator Pattern (`src/db/*.ts`)
17
+
18
+ - Accept optional external type via `Create*TypeParams` (e.g., `unitType?: UnitType`)
19
+ - Use a ternary on the param to conditionally add `.relation()`:
20
+ - **Present**: `db.uuid().relation({ type: "n-1", toward: { type: param }, backward: "..." })`
21
+ - **Absent**: `db.uuid()` (plain UUID, no relation) — preserves standalone usage
22
+ - Export a default instance at file bottom with `{}` params for internal/standalone use
23
+
24
+ ## Module Wiring (`src/module.ts`)
25
+
26
+ - Group external dependencies under a `primitives?` key in `DefineModuleParams`
27
+ - Spread consumer's `Create*TypeParams` and merge the injected type: `{ ...params.productTemplate, unitType: params.primitives?.unit }`
28
+ - Export `DefineModuleParams` type from `index.ts` so consumers can type their config
@@ -0,0 +1,24 @@
1
+ ---
2
+ paths:
3
+ - "modules/*/src/"
4
+ ---
5
+
6
+ # Dependency Modules
7
+
8
+ When a module depends on another module, create `modules/<module-name>/src/dep.ts` to instantiate and re-export the dependency:
9
+
10
+ ```typescript
11
+ import { defineModule } from "../../primitives/src/module";
12
+
13
+ const primitives = defineModule();
14
+
15
+ // Re-export db types for use in this module's db definitions and commands
16
+ export const { unit, currency } = primitives.db;
17
+ ```
18
+
19
+ ## Module Return Structure
20
+
21
+ `defineModule` returns an object with:
22
+
23
+ - `db`: Object of database model types (keyed by name)
24
+ - `executors`: Object of executor instances (keyed by name, if any)
@@ -0,0 +1,12 @@
1
+ ---
2
+ paths:
3
+ - "modules/*/src/lib/errors.ts"
4
+ ---
5
+
6
+ # Error Classes
7
+
8
+ Naming convention:
9
+
10
+ - `name`: Class name exactly, `as const`
11
+ - `code`: SCREAMING_SNAKE_CASE, `as const`
12
+ - Constructor includes context (IDs, values)
@@ -0,0 +1,67 @@
1
+ ---
2
+ paths:
3
+ - "modules/*/src/executor/"
4
+ - "modules/*/src/module.ts"
5
+ ---
6
+
7
+ # Executors
8
+
9
+ Executors handle asynchronous operations triggered by database record changes.
10
+
11
+ ## Factory Pattern
12
+
13
+ Executors are **factory functions** that accept configuration and return an executor:
14
+
15
+ ```typescript
16
+ export const myExecutor = function myExecutor({ namespace }: { namespace: string }) {
17
+ return createExecutor({
18
+ name: "myExecutor",
19
+ // ... executor config
20
+ });
21
+ };
22
+ ```
23
+
24
+ **Why factory functions:**
25
+
26
+ - Executors need runtime configuration (db namespace) not known at import time
27
+ - Named function expression enables better stack traces
28
+ - Module consumers control configuration via `defineModule` params
29
+
30
+ ## File Organization
31
+
32
+ - Place in `src/executor/` directory
33
+ - Group related executors in one file (e.g., `recomputeOnRolePermissionChange.ts`)
34
+ - Name files after the operation, not the trigger
35
+
36
+ ## Executor Structure
37
+
38
+ Required fields:
39
+
40
+ - `name`: Matches function name exactly
41
+ - `description`: Human-readable purpose
42
+ - `trigger`: `recordCreatedTrigger` or `recordDeletedTrigger` with `type`
43
+ - `operation`: `kind: "jobFunction"` with async `body`
44
+
45
+ ## Database Access
46
+
47
+ Use namespace parameter with `getDB`:
48
+
49
+ ```typescript
50
+ // @ts-expect-error unsure at build time
51
+ const db = getDB(namespace);
52
+ ```
53
+
54
+ The `@ts-expect-error` comment is required because the namespace is validated at runtime.
55
+
56
+ ## Module Integration
57
+
58
+ `defineModule` returns executors in a dedicated array:
59
+
60
+ ```typescript
61
+ return {
62
+ db: [user, role, ...],
63
+ executors: [rolePermissionCreated, rolePermissionDeleted],
64
+ };
65
+ ```
66
+
67
+ Instantiate executors in `module.ts` with the `dbNamespace` parameter.
@@ -0,0 +1,13 @@
1
+ ---
2
+ paths:
3
+ - "modules/*/src/index.ts"
4
+ ---
5
+
6
+ # Module Exports
7
+
8
+ Export order:
9
+
10
+ 1. `defineModule` from module.ts
11
+ 2. Generated types (enum values + types separately)
12
+ 3. Error classes (for typed catch blocks)
13
+ 4. Domain functions with input types
@@ -0,0 +1,34 @@
1
+ ---
2
+ paths:
3
+ - "modules/*/src/db/*.ts"
4
+ ---
5
+
6
+ # Database Models
7
+
8
+ ## Factory Function Pattern
9
+
10
+ Each model is a `createXType<const F>(params)` factory:
11
+
12
+ - Params interface generic: `F extends Record<string, TailorAnyDBField>`
13
+ - `fields?: F` — optional custom fields from parent modules
14
+ - Spread as `...(params.fields ?? {}) as F` to preserve type information
15
+ - Include `...db.fields.timestamps()`
16
+ - Use `.description()` for field docs
17
+ - Apply permissions at model level
18
+ - Export a default instance: `export const x = createXType({})`
19
+
20
+ ## Stateful Model Enums
21
+
22
+ Status enums are tied to the module's state machine. Base module defines core statuses; parent callers can only **extend, not replace**.
23
+
24
+ ```typescript
25
+ // Good: extension only
26
+ const BASE_STATUSES = ["PENDING", "ACTIVE", "INACTIVE"] as const;
27
+ const statuses = [...BASE_STATUSES, ...(params.additionalStatuses ?? [])];
28
+
29
+ // Bad: allows replacement
30
+ const statuses = params.statuses ?? ["PENDING", "ACTIVE", "INACTIVE"];
31
+ ```
32
+
33
+ - Name parameter `additionalX` to signal extension-only
34
+ - Parent modules handle their additional status transitions
@@ -0,0 +1,27 @@
1
+ ---
2
+ paths:
3
+ - "modules/*/src/"
4
+ ---
5
+
6
+ # Module Directory Structure
7
+
8
+ ```
9
+ src/
10
+ ├── db/ # Database models (one file per model)
11
+ ├── executor/ # Async executors (record triggers, job functions)
12
+ ├── function/ # Domain functions + tests (*.test.ts co-located)
13
+ ├── lib/ # Internal shared code (errors.ts, types.ts)
14
+ ├── testing/ # Test fixtures and helpers
15
+ ├── generated/ # Auto-generated code (do not edit)
16
+ ├── index.ts # Public exports
17
+ └── module.ts # Module definition
18
+ ```
19
+
20
+ ## Rules
21
+
22
+ - `db/`: Only documentable model definitions, no helpers
23
+ - `executor/`: Async executors as factory functions (see executors.md)
24
+ - `function/`: Domain functions + co-located tests, no utilities
25
+ - `lib/`: Internal errors and types (not documented)
26
+ - `testing/`: Fixtures for tests only
27
+ - Run `pnpm generate` after modifying `db/` models
@@ -0,0 +1,83 @@
1
+ ---
2
+ paths:
3
+ - "modules/*/src/"
4
+ ---
5
+
6
+ # Sync vs Async Operations
7
+
8
+ ## Decision Criteria
9
+
10
+ Choose synchronous or asynchronous execution based on:
11
+
12
+ 1. **Number of affected records** - How many records need processing?
13
+ 2. **Data growth trajectory** - Will the operation slow down as data grows?
14
+
15
+ | Scenario | Approach | Implementation |
16
+ | ----------------------------------- | ------------------ | --------------------------- |
17
+ | Single record, bounded complexity | **Synchronous** | Inline in command |
18
+ | Single record, unbounded complexity | **Consider async** | Evaluate growth |
19
+ | Multiple records | **Asynchronous** | Executor with `jobFunction` |
20
+
21
+ ## Growth Considerations
22
+
23
+ Ask: "How does this operation scale as the system grows?"
24
+
25
+ - **Bounded**: User status update (always 1 record, O(1))
26
+ - **Bounded**: Single user permission recompute (limited by reasonable role/permission counts)
27
+ - **Unbounded**: All users with role X (grows with user base, O(n))
28
+ - **Unbounded**: All orders in date range (grows with transaction volume)
29
+
30
+ When in doubt, monitor operation timing in production and migrate to async if latency increases.
31
+
32
+ ## Rationale
33
+
34
+ - **Synchronous**: Acceptable when operation time is predictable and bounded
35
+ - **Asynchronous**: Required when operation time scales with data volume
36
+
37
+ ## Pattern: Synchronous (Single Record)
38
+
39
+ Commands that affect a single known record should execute the operation inline and return the result:
40
+
41
+ ```typescript
42
+ // In command - recompute immediately
43
+ const updatedUser = await recomputeUserPermissions(db, input.userId);
44
+ return { userRole, user: updatedUser, auditEvent };
45
+ ```
46
+
47
+ ## Pattern: Asynchronous (Multiple Records)
48
+
49
+ Commands that affect an unknown number of records should delegate to an executor:
50
+
51
+ ```typescript
52
+ // Command just creates/deletes the record
53
+ // Executor handles recomputation asynchronously
54
+ ```
55
+
56
+ Create executors with record triggers:
57
+
58
+ - `recordCreatedTrigger` - React to inserts
59
+ - `recordDeletedTrigger` - React to deletes
60
+ - Use `kind: "jobFunction"` for extended execution
61
+
62
+ ## Example: Permission Recomputation
63
+
64
+ | Command | Affected | Approach |
65
+ | ------------------------ | ----------------------- | --------------- |
66
+ | assignRoleToUser | 1 user | Sync in command |
67
+ | revokeRoleFromUser | 1 user | Sync in command |
68
+ | assignPermissionToRole | N users (all with role) | Async executor |
69
+ | revokePermissionFromRole | N users (all with role) | Async executor |
70
+
71
+ ## Trade-offs
72
+
73
+ **Synchronous:**
74
+
75
+ - Immediate consistency
76
+ - Simpler error handling
77
+ - Blocks request until complete
78
+
79
+ **Asynchronous:**
80
+
81
+ - Eventual consistency
82
+ - Non-blocking requests
83
+ - Requires executor infrastructure
@@ -0,0 +1,43 @@
1
+ ---
2
+ paths:
3
+ - "modules/*/src/command/*.test.ts"
4
+ - "modules/*/src/testing/*.ts"
5
+ ---
6
+
7
+ # Testing Patterns
8
+
9
+ ## Test Coverage Goal
10
+
11
+ Tests should cover all paths in the corresponding `docs/commands/*.md`:
12
+
13
+ - **Process Flow**: Each branch in the mermaid flowchart = one test case
14
+ - **Error Scenarios**: Each error code listed = one test case
15
+ - **Idempotent paths**: If flowchart shows "Already exists? → Return existing"
16
+
17
+ ## Mock Database
18
+
19
+ ```typescript
20
+ const { db, spies } = createMockDb<DB>();
21
+
22
+ // Single return
23
+ spies.select.mockReturnValue(entity);
24
+
25
+ // Sequential returns (in query execution order)
26
+ spies.select.mockReturnValueOnce(first).mockReturnValueOnce(second);
27
+ ```
28
+
29
+ ## Custom Fields Passthrough
30
+
31
+ For `makeCreateX` factory commands, add one test verifying custom fields reach `.values()`:
32
+
33
+ - Instantiate with concrete type: `makeCreateX<{ myField: string }>()`
34
+ - Assert with `spies.values`: `expect(spies.values).toHaveBeenNthCalledWith(1, expect.objectContaining({ myField: "value" }))`
35
+ - Use `toHaveBeenNthCalledWith(n, ...)` when multiple inserts occur (e.g., audit events)
36
+
37
+ ## Fixtures (`src/testing/fixtures.ts`)
38
+
39
+ - Import `Schema` from `lib/types` (not `Namespace` from generated code)
40
+ - Pattern: `export const baseEntity = { ... } as const satisfies Entity<Schema>`
41
+ - Fixed IDs for traceability: `"entity-1"`
42
+ - Consistent timestamp: `new Date("2024-01-01T00:00:00.000Z")`
43
+ - `updatedAt: null` for base fixtures
@@ -0,0 +1,74 @@
1
+ ---
2
+ paths:
3
+ - "modules/*/src/db/*.ts"
4
+ ---
5
+
6
+ # Database Relations
7
+
8
+ ## Relation Pattern
9
+
10
+ Use `.relation()` for foreign keys instead of plain `db.uuid()`:
11
+
12
+ ```typescript
13
+ userId: db.uuid().relation({
14
+ type: "n-1",
15
+ toward: { type: user },
16
+ backward: "userRoles",
17
+ }).description("Foreign key to User"),
18
+ ```
19
+
20
+ ### Parameters
21
+
22
+ - `type`: `"n-1"` (many-to-one), `"1-1"` (one-to-one), or `"keyOnly"` (FK only, no navigation)
23
+ - `toward.type`: The related type definition (import it)
24
+ - `backward`: Field name for reverse navigation from the related type
25
+
26
+ ### Benefits over plain `db.uuid()`
27
+
28
+ - Foreign key constraints (referential integrity)
29
+ - Forward navigation: `UserRole.user`
30
+ - Backward navigation: `User.userRoles`
31
+ - GraphQL relational queries
32
+
33
+ ## Junction Tables (Many-to-Many)
34
+
35
+ Junction tables need relations on both foreign keys plus a composite unique index:
36
+
37
+ ```typescript
38
+ import { user } from "./user";
39
+ import { role } from "./role";
40
+
41
+ db.type("UserRole", {
42
+ userId: db.uuid().relation({
43
+ type: "n-1",
44
+ toward: { type: user },
45
+ backward: "userRoles",
46
+ }),
47
+ roleId: db.uuid().relation({
48
+ type: "n-1",
49
+ toward: { type: role },
50
+ backward: "userRoles",
51
+ }),
52
+ }).indexes({
53
+ fields: ["userId", "roleId"],
54
+ unique: true,
55
+ name: "user_role_unique_idx",
56
+ });
57
+ ```
58
+
59
+ ### Naming Conventions
60
+
61
+ - `backward` field: Use plural of the junction table name (e.g., `"userRoles"`, `"rolePermissions"`)
62
+ - Index name: `{table}_unique_idx` pattern
63
+
64
+ ## Composite Indexes
65
+
66
+ Use `.indexes()` (not `.uniqueIndex()`) for multi-column constraints:
67
+
68
+ ```typescript
69
+ .indexes({
70
+ fields: ["fieldA", "fieldB"],
71
+ unique: true,
72
+ name: "descriptive_idx_name",
73
+ })
74
+ ```
@@ -0,0 +1,14 @@
1
+ ---
2
+ paths:
3
+ - "modules/*/src/db/*.ts"
4
+ - "modules/*/src/executor/*.ts"
5
+ - "modules/*/src/workflow/*.ts"
6
+ ---
7
+
8
+ # SDK Docs Reference
9
+
10
+ Read the official Tailor SDK documentation for database models, executors:
11
+
12
+ - [TailorDB Models](https://raw.githubusercontent.com/tailor-platform/sdk/refs/heads/main/packages/sdk/docs/services/tailordb.md)
13
+ - [Executors](https://raw.githubusercontent.com/tailor-platform/sdk/refs/heads/main/packages/sdk/docs/services/executors.md)
14
+ - [Workflow](https://raw.githubusercontent.com/tailor-platform/sdk/refs/heads/main/packages/sdk/docs/services/workflow.md)