@lorion-org/runtime-config 1.0.0-beta.0 → 1.0.0-beta.1

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.
@@ -6,15 +6,15 @@ import {
6
6
  const runtimeConfig = projectSectionedRuntimeConfig(
7
7
  [
8
8
  {
9
- scopeId: 'billing',
9
+ scopeId: 'checkout',
10
10
  config: {
11
11
  public: {
12
- apiBase: '/api/billing',
12
+ successPath: '/orders/confirmed',
13
13
  },
14
- tenants: {
15
- tenantA: {
14
+ stores: {
15
+ 'eu-store': {
16
16
  public: {
17
- apiBase: '/tenant-a/billing',
17
+ successPath: '/eu-store/orders/confirmed',
18
18
  },
19
19
  },
20
20
  },
@@ -22,25 +22,25 @@ const runtimeConfig = projectSectionedRuntimeConfig(
22
22
  },
23
23
  ],
24
24
  {
25
- contextInputKey: 'tenants',
26
- contextOutputKey: '__tenants',
25
+ contextInputKey: 'stores',
26
+ contextOutputKey: '__stores',
27
27
  },
28
28
  );
29
29
 
30
30
  console.log(runtimeConfig.public);
31
31
  // {
32
- // billingApiBase: '/api/billing',
33
- // __tenants: {
34
- // tenantA: {
35
- // billingApiBase: '/tenant-a/billing'
32
+ // checkoutSuccessPath: '/orders/confirmed',
33
+ // __stores: {
34
+ // 'eu-store': {
35
+ // checkoutSuccessPath: '/eu-store/orders/confirmed'
36
36
  // }
37
37
  // }
38
38
  // }
39
39
 
40
40
  console.log(
41
- resolveRuntimeConfigValue(runtimeConfig.public, 'billing', 'apiBase', {
42
- contextId: 'tenantA',
43
- contextOutputKey: '__tenants',
41
+ resolveRuntimeConfigValue(runtimeConfig.public, 'checkout', 'successPath', {
42
+ contextId: 'eu-store',
43
+ contextOutputKey: '__stores',
44
44
  }),
45
45
  );
46
- // '/tenant-a/billing'
46
+ // '/eu-store/orders/confirmed'
@@ -7,19 +7,19 @@ import {
7
7
  const envVars = toRuntimeEnvVars(
8
8
  {
9
9
  public: {
10
- billingApiBase: '/api/billing',
10
+ checkoutSuccessPath: '/orders/confirmed',
11
11
  },
12
12
  private: {
13
- billingApiSecret: 'secret',
13
+ checkoutSigningSecret: 'checkout_signing_secret_demo',
14
14
  },
15
15
  },
16
16
  'APP',
17
17
  );
18
18
 
19
19
  console.log(runtimeEnvVarsToString(envVars));
20
- // APP_PUBLIC_BILLING_API_BASE=/api/billing
21
- // APP_PRIVATE_BILLING_API_SECRET=secret
20
+ // APP_PUBLIC_CHECKOUT_SUCCESS_PATH=/orders/confirmed
21
+ // APP_PRIVATE_CHECKOUT_SIGNING_SECRET=checkout_signing_secret_demo
22
22
 
23
23
  console.log(runtimeEnvVarsToShellAssignments(envVars));
24
- // APP_PUBLIC_BILLING_API_BASE='"/api/billing"'
25
- // APP_PRIVATE_BILLING_API_SECRET='"secret"'
24
+ // APP_PUBLIC_CHECKOUT_SUCCESS_PATH='"/orders/confirmed"'
25
+ // APP_PRIVATE_CHECKOUT_SIGNING_SECRET='"checkout_signing_secret_demo"'
@@ -3,48 +3,48 @@ import {
3
3
  projectRuntimeConfigNamespaces,
4
4
  } from '@lorion-org/runtime-config';
5
5
 
6
- const billingRuntimeConfig = projectRuntimeConfigNamespace('billing', {
6
+ const checkoutRuntimeConfig = projectRuntimeConfigNamespace('checkout', {
7
7
  public: {
8
- apiBase: '/api/billing',
8
+ successPath: '/orders/confirmed',
9
9
  },
10
10
  private: {
11
- token: 'billing-token',
11
+ signingSecret: 'checkout_signing_secret_demo',
12
12
  },
13
13
  });
14
14
 
15
- console.log(billingRuntimeConfig.public.billing);
16
- // { apiBase: '/api/billing' }
15
+ console.log(checkoutRuntimeConfig.public.checkout);
16
+ // { successPath: '/orders/confirmed' }
17
17
 
18
- console.log(billingRuntimeConfig.billing);
19
- // { token: 'billing-token' }
18
+ console.log(checkoutRuntimeConfig.checkout);
19
+ // { signingSecret: 'checkout_signing_secret_demo' }
20
20
 
21
21
  const combinedRuntimeConfig = projectRuntimeConfigNamespaces([
22
22
  {
23
- scopeId: 'billing',
23
+ scopeId: 'checkout',
24
24
  config: {
25
25
  public: {
26
- apiBase: '/api/billing',
26
+ successPath: '/orders/confirmed',
27
27
  },
28
28
  private: {
29
- token: 'billing-token',
29
+ signingSecret: 'checkout_signing_secret_demo',
30
30
  },
31
31
  },
32
32
  },
33
33
  {
34
- scopeId: 'mail',
34
+ scopeId: 'payments',
35
35
  config: {
36
36
  public: {
37
- apiBase: '/api/mail',
37
+ configuredProvider: 'payment-provider-stripe',
38
38
  },
39
39
  },
40
40
  },
41
41
  ]);
42
42
 
43
- console.log(combinedRuntimeConfig.public.billing);
44
- // { apiBase: '/api/billing' }
43
+ console.log(combinedRuntimeConfig.public.checkout);
44
+ // { successPath: '/orders/confirmed' }
45
45
 
46
- console.log(combinedRuntimeConfig.public.mail);
47
- // { apiBase: '/api/mail' }
46
+ console.log(combinedRuntimeConfig.public.payments);
47
+ // { configuredProvider: 'payment-provider-stripe' }
48
48
 
49
- console.log(combinedRuntimeConfig.billing);
50
- // { token: 'billing-token' }
49
+ console.log(combinedRuntimeConfig.checkout);
50
+ // { signingSecret: 'checkout_signing_secret_demo' }
@@ -5,28 +5,28 @@ import {
5
5
  toRuntimeEnvVars,
6
6
  } from '@lorion-org/runtime-config';
7
7
 
8
- const runtimeConfig = projectRuntimeConfigFragment('auth', {
8
+ const runtimeConfig = projectRuntimeConfigFragment('checkout', {
9
9
  public: {
10
- url: 'https://auth.example.test',
11
- realm: 'main',
10
+ currency: 'EUR',
11
+ successPath: '/orders/confirmed',
12
12
  },
13
13
  private: {
14
- clientSecret: 'secret',
14
+ signingSecret: 'checkout_signing_secret_demo',
15
15
  },
16
16
  });
17
17
 
18
- console.log(runtimeConfig.public.authUrl);
19
- // 'https://auth.example.test'
18
+ console.log(runtimeConfig.public.checkoutCurrency);
19
+ // 'EUR'
20
20
 
21
21
  console.log(toRuntimeEnvVars(runtimeConfig, 'APP'));
22
22
  // {
23
- // APP_PUBLIC_AUTH_URL: 'https://auth.example.test',
24
- // APP_PUBLIC_AUTH_REALM: 'main',
25
- // APP_PRIVATE_AUTH_CLIENT_SECRET: 'secret'
23
+ // APP_PUBLIC_CHECKOUT_CURRENCY: 'EUR',
24
+ // APP_PUBLIC_CHECKOUT_SUCCESS_PATH: '/orders/confirmed',
25
+ // APP_PRIVATE_CHECKOUT_SIGNING_SECRET: 'checkout_signing_secret_demo'
26
26
  // }
27
27
 
28
- console.log(getPublicRuntimeConfigScope(runtimeConfig, 'auth'));
29
- // { url: 'https://auth.example.test', realm: 'main' }
28
+ console.log(getPublicRuntimeConfigScope(runtimeConfig, 'checkout'));
29
+ // { currency: 'EUR', successPath: '/orders/confirmed' }
30
30
 
31
- console.log(getPrivateRuntimeConfigScope(runtimeConfig, 'auth'));
32
- // { clientSecret: 'secret' }
31
+ console.log(getPrivateRuntimeConfigScope(runtimeConfig, 'checkout'));
32
+ // { signingSecret: 'checkout_signing_secret_demo' }
@@ -7,46 +7,51 @@ import {
7
7
 
8
8
  const fragments: RuntimeConfigFragmentMap = new Map([
9
9
  [
10
- 'billing',
10
+ 'checkout',
11
11
  {
12
12
  public: {
13
- apiBase: '/api/billing',
13
+ successPath: '/orders/confirmed',
14
14
  },
15
15
  private: {
16
- apiSecret: 'secret',
16
+ signingSecret: 'checkout_signing_secret_demo',
17
17
  },
18
18
  contexts: {
19
- tenantA: {
19
+ 'eu-store': {
20
20
  public: {
21
- apiBase: '/tenant-a/billing',
21
+ successPath: '/eu-store/orders/confirmed',
22
22
  },
23
23
  },
24
24
  },
25
25
  },
26
26
  ],
27
27
  [
28
- 'mail',
28
+ 'payments',
29
29
  {
30
30
  public: {
31
- apiBase: '/api/mail',
31
+ configuredProvider: 'payment-provider-stripe',
32
32
  },
33
33
  },
34
34
  ],
35
35
  ]);
36
36
 
37
37
  const runtimeConfig = projectSectionedRuntimeConfig(fragments);
38
- const tenantApiBase = resolveRuntimeConfigValue(runtimeConfig.public, 'billing', 'apiBase', {
39
- contextId: 'tenantA',
40
- });
38
+ const euStoreSuccessPath = resolveRuntimeConfigValue(
39
+ runtimeConfig.public,
40
+ 'checkout',
41
+ 'successPath',
42
+ {
43
+ contextId: 'eu-store',
44
+ },
45
+ );
41
46
 
42
- console.log(runtimeConfig.public.billingApiBase);
43
- // '/api/billing'
47
+ console.log(runtimeConfig.public.checkoutSuccessPath);
48
+ // '/orders/confirmed'
44
49
 
45
- console.log(runtimeConfig.private.billingApiSecret);
46
- // 'secret'
50
+ console.log(runtimeConfig.private.checkoutSigningSecret);
51
+ // 'checkout_signing_secret_demo'
47
52
 
48
- console.log(tenantApiBase);
49
- // '/tenant-a/billing'
53
+ console.log(euStoreSuccessPath);
54
+ // '/eu-store/orders/confirmed'
50
55
 
51
- console.log(getPublicRuntimeConfigScope(runtimeConfig, 'billing'));
52
- // { apiBase: '/api/billing' }
56
+ console.log(getPublicRuntimeConfigScope(runtimeConfig, 'checkout'));
57
+ // { successPath: '/orders/confirmed' }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lorion-org/runtime-config",
3
- "version": "1.0.0-beta.0",
3
+ "version": "1.0.0-beta.1",
4
4
  "description": "Framework-free runtime-config types and merge helpers.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -22,6 +22,10 @@
22
22
  "types": "./dist/index.d.ts",
23
23
  "exports": {
24
24
  ".": {
25
+ "lorion-source": {
26
+ "types": "./src/index.ts",
27
+ "default": "./src/index.ts"
28
+ },
25
29
  "import": {
26
30
  "types": "./dist/index.d.ts",
27
31
  "default": "./dist/index.js"
@@ -35,7 +39,9 @@
35
39
  "files": [
36
40
  "dist",
37
41
  "examples",
38
- "LICENSE"
42
+ "LICENSE",
43
+ "src/**/*.ts",
44
+ "!src/**/*.spec.ts"
39
45
  ],
40
46
  "keywords": [
41
47
  "runtime-config",
@@ -46,6 +52,9 @@
46
52
  "engines": {
47
53
  "node": "^20.19.0 || >=22.12.0"
48
54
  },
55
+ "dependencies": {
56
+ "ajv": "^8.17.1"
57
+ },
49
58
  "scripts": {
50
59
  "build": "tsup src/index.ts --format esm,cjs --dts",
51
60
  "clean": "rimraf coverage dist tsconfig.tsbuildinfo",
package/src/env.ts ADDED
@@ -0,0 +1,91 @@
1
+ import { toSnakeUpperCase } from './key';
2
+ import {
3
+ projectSectionedRuntimeConfig,
4
+ type ProjectSectionedRuntimeConfigOptions,
5
+ } from './project';
6
+ import {
7
+ runtimeConfigVisibilities,
8
+ type ConfigVisibility,
9
+ type NamedRuntimeConfigFragment,
10
+ type RuntimeConfigFragmentMap,
11
+ type RuntimeEnvVars,
12
+ type SectionedRuntimeConfig,
13
+ } from './types';
14
+
15
+ export type ProjectRuntimeConfigEnvVarsOptions = ProjectSectionedRuntimeConfigOptions & {
16
+ prefix?: string;
17
+ };
18
+
19
+ export function createRuntimeConfigEnvKey(input: {
20
+ key: string;
21
+ prefix?: string;
22
+ scopeId?: string;
23
+ visibility?: ConfigVisibility;
24
+ }): string {
25
+ return toSnakeUpperCase(
26
+ [input.prefix ?? 'APP', input.visibility, input.scopeId, input.key].filter(Boolean).join('_'),
27
+ );
28
+ }
29
+
30
+ export function toRuntimeEnvVars(
31
+ runtimeConfig: SectionedRuntimeConfig,
32
+ prefix = 'APP',
33
+ ): RuntimeEnvVars {
34
+ const envVars: RuntimeEnvVars = {};
35
+
36
+ for (const visibility of runtimeConfigVisibilities) {
37
+ for (const [key, value] of Object.entries(runtimeConfig[visibility])) {
38
+ if (key.startsWith('__')) continue;
39
+
40
+ envVars[createRuntimeConfigEnvKey({ prefix, visibility, key })] = value;
41
+ }
42
+ }
43
+
44
+ return envVars;
45
+ }
46
+
47
+ export function projectRuntimeConfigEnvVars(
48
+ fragments: Iterable<NamedRuntimeConfigFragment> | RuntimeConfigFragmentMap,
49
+ options: ProjectRuntimeConfigEnvVarsOptions = {},
50
+ ): RuntimeEnvVars {
51
+ const { prefix = 'APP', ...projectOptions } = options;
52
+
53
+ return toRuntimeEnvVars(
54
+ projectSectionedRuntimeConfig(fragments, {
55
+ ...projectOptions,
56
+ includeContexts: false,
57
+ }),
58
+ prefix,
59
+ );
60
+ }
61
+
62
+ export function injectRuntimeEnvVars(
63
+ envVars: RuntimeEnvVars,
64
+ processEnv: Record<string, unknown>,
65
+ ): RuntimeEnvVars {
66
+ const items: RuntimeEnvVars = {};
67
+
68
+ for (const key of Object.keys(envVars)) {
69
+ items[key] = processEnv[key] ?? envVars[key];
70
+ }
71
+
72
+ return items;
73
+ }
74
+
75
+ export function runtimeEnvVarsToString(envVars: RuntimeEnvVars): string {
76
+ return Object.entries(envVars)
77
+ .map(([key, value]) => `${key}=${String(value)}`)
78
+ .join('\n');
79
+ }
80
+
81
+ export function runtimeEnvValueToShellLiteral(value: unknown): string {
82
+ const json = JSON.stringify(value) ?? 'undefined';
83
+
84
+ return `'${json.replace(/'/g, "'\\''")}'`;
85
+ }
86
+
87
+ export function runtimeEnvVarsToShellAssignments(envVars: RuntimeEnvVars): string {
88
+ return Object.entries(envVars)
89
+ .map(([key, value]) => `${key}=${runtimeEnvValueToShellLiteral(value)}`)
90
+ .join('\n');
91
+ }
@@ -0,0 +1,28 @@
1
+ import type {
2
+ RuntimeConfigContext,
3
+ RuntimeConfigFragment,
4
+ RuntimeConfigFragmentInput,
5
+ } from './types';
6
+
7
+ export type NormalizeRuntimeConfigFragmentOptions = {
8
+ contextInputKey?: string;
9
+ };
10
+
11
+ const isObject = (value: unknown): value is Record<string, unknown> => {
12
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
13
+ };
14
+
15
+ export function toRuntimeConfigFragment(
16
+ config: RuntimeConfigFragmentInput,
17
+ options: NormalizeRuntimeConfigFragmentOptions = {},
18
+ ): RuntimeConfigFragment {
19
+ const contextInputKey = options.contextInputKey ?? 'contexts';
20
+ const contexts =
21
+ contextInputKey === 'contexts' ? config.contexts : (config.contexts ?? config[contextInputKey]);
22
+
23
+ return {
24
+ ...(isObject(config.public) ? { public: config.public } : {}),
25
+ ...(isObject(config.private) ? { private: config.private } : {}),
26
+ ...(isObject(contexts) ? { contexts: contexts as Record<string, RuntimeConfigContext> } : {}),
27
+ };
28
+ }
package/src/index.ts ADDED
@@ -0,0 +1,8 @@
1
+ export * from './env';
2
+ export * from './fragment';
3
+ export * from './key';
4
+ export * from './project';
5
+ export * from './resolve';
6
+ export * from './scope';
7
+ export * from './types';
8
+ export * from './validation';
package/src/key.ts ADDED
@@ -0,0 +1,54 @@
1
+ export type RuntimeConfigKeyStrategy = (scopeId: string, key: string) => string;
2
+
3
+ export type RuntimeConfigKeyOptions = {
4
+ keyStrategy?: RuntimeConfigKeyStrategy;
5
+ };
6
+
7
+ function upperFirst(input: string): string {
8
+ return input ? input[0]!.toUpperCase() + input.slice(1) : input;
9
+ }
10
+
11
+ function uncapitalize(input: string): string {
12
+ return input ? input[0]!.toLowerCase() + input.slice(1) : input;
13
+ }
14
+
15
+ function camelCase(input: string): string {
16
+ return input
17
+ .replace(/['\u2019]/g, '')
18
+ .split(/[^A-Za-z0-9]+|(?=[A-Z][a-z])/)
19
+ .map((part) => part.trim())
20
+ .filter(Boolean)
21
+ .map((part) => part.toLowerCase())
22
+ .map((part, index) => (index === 0 ? part : upperFirst(part)))
23
+ .join('');
24
+ }
25
+
26
+ export function createRuntimeConfigKey(
27
+ scopeId: string,
28
+ key: string,
29
+ options: RuntimeConfigKeyOptions = {},
30
+ ): string {
31
+ return options.keyStrategy?.(scopeId, key) ?? camelCase(`${scopeId} ${key}`);
32
+ }
33
+
34
+ export function stripRuntimeConfigScopePrefix(
35
+ scopeId: string,
36
+ configKey: string,
37
+ options: RuntimeConfigKeyOptions = {},
38
+ ): string | undefined {
39
+ const prefix = createRuntimeConfigKey(scopeId, '', options);
40
+ if (!prefix || !configKey.startsWith(prefix) || configKey.length <= prefix.length) {
41
+ return undefined;
42
+ }
43
+
44
+ return uncapitalize(configKey.slice(prefix.length));
45
+ }
46
+
47
+ export function toSnakeUpperCase(input: string): string {
48
+ return input
49
+ .replace(/([a-z0-9])([A-Z])/g, '$1_$2')
50
+ .replace(/[^a-zA-Z0-9]+/g, '_')
51
+ .replace(/_+/g, '_')
52
+ .replace(/^_|_$/g, '')
53
+ .toUpperCase();
54
+ }