@zerotal/core 1.7.2 → 1.7.4

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 CHANGED
@@ -8,6 +8,39 @@ follows the Zerotal monorepo's unified versioning.
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [1.7.3] — 2026-08-20
12
+
13
+ ### Fixed
14
+
15
+ - **`make:request` generates a file that compiles.** The stub imported `@zerotal/validator`,
16
+ which a scaffolded app does not depend on — it depends on the `zerotal` umbrella — so the
17
+ generated file failed to resolve until the import was changed by hand. It also annotated
18
+ `rules(): Record<string, FieldRule>`, the one thing `FormRequest`'s own docblock warns
19
+ against: `validate()` reads the narrow return type through `ReturnType<T['rules']>`, so the
20
+ annotation widened it back and every validated field arrived as `unknown`, silently, with a
21
+ cast somewhere downstream the first sign of it.
22
+
23
+ - **Every other `make:*` stub names a package the app has too.** The same fault ran through
24
+ nine generators: `make:command`, `make:controller`, `make:middleware` and `make:provider`
25
+ named `@zerotal/core`; `make:job` `@zerotal/queue`; `make:observer` `@zerotal/orm`;
26
+ `make:policy` `@zerotal/auth`; `make:test` `@zerotal/testing`. A scaffolded app depends on
27
+ none of them — it has the `zerotal` umbrella — so each wrote a file that did not resolve.
28
+ They now emit `zerotal` and its subpaths.
29
+
30
+ `make:resource` was worse: `Resource`, `ResourceCollection` and `PaginatedData` are not on
31
+ `@zerotal/core`'s root entry at all, so that stub was broken against the scoped name as
32
+ well. It emits `zerotal/http`, where they live.
33
+
34
+ `make:notification` keeps `@zerotal/notifications`: there is no umbrella subpath for it, and
35
+ the `api` template installs it directly.
36
+
37
+ A single test now runs all thirteen generators and fails on any import that is neither the
38
+ umbrella, a Bun/Node builtin, a relative path, nor one of the scoped packages a template
39
+ actually installs. Each generator's own test had only checked that the output mentioned the
40
+ class being made, which is why none of this showed.
41
+
42
+ ## [1.7.2] — 2026-08-18
43
+
11
44
  ### Added
12
45
 
13
46
  - **`@zerotal/core/errors`** — a subpath for the error classes, so a module that can run in a
@@ -19,7 +52,6 @@ follows the Zerotal monorepo's unified versioning.
19
52
  The rule this makes workable: **core's root entry is server-only.** Anything that might be
20
53
  bundled for a browser imports from a narrow subpath.
21
54
 
22
-
23
55
  ## [1.7.1] — 2026-08-16
24
56
 
25
57
  ### Changed
