@stacksjs/config 0.74.50 → 0.74.52
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/dist/config.d.ts +1 -114
- package/dist/config.js +1 -1
- package/dist/features.js +1 -1
- package/dist/helpers.d.ts +1 -1
- package/dist/helpers.js +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.js +1 -1
- package/dist/request-context.d.ts +71 -33
- package/dist/request-context.js +1 -1
- package/dist/runtime.d.ts +114 -0
- package/dist/runtime.js +1 -0
- package/package.json +6 -6
package/dist/config.d.ts
CHANGED
|
@@ -1,115 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
import { overrides, overridesReady } from './overrides';
|
|
3
|
-
import type { StacksOptions } from '@stacksjs/types';
|
|
4
|
-
/*.ts` file has loaded and the
|
|
5
|
-
* `overrides` instance has been mutated in place. Use this in code paths
|
|
6
|
-
* that *must* see the merged values — typically anything outside the
|
|
7
|
-
* normal request flow (e.g. CLI commands, top-level boot scripts) where
|
|
8
|
-
* the request hasn't yet hit a route handler that already awaited.
|
|
9
|
-
*
|
|
10
|
-
* Inside request handlers / routes you generally do *not* need to await
|
|
11
|
-
* this: the router's `serverResponse()` only fires after `importRoutes()`
|
|
12
|
-
* resolves, and `importRoutes()` indirectly awaits config (it reads
|
|
13
|
-
* `app/Routes.ts`, which imports config). So the proxy returns merged
|
|
14
|
-
* values for all "normal" request-time reads.
|
|
15
|
-
*
|
|
16
|
-
* @example
|
|
17
|
-
* ```ts
|
|
18
|
-
* import { config, awaitConfig } from '@stacksjs/config'
|
|
19
|
-
*
|
|
20
|
-
* // CLI / top-level script — config files may not be loaded yet
|
|
21
|
-
* await awaitConfig()
|
|
22
|
-
* console.log(config.ports.api) // guaranteed to be the user value
|
|
23
|
-
* ```
|
|
24
|
-
*/
|
|
25
|
-
export declare function awaitConfig(): Promise<StacksOptions>;
|
|
26
|
-
/**
|
|
27
|
-
* Resolves once `database` config has been read by `@stacksjs/database`'s
|
|
28
|
-
* lazy initializer. Use it from boot scripts that need a live `db` handle
|
|
29
|
-
* before the request loop starts.
|
|
30
|
-
*
|
|
31
|
-
* Why a separate signal: `overridesReady` only signals that `~/config/database.ts`
|
|
32
|
-
* was *imported*, not that `@stacksjs/database` has actually wired up the
|
|
33
|
-
* connection. Calling `db.selectFrom(...)` immediately after `overridesReady`
|
|
34
|
-
* resolves can still race the connection setup. The database driver flips
|
|
35
|
-
* this signal to `true` once it has a working connection.
|
|
36
|
-
*/
|
|
37
|
-
export declare function awaitDatabaseConfig(): Promise<StacksOptions>;
|
|
38
|
-
/**
|
|
39
|
-
* Called by `@stacksjs/database` once the connection is live. Internal —
|
|
40
|
-
* users shouldn't need to invoke this directly.
|
|
41
|
-
*/
|
|
42
|
-
export declare function markDatabaseReady(): void;
|
|
43
|
-
export declare function getConfig(): StacksOptions;
|
|
44
|
-
export declare function determineAppEnv(): AppEnv;
|
|
45
|
-
export declare const config: StacksOptions;
|
|
46
|
-
// Per-section convenience exports.
|
|
47
|
-
//
|
|
48
|
-
// Caveat: these are *snapshots* taken at module-load time. ESM `export
|
|
49
|
-
// const x = expr` evaluates `expr` once and binds `x` to that result —
|
|
50
|
-
// there's no language-level way to make a named export re-evaluate
|
|
51
|
-
// per access. So `import { ports } from '@stacksjs/config'` returns
|
|
52
|
-
// whatever `config.ports` was when the config module first evaluated,
|
|
53
|
-
// which is *before* `overridesReady` resolves and the user's
|
|
54
|
-
// `config/*.ts` files land.
|
|
55
|
-
//
|
|
56
|
-
// These start as snapshots of the framework defaults (because the user's
|
|
57
|
-
// `config/*.ts` haven't loaded yet — see `overridesReady`). Once the
|
|
58
|
-
// async loader resolves we reassign each `let` binding so consumers of
|
|
59
|
-
// `import { ports } from '@stacksjs/config'` see the merged value.
|
|
60
|
-
//
|
|
61
|
-
// ESM live bindings make this work: when an exporting module reassigns
|
|
62
|
-
// a `let`-bound export, every importer immediately sees the new value
|
|
63
|
-
// (no re-import needed). The earlier `export const x = config.x` form
|
|
64
|
-
// captured the empty-default snapshot forever.
|
|
65
|
-
//
|
|
66
|
-
// Caveat: code that destructures (`const { ports } = config` or reads
|
|
67
|
-
// at the top of a function) before `overridesReady` resolves still
|
|
68
|
-
// gets the early snapshot. For correctness in that path, read off
|
|
69
|
-
// the `config` proxy: `config.ports.api` always pulls live.
|
|
70
|
-
export declare let ai: StacksOptions['ai'];
|
|
71
|
-
export declare let analytics: StacksOptions['analytics'];
|
|
72
|
-
export declare let app: StacksOptions['app'];
|
|
73
|
-
export declare let auth: StacksOptions['auth'];
|
|
74
|
-
export declare let realtime: StacksOptions['realtime'];
|
|
75
|
-
export declare let cache: StacksOptions['cache'];
|
|
76
|
-
export declare let cloud: StacksOptions['cloud'];
|
|
77
|
-
export declare let cli: StacksOptions['cli'];
|
|
78
|
-
export declare let dashboard: StacksOptions['dashboard'];
|
|
79
|
-
export declare let database: StacksOptions['database'];
|
|
80
|
-
export declare let dns: StacksOptions['dns'];
|
|
81
|
-
export declare let docs: StacksOptions['docs'];
|
|
82
|
-
export declare let email: StacksOptions['email'];
|
|
83
|
-
export declare let errors: StacksOptions['errors'];
|
|
84
|
-
export declare let featureFlags: StacksOptions['featureFlags'];
|
|
85
|
-
export declare let git: StacksOptions['git'];
|
|
86
|
-
export declare let hashing: StacksOptions['hashing'];
|
|
87
|
-
export declare let library: StacksOptions['library'];
|
|
88
|
-
export declare let logging: StacksOptions['logging'];
|
|
89
|
-
export declare let notification: StacksOptions['notification'];
|
|
90
|
-
export declare let payment: StacksOptions['payment'];
|
|
91
|
-
export declare let ports: StacksOptions['ports'];
|
|
92
|
-
export declare let queue: StacksOptions['queue'];
|
|
93
|
-
export declare let security: StacksOptions['security'];
|
|
94
|
-
export declare let saas: StacksOptions['saas'];
|
|
95
|
-
export declare let searchEngine: StacksOptions['searchEngine'];
|
|
96
|
-
export declare let server: StacksOptions['server'];
|
|
97
|
-
export declare let services: StacksOptions['services'];
|
|
98
|
-
export declare let filesystems: StacksOptions['filesystems'];
|
|
99
|
-
export declare let team: StacksOptions['team'];
|
|
100
|
-
export declare let ui: StacksOptions['ui'];
|
|
101
|
-
/**
|
|
102
|
-
* What `determineAppEnv` answers.
|
|
103
|
-
*
|
|
104
|
-
* The three canonical values, plus a passthrough for any other configured
|
|
105
|
-
* environment - the function maps local/development, staging and production
|
|
106
|
-
* onto them and returns anything else unchanged.
|
|
107
|
-
*
|
|
108
|
-
* `(string & {})` rather than a bare `string`: a bare one absorbs the literals
|
|
109
|
-
* and the union collapses, so the three canonical values stop being offered as
|
|
110
|
-
* completions and the type says nothing at all.
|
|
111
|
-
*/
|
|
112
|
-
// eslint-disable-next-line ts/ban-types -- `string & {}` keeps literal completions alive
|
|
113
|
-
declare type AppEnv = 'dev' | 'stage' | 'prod' | (string & {});
|
|
1
|
+
export * from './runtime';
|
|
114
2
|
export * from './helpers';
|
|
115
|
-
export { defaults, overrides, overridesReady };
|
package/dist/config.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
export*from"./runtime";export*from"./helpers";
|
package/dist/features.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{config}from"./
|
|
1
|
+
import{config}from"./runtime";const FEATURE_NAMES=["auth","marketing","cms","commerce","forms","dashboard","monitoring","realtime","queue"],FEATURE_DEFAULTS={dashboard:!0},overrides=new Map;function configFor(name){const raw=config[name];return raw&&typeof raw==="object"?raw:void 0}function canonicalFeatures(){return process.env.STACKS_CANONICAL_FEATURES==="1"}export function feature(name){if(overrides.has(name))return overrides.get(name);if(canonicalFeatures()&&FEATURE_NAMES.includes(name))return!0;const cfg=configFor(name);if(cfg){const enabledField=cfg.enabled;if(enabledField===!1)return!1;if(Array.isArray(cfg.env)&&cfg.env.length>0){const currentEnv=(config.app?.env??"").toString();if(!cfg.env.includes(currentEnv))return!1}if(enabledField!==void 0)return!!enabledField;return!0}return FEATURE_DEFAULTS[name]??!1}export function enableFeature(name){overrides.set(name,!0)}export function disableFeature(name){overrides.set(name,!1)}export function resetFeature(name){overrides.delete(name)}export function listFeatures(){const out={};for(const name of FEATURE_NAMES)out[name]=feature(name);for(const name of overrides.keys())out[name]=feature(name);return out}
|
package/dist/helpers.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { config } from '
|
|
1
|
+
import { config } from './runtime';
|
|
2
2
|
import type { AppConfig, AuthConfig, CacheConfig, CdnConfig, ChatConfig, CliConfig, CloudConfig, DatabaseConfig, DependenciesConfig, DnsConfig, EmailConfig, FilesystemsConfig, FeatureFlagsConfig, GitConfig, HashingConfig, ImagesConfig, LibraryConfig, NotificationConfig, PaymentConfig, QueueConfig, SearchEngineConfig, SecurityConfig, ServicesConfig, SmsConfig, StacksConfig, StorageConfig, UiConfig } from '@stacksjs/types';
|
|
3
3
|
export declare function localUrl(options?: {
|
|
4
4
|
domain?: string
|
package/dist/helpers.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{config}from"
|
|
1
|
+
import{config}from"./runtime";export async function localUrl(options={}){const domain=options.domain??config.app.url??"stacks",type=options.type??"frontend",localhost=options.localhost??!1,{https,network}=options;let url=domain.replace(/\.[^.]+$/,".localhost");async function tunnel(port){const{createLocalTunnel}=await import("@stacksjs/tunnel");return createLocalTunnel(port)}switch(type){case"frontend":if(network)return await tunnel(config.ports?.frontend||3000);if(localhost)return`http://localhost:${config.ports?.frontend}`;break;case"backend":if(network)return await tunnel(config.ports?.backend||3001);if(localhost)return`http://localhost:${config.ports?.backend}`;url=`api.${url}`;break;case"admin":if(network)return await tunnel(config.ports?.admin||3002);if(localhost)return`http://localhost:${config.ports?.admin}`;url=`admin.${url}`;break;case"library":if(network)return await tunnel(config.ports?.library||3003);if(localhost)return`http://localhost:${config.ports?.library}`;url=`libs.${url}`;break;case"email":if(network)return await tunnel(config.ports?.email||3005);if(localhost)return`http://localhost:${config.ports?.email}`;url=`email.${url}`;break;case"desktop":if(network)return await tunnel(config.ports?.desktop||3004);if(localhost)return`http://localhost:${config.ports?.desktop}`;url=`desktop.${url}`;break;case"docs":if(network)return await tunnel(config.ports?.docs||3006);if(localhost)return`http://localhost:${config.ports?.docs}`;url=`docs.${url}`;break;case"inspect":if(network)return await tunnel(config.ports?.inspect||3007);if(localhost)return`http://localhost:${config.ports?.inspect}`;url=`inspect.${url}`;break;default:if(localhost)return`http://localhost:${config.ports?.frontend}`}if(https)return`https://${url}`;return`http://${url}`}export function defineStacksConfig(config){return config}export function defineApp(config){return config}export function defineAuth(config){return config}export function defineCache(config){return config}export function defineCloud(config){return config}export function defineCdn(config){return config}export function defineChat(config){return config}export function defineCli(config){return config}export function defineDatabase(config){return config}export function defineDependencies(config){return config}export function defineDns(config){return config}export function defineEmailConfig(config){return config}export function defineEmail(config){return config}export function defineGit(config){return config}export function defineHashing(config){return config}export function defineImages(config){return config}export function defineLibrary(config){return config}export function defineNotification(config){return config}export function definePayment(config){return config}export function defineQueue(config){return config}export function defineSearchEngine(config){return config}export function defineSecurity(config){return config}export function defineServices(config){return config}export function defineSms(config){return config}export function defineStorage(config){return config}export function defineFilesystems(config){return config}export function defineFeatureFlags(config){return config}export function defineUi(config){return config}export{defineEvents}from"@stacksjs/events";
|
package/dist/index.d.ts
CHANGED
|
@@ -16,10 +16,15 @@ export { packageComponentRoots, packageJobRoots, packageMigrationRoots, packageM
|
|
|
16
16
|
export { resolveViewPatterns, type DefaultViewsSetting, type ViewPatternResolution } from './views';
|
|
17
17
|
export {
|
|
18
18
|
createRequestContext,
|
|
19
|
+
enterRequestScope,
|
|
19
20
|
installRequestContext,
|
|
21
|
+
installRequestScope,
|
|
20
22
|
parseCookieHeader,
|
|
23
|
+
scopedRequestSnapshot,
|
|
24
|
+
scopeStxServeContext,
|
|
21
25
|
useRequestEvent,
|
|
22
26
|
type RequestContextSnapshot,
|
|
27
|
+
type SiteSnapshot,
|
|
23
28
|
type StacksRequestContext,
|
|
24
29
|
} from './request-context';
|
|
25
30
|
export * from './capabilities';
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export*from"./config";export{FRAMEWORK_DEFAULTS}from"./defaults";export{validateConfig,reportConfigIssues}from"./validators";export{feature,enableFeature,disableFeature,resetFeature,listFeatures}from"./features";export{packageComponentRoots,packageJobRoots,packageMigrationRoots,packageModelRoots,packageViewRoots}from"./discovered-resources";export{resolveViewPatterns}from"./views";export{createRequestContext,installRequestContext,parseCookieHeader,useRequestEvent}from"./request-context";export*from"./capabilities";
|
|
1
|
+
export*from"./config";export{FRAMEWORK_DEFAULTS}from"./defaults";export{validateConfig,reportConfigIssues}from"./validators";export{feature,enableFeature,disableFeature,resetFeature,listFeatures}from"./features";export{packageComponentRoots,packageJobRoots,packageMigrationRoots,packageModelRoots,packageViewRoots}from"./discovered-resources";export{resolveViewPatterns}from"./views";export{createRequestContext,enterRequestScope,installRequestContext,installRequestScope,parseCookieHeader,scopedRequestSnapshot,scopeStxServeContext,useRequestEvent}from"./request-context";export*from"./capabilities";
|
|
@@ -20,6 +20,70 @@ export declare function createRequestContext(read: () => RequestContextSnapshot
|
|
|
20
20
|
* object by accident.
|
|
21
21
|
*/
|
|
22
22
|
export declare function installRequestContext(read: () => RequestContextSnapshot | undefined): StacksRequestContext;
|
|
23
|
+
/**
|
|
24
|
+
* Open a scope for `request` and return its snapshot, for the server to add
|
|
25
|
+
* the site or a locale to once it has resolved them.
|
|
26
|
+
*
|
|
27
|
+
* Call it FIRST in stx serve's `onRequest` hook, before the hook's first
|
|
28
|
+
* `await`. stx calls the hook from the request's own async context and awaits
|
|
29
|
+
* it before rendering, so a scope entered in the hook's synchronous prefix is
|
|
30
|
+
* the one every `<script server>` of that render runs in: the page's, the
|
|
31
|
+
* layout's and the components'. core/buddy/tests/request-context-concurrency
|
|
32
|
+
* .test.ts sends 400 storefront requests, sixteen in flight, to each view
|
|
33
|
+
* server and checks the page and the layout both read their own.
|
|
34
|
+
*
|
|
35
|
+
* Entered after an `await` in the hook, the scope reaches no script. The rest
|
|
36
|
+
* of an async function runs in a context of its own, and stx resumes in the
|
|
37
|
+
* one it called the hook from. The dev server entered its scope there, and
|
|
38
|
+
* production-server.ts said it had tried the dev server's approach before
|
|
39
|
+
* settling on globals, which is where "AsyncLocalStorage does not survive
|
|
40
|
+
* into stx-serve's render" came from. request-scope.test.ts pins both
|
|
41
|
+
* placements, and that a request that entered no scope reads none rather
|
|
42
|
+
* than a neighbour's.
|
|
43
|
+
*/
|
|
44
|
+
export declare function enterRequestScope(request: Request): RequestContextSnapshot;
|
|
45
|
+
/**
|
|
46
|
+
* The request the caller is running inside, or undefined outside any scope.
|
|
47
|
+
*
|
|
48
|
+
* What stx published for the render wins, so `requestContext` answers what
|
|
49
|
+
* stx hands the same render as bindings (route `params`, the client `ip`, its
|
|
50
|
+
* locale, a CSRF cookie it minted). The server's own snapshot supplies the
|
|
51
|
+
* site, which stx does not carry, and stands in for everything on a stx that
|
|
52
|
+
* publishes nothing.
|
|
53
|
+
*
|
|
54
|
+
* Outside a scope there is no request, and every `requestContext` accessor
|
|
55
|
+
* answers with its empty value. Nothing here falls back to a process-wide
|
|
56
|
+
* value: that fallback is how one visitor saw another's cart.
|
|
57
|
+
*/
|
|
58
|
+
export declare function scopedRequestSnapshot(): RequestContextSnapshot | undefined;
|
|
59
|
+
/**
|
|
60
|
+
* Keep stx's `__stxServeContext` mirror per request.
|
|
61
|
+
*
|
|
62
|
+
* stx serve assigns the request it is about to render to
|
|
63
|
+
* `globalThis.__stxServeContext` (`injectServeRequestContext` in
|
|
64
|
+
* bun-plugin-stx's serve), once per render, before the page's server
|
|
65
|
+
* scripts. As a plain global it holds whichever render assigned it last. The
|
|
66
|
+
* assignment runs inside the rendering request's async context, so this turns
|
|
67
|
+
* the global into an accessor that files the value under that request's scope
|
|
68
|
+
* and reads back the reader's own.
|
|
69
|
+
*
|
|
70
|
+
* It is what carries route `params`, the client `ip` and stx's locale into
|
|
71
|
+
* {@link scopedRequestSnapshot}: `onRequest` runs before stx matches the route,
|
|
72
|
+
* and is not handed the server stx reads the address from.
|
|
73
|
+
*
|
|
74
|
+
* An assignment made outside any scope is kept for readers outside one, as
|
|
75
|
+
* the plain global kept it. A reader inside a scope only ever gets its own.
|
|
76
|
+
*/
|
|
77
|
+
export declare function scopeStxServeContext(): void;
|
|
78
|
+
/**
|
|
79
|
+
* What both servers install at boot: `requestContext`, reading the scope of
|
|
80
|
+
* the request each script runs in.
|
|
81
|
+
*
|
|
82
|
+
* One call rather than the pieces, so neither server can scope stx's mirror
|
|
83
|
+
* and forget the reader, or the reverse. The other half is
|
|
84
|
+
* {@link enterRequestScope}, first in each server's `onRequest`.
|
|
85
|
+
*/
|
|
86
|
+
export declare function installRequestScope(): StacksRequestContext;
|
|
23
87
|
/**
|
|
24
88
|
* The single accessor (#2232 ask 4), for callers who would rather have one
|
|
25
89
|
* object than reach for ambient globals.
|
|
@@ -40,34 +104,6 @@ export declare function useRequestEvent(): {
|
|
|
40
104
|
host: string
|
|
41
105
|
site: SiteSnapshot | null
|
|
42
106
|
};
|
|
43
|
-
/**
|
|
44
|
-
* One request object for `<script server>` blocks (stacksjs/stacks#2232).
|
|
45
|
-
*
|
|
46
|
-
* `requestContext` was installed twice — once by the dev views server, once by
|
|
47
|
-
* the production server — with two different backings, two different sets of
|
|
48
|
-
* methods, and no shared type. Both installers were `(globalThis)`, so
|
|
49
|
-
* nothing could catch a divergence. Two already shipped:
|
|
50
|
-
*
|
|
51
|
-
* - production's `url()` returned only the query string, so a page doing
|
|
52
|
-
* `new URL(requestContext.url())` worked in dev and threw on the box
|
|
53
|
-
* - production had no `locale()` at all, so a page that branched on locale
|
|
54
|
-
* threw "requestContext.locale is not a function" on the box
|
|
55
|
-
*
|
|
56
|
-
* Both were found by an end-to-end test, not by inspection, because there was
|
|
57
|
-
* nothing to inspect against.
|
|
58
|
-
*
|
|
59
|
-
* A shared TYPE would only have made those detectable. A shared FACTORY makes
|
|
60
|
-
* them impossible: each server supplies a snapshot reader and gets the same
|
|
61
|
-
* object built the same way. The only thing a server still chooses is where
|
|
62
|
-
* the snapshot comes from, which is the one thing that genuinely differs (dev
|
|
63
|
-
* has AsyncLocalStorage available; production established it does not survive
|
|
64
|
-
* into stx-serve's render).
|
|
65
|
-
*
|
|
66
|
-
* Home of convenience: `@stacksjs/config` is the only package both the dev
|
|
67
|
-
* server (`@stacksjs/actions`) and the production server (`@stacksjs/buddy`)
|
|
68
|
-
* already depend on. It is not conceptually config, and moving it later is a
|
|
69
|
-
* re-export away.
|
|
70
|
-
*/
|
|
71
107
|
/**
|
|
72
108
|
* What a server knows about the request in flight.
|
|
73
109
|
*
|
|
@@ -90,11 +126,13 @@ export declare interface RequestContextSnapshot {
|
|
|
90
126
|
/**
|
|
91
127
|
* The site a multi-site server resolved for this request's Host header.
|
|
92
128
|
*
|
|
93
|
-
* Carried on the snapshot
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
129
|
+
* Carried on the snapshot because `@stacksjs/sites`' own AsyncLocalStorage
|
|
130
|
+
* context does not reach the render: both servers set it (`setCurrentSite`)
|
|
131
|
+
* after the first `await` of their `onRequest` hook, and a scope entered there
|
|
132
|
+
* ends with the hook (see {@link enterRequestScope}). A `<script server>`
|
|
133
|
+
* block that asked `currentSite()` would get undefined. The resolving server
|
|
134
|
+
* stores it on the snapshot {@link enterRequestScope} returned; pages read
|
|
135
|
+
* `requestContext.site()`.
|
|
98
136
|
*/
|
|
99
137
|
export declare interface SiteSnapshot {
|
|
100
138
|
id: number
|
package/dist/request-context.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export function parseCookieHeader(header){const out={};if(!header)return out;for(const part of header.split(";")){const trimmed=part.trim(),eq=trimmed.indexOf("=");if(eq===-1)continue;const key=trimmed.slice(0,eq).trim();if(!key)continue;const value=trimmed.slice(eq+1).trim();try{out[key]=decodeURIComponent(value)}catch{out[key]=value}}return out}export function createRequestContext(read){const snapshot=()=>read()??{},searchOf=()=>{const direct=snapshot().search;if(direct)return direct;const url=snapshot().url??"",mark=url.indexOf("?");return mark===-1?"":url.slice(mark)};return{cookie:(name)=>snapshot().cookies?.[name]??null,cookies:()=>snapshot().cookies??{},url:()=>snapshot().url??"",path:()=>{const direct=snapshot().path;if(direct)return direct;const url=snapshot().url??"";if(!url)return"";try{return new URL(url).pathname}catch{const mark=url.indexOf("?");return mark===-1?url:url.slice(0,mark)}},search:searchOf,query:()=>{const out={},search=searchOf();if(!search)return out;new URLSearchParams(search.startsWith("?")?search.slice(1):search).forEach((value,key)=>{out[key]=value});return out},params:()=>snapshot().params??{},locale:()=>snapshot().locale??"en",ip:()=>snapshot().ip??"",host:()=>snapshot().host??"",site:()=>snapshot().site??null}}export function installRequestContext(read){const context=createRequestContext(read);globalThis.requestContext=context;return context}export function useRequestEvent(){const context=globalThis.requestContext??createRequestContext(()=>{return});return{url:context.url(),path:context.path(),search:context.search(),query:context.query(),cookies:context.cookies(),params:context.params(),locale:context.locale(),ip:context.ip(),host:context.host(),site:context.site()}}
|
|
1
|
+
import{AsyncLocalStorage}from"node:async_hooks";export function parseCookieHeader(header){const out={};if(!header)return out;for(const part of header.split(";")){const trimmed=part.trim(),eq=trimmed.indexOf("=");if(eq===-1)continue;const key=trimmed.slice(0,eq).trim();if(!key)continue;const value=trimmed.slice(eq+1).trim();try{out[key]=decodeURIComponent(value)}catch{out[key]=value}}return out}export function createRequestContext(read){const snapshot=()=>read()??{},searchOf=()=>{const direct=snapshot().search;if(direct)return direct;const url=snapshot().url??"",mark=url.indexOf("?");return mark===-1?"":url.slice(mark)};return{cookie:(name)=>snapshot().cookies?.[name]??null,cookies:()=>snapshot().cookies??{},url:()=>snapshot().url??"",path:()=>{const direct=snapshot().path;if(direct)return direct;const url=snapshot().url??"";if(!url)return"";try{return new URL(url).pathname}catch{const mark=url.indexOf("?");return mark===-1?url:url.slice(0,mark)}},search:searchOf,query:()=>{const out={},search=searchOf();if(!search)return out;new URLSearchParams(search.startsWith("?")?search.slice(1):search).forEach((value,key)=>{out[key]=value});return out},params:()=>snapshot().params??{},locale:()=>snapshot().locale??"en",ip:()=>snapshot().ip??"",host:()=>snapshot().host??"",site:()=>snapshot().site??null}}export function installRequestContext(read){const context=createRequestContext(read);globalThis.requestContext=context;return context}const SCOPE_STORAGE_KEY=Symbol.for("stacks.config.requestScope"),scopeStorage=globalThis[SCOPE_STORAGE_KEY]??=new AsyncLocalStorage;export function enterRequestScope(request){const url=new URL(request.url),own={cookies:parseCookieHeader(request.headers.get("cookie")),url:request.url,path:url.pathname,search:url.search,host:request.headers.get("host")??url.host,site:null};scopeStorage.enterWith({own});return own}export function scopedRequestSnapshot(){const scope=scopeStorage.getStore();if(!scope)return;const{own,published}=scope;if(!published)return own;return{...own,...published,site:own.site??published.site??null}}const STX_SERVE_CONTEXT="__stxServeContext";export function scopeStxServeContext(){const existing=Object.getOwnPropertyDescriptor(globalThis,STX_SERVE_CONTEXT);if(existing?.get)return;let unscoped=existing?.value;Object.defineProperty(globalThis,STX_SERVE_CONTEXT,{configurable:!0,enumerable:!1,get:()=>{const scope=scopeStorage.getStore();return scope?scope.published:unscoped},set:(value)=>{const scope=scopeStorage.getStore();if(scope)scope.published=value;else unscoped=value}})}export function installRequestScope(){scopeStxServeContext();return installRequestContext(scopedRequestSnapshot)}export function useRequestEvent(){const context=globalThis.requestContext??createRequestContext(()=>{return});return{url:context.url(),path:context.path(),search:context.search(),query:context.query(),cookies:context.cookies(),params:context.params(),locale:context.locale(),ip:context.ip(),host:context.host(),site:context.site()}}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { defaults } from './defaults';
|
|
2
|
+
import { overrides, overridesReady } from './overrides';
|
|
3
|
+
import type { StacksOptions } from '@stacksjs/types';
|
|
4
|
+
/*.ts` file has loaded and the
|
|
5
|
+
* `overrides` instance has been mutated in place. Use this in code paths
|
|
6
|
+
* that *must* see the merged values — typically anything outside the
|
|
7
|
+
* normal request flow (e.g. CLI commands, top-level boot scripts) where
|
|
8
|
+
* the request hasn't yet hit a route handler that already awaited.
|
|
9
|
+
*
|
|
10
|
+
* Inside request handlers / routes you generally do *not* need to await
|
|
11
|
+
* this: the router's `serverResponse()` only fires after `importRoutes()`
|
|
12
|
+
* resolves, and `importRoutes()` indirectly awaits config (it reads
|
|
13
|
+
* `app/Routes.ts`, which imports config). So the proxy returns merged
|
|
14
|
+
* values for all "normal" request-time reads.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* import { config, awaitConfig } from '@stacksjs/config'
|
|
19
|
+
*
|
|
20
|
+
* // CLI / top-level script — config files may not be loaded yet
|
|
21
|
+
* await awaitConfig()
|
|
22
|
+
* console.log(config.ports.api) // guaranteed to be the user value
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare function awaitConfig(): Promise<StacksOptions>;
|
|
26
|
+
/**
|
|
27
|
+
* Resolves once `database` config has been read by `@stacksjs/database`'s
|
|
28
|
+
* lazy initializer. Use it from boot scripts that need a live `db` handle
|
|
29
|
+
* before the request loop starts.
|
|
30
|
+
*
|
|
31
|
+
* Why a separate signal: `overridesReady` only signals that `~/config/database.ts`
|
|
32
|
+
* was *imported*, not that `@stacksjs/database` has actually wired up the
|
|
33
|
+
* connection. Calling `db.selectFrom(...)` immediately after `overridesReady`
|
|
34
|
+
* resolves can still race the connection setup. The database driver flips
|
|
35
|
+
* this signal to `true` once it has a working connection.
|
|
36
|
+
*/
|
|
37
|
+
export declare function awaitDatabaseConfig(): Promise<StacksOptions>;
|
|
38
|
+
/**
|
|
39
|
+
* Called by `@stacksjs/database` once the connection is live. Internal —
|
|
40
|
+
* users shouldn't need to invoke this directly.
|
|
41
|
+
*/
|
|
42
|
+
export declare function markDatabaseReady(): void;
|
|
43
|
+
export declare function getConfig(): StacksOptions;
|
|
44
|
+
export declare function determineAppEnv(): AppEnv;
|
|
45
|
+
export declare const config: StacksOptions;
|
|
46
|
+
// Per-section convenience exports.
|
|
47
|
+
//
|
|
48
|
+
// Caveat: these are *snapshots* taken at module-load time. ESM `export
|
|
49
|
+
// const x = expr` evaluates `expr` once and binds `x` to that result —
|
|
50
|
+
// there's no language-level way to make a named export re-evaluate
|
|
51
|
+
// per access. So `import { ports } from '@stacksjs/config'` returns
|
|
52
|
+
// whatever `config.ports` was when the config module first evaluated,
|
|
53
|
+
// which is *before* `overridesReady` resolves and the user's
|
|
54
|
+
// `config/*.ts` files land.
|
|
55
|
+
//
|
|
56
|
+
// These start as snapshots of the framework defaults (because the user's
|
|
57
|
+
// `config/*.ts` haven't loaded yet — see `overridesReady`). Once the
|
|
58
|
+
// async loader resolves we reassign each `let` binding so consumers of
|
|
59
|
+
// `import { ports } from '@stacksjs/config'` see the merged value.
|
|
60
|
+
//
|
|
61
|
+
// ESM live bindings make this work: when an exporting module reassigns
|
|
62
|
+
// a `let`-bound export, every importer immediately sees the new value
|
|
63
|
+
// (no re-import needed). The earlier `export const x = config.x` form
|
|
64
|
+
// captured the empty-default snapshot forever.
|
|
65
|
+
//
|
|
66
|
+
// Caveat: code that destructures (`const { ports } = config` or reads
|
|
67
|
+
// at the top of a function) before `overridesReady` resolves still
|
|
68
|
+
// gets the early snapshot. For correctness in that path, read off
|
|
69
|
+
// the `config` proxy: `config.ports.api` always pulls live.
|
|
70
|
+
export declare let ai: StacksOptions['ai'];
|
|
71
|
+
export declare let analytics: StacksOptions['analytics'];
|
|
72
|
+
export declare let app: StacksOptions['app'];
|
|
73
|
+
export declare let auth: StacksOptions['auth'];
|
|
74
|
+
export declare let realtime: StacksOptions['realtime'];
|
|
75
|
+
export declare let cache: StacksOptions['cache'];
|
|
76
|
+
export declare let cloud: StacksOptions['cloud'];
|
|
77
|
+
export declare let cli: StacksOptions['cli'];
|
|
78
|
+
export declare let dashboard: StacksOptions['dashboard'];
|
|
79
|
+
export declare let database: StacksOptions['database'];
|
|
80
|
+
export declare let dns: StacksOptions['dns'];
|
|
81
|
+
export declare let docs: StacksOptions['docs'];
|
|
82
|
+
export declare let email: StacksOptions['email'];
|
|
83
|
+
export declare let errors: StacksOptions['errors'];
|
|
84
|
+
export declare let featureFlags: StacksOptions['featureFlags'];
|
|
85
|
+
export declare let git: StacksOptions['git'];
|
|
86
|
+
export declare let hashing: StacksOptions['hashing'];
|
|
87
|
+
export declare let library: StacksOptions['library'];
|
|
88
|
+
export declare let logging: StacksOptions['logging'];
|
|
89
|
+
export declare let notification: StacksOptions['notification'];
|
|
90
|
+
export declare let payment: StacksOptions['payment'];
|
|
91
|
+
export declare let ports: StacksOptions['ports'];
|
|
92
|
+
export declare let queue: StacksOptions['queue'];
|
|
93
|
+
export declare let security: StacksOptions['security'];
|
|
94
|
+
export declare let saas: StacksOptions['saas'];
|
|
95
|
+
export declare let searchEngine: StacksOptions['searchEngine'];
|
|
96
|
+
export declare let server: StacksOptions['server'];
|
|
97
|
+
export declare let services: StacksOptions['services'];
|
|
98
|
+
export declare let filesystems: StacksOptions['filesystems'];
|
|
99
|
+
export declare let team: StacksOptions['team'];
|
|
100
|
+
export declare let ui: StacksOptions['ui'];
|
|
101
|
+
/**
|
|
102
|
+
* What `determineAppEnv` answers.
|
|
103
|
+
*
|
|
104
|
+
* The three canonical values, plus a passthrough for any other configured
|
|
105
|
+
* environment - the function maps local/development, staging and production
|
|
106
|
+
* onto them and returns anything else unchanged.
|
|
107
|
+
*
|
|
108
|
+
* `(string & {})` rather than a bare `string`: a bare one absorbs the literals
|
|
109
|
+
* and the union collapses, so the three canonical values stop being offered as
|
|
110
|
+
* completions and the type says nothing at all.
|
|
111
|
+
*/
|
|
112
|
+
// eslint-disable-next-line ts/ban-types -- `string & {}` keeps literal completions alive
|
|
113
|
+
declare type AppEnv = 'dev' | 'stage' | 'prod' | (string & {});
|
|
114
|
+
export { defaults, overrides, overridesReady };
|
package/dist/runtime.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{defaults}from"./defaults";import{overrides,overridesReady}from"./overrides";function isPlainObject(value){if(typeof value!=="object"||value===null||Array.isArray(value))return!1;const proto=Object.getPrototypeOf(value);return proto===Object.prototype||proto===null}function overlay(base,over){const out={...base};for(const key of Object.keys(over)){const next=over[key],prev=out[key];if(next===void 0)continue;out[key]=isPlainObject(prev)&&isPlainObject(next)?overlay(prev,next):next}return out}const mergedSections=new Map;function readMerged(prop){const o=overrides[prop],d=defaults[prop];if(!(o!==void 0&&(typeof o!=="object"||o===null||Object.keys(o).length>0)))return d;if(!isPlainObject(o)||!isPlainObject(d))return o;const cached=mergedSections.get(prop);if(cached&&cached.from===o)return cached.merged;const merged=overlay(d,o);mergedSections.set(prop,{from:o,merged});return merged}const proxyTarget=function configProxyTarget(){};export const config=new Proxy(proxyTarget,{get(_t,prop){return readMerged(prop)},set(_t,prop,value){overrides[prop]=value;return!0},deleteProperty(_t,prop){delete overrides[prop];return!0},has(_t,prop){return prop in overrides||prop in defaults},ownKeys(){return Array.from(new Set([...Object.keys(overrides),...Object.keys(defaults)]))},getOwnPropertyDescriptor(_t,prop){if(typeof prop!=="string")return;if(!(prop in overrides)&&!(prop in defaults))return;return{enumerable:!0,configurable:!0,writable:!0,value:readMerged(prop)}},isExtensible(){return!0},preventExtensions(){return!1}});export async function awaitConfig(){await overridesReady;return config}const DB_READY=Symbol.for("@stacksjs/config:databaseReady"),globalScope=globalThis;export async function awaitDatabaseConfig(){await overridesReady;const deadline=Date.now()+5000;while(!globalScope[DB_READY]&&Date.now()<deadline)await new Promise((r)=>setTimeout(r,25));if(!globalScope[DB_READY])console.warn("[config] awaitDatabaseConfig() timed out - database driver did not signal readiness within 5s");return config}export function markDatabaseReady(){globalScope[DB_READY]=!0}export function getConfig(){return config}export let{ai,analytics,app,auth,realtime,cache,cloud,cli,dashboard,database,dns,docs,email,errors,featureFlags,git,hashing,library,logging,notification,payment,ports,queue,security,saas,searchEngine,server,services,filesystems,team,ui}=config;overridesReady.then(()=>{ai=config.ai;analytics=config.analytics;app=config.app;auth=config.auth;realtime=config.realtime;cache=config.cache;cloud=config.cloud;cli=config.cli;dashboard=config.dashboard;database=config.database;dns=config.dns;docs=config.docs;email=config.email;errors=config.errors;featureFlags=config.featureFlags;git=config.git;hashing=config.hashing;library=config.library;logging=config.logging;notification=config.notification;payment=config.payment;ports=config.ports;queue=config.queue;security=config.security;saas=config.saas;searchEngine=config.searchEngine;server=config.server;services=config.services;filesystems=config.filesystems;team=config.team;ui=config.ui}).catch(()=>{});export{defaults,overrides,overridesReady};export function determineAppEnv(){const env=config.app?.env;if(env==="local"||env==="development")return"dev";if(env==="staging")return"stage";if(env==="production")return"prod";if(!env)throw Error("Couldn't determine app environment");return env}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/config",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.74.
|
|
5
|
+
"version": "0.74.52",
|
|
6
6
|
"description": "The Stacks config helper methods.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -55,14 +55,14 @@
|
|
|
55
55
|
"prepublishOnly": "bun run build"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@stacksjs/events": "0.74.
|
|
59
|
-
"@stacksjs/path": "0.74.
|
|
60
|
-
"@stacksjs/tunnel": "0.74.
|
|
58
|
+
"@stacksjs/events": "0.74.52",
|
|
59
|
+
"@stacksjs/path": "0.74.52",
|
|
60
|
+
"@stacksjs/tunnel": "0.74.52",
|
|
61
61
|
"ts-pantry": "^0.11.35"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
|
-
"@stacksjs/alias": "0.74.
|
|
65
|
-
"@stacksjs/types": "0.74.
|
|
64
|
+
"@stacksjs/alias": "0.74.52",
|
|
65
|
+
"@stacksjs/types": "0.74.52",
|
|
66
66
|
"better-dx": "^0.2.24"
|
|
67
67
|
}
|
|
68
68
|
}
|