@nymbalhq/config 0.0.0-stage → 1.0.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.
- package/dist/index.d.ts +1138 -0
- package/dist/index.js +277 -0
- package/dist/index.js.map +1 -0
- package/package.json +40 -3
- package/README.md +0 -3
package/dist/index.js
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
// src/define-config.ts
|
|
2
|
+
function defineConfig(config) {
|
|
3
|
+
return config;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
// src/env.ts
|
|
7
|
+
import { ConfigError } from "@nymbalhq/types";
|
|
8
|
+
function env(key, defaultValue) {
|
|
9
|
+
const value = process.env[key];
|
|
10
|
+
if (value !== void 0 && value !== "") return value;
|
|
11
|
+
if (defaultValue !== void 0) return defaultValue;
|
|
12
|
+
throw new ConfigError(`Missing required environment variable: ${key}`, {
|
|
13
|
+
context: { key }
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
function envOptional(key) {
|
|
17
|
+
const value = process.env[key];
|
|
18
|
+
return value !== void 0 && value !== "" ? value : void 0;
|
|
19
|
+
}
|
|
20
|
+
function envNumber(key, defaultValue) {
|
|
21
|
+
const raw = envOptional(key);
|
|
22
|
+
if (raw === void 0) {
|
|
23
|
+
if (defaultValue !== void 0) return defaultValue;
|
|
24
|
+
throw new ConfigError(`Missing required environment variable: ${key}`, { context: { key } });
|
|
25
|
+
}
|
|
26
|
+
const parsed = Number(raw);
|
|
27
|
+
if (!Number.isFinite(parsed)) {
|
|
28
|
+
throw new ConfigError(`Environment variable ${key} is not a valid number: ${raw}`, {
|
|
29
|
+
context: { key, raw }
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
return parsed;
|
|
33
|
+
}
|
|
34
|
+
function envBool(key, defaultValue = false) {
|
|
35
|
+
const raw = envOptional(key)?.toLowerCase();
|
|
36
|
+
if (raw === void 0) return defaultValue;
|
|
37
|
+
return raw === "true" || raw === "1" || raw === "yes" || raw === "on";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// src/known-providers.ts
|
|
41
|
+
var KNOWN_PROVIDERS = {
|
|
42
|
+
payments: ["native", "stripe", "custom"],
|
|
43
|
+
email: ["native", "smtp", "custom"],
|
|
44
|
+
reviews: ["native", "custom"],
|
|
45
|
+
search: ["native", "custom"],
|
|
46
|
+
analytics: ["native", "custom"],
|
|
47
|
+
shipping: ["native", "custom"],
|
|
48
|
+
tax: ["native", "custom"],
|
|
49
|
+
ai: ["none", "anthropic", "custom"]
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// src/load.ts
|
|
53
|
+
import { existsSync } from "fs";
|
|
54
|
+
import { dirname, isAbsolute, resolve } from "path";
|
|
55
|
+
import { pathToFileURL } from "url";
|
|
56
|
+
import { createJiti } from "jiti";
|
|
57
|
+
import { z as z2 } from "zod";
|
|
58
|
+
import { ConfigError as ConfigError2 } from "@nymbalhq/types";
|
|
59
|
+
|
|
60
|
+
// src/schema.ts
|
|
61
|
+
import { z } from "zod";
|
|
62
|
+
var rateLimitRuleSchema = z.object({
|
|
63
|
+
requests: z.number().int().positive(),
|
|
64
|
+
window: z.string().regex(/^\d+(ms|s|m|h)$/, 'window must look like "60s", "5m", "1h"'),
|
|
65
|
+
action: z.enum(["block", "challenge", "throttle"])
|
|
66
|
+
});
|
|
67
|
+
var rateLimitPathPattern = /^\/[\w\-/*:]*$/;
|
|
68
|
+
var DEFAULT_JWT_SECRET = "dev-secret-please-override-me-1234567890";
|
|
69
|
+
var storeSchema = z.object({
|
|
70
|
+
name: z.string().min(1),
|
|
71
|
+
currency: z.string().length(3),
|
|
72
|
+
locale: z.string().min(2),
|
|
73
|
+
timezone: z.string().min(1)
|
|
74
|
+
});
|
|
75
|
+
var infrastructureSchema = z.object({
|
|
76
|
+
cloud: z.enum(["local", "aws", "gcp", "azure", "docker"]),
|
|
77
|
+
commandStore: z.enum(["sqlite", "postgres"]),
|
|
78
|
+
documentStore: z.enum(["in-memory", "dynamodb", "firestore", "cosmosdb", "postgres-jsonb"]),
|
|
79
|
+
eventBus: z.enum(["in-process", "eventbridge", "pubsub", "service-bus", "redis-streams"]),
|
|
80
|
+
compute: z.enum(["in-process", "lambda", "cloud-functions", "azure-functions", "worker-threads"])
|
|
81
|
+
});
|
|
82
|
+
var securitySchema = z.object({
|
|
83
|
+
adapter: z.enum(["middleware", "cloudflare", "aws-waf", "akamai"]),
|
|
84
|
+
rateLimit: z.record(
|
|
85
|
+
z.string().regex(rateLimitPathPattern, {
|
|
86
|
+
message: 'rate limit keys must start with "/" and use only letters, numbers, -, _, /, *, :'
|
|
87
|
+
}),
|
|
88
|
+
rateLimitRuleSchema
|
|
89
|
+
).default({}),
|
|
90
|
+
botProtection: z.object({
|
|
91
|
+
mode: z.enum(["off", "managed", "strict"]).default("off"),
|
|
92
|
+
allowList: z.array(z.string()).default([])
|
|
93
|
+
}).default({ mode: "off", allowList: [] }),
|
|
94
|
+
headers: z.object({
|
|
95
|
+
hsts: z.boolean().default(true),
|
|
96
|
+
contentSecurityPolicy: z.union([z.enum(["strict", "relaxed", "off"]), z.string()]).default("strict"),
|
|
97
|
+
referrerPolicy: z.string().default("strict-origin-when-cross-origin"),
|
|
98
|
+
xFrameOptions: z.enum(["deny", "sameorigin"]).default("deny")
|
|
99
|
+
}).default({}),
|
|
100
|
+
csrf: z.boolean().default(true),
|
|
101
|
+
auth: z.object({
|
|
102
|
+
jwtSecret: z.string().min(16).default(DEFAULT_JWT_SECRET),
|
|
103
|
+
accessTtl: z.string().regex(/^\d+(ms|s|m|h|d)$/, 'TTL must look like "15m"').default("15m"),
|
|
104
|
+
refreshTtl: z.string().regex(/^\d+(ms|s|m|h|d)$/, 'TTL must look like "7d"').default("7d")
|
|
105
|
+
}).default({})
|
|
106
|
+
});
|
|
107
|
+
function adapterBlock(slot, providers) {
|
|
108
|
+
const known = providers.map((p) => `'${p}'`).join(", ");
|
|
109
|
+
return z.object({
|
|
110
|
+
provider: z.enum(providers, {
|
|
111
|
+
errorMap: (issue, ctx) => issue.code === z.ZodIssueCode.invalid_enum_value ? {
|
|
112
|
+
message: `unknown ${slot} provider '${String(issue.received)}'; the ${slot} providers are ${known}`
|
|
113
|
+
} : { message: ctx.defaultError }
|
|
114
|
+
}),
|
|
115
|
+
config: z.record(z.string(), z.unknown()).optional()
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
var commerceSchema = z.object({
|
|
119
|
+
payments: adapterBlock("payments", KNOWN_PROVIDERS.payments),
|
|
120
|
+
email: adapterBlock("email", KNOWN_PROVIDERS.email),
|
|
121
|
+
reviews: adapterBlock("reviews", KNOWN_PROVIDERS.reviews),
|
|
122
|
+
search: adapterBlock("search", KNOWN_PROVIDERS.search),
|
|
123
|
+
analytics: adapterBlock("analytics", KNOWN_PROVIDERS.analytics),
|
|
124
|
+
shipping: adapterBlock("shipping", KNOWN_PROVIDERS.shipping),
|
|
125
|
+
tax: adapterBlock("tax", KNOWN_PROVIDERS.tax),
|
|
126
|
+
ai: adapterBlock("ai", KNOWN_PROVIDERS.ai),
|
|
127
|
+
orders: z.object({
|
|
128
|
+
numberPrefix: z.string().min(1).default("NYM"),
|
|
129
|
+
numberStart: z.number().int().nonnegative().default(1e3)
|
|
130
|
+
}).default({})
|
|
131
|
+
});
|
|
132
|
+
var httpSchema = z.object({
|
|
133
|
+
adapter: z.enum(["fastify", "hono"]).default("fastify"),
|
|
134
|
+
port: z.number().int().positive().default(3001),
|
|
135
|
+
host: z.string().default("0.0.0.0"),
|
|
136
|
+
/** Whether `X-Forwarded-For` sets the client IP the rate limiter keys on; only behind a proxy you run. */
|
|
137
|
+
trustProxy: z.boolean().default(false),
|
|
138
|
+
cors: z.object({
|
|
139
|
+
allowedOrigins: z.array(z.string()).default(["http://localhost:3000", "http://localhost:4321"]),
|
|
140
|
+
credentials: z.boolean().default(true)
|
|
141
|
+
}).default({})
|
|
142
|
+
});
|
|
143
|
+
var deploymentSchema = z.object({
|
|
144
|
+
strategy: z.enum(["standard", "blue-green"]).default("standard")
|
|
145
|
+
});
|
|
146
|
+
var featuresSchema = z.object({
|
|
147
|
+
staging: z.boolean().default(false),
|
|
148
|
+
heartbeat: z.boolean().default(false),
|
|
149
|
+
autoUpdates: z.boolean().default(false)
|
|
150
|
+
});
|
|
151
|
+
var nymbalConfigSchema = z.object({
|
|
152
|
+
store: storeSchema,
|
|
153
|
+
template: z.enum(["astro", "nextjs"]),
|
|
154
|
+
infrastructure: infrastructureSchema,
|
|
155
|
+
security: securitySchema,
|
|
156
|
+
commerce: commerceSchema,
|
|
157
|
+
http: httpSchema,
|
|
158
|
+
deployment: deploymentSchema,
|
|
159
|
+
features: featuresSchema.default({
|
|
160
|
+
staging: false,
|
|
161
|
+
heartbeat: false,
|
|
162
|
+
autoUpdates: false
|
|
163
|
+
})
|
|
164
|
+
}).superRefine((cfg, ctx) => {
|
|
165
|
+
if (cfg.infrastructure.cloud === "local") {
|
|
166
|
+
if (!["in-memory", "postgres-jsonb"].includes(cfg.infrastructure.documentStore)) {
|
|
167
|
+
ctx.addIssue({
|
|
168
|
+
code: z.ZodIssueCode.custom,
|
|
169
|
+
path: ["infrastructure", "documentStore"],
|
|
170
|
+
message: `cloud=local only supports 'in-memory' or 'postgres-jsonb' for documentStore`
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
if (!["in-process", "redis-streams"].includes(cfg.infrastructure.eventBus)) {
|
|
174
|
+
ctx.addIssue({
|
|
175
|
+
code: z.ZodIssueCode.custom,
|
|
176
|
+
path: ["infrastructure", "eventBus"],
|
|
177
|
+
message: `cloud=local only supports 'in-process' or 'redis-streams' for eventBus`
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
if (!["in-process", "worker-threads"].includes(cfg.infrastructure.compute)) {
|
|
181
|
+
ctx.addIssue({
|
|
182
|
+
code: z.ZodIssueCode.custom,
|
|
183
|
+
path: ["infrastructure", "compute"],
|
|
184
|
+
message: `cloud=local only supports 'in-process' or 'worker-threads' for compute`
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
const email = cfg.commerce.email;
|
|
189
|
+
if (email.provider === "native" && email.config?.["transport"] === "smtp") {
|
|
190
|
+
ctx.addIssue({
|
|
191
|
+
code: z.ZodIssueCode.custom,
|
|
192
|
+
path: ["commerce", "email", "config", "transport"],
|
|
193
|
+
message: `the native email provider only prints to the console; for SMTP mail set email.provider to 'smtp' (@nymbalhq/adapter-smtp)`
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
if (cfg.security.adapter === "middleware") {
|
|
197
|
+
if (["aws", "gcp", "azure"].includes(cfg.infrastructure.cloud)) {
|
|
198
|
+
ctx.addIssue({
|
|
199
|
+
code: z.ZodIssueCode.custom,
|
|
200
|
+
path: ["security", "adapter"],
|
|
201
|
+
message: `security.adapter='middleware' is intended for local/docker development. Use a WAF adapter ('cloudflare', 'aws-waf', 'akamai') for cloud=${cfg.infrastructure.cloud}.`
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// src/load.ts
|
|
208
|
+
var CONFIG_FILE = "nymbal.config.ts";
|
|
209
|
+
function findConfigFile(startDir) {
|
|
210
|
+
let current = isAbsolute(startDir) ? startDir : resolve(startDir);
|
|
211
|
+
while (true) {
|
|
212
|
+
const candidate = resolve(current, CONFIG_FILE);
|
|
213
|
+
if (existsSync(candidate)) return candidate;
|
|
214
|
+
const parent = dirname(current);
|
|
215
|
+
if (parent === current) return null;
|
|
216
|
+
current = parent;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
async function loadConfig(startDir = process.cwd()) {
|
|
220
|
+
const path = findConfigFile(startDir);
|
|
221
|
+
if (!path) {
|
|
222
|
+
throw new ConfigError2(
|
|
223
|
+
`Could not find ${CONFIG_FILE} in ${startDir} or any parent directory.`,
|
|
224
|
+
{ context: { startDir } }
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
const jiti = createJiti(pathToFileURL(path).href, { interopDefault: true });
|
|
228
|
+
let raw;
|
|
229
|
+
try {
|
|
230
|
+
raw = await jiti.import(path, { default: true });
|
|
231
|
+
} catch (err) {
|
|
232
|
+
throw new ConfigError2(`Failed to load ${path}: ${err.message}`, {
|
|
233
|
+
cause: err,
|
|
234
|
+
context: { path }
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
try {
|
|
238
|
+
const config = nymbalConfigSchema.parse(raw);
|
|
239
|
+
return { config, path, projectRoot: dirname(path) };
|
|
240
|
+
} catch (err) {
|
|
241
|
+
if (err instanceof z2.ZodError) {
|
|
242
|
+
throw new ConfigError2(formatZodError(err, path), {
|
|
243
|
+
cause: err,
|
|
244
|
+
context: { path, issues: err.issues }
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
throw err;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
function formatZodError(err, path) {
|
|
251
|
+
const lines = err.issues.map((issue) => {
|
|
252
|
+
const at = issue.path.length > 0 ? issue.path.join(".") : "<root>";
|
|
253
|
+
return ` \u2022 ${at}: ${issue.message}`;
|
|
254
|
+
});
|
|
255
|
+
return `Invalid configuration in ${path}:
|
|
256
|
+
${lines.join("\n")}`;
|
|
257
|
+
}
|
|
258
|
+
export {
|
|
259
|
+
DEFAULT_JWT_SECRET,
|
|
260
|
+
KNOWN_PROVIDERS,
|
|
261
|
+
commerceSchema,
|
|
262
|
+
defineConfig,
|
|
263
|
+
deploymentSchema,
|
|
264
|
+
env,
|
|
265
|
+
envBool,
|
|
266
|
+
envNumber,
|
|
267
|
+
envOptional,
|
|
268
|
+
featuresSchema,
|
|
269
|
+
findConfigFile,
|
|
270
|
+
httpSchema,
|
|
271
|
+
infrastructureSchema,
|
|
272
|
+
loadConfig,
|
|
273
|
+
nymbalConfigSchema,
|
|
274
|
+
securitySchema,
|
|
275
|
+
storeSchema
|
|
276
|
+
};
|
|
277
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/define-config.ts","../src/env.ts","../src/known-providers.ts","../src/load.ts","../src/schema.ts"],"sourcesContent":["import type { NymbalConfigInput } from './schema.js'\n\nexport function defineConfig<T extends NymbalConfigInput>(config: T): T {\n return config\n}\n","import { ConfigError } from '@nymbalhq/types'\n\nexport function env(key: string, defaultValue?: string): string {\n const value = process.env[key]\n if (value !== undefined && value !== '') return value\n if (defaultValue !== undefined) return defaultValue\n throw new ConfigError(`Missing required environment variable: ${key}`, {\n context: { key },\n })\n}\n\nexport function envOptional(key: string): string | undefined {\n const value = process.env[key]\n return value !== undefined && value !== '' ? value : undefined\n}\n\nexport function envNumber(key: string, defaultValue?: number): number {\n const raw = envOptional(key)\n if (raw === undefined) {\n if (defaultValue !== undefined) return defaultValue\n throw new ConfigError(`Missing required environment variable: ${key}`, { context: { key } })\n }\n const parsed = Number(raw)\n if (!Number.isFinite(parsed)) {\n throw new ConfigError(`Environment variable ${key} is not a valid number: ${raw}`, {\n context: { key, raw },\n })\n }\n return parsed\n}\n\nexport function envBool(key: string, defaultValue = false): boolean {\n const raw = envOptional(key)?.toLowerCase()\n if (raw === undefined) return defaultValue\n return raw === 'true' || raw === '1' || raw === 'yes' || raw === 'on'\n}\n","import type { CommerceSlot } from '@nymbalhq/types'\n\nexport type { CommerceSlot }\n\n/**\n * The providers each commerce slot accepts. A provider that needs an SDK lives in an `@nymbalhq/adapter-*` package the\n * kernel loads only when the config names it; `custom` takes the adapter a project passes to `createApp`.\n */\nexport const KNOWN_PROVIDERS = {\n payments: ['native', 'stripe', 'custom'],\n email: ['native', 'smtp', 'custom'],\n reviews: ['native', 'custom'],\n search: ['native', 'custom'],\n analytics: ['native', 'custom'],\n shipping: ['native', 'custom'],\n tax: ['native', 'custom'],\n ai: ['none', 'anthropic', 'custom'],\n} as const satisfies Readonly<Record<CommerceSlot, readonly [string, ...string[]]>>\n","import { existsSync } from 'node:fs'\nimport { dirname, isAbsolute, resolve } from 'node:path'\nimport { pathToFileURL } from 'node:url'\nimport { createJiti } from 'jiti'\nimport { z } from 'zod'\nimport { ConfigError } from '@nymbalhq/types'\nimport { nymbalConfigSchema, type NymbalConfig } from './schema.js'\n\nconst CONFIG_FILE = 'nymbal.config.ts'\n\nexport interface LoadedConfig {\n config: NymbalConfig\n path: string\n projectRoot: string\n}\n\nexport function findConfigFile(startDir: string): string | null {\n let current = isAbsolute(startDir) ? startDir : resolve(startDir)\n while (true) {\n const candidate = resolve(current, CONFIG_FILE)\n if (existsSync(candidate)) return candidate\n const parent = dirname(current)\n if (parent === current) return null\n current = parent\n }\n}\n\nexport async function loadConfig(startDir: string = process.cwd()): Promise<LoadedConfig> {\n const path = findConfigFile(startDir)\n if (!path) {\n throw new ConfigError(\n `Could not find ${CONFIG_FILE} in ${startDir} or any parent directory.`,\n { context: { startDir } },\n )\n }\n\n const jiti = createJiti(pathToFileURL(path).href, { interopDefault: true })\n let raw: unknown\n try {\n raw = await jiti.import(path, { default: true })\n } catch (err) {\n throw new ConfigError(`Failed to load ${path}: ${(err as Error).message}`, {\n cause: err,\n context: { path },\n })\n }\n\n try {\n const config = nymbalConfigSchema.parse(raw)\n return { config, path, projectRoot: dirname(path) }\n } catch (err) {\n if (err instanceof z.ZodError) {\n throw new ConfigError(formatZodError(err, path), {\n cause: err,\n context: { path, issues: err.issues },\n })\n }\n throw err\n }\n}\n\nfunction formatZodError(err: z.ZodError, path: string): string {\n const lines = err.issues.map((issue) => {\n const at = issue.path.length > 0 ? issue.path.join('.') : '<root>'\n return ` • ${at}: ${issue.message}`\n })\n return `Invalid configuration in ${path}:\\n${lines.join('\\n')}`\n}\n","import { z } from 'zod'\nimport { KNOWN_PROVIDERS, type CommerceSlot } from './known-providers.js'\n\nconst rateLimitRuleSchema = z.object({\n requests: z.number().int().positive(),\n window: z.string().regex(/^\\d+(ms|s|m|h)$/, 'window must look like \"60s\", \"5m\", \"1h\"'),\n action: z.enum(['block', 'challenge', 'throttle']),\n})\n\nconst rateLimitPathPattern = /^\\/[\\w\\-/*:]*$/\n\n/** The schema's JWT secret when none is configured: fine for development, refused in production. */\nexport const DEFAULT_JWT_SECRET = 'dev-secret-please-override-me-1234567890'\n\nexport const storeSchema = z.object({\n name: z.string().min(1),\n currency: z.string().length(3),\n locale: z.string().min(2),\n timezone: z.string().min(1),\n})\n\nexport const infrastructureSchema = z.object({\n cloud: z.enum(['local', 'aws', 'gcp', 'azure', 'docker']),\n commandStore: z.enum(['sqlite', 'postgres']),\n documentStore: z.enum(['in-memory', 'dynamodb', 'firestore', 'cosmosdb', 'postgres-jsonb']),\n eventBus: z.enum(['in-process', 'eventbridge', 'pubsub', 'service-bus', 'redis-streams']),\n compute: z.enum(['in-process', 'lambda', 'cloud-functions', 'azure-functions', 'worker-threads']),\n})\n\nexport const securitySchema = z.object({\n adapter: z.enum(['middleware', 'cloudflare', 'aws-waf', 'akamai']),\n rateLimit: z\n .record(\n z.string().regex(rateLimitPathPattern, {\n message: 'rate limit keys must start with \"/\" and use only letters, numbers, -, _, /, *, :',\n }),\n rateLimitRuleSchema,\n )\n .default({}),\n botProtection: z\n .object({\n mode: z.enum(['off', 'managed', 'strict']).default('off'),\n allowList: z.array(z.string()).default([]),\n })\n .default({ mode: 'off', allowList: [] }),\n headers: z\n .object({\n hsts: z.boolean().default(true),\n contentSecurityPolicy: z\n .union([z.enum(['strict', 'relaxed', 'off']), z.string()])\n .default('strict'),\n referrerPolicy: z.string().default('strict-origin-when-cross-origin'),\n xFrameOptions: z.enum(['deny', 'sameorigin']).default('deny'),\n })\n .default({}),\n csrf: z.boolean().default(true),\n auth: z\n .object({\n jwtSecret: z.string().min(16).default(DEFAULT_JWT_SECRET),\n accessTtl: z\n .string()\n .regex(/^\\d+(ms|s|m|h|d)$/, 'TTL must look like \"15m\"')\n .default('15m'),\n refreshTtl: z\n .string()\n .regex(/^\\d+(ms|s|m|h|d)$/, 'TTL must look like \"7d\"')\n .default('7d'),\n })\n .default({}),\n})\n\n/** A slot's block: its provider must be one the slot knows, and an unknown one is named with the slot's list. */\nfunction adapterBlock<const P extends readonly [string, ...string[]]>(\n slot: CommerceSlot,\n providers: P,\n) {\n const known = providers.map((p) => `'${p}'`).join(', ')\n return z.object({\n provider: z.enum(providers, {\n errorMap: (issue, ctx) =>\n issue.code === z.ZodIssueCode.invalid_enum_value\n ? {\n message: `unknown ${slot} provider '${String(issue.received)}'; the ${slot} providers are ${known}`,\n }\n : { message: ctx.defaultError },\n }),\n config: z.record(z.string(), z.unknown()).optional(),\n })\n}\n\nexport const commerceSchema = z.object({\n payments: adapterBlock('payments', KNOWN_PROVIDERS.payments),\n email: adapterBlock('email', KNOWN_PROVIDERS.email),\n reviews: adapterBlock('reviews', KNOWN_PROVIDERS.reviews),\n search: adapterBlock('search', KNOWN_PROVIDERS.search),\n analytics: adapterBlock('analytics', KNOWN_PROVIDERS.analytics),\n shipping: adapterBlock('shipping', KNOWN_PROVIDERS.shipping),\n tax: adapterBlock('tax', KNOWN_PROVIDERS.tax),\n ai: adapterBlock('ai', KNOWN_PROVIDERS.ai),\n orders: z\n .object({\n numberPrefix: z.string().min(1).default('NYM'),\n numberStart: z.number().int().nonnegative().default(1000),\n })\n .default({}),\n})\n\nexport const httpSchema = z.object({\n adapter: z.enum(['fastify', 'hono']).default('fastify'),\n port: z.number().int().positive().default(3001),\n host: z.string().default('0.0.0.0'),\n /** Whether `X-Forwarded-For` sets the client IP the rate limiter keys on; only behind a proxy you run. */\n trustProxy: z.boolean().default(false),\n cors: z\n .object({\n allowedOrigins: z\n .array(z.string())\n .default(['http://localhost:3000', 'http://localhost:4321']),\n credentials: z.boolean().default(true),\n })\n .default({}),\n})\n\nexport const deploymentSchema = z.object({\n strategy: z.enum(['standard', 'blue-green']).default('standard'),\n})\n\nexport const featuresSchema = z.object({\n staging: z.boolean().default(false),\n heartbeat: z.boolean().default(false),\n autoUpdates: z.boolean().default(false),\n})\n\nexport const nymbalConfigSchema = z\n .object({\n store: storeSchema,\n template: z.enum(['astro', 'nextjs']),\n infrastructure: infrastructureSchema,\n security: securitySchema,\n commerce: commerceSchema,\n http: httpSchema,\n deployment: deploymentSchema,\n features: featuresSchema.default({\n staging: false,\n heartbeat: false,\n autoUpdates: false,\n }),\n })\n .superRefine((cfg, ctx) => {\n // Local infrastructure restricts which document-store / event-bus options make sense.\n if (cfg.infrastructure.cloud === 'local') {\n if (!['in-memory', 'postgres-jsonb'].includes(cfg.infrastructure.documentStore)) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['infrastructure', 'documentStore'],\n message: `cloud=local only supports 'in-memory' or 'postgres-jsonb' for documentStore`,\n })\n }\n if (!['in-process', 'redis-streams'].includes(cfg.infrastructure.eventBus)) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['infrastructure', 'eventBus'],\n message: `cloud=local only supports 'in-process' or 'redis-streams' for eventBus`,\n })\n }\n if (!['in-process', 'worker-threads'].includes(cfg.infrastructure.compute)) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['infrastructure', 'compute'],\n message: `cloud=local only supports 'in-process' or 'worker-threads' for compute`,\n })\n }\n }\n\n // SMTP left the kernel for @nymbalhq/adapter-smtp; a native block still asking for it would print to the console.\n const email = cfg.commerce.email\n if (email.provider === 'native' && email.config?.['transport'] === 'smtp') {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['commerce', 'email', 'config', 'transport'],\n message: `the native email provider only prints to the console; for SMTP mail set email.provider to 'smtp' (@nymbalhq/adapter-smtp)`,\n })\n }\n\n // middleware security adapter is v0.1 and not intended for managed-cloud production deployments.\n if (cfg.security.adapter === 'middleware') {\n if (['aws', 'gcp', 'azure'].includes(cfg.infrastructure.cloud)) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['security', 'adapter'],\n message: `security.adapter='middleware' is intended for local/docker development. Use a WAF adapter ('cloudflare', 'aws-waf', 'akamai') for cloud=${cfg.infrastructure.cloud}.`,\n })\n }\n }\n })\n\nexport type NymbalConfigInput = z.input<typeof nymbalConfigSchema>\nexport type NymbalConfig = z.output<typeof nymbalConfigSchema>\n"],"mappings":"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","names":["z","ConfigError","ConfigError","z"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,43 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nymbalhq/config",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Typed configuration surface for Nymbal",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/nymbalhq/nymbal.git",
|
|
9
|
+
"directory": "packages/config"
|
|
10
|
+
},
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
14
|
+
"type": "module",
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=22"
|
|
17
|
+
},
|
|
18
|
+
"main": "./dist/index.js",
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"import": "./dist/index.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist"
|
|
28
|
+
],
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"jiti": "^2.4.0",
|
|
31
|
+
"zod": "^3.23.8",
|
|
32
|
+
"@nymbalhq/types": "1.0.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"tsup": "^8.3.5",
|
|
36
|
+
"typescript": "^5.6.3"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsup",
|
|
40
|
+
"dev": "tsup --watch",
|
|
41
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
42
|
+
}
|
|
6
43
|
}
|
package/README.md
DELETED