create-m5kdev 0.41.0 → 0.43.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/package.json +1 -1
- package/templates/minimal-app/.cursor/rules/frontend-component-guide.mdc +95 -95
- package/templates/minimal-app/.cursor/rules/frontend-data-guide.mdc +103 -103
- package/templates/minimal-app/.cursor/rules/frontend-form-guide.mdc +80 -80
- package/templates/minimal-app/.cursor/rules/frontend-hook-guide.mdc +79 -79
- package/templates/minimal-app/.cursor/rules/frontend-i18n-guide.mdc +45 -45
- package/templates/minimal-app/.cursor/rules/module-dto-guide.mdc +60 -60
- package/templates/minimal-app/.cursor/rules/module-prompt-guide.mdc +214 -214
- package/templates/minimal-app/.cursor/rules/module-repository-guide.mdc +167 -167
- package/templates/minimal-app/.cursor/rules/module-schema-guide.mdc +53 -53
- package/templates/minimal-app/.cursor/rules/module-service-guide.mdc +367 -367
- package/templates/minimal-app/.cursor/rules/module-trpc-guide.mdc +71 -71
- package/templates/minimal-app/.github/workflows/check.yml.tpl +21 -0
- package/templates/minimal-app/AGENTS.md.tpl +22 -31
- package/templates/minimal-app/CLAUDE.md.tpl +17 -3
- package/templates/minimal-app/GLOSSARY.md.tpl +181 -0
- package/templates/minimal-app/README.md.tpl +14 -6
- package/templates/minimal-app/apps/.gitignore.tpl +13 -13
- package/templates/minimal-app/apps/e2e/package.json.tpl +23 -23
- package/templates/minimal-app/apps/e2e/servers.ts.tpl +22 -6
- package/templates/minimal-app/apps/email/package.json.tpl +34 -34
- package/templates/minimal-app/apps/expo/.gitignore.tpl +44 -44
- package/templates/minimal-app/apps/expo/.prettierignore +13 -13
- package/templates/minimal-app/apps/expo/.prettierrc.json.tpl +9 -9
- package/templates/minimal-app/apps/expo/CHANGELOG.md.tpl +156 -156
- package/templates/minimal-app/apps/expo/README.md.tpl +44 -44
- package/templates/minimal-app/apps/expo/expo-env.d.ts.tpl +3 -0
- package/templates/minimal-app/apps/expo/package.json.tpl +63 -63
- package/templates/minimal-app/apps/expo/uniwind-types.d.ts.tpl +10 -0
- package/templates/minimal-app/apps/landing/package.json.tpl +34 -34
- package/templates/minimal-app/apps/server/AGENTS.md.tpl +1 -1
- package/templates/minimal-app/apps/server/drizzle/0000_whole_echo.sql +366 -366
- package/templates/minimal-app/apps/server/drizzle/0002_crazy_lilith.sql +46 -46
- package/templates/minimal-app/apps/server/drizzle/0003_freezing_fantastic_four.sql +3 -3
- package/templates/minimal-app/apps/server/drizzle/0004_slow_ezekiel_stane.sql +98 -98
- package/templates/minimal-app/apps/server/drizzle/0005_wonderful_the_professor.sql +1 -1
- package/templates/minimal-app/apps/server/drizzle/0006_lazy_blue_marvel.sql +158 -158
- package/templates/minimal-app/apps/server/drizzle/0007_abnormal_ultimo.sql +1 -1
- package/templates/minimal-app/apps/server/drizzle/0008_steady_nekra.sql +86 -86
- package/templates/minimal-app/apps/server/drizzle/meta/0000_snapshot.json.tpl +2624 -2624
- package/templates/minimal-app/apps/server/drizzle/meta/0001_snapshot.json.tpl +2670 -2670
- package/templates/minimal-app/apps/server/drizzle/meta/0002_snapshot.json.tpl +2715 -2715
- package/templates/minimal-app/apps/server/drizzle/meta/0003_snapshot.json.tpl +2687 -2687
- package/templates/minimal-app/apps/server/drizzle/meta/0004_snapshot.json.tpl +2497 -2497
- package/templates/minimal-app/apps/server/drizzle/meta/0005_snapshot.json.tpl +2483 -2483
- package/templates/minimal-app/apps/server/drizzle/meta/0006_snapshot.json.tpl +3602 -3602
- package/templates/minimal-app/apps/server/drizzle/meta/0007_snapshot.json.tpl +3619 -3619
- package/templates/minimal-app/apps/server/drizzle/meta/0008_snapshot.json.tpl +3619 -3619
- package/templates/minimal-app/apps/server/drizzle/meta/_journal.json.tpl +76 -76
- package/templates/minimal-app/apps/server/drizzle-postgresql/0000_glorious_warhawk.sql +522 -0
- package/templates/minimal-app/apps/server/drizzle-postgresql/meta/0000_snapshot.json.tpl +3555 -0
- package/templates/minimal-app/apps/server/drizzle-postgresql/meta/_journal.json.tpl +13 -0
- package/templates/minimal-app/apps/server/drizzle.config.ts.tpl +1 -1
- package/templates/minimal-app/apps/server/mastra.ts.tpl +42 -0
- package/templates/minimal-app/apps/server/package.json.tpl +9 -4
- package/templates/minimal-app/apps/server/src/app.ts.tpl +21 -16
- package/templates/minimal-app/apps/server/src/drizzle.schema.ts.tpl +2 -0
- package/templates/minimal-app/apps/server/src/modules/conversation/conversation.mastra.ts.tpl +48 -0
- package/templates/minimal-app/apps/server/src/modules/conversation/conversation.persistence.ts.tpl +47 -0
- package/templates/minimal-app/apps/server/src/modules/posts/posts.db.ts.tpl +7 -6
- package/templates/minimal-app/apps/server/src/modules/posts/posts.repository.ts.tpl +2 -2
- package/templates/minimal-app/apps/shared/.env.example.tpl +6 -3
- package/templates/minimal-app/apps/shared/.env.production.example.tpl +7 -2
- package/templates/minimal-app/apps/shared/.env.tpl +6 -3
- package/templates/minimal-app/apps/shared/Dockerfile.pgbouncer +6 -0
- package/templates/minimal-app/apps/shared/compose.yaml.tpl +27 -0
- package/templates/minimal-app/apps/shared/fly.toml.tpl +0 -4
- package/templates/minimal-app/apps/shared/package.json.tpl +27 -27
- package/templates/minimal-app/apps/shared/pgbouncer.ini +13 -0
- package/templates/minimal-app/apps/shared/pgbouncer.users +1 -0
- package/templates/minimal-app/apps/webapp/AGENTS.md.tpl +24 -24
- package/templates/minimal-app/apps/webapp/index.html.tpl +22 -22
- package/templates/minimal-app/apps/webapp/package.json.tpl +53 -53
- package/templates/minimal-app/apps/webapp/public/logo.svg.tpl +6 -6
- package/templates/minimal-app/apps/webapp/translations/en/{{APP_SLUG}}.json.tpl +1 -1
- package/templates/minimal-app/biome.json.tpl +1 -0
- package/templates/minimal-app/docs/adr/.gitkeep +0 -0
- package/templates/minimal-app/docs/agents/domain.md.tpl +38 -0
- package/templates/minimal-app/docs/agents/issue-tracker.md.tpl +45 -0
- package/templates/minimal-app/docs/agents/triage-labels.md.tpl +15 -0
- package/templates/minimal-app/package.json.tpl +5 -0
- package/templates/minimal-app/pnpm-workspace.yaml.tpl +16 -13
- package/templates/minimal-app/template.manifest.json +21 -0
|
@@ -1,167 +1,167 @@
|
|
|
1
|
-
---
|
|
2
|
-
globs: apps/**/server/src/modules/**/*.repository.ts
|
|
3
|
-
alwaysApply: false
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Module Repository File Style Guide
|
|
7
|
-
|
|
8
|
-
Conventions for repository classes in app modules. Repositories consume the
|
|
9
|
-
module DTO object (`postSchemas`) for query inputs/outputs — they do not
|
|
10
|
-
import hand-written types.
|
|
11
|
-
|
|
12
|
-
## File Location
|
|
13
|
-
|
|
14
|
-
```
|
|
15
|
-
apps/<app>/server/src/modules/<module>/<module>.repository.ts
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
## Conventions
|
|
19
|
-
|
|
20
|
-
- Extend `BaseTableRepository<Orm, Schema, NestedRepos, Schema["table"]>` for table-bound modules.
|
|
21
|
-
- Use `BaseRepository` for non-table or multi-table repositories.
|
|
22
|
-
- Keep repositories data-focused: persistence, query composition, and lightweight mapping only.
|
|
23
|
-
- Do not import service classes into repositories.
|
|
24
|
-
- Use Drizzle operators (`and`, `eq`, `inArray`, `isNull`, `like`, etc.) and query helpers from base classes.
|
|
25
|
-
- When running inside `this.orm.transaction`, use `tx` consistently; avoid mixing `this.orm` calls in the same transaction flow.
|
|
26
|
-
- Leave orchestration, policy decisions, and side effects to services.
|
|
27
|
-
|
|
28
|
-
## Orm/Schema Type Pattern
|
|
29
|
-
|
|
30
|
-
```typescript
|
|
31
|
-
import type { LibSQLDatabase } from "drizzle-orm/libsql";
|
|
32
|
-
import { posts } from "./posts.db";
|
|
33
|
-
|
|
34
|
-
const schema = { posts };
|
|
35
|
-
type Schema = typeof schema;
|
|
36
|
-
type Orm = LibSQLDatabase<Schema>;
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
## Prefer Built-in Base Methods (Strong Default)
|
|
40
|
-
|
|
41
|
-
`BaseTableRepository` provides typed CRUD helpers. **Always use these before
|
|
42
|
-
adding custom repository methods or raw Drizzle.** Services should call these
|
|
43
|
-
helpers directly in most cases.
|
|
44
|
-
|
|
45
|
-
- `queryList(query, options)` — filtered/sorted/paginated lists (`page`,
|
|
46
|
-
`limit`, `sort`, `order`, `q`, `filters`). Pass `globalSearchColumns` for
|
|
47
|
-
`q`, `conditions` for extra scoping, `showDeleted` to include soft-deleted
|
|
48
|
-
rows (excluded by default when the table has `deletedAt`).
|
|
49
|
-
- `queryFind(query, options)` — filtered/sorted single-row lookup (no
|
|
50
|
-
pagination/count). Same filter/`conditions` contract as `queryList`.
|
|
51
|
-
- `findById(id)` / `findManyById(ids)` — primary-key lookups.
|
|
52
|
-
- `create(data)` / `createMany(data)` — inserts.
|
|
53
|
-
- `update(data)` / `updateMany(data)` — updates.
|
|
54
|
-
- `softDeleteById(id)` / `deleteById(id)` — deletes.
|
|
55
|
-
|
|
56
|
-
```typescript
|
|
57
|
-
// Preferred from the service — no custom repository list method
|
|
58
|
-
this.repository.posts.queryList(input, {
|
|
59
|
-
globalSearchColumns: ["title", "excerpt", "content"],
|
|
60
|
-
});
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
### Usually Do Not Add Custom List / Getter / Create / Update Methods
|
|
64
|
-
|
|
65
|
-
- **Lists and getters**: `queryList` / `queryFind` already expose read APIs
|
|
66
|
-
via `filters` (and optional `conditions`). Prefer calling them from the
|
|
67
|
-
service with the needed filters (or `.addFilters` on the procedure). Do not
|
|
68
|
-
add thin `listPublished`, `findBySlug`, `getMain`, etc. unless the same
|
|
69
|
-
filter set is reused heavily *and* belongs in the repository boundary.
|
|
70
|
-
- **Creates**: `create` is permissive — stamp defaults (`status`, `memberId`,
|
|
71
|
-
…) in the **service** and pass them to `create`. Do not add a custom
|
|
72
|
-
repository `create*` just to apply defaults.
|
|
73
|
-
- **Updates**: `update` accepts partial payloads — omit fields you do not
|
|
74
|
-
want to change. Do not add a custom repository `update*` just to restrict
|
|
75
|
-
columns or merge patches.
|
|
76
|
-
- Write raw `this.orm.select()...` or a custom method only for joins, CTEs,
|
|
77
|
-
multi-table writes, transactions, or predicates that cannot be expressed
|
|
78
|
-
with filters / `TableConditionBuilder`.
|
|
79
|
-
|
|
80
|
-
Filter shape: `{ columnId, type, method, value }` (`type`: `string` |
|
|
81
|
-
`number` | `date` | `boolean` | `enum` | `jsonArray`; common `method`s:
|
|
82
|
-
`equals`, `contains`, `oneOf`, `is_null`, …). Soft-delete exclusion is
|
|
83
|
-
applied automatically when the table has `deletedAt` (unless
|
|
84
|
-
`showDeleted: true`).
|
|
85
|
-
|
|
86
|
-
## Custom queries: the `this.query()` builder
|
|
87
|
-
|
|
88
|
-
Use the builder only when a shared lookup truly belongs in the repository.
|
|
89
|
-
Wire DTO schemas for inputs/outputs. **Outputs are validated at runtime** —
|
|
90
|
-
a find-style query that can return no row must use `output.found` (the
|
|
91
|
-
optional row schema), or validation will fail on the legitimate "not found"
|
|
92
|
-
case.
|
|
93
|
-
|
|
94
|
-
Wrap `queryFind` / `queryList` with filters — never reimplement
|
|
95
|
-
select/where/limit:
|
|
96
|
-
|
|
97
|
-
```typescript
|
|
98
|
-
import { z } from "zod";
|
|
99
|
-
import { postSchemas } from "./posts.dto";
|
|
100
|
-
|
|
101
|
-
// Shared domain lookup used from multiple services/jobs — still no hand-written SQL
|
|
102
|
-
findBySlug = this.query("findBySlug")
|
|
103
|
-
.input(z.object({ slug: z.string() }))
|
|
104
|
-
.output(postSchemas.output.found) // NOT output.single — may return undefined
|
|
105
|
-
.handle(({ slug }) =>
|
|
106
|
-
this.queryFind({
|
|
107
|
-
filters: [
|
|
108
|
-
{ columnId: "slug", type: "string", method: "equals", value: slug },
|
|
109
|
-
],
|
|
110
|
-
})
|
|
111
|
-
);
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
### Extend With `conditions` When Filters Alone Are Not Enough
|
|
115
|
-
|
|
116
|
-
Pass a `TableConditionBuilder` via `options.conditions` for extra predicates
|
|
117
|
-
(optional excludes, `ne`, `inArray`, …) while still reusing soft-delete and
|
|
118
|
-
column picking from `queryFind` / `queryList`:
|
|
119
|
-
|
|
120
|
-
```typescript
|
|
121
|
-
import { ne } from "drizzle-orm";
|
|
122
|
-
import { z } from "zod";
|
|
123
|
-
import { postSchemas } from "./posts.dto";
|
|
124
|
-
|
|
125
|
-
// Prefer calling queryFind with conditions from the service when one-off;
|
|
126
|
-
// repository wrapper only if shared across callers.
|
|
127
|
-
findBySlug = this.query("findBySlug")
|
|
128
|
-
.input(
|
|
129
|
-
z.object({
|
|
130
|
-
slug: z.string(),
|
|
131
|
-
excludeId: z.string().optional(),
|
|
132
|
-
})
|
|
133
|
-
)
|
|
134
|
-
.output(postSchemas.output.found)
|
|
135
|
-
.handle(({ slug, excludeId }) => {
|
|
136
|
-
const conditions = this.getConditionBuilder(this.table);
|
|
137
|
-
if (excludeId) {
|
|
138
|
-
conditions.push(ne(this.table.id, excludeId));
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return this.queryFind(
|
|
142
|
-
{
|
|
143
|
-
filters: [
|
|
144
|
-
{ columnId: "slug", type: "string", method: "equals", value: slug },
|
|
145
|
-
],
|
|
146
|
-
},
|
|
147
|
-
{ conditions }
|
|
148
|
-
);
|
|
149
|
-
});
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
Use plain `async` methods instead of the builder only when the method needs an
|
|
153
|
-
optional `tx` parameter, wraps `this.orm.transaction()`, or orchestrates
|
|
154
|
-
multiple queries with branching between them.
|
|
155
|
-
|
|
156
|
-
## Error Handling
|
|
157
|
-
|
|
158
|
-
- Use `throwableQuery` tightly around each **raw** Drizzle call — it converts
|
|
159
|
-
thrown database errors into `ServerResultAsync`. Do not wrap whole method
|
|
160
|
-
bodies.
|
|
161
|
-
- Built-in helpers (`queryFind`, `queryList`, `findById`, `create`, …)
|
|
162
|
-
already use `throwableQuery` internally — do not wrap their return values
|
|
163
|
-
again.
|
|
164
|
-
- Chain results with `isErr()` checks and `err(result.error)`.
|
|
165
|
-
- Soft-deleted rows are excluded automatically by `queryList` / `queryFind`
|
|
166
|
-
when the table has `deletedAt` (unless `showDeleted: true`). Only add
|
|
167
|
-
`isNull(this.table.deletedAt)` in raw custom queries.
|
|
1
|
+
---
|
|
2
|
+
globs: apps/**/server/src/modules/**/*.repository.ts
|
|
3
|
+
alwaysApply: false
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Module Repository File Style Guide
|
|
7
|
+
|
|
8
|
+
Conventions for repository classes in app modules. Repositories consume the
|
|
9
|
+
module DTO object (`postSchemas`) for query inputs/outputs — they do not
|
|
10
|
+
import hand-written types.
|
|
11
|
+
|
|
12
|
+
## File Location
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
apps/<app>/server/src/modules/<module>/<module>.repository.ts
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Conventions
|
|
19
|
+
|
|
20
|
+
- Extend `BaseTableRepository<Orm, Schema, NestedRepos, Schema["table"]>` for table-bound modules.
|
|
21
|
+
- Use `BaseRepository` for non-table or multi-table repositories.
|
|
22
|
+
- Keep repositories data-focused: persistence, query composition, and lightweight mapping only.
|
|
23
|
+
- Do not import service classes into repositories.
|
|
24
|
+
- Use Drizzle operators (`and`, `eq`, `inArray`, `isNull`, `like`, etc.) and query helpers from base classes.
|
|
25
|
+
- When running inside `this.orm.transaction`, use `tx` consistently; avoid mixing `this.orm` calls in the same transaction flow.
|
|
26
|
+
- Leave orchestration, policy decisions, and side effects to services.
|
|
27
|
+
|
|
28
|
+
## Orm/Schema Type Pattern
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import type { LibSQLDatabase } from "drizzle-orm/libsql";
|
|
32
|
+
import { posts } from "./posts.db";
|
|
33
|
+
|
|
34
|
+
const schema = { posts };
|
|
35
|
+
type Schema = typeof schema;
|
|
36
|
+
type Orm = LibSQLDatabase<Schema>;
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Prefer Built-in Base Methods (Strong Default)
|
|
40
|
+
|
|
41
|
+
`BaseTableRepository` provides typed CRUD helpers. **Always use these before
|
|
42
|
+
adding custom repository methods or raw Drizzle.** Services should call these
|
|
43
|
+
helpers directly in most cases.
|
|
44
|
+
|
|
45
|
+
- `queryList(query, options)` — filtered/sorted/paginated lists (`page`,
|
|
46
|
+
`limit`, `sort`, `order`, `q`, `filters`). Pass `globalSearchColumns` for
|
|
47
|
+
`q`, `conditions` for extra scoping, `showDeleted` to include soft-deleted
|
|
48
|
+
rows (excluded by default when the table has `deletedAt`).
|
|
49
|
+
- `queryFind(query, options)` — filtered/sorted single-row lookup (no
|
|
50
|
+
pagination/count). Same filter/`conditions` contract as `queryList`.
|
|
51
|
+
- `findById(id)` / `findManyById(ids)` — primary-key lookups.
|
|
52
|
+
- `create(data)` / `createMany(data)` — inserts.
|
|
53
|
+
- `update(data)` / `updateMany(data)` — updates.
|
|
54
|
+
- `softDeleteById(id)` / `deleteById(id)` — deletes.
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
// Preferred from the service — no custom repository list method
|
|
58
|
+
this.repository.posts.queryList(input, {
|
|
59
|
+
globalSearchColumns: ["title", "excerpt", "content"],
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Usually Do Not Add Custom List / Getter / Create / Update Methods
|
|
64
|
+
|
|
65
|
+
- **Lists and getters**: `queryList` / `queryFind` already expose read APIs
|
|
66
|
+
via `filters` (and optional `conditions`). Prefer calling them from the
|
|
67
|
+
service with the needed filters (or `.addFilters` on the procedure). Do not
|
|
68
|
+
add thin `listPublished`, `findBySlug`, `getMain`, etc. unless the same
|
|
69
|
+
filter set is reused heavily *and* belongs in the repository boundary.
|
|
70
|
+
- **Creates**: `create` is permissive — stamp defaults (`status`, `memberId`,
|
|
71
|
+
…) in the **service** and pass them to `create`. Do not add a custom
|
|
72
|
+
repository `create*` just to apply defaults.
|
|
73
|
+
- **Updates**: `update` accepts partial payloads — omit fields you do not
|
|
74
|
+
want to change. Do not add a custom repository `update*` just to restrict
|
|
75
|
+
columns or merge patches.
|
|
76
|
+
- Write raw `this.orm.select()...` or a custom method only for joins, CTEs,
|
|
77
|
+
multi-table writes, transactions, or predicates that cannot be expressed
|
|
78
|
+
with filters / `TableConditionBuilder`.
|
|
79
|
+
|
|
80
|
+
Filter shape: `{ columnId, type, method, value }` (`type`: `string` |
|
|
81
|
+
`number` | `date` | `boolean` | `enum` | `jsonArray`; common `method`s:
|
|
82
|
+
`equals`, `contains`, `oneOf`, `is_null`, …). Soft-delete exclusion is
|
|
83
|
+
applied automatically when the table has `deletedAt` (unless
|
|
84
|
+
`showDeleted: true`).
|
|
85
|
+
|
|
86
|
+
## Custom queries: the `this.query()` builder
|
|
87
|
+
|
|
88
|
+
Use the builder only when a shared lookup truly belongs in the repository.
|
|
89
|
+
Wire DTO schemas for inputs/outputs. **Outputs are validated at runtime** —
|
|
90
|
+
a find-style query that can return no row must use `output.found` (the
|
|
91
|
+
optional row schema), or validation will fail on the legitimate "not found"
|
|
92
|
+
case.
|
|
93
|
+
|
|
94
|
+
Wrap `queryFind` / `queryList` with filters — never reimplement
|
|
95
|
+
select/where/limit:
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
import { z } from "zod";
|
|
99
|
+
import { postSchemas } from "./posts.dto";
|
|
100
|
+
|
|
101
|
+
// Shared domain lookup used from multiple services/jobs — still no hand-written SQL
|
|
102
|
+
findBySlug = this.query("findBySlug")
|
|
103
|
+
.input(z.object({ slug: z.string() }))
|
|
104
|
+
.output(postSchemas.output.found) // NOT output.single — may return undefined
|
|
105
|
+
.handle(({ slug }) =>
|
|
106
|
+
this.queryFind({
|
|
107
|
+
filters: [
|
|
108
|
+
{ columnId: "slug", type: "string", method: "equals", value: slug },
|
|
109
|
+
],
|
|
110
|
+
})
|
|
111
|
+
);
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Extend With `conditions` When Filters Alone Are Not Enough
|
|
115
|
+
|
|
116
|
+
Pass a `TableConditionBuilder` via `options.conditions` for extra predicates
|
|
117
|
+
(optional excludes, `ne`, `inArray`, …) while still reusing soft-delete and
|
|
118
|
+
column picking from `queryFind` / `queryList`:
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
import { ne } from "drizzle-orm";
|
|
122
|
+
import { z } from "zod";
|
|
123
|
+
import { postSchemas } from "./posts.dto";
|
|
124
|
+
|
|
125
|
+
// Prefer calling queryFind with conditions from the service when one-off;
|
|
126
|
+
// repository wrapper only if shared across callers.
|
|
127
|
+
findBySlug = this.query("findBySlug")
|
|
128
|
+
.input(
|
|
129
|
+
z.object({
|
|
130
|
+
slug: z.string(),
|
|
131
|
+
excludeId: z.string().optional(),
|
|
132
|
+
})
|
|
133
|
+
)
|
|
134
|
+
.output(postSchemas.output.found)
|
|
135
|
+
.handle(({ slug, excludeId }) => {
|
|
136
|
+
const conditions = this.getConditionBuilder(this.table);
|
|
137
|
+
if (excludeId) {
|
|
138
|
+
conditions.push(ne(this.table.id, excludeId));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return this.queryFind(
|
|
142
|
+
{
|
|
143
|
+
filters: [
|
|
144
|
+
{ columnId: "slug", type: "string", method: "equals", value: slug },
|
|
145
|
+
],
|
|
146
|
+
},
|
|
147
|
+
{ conditions }
|
|
148
|
+
);
|
|
149
|
+
});
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Use plain `async` methods instead of the builder only when the method needs an
|
|
153
|
+
optional `tx` parameter, wraps `this.orm.transaction()`, or orchestrates
|
|
154
|
+
multiple queries with branching between them.
|
|
155
|
+
|
|
156
|
+
## Error Handling
|
|
157
|
+
|
|
158
|
+
- Use `throwableQuery` tightly around each **raw** Drizzle call — it converts
|
|
159
|
+
thrown database errors into `ServerResultAsync`. Do not wrap whole method
|
|
160
|
+
bodies.
|
|
161
|
+
- Built-in helpers (`queryFind`, `queryList`, `findById`, `create`, …)
|
|
162
|
+
already use `throwableQuery` internally — do not wrap their return values
|
|
163
|
+
again.
|
|
164
|
+
- Chain results with `isErr()` checks and `err(result.error)`.
|
|
165
|
+
- Soft-deleted rows are excluded automatically by `queryList` / `queryFind`
|
|
166
|
+
when the table has `deletedAt` (unless `showDeleted: true`). Only add
|
|
167
|
+
`isNull(this.table.deletedAt)` in raw custom queries.
|
|
@@ -1,53 +1,53 @@
|
|
|
1
|
-
---
|
|
2
|
-
globs: apps/**/shared/src/modules/**/*.schema.ts
|
|
3
|
-
alwaysApply: false
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Shared Schema Style Guide
|
|
7
|
-
|
|
8
|
-
Shared schemas have a **deliberately narrow role**. Do not mirror server types
|
|
9
|
-
here — types flow from the server:
|
|
10
|
-
|
|
11
|
-
- Row and list types are **inferred from the tRPC router**:
|
|
12
|
-
`inferRouterOutputs<AppRouter>` with `AppRouter` from
|
|
13
|
-
`{{PACKAGE_SCOPE}}/server/types`.
|
|
14
|
-
- The API surface (inputs/outputs) is defined by the server module's DTO
|
|
15
|
-
(`<module>.dto.ts`), derived from the Drizzle table.
|
|
16
|
-
|
|
17
|
-
## When a schema belongs in shared
|
|
18
|
-
|
|
19
|
-
Put a Zod schema here only when at least one of these holds:
|
|
20
|
-
|
|
21
|
-
1. **The frontend needs it at runtime** — e.g. validating a form beyond native
|
|
22
|
-
HTML validation.
|
|
23
|
-
2. **Both sides genuinely share it** — then the server DTO must import it
|
|
24
|
-
(`postSchemas.input.create = postCreateInputSchema`), so the API contract
|
|
25
|
-
and the frontend validation are the same object and cannot drift.
|
|
26
|
-
3. **It cannot be re-exported from the server** because the server version
|
|
27
|
-
depends on Drizzle (`drizzle-zod` schemas) and shipping that dependency to
|
|
28
|
-
the client is not acceptable.
|
|
29
|
-
|
|
30
|
-
Everything else — row schemas, list outputs, trivial `{ id }` inputs — must
|
|
31
|
-
NOT be duplicated in shared. Hand-written mirrors silently drift from the real
|
|
32
|
-
contract.
|
|
33
|
-
|
|
34
|
-
```ts
|
|
35
|
-
// GOOD — form-validation schema, imported by both the DTO and the webapp form
|
|
36
|
-
export const postCreateInputSchema = z.object({
|
|
37
|
-
title: z.string().min(1),
|
|
38
|
-
slug: z.string().optional(),
|
|
39
|
-
excerpt: z.string().optional(),
|
|
40
|
-
content: z.string().min(1),
|
|
41
|
-
});
|
|
42
|
-
export type PostCreateInputSchema = z.infer<typeof postCreateInputSchema>;
|
|
43
|
-
|
|
44
|
-
// BAD — hand-mirrored row schema; the table already defines this shape
|
|
45
|
-
export const postSchema = z.object({ id: z.string(), title: z.string(), /* ... */ });
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
## Conventions
|
|
49
|
-
|
|
50
|
-
- File location: `apps/shared/src/modules/<module>/<module>.schema.ts`, with
|
|
51
|
-
enum values and constants in the companion `<module>.constants.ts`.
|
|
52
|
-
- Export both the schema and its inferred `type`.
|
|
53
|
-
- Build enums from constants: `z.enum(POST_STATUS_VALUES)`.
|
|
1
|
+
---
|
|
2
|
+
globs: apps/**/shared/src/modules/**/*.schema.ts
|
|
3
|
+
alwaysApply: false
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Shared Schema Style Guide
|
|
7
|
+
|
|
8
|
+
Shared schemas have a **deliberately narrow role**. Do not mirror server types
|
|
9
|
+
here — types flow from the server:
|
|
10
|
+
|
|
11
|
+
- Row and list types are **inferred from the tRPC router**:
|
|
12
|
+
`inferRouterOutputs<AppRouter>` with `AppRouter` from
|
|
13
|
+
`{{PACKAGE_SCOPE}}/server/types`.
|
|
14
|
+
- The API surface (inputs/outputs) is defined by the server module's DTO
|
|
15
|
+
(`<module>.dto.ts`), derived from the Drizzle table.
|
|
16
|
+
|
|
17
|
+
## When a schema belongs in shared
|
|
18
|
+
|
|
19
|
+
Put a Zod schema here only when at least one of these holds:
|
|
20
|
+
|
|
21
|
+
1. **The frontend needs it at runtime** — e.g. validating a form beyond native
|
|
22
|
+
HTML validation.
|
|
23
|
+
2. **Both sides genuinely share it** — then the server DTO must import it
|
|
24
|
+
(`postSchemas.input.create = postCreateInputSchema`), so the API contract
|
|
25
|
+
and the frontend validation are the same object and cannot drift.
|
|
26
|
+
3. **It cannot be re-exported from the server** because the server version
|
|
27
|
+
depends on Drizzle (`drizzle-zod` schemas) and shipping that dependency to
|
|
28
|
+
the client is not acceptable.
|
|
29
|
+
|
|
30
|
+
Everything else — row schemas, list outputs, trivial `{ id }` inputs — must
|
|
31
|
+
NOT be duplicated in shared. Hand-written mirrors silently drift from the real
|
|
32
|
+
contract.
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
// GOOD — form-validation schema, imported by both the DTO and the webapp form
|
|
36
|
+
export const postCreateInputSchema = z.object({
|
|
37
|
+
title: z.string().min(1),
|
|
38
|
+
slug: z.string().optional(),
|
|
39
|
+
excerpt: z.string().optional(),
|
|
40
|
+
content: z.string().min(1),
|
|
41
|
+
});
|
|
42
|
+
export type PostCreateInputSchema = z.infer<typeof postCreateInputSchema>;
|
|
43
|
+
|
|
44
|
+
// BAD — hand-mirrored row schema; the table already defines this shape
|
|
45
|
+
export const postSchema = z.object({ id: z.string(), title: z.string(), /* ... */ });
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Conventions
|
|
49
|
+
|
|
50
|
+
- File location: `apps/shared/src/modules/<module>/<module>.schema.ts`, with
|
|
51
|
+
enum values and constants in the companion `<module>.constants.ts`.
|
|
52
|
+
- Export both the schema and its inferred `type`.
|
|
53
|
+
- Build enums from constants: `z.enum(POST_STATUS_VALUES)`.
|