@zerotal/core 1.9.0 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CHANGELOG.md +148 -0
  2. package/api-surface.md +151 -81
  3. package/package.json +2 -1
  4. package/src/application/Application.ts +4 -4
  5. package/src/build/codemod.ts +1 -1
  6. package/src/command/CommandRunner.ts +35 -4
  7. package/src/command/builtin/AssetsPruneCommand.ts +138 -0
  8. package/src/command/builtin/DeployCommand.ts +19 -0
  9. package/src/command/builtin/index.ts +1 -0
  10. package/src/command/startZerotal.ts +51 -8
  11. package/src/config/AppConfig.ts +10 -10
  12. package/src/conventions/ConventionLoader.ts +39 -2
  13. package/src/dev/BuildOutput.ts +58 -6
  14. package/src/dev/CssPlugins.ts +2 -2
  15. package/src/dev/DevDeck.ts +3 -3
  16. package/src/dev/DevProcess.ts +2 -2
  17. package/src/dev/DevSupervisor.ts +5 -5
  18. package/src/dev/index.ts +6 -1
  19. package/src/dev/startDevMode.ts +1 -1
  20. package/src/doctor/throttleIdentity.ts +6 -2
  21. package/src/health/Health.ts +5 -5
  22. package/src/helpers/index.ts +2 -1
  23. package/src/helpers/markdown.ts +7 -0
  24. package/src/http/UploadedFile.ts +2 -2
  25. package/src/index.ts +2 -1
  26. package/src/lock/LockManager.ts +4 -4
  27. package/src/logger/LoggerMiddleware.ts +1 -1
  28. package/src/logger/types.ts +4 -4
  29. package/src/middleware/CorsMiddleware.ts +6 -6
  30. package/src/middleware/RateLimiter.ts +105 -22
  31. package/src/middleware/SecureHeadersMiddleware.ts +8 -8
  32. package/src/middleware/ThrottleMiddleware.ts +57 -4
  33. package/src/middleware/WebhookMiddleware.ts +7 -7
  34. package/src/router/RouteHandler.ts +2 -2
  35. package/src/router/Router.ts +5 -5
  36. package/src/security/redactGraph.ts +1 -1
  37. package/src/shared/format.ts +8 -8
  38. package/src/storage/StorageFilesMiddleware.ts +2 -2
  39. package/src/storage/types.ts +10 -10
  40. package/src/support/cookie.ts +6 -6
  41. package/src/support/deepMerge.ts +49 -0
  42. package/src/support/runtime.ts +169 -4
