@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 +33 -1
- package/api-surface.md +236 -11
- package/package.json +1 -1
- package/src/command/builtin/MakeCommandCommand.ts +2 -2
- package/src/command/builtin/MakeControllerCommand.ts +2 -2
- package/src/command/builtin/MakeJobCommand.ts +1 -1
- package/src/command/builtin/MakeMiddlewareCommand.ts +1 -1
- package/src/command/builtin/MakeObserverCommand.ts +1 -1
- package/src/command/builtin/MakePolicyCommand.ts +1 -1
- package/src/command/builtin/MakeProviderCommand.ts +1 -1
- package/src/command/builtin/MakeRequestCommand.ts +14 -4
- package/src/command/builtin/MakeResourceCommand.ts +2 -2
- package/src/command/builtin/MakeTestCommand.ts +2 -2
- package/src/router/routes.ts +12 -2
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 =
|
|
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 | {
|
|
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 | {
|
|
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 = '
|
|
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' | '
|
|
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 = '
|
|
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 = {
|
|
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 = '
|
|
3463
|
+
type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'fatal'
|
|
3262
3464
|
|
|
3263
|
-
type TableData = 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 = '
|
|
3567
|
+
type HashAlgorithm = 'argon2id' | 'argon2i' | 'argon2d' | 'bcrypt'
|
|
3343
3568
|
|
|
3344
3569
|
## ./storage `(./src/storage/index.ts)`
|
|
3345
3570
|
|
package/package.json
CHANGED
|
@@ -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 '
|
|
18
|
-
import type { ArgDef, FlagDef } from '
|
|
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 '
|
|
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 '
|
|
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 '
|
|
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 '
|
|
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 '
|
|
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 '
|
|
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 '
|
|
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 '
|
|
36
|
-
import type { RuleBuilder }
|
|
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)
|
|
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 '
|
|
39
|
-
import type { PaginatedData } from '
|
|
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 '
|
|
15
|
-
import type { TestApp } from '
|
|
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;
|
package/src/router/routes.ts
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
}
|