package/api-surface.md CHANGED
@@ -316,6 +316,7 @@ class HttpContext = {
316
316
  new <TParams extends Record<string, unknown> = Record<string, string>>(request: Request, container: ScopedResolver): HttpContext<TParams>
317
317
  static fake: (url?: string, init?: RequestInit, container?: ScopedResolver) => HttpContext
318
318
  static tryGet: () => HttpContext | undefined
319
+ __: (key: string, replacements?: Replacements, locale?: string) => string
319
320
  _afterResponseCallbacks: (() => Promise<void>)[]
320
321
  _pageResolver?: (pageName: string) => number | undefined
321
322
  _primeBody: (data: Record<string, unknown>) => void
@@ -368,7 +369,6 @@ class HttpContext = {
368
369
  string: (key: string, fallback?: string) => string | undefined
369
370
  subdomain: (name: string) => string | null
370
371
  subdomains: Record<string, string>
371
- t: (key: string, replacements?: Replacements, locale?: string) => string
372
372
  took: number
373
373
  user?: UserModel | undefined
374
374
  view: { (markup: ViewMarkup, status?: number): void; <P extends Record<string, unknown> = Record<string, never>>(component: (ctx: HttpContext, props: P) => ViewMarkup | Promise<ViewMarkup>, props?: P | undefined, status?: number): void | Promise<void>;}
@@ -1101,6 +1101,7 @@ interface WebhookOptions = {
1101
1101
  interface WebSocketHandlers = {
1102
1102
  close?: (ws: unknown, code: number, reason: string) => void
1103
1103
  drain?: (ws: unknown) => void
1104
+ idleTimeout?: number
1104
1105
  message: (ws: unknown, message: string | Uint8Array) => void
1105
1106
  open?: (ws: unknown) => void
1106
1107
  }
@@ -1111,7 +1112,7 @@ type AppScopeInstaller = () => () => void
1111
1112
 
1112
1113
  type ArgDef = { name: string; required?: boolean; default?: string;}
1113
1114
 
1114
- type BindingToken = keyof ContainerBindings | ClassToken<T> | AbstractToken<T>
1115
+ type BindingToken = ClassToken<T> | AbstractToken<T> | keyof ContainerBindings
1115
1116
 
1116
1117
  type ClassRef = abstract new (...args: never[]) => unknown
1117
1118
 
@@ -1139,7 +1140,7 @@ type FileRouteResolver = (ctx: FileRouteContext) => boolean
1139
1140
 
1140
1141
  type FileRoutingConfig = { [x: string]: FileRoutingEntry;}
1141
1142
 
1142
- type FileRoutingEntry = string | { dir: string; prefix?: string; middleware?: MiddlewareInput;}
1143
+ type FileRoutingEntry = string | { dir: string; prefix?: string; middleware?: MiddlewareInput }
1143
1144
 
1144
1145
  type FlagDef = { name: string; short?: string; type: 'string' | 'boolean' | 'number'; description?: string; default?: unknown;}
1145
1146
 
@@ -1177,7 +1178,7 @@ type RouteTarget = string
1177
1178
 
1178
1179
  type RoutingConfig = { [x: string]: RoutingEntry;}
1179
1180
 
1180
- type RoutingEntry = string | { file: string; prefix?: string; middleware?: MiddlewareInput;}
1181
+ type RoutingEntry = string | { file: string; prefix?: string; middleware?: MiddlewareInput }
1181
1182
 
1182
1183
  type SameSite = 'Strict' | 'Lax' | 'None'
1183
1184
 
@@ -2428,7 +2429,7 @@ interface RegisteredConfigValidator = {
2428
2429
  validate: ConfigValidator
2429
2430
  }
2430
2431
 
2431
- type AssetLoaderKind = 'text' | 'file' | 'json' | 'dataurl' | 'base64' | 'toml'
2432
+ type AssetLoaderKind = 'file' | 'dataurl' | 'base64' | 'text' | 'json' | 'toml'
2432
2433
 
2433
2434
  type ConfigIssueLevel = 'error' | 'warning'
2434
2435
 
@@ -2645,10 +2646,211 @@ const t = { readonly string: () => Def<string | undefined>; readonly numbe
2645
2646
 
2646
2647
  type EnvOutput = { readonly [K in keyof S]: InferDef<S[K]>; }
2647
2648
 
2648
- type FieldType = 'string' | 'number' | 'boolean' | 'url' | 'enum' | 'port'
2649
+ type FieldType = 'string' | 'number' | 'boolean' | 'enum' | 'url' | 'port'
2649
2650
 
2650
2651
  type InferDef = D extends Def<infer T> ? T : never
2651
2652
 
2653
+ ## ./errors `(./src/errors/index.ts)`
2654
+
2655
+ class BadRequestError = {
2656
+ new (message?: string): BadRequestError
2657
+ headers?: Record<string, string>
2658
+ readonly code: string
2659
+ readonly context?: Record<string, unknown> | undefined
2660
+ readonly status: number
2661
+ }
2662
+
2663
+ class BindingNotFoundError = {
2664
+ new (token: string): BindingNotFoundError
2665
+ readonly code: string
2666
+ readonly context?: Record<string, unknown> | undefined
2667
+ readonly status: number
2668
+ }
2669
+
2670
+ class BootCheckError = {
2671
+ new (failures: BootCheckFailure[]): BootCheckError
2672
+ readonly code: string
2673
+ readonly context?: Record<string, unknown> | undefined
2674
+ readonly failures: BootCheckFailure[]
2675
+ readonly status: number
2676
+ }
2677
+
2678
+ class CircularDependencyError = {
2679
+ new (chain: string[]): CircularDependencyError
2680
+ readonly code: string
2681
+ readonly context?: Record<string, unknown> | undefined
2682
+ readonly status: number
2683
+ }
2684
+
2685
+ class ConfigError = {
2686
+ new (message: string): ConfigError
2687
+ readonly code: string
2688
+ readonly context?: Record<string, unknown> | undefined
2689
+ readonly status: number
2690
+ }
2691
+
2692
+ class ConfigValidationError = {
2693
+ new (issues: Array<{ namespace: string; message: string; }>): ConfigValidationError
2694
+ readonly code: string
2695
+ readonly context?: Record<string, unknown> | undefined
2696
+ readonly issues: { namespace: string; message: string;}[]
2697
+ readonly status: number
2698
+ }
2699
+
2700
+ class ConflictError = {
2701
+ new (message?: string): ConflictError
2702
+ headers?: Record<string, string>
2703
+ readonly code: string
2704
+ readonly context?: Record<string, unknown> | undefined
2705
+ readonly status: number
2706
+ }
2707
+
2708
+ class ContainerLockedError = {
2709
+ new (method: string): ContainerLockedError
2710
+ readonly code: string
2711
+ readonly context?: Record<string, unknown> | undefined
2712
+ readonly status: number
2713
+ }
2714
+
2715
+ class ContextOutsideRequestError = {
2716
+ new (): ContextOutsideRequestError
2717
+ headers?: Record<string, string>
2718
+ readonly code: string
2719
+ readonly context?: Record<string, unknown> | undefined
2720
+ readonly status: number
2721
+ }
2722
+
2723
+ class FacadeAccessedBeforeBootError = {
2724
+ new (facadeKey: string): FacadeAccessedBeforeBootError
2725
+ readonly code: string
2726
+ readonly context?: Record<string, unknown> | undefined
2727
+ readonly status: number
2728
+ }
2729
+
2730
+ class FacadeBindingMissingError = {
2731
+ new (facadeKey: string): FacadeBindingMissingError
2732
+ readonly code: string
2733
+ readonly context?: Record<string, unknown> | undefined
2734
+ readonly status: number
2735
+ }
2736
+
2737
+ class ForbiddenError = {
2738
+ new (message?: string): ForbiddenError
2739
+ headers?: Record<string, string>
2740
+ readonly code: string
2741
+ readonly context?: Record<string, unknown> | undefined
2742
+ readonly status: number
2743
+ }
2744
+
2745
+ class GoneError = {
2746
+ new (message?: string): GoneError
2747
+ headers?: Record<string, string>
2748
+ readonly code: string
2749
+ readonly context?: Record<string, unknown> | undefined
2750
+ readonly status: number
2751
+ }
2752
+
2753
+ class HttpError = {
2754
+ new (message: string, status: number, code?: string, headers?: Record<string, string>): HttpError
2755
+ headers?: Record<string, string>
2756
+ readonly code: string
2757
+ readonly context?: Record<string, unknown> | undefined
2758
+ readonly status: number
2759
+ }
2760
+
2761
+ class MethodNotAllowedError = {
2762
+ new (allowed?: string[], message?: string): MethodNotAllowedError
2763
+ headers?: Record<string, string>
2764
+ readonly code: string
2765
+ readonly context?: Record<string, unknown> | undefined
2766
+ readonly status: number
2767
+ }
2768
+
2769
+ class NotFoundError = {
2770
+ new (message?: string): NotFoundError
2771
+ headers?: Record<string, string>
2772
+ readonly code: string
2773
+ readonly context?: Record<string, unknown> | undefined
2774
+ readonly status: number
2775
+ }
2776
+
2777
+ class ScopedAfterFlushError = {
2778
+ new (message: string): ScopedAfterFlushError
2779
+ readonly code: string
2780
+ readonly context?: Record<string, unknown> | undefined
2781
+ readonly status: number
2782
+ }
2783
+
2784
+ class ScopedOutsideRequestError = {
2785
+ new (message: string): ScopedOutsideRequestError
2786
+ readonly code: string
2787
+ readonly context?: Record<string, unknown> | undefined
2788
+ readonly status: number
2789
+ }
2790
+
2791
+ class ServiceUnavailableError = {
2792
+ new (reason?: string, retryAfter?: number): ServiceUnavailableError
2793
+ headers?: Record<string, string>
2794
+ readonly code: string
2795
+ readonly context?: Record<string, unknown> | undefined
2796
+ readonly retryAfter?: number | undefined
2797
+ readonly status: number
2798
+ }
2799
+
2800
+ class SyncResolutionError = {
2801
+ new (message: string): SyncResolutionError
2802
+ readonly code: string
2803
+ readonly context?: Record<string, unknown> | undefined
2804
+ readonly status: number
2805
+ }
2806
+
2807
+ class TooManyRequestsError = {
2808
+ new (retryAfter?: number, message?: string): TooManyRequestsError
2809
+ headers?: Record<string, string>
2810
+ readonly code: string
2811
+ readonly context?: Record<string, unknown> | undefined
2812
+ readonly retryAfter?: number | undefined
2813
+ readonly status: number
2814
+ }
2815
+
2816
+ class UnauthorizedError = {
2817
+ new (message?: string): UnauthorizedError
2818
+ headers?: Record<string, string>
2819
+ readonly code: string
2820
+ readonly context?: Record<string, unknown> | undefined
2821
+ readonly status: number
2822
+ }
2823
+
2824
+ class UnprocessableEntityError = {
2825
+ new (message?: string): UnprocessableEntityError
2826
+ headers?: Record<string, string>
2827
+ readonly code: string
2828
+ readonly context?: Record<string, unknown> | undefined
2829
+ readonly status: number
2830
+ }
2831
+
2832
+ class ValidationError = {
2833
+ new (message: string, errors: Record<string, string[]>): ValidationError
2834
+ headers?: Record<string, string>
2835
+ readonly code: string
2836
+ readonly context?: Record<string, unknown> | undefined
2837
+ readonly errors: Record<string, string[]>
2838
+ readonly status: number
2839
+ }
2840
+
2841
+ class ZerotalError = {
2842
+ new (message: string, code: string, status?: number, context?: Record<string, unknown> | undefined): ZerotalError
2843
+ readonly code: string
2844
+ readonly context?: Record<string, unknown> | undefined
2845
+ readonly status: number
2846
+ }
2847
+
2848
+ interface BootCheckFailure = {
2849
+ provider: string
2850
+ reason: string
2851
+ token: string
2852
+ }
2853
+
2652
2854
  ## ./facades `(./src/facade/facades/index.ts)`
2653
2855
 
2654
2856
  const App = { readonly container: Container; readonly instance: () => Application; readonly environment: () => Application['environment']; readonly isProduction: () => boolean; readonly isLocal: () => boolean; readonly make: <T>(token: BindingToken<T>, consumer?: unknown) => Promise<T>; readonly makeSync: <T>(token: BindingToken<T>) => T; readonly build: <T>(ctor: new (...args: unknown[]) => T) => Promise<T>; readonly tryMake: <K extends keyof ContainerBindings>(token: K) => ContainerBindings[K] | undefined; readonly bound: (token: BindingToken) => boolean; readonly bind: <T>(token: BindingToken<T>, factory: Factory<T>) => Container; readonly singleton: <T>(token: BindingToken<T>, factory: Factory<T>) => Container; readonly scoped: <T>(token: BindingToken<T>, factory: Factory<T>) => Container; readonly value: <T>(token: BindingToken<T>, instance: T) => Container; readonly alias: (from: unknown, to: unknown) => Container; readonly forget: (token: BindingToken) => boolean;}
@@ -2995,7 +3197,7 @@ interface WebContext = {
2995
3197
  view: (markup: string | { toString(): string;}, status?: number) => void
2996
3198
  }
2997
3199
 
2998
- type AnsiColor = 'cyan' | 'yellow' | 'green' | 'blue' | 'red' | 'dim'
3200
+ type AnsiColor = 'red' | 'green' | 'yellow' | 'blue' | 'cyan' | 'dim'
2999
3201
 
3000
3202
  type Channel = 'web' | 'json' | 'cli'
3001
3203
 
@@ -3256,11 +3458,11 @@ interface LoggingConfigShape = {
3256
3458
  slowQueryMs?: number
3257
3459
  }
3258
3460
 
3259
- type ChannelConfig = { driver: 'console'; level?: LogLevel; format?: 'json' | 'pretty';} | { driver: 'single'; level?: LogLevel; path: string;} | { driver: 'daily'; level?: LogLevel; path: string; days?: number;} | { driver: 'stack'; level?: LogLevel; channels: string[];} | { driver: 'null';}
3461
+ type ChannelConfig = { driver: 'console'; level?: LogLevel; format?: 'json' | 'pretty' } | { driver: 'single'; level?: LogLevel; path: string } | { driver: 'daily'; level?: LogLevel; path: string; days?: number } | { driver: 'stack'; level?: LogLevel; channels: string[] } | { driver: 'null' }
3260
3462
 
3261
- type LogLevel = 'error' | 'info' | 'warn' | 'debug' | 'fatal'
3463
+ type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'fatal'
3262
3464
 
3263
- type TableData = Record<string, unknown> | readonly Record<string, unknown>[]
3465
+ type TableData = Record<string, unknown> | ReadonlyArray<Record<string, unknown>>
3264
3466
 
3265
3467
  ## ./macros/config `(./src/macros/config.macro.ts)`
3266
3468
 
@@ -3294,14 +3496,37 @@ interface HttpMetricsSnapshot = {
3294
3496
 
3295
3497
  const route = RouteBuilder
3296
3498
 
3499
+ function action = <N extends RouteTarget>(name: N, params?: RouteParamValues | undefined, query?: RouteQuery | undefined) => RouteAction
3500
+
3501
+ function defineRouteMethods = (table: Readonly<Record<string, string>>) => void
3502
+
3297
3503
  function defineRoutes = (table: RouteTable) => void
3298
3504
 
3299
3505
  function hasRoute = (name: string) => boolean
3300
3506
 
3301
3507
  function resetRoutes = () => void
3302
3508
 
3509
+ function routeMethod = (name: string) => string | undefined
3510
+
3511
+ interface RouteAction = {
3512
+ method: string
3513
+ url: string
3514
+ }
3515
+
3516
+ interface RouteMethodRegistry = {}
3517
+
3518
+ type MethodedRouteName = never
3519
+
3520
+ type RouteArgs = [params?: RouteParamValues, query?: RouteQuery]
3521
+
3522
+ type RouteParamValues = { [x: string]: RouteParamValue | readonly RouteParamValue[];}
3523
+
3524
+ type RouteQuery = { [x: string]: string | number | boolean | readonly (string | number | boolean)[] | null | undefined;}
3525
+
3303
3526
  type RouteTable = Readonly<Record<string, string>> | ReadonlyMap<string, string>
3304
3527
 
3528
+ type RouteTarget = string
3529
+
3305
3530
  ## ./security `(./src/security/index.ts)`
3306
3531
 
3307
3532
  class CryptKeyMissingError = {
@@ -3339,7 +3564,7 @@ interface RedactGraphOptions = {
3339
3564
  tooDeep: string
3340
3565
  }
3341
3566
 
3342
- type HashAlgorithm = 'bcrypt' | 'argon2id' | 'argon2i' | 'argon2d'
3567
+ type HashAlgorithm = 'argon2id' | 'argon2i' | 'argon2d' | 'bcrypt'
3343
3568
 
3344
3569
  ## ./storage `(./src/storage/index.ts)`
3345
3570
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zerotal/core",
3
- "version": "1.7.2",
3
+ "version": "1.7.4",
4
4
  "license": "MIT",
5
5
  "maturity": "stable",
6
6
  "private": false,
@@ -14,8 +14,8 @@ export function toKebab(name: string): string {
14
14
 
15
15
  /** Source for a new CLI command class extending `Command`. */
16
16
  export function commandStub(name: string): string {
17
- return `import { Command } from '@zerotal/core';
18
- import type { ArgDef, FlagDef } from '@zerotal/core';
17
+ return `import { Command } from 'zerotal';
18
+ import type { ArgDef, FlagDef } from 'zerotal';
19
19
 
20
20
  export class ${name} extends Command {
21
21
  static commandName = '${toKebab(name)}';
@@ -5,7 +5,7 @@ import { Command } from "../Command.ts";
5
5
 
6
6
  /** Source for a minimal controller with a single `index` action. */
7
7
  export function basicStub(name: string): string {
8
- return `import type { HttpContext } from '@zerotal/core';
8
+ return `import type { HttpContext } from 'zerotal';
9
9
 
10
10
  export class ${name} {
11
11
  async index(ctx: HttpContext): Promise<void> {
@@ -17,7 +17,7 @@ export class ${name} {
17
17
 
18
18
  /** Source for a resourceful controller with full CRUD action stubs. */
19
19
  export function resourceStub(name: string): string {
20
- return `import type { HttpContext } from '@zerotal/core';
20
+ return `import type { HttpContext } from 'zerotal';
21
21
 
22
22
  export class ${name} {
23
23
  async index(ctx: HttpContext): Promise<void> {
@@ -27,7 +27,7 @@ export class MakeJobCommand extends Command {
27
27
  }
28
28
  await Bun.write(
29
29
  path,
30
- `import { Job, JobRegistry } from '@zerotal/queue';
30
+ `import { Job, JobRegistry } from 'zerotal/queue';
31
31
 
32
32
  export class ${name} extends Job {
33
33
  readonly queue = 'default';
@@ -5,7 +5,7 @@ import { Command } from "../Command.ts";
5
5
 
6
6
  /** Source for a pass-through middleware class implementing `Pipe<HttpContext>`. */
7
7
  export function middlewareStub(name: string): string {
8
- return `import type { HttpContext, Pipe, NextFn } from '@zerotal/core';
8
+ return `import type { HttpContext, Pipe, NextFn } from 'zerotal';
9
9
 
10
10
  export class ${name} implements Pipe<HttpContext> {
11
11
  async handle(ctx: HttpContext, next: NextFn): Promise<Response | void> {
@@ -47,7 +47,7 @@ export class MakeObserverCommand extends Command {
47
47
  }
48
48
 
49
49
  function _stub(name: string, model: string): string {
50
- return `import type { ModelObserver } from '@zerotal/orm';
50
+ return `import type { ModelObserver } from 'zerotal/orm';
51
51
 
52
52
  export class ${name} implements ModelObserver {
53
53
  creating(${model.toLowerCase()}: Record<string, unknown>): void {
@@ -41,7 +41,7 @@ export class MakePolicyCommand extends Command {
41
41
 
42
42
  /** Source for a new authorization policy class extending `Policy`. */
43
43
  export function policyStub(name: string, model: string): string {
44
- return `import { Policy } from '@zerotal/auth';
44
+ return `import { Policy } from 'zerotal/auth';
45
45
  // import type { ${model} } from '../models/${model}.ts';
46
46
  // import type { User } from '../models/User.ts';
47
47
 
@@ -6,7 +6,7 @@ import { Command } from "../Command.ts";
6
6
  import { registerProvider } from "../../build/codemod.ts";
7
7
 
8
8
  function providerStub(name: string): string {
9
- return `import { ServiceProvider } from '@zerotal/core';
9
+ return `import { ServiceProvider } from 'zerotal';
10
10
 
11
11
  export class ${name} extends ServiceProvider {
12
12
  override onRegister(): void {
@@ -31,13 +31,23 @@ export class MakeRequestCommand extends Command {
31
31
  }
32
32
  }
33
33
 
34
+ // Two things this stub deliberately does not do.
35
+ //
36
+ // It imports from `zerotal/validator`, not `@zerotal/validator`: a scaffolded app
37
+ // depends on the umbrella, so the scoped name resolves to nothing and the file it
38
+ // just generated does not compile.
39
+ //
40
+ // And it leaves `rules()` unannotated. `validate()` reads the narrow return type
41
+ // through `ReturnType<T['rules']>`, so writing `Record<string, FieldRule>` there
42
+ // widens it back and every validated field arrives as `unknown` — silently, with
43
+ // the first sign a cast somewhere downstream. `FormRequest`'s own docblock says
44
+ // so; the generator used to emit exactly what it warns against.
34
45
  function stub(name: string): string {
35
- return `import { FormRequest } from '@zerotal/validator';
36
- import type { RuleBuilder } from '@zerotal/validator';
37
- import type { FieldRule } from '@zerotal/validator';
46
+ return `import { FormRequest } from 'zerotal/validator';
47
+ import type { RuleBuilder } from 'zerotal/validator';
38
48
 
39
49
  export class ${name} extends FormRequest {
40
- rules(r: RuleBuilder): Record<string, FieldRule> {
50
+ rules(r: RuleBuilder) {
41
51
  return {
42
52
  // example: title: r.string().min(3).max(255),
43
53
  };
@@ -35,8 +35,8 @@ export class MakeResourceCommand extends Command {
35
35
  function _stub(name: string): string {
36
36
  const model = name.replace(/Resource$/, "");
37
37
  const snake = model.replace(/([A-Z])/g, (char, index) => (index ? "-" : "") + char.toLowerCase());
38
- return `import { Resource, ResourceCollection } from '@zerotal/core';
39
- import type { PaginatedData } from '@zerotal/core';
38
+ return `import { Resource, ResourceCollection } from 'zerotal/http';
39
+ import type { PaginatedData } from 'zerotal/http';
40
40
 
41
41
  export class ${name} extends Resource<${model}> {
42
42
  toArray(): Record<string, unknown> {
@@ -11,8 +11,8 @@ export function featureTestStub(name: string): string {
11
11
  // edit rather than one you have to rewrite.
12
12
  const resource = pluralize(subject.toLowerCase());
13
13
  return `import { describe, it, beforeAll, afterAll } from 'bun:test';
14
- import { migrateDatabase, refreshDatabase, assertDatabaseHas } from '@zerotal/testing';
15
- import type { TestApp } from '@zerotal/testing';
14
+ import { migrateDatabase, refreshDatabase, assertDatabaseHas } from 'zerotal/testing';
15
+ import type { TestApp } from 'zerotal/testing';
16
16
  import { createApp } from '../helpers.ts';
17
17
 
18
18
  let app: TestApp;
@@ -197,10 +197,16 @@ export const route: RouteBuilder = Object.assign(
197
197
  /**
198
198
  * Augmented by `types/routes.generated.ts` with the HTTP method of every named
199
199
  * route, exactly as {@link RouteRegistry} is augmented with their patterns.
200
+ *
201
+ * @internal The generator writes the augmentation; an app never names this.
200
202
  */
201
203
  export interface RouteMethodRegistry {}
202
204
 
203
- /** A name the generated table knows a verb for. */
205
+ /**
206
+ * A name the generated table knows a verb for.
207
+ *
208
+ * @internal Derived from {@link RouteMethodRegistry}, which the generator owns.
209
+ */
204
210
  export type MethodedRouteName = Extract<keyof RouteMethodRegistry, string>;
205
211
 
206
212
  const methodTable = new Map<string, string>();
@@ -217,7 +223,11 @@ export function defineRouteMethods(table: Readonly<Record<string, string>>): voi
217
223
  for (const [name, method] of Object.entries(table)) methodTable.set(name, method);
218
224
  }
219
225
 
220
- /** The verb a named route answers on, or undefined when it was never registered. */
226
+ /**
227
+ * The verb a named route answers on, or undefined when it was never registered.
228
+ *
229
+ * @internal The read side of {@link defineRouteMethods}; apps call `action()`.
230
+ */
221
231
  export function routeMethod(name: string): string | undefined {
222
232
  return methodTable.get(name);
223
233
  }