create-warlock 4.1.15 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/LICENSE +21 -0
- package/esm/commands/create-new-app/get-app-path.mjs +1 -1
- package/esm/commands/create-new-app/index.mjs +1 -1
- package/esm/commands/create-new-app/index.mjs.map +1 -1
- package/esm/commands/create-new-app/types.d.mts +1 -1
- package/esm/commands/create-warlock-app/index.mjs +1 -1
- package/esm/commands/create-warlock-app/index.mjs.map +1 -1
- package/esm/features/database-drivers.mjs +1 -1
- package/esm/features/features-map.mjs +13 -1
- package/esm/features/features-map.mjs.map +1 -1
- package/esm/helpers/app.mjs +1 -1
- package/esm/helpers/app.mjs.map +1 -1
- package/esm/helpers/exec.mjs +1 -1
- package/esm/helpers/package-manager.mjs +1 -1
- package/esm/helpers/package-manager.mjs.map +1 -1
- package/esm/helpers/paths.mjs +1 -1
- package/esm/helpers/project-builder-helpers.mjs +1 -1
- package/esm/index.d.mts +1 -1
- package/esm/index.mjs +1 -1
- package/esm/index.mjs.map +1 -1
- package/esm/ui/banner.mjs +1 -1
- package/esm/ui/spinners.mjs +1 -1
- package/package.json +2 -2
- package/bin/create-app.js +0 -5
- package/templates/warlock/.env.example +0 -36
- package/templates/warlock/.gitattributes +0 -1
- package/templates/warlock/.husky/pre-commit +0 -4
- package/templates/warlock/.prettierignore +0 -4
- package/templates/warlock/.prettierrc.json +0 -10
- package/templates/warlock/.vscode/settings.json +0 -41
- package/templates/warlock/README.md +0 -57
- package/templates/warlock/_.gitignore +0 -6
- package/templates/warlock/docs/new-module.md +0 -551
- package/templates/warlock/eslint.config.js +0 -98
- package/templates/warlock/package.json +0 -74
- package/templates/warlock/public/home.css +0 -523
- package/templates/warlock/skills/api-design/SKILL.md +0 -461
- package/templates/warlock/skills/code-standards/SKILL.md +0 -595
- package/templates/warlock/skills/data-and-persistence/SKILL.md +0 -330
- package/templates/warlock/skills/git-workflow/SKILL.md +0 -282
- package/templates/warlock/skills/module-boundaries/SKILL.md +0 -283
- package/templates/warlock/skills/observability-and-resilience/SKILL.md +0 -306
- package/templates/warlock/skills/security-baseline/SKILL.md +0 -352
- package/templates/warlock/skills/testing-strategy/SKILL.md +0 -323
- package/templates/warlock/src/app/auth/controllers/forgot-password.controller.ts +0 -28
- package/templates/warlock/src/app/auth/controllers/login.controller.ts +0 -22
- package/templates/warlock/src/app/auth/controllers/logout-all.controller.ts +0 -16
- package/templates/warlock/src/app/auth/controllers/logout.controller.ts +0 -16
- package/templates/warlock/src/app/auth/controllers/me.controller.ts +0 -13
- package/templates/warlock/src/app/auth/controllers/refresh-token.controller.ts +0 -29
- package/templates/warlock/src/app/auth/controllers/reset-password.controller.ts +0 -23
- package/templates/warlock/src/app/auth/main.ts +0 -9
- package/templates/warlock/src/app/auth/models/otp/index.ts +0 -1
- package/templates/warlock/src/app/auth/models/otp/migrations/22-12-2025_10-30-20.otp-migration.ts +0 -30
- package/templates/warlock/src/app/auth/models/otp/otp.model.ts +0 -69
- package/templates/warlock/src/app/auth/requests/guarded.request.ts +0 -10
- package/templates/warlock/src/app/auth/routes.ts +0 -22
- package/templates/warlock/src/app/auth/schema/login.schema.ts +0 -8
- package/templates/warlock/src/app/auth/schema/reset-password.schema.ts +0 -9
- package/templates/warlock/src/app/auth/services/auth.service.ts +0 -66
- package/templates/warlock/src/app/auth/services/forgot-password.service.ts +0 -28
- package/templates/warlock/src/app/auth/services/otp.service.ts +0 -173
- package/templates/warlock/src/app/auth/services/reset-password.service.ts +0 -39
- package/templates/warlock/src/app/auth/utils/auth-error-code.ts +0 -6
- package/templates/warlock/src/app/auth/utils/locales.ts +0 -89
- package/templates/warlock/src/app/auth/utils/types.ts +0 -14
- package/templates/warlock/src/app/posts/controllers/create-new-post.controller.ts +0 -21
- package/templates/warlock/src/app/posts/controllers/update-post.controller.ts +0 -30
- package/templates/warlock/src/app/posts/models/post/migrations/09-01-2026_02-07-51-post.migration.ts +0 -15
- package/templates/warlock/src/app/posts/models/post/post.model.ts +0 -23
- package/templates/warlock/src/app/posts/resources/post.resource.ts +0 -14
- package/templates/warlock/src/app/posts/routes.ts +0 -8
- package/templates/warlock/src/app/posts/schema/create-post.schema.ts +0 -9
- package/templates/warlock/src/app/posts/schema/update-post.schema.ts +0 -9
- package/templates/warlock/src/app/shared/components/HomePageComponent.tsx +0 -229
- package/templates/warlock/src/app/shared/controllers/home-page.controller.ts +0 -18
- package/templates/warlock/src/app/shared/controllers/home-page.controller.tsx +0 -8
- package/templates/warlock/src/app/shared/routes.ts +0 -4
- package/templates/warlock/src/app/shared/services/scheduler.service.ts +0 -3
- package/templates/warlock/src/app/shared/tests/infrastructure.test.ts +0 -22
- package/templates/warlock/src/app/shared/utils/global-columns-schema.ts +0 -8
- package/templates/warlock/src/app/shared/utils/locales.ts +0 -766
- package/templates/warlock/src/app/shared/utils/router.ts +0 -30
- package/templates/warlock/src/app/uploads/controllers/fetch-uploaded-file.controller.ts +0 -33
- package/templates/warlock/src/app/uploads/routes.ts +0 -4
- package/templates/warlock/src/app/users/commands/hello-world.command.ts +0 -8
- package/templates/warlock/src/app/users/controllers/create-new-user.controller.ts +0 -27
- package/templates/warlock/src/app/users/controllers/list-users.controller.ts +0 -12
- package/templates/warlock/src/app/users/events/inject-created-by-user.into-model.event.ts +0 -32
- package/templates/warlock/src/app/users/events/sync.ts +0 -5
- package/templates/warlock/src/app/users/main.ts +0 -5
- package/templates/warlock/src/app/users/models/user/index.ts +0 -1
- package/templates/warlock/src/app/users/models/user/migrations/11-12-2025_23-58-03-user.migration.ts +0 -15
- package/templates/warlock/src/app/users/models/user/user.model.ts +0 -64
- package/templates/warlock/src/app/users/repositories/users.repository.ts +0 -23
- package/templates/warlock/src/app/users/resources/user.resource.ts +0 -14
- package/templates/warlock/src/app/users/routes.ts +0 -8
- package/templates/warlock/src/app/users/schema/create-user.schema.ts +0 -11
- package/templates/warlock/src/app/users/seeds/users.seed.ts +0 -21
- package/templates/warlock/src/app/users/services/get-users.service.ts +0 -5
- package/templates/warlock/src/app/users/services/list-users.service.ts +0 -17
- package/templates/warlock/src/app/users/services/login-social.ts +0 -19
- package/templates/warlock/src/config/app.ts +0 -12
- package/templates/warlock/src/config/auth.ts +0 -20
- package/templates/warlock/src/config/cache.ts +0 -59
- package/templates/warlock/src/config/database.ts +0 -65
- package/templates/warlock/src/config/http.ts +0 -23
- package/templates/warlock/src/config/log.ts +0 -22
- package/templates/warlock/src/config/mail.ts +0 -16
- package/templates/warlock/src/config/repository.ts +0 -11
- package/templates/warlock/src/config/storage.ts +0 -34
- package/templates/warlock/src/config/tests.ts +0 -5
- package/templates/warlock/src/config/validation.ts +0 -7
- package/templates/warlock/storage/.gitignore +0 -2
- package/templates/warlock/tsconfig.json +0 -27
- package/templates/warlock/warlock.config.ts +0 -15
- package/templates/warlock/yarn.lock +0 -2332
|
@@ -1,330 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: data-and-persistence
|
|
3
|
-
description: 'How this project models, stores, and migrates data with `@warlock.js/cascade` — money as integer minor units (`total_cents`, `amount_cents`) with `currency` alongside, time as UTC `Date` columns named `<verb>_at` (e.g. `synced_at`, `checked_out_at`, `abandoned_at`), opaque IDs in URLs (UUID / nanoid), audit columns auto-managed by the framework (`created_at` / `updated_at`), soft-delete via cascade''s delete strategy (`@warlock.js/cascade/configure-delete-strategy/SKILL.md`), schemas defined with `v.object` and inferred into `Infer<>` types, models declared via `@RegisterModel()` + `Model<Schema>` + typed getters (`this.get<T>(key, default)`), migrations via `Migration.create(Model, {...columns}, { unique })` with column types `text() / integer() / double() / bool() / json() / arrayText()`, relations via `@BelongsTo("Name")` and `@HasMany("Name")`, snake_case columns + camelCase getters. Triggers: defining a model / schema / cascade blueprint; writing a migration; choosing a column type for currency, prices, totals, balances, refunds, fees; choosing how to store a timestamp / date; deciding ID format; adding audit columns; designing soft-delete vs hard-delete; defining relations (BelongsTo, HasMany); user asks "how do we store money", "UTC vs local time", "what ID format do we use", "should this be soft-deleted", "migration rules", "BelongsTo vs HasMany", "what does the model look like", "how do I add a column". Skip: API response shape (load `skills/api-design/SKILL.md`); query performance / N+1 / caching (load `skills/observability-and-resilience/SKILL.md`); pure business-logic refactors with no schema change; framework primitive deep-dive — load the relevant `@warlock.js/cascade/*` skill (define-model, paginate-results, configure-delete-strategy, etc.).'
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Data & persistence
|
|
7
|
-
|
|
8
|
-
**Status:** Stable
|
|
9
|
-
**Applies to:** Every model, schema, migration, and persisted entity in `src/app/**`.
|
|
10
|
-
|
|
11
|
-
How we store data so it survives a year of new requirements, a currency conversion bug, and a timezone-aware feature request. Framework mechanics live in `@warlock.js/cascade/skills/*` — this skill is the **project-level conventions** layered on top.
|
|
12
|
-
|
|
13
|
-
> **Sub-agent rule:** Before writing any model, schema, or migration, read this file.
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## 1. Money — integer minor units, always
|
|
18
|
-
|
|
19
|
-
### 1.1 The rule
|
|
20
|
-
|
|
21
|
-
- Store money as **integers in the currency's smallest unit** — cents for USD, halalas for SAR.
|
|
22
|
-
- Column name carries the unit: `total_cents`, `amount_cents`, `fee_cents`, `discount_cents`.
|
|
23
|
-
- A `currency` column lives next to every money column (or once per row if the entity is mono-currency).
|
|
24
|
-
|
|
25
|
-
The Cart model is the project's canonical example:
|
|
26
|
-
|
|
27
|
-
```typescript
|
|
28
|
-
// ✅ from cart.model.ts
|
|
29
|
-
export const cartSchema = v.object({
|
|
30
|
-
/* ... */
|
|
31
|
-
total_cents: v.number().default(0),
|
|
32
|
-
total_items: v.number().default(0),
|
|
33
|
-
currency: v.string().default("USD"),
|
|
34
|
-
/* ... */
|
|
35
|
-
});
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
### 1.2 Why never floats
|
|
39
|
-
|
|
40
|
-
```typescript
|
|
41
|
-
0.1 + 0.2 === 0.3 // false
|
|
42
|
-
1234.567 * 100 // 123456.69999999999
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
Compound a million orders' subtotals across a payout report and you'll be off by enough to matter. Integer cents prevents this entire class of bug.
|
|
46
|
-
|
|
47
|
-
### 1.3 The AI-pricing exception
|
|
48
|
-
|
|
49
|
-
`ai-models.input_price` and `ai-models.output_price` are stored as `v.number()` — not cents. This is **deliberate**: AI provider pricing is sub-cent per token (e.g. $0.000002), where integer minor units round to zero. The exception is *only* AI / token pricing; business money (orders, products, payments, balances, refunds) follows the cents rule.
|
|
50
|
-
|
|
51
|
-
### 1.4 Display formatting at the edge
|
|
52
|
-
|
|
53
|
-
Formatting (`$12.99`, `12.99 SAR`) is the resource layer's job — see `skills/api-design/SKILL.md` § 5. The model and service work in cents; the resource converts.
|
|
54
|
-
|
|
55
|
-
---
|
|
56
|
-
|
|
57
|
-
## 2. Time — UTC at rest, convert at the edge
|
|
58
|
-
|
|
59
|
-
### 2.1 Storage
|
|
60
|
-
|
|
61
|
-
- All timestamp columns store **UTC**, full stop.
|
|
62
|
-
- The column type is `date()` in the migration; the schema type is `v.date()` returning `Date`.
|
|
63
|
-
|
|
64
|
-
### 2.2 Naming — `<verb>_at`
|
|
65
|
-
|
|
66
|
-
Time columns end with `_at` and describe what happened, in past tense:
|
|
67
|
-
|
|
68
|
-
| Column | Meaning |
|
|
69
|
-
| --------------- | -------------------------------- |
|
|
70
|
-
| `created_at` | row was created |
|
|
71
|
-
| `updated_at` | row was last updated |
|
|
72
|
-
| `synced_at` | row was synced from a source |
|
|
73
|
-
| `checked_out_at`| cart was checked out |
|
|
74
|
-
| `abandoned_at` | cart was marked abandoned |
|
|
75
|
-
| `expires_at` | row will become invalid |
|
|
76
|
-
|
|
77
|
-
```typescript
|
|
78
|
-
// ✅ from cart.model.ts
|
|
79
|
-
synced_at: v.date().optional(),
|
|
80
|
-
abandoned_at: v.date().optional(),
|
|
81
|
-
checked_out_at: v.date().optional(),
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
### 2.3 Conversion happens at the response layer
|
|
85
|
-
|
|
86
|
-
Services and models work in UTC `Date` objects. Timezone conversion (UTC → user's local timezone) happens once, at the resource / response layer, using the authenticated user's preference. Never store timezone-adjusted timestamps.
|
|
87
|
-
|
|
88
|
-
### 2.4 Date library
|
|
89
|
-
|
|
90
|
-
The project uses `dayjs` for time arithmetic and formatting (already in dependencies). Never `moment`, never hand-rolled UTC math. The framework's `@mongez/time-wizard` provides typed helpers on top of dayjs — prefer it where available.
|
|
91
|
-
|
|
92
|
-
---
|
|
93
|
-
|
|
94
|
-
## 3. IDs
|
|
95
|
-
|
|
96
|
-
### 3.1 The rule
|
|
97
|
-
|
|
98
|
-
- **Internal primary keys** may be sequential — they're cheap, indexed, and never leave the database.
|
|
99
|
-
- **Any ID that crosses a process boundary** (URL, response body, log line, webhook) is opaque — UUID or nanoid.
|
|
100
|
-
|
|
101
|
-
### 3.2 Project default
|
|
102
|
-
|
|
103
|
-
Cascade generates string IDs by default for new records. Use those as the public identifier. Don't expose `_id` (Mongo) or sequential integers (Postgres serial) in URLs even if they exist internally.
|
|
104
|
-
|
|
105
|
-
### 3.3 Why
|
|
106
|
-
|
|
107
|
-
Sequential keys leak business volume (`/orders/42178` tells anyone you've placed ~42k orders) and invite enumeration attacks (incrementing the path to find someone else's resource). Opaque IDs prevent both.
|
|
108
|
-
|
|
109
|
-
---
|
|
110
|
-
|
|
111
|
-
## 4. Audit columns
|
|
112
|
-
|
|
113
|
-
### 4.1 Framework-managed
|
|
114
|
-
|
|
115
|
-
Cascade auto-adds `created_at` and `updated_at` to every model. You do **not** add them to the schema or migration — the framework handles them.
|
|
116
|
-
|
|
117
|
-
### 4.2 Author tracking
|
|
118
|
-
|
|
119
|
-
For entities where "who created this" matters (most business entities), add author columns to the resource:
|
|
120
|
-
|
|
121
|
-
```typescript
|
|
122
|
-
// ✅ from ai-model.resource.ts
|
|
123
|
-
created_by: "string",
|
|
124
|
-
updated_by: "string",
|
|
125
|
-
deleted_by: "string",
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
These store the user-id of the actor at the time of the action. Wire them in the service:
|
|
129
|
-
|
|
130
|
-
```typescript
|
|
131
|
-
await ordersRepository.create({ ...input, created_by: user.id });
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
### 4.3 What to audit
|
|
135
|
-
|
|
136
|
-
Every entity that's user-mutable: yes. Read-only reference data (countries, currencies, system enums): no — audit columns are noise there.
|
|
137
|
-
|
|
138
|
-
---
|
|
139
|
-
|
|
140
|
-
## 5. Soft-delete vs hard-delete
|
|
141
|
-
|
|
142
|
-
### 5.1 Default to soft-delete for user-visible entities
|
|
143
|
-
|
|
144
|
-
Soft-delete keeps the row, sets `deleted_at` + `deleted_by`. The entity is excluded from default queries but recoverable.
|
|
145
|
-
|
|
146
|
-
### 5.2 Hard-delete for ephemeral data
|
|
147
|
-
|
|
148
|
-
Use hard-delete for:
|
|
149
|
-
|
|
150
|
-
- Sessions, OTPs, refresh tokens (expired = gone)
|
|
151
|
-
- Cache entries
|
|
152
|
-
- Idempotency-key records past their TTL
|
|
153
|
-
- GDPR right-to-erasure requests (when triggered)
|
|
154
|
-
|
|
155
|
-
### 5.3 Mechanics
|
|
156
|
-
|
|
157
|
-
Configure the delete strategy at the model level — see `@warlock.js/cascade/configure-delete-strategy/SKILL.md` for the framework's per-model knobs (`paranoid`, `forever`, `cascade`).
|
|
158
|
-
|
|
159
|
-
### 5.4 Restore is admin-only
|
|
160
|
-
|
|
161
|
-
If an entity supports restore, expose it through an admin endpoint, never end-user UI (unless the feature is explicitly "trash / undelete" — a deliberate UX, not an accident).
|
|
162
|
-
|
|
163
|
-
---
|
|
164
|
-
|
|
165
|
-
## 6. Migrations
|
|
166
|
-
|
|
167
|
-
### 6.1 Forward-only
|
|
168
|
-
|
|
169
|
-
Never edit a shipped migration. Once it's on `main`, the only change is a new migration on top.
|
|
170
|
-
|
|
171
|
-
### 6.2 File location
|
|
172
|
-
|
|
173
|
-
Migrations live inside the model folder: `src/app/<module>/models/<model>/migrations/<timestamp>-<noun>.migration.ts`. The framework's `yarn cli migrate` discovers them.
|
|
174
|
-
|
|
175
|
-
### 6.3 Definition shape
|
|
176
|
-
|
|
177
|
-
```typescript
|
|
178
|
-
// ✅ from ai-model.migration.ts
|
|
179
|
-
import { arrayText, bool, double, integer, json, Migration, text } from "@warlock.js/cascade";
|
|
180
|
-
import { AiModel } from "../ai-model.model";
|
|
181
|
-
|
|
182
|
-
export default Migration.create(
|
|
183
|
-
AiModel,
|
|
184
|
-
{
|
|
185
|
-
provider: text().notNullable(),
|
|
186
|
-
code: text().notNullable(),
|
|
187
|
-
name: text().notNullable(),
|
|
188
|
-
context: integer().notNullable(),
|
|
189
|
-
is_free: bool().notNullable(),
|
|
190
|
-
input_price: double().notNullable(),
|
|
191
|
-
config: json().nullable(),
|
|
192
|
-
features: arrayText().nullable(),
|
|
193
|
-
},
|
|
194
|
-
{
|
|
195
|
-
unique: [{ columns: ["provider", "provider_model_id"] }],
|
|
196
|
-
},
|
|
197
|
-
);
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
Column type helpers: `text()`, `integer()`, `double()`, `bool()`, `json()`, `arrayText()`, `date()`. Always declare `.notNullable()` or `.nullable()` explicitly — never leave nullability implicit.
|
|
201
|
-
|
|
202
|
-
### 6.4 Backfill separately from schema change
|
|
203
|
-
|
|
204
|
-
Two migrations, not one:
|
|
205
|
-
|
|
206
|
-
1. Schema change (add column nullable, or add column with default).
|
|
207
|
-
2. Backfill (populate the column from existing data).
|
|
208
|
-
|
|
209
|
-
This keeps each migration short, fast, and safe to revert in stages.
|
|
210
|
-
|
|
211
|
-
### 6.5 Long-running backfills
|
|
212
|
-
|
|
213
|
-
If a backfill is going to touch millions of rows, gate it behind a feature flag and run it as a background job — not inside the migration runner. The migration just creates the schema; the backfill is application code.
|
|
214
|
-
|
|
215
|
-
---
|
|
216
|
-
|
|
217
|
-
## 7. Indexes
|
|
218
|
-
|
|
219
|
-
### 7.1 Every query path has a supporting index
|
|
220
|
-
|
|
221
|
-
- Foreign keys are indexed by default.
|
|
222
|
-
- Any column you filter or sort on regularly gets an index.
|
|
223
|
-
- Composite indexes match query order (`WHERE org_id = ? AND status = ?` → index `(org_id, status)`).
|
|
224
|
-
|
|
225
|
-
### 7.2 Add the index alongside the query
|
|
226
|
-
|
|
227
|
-
If a new query is introduced, the index lands in the same PR. Don't push slow queries to production and patch later.
|
|
228
|
-
|
|
229
|
-
### 7.3 Tooling
|
|
230
|
-
|
|
231
|
-
`yarn db.indexes` runs the framework's index management. Define indexes declaratively in the migration's options.
|
|
232
|
-
|
|
233
|
-
---
|
|
234
|
-
|
|
235
|
-
## 8. Schema conventions
|
|
236
|
-
|
|
237
|
-
### 8.1 Column naming — snake_case
|
|
238
|
-
|
|
239
|
-
All database column names use `snake_case`: `created_at`, `total_cents`, `provider_model_id`. This matches the wire-format convention in resources.
|
|
240
|
-
|
|
241
|
-
### 8.2 Foreign keys — `<noun>_id`
|
|
242
|
-
|
|
243
|
-
`user_id`, `organization_id`, `cart_id`. Always singular, always `_id` suffix. Cascade's `@BelongsTo("Name")` decorator wires the relation off this column by convention.
|
|
244
|
-
|
|
245
|
-
### 8.3 Booleans — affirmative
|
|
246
|
-
|
|
247
|
-
`is_active`, `is_free`, `has_shipped`, `was_refunded`. Never negative (`is_not_deleted`, `is_unverified`) — negative booleans break readability under negation (`!isNotDeleted` reads as a riddle).
|
|
248
|
-
|
|
249
|
-
### 8.4 Enums — string in the column, enum in the schema
|
|
250
|
-
|
|
251
|
-
```typescript
|
|
252
|
-
status: v.enum(CartStatus).default(CartStatus.ACTIVE),
|
|
253
|
-
```
|
|
254
|
-
|
|
255
|
-
The enum is defined once in `app/<module>/types/<noun>-status.type.ts` and referenced everywhere. Never use raw string literals for status fields outside the type definition.
|
|
256
|
-
|
|
257
|
-
### 8.5 Defaults
|
|
258
|
-
|
|
259
|
-
Declare defaults in the schema (`v.string().default("USD")`), not just in the migration. The schema default keeps `Infer<>` clean and gives the service a sensible starting value.
|
|
260
|
-
|
|
261
|
-
---
|
|
262
|
-
|
|
263
|
-
## 9. Models — structural rules
|
|
264
|
-
|
|
265
|
-
### 9.1 File layout
|
|
266
|
-
|
|
267
|
-
```
|
|
268
|
-
src/app/<module>/models/<noun>/
|
|
269
|
-
<noun>.model.ts ← @RegisterModel() + schema + class + getters
|
|
270
|
-
index.ts ← re-export
|
|
271
|
-
migrations/
|
|
272
|
-
<timestamp>-<noun>.migration.ts
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
### 9.2 The model file shape (Cart is the gold standard)
|
|
276
|
-
|
|
277
|
-
```typescript
|
|
278
|
-
// 1. schema first
|
|
279
|
-
export const cartSchema = v.object({ /* columns */ });
|
|
280
|
-
export type CartSchema = Infer<typeof cartSchema>;
|
|
281
|
-
|
|
282
|
-
// 2. class with decorator
|
|
283
|
-
@RegisterModel()
|
|
284
|
-
export class Cart extends Model<CartSchema> {
|
|
285
|
-
public static table = "carts";
|
|
286
|
-
public static schema = cartSchema;
|
|
287
|
-
|
|
288
|
-
// 3. relations
|
|
289
|
-
@BelongsTo("Organization")
|
|
290
|
-
public organization?: Organization;
|
|
291
|
-
|
|
292
|
-
@HasMany("CartItem")
|
|
293
|
-
public items?: CartItem[];
|
|
294
|
-
|
|
295
|
-
// 4. typed getters — one per persisted field accessed from app code
|
|
296
|
-
public get totalCents() {
|
|
297
|
-
return this.get<number>("total_cents", 0);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
```
|
|
301
|
-
|
|
302
|
-
### 9.3 Typed getters over `.get<T>("field")` at call sites
|
|
303
|
-
|
|
304
|
-
This is project policy (see memory `feedback_use_model_getters`). Every column accessed from application code gets a typed getter on the model. One line beats `cart.get<number>("total_cents")` across N call sites.
|
|
305
|
-
|
|
306
|
-
### 9.4 Class-level JSDoc for non-obvious domain logic
|
|
307
|
-
|
|
308
|
-
The Cart model has a multi-paragraph JSDoc explaining identity ownership, lead linkage, source-of-truth rules, and currency snapshotting. Mirror this for any model whose semantics aren't obvious from the columns alone.
|
|
309
|
-
|
|
310
|
-
---
|
|
311
|
-
|
|
312
|
-
## 10. Review checklist
|
|
313
|
-
|
|
314
|
-
Before merging a change that touches a model, schema, or migration:
|
|
315
|
-
|
|
316
|
-
- [ ] Money columns are `integer cents` with `currency` alongside (AI-pricing is the only exception)
|
|
317
|
-
- [ ] Time columns end with `_at`, store UTC `Date`
|
|
318
|
-
- [ ] No timezone-adjusted timestamps in storage
|
|
319
|
-
- [ ] IDs exposed in URLs / responses are opaque
|
|
320
|
-
- [ ] Audit columns (`created_by`, `updated_by`) wired where author tracking matters
|
|
321
|
-
- [ ] Soft-delete strategy chosen deliberately, configured via cascade
|
|
322
|
-
- [ ] Migration is forward-only, not editing a shipped file
|
|
323
|
-
- [ ] Backfill is a separate migration / background job from the schema change
|
|
324
|
-
- [ ] Every new query path has a supporting index
|
|
325
|
-
- [ ] Column names snake_case, foreign keys `<noun>_id`
|
|
326
|
-
- [ ] Booleans are affirmative (`is_active`, never `is_not_deleted`)
|
|
327
|
-
- [ ] Enums defined once in `types/`, never repeated as string literals
|
|
328
|
-
- [ ] Defaults declared in the schema, not only in the migration
|
|
329
|
-
- [ ] Every accessed column has a typed getter on the model
|
|
330
|
-
- [ ] Class-level JSDoc on the model if domain semantics aren't obvious
|
|
@@ -1,282 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: git-workflow
|
|
3
|
-
description: 'Git, branching, commit messages, PRs, and CI gates for this project — conventional commits with module-scoped types (`feat(orders): ...`, `fix(auth): ...`, `refactor(cart): ...`, `docs(users): ...`, `chore: ...`), branch naming (`feat/`, `fix/`, `chore/`, `refactor/`, `docs/` prefix + slug), PR size cap (~400 LoC), required reviewers, CI gates (`yarn tsc` / `yarn lint` / `yarn test` / `yarn audit --level=high`), no force-push to `main`, squash-merge policy, tagging and releases. Triggers: writing a commit message; opening / reviewing / merging a PR; naming a branch; setting up CI; user asks "commit format", "conventional commits", "branch naming", "how big should a PR be", "what CI gates do we need", "how do we release", "can I force push", "squash or merge", "scope in commit message", "what scopes are valid". Skip: code style inside files (load `skills/code-standards/SKILL.md`); deploy mechanics / pm2 setup; framework primitive questions; publishing / semantic-versioning of npm packages.'
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Git workflow
|
|
7
|
-
|
|
8
|
-
**Status:** Stable
|
|
9
|
-
**Applies to:** Every commit, branch, and pull request in the project.
|
|
10
|
-
|
|
11
|
-
The process layer that lets a team of N work without stepping on each other and lets a reader six months later understand why a line is there.
|
|
12
|
-
|
|
13
|
-
> **Sub-agent rule:** Before opening a commit or PR, read this file.
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## 1. Commit messages — conventional commits
|
|
18
|
-
|
|
19
|
-
### 1.1 Format
|
|
20
|
-
|
|
21
|
-
```
|
|
22
|
-
<type>(<scope>): <imperative summary>
|
|
23
|
-
|
|
24
|
-
[optional body — explains the WHY when the diff doesn't]
|
|
25
|
-
|
|
26
|
-
[optional footer — BREAKING CHANGE, ticket refs, co-authors]
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
Examples:
|
|
30
|
-
|
|
31
|
-
```
|
|
32
|
-
feat(orders): add cancellation flow with refund + audit trail
|
|
33
|
-
fix(auth): reject expired refresh tokens on rotation
|
|
34
|
-
docs(users): document the profile-update endpoint
|
|
35
|
-
refactor(cart): extract totals calculation into a util
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
### 1.2 Types
|
|
39
|
-
|
|
40
|
-
| Type | Use for |
|
|
41
|
-
| ----------- | ------------------------------------------------ |
|
|
42
|
-
| `feat` | New user-visible feature or capability |
|
|
43
|
-
| `fix` | Bug fix |
|
|
44
|
-
| `refactor` | Internal restructure with no behaviour change |
|
|
45
|
-
| `docs` | Documentation only (skills, README, code docs) |
|
|
46
|
-
| `test` | Adding or updating tests only |
|
|
47
|
-
| `chore` | Tooling, config, scripts — no app code change |
|
|
48
|
-
| `perf` | Performance improvement |
|
|
49
|
-
| `build` | Build system or external dependencies |
|
|
50
|
-
| `ci` | CI configuration |
|
|
51
|
-
| `style` | Formatting only (rare — Prettier handles this) |
|
|
52
|
-
|
|
53
|
-
### 1.3 Scope
|
|
54
|
-
|
|
55
|
-
The scope identifies what area of the codebase changed. Use:
|
|
56
|
-
|
|
57
|
-
- The module name for app-level changes: `feat(orders): ...`, `fix(auth): ...`
|
|
58
|
-
- Omit when the change is genuinely cross-cutting: `chore: bump node version`
|
|
59
|
-
|
|
60
|
-
### 1.4 Summary
|
|
61
|
-
|
|
62
|
-
- Imperative present tense: "add" not "added", "fix" not "fixed"
|
|
63
|
-
- Under 72 characters
|
|
64
|
-
- No trailing period
|
|
65
|
-
- Lowercase first word (after the type / scope)
|
|
66
|
-
|
|
67
|
-
### 1.5 Body — explain the WHY
|
|
68
|
-
|
|
69
|
-
If the diff doesn't make the reasoning obvious, the body does. One short paragraph is usually enough. Skip the body for trivial changes.
|
|
70
|
-
|
|
71
|
-
### 1.6 Breaking changes
|
|
72
|
-
|
|
73
|
-
Either:
|
|
74
|
-
|
|
75
|
-
- Add `!` after type: `feat(api)!: rename /users to /accounts`
|
|
76
|
-
- Or include a `BREAKING CHANGE:` footer with details
|
|
77
|
-
|
|
78
|
-
Both work; pick one per project and use it consistently. Project default: footer (more visible in `git log` output).
|
|
79
|
-
|
|
80
|
-
### 1.7 Ticket references
|
|
81
|
-
|
|
82
|
-
When the change traces to a ticket, reference it in the footer:
|
|
83
|
-
|
|
84
|
-
```
|
|
85
|
-
feat(orders): add cancellation flow
|
|
86
|
-
|
|
87
|
-
Refs: WAR-1234
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
---
|
|
91
|
-
|
|
92
|
-
## 2. Branch naming
|
|
93
|
-
|
|
94
|
-
### 2.1 Format
|
|
95
|
-
|
|
96
|
-
`<type>/<short-slug>` — lowercase, hyphenated, no spaces.
|
|
97
|
-
|
|
98
|
-
Examples:
|
|
99
|
-
|
|
100
|
-
```
|
|
101
|
-
feat/order-cancel-endpoint
|
|
102
|
-
fix/login-rate-limit
|
|
103
|
-
refactor/extract-cart-totals-helper
|
|
104
|
-
docs/skills-code-standards-section-9
|
|
105
|
-
chore/bump-vitest-to-4
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
### 2.2 Types match commit types
|
|
109
|
-
|
|
110
|
-
Use the same vocabulary as commits: `feat/`, `fix/`, `refactor/`, `docs/`, `test/`, `chore/`, `perf/`, `ci/`.
|
|
111
|
-
|
|
112
|
-
### 2.3 Optional ticket prefix
|
|
113
|
-
|
|
114
|
-
When the team uses ticket tracking, `<type>/<ticket-id>-<slug>` is fine:
|
|
115
|
-
|
|
116
|
-
```
|
|
117
|
-
feat/war-1234-order-cancel-endpoint
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
### 2.4 Never work directly on `main`
|
|
121
|
-
|
|
122
|
-
Even one-line doc fixes get a branch. The protected-branch rule (§ 7) makes this enforced, not optional.
|
|
123
|
-
|
|
124
|
-
---
|
|
125
|
-
|
|
126
|
-
## 3. Pull requests
|
|
127
|
-
|
|
128
|
-
### 3.1 Size cap — under 400 lines changed
|
|
129
|
-
|
|
130
|
-
400 lines of meaningful changes (excluding lockfiles, generated code, snapshot data). Beyond that, the review becomes performative — reviewers skim, real issues slip through.
|
|
131
|
-
|
|
132
|
-
### 3.2 When it's bigger, split
|
|
133
|
-
|
|
134
|
-
A common shape:
|
|
135
|
-
|
|
136
|
-
1. **Prep PR** — pure refactor / extraction, no behaviour change. Lands first.
|
|
137
|
-
2. **Feature PR** — the actual new logic, smaller because the prep already landed.
|
|
138
|
-
3. **Tests PR** — coverage for the feature.
|
|
139
|
-
|
|
140
|
-
Each PR is independently mergeable, independently reviewable.
|
|
141
|
-
|
|
142
|
-
### 3.3 Description template
|
|
143
|
-
|
|
144
|
-
```markdown
|
|
145
|
-
## What
|
|
146
|
-
One-line summary of the change.
|
|
147
|
-
|
|
148
|
-
## Why
|
|
149
|
-
The reason — link to a ticket, design doc, or bug report.
|
|
150
|
-
|
|
151
|
-
## How to verify
|
|
152
|
-
Steps a reviewer can take to confirm it works.
|
|
153
|
-
|
|
154
|
-
## Screenshots / recordings
|
|
155
|
-
If UI or output changed.
|
|
156
|
-
|
|
157
|
-
## Notes for reviewer
|
|
158
|
-
Anything that's intentionally weird or worth knowing.
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
### 3.4 Self-review first
|
|
162
|
-
|
|
163
|
-
Read your own diff before requesting review. The number of "oh I forgot to remove that" moments you catch yourself saves the reviewer a round-trip and saves your reputation.
|
|
164
|
-
|
|
165
|
-
### 3.5 Required reviewers
|
|
166
|
-
|
|
167
|
-
- **Default** — 1 reviewer
|
|
168
|
-
- **Security, auth, payments, data migrations** — 2 reviewers, one with domain ownership
|
|
169
|
-
|
|
170
|
-
---
|
|
171
|
-
|
|
172
|
-
## 4. Review
|
|
173
|
-
|
|
174
|
-
### 4.1 Read the diff cover to cover
|
|
175
|
-
|
|
176
|
-
Not just the changed lines — the surrounding context too. A line that looks fine in isolation may be obviously broken in context.
|
|
177
|
-
|
|
178
|
-
### 4.2 Suggestion vs. blocker — label it
|
|
179
|
-
|
|
180
|
-
- **Blocker**: must change before merge. State it as such.
|
|
181
|
-
- **Suggestion**: nice-to-have, optional, can land later. Mark it explicitly so the author isn't guessing.
|
|
182
|
-
- **Question**: you don't understand something — ask, don't assume.
|
|
183
|
-
|
|
184
|
-
### 4.3 Approve means "I'd be on call for this"
|
|
185
|
-
|
|
186
|
-
Approval is a load-bearing signature. Approve only if you're comfortable owning the code after the author rotates off the project.
|
|
187
|
-
|
|
188
|
-
### 4.4 No "LGTM" without reading
|
|
189
|
-
|
|
190
|
-
A drive-by approve is worse than no approve — it gives false confidence. If you don't have time to review properly, say so and let someone else.
|
|
191
|
-
|
|
192
|
-
---
|
|
193
|
-
|
|
194
|
-
## 5. CI gates
|
|
195
|
-
|
|
196
|
-
Every PR runs these. All must pass before merge. The exact commands match `package.json` scripts:
|
|
197
|
-
|
|
198
|
-
| Gate | Command | Blocks on |
|
|
199
|
-
| ----------------------------- | ----------------------------- | ---------------------------------- |
|
|
200
|
-
| Type-check | `yarn tsc` | Any TS error |
|
|
201
|
-
| Lint | `yarn lint` | Any ESLint error |
|
|
202
|
-
| Tests | `yarn test --run` | Any failed test |
|
|
203
|
-
| Coverage (recommended floor) | `yarn test:coverage` | TBD per team policy |
|
|
204
|
-
| Dependency audit | `yarn audit --level=high` | High / critical vulnerabilities |
|
|
205
|
-
| Format check | `yarn format --check` | Any unformatted file |
|
|
206
|
-
|
|
207
|
-
Additional rules:
|
|
208
|
-
|
|
209
|
-
- No `.only` / `.skip` in committed tests (lint rule or CI grep).
|
|
210
|
-
- No `console.log` in committed code (lint rule).
|
|
211
|
-
- No `TODO` without a ticket reference (warning, not blocker).
|
|
212
|
-
|
|
213
|
-
---
|
|
214
|
-
|
|
215
|
-
## 6. Merging
|
|
216
|
-
|
|
217
|
-
### 6.1 Squash merge
|
|
218
|
-
|
|
219
|
-
One commit per PR onto `main`. The squashed commit message is the conventional-commit format from § 1, generated from the PR title.
|
|
220
|
-
|
|
221
|
-
This keeps `main`'s history readable — one logical change per commit — while allowing PR branches to have messy work-in-progress commits.
|
|
222
|
-
|
|
223
|
-
### 6.2 `main` is always green
|
|
224
|
-
|
|
225
|
-
If `main` is red, the team's first job is to make it green. No new PRs merge until it is.
|
|
226
|
-
|
|
227
|
-
### 6.3 Hotfix flow
|
|
228
|
-
|
|
229
|
-
Same conventional commits + same CI gates + expedited review (1 reviewer, can be self-review for true emergencies if a maintainer is on the call). Hotfix PRs are still squash-merged.
|
|
230
|
-
|
|
231
|
-
---
|
|
232
|
-
|
|
233
|
-
## 7. Protected branches
|
|
234
|
-
|
|
235
|
-
`main` is protected:
|
|
236
|
-
|
|
237
|
-
- No direct push.
|
|
238
|
-
- No force-push.
|
|
239
|
-
- Required CI checks pass before merge.
|
|
240
|
-
- Required reviewer count met.
|
|
241
|
-
- Linear history (squash merges only — no merge commits).
|
|
242
|
-
|
|
243
|
-
These are enforced in the repo settings, not just on the honour system.
|
|
244
|
-
|
|
245
|
-
---
|
|
246
|
-
|
|
247
|
-
## 8. Tags and releases
|
|
248
|
-
|
|
249
|
-
### 8.1 Versioning
|
|
250
|
-
|
|
251
|
-
For a single-monolith app (most templates), versioning is optional — date-tags work fine if you want them at all.
|
|
252
|
-
|
|
253
|
-
For a library / framework package, semver:
|
|
254
|
-
|
|
255
|
-
```
|
|
256
|
-
v<major>.<minor>.<patch>
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
### 8.2 Changelog
|
|
260
|
-
|
|
261
|
-
Generate from conventional commits — every well-formed history can be turned into a readable changelog automatically. Tools: `changesets`, `conventional-changelog`, or the framework's own release script.
|
|
262
|
-
|
|
263
|
-
### 8.3 Release notes
|
|
264
|
-
|
|
265
|
-
For user-facing apps, ship release notes alongside the tag — what changed, what to test, any known issues.
|
|
266
|
-
|
|
267
|
-
---
|
|
268
|
-
|
|
269
|
-
## 9. Review checklist
|
|
270
|
-
|
|
271
|
-
Before merging:
|
|
272
|
-
|
|
273
|
-
- [ ] Commit message follows `<type>(<scope>): <imperative summary>`
|
|
274
|
-
- [ ] Branch name matches `<type>/<slug>`
|
|
275
|
-
- [ ] PR under ~400 lines changed (or split into staged PRs)
|
|
276
|
-
- [ ] Self-reviewed before requesting review
|
|
277
|
-
- [ ] Description filled (what / why / how to verify)
|
|
278
|
-
- [ ] Required reviewer count met
|
|
279
|
-
- [ ] All CI gates green
|
|
280
|
-
- [ ] No `.only` / `.skip` / `console.log` left in
|
|
281
|
-
- [ ] Squash-merge selected, squashed message in conventional format
|
|
282
|
-
- [ ] `main` stays green after merge
|