package/CHANGELOG.md CHANGED
@@ -8,6 +8,154 @@ follows the Zerotal monorepo's unified versioning.
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [1.11.0] — 2026-08-31
12
+
13
+ ### Fixed
14
+
15
+ - **A command can fail without throwing.** `CommandRunner` ran an unconditional
16
+ `process.exit(0)` the moment `run()` returned and never read `process.exitCode` —
17
+ which is the idiomatic way to fail a Bun/Node CLI without an exception, and what
18
+ most people reach for. An app's `release:check` printed six blockers, set the code,
19
+ and exited **0**; their deploy script read success and would have restarted a broken
20
+ production deployment. `zt deploy` gates on the same value through
21
+ `callInProcess`, so its own preflight had the hole too. A gate that cannot fail is
22
+ not a gate, and this one failed _open_.
23
+
24
+ `callInProcess` saves and restores the code around each command, because it runs
25
+ them in-process and the value is global. Note for anyone doing the same:
26
+ `process.exitCode = undefined` is a **no-op in Bun** — the previous value survives —
27
+ so clearing it takes `0`.
28
+
29
+ - **A Bun the project never asked for is a warning, not a refusal.**
30
+ `bun-plugin-tailwind` declares `bun` as a _required_ peer, so `bun install` fetches
31
+ the Bun npm package as a second, newer runtime — and the runtime guard refused to
32
+ boot. An app took two production outages on this: the first crash-looping behind a
33
+ 502, the second because the obvious fix (removing `node_modules/bun` after install)
34
+ cannot work — that package's postinstall runs _during_ install, so deleting it
35
+ leaves the tree incomplete.
36
+
37
+ Nothing about that is two runtimes in play; nobody executes the stray copy. The
38
+ guard now asks whether the project _declared_ `bun` in its own dependencies. If it
39
+ did, the refusal stands. If it did not, it warns and names the likely culprit, the
40
+ fix that works (`bun install --omit=peer`) and the one that does not.
41
+
42
+ ## [1.10.0] — 2026-08-30
43
+
44
+ ### Changed
45
+
46
+ - **Optional properties in public option shapes are declared `?: T | undefined`.**
47
+ The generated `tsconfig.json` turns on `exactOptionalPropertyTypes`, under which
48
+ `image?: string` and `image?: string | undefined` are different types: only the
49
+ second accepts a key that is present and holds `undefined`. So an app with the
50
+ strictness the framework shipped it could not write the most ordinary thing there
51
+ is — `{ image: candidate ?? undefined }` — and had to spell every conditional field
52
+ `...(x ? { x } : {})` instead. An options bag exists to be spread into; one built by
53
+ spreading has conditionally-absent keys.
54
+
55
+ 438 properties across 115 files, applied by `scripts/exact-optional.ts` and checked
56
+ by `bun run exact:optional:check` so the convention holds for shapes written later.
57
+ Scoped to exported `*Options` / `*Config` shapes — what an app constructs — rather
58
+ than every optional property in the codebase. Nothing changes for a reader: an
59
+ absent optional property already read as `undefined`.
60
+
61
+ ### Added
62
+
63
+ - **`zt assets:prune`** — remove the chunks an earlier release left behind, on the
64
+ machine that never ran a build. `assets:build --clean` cleans the directory it
65
+ _builds into_, which does nothing for the common release shape: build here, tar the
66
+ output, extract it over `public/` there. Extracting merges — nothing removes a file
67
+ the new release does not contain — so every deploy adds another set of
68
+ content-hashed chunks. One app reached 225 chunk files for the 49 its entry point
69
+ references. Ship `.zerotal/` with the release and this removes what the build record
70
+ does not claim; without a record it says so and removes nothing rather than guessing.
71
+
72
+ - **`definedOnly` (`@zerotal/core/helpers`) and the `Resolved<T>` type.** Widening the
73
+ option shapes moves a problem to the merge, and both of these are that problem's
74
+ answer. Object spread copies own properties even when their value is `undefined`,
75
+ so `{ ...DEFAULTS, ...options }` now lets an explicit `undefined` overwrite a
76
+ default; `definedOnly` drops those keys, turning "supplied as undefined" back into
77
+ "not supplied". (`deepMerge` already did this at every depth.)
78
+
79
+ `Resolved<T>` is what a defaults-applied object is. **`Required<T>` is not**, which
80
+ is the trap: `-?` removes the optionality a `?` introduced but not an `undefined`
81
+ written into the type, so `Required<Options>` quietly stopped meaning "the defaults
82
+ have been applied".
83
+
84
+ - **`zt deploy:<env> --check`** — the preflight gate on its own. A release script has a
85
+ moment where the new code is on disk and the service has not restarted yet, and that
86
+ is where a gate belongs: exit 0 and restart, exit non-zero and keep serving the
87
+ previous release. Everything that can refuse already runs by the end of preflight and
88
+ none of it mutates, so stopping there is a complete answer rather than half a deploy.
89
+ A workspace that has lost its banking details, or had its mail driver knocked back to
90
+ `log`, never goes live broken.
91
+
92
+ - **`engines.bun` is enforced.** Every generated app writes a floor and nothing had
93
+ ever read it. `runtimeMismatch` did not cover this: it compares the running Bun
94
+ against an _installed_ one, and most projects do not install Bun as a package, so
95
+ it correctly says nothing about most of them. `startZerotal()` now refuses to run
96
+ below the declared floor, so every `zt` command is covered.
97
+
98
+ The failure it catches is narrow and expensive. `Intl` output moves between Bun
99
+ releases, so a suite with currency or date assertions goes red on a runtime that is
100
+ otherwise fine, the failures name the code they touch, and the version is the last
101
+ thing anyone checks. `ZT_ALLOW_RUNTIME_MISMATCH=1` downgrades it to a warning.
102
+
103
+ - **`@zerotal/core/runtime`** (`zerotal/runtime`) — a subpath exporting the checks
104
+ themselves: `runtimeBelowFloor`, `declaredBunFloor`, `runtimeMismatch`, `bunBinary`
105
+ and the messages that go with them, so a script or a test can make the same
106
+ assertion. A subpath rather than the main barrel, which is deliberately lean.
107
+
108
+ - **A boot line when a convention is skipped in this environment.** An env-restricted
109
+ concern is skipped by _not looking_, which is correct and completely silent: from a
110
+ web process's point of view nothing is wrong, because from a web process's point of
111
+ view nothing exists. An app ran for weeks in production with `app/schedules` full
112
+ and no worker process — no inventory hold released, no payment reminder sent, and
113
+ nothing logged, because there was nothing to log. It was found by going looking.
114
+
115
+ `runConventions` now says so once, at boot, and only when the directory actually
116
+ holds files:
117
+
118
+ Skipping 3 file(s) in app/schedules — the "schedules" convention does not run in env=web (it runs in: worker, console).
119
+
120
+ "Skipping 0 schedules" on every boot of every app would be noise, and noise is how
121
+ the line that matters gets scrolled past.
122
+
123
+ ### Fixed
124
+
125
+ - **Named rate limiters honour `trustedProxies`.** `ThrottleMiddleware` resolves the
126
+ client address from the socket unless told how many proxies sit in front — that was
127
+ fixed, documented and covered by a doctor check. `RateLimiter`'s `.byIp()`,
128
+ `.byUser()` and `.byApiKey()` did not: they used a private resolver that read the
129
+ socket address and fell back to the **leftmost** `X-Forwarded-For` entry, with no
130
+ proxy count anywhere in it. A `keyResolver` replaces the middleware's own
131
+ proxy-aware path entirely, so the sibling API kept the bug the middleware had shed.
132
+
133
+ Two consequences, in opposite directions. Where there was no socket address a client
134
+ picked its own bucket by writing the header, which is the part of it the client
135
+ controls. Behind a real reverse proxy — where there _is_ a socket address — it was
136
+ the proxy's, so **every visitor shared one bucket**: the documented
137
+ `RateLimiter.for("login").limit(5).every(60)` was five attempts a minute across the
138
+ entire user base, and one attacker locked everybody out.
139
+
140
+ All three now resolve through the same proxy-aware function, and
141
+ `.trustedProxies(n)` is on the builder. The strategy is recorded and the resolver
142
+ built at the end, so `.byIp().trustedProxies(1)` and `.trustedProxies(1).byIp()`
143
+ mean the same thing.
144
+
145
+ - **`zt doctor` audits named limiters.** Its `trustedProxies` check exempted any
146
+ throttle with a custom `keyResolver` — right for one keyed on a user id, where no
147
+ proxy can affect the answer, and exactly wrong for `RateLimiter`'s built-ins, which
148
+ _are_ custom resolvers and do key on an address. The check that exists to catch this
149
+ skipped the whole API that still had it. The built-in resolvers mark themselves now;
150
+ an app's own `.by(fn)` stays exempt, which is the point of the exemption.
151
+
152
+ - **The build record is portable.** Its filename was hashed from the output
153
+ directory's _absolute_ path, so the same project built at `/home/me/app` and
154
+ unpacked at `/opt/app` wrote and looked for two different files. A record shipped
155
+ with a release matched nothing at the other end — which is why a server could not
156
+ say which files belonged to the current release — and moving a checkout silently
157
+ orphaned it. It is keyed on the path relative to the project now.
158
+
11
159
  ## [1.9.0] — 2026-08-29
