@joktec/skills 0.1.4 → 0.1.8
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/README.md +4 -2
- package/dist/claude/skills/advanced-typescript-design/SKILL.md +60 -0
- package/dist/claude/skills/advanced-typescript-design/agents/openai.yaml +4 -0
- package/dist/claude/skills/advanced-typescript-design/references/advanced.md +219 -0
- package/dist/claude/skills/advanced-typescript-design/references/simple.md +149 -0
- package/dist/claude/skills/joktec-mongo-skill/SKILL.md +9 -2
- package/dist/claude/skills/joktec-mongo-skill/references/repository.md +14 -11
- package/dist/claude/skills/joktec-mongo-skill/references/schema-and-plugins.md +43 -7
- package/dist/claude/skills/joktec-mysql-skill/SKILL.md +6 -2
- package/dist/claude/skills/joktec-mysql-skill/references/entities.md +82 -53
- package/dist/claude/skills/joktec-mysql-skill/references/repository.md +15 -1
- package/dist/claude/skills/joktec-tool-skill/references/tools.md +1 -0
- package/dist/codex/skills/advanced-typescript-design/SKILL.md +60 -0
- package/dist/codex/skills/advanced-typescript-design/agents/openai.yaml +4 -0
- package/dist/codex/skills/advanced-typescript-design/references/advanced.md +219 -0
- package/dist/codex/skills/advanced-typescript-design/references/simple.md +149 -0
- package/dist/codex/skills/joktec-mongo-skill/SKILL.md +9 -2
- package/dist/codex/skills/joktec-mongo-skill/references/repository.md +14 -11
- package/dist/codex/skills/joktec-mongo-skill/references/schema-and-plugins.md +43 -7
- package/dist/codex/skills/joktec-mysql-skill/SKILL.md +6 -2
- package/dist/codex/skills/joktec-mysql-skill/references/entities.md +82 -53
- package/dist/codex/skills/joktec-mysql-skill/references/repository.md +15 -1
- package/dist/codex/skills/joktec-tool-skill/references/tools.md +1 -0
- package/dist/copilot/.github/copilot-instructions.md +601 -73
- package/dist/cursor/.cursor/rules/advanced-typescript-design.mdc +437 -0
- package/dist/cursor/.cursor/rules/joktec-mongo-skill.mdc +66 -20
- package/dist/cursor/.cursor/rules/joktec-mysql-skill.mdc +103 -56
- package/dist/cursor/.cursor/rules/joktec-tool-skill.mdc +1 -0
- package/dist/gemini/GEMINI.md +603 -73
- package/dist/windsurf/.windsurf/rules/advanced-typescript-design.md +433 -0
- package/dist/windsurf/.windsurf/rules/joktec-mongo-skill.md +66 -20
- package/dist/windsurf/.windsurf/rules/joktec-mysql-skill.md +103 -56
- package/dist/windsurf/.windsurf/rules/joktec-tool-skill.md +1 -0
- package/package.json +6 -3
- package/scripts/sync-pack-version.mjs +38 -0
- package/skill-pack.json +35 -1
- package/skills/advanced-typescript-design/SKILL.md +60 -0
- package/skills/advanced-typescript-design/agents/openai.yaml +4 -0
- package/skills/advanced-typescript-design/references/advanced.md +219 -0
- package/skills/advanced-typescript-design/references/simple.md +149 -0
- package/skills/joktec-mongo-skill/SKILL.md +9 -2
- package/skills/joktec-mongo-skill/references/repository.md +14 -11
- package/skills/joktec-mongo-skill/references/schema-and-plugins.md +43 -7
- package/skills/joktec-mysql-skill/SKILL.md +6 -2
- package/skills/joktec-mysql-skill/references/entities.md +82 -53
- package/skills/joktec-mysql-skill/references/repository.md +15 -1
- package/skills/joktec-tool-skill/references/tools.md +1 -0
|
@@ -261,9 +261,16 @@ Use this skill for MongoDB-backed resources that rely on JokTec's Mongoose/Typeg
|
|
|
261
261
|
- Keep schema classes, app repositories, and app-specific queries in the consumer app.
|
|
262
262
|
- Extend `MongoRepo<T, ID>` for app repositories.
|
|
263
263
|
- Preserve `conId` when the app has multiple Mongo connections.
|
|
264
|
-
- Use schema-first decorators when a schema class should be reused as a DTO source.
|
|
264
|
+
- Use schema-first decorators when a schema class should be reused as a DTO source; wrappers should reduce repeated Typegoose, validator, transformer, and Swagger stacks.
|
|
265
|
+
- Use `RefId<T>` for stored reference id fields and `PopulatedRef<T>` for populated virtual fields.
|
|
266
|
+
- Use `@Schema({ kind: 'embedded' })` for value objects without `_id` or timestamps.
|
|
267
|
+
- Use `@Schema({ kind: 'subdocument' })` for embedded documents that need their own `_id` and timestamps.
|
|
268
|
+
- Use `@Prop({ kind: 'virtual', mode: 'getter' })` for computed getters that need expose/Swagger metadata without persistence.
|
|
269
|
+
- Use `@Prop({ ref: () => Target, foreignField, localField })` for populate-one virtuals when inferred defaults are enough.
|
|
270
|
+
- Use `@Prop({ type: () => [Target], ref: () => Target, foreignField, localField })` for populate-array virtuals.
|
|
271
|
+
- Use `@Prop({ kind: 'map', type: Object })` for map/snapshot payloads that must keep their raw shape.
|
|
265
272
|
- Treat ObjectId casting and regex behavior as safety-sensitive.
|
|
266
|
-
-
|
|
273
|
+
- For real migrations, inspect `node_modules/@joktec/mongo` first, then installed README or GitHub package docs, then GitHub source before assuming APIs.
|
|
267
274
|
|
|
268
275
|
## References
|
|
269
276
|
|
|
@@ -280,16 +287,18 @@ Use this skill for MongoDB-backed resources that rely on JokTec's Mongoose/Typeg
|
|
|
280
287
|
|
|
281
288
|
When blocked, inspect:
|
|
282
289
|
|
|
283
|
-
- `
|
|
284
|
-
- `
|
|
285
|
-
- `packages/databases/mongo
|
|
286
|
-
-
|
|
287
|
-
- `packages/databases/mongo/src/
|
|
288
|
-
- `packages/databases/mongo/src/mongo.
|
|
289
|
-
- `packages/databases/mongo/src/
|
|
290
|
-
- `packages/databases/mongo/src/
|
|
291
|
-
- `packages/databases/mongo/src/helpers/mongo.
|
|
292
|
-
- `packages/databases/mongo/src/
|
|
290
|
+
- Consumer project first: `node_modules/@joktec/mongo`.
|
|
291
|
+
- Installed docs next: `node_modules/@joktec/mongo/README.md`.
|
|
292
|
+
- GitHub docs next: `https://github.com/joktec/joktec-framework/tree/main/packages/databases/mongo`.
|
|
293
|
+
- GitHub source fallback:
|
|
294
|
+
- `packages/databases/mongo/src/index.ts`
|
|
295
|
+
- `packages/databases/mongo/src/mongo.module.ts`
|
|
296
|
+
- `packages/databases/mongo/src/mongo.service.ts`
|
|
297
|
+
- `packages/databases/mongo/src/mongo.repo.ts`
|
|
298
|
+
- `packages/databases/mongo/src/helpers/mongo.helper.ts`
|
|
299
|
+
- `packages/databases/mongo/src/helpers/mongo.pipeline.ts`
|
|
300
|
+
- `packages/databases/mongo/src/helpers/mongo.utils.ts`
|
|
301
|
+
- `packages/databases/mongo/src/models/*`
|
|
293
302
|
|
|
294
303
|
## Module Setup
|
|
295
304
|
|
|
@@ -311,7 +320,8 @@ Repository checklist:
|
|
|
311
320
|
- Keep schema-specific query helpers in the app repository, not in controllers.
|
|
312
321
|
- Use repository methods for standard reads so query parsing, soft delete, populate, and pagination stay consistent.
|
|
313
322
|
- Pass transaction/session options through read-modify-write flows when the app uses transactions.
|
|
314
|
-
-
|
|
323
|
+
- Repository read paths should return schema class instances with normalized ObjectId/string values, including populated and deep-populated values.
|
|
324
|
+
- Code that needs raw Mongoose documents should use `MongoService.getModel(...)` or Typegoose/Mongoose APIs directly.
|
|
315
325
|
|
|
316
326
|
## Query Safety
|
|
317
327
|
|
|
@@ -346,13 +356,20 @@ Cursor checklist:
|
|
|
346
356
|
|
|
347
357
|
When blocked, inspect:
|
|
348
358
|
|
|
349
|
-
- `
|
|
350
|
-
- `
|
|
351
|
-
- `packages/databases/mongo
|
|
352
|
-
-
|
|
353
|
-
- `packages/databases/mongo/src/
|
|
354
|
-
- `packages/databases/mongo/src/
|
|
355
|
-
- `packages/databases/mongo/src/
|
|
359
|
+
- Consumer project first: `node_modules/@joktec/mongo`.
|
|
360
|
+
- Package docs next: `node_modules/@joktec/mongo/README.md`.
|
|
361
|
+
- GitHub docs next: `https://github.com/joktec/joktec-framework/tree/main/packages/databases/mongo`.
|
|
362
|
+
- GitHub source fallback:
|
|
363
|
+
- `packages/databases/mongo/src/decorators/schema.decorator.ts`
|
|
364
|
+
- `packages/databases/mongo/src/decorators/schema.options.ts`
|
|
365
|
+
- `packages/databases/mongo/src/decorators/prop.decorator.ts`
|
|
366
|
+
- `packages/databases/mongo/src/decorators/props/*`
|
|
367
|
+
- `packages/databases/mongo/src/models/mongo.ref.ts`
|
|
368
|
+
- `packages/databases/mongo/src/models/object-id.ts`
|
|
369
|
+
- `packages/databases/mongo/src/models/mongo.schema.ts`
|
|
370
|
+
- `packages/databases/mongo/src/plugins/paranoid.plugin.ts`
|
|
371
|
+
- `packages/databases/mongo/src/plugins/strict-reference.plugin.ts`
|
|
372
|
+
- `packages/databases/mongo/src/plugins/transform.plugin.ts`
|
|
356
373
|
|
|
357
374
|
## Schema Decorators
|
|
358
375
|
|
|
@@ -367,6 +384,35 @@ Best practice:
|
|
|
367
384
|
- Pass custom validators/transforms explicitly rather than adding hidden global behavior.
|
|
368
385
|
- Keep maps, snapshots, and dynamic objects explicit so helper conversion does not alter their shape.
|
|
369
386
|
- Keep app-level reference semantics visible; strict reference plugin checks existence, but the app still owns domain rules.
|
|
387
|
+
- Use `RefId<T>` for persisted id fields and `PopulatedRef<T>` for populated virtual instance fields.
|
|
388
|
+
- Use lazy `type` resolvers such as `type: () => User` or `type: () => [User]` when the wrapper cannot infer the runtime class.
|
|
389
|
+
- Use `@Schema({ kind: 'embedded' })` for value objects without `_id` or timestamps.
|
|
390
|
+
- Use `@Schema({ kind: 'subdocument' })` for embedded documents that need `_id` and timestamps but should not create a collection.
|
|
391
|
+
- Use `@Prop({ kind: 'virtual', mode: 'getter', comment, optional, hidden, expose, swagger })` for computed getters that only need class-transformer and Swagger metadata.
|
|
392
|
+
- Use `@Prop({ ref: () => User, foreignField, localField })` for populate-one virtuals when inferred defaults are enough.
|
|
393
|
+
- Use `@Prop({ type: () => [User], ref: () => User, foreignField, localField })` for populate-array virtuals.
|
|
394
|
+
- Use `@Prop({ kind: 'map', type: Object })` for raw maps/snapshots instead of passing `PropType.MAP` at the call site.
|
|
395
|
+
|
|
396
|
+
Common mappings:
|
|
397
|
+
|
|
398
|
+
| Use case | Preferred shape |
|
|
399
|
+
| --- | --- |
|
|
400
|
+
| Stored single reference id | `fieldId?: RefId<User>` with `@Prop({ type: ObjectId, ref: () => User })` |
|
|
401
|
+
| Stored reference id array | `fieldIds?: RefId<User>[]` with `@Prop({ type: [ObjectId], ref: () => User })` |
|
|
402
|
+
| Embedded value object | `@Schema({ kind: 'embedded' })` on the nested class |
|
|
403
|
+
| Embedded document | `@Schema({ kind: 'subdocument' })` on the nested class |
|
|
404
|
+
| Raw map/snapshot | `@Prop({ kind: 'map', type: Object })` |
|
|
405
|
+
| Populated single virtual | `field?: PopulatedRef<User>` with `@Prop({ ref: () => User, foreignField: '_id', localField: 'fieldId' })` |
|
|
406
|
+
| Populated virtual array | `fields?: PopulatedRef<User>[]` with `@Prop({ type: () => [User], ref: () => User, foreignField: '_id', localField: 'fieldIds' })` |
|
|
407
|
+
| Computed getter | `@Prop({ kind: 'virtual', mode: 'getter', comment: '...' }) get value() { ... }` |
|
|
408
|
+
|
|
409
|
+
Populate inference:
|
|
410
|
+
|
|
411
|
+
- `ref` + `localField` + `foreignField` marks the field as virtual populate.
|
|
412
|
+
- Populate-one can fallback to the same class from `ref`.
|
|
413
|
+
- Populate arrays still need `type: () => [Target]` because runtime reflection only sees `Array`.
|
|
414
|
+
- `justOne` defaults to `true` for non-array populate fields.
|
|
415
|
+
- Swagger examples default to `{}` or `[]` for populated fields unless explicitly overridden.
|
|
370
416
|
|
|
371
417
|
## Plugins
|
|
372
418
|
|
|
@@ -401,8 +447,12 @@ Use this skill for relational resources backed by JokTec's TypeORM wrapper.
|
|
|
401
447
|
- Treat `mysql`, `mariadb`, and `postgres` as the first-class dialects.
|
|
402
448
|
- Keep `sync` explicit and normally enabled only by an owner process or development bootstrap.
|
|
403
449
|
- Do not add new behavior to deprecated `MysqlFinder`; use `MysqlRepo.qb()` and `MysqlHelper` paths.
|
|
404
|
-
-
|
|
405
|
-
-
|
|
450
|
+
- Use schema-first `@Column`, `@PrimaryColumn`, and `@TimestampColumn` wrappers when an entity also acts as DTO metadata.
|
|
451
|
+
- Use `@Column({ kind: 'virtual' })` for computed getters that need expose/Swagger metadata without persistence.
|
|
452
|
+
- Use `immutable` for API read-only metadata; TypeORM `update: false` remains storage write behavior and is also inferred as Swagger read-only when `immutable` is not set.
|
|
453
|
+
- Do not add `swagger.type` just because a column has a primitive, date, array, nested JSON class, or relation type. The wrapper infers Swagger metadata from TypeScript design type and JokTec options. Use `swagger` only to override an inferred shape.
|
|
454
|
+
- Do not use `@joktec/mysql` for Mongo/ObjectId columns, even though TypeORM has Mongo-related APIs.
|
|
455
|
+
- For real migrations, inspect the installed `@joktec/mysql` source in the consumer project's `node_modules` first. If that is insufficient, read GitHub package docs, then GitHub source. Use the local `../joktec-framework` checkout only when you are working inside the JokTec development workspace.
|
|
406
456
|
|
|
407
457
|
## References
|
|
408
458
|
|
|
@@ -417,13 +467,29 @@ Use this skill for relational resources backed by JokTec's TypeORM wrapper.
|
|
|
417
467
|
|
|
418
468
|
## Source Lookup
|
|
419
469
|
|
|
420
|
-
When blocked, inspect:
|
|
470
|
+
When blocked in a consumer project, inspect the installed package first:
|
|
471
|
+
|
|
472
|
+
- `node_modules/@joktec/mysql/README.md`
|
|
473
|
+
- `node_modules/@joktec/mysql/AGENTS.md` when published with the package
|
|
474
|
+
- `node_modules/@joktec/mysql/dist/index.d.ts`
|
|
475
|
+
- `node_modules/@joktec/mysql/dist/decorators/column.decorator.d.ts`
|
|
476
|
+
- `node_modules/@joktec/mysql/dist/decorators/columns/column.type.d.ts`
|
|
477
|
+
- `node_modules/@joktec/mysql/dist/decorators/timestamp.decorator.d.ts`
|
|
478
|
+
|
|
479
|
+
If the installed package is missing enough detail, use the GitHub package docs next:
|
|
480
|
+
|
|
481
|
+
- `https://github.com/joktec/joktec-framework/tree/main/packages/databases/mysql`
|
|
482
|
+
|
|
483
|
+
Use GitHub source only after package docs and installed types are not enough:
|
|
421
484
|
|
|
422
485
|
- `packages/databases/mysql/src/decorators/table.decorator.ts`
|
|
423
486
|
- `packages/databases/mysql/src/decorators/column.decorator.ts`
|
|
424
487
|
- `packages/databases/mysql/src/decorators/columns/column.type.ts`
|
|
425
488
|
- `packages/databases/mysql/src/decorators/columns/column.factory.ts`
|
|
426
489
|
- `packages/databases/mysql/src/decorators/columns/primary.column.ts`
|
|
490
|
+
- `packages/databases/mysql/src/decorators/columns/timestamp.column.ts`
|
|
491
|
+
- `packages/databases/mysql/src/decorators/columns/virtual.column.ts`
|
|
492
|
+
- `packages/databases/mysql/src/decorators/columns/object.column.ts`
|
|
427
493
|
- `packages/databases/mysql/src/decorators/columns/string.column.ts`
|
|
428
494
|
- `packages/databases/mysql/src/decorators/columns/number.column.ts`
|
|
429
495
|
- `packages/databases/mysql/src/decorators/columns/transform.column.ts`
|
|
@@ -431,7 +497,7 @@ When blocked, inspect:
|
|
|
431
497
|
|
|
432
498
|
## Schema-First Entity Pattern
|
|
433
499
|
|
|
434
|
-
Use `@Tables`, `@Column`, and `@
|
|
500
|
+
Use `@Tables`, `@Column`, `@PrimaryColumn`, and `@TimestampColumn` from `@joktec/mysql` when an entity should also act as the source class for mapped DTOs.
|
|
435
501
|
|
|
436
502
|
The new decorators are not thin TypeORM aliases. They are schema-first wrappers that compose:
|
|
437
503
|
|
|
@@ -440,11 +506,19 @@ The new decorators are not thin TypeORM aliases. They are schema-first wrappers
|
|
|
440
506
|
- `class-transformer` expose/exclude behavior.
|
|
441
507
|
- Swagger property metadata.
|
|
442
508
|
|
|
443
|
-
|
|
509
|
+
The wrapper can also represent virtual computed getters and nested JSON/jsonb class payloads. Do not use this package for Mongo/ObjectId columns.
|
|
510
|
+
|
|
511
|
+
Wrapper philosophy:
|
|
444
512
|
|
|
445
|
-
|
|
513
|
+
- prefer one schema declaration that carries persistence, validation, transform, and Swagger metadata
|
|
514
|
+
- use wrapper options before duplicating `@ApiProperty`, `@Expose`, `@Type`, or common validators
|
|
515
|
+
- do not add `swagger.type` for normal scalar/date fields, arrays, nested JSON classes, or relations when the wrapper can infer the shape
|
|
516
|
+
- use raw TypeORM only for advanced cases that the wrapper does not model cleanly
|
|
517
|
+
- keep storage write behavior and API documentation behavior distinct when needed
|
|
446
518
|
|
|
447
|
-
|
|
519
|
+
## Current Decorator Capabilities
|
|
520
|
+
|
|
521
|
+
Use the package README and actual installed source for full migration details. This skill only keeps the common mappings that help an agent recognize old patterns.
|
|
448
522
|
|
|
449
523
|
Common mappings:
|
|
450
524
|
|
|
@@ -453,11 +527,21 @@ Common mappings:
|
|
|
453
527
|
| `@PrimaryGeneratedColumn()` | `@PrimaryColumn('increment')` |
|
|
454
528
|
| `@PrimaryGeneratedColumn('uuid')` | `@PrimaryColumn('uuid')` |
|
|
455
529
|
| app-generated ordered UUID id | `@PrimaryColumn('uuidv7')` |
|
|
530
|
+
| `@CreateDateColumn(...)` | `@TimestampColumn('create', ...)` |
|
|
531
|
+
| `@UpdateDateColumn(...)` | `@TimestampColumn('update', ...)` |
|
|
532
|
+
| `@DeleteDateColumn(...)` | `@TimestampColumn('delete', ...)` |
|
|
533
|
+
| TypeORM `@VersionColumn(...)` | `@Column({ kind: 'version', ... })` |
|
|
534
|
+
| TypeORM `@VirtualColumn(...)` | `@Column({ kind: 'virtual', mode: 'sql', query, ... })` |
|
|
535
|
+
| TypeORM `@ViewColumn(...)` | `@Column({ kind: 'view', ... })` |
|
|
456
536
|
| `@Column(...)` | `@Column(...)` from `@joktec/mysql` |
|
|
537
|
+
| `@RelationId(...)` | `@Column({ kind: 'relation-id', relationId })` |
|
|
457
538
|
| `@IsNotEmpty()` | `@Column({ required: true })` |
|
|
458
539
|
| `@IsOptional()` | `@Column({ required: false })` or nullable TypeORM option when storage allows null |
|
|
459
540
|
| `@IsEmail()` | `@Column({ isEmail: true })` |
|
|
460
541
|
| `@IsMobilePhone()` | `@Column({ isPhone: true })` |
|
|
542
|
+
| `@IsInt()` | `@Column({ isInt: true })` or an integer column type |
|
|
543
|
+
| `@IsUUID()` | `@Column({ isUUID: true })` |
|
|
544
|
+
| `@IsObject()` | `@Column({ isObject: true })` or a JSON column |
|
|
461
545
|
| `@IsHexColor()` | `@Column({ isHexColor: true })` |
|
|
462
546
|
| `@IsUrl()` | `@Column({ isUrl: true })` |
|
|
463
547
|
| `@MinLength(n)` | `@Column({ minLength: n })` |
|
|
@@ -467,54 +551,49 @@ Common mappings:
|
|
|
467
551
|
| `@Expose()` | default behavior of `@Column(...)` |
|
|
468
552
|
| `@Expose({ groups })` | `@Column({ groups })` |
|
|
469
553
|
| `@Exclude({ toPlainOnly: true })` plus hidden Swagger | `@Column({ hidden: true })` |
|
|
470
|
-
| `@ApiProperty(...)` |
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
- Replace TypeORM column decorators with wrappers from `@joktec/mysql`.
|
|
477
|
-
- Remove duplicate `class-validator` decorators when equivalent wrapper options exist.
|
|
478
|
-
- Remove duplicate `class-transformer` decorators when `hidden` or `groups` expresses the same behavior.
|
|
479
|
-
- Move Swagger examples/descriptions/limits into wrapper options where possible.
|
|
480
|
-
- Preserve custom validators/transforms only through `decorators: [...]`.
|
|
481
|
-
- Keep database-specific options such as `type`, `length`, `nullable`, `unique`, `default`, `enum`, and `comment`.
|
|
482
|
-
- Rebuild and run entity-related tests after migration because DTO metadata and TypeORM metadata both change.
|
|
483
|
-
|
|
484
|
-
Example migration:
|
|
485
|
-
|
|
486
|
-
```ts
|
|
487
|
-
// Before
|
|
488
|
-
@Column({ type: 'varchar', length: 255 })
|
|
489
|
-
@IsEmail()
|
|
490
|
-
@IsNotEmpty()
|
|
491
|
-
@Expose()
|
|
492
|
-
@ApiProperty({ example: 'user@example.com' })
|
|
493
|
-
email!: string;
|
|
494
|
-
|
|
495
|
-
// After
|
|
496
|
-
@Column('varchar', {
|
|
497
|
-
length: 255,
|
|
498
|
-
required: true,
|
|
499
|
-
isEmail: true,
|
|
500
|
-
example: 'user@example.com',
|
|
501
|
-
})
|
|
502
|
-
email!: string;
|
|
503
|
-
```
|
|
554
|
+
| `@ApiProperty(...)` | Prefer native options such as `example`, `comment`, `deprecated`, `min`, `max`, `minLength`, `maxLength`; use `swagger` only as an override |
|
|
555
|
+
| `@ValidateNested()` + `@Type(() => Preference)` | `@Column('jsonb', { nested: Preference })` |
|
|
556
|
+
| `@ValidateNested({ each: true })` + `@Type(() => Preference)` | `@Column('jsonb', { nested: Preference, each: true })` |
|
|
557
|
+
| `@Expose()` + `@ApiProperty(...)` on a getter | `@Column({ kind: 'virtual', ... })` |
|
|
558
|
+
| Swagger `readOnly: true` | `@Column({ immutable: true })` or TypeORM `update: false` when ORM updates must also be blocked |
|
|
504
559
|
|
|
505
|
-
|
|
560
|
+
## Swagger Metadata Rules
|
|
506
561
|
|
|
507
|
-
|
|
508
|
-
// Before
|
|
509
|
-
@Column({ type: 'varchar', length: 255 })
|
|
510
|
-
@Exclude({ toPlainOnly: true })
|
|
511
|
-
@ApiHideProperty()
|
|
512
|
-
password!: string;
|
|
562
|
+
The `@Column` wrapper already composes Swagger metadata. During migrations, keep entity code small and avoid redundant `swagger` declarations:
|
|
513
563
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
564
|
+
- Do not add `swagger: { type: String }` for string columns.
|
|
565
|
+
- Do not add `swagger: { type: Number }` for numeric columns.
|
|
566
|
+
- Do not add `swagger: { type: Boolean }` for boolean columns.
|
|
567
|
+
- Do not add `swagger: { type: Date }` for date, datetime, or timestamp columns.
|
|
568
|
+
- Do not add `swagger: { type: String, isArray: true }` for `simple-array` or reflected array fields unless the OpenAPI shape must differ from the entity field.
|
|
569
|
+
- Do not add `swagger: { type: NestedClass }` when `nested: NestedClass` is already present.
|
|
570
|
+
- Do not add `swagger: { type: () => Entity }` on `Column({ kind: 'relation', type: () => Entity })`; the relation wrapper keeps the Swagger type lazy to avoid circular schema evaluation.
|
|
571
|
+
|
|
572
|
+
Use `swagger` only for actual overrides, for example:
|
|
573
|
+
|
|
574
|
+
- an OpenAPI primitive differs from the TypeScript property type, such as a SQL decimal represented as `string`
|
|
575
|
+
- a property needs `oneOf`, `anyOf`, `allOf`, custom `items`, or a deliberately shortened example
|
|
576
|
+
- a generated schema needs an explicit read/write/deprecated override that cannot be represented by wrapper options
|
|
577
|
+
|
|
578
|
+
If a relation wrapper still triggers a circular Swagger error in a consumer project, inspect `node_modules/@joktec/mysql/dist/decorators/column.decorator.d.ts` and the installed implementation first. Do not paper over the issue by adding `swagger.type` to every relation; identify whether the installed package version has lazy relation Swagger support.
|
|
579
|
+
|
|
580
|
+
## Read-Only Metadata
|
|
581
|
+
|
|
582
|
+
`immutable` is the API read-only hint used by the JokTec MySQL wrapper. TypeORM `update: false` is the ORM write behavior. The wrapper maps both to Swagger `readOnly` when appropriate:
|
|
583
|
+
|
|
584
|
+
- `immutable` has priority over `update: false`
|
|
585
|
+
- `update: false` implies Swagger `readOnly` only when `immutable` is not set
|
|
586
|
+
- `swagger.readOnly` remains the final explicit override
|
|
587
|
+
|
|
588
|
+
Some field kinds default to API read-only because they are system-managed or computed:
|
|
589
|
+
|
|
590
|
+
- primary keys
|
|
591
|
+
- timestamp columns
|
|
592
|
+
- version columns
|
|
593
|
+
- view columns
|
|
594
|
+
- virtual getter and SQL virtual columns
|
|
595
|
+
- relation-id columns
|
|
596
|
+
- tree level columns
|
|
518
597
|
|
|
519
598
|
## Primary Keys
|
|
520
599
|
|
|
@@ -535,7 +614,21 @@ The stable dialects are MySQL, MariaDB, and Postgres. Dialect capabilities own d
|
|
|
535
614
|
|
|
536
615
|
## Source Lookup
|
|
537
616
|
|
|
538
|
-
When blocked, inspect:
|
|
617
|
+
When blocked in a consumer project, inspect installed package docs and types first:
|
|
618
|
+
|
|
619
|
+
- `node_modules/@joktec/mysql/README.md`
|
|
620
|
+
- `node_modules/@joktec/mysql/AGENTS.md` when published with the package
|
|
621
|
+
- `node_modules/@joktec/mysql/dist/index.d.ts`
|
|
622
|
+
- `node_modules/@joktec/mysql/dist/mysql.module.d.ts`
|
|
623
|
+
- `node_modules/@joktec/mysql/dist/mysql.service.d.ts`
|
|
624
|
+
- `node_modules/@joktec/mysql/dist/mysql.repo.d.ts`
|
|
625
|
+
- `node_modules/@joktec/mysql/dist/models/mysql.request.d.ts`
|
|
626
|
+
|
|
627
|
+
If the installed package is insufficient, read GitHub package docs next:
|
|
628
|
+
|
|
629
|
+
- `https://github.com/joktec/joktec-framework/tree/main/packages/databases/mysql`
|
|
630
|
+
|
|
631
|
+
Use GitHub source only after installed types and package docs are not enough:
|
|
539
632
|
|
|
540
633
|
- `packages/databases/mysql/README.md`
|
|
541
634
|
- `packages/databases/mysql/AGENTS.md`
|
|
@@ -924,6 +1017,7 @@ Best practice:
|
|
|
924
1017
|
- Use the package service for outbound HTTP so retry/proxy/metrics behavior stays centralized.
|
|
925
1018
|
- Keep external endpoint URLs and credentials in runtime config.
|
|
926
1019
|
- Be careful with ESM/CommonJS import changes in HTTP/Axios ecosystem packages.
|
|
1020
|
+
- `HttpService.buildAgent(proxy, opts)` expects proxy identity in `HttpProxyConfig` and agent tuning in Node `AgentOptions`; inspect the installed source before adapting to proxy-agent major-version changes.
|
|
927
1021
|
- Test request behavior with mocks unless the test is an explicit consumer integration scenario.
|
|
928
1022
|
|
|
929
1023
|
## File
|
|
@@ -951,3 +1045,437 @@ Best practice:
|
|
|
951
1045
|
- Do not scatter raw Axios instances across the app when `@joktec/http` should own shared behavior.
|
|
952
1046
|
- Do not commit webhook URLs or proxy credentials.
|
|
953
1047
|
- Do not use tool packages as hidden places for app business rules.
|
|
1048
|
+
|
|
1049
|
+
---
|
|
1050
|
+
|
|
1051
|
+
## Advanced TypeScript Design
|
|
1052
|
+
|
|
1053
|
+
## Overview
|
|
1054
|
+
|
|
1055
|
+
Act as a TypeScript architecture partner. Choose the simplest design that preserves clear boundaries, runtime correctness, and useful compile-time guarantees.
|
|
1056
|
+
|
|
1057
|
+
Use design patterns as vocabulary and pressure tests, not as decoration. Prefer local project conventions, readable APIs, and low-friction extension points before adding type-level machinery.
|
|
1058
|
+
|
|
1059
|
+
## Architectural Mindset
|
|
1060
|
+
|
|
1061
|
+
- Start from the domain boundary: identify entity, request/response, service, repository, client, decorator, loader, and integration responsibilities.
|
|
1062
|
+
- Keep public APIs narrow and stable. Make extension explicit through interfaces, abstract classes, generic constraints, or composition.
|
|
1063
|
+
- Use classes when lifecycle, inheritance hooks, decorators, or framework reflection matter. Use plain functions/types when behavior is stateless or purely transformational.
|
|
1064
|
+
- Let runtime validation and compile-time types reinforce each other. Do not pretend TypeScript types validate untrusted runtime data.
|
|
1065
|
+
- Do not assume TypeScript generics, interfaces, unions, or array element types exist at runtime through `reflect-metadata`.
|
|
1066
|
+
- Escalate type complexity only when it removes real duplication, prevents invalid states, or makes an API substantially safer.
|
|
1067
|
+
- Check existing code before inventing a new pattern; mirror the repository's style when it already solves the same class of problem.
|
|
1068
|
+
|
|
1069
|
+
## Public API Compatibility
|
|
1070
|
+
|
|
1071
|
+
- Treat exported types, classes, decorators, config objects, modules, and provider APIs as public contracts.
|
|
1072
|
+
- Prefer additive changes over breaking renames, deleted fields, changed generic parameter order, or narrower accepted input shapes.
|
|
1073
|
+
- Before changing exported generic types, check downstream inference from normal call sites and verify that common extension patterns still compile.
|
|
1074
|
+
- If a breaking type or runtime contract change is unavoidable, report migration impact explicitly and include the smallest migration path.
|
|
1075
|
+
|
|
1076
|
+
## Pattern Vocabulary
|
|
1077
|
+
|
|
1078
|
+
Use the classic catalog as shared language, including the TypeScript examples catalog from Refactoring.Guru.
|
|
1079
|
+
|
|
1080
|
+
- Creational Patterns: Abstract Factory, Builder, Factory Method, Prototype, Singleton.
|
|
1081
|
+
- Structural Patterns: Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy.
|
|
1082
|
+
- Behavioral Patterns: Chain of Responsibility, Iterator, Memento, State, Template Method, Command, Mediator, Observer, Strategy, Visitor.
|
|
1083
|
+
|
|
1084
|
+
Treat pattern names as a starting point for design discussion. Validate whether the implementation needs the pattern's tradeoffs, or whether a direct function, data object, or interface is enough.
|
|
1085
|
+
|
|
1086
|
+
## Agent Workflow
|
|
1087
|
+
|
|
1088
|
+
1. Inspect local code first when working inside a repository. Look for existing abstractions, decorators, DTO types, factory functions, lifecycle hooks, and tests.
|
|
1089
|
+
2. Classify the task:
|
|
1090
|
+
- Use `references/simple.md` for everyday TypeScript, OOP, data modeling, classes, interfaces, types, records, maps, arrays, simple decorators, and pragmatic refactors.
|
|
1091
|
+
- Use `references/advanced.md` for generic framework code, recursive mapped types, `infer`, distributed/deferred conditional types, reflection metadata, advanced decorators, type-safe builders, plugin architectures, or expert pattern selection.
|
|
1092
|
+
3. Choose the least complex pattern that solves the force in front of you. Record why a simpler alternative was not enough when choosing advanced machinery.
|
|
1093
|
+
4. Design the public surface before implementation: inputs, outputs, extension points, error behavior, lifecycle, and type inference experience.
|
|
1094
|
+
5. Implement incrementally. Keep runtime behavior testable, and add focused type-level checks when exported generic or decorator behavior is subtle.
|
|
1095
|
+
6. Review for overengineering: remove unused generic parameters, speculative base classes, unnecessary inheritance, and type utilities that do not protect a real API.
|
|
1096
|
+
|
|
1097
|
+
## Repository Signals
|
|
1098
|
+
|
|
1099
|
+
In JokTec-style TypeScript, expect patterns such as:
|
|
1100
|
+
|
|
1101
|
+
- Generic request/query types with recursive conditions and sort/populate typing.
|
|
1102
|
+
- Factory functions that return decorated NestJS classes.
|
|
1103
|
+
- Abstract services and clients with template methods for lifecycle-specific behavior.
|
|
1104
|
+
- Decorator factories that compose validation, Swagger, transformation, metrics, and integration metadata.
|
|
1105
|
+
- Loader/registry patterns that collect decorator metadata and wire runtime behavior during module initialization.
|
|
1106
|
+
|
|
1107
|
+
## Bundled References
|
|
1108
|
+
|
|
1109
|
+
### references/advanced.md
|
|
1110
|
+
|
|
1111
|
+
# Advanced TypeScript Design Guidance
|
|
1112
|
+
|
|
1113
|
+
Use this reference for framework-level TypeScript, generic libraries, decorator infrastructure, metadata-driven loaders, and APIs where compile-time inference is part of the product experience.
|
|
1114
|
+
|
|
1115
|
+
## Escalation Criteria
|
|
1116
|
+
|
|
1117
|
+
Reach for advanced TypeScript only when at least one is true:
|
|
1118
|
+
|
|
1119
|
+
- The API is reused widely and type inference prevents real misuse.
|
|
1120
|
+
- The runtime model is already generic, recursive, or metadata-driven.
|
|
1121
|
+
- The abstraction eliminates repeated boilerplate across many entities, DTOs, repositories, services, clients, or transports.
|
|
1122
|
+
- The type-level design mirrors a stable domain contract, not a speculative future.
|
|
1123
|
+
- Tests or examples can prove both runtime behavior and developer ergonomics.
|
|
1124
|
+
|
|
1125
|
+
If the advanced type exists only to feel clever, delete it.
|
|
1126
|
+
|
|
1127
|
+
## Infer, Conditional, and Deferred Types
|
|
1128
|
+
|
|
1129
|
+
- Use `infer` to extract return types, payloads, entity types, DTO shapes, tuple elements, and callback signatures from source contracts.
|
|
1130
|
+
- Control distributive conditional types intentionally. Wrap operands in tuples, such as `[T] extends [U]`, when union distribution is not wanted.
|
|
1131
|
+
- Prefer named intermediate aliases when nested conditionals exceed two branches.
|
|
1132
|
+
- Use `never` as a filter, but verify that it cannot erase useful error information from public APIs.
|
|
1133
|
+
- Treat deferred conditional types and generic inference as public UX: callers should get helpful autocomplete and errors without manual type arguments.
|
|
1134
|
+
|
|
1135
|
+
## Recursive and Mapped Types
|
|
1136
|
+
|
|
1137
|
+
- Use recursive mapped types for query languages, nested sort/select/populate APIs, deep partials, and entity graph traversal.
|
|
1138
|
+
- Add clear stop conditions for primitives, dates, arrays, functions, and branded values.
|
|
1139
|
+
- Use branded or opaque types for special primitives such as `ObjectId`, `UserId`, tenant IDs, cursors, or external reference IDs when plain strings would blur domain boundaries.
|
|
1140
|
+
- Avoid infinite or overly expensive type recursion. Keep recursion shallow enough for editor performance.
|
|
1141
|
+
- Preserve optionality and readonly modifiers intentionally with `+?`, `-?`, `readonly`, and `-readonly`.
|
|
1142
|
+
- Separate query operator typing from entity typing so the operator model remains testable and reusable.
|
|
1143
|
+
|
|
1144
|
+
## Reflection and Decorators
|
|
1145
|
+
|
|
1146
|
+
- Use `reflect-metadata` only when runtime type information materially improves the API: schema generation, validation composition, serialization, dependency injection, or loader registration.
|
|
1147
|
+
- Remember that reflected TypeScript types are lossy at runtime. Arrays, unions, generics, and interfaces need explicit options or factories.
|
|
1148
|
+
- Prefer decorator factories that normalize options, resolve type factories, compose framework decorators, and define one clear metadata contract.
|
|
1149
|
+
- Keep advanced decorators thin at the call site and explicit internally: clone options, sanitize runtime-only fields, then compose validators, transformers, docs, and persistence metadata.
|
|
1150
|
+
- For method decorators, preserve `this`, return values, thrown errors, and async behavior unless the decorator explicitly changes them.
|
|
1151
|
+
- Use function source parsing only as a last-resort runtime technique for decorator infrastructure, such as mapping method argument names. Keep it isolated, deterministic, and covered by tests because minification, transpilation, defaults, destructuring, and comments can break it.
|
|
1152
|
+
- Test decorator behavior through a class that uses it, especially for metadata, wrapping behavior, and dependency injection.
|
|
1153
|
+
|
|
1154
|
+
## Type-Level Verification
|
|
1155
|
+
|
|
1156
|
+
- Add type-level tests when changing exported generic utilities, query DSLs, decorators, builders, or public inference-heavy APIs.
|
|
1157
|
+
- Prefer the project's existing compile/type test setup. If available, use `tsd`, `expect-type`, `vitest`/`jest` type helpers, or a dedicated `tsc --noEmit` fixture.
|
|
1158
|
+
- Include positive inference examples from normal call sites, not only explicit generic arguments.
|
|
1159
|
+
- Include negative examples with `@ts-expect-error` when an invalid state must stay rejected.
|
|
1160
|
+
- Verify runtime tests separately when decorators, reflection metadata, validation, transformation, or loaders are involved.
|
|
1161
|
+
|
|
1162
|
+
## Expert Pattern Selection
|
|
1163
|
+
|
|
1164
|
+
- Abstract Factory fits families of related clients, repositories, or transport adapters that must be created consistently.
|
|
1165
|
+
- Builder fits fluent configuration with required-step guarantees; type-state builders can enforce completeness but should stay readable.
|
|
1166
|
+
- Factory Method fits framework hooks that create DTOs, pagination wrappers, controllers, or provider instances.
|
|
1167
|
+
- Prototype fits cloning configured objects when construction is expensive or stateful.
|
|
1168
|
+
- Singleton should usually be delegated to the DI container; avoid hand-rolled global state.
|
|
1169
|
+
- Adapter fits third-party client normalization and migration layers.
|
|
1170
|
+
- Bridge fits separating abstraction from implementation, such as transport-agnostic messaging APIs over Rabbit, Kafka, or Redis.
|
|
1171
|
+
- Composite fits tree-shaped filters, pipelines, menu/routes, or nested query conditions.
|
|
1172
|
+
- Decorator fits metrics, retries, circuit breakers, serialization, validation, or publishing side effects around existing behavior.
|
|
1173
|
+
- Facade fits a stable service hiding multiple low-level collaborators.
|
|
1174
|
+
- Flyweight fits large repeated metadata or schema objects only after measuring memory pressure.
|
|
1175
|
+
- Proxy fits lazy clients, caching, access control, retries, and remote boundaries.
|
|
1176
|
+
- Chain of Responsibility fits validation, middleware, parsing, and request pipelines.
|
|
1177
|
+
- Command fits queued work, replayable operations, and undoable actions.
|
|
1178
|
+
- Iterator fits cursor pagination and collection traversal without exposing storage details.
|
|
1179
|
+
- Mediator fits module coordination where direct dependencies would become tangled.
|
|
1180
|
+
- Memento fits snapshots, rollbacks, and state restoration.
|
|
1181
|
+
- Observer fits event streams and pub/sub, with explicit unsubscribe and error policy.
|
|
1182
|
+
- State fits lifecycle-heavy clients, jobs, or connections with mode-specific behavior.
|
|
1183
|
+
- Strategy fits interchangeable algorithms selected by config or runtime context.
|
|
1184
|
+
- Template Method fits abstract base services/clients that own lifecycle while subclasses implement `init`, `start`, `stop`, `validate`, or `transform` steps.
|
|
1185
|
+
- Visitor fits operations over stable object structures when adding new operations is more common than adding new node types.
|
|
1186
|
+
|
|
1187
|
+
## Symbolic Examples
|
|
1188
|
+
|
|
1189
|
+
Use examples like these as compact templates for thinking. Keep production implementations smaller or larger depending on the actual force.
|
|
1190
|
+
|
|
1191
|
+
### Type-Safe Event Map
|
|
1192
|
+
|
|
1193
|
+
```typescript
|
|
1194
|
+
type EventMap = {
|
|
1195
|
+
"user.created": { id: string };
|
|
1196
|
+
"invoice.paid": { invoiceId: string; amount: number };
|
|
1197
|
+
};
|
|
1198
|
+
|
|
1199
|
+
class EventBus<TEvents extends Record<string, unknown>> {
|
|
1200
|
+
on<K extends keyof TEvents>(event: K, handler: (payload: TEvents[K]) => void) {}
|
|
1201
|
+
emit<K extends keyof TEvents>(event: K, payload: TEvents[K]) {}
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
const bus = new EventBus<EventMap>();
|
|
1205
|
+
bus.emit("invoice.paid", { invoiceId: "inv_1", amount: 100 });
|
|
1206
|
+
```
|
|
1207
|
+
|
|
1208
|
+
Use this for Observer/Mediator-style APIs where event names and payloads must stay coupled.
|
|
1209
|
+
|
|
1210
|
+
### API Contract Inference
|
|
1211
|
+
|
|
1212
|
+
```typescript
|
|
1213
|
+
type Endpoint = {
|
|
1214
|
+
"/users/:id": {
|
|
1215
|
+
GET: { params: { id: string }; response: User };
|
|
1216
|
+
PATCH: { params: { id: string }; body: Partial<User>; response: User };
|
|
1217
|
+
};
|
|
1218
|
+
};
|
|
1219
|
+
|
|
1220
|
+
type ResponseOf<T> = T extends { response: infer R } ? R : never;
|
|
1221
|
+
|
|
1222
|
+
class ApiClient<TContract extends Record<string, any>> {
|
|
1223
|
+
request<Path extends keyof TContract, Method extends keyof TContract[Path]>(
|
|
1224
|
+
path: Path,
|
|
1225
|
+
method: Method,
|
|
1226
|
+
): Promise<ResponseOf<TContract[Path][Method]>> {
|
|
1227
|
+
return null as any;
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
```
|
|
1231
|
+
|
|
1232
|
+
Use this when a contract object should drive call-site inference.
|
|
1233
|
+
|
|
1234
|
+
### Type-State Builder
|
|
1235
|
+
|
|
1236
|
+
```typescript
|
|
1237
|
+
type With<K extends string> = Record<K, true>;
|
|
1238
|
+
|
|
1239
|
+
class JobBuilder<State = {}> {
|
|
1240
|
+
queue(name: string): JobBuilder<State & With<"queue">> {
|
|
1241
|
+
return this as any;
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
handler(fn: () => Promise<void>): JobBuilder<State & With<"handler">> {
|
|
1245
|
+
return this as any;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
build(this: State extends With<"queue"> & With<"handler"> ? JobBuilder<State> : never) {
|
|
1249
|
+
return {};
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
```
|
|
1253
|
+
|
|
1254
|
+
Use this when incomplete configuration is common and worth rejecting at compile time.
|
|
1255
|
+
|
|
1256
|
+
### Recursive Query Shape
|
|
1257
|
+
|
|
1258
|
+
```typescript
|
|
1259
|
+
type Primitive = string | number | boolean | Date;
|
|
1260
|
+
type FieldOp<T> = T | { $eq?: T; $in?: T[] };
|
|
1261
|
+
type Brand<T, Name extends string> = T & { readonly __brand: Name };
|
|
1262
|
+
type ObjectId = Brand<string, "ObjectId">;
|
|
1263
|
+
|
|
1264
|
+
type Query<T> = {
|
|
1265
|
+
[K in keyof T]?: T[K] extends Primitive
|
|
1266
|
+
? FieldOp<T[K]>
|
|
1267
|
+
: T[K] extends Array<infer U>
|
|
1268
|
+
? Query<U>
|
|
1269
|
+
: Query<T[K]>;
|
|
1270
|
+
} & {
|
|
1271
|
+
$or?: Query<T>[];
|
|
1272
|
+
};
|
|
1273
|
+
```
|
|
1274
|
+
|
|
1275
|
+
Use this for Composite-style nested filters, but add stop conditions before expanding it.
|
|
1276
|
+
|
|
1277
|
+
### Opaque ID Boundary
|
|
1278
|
+
|
|
1279
|
+
```typescript
|
|
1280
|
+
type Brand<T, Name extends string> = T & { readonly __brand: Name };
|
|
1281
|
+
type UserId = Brand<string, "UserId">;
|
|
1282
|
+
type PostId = Brand<string, "PostId">;
|
|
1283
|
+
|
|
1284
|
+
function asUserId(value: string): UserId {
|
|
1285
|
+
return value as UserId;
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
function findUser(id: UserId) {}
|
|
1289
|
+
|
|
1290
|
+
findUser(asUserId("u_1"));
|
|
1291
|
+
// @ts-expect-error raw strings are not accepted here
|
|
1292
|
+
findUser("u_1");
|
|
1293
|
+
```
|
|
1294
|
+
|
|
1295
|
+
Use this when a domain primitive crosses many generic/query layers and accidental mixing would be costly.
|
|
1296
|
+
|
|
1297
|
+
### Decorator Wrapper with Preserved Method Contract
|
|
1298
|
+
|
|
1299
|
+
```typescript
|
|
1300
|
+
function Around(run: (next: () => unknown) => unknown): MethodDecorator {
|
|
1301
|
+
return (_, __, descriptor) => {
|
|
1302
|
+
const original = descriptor.value;
|
|
1303
|
+
|
|
1304
|
+
descriptor.value = function (...args: unknown[]) {
|
|
1305
|
+
return run(() => original.apply(this, args));
|
|
1306
|
+
};
|
|
1307
|
+
};
|
|
1308
|
+
}
|
|
1309
|
+
```
|
|
1310
|
+
|
|
1311
|
+
Use this for metrics, retry, logging, or publishing side effects; preserve `this`, args, return values, and thrown errors.
|
|
1312
|
+
|
|
1313
|
+
## JokTec-Style Signals to Reuse
|
|
1314
|
+
|
|
1315
|
+
- Recursive query typing can combine entity properties with operator unions, nested entity traversal, and logical `$or`/`$and` shapes.
|
|
1316
|
+
- Base services and clients commonly use Template Method: the base class owns lifecycle and shared behavior while subclasses provide specific implementation steps.
|
|
1317
|
+
- Controller factories can return decorated classes to avoid repetitive NestJS endpoint scaffolding while preserving DTO-specific metadata.
|
|
1318
|
+
- Decorator infrastructure often composes Swagger, validation, transformation, persistence, and metric behavior from one options object.
|
|
1319
|
+
- Rabbit loaders show a metadata registry plus module-init loader pattern: decorators declare intent; loaders resolve providers and connect runtime consumers.
|
|
1320
|
+
|
|
1321
|
+
## Advanced Review Checklist
|
|
1322
|
+
|
|
1323
|
+
- Does every generic parameter appear in the public contract or implementation?
|
|
1324
|
+
- Can inference succeed from normal call-site arguments?
|
|
1325
|
+
- Does the type-level model match runtime validation and transformation?
|
|
1326
|
+
- Are metadata keys centralized and collision-resistant?
|
|
1327
|
+
- Are decorator side effects documented by tests?
|
|
1328
|
+
- Is editor performance acceptable after adding recursive or conditional types?
|
|
1329
|
+
- Is there a simpler Strategy, Adapter, or function-based design that would provide the same value?
|
|
1330
|
+
|
|
1331
|
+
### references/simple.md
|
|
1332
|
+
|
|
1333
|
+
# Simple TypeScript Design Guidance
|
|
1334
|
+
|
|
1335
|
+
Use this reference for ordinary application and library work where readability, stable APIs, and maintainable TypeScript matter more than type-level cleverness.
|
|
1336
|
+
|
|
1337
|
+
## Default Practices
|
|
1338
|
+
|
|
1339
|
+
- Prefer explicit, small interfaces for public boundaries and concrete classes for runtime behavior with lifecycle, dependency injection, or decorators.
|
|
1340
|
+
- Use `type` for unions, mapped shapes, conditional aliases, and composition. Use `interface` for object contracts intended to be implemented or extended.
|
|
1341
|
+
- Keep primitive aliases meaningful. A `UserId` alias can clarify intent, but it does not add runtime safety unless paired with validation or branding.
|
|
1342
|
+
- Model data with plain objects when behavior is absent. Add classes when construction, methods, inheritance hooks, decorators, or framework reflection are required.
|
|
1343
|
+
- Keep DTOs, entities, requests, and responses separate when they have different validation, persistence, or transport concerns.
|
|
1344
|
+
- Avoid `any` at public boundaries. Use `unknown` for untrusted data, then narrow or validate it.
|
|
1345
|
+
- Prefer narrow generic constraints such as `T extends Entity` over unconstrained `T` when the implementation depends on object semantics.
|
|
1346
|
+
|
|
1347
|
+
## Classes, Interfaces, and OOP
|
|
1348
|
+
|
|
1349
|
+
- Use abstract classes for shared runtime behavior, protected hooks, and constructor-injected dependencies.
|
|
1350
|
+
- Use interfaces for contracts that should not carry runtime behavior.
|
|
1351
|
+
- Prefer composition over inheritance when variants differ by collaborator rather than lifecycle.
|
|
1352
|
+
- Keep protected hooks purposeful: `afterInit`, `transform`, `validate`, and `map` are good when subclasses are expected to customize one stable step.
|
|
1353
|
+
- Avoid deep inheritance chains. If a third level appears, consider Strategy, Adapter, or composition.
|
|
1354
|
+
|
|
1355
|
+
## Common Data Structures
|
|
1356
|
+
|
|
1357
|
+
- Use `Record<K, V>` when the key set is known or constrained.
|
|
1358
|
+
- Use `{ [key: string]: V }` when the object is truly open-ended.
|
|
1359
|
+
- Use `Map<K, V>` when keys are not strings, insertion order matters, or frequent add/remove operations are central.
|
|
1360
|
+
- Use arrays for ordered collections and tuples for fixed positional contracts.
|
|
1361
|
+
- Use discriminated unions for state or command variants instead of loose booleans.
|
|
1362
|
+
- Keep hash-like caches private unless callers need iteration, eviction, or explicit lifecycle.
|
|
1363
|
+
|
|
1364
|
+
## Basic Types and Utilities
|
|
1365
|
+
|
|
1366
|
+
- Use union literals for finite options: status, mode, operation, direction.
|
|
1367
|
+
- Use `Pick`, `Omit`, `Partial`, `Required`, `Readonly`, `Record`, `Extract`, and `Exclude` before writing custom utilities.
|
|
1368
|
+
- Use `keyof` and indexed access types for property-safe APIs.
|
|
1369
|
+
- Use overloads sparingly; prefer a single options object when overloads become hard to read.
|
|
1370
|
+
- Keep mapped types shallow unless the data is truly nested and the API benefits from deep transformation.
|
|
1371
|
+
|
|
1372
|
+
## Simple Decorators
|
|
1373
|
+
|
|
1374
|
+
- Use decorator factories to attach framework metadata or compose existing decorators.
|
|
1375
|
+
- Keep decorator options serializable and explicit where possible.
|
|
1376
|
+
- Separate metadata collection from runtime execution. A decorator should usually register intent; a loader/service should execute it later.
|
|
1377
|
+
- Avoid parsing function source in ordinary decorators. If runtime argument-name mapping truly requires it, escalate to `advanced.md` and isolate the parser behind tests.
|
|
1378
|
+
- Test decorators at the behavior boundary, not only by checking metadata keys.
|
|
1379
|
+
|
|
1380
|
+
## Pattern Choices
|
|
1381
|
+
|
|
1382
|
+
- Use Factory Method or simple factory functions when object creation varies by type or config.
|
|
1383
|
+
- Use Builder for stepwise configuration only when partially built objects are common or order matters.
|
|
1384
|
+
- Use Adapter to normalize third-party APIs behind project interfaces.
|
|
1385
|
+
- Use Facade to simplify a noisy subsystem for callers.
|
|
1386
|
+
- Use Decorator when behavior should wrap a method/object without changing its public contract.
|
|
1387
|
+
- Use Template Method when a base class owns an algorithm and subclasses fill in specific steps.
|
|
1388
|
+
- Use Strategy when an algorithm family changes independently from the caller.
|
|
1389
|
+
- Use Observer or Mediator for event-style communication, but keep ownership and error handling explicit.
|
|
1390
|
+
|
|
1391
|
+
## Symbolic Examples
|
|
1392
|
+
|
|
1393
|
+
Use examples like these to reason about shape and tradeoffs. Keep final code adapted to the repository style.
|
|
1394
|
+
|
|
1395
|
+
### Strategy with a Narrow Interface
|
|
1396
|
+
|
|
1397
|
+
```typescript
|
|
1398
|
+
interface PriceStrategy {
|
|
1399
|
+
total(items: CartItem[]): number;
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
class RetailPrice implements PriceStrategy {
|
|
1403
|
+
total(items: CartItem[]) {
|
|
1404
|
+
return items.reduce((sum, item) => sum + item.price, 0);
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
class WholesalePrice implements PriceStrategy {
|
|
1409
|
+
total(items: CartItem[]) {
|
|
1410
|
+
return items.reduce((sum, item) => sum + item.price * 0.9, 0);
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
class CheckoutService {
|
|
1415
|
+
constructor(private readonly strategy: PriceStrategy) {}
|
|
1416
|
+
|
|
1417
|
+
quote(items: CartItem[]) {
|
|
1418
|
+
return this.strategy.total(items);
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
```
|
|
1422
|
+
|
|
1423
|
+
Use this when the caller should not know which algorithm is active.
|
|
1424
|
+
|
|
1425
|
+
### Adapter for Third-Party Boundaries
|
|
1426
|
+
|
|
1427
|
+
```typescript
|
|
1428
|
+
interface MessageBus {
|
|
1429
|
+
publish(topic: string, payload: unknown): Promise<void>;
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
class RabbitBusAdapter implements MessageBus {
|
|
1433
|
+
constructor(private readonly rabbit: RabbitClient) {}
|
|
1434
|
+
|
|
1435
|
+
publish(topic: string, payload: unknown) {
|
|
1436
|
+
return this.rabbit.sendToQueue(topic, JSON.stringify(payload));
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
```
|
|
1440
|
+
|
|
1441
|
+
Use this when external clients have noisy or unstable APIs.
|
|
1442
|
+
|
|
1443
|
+
### Template Method for Lifecycle Hooks
|
|
1444
|
+
|
|
1445
|
+
```typescript
|
|
1446
|
+
abstract class ManagedClient<TConfig, TClient> {
|
|
1447
|
+
async connect(config: TConfig) {
|
|
1448
|
+
const client = await this.create(config);
|
|
1449
|
+
await this.start(client);
|
|
1450
|
+
return client;
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
protected abstract create(config: TConfig): Promise<TClient>;
|
|
1454
|
+
protected abstract start(client: TClient): Promise<void>;
|
|
1455
|
+
}
|
|
1456
|
+
```
|
|
1457
|
+
|
|
1458
|
+
Use this when the base class owns lifecycle order and subclasses fill the variable steps.
|
|
1459
|
+
|
|
1460
|
+
### Simple Decorator Metadata
|
|
1461
|
+
|
|
1462
|
+
```typescript
|
|
1463
|
+
const HANDLER_KEY = "app:handler";
|
|
1464
|
+
|
|
1465
|
+
function Handler(name: string): MethodDecorator {
|
|
1466
|
+
return (_, __, descriptor) => {
|
|
1467
|
+
Reflect.defineMetadata(HANDLER_KEY, name, descriptor.value);
|
|
1468
|
+
};
|
|
1469
|
+
}
|
|
1470
|
+
```
|
|
1471
|
+
|
|
1472
|
+
Use this when methods declare intent and another loader executes it later.
|
|
1473
|
+
|
|
1474
|
+
## Practical Review Checklist
|
|
1475
|
+
|
|
1476
|
+
- Can a teammate understand the public API without reading private helpers?
|
|
1477
|
+
- Does the type design represent real runtime rules?
|
|
1478
|
+
- Are names domain-specific enough to explain intent?
|
|
1479
|
+
- Is the pattern solving current duplication or variability?
|
|
1480
|
+
- Are validation, transformation, persistence, and transport concerns separated?
|
|
1481
|
+
- Are tests focused on behavior that the abstraction promises to preserve?
|