12
160
 
13
161
  ### Added
package/api-surface.md CHANGED
@@ -394,6 +394,7 @@ class LimiterDefinition = {
394
394
  register: () => LimiterDefinition
395
395
  toMiddleware: () => ThrottleMiddleware
396
396
  toMiddlewareClass: () => new () => ThrottleMiddleware
397
+ trustedProxies: (count: number) => LimiterDefinition
397
398
  }
398
399
 
399
400
  class MarkdownBuilder = {
@@ -808,23 +809,23 @@ interface ContainerBindings = {
808
809
  }
809
810
 
810
811
  interface CookieOptions = {
811
- domain?: string
812
- httpOnly?: boolean
813
- maxAge?: number
812
+ domain?: string | undefined
813
+ httpOnly?: boolean | undefined
814
+ maxAge?: number | undefined
814
815
  name: string
815
- path?: string
816
- sameSite?: SameSite
817
- secure?: boolean
816
+ path?: string | undefined
817
+ sameSite?: SameSite | undefined
818
+ secure?: boolean | undefined
818
819
  value: string
819
820
  }
820
821
 
821
822
  interface CorsOptions = {
822
- allowedHeaders?: string[]
823
- credentials?: boolean
824
- exposedHeaders?: string[]
825
- maxAge?: number
826
- methods?: string[]
827
- origin?: string | string[] | ((origin: string) => boolean)
823
+ allowedHeaders?: string[] | undefined
824
+ credentials?: boolean | undefined
825
+ exposedHeaders?: string[] | undefined
826
+ maxAge?: number | undefined
827
+ methods?: string[] | undefined
828
+ origin?: string | string[] | ((origin: string) => boolean) | undefined
828
829
  }
829
830
 
830
831
  interface DevProcessDefinition = {
@@ -870,9 +871,9 @@ interface ErrorDiagnosis = {
870
871
  }
871
872
 
872
873
  interface GroupOptions = {
873
- domain?: string
874
- middleware?: string | string[] | MiddlewareClass | MiddlewareClass[]
875
- prefix?: string
874
+ domain?: string | undefined
875
+ middleware?: string | string[] | MiddlewareClass | MiddlewareClass[] | undefined
876
+ prefix?: string | undefined
876
877
  }
877
878
 
878
879
  interface ModelClass = {
@@ -925,25 +926,25 @@ interface RouterMacros = {
925
926
  }
926
927
 
927
928
  interface SecureHeadersOptions = {
928
- contentSecurityPolicy?: string
929
- frameOptions?: false | 'DENY' | 'SAMEORIGIN'
930
- hstsIncludeSubDomains?: boolean
931
- hstsMaxAge?: number
932
- hstsPreload?: boolean
933
- permissionsPolicy?: string | false
934
- referrerPolicy?: string
935
- secure?: boolean
929
+ contentSecurityPolicy?: string | undefined
930
+ frameOptions?: false | 'DENY' | 'SAMEORIGIN' | undefined
931
+ hstsIncludeSubDomains?: boolean | undefined
932
+ hstsMaxAge?: number | undefined
933
+ hstsPreload?: boolean | undefined
934
+ permissionsPolicy?: string | false | undefined
935
+ referrerPolicy?: string | undefined
936
+ secure?: boolean | undefined
936
937
  }
937
938
 
938
939
  interface StartZerotalOptions = {
939
- configDir?: string
940
+ configDir?: string | undefined
940
941
  }
941
942
 
942
943
  interface ThrottleOptions = {
943
- keyResolver?: (ctx: HttpContext) => string
944
+ keyResolver?: ((ctx: HttpContext) => string) | undefined
944
945
  maxAttempts: number
945
- trustedProxies?: number
946
- windowSeconds?: number
946
+ trustedProxies?: number | undefined
947
+ windowSeconds?: number | undefined
947
948
  }
948
949
 
949
950
  interface ViewRegistration = {
@@ -952,14 +953,14 @@ interface ViewRegistration = {
952
953
  }
953
954
 
954
955
  interface WebhookOptions = {
955
- algorithm?: 'sha256' | 'sha1' | 'sha512'
956
- format?: 'raw' | 'stripe'
957
- header?: string
958
- prefix?: string
956
+ algorithm?: 'sha256' | 'sha1' | 'sha512' | undefined
957
+ format?: 'raw' | 'stripe' | undefined
958
+ header?: string | undefined
959
+ prefix?: string | undefined
959
960
  secret: string
960
- timestampHeader?: string
961
- timestampSeparator?: string
962
- tolerance?: number
961
+ timestampHeader?: string | undefined
962
+ timestampSeparator?: string | undefined
963
+ tolerance?: number | undefined
963
964
  }
964
965
 
965
966
  interface WebSocketHandlers = {
@@ -998,6 +999,8 @@ type NextFn = () => Promise<Response | void>
998
999
 
999
1000
  type ParamsOf = P extends `${string}:${infer Name}/${infer Rest}` ? { [K in Name]: RouteParamValue; } & ParamsOf<`/${Rest}`> : P extends `${string}:${infer Name}` ? { [K in Name]: RouteParamValue; } : P extends `${string}*${string}` ? { "*": RouteParamValue | readonly RouteParamValue[];} : Record<never, never>
1000
1001
 
1002
+ type Resolved = { [K in keyof T]-?: Exclude<T[K], undefined>; }
1003
+
1001
1004
  type RouteArgs = [params?: RouteParamValues, query?: RouteQuery]
1002
1005
 
1003
1006
  type RouteMiddleware = MiddlewareClass[] | RouteMethodMiddleware
@@ -1037,7 +1040,7 @@ function addToDefaultArrayExport = (source: string, identifier: string) => strin
1037
1040
  function registerProvider = (options: RegisterProviderOptions) => Promise<RegisterResult>
1038
1041
 
1039
1042
  interface RegisterProviderOptions = {
1040
- bootstrapPath?: string
1043
+ bootstrapPath?: string | undefined
1041
1044
  className: string
1042
1045
  importPath: string
1043
1046
  }
@@ -1304,6 +1307,34 @@ class AssetsBuildCommand = {
1304
1307
  write: (msg: string) => void
1305
1308
  }
1306
1309
 
1310
+ class AssetsPruneCommand = {
1311
+ new (): AssetsPruneCommand
1312
+ static args: ArgDef[]
1313
+ static commandName: string
1314
+ static description: string
1315
+ static flags: ({ name: string; type: 'string'; description: string; default?: never;} | { name: string; type: 'boolean'; description: string; default: boolean;})[]
1316
+ static needsApp: boolean
1317
+ _readLine: () => Promise<string>
1318
+ _writer: OutputWriter
1319
+ app: unknown
1320
+ args: Record<string, string>
1321
+ ask: (question: string, defaultValue?: string) => Promise<string>
1322
+ choice: (question: string, options: string[]) => Promise<string>
1323
+ confirm: (question: string, defaultValue?: boolean) => Promise<boolean>
1324
+ dim: (msg: string) => void
1325
+ error: (msg: string) => void
1326
+ flags: Record<string, string | number | boolean>
1327
+ info: (msg: string) => void
1328
+ line: (msg: string) => void
1329
+ newLine: () => void
1330
+ run: () => Promise<void>
1331
+ secret: (question: string) => Promise<string>
1332
+ section: (title: string) => void
1333
+ table: (rows: [string, string][], indent?: number) => void
1334
+ warn: (msg: string) => void
1335
+ write: (msg: string) => void
1336
+ }
1337
+
1307
1338
  class CompileCommand = {
1308
1339
  new (): CompileCommand
1309
1340
  static args: ArgDef[]
@@ -2191,7 +2222,7 @@ function DeployConfig = (options?: Partial<DeployConfigShape>) => DeployConfigSh
2191
2222
 
2192
2223
  interface AppAssetsConfig = {
2193
2224
  entrypoint: string | string[]
2194
- loader?: Record<string, AssetLoaderKind>
2225
+ loader?: Record<string, AssetLoaderKind> | undefined
2195
2226
  minify: boolean
2196
2227
  outDir: string
2197
2228
  prefix: string
@@ -2199,11 +2230,11 @@ interface AppAssetsConfig = {
2199
2230
 
2200
2231
  interface AppConfigShape = {
2201
2232
  allowedOrigins: string[]
2202
- assets?: AppAssetsConfig
2233
+ assets?: AppAssetsConfig | undefined
2203
2234
  conventions: ConventionsConfig
2204
2235
  cors: AppCorsConfig
2205
2236
  debug: boolean
2206
- dev?: DevConfigShape
2237
+ dev?: DevConfigShape | undefined
2207
2238
  env: string
2208
2239
  health: boolean | HealthConfigShape
2209
2240
  http3: boolean
@@ -2215,7 +2246,7 @@ interface AppConfigShape = {
2215
2246
  secureHeaders: AppSecureHeadersConfig
2216
2247
  throttle: AppThrottleConfig
2217
2248
  timezone: string
2218
- tls?: AppTlsConfig
2249
+ tls?: AppTlsConfig | undefined
2219
2250
  url: string
2220
2251
  }
2221
2252
 
@@ -2579,10 +2610,10 @@ interface HealthCheckReport = {
2579
2610
  }
2580
2611
 
2581
2612
  interface HealthConfigShape = {
2582
- enabled?: boolean
2583
- path?: string
2584
- secret?: string
2585
- showDetails?: boolean
2613
+ enabled?: boolean | undefined
2614
+ path?: string | undefined
2615
+ secret?: string | undefined
2616
+ showDetails?: boolean | undefined
2586
2617
  }
2587
2618
 
2588
2619
  interface HealthReport = {
@@ -2602,7 +2633,7 @@ interface HealthResult = {
2602
2633
  interface ResolvedHealthConfig = {
2603
2634
  enabled: boolean
2604
2635
  path: string
2605
- secret?: string
2636
+ secret?: string | undefined
2606
2637
  showDetails: boolean
2607
2638
  }
2608
2639
 
@@ -2620,6 +2651,8 @@ function data_get = (target: unknown, key: string, defaultValue?: unknown) => un
2620
2651
 
2621
2652
  function deepMerge = <T extends object>(base: T, override: DeepPartial<T>) => T
2622
2653
 
2654
+ function definedOnly = <T extends object>(value: T) => { [K in keyof T]?: Exclude<T[K], undefined>; }
2655
+
2623
2656
  function env = { (key: string): string | undefined; (key: string, fallback: string): string; (key: string, fallback: boolean): boolean; (key: string, fallback: number): number;}
2624
2657
 
2625
2658
  function pipe = <T, R>(value: T, fn: (val: T) => R) => R
@@ -2644,6 +2677,8 @@ function tap = <T>(value: T, callback: (val: T) => void) => T
2644
2677
 
2645
2678
  function tapAsync = <T>(value: T, callback: (val: T) => Promise<void>) => Promise<T>
2646
2679
 
2680
+ type Resolved = { [K in keyof T]-?: Exclude<T[K], undefined>; }
2681
+
2647
2682
  ## ./http `(./src/http/index.ts)`
2648
2683
 
2649
2684
  class Http = {
@@ -2802,8 +2837,8 @@ interface CliContext = {
2802
2837
  }
2803
2838
 
2804
2839
  interface FileValidationOptions = {
2805
- maxSize?: number
2806
- mimes?: string[]
2840
+ maxSize?: number | undefined
2841
+ mimes?: string[] | undefined
2807
2842
  }
2808
2843
 
2809
2844
  interface NegotiateMap = {
@@ -2955,10 +2990,10 @@ class SqliteLockDriver = {
2955
2990
  function LockConfig = (options?: Partial<LockConfigShape>) => LockConfigShape
2956
2991
 
2957
2992
  interface BlockOptions = {
2958
- refresh?: boolean
2959
- refreshEvery?: number
2960
- retryDelay?: number
2961
- timeout?: number
2993
+ refresh?: boolean | undefined
2994
+ refreshEvery?: number | undefined
2995
+ retryDelay?: number | undefined
2996
+ timeout?: number | undefined
2962
2997
  }
2963
2998
 
2964
2999
  interface LockConfigShape = {
@@ -2977,8 +3012,8 @@ interface LockDriver = {
2977
3012
  }
2978
3013
 
2979
3014
  interface RefreshOptions = {
2980
- refresh?: boolean
2981
- refreshEvery?: number
3015
+ refresh?: boolean | undefined
3016
+ refreshEvery?: number | undefined
2982
3017
  }
2983
3018
 
2984
3019
  type LockedCallback = (lock: ManagedLock, signal: AbortSignal) => Promise<T> | T
@@ -3093,16 +3128,16 @@ interface LogEntry = {
3093
3128
  }
3094
3129
 
3095
3130
  interface LoggerOptions = {
3096
- format?: 'text' | 'json'
3131
+ format?: 'text' | 'json' | undefined
3097
3132
  }
3098
3133
 
3099
3134
  interface LoggingConfigShape = {
3100
3135
  channels: Record<string, ChannelConfig>
3101
- console?: ConsoleSinkConfig
3136
+ console?: ConsoleSinkConfig | undefined
3102
3137
  default: string
3103
- file?: FileSinkConfig
3104
- requests?: boolean
3105
- slowQueryMs?: number
3138
+ file?: FileSinkConfig | undefined
3139
+ requests?: boolean | undefined
3140
+ slowQueryMs?: number | undefined
3106
3141
  }
3107
3142
 
3108
3143
  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' }
@@ -3164,6 +3199,41 @@ type RouteTable = Readonly<Record<string, string>> | ReadonlyMap<string, string>
3164
3199
 
3165
3200
  type RouteTarget = string
3166
3201
 
3202
+ ## ./runtime `(./src/support/runtime.ts)`
3203
+
3204
+ const RUNTIME_MISMATCH_ESCAPE = 'ZT_ALLOW_RUNTIME_MISMATCH'
3205
+
3206
+ function bunBinary = () => string
3207
+
3208
+ function declaredBunFloor = (cwd: string) => { range: string; manifest: string;} | null
3209
+
3210
+ function declaresBunDependency = (cwd: string) => boolean
3211
+
3212
+ function installedBunVersion = (cwd: string) => { version: string; manifest: string;} | null
3213
+
3214
+ function runtimeBelowFloor = (cwd?: string) => RuntimeFloor | null
3215
+
3216
+ function runtimeBelowFloorMessage = (floor: RuntimeFloor) => string
3217
+
3218
+ function runtimeMismatch = (cwd?: string) => RuntimeMismatch | null
3219
+
3220
+ function runtimeMismatchAllowed = () => boolean
3221
+
3222
+ function runtimeMismatchMessage = (mismatch: RuntimeMismatch) => string
3223
+
3224
+ interface RuntimeFloor = {
3225
+ manifest: string
3226
+ required: string
3227
+ running: string
3228
+ }
3229
+
3230
+ interface RuntimeMismatch = {
3231
+ chosen?: boolean | undefined
3232
+ installed: string
3233
+ manifest: string
3234
+ running: string
3235
+ }
3236
+
3167
3237
  ## ./security `(./src/security/index.ts)`
3168
3238
 
3169
3239
  class CryptKeyMissingError = {
@@ -3194,7 +3264,7 @@ function redactGraph = (value: unknown, options: RedactGraphOptions) => unknown
3194
3264
 
3195
3265
  interface RedactGraphOptions = {
3196
3266
  circular: string
3197
- flatten?: (value: unknown) => string | undefined
3267
+ flatten?: ((value: unknown) => string | undefined) | undefined
3198
3268
  mask: string
3199
3269
  maxDepth: number
3200
3270
  sensitive: (key: string) => boolean
@@ -3224,27 +3294,27 @@ function snakeCase = (value: string) => string
3224
3294
  function tableNameFor = (className: string) => string
3225
3295
 
3226
3296
  interface DateOptions = {
3227
- dateStyle?: 'medium' | 'short' | 'full' | 'long'
3228
- locale?: string
3229
- timeStyle?: 'medium' | 'short' | 'full' | 'long'
3230
- timeZone?: string
3297
+ dateStyle?: 'medium' | 'short' | 'full' | 'long' | undefined
3298
+ locale?: string | undefined
3299
+ timeStyle?: 'medium' | 'short' | 'full' | 'long' | undefined
3300
+ timeZone?: string | undefined
3231
3301
  }
3232
3302
 
3233
3303
  interface FormatOptions = {
3234
- locale?: string
3304
+ locale?: string | undefined
3235
3305
  }
3236
3306
 
3237
3307
  interface MoneyOptions = {
3238
3308
  currency: string
3239
- fractionDigits?: number
3240
- locale?: string
3241
- minorUnits?: boolean
3309
+ fractionDigits?: number | undefined
3310
+ locale?: string | undefined
3311
+ minorUnits?: boolean | undefined
3242
3312
  }
3243
3313
 
3244
3314
  interface NumberOptions = {
3245
- locale?: string
3246
- maximumFractionDigits?: number
3247
- minimumFractionDigits?: number
3315
+ locale?: string | undefined
3316
+ maximumFractionDigits?: number | undefined
3317
+ minimumFractionDigits?: number | undefined
3248
3318
  }
3249
3319
 
3250
3320
  ## ./storage `(./src/storage/index.ts)`
@@ -3411,10 +3481,10 @@ const Storage = StorageManager
3411
3481
  function StorageConfig = (options?: Partial<StorageConfigShape>) => StorageConfigShape
3412
3482
 
3413
3483
  interface DiskServeConfig = {
3414
- expiresIn?: number
3415
- headers?: Record<string, string>
3484
+ expiresIn?: number | undefined
3485
+ headers?: Record<string, string> | undefined
3416
3486
  path: string
3417
- signed?: boolean
3487
+ signed?: boolean | undefined
3418
3488
  }
3419
3489
 
3420
3490
  interface FakeStoredFile = {
@@ -3428,24 +3498,24 @@ interface FakeStoredFile = {
3428
3498
  interface LocalDiskConfig = {
3429
3499
  driver: 'local'
3430
3500
  root: string
3431
- serve?: DiskServeConfig
3432
- url?: string
3501
+ serve?: DiskServeConfig | undefined
3502
+ url?: string | undefined
3433
3503
  }
3434
3504
 
3435
3505
  interface PutOptions = {
3436
- contentType?: string
3437
- visibility?: 'public' | 'private'
3506
+ contentType?: string | undefined
3507
+ visibility?: 'public' | 'private' | undefined
3438
3508
  }
3439
3509
 
3440
3510
  interface S3DiskConfig = {
3441
3511
  bucket: string
3442
3512
  driver: 's3'
3443
- endpoint?: string
3513
+ endpoint?: string | undefined
3444
3514
  key: string
3445
3515
  region: string
3446
3516
  secret: string
3447
- serve?: DiskServeConfig
3448
- url?: string
3517
+ serve?: DiskServeConfig | undefined
3518
+ url?: string | undefined
3449
3519
  }
3450
3520
 
3451
3521
  interface StorageConfigShape = {
@@ -3470,8 +3540,8 @@ interface StorageDriver = {
3470
3540
  }
3471
3541
 
3472
3542
  interface StorageFilesOptions = {
3473
- mounts?: Mount[]
3474
- storage?: StorageManager
3543
+ mounts?: Mount[] | undefined
3544
+ storage?: StorageManager | undefined
3475
3545
  }
3476
3546
 
3477
3547
  type DiskConfig = LocalDiskConfig | S3DiskConfig
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zerotal/core",
3
- "version": "1.9.0",
3
+ "version": "1.11.0",
4
4
  "license": "MIT",
5
5
  "maturity": "stable",
6
6
  "private": false,
@@ -16,6 +16,7 @@
16
16
  "./logger": "./src/logger/index.ts",
17
17
  "./commands": "./src/command/builtin/index.ts",
18
18
  "./helpers": "./src/helpers/index.ts",
19
+ "./runtime": "./src/support/runtime.ts",
19
20
  "./shared": "./src/shared/index.ts",
20
21
  "./facades": "./src/facade/facades/index.ts",
21
22
  "./carbon": "./src/datetime/index.ts",
@@ -182,9 +182,9 @@ type PipeClass = new (...args: any[]) => Pipe<HttpContext>;
182
182
 
183
183
  /** Options form for `Application.create({ ... })`. */
184
184
  export interface CreateOptions {
185
- providers?: ProviderClass[];
186
- env?: Environment;
187
- config?: ConfigLoader | ConfigMap;
185
+ providers?: ProviderClass[] | undefined;
186
+ env?: Environment | undefined;
187
+ config?: ConfigLoader | ConfigMap | undefined;
188
188
  /**
189
189
  * Drop {@link SecureHeadersMiddleware} from the front of the pipeline.
190
190
  *
@@ -194,7 +194,7 @@ export interface CreateOptions {
194
194
  * `config('app.secureHeaders')`; turn it off only when a proxy in front of the app is
195
195
  * already emitting the same headers.
196
196
  */
197
- secureHeaders?: false;
197
+ secureHeaders?: false | undefined;
198
198
  }
199
199
 
200
200
  function _normaliseEnv(raw: string): Environment {
@@ -40,7 +40,7 @@ export interface RegisterProviderOptions {
40
40
  /** Import specifier, e.g. '../app/providers/FooProvider.ts' or '@zerotal/foo'. */
41
41
  importPath: string;
42
42
  /** Bootstrap file. Default: 'bootstrap/providers.ts'. */
43
- bootstrapPath?: string;
43
+ bootstrapPath?: string | undefined;
44
44
  }
45
45
 
46
46
  /**