@b9g/platform 0.1.11 → 0.1.12

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/src/config.js DELETED
@@ -1,641 +0,0 @@
1
- /// <reference types="./config.d.ts" />
2
- import "../chunk-P57PW2II.js";
3
-
4
- // src/config.ts
5
- import { readFileSync } from "fs";
6
- import { resolve } from "path";
7
- import { Cache } from "@b9g/cache";
8
- import { configure } from "@logtape/logtape";
9
- function getEnv() {
10
- if (typeof import.meta !== "undefined" && import.meta.env) {
11
- return import.meta.env;
12
- }
13
- if (typeof process !== "undefined" && process.env) {
14
- return process.env;
15
- }
16
- return {};
17
- }
18
- var Tokenizer = class {
19
- #input;
20
- #pos;
21
- constructor(input) {
22
- this.#input = input;
23
- this.#pos = 0;
24
- }
25
- #peek() {
26
- return this.#input[this.#pos] || "";
27
- }
28
- #advance() {
29
- return this.#input[this.#pos++] || "";
30
- }
31
- #skipWhitespace() {
32
- while (/\s/.test(this.#peek())) {
33
- this.#advance();
34
- }
35
- }
36
- next() {
37
- this.#skipWhitespace();
38
- const start = this.#pos;
39
- const ch = this.#peek();
40
- if (!ch) {
41
- return { type: "EOF" /* EOF */, value: null, start, end: start };
42
- }
43
- if (ch === '"') {
44
- this.#advance();
45
- let value = "";
46
- while (this.#peek() && this.#peek() !== '"') {
47
- if (this.#peek() === "\\") {
48
- this.#advance();
49
- const next = this.#advance();
50
- if (next === "n")
51
- value += "\n";
52
- else if (next === "t")
53
- value += " ";
54
- else
55
- value += next;
56
- } else {
57
- value += this.#advance();
58
- }
59
- }
60
- if (this.#peek() !== '"') {
61
- throw new Error(`Unterminated string at position ${start}`);
62
- }
63
- this.#advance();
64
- return { type: "STRING" /* STRING */, value, start, end: this.#pos };
65
- }
66
- if (/\d/.test(ch)) {
67
- let value = "";
68
- while (/\d/.test(this.#peek())) {
69
- value += this.#advance();
70
- }
71
- return {
72
- type: "NUMBER" /* NUMBER */,
73
- value: parseInt(value, 10),
74
- start,
75
- end: this.#pos
76
- };
77
- }
78
- if (ch === "=" && this.#input[this.#pos + 1] === "=" && this.#input[this.#pos + 2] === "=") {
79
- this.#pos += 3;
80
- return { type: "===" /* EQ_STRICT */, value: "===", start, end: this.#pos };
81
- }
82
- if (ch === "!" && this.#input[this.#pos + 1] === "=" && this.#input[this.#pos + 2] === "=") {
83
- this.#pos += 3;
84
- return { type: "!==" /* NE_STRICT */, value: "!==", start, end: this.#pos };
85
- }
86
- if (ch === "=" && this.#input[this.#pos + 1] === "=") {
87
- this.#pos += 2;
88
- return { type: "==" /* EQ */, value: "==", start, end: this.#pos };
89
- }
90
- if (ch === "!" && this.#input[this.#pos + 1] === "=") {
91
- this.#pos += 2;
92
- return { type: "!=" /* NE */, value: "!=", start, end: this.#pos };
93
- }
94
- if (ch === "|" && this.#input[this.#pos + 1] === "|") {
95
- this.#pos += 2;
96
- return { type: "||" /* OR */, value: "||", start, end: this.#pos };
97
- }
98
- if (ch === "&" && this.#input[this.#pos + 1] === "&") {
99
- this.#pos += 2;
100
- return { type: "&&" /* AND */, value: "&&", start, end: this.#pos };
101
- }
102
- if (ch === "?") {
103
- this.#advance();
104
- return { type: "?" /* QUESTION */, value: "?", start, end: this.#pos };
105
- }
106
- if (ch === "!") {
107
- this.#advance();
108
- return { type: "!" /* NOT */, value: "!", start, end: this.#pos };
109
- }
110
- if (ch === "(") {
111
- this.#advance();
112
- return { type: "(" /* LPAREN */, value: "(", start, end: this.#pos };
113
- }
114
- if (ch === ")") {
115
- this.#advance();
116
- return { type: ")" /* RPAREN */, value: ")", start, end: this.#pos };
117
- }
118
- if (ch === ":") {
119
- const next = this.#input[this.#pos + 1];
120
- if (next !== "/" && !/\d/.test(next)) {
121
- this.#advance();
122
- return { type: ":" /* COLON */, value: ":", start, end: this.#pos };
123
- }
124
- }
125
- if (/\S/.test(ch) && !/[?!()=|&]/.test(ch)) {
126
- let value = "";
127
- while (/\S/.test(this.#peek()) && !/[?!()=|&]/.test(this.#peek())) {
128
- if (this.#peek() === ":") {
129
- const next = this.#input[this.#pos + 1];
130
- if (next !== "/" && !/\d/.test(next)) {
131
- break;
132
- }
133
- }
134
- value += this.#advance();
135
- }
136
- if (value === "true")
137
- return { type: "TRUE" /* TRUE */, value: true, start, end: this.#pos };
138
- if (value === "false")
139
- return { type: "FALSE" /* FALSE */, value: false, start, end: this.#pos };
140
- if (value === "null")
141
- return { type: "NULL" /* NULL */, value: null, start, end: this.#pos };
142
- if (value === "undefined")
143
- return {
144
- type: "UNDEFINED" /* UNDEFINED */,
145
- value: void 0,
146
- start,
147
- end: this.#pos
148
- };
149
- return { type: "IDENTIFIER" /* IDENTIFIER */, value, start, end: this.#pos };
150
- }
151
- throw new Error(`Unexpected character '${ch}' at position ${start}`);
152
- }
153
- };
154
- var Parser = class {
155
- #tokens;
156
- #pos;
157
- #env;
158
- #strict;
159
- constructor(input, env, strict) {
160
- const tokenizer = new Tokenizer(input);
161
- this.#tokens = [];
162
- let token;
163
- do {
164
- token = tokenizer.next();
165
- this.#tokens.push(token);
166
- } while (token.type !== "EOF" /* EOF */);
167
- this.#pos = 0;
168
- this.#env = env;
169
- this.#strict = strict;
170
- }
171
- #peek() {
172
- return this.#tokens[this.#pos];
173
- }
174
- #advance() {
175
- return this.#tokens[this.#pos++];
176
- }
177
- #expect(type) {
178
- const token = this.#peek();
179
- if (token.type !== type) {
180
- throw new Error(
181
- `Expected ${type} but got ${token.type} at position ${token.start}`
182
- );
183
- }
184
- return this.#advance();
185
- }
186
- parse() {
187
- const result = this.#parseExpr();
188
- this.#expect("EOF" /* EOF */);
189
- return result;
190
- }
191
- // Expr := Ternary
192
- #parseExpr() {
193
- return this.#parseTernary();
194
- }
195
- // Ternary := LogicalOr ('?' Expr ':' Expr)?
196
- #parseTernary() {
197
- let left = this.#parseLogicalOr();
198
- if (this.#peek().type === "?" /* QUESTION */) {
199
- this.#advance();
200
- const trueBranch = this.#parseExpr();
201
- this.#expect(":" /* COLON */);
202
- const falseBranch = this.#parseExpr();
203
- return left ? trueBranch : falseBranch;
204
- }
205
- return left;
206
- }
207
- // LogicalOr := LogicalAnd ('||' LogicalAnd)*
208
- #parseLogicalOr() {
209
- let left = this.#parseLogicalAnd();
210
- while (this.#peek().type === "||" /* OR */) {
211
- this.#advance();
212
- const right = this.#parseLogicalAnd();
213
- left = left || right;
214
- }
215
- return left;
216
- }
217
- // LogicalAnd := Equality ('&&' Equality)*
218
- #parseLogicalAnd() {
219
- let left = this.#parseEquality();
220
- while (this.#peek().type === "&&" /* AND */) {
221
- this.#advance();
222
- const right = this.#parseEquality();
223
- left = left && right;
224
- }
225
- return left;
226
- }
227
- // Equality := Unary (('===' | '!==' | '==' | '!=') Unary)*
228
- #parseEquality() {
229
- let left = this.#parseUnary();
230
- while (true) {
231
- const token = this.#peek();
232
- if (token.type === "===" /* EQ_STRICT */) {
233
- this.#advance();
234
- const right = this.#parseUnary();
235
- left = left === right;
236
- } else if (token.type === "!==" /* NE_STRICT */) {
237
- this.#advance();
238
- const right = this.#parseUnary();
239
- left = left !== right;
240
- } else if (token.type === "==" /* EQ */) {
241
- this.#advance();
242
- const right = this.#parseUnary();
243
- left = left == right;
244
- } else if (token.type === "!=" /* NE */) {
245
- this.#advance();
246
- const right = this.#parseUnary();
247
- left = left != right;
248
- } else {
249
- break;
250
- }
251
- }
252
- return left;
253
- }
254
- // Unary := '!' Unary | Primary
255
- #parseUnary() {
256
- if (this.#peek().type === "!" /* NOT */) {
257
- this.#advance();
258
- return !this.#parseUnary();
259
- }
260
- return this.#parsePrimary();
261
- }
262
- // Primary := EnvVar | Literal | '(' Expr ')'
263
- #parsePrimary() {
264
- const token = this.#peek();
265
- if (token.type === "(" /* LPAREN */) {
266
- this.#advance();
267
- const value = this.#parseExpr();
268
- this.#expect(")" /* RPAREN */);
269
- return value;
270
- }
271
- if (token.type === "STRING" /* STRING */) {
272
- this.#advance();
273
- return token.value;
274
- }
275
- if (token.type === "NUMBER" /* NUMBER */) {
276
- this.#advance();
277
- return token.value;
278
- }
279
- if (token.type === "TRUE" /* TRUE */) {
280
- this.#advance();
281
- return true;
282
- }
283
- if (token.type === "FALSE" /* FALSE */) {
284
- this.#advance();
285
- return false;
286
- }
287
- if (token.type === "NULL" /* NULL */) {
288
- this.#advance();
289
- return null;
290
- }
291
- if (token.type === "UNDEFINED" /* UNDEFINED */) {
292
- this.#advance();
293
- return void 0;
294
- }
295
- if (token.type === "IDENTIFIER" /* IDENTIFIER */) {
296
- this.#advance();
297
- const name = token.value;
298
- if (/^[A-Z][A-Z0-9_]*$/.test(name)) {
299
- const value = this.#env[name];
300
- if (this.#strict && value === void 0) {
301
- throw new Error(
302
- `Undefined environment variable: ${name}
303
- Fix:
304
- 1. Set the env var: export ${name}=value
305
- 2. Add a fallback: ${name} || defaultValue
306
- 3. Add null check: ${name} == null ? ... : ...
307
- 4. Use empty string for falsy: export ${name}=""`
308
- );
309
- }
310
- if (typeof value === "string" && /^\d+$/.test(value)) {
311
- return parseInt(value, 10);
312
- }
313
- return value;
314
- }
315
- return name;
316
- }
317
- throw new Error(
318
- `Unexpected token ${token.type} at position ${token.start}`
319
- );
320
- }
321
- };
322
- function parseConfigExpr(expr, env = getEnv(), options = {}) {
323
- const strict = options.strict !== false;
324
- try {
325
- const parser = new Parser(expr, env, strict);
326
- return parser.parse();
327
- } catch (error) {
328
- throw new Error(
329
- `Invalid config expression: ${expr}
330
- Error: ${error instanceof Error ? error.message : String(error)}`
331
- );
332
- }
333
- }
334
- function processConfigValue(value, env = getEnv(), options = {}) {
335
- if (typeof value === "string") {
336
- if (/(\|\||&&|===|!==|==|!=|[?:!]|^[A-Z][A-Z0-9_]*$)/.test(value)) {
337
- return parseConfigExpr(value, env, options);
338
- }
339
- return value;
340
- }
341
- if (Array.isArray(value)) {
342
- return value.map((item) => processConfigValue(item, env, options));
343
- }
344
- if (value !== null && typeof value === "object") {
345
- const processed = {};
346
- for (const [key, val] of Object.entries(value)) {
347
- processed[key] = processConfigValue(val, env, options);
348
- }
349
- return processed;
350
- }
351
- return value;
352
- }
353
- function matchPattern(name, config) {
354
- if (config[name]) {
355
- return config[name];
356
- }
357
- const patterns = [];
358
- for (const [pattern, cfg] of Object.entries(config)) {
359
- if (pattern === "*")
360
- continue;
361
- if (pattern.endsWith("*")) {
362
- const prefix = pattern.slice(0, -1);
363
- if (name.startsWith(prefix)) {
364
- patterns.push({
365
- pattern,
366
- config: cfg,
367
- prefixLength: prefix.length
368
- });
369
- }
370
- }
371
- }
372
- if (patterns.length > 0) {
373
- patterns.sort((a, b) => b.prefixLength - a.prefixLength);
374
- return patterns[0].config;
375
- }
376
- return config["*"];
377
- }
378
- function loadConfig(cwd) {
379
- const env = getEnv();
380
- let rawConfig = {};
381
- try {
382
- const shovelPath = `${cwd}/shovel.json`;
383
- const content = readFileSync(shovelPath, "utf-8");
384
- rawConfig = JSON.parse(content);
385
- } catch (error) {
386
- try {
387
- const pkgPath = `${cwd}/package.json`;
388
- const content = readFileSync(pkgPath, "utf-8");
389
- const pkgJSON = JSON.parse(content);
390
- rawConfig = pkgJSON.shovel || {};
391
- } catch (error2) {
392
- }
393
- }
394
- const processed = processConfigValue(rawConfig, env, {
395
- strict: true
396
- });
397
- const defaultSinks = [{ provider: "console" }];
398
- const config = {
399
- platform: processed.platform,
400
- port: typeof processed.port === "number" ? processed.port : 3e3,
401
- host: processed.host || "localhost",
402
- workers: typeof processed.workers === "number" ? processed.workers : 1,
403
- logging: {
404
- level: processed.logging?.level || "info",
405
- sinks: processed.logging?.sinks || defaultSinks,
406
- categories: processed.logging?.categories || {}
407
- },
408
- caches: processed.caches || {},
409
- buckets: processed.buckets || {}
410
- };
411
- return config;
412
- }
413
- var SHOVEL_CATEGORIES = [
414
- "cli",
415
- "watcher",
416
- "worker",
417
- "single-threaded",
418
- "assets",
419
- "platform-node",
420
- "platform-bun",
421
- "platform-cloudflare",
422
- "cache",
423
- "cache-redis",
424
- "router"
425
- ];
426
- var BUILTIN_SINK_PROVIDERS = {
427
- console: { module: "@logtape/logtape", factory: "getConsoleSink" },
428
- file: { module: "@logtape/file", factory: "getFileSink" },
429
- rotating: { module: "@logtape/file", factory: "getRotatingFileSink" },
430
- "stream-file": { module: "@logtape/file", factory: "getStreamFileSink" },
431
- otel: { module: "@logtape/otel", factory: "getOpenTelemetrySink" },
432
- sentry: { module: "@logtape/sentry", factory: "getSentrySink" },
433
- syslog: { module: "@logtape/syslog", factory: "getSyslogSink" },
434
- cloudwatch: {
435
- module: "@logtape/cloudwatch-logs",
436
- factory: "getCloudWatchLogsSink"
437
- }
438
- };
439
- async function createSink(config, options = {}) {
440
- const { provider, ...sinkOptions } = config;
441
- if (sinkOptions.path && options.cwd) {
442
- sinkOptions.path = resolve(options.cwd, sinkOptions.path);
443
- }
444
- const builtin = BUILTIN_SINK_PROVIDERS[provider];
445
- const modulePath = builtin?.module || provider;
446
- const factoryName = builtin?.factory || "default";
447
- const module = await import(modulePath);
448
- const factory = module[factoryName] || module.default;
449
- if (!factory) {
450
- throw new Error(
451
- `Sink module "${modulePath}" has no export "${factoryName}"`
452
- );
453
- }
454
- return factory(sinkOptions);
455
- }
456
- async function configureLogging(loggingConfig, options = {}) {
457
- const { level, sinks: defaultSinkConfigs, categories } = loggingConfig;
458
- const reset = options.reset !== false;
459
- const allSinkConfigs = /* @__PURE__ */ new Map();
460
- const sinkNameMap = /* @__PURE__ */ new Map();
461
- for (let i = 0; i < defaultSinkConfigs.length; i++) {
462
- const config = defaultSinkConfigs[i];
463
- const name = `sink_${i}`;
464
- allSinkConfigs.set(name, config);
465
- sinkNameMap.set(config, name);
466
- }
467
- let sinkIndex = defaultSinkConfigs.length;
468
- for (const [_, categoryConfig] of Object.entries(categories)) {
469
- if (categoryConfig.sinks) {
470
- for (const config of categoryConfig.sinks) {
471
- let found = false;
472
- for (const [existingConfig, _name] of sinkNameMap) {
473
- if (JSON.stringify(existingConfig) === JSON.stringify(config)) {
474
- found = true;
475
- break;
476
- }
477
- }
478
- if (!found) {
479
- const name = `sink_${sinkIndex++}`;
480
- allSinkConfigs.set(name, config);
481
- sinkNameMap.set(config, name);
482
- }
483
- }
484
- }
485
- }
486
- const sinks = {};
487
- for (const [name, config] of allSinkConfigs) {
488
- sinks[name] = await createSink(config, { cwd: options.cwd });
489
- }
490
- const getSinkNames = (configs) => {
491
- return configs.map((config) => {
492
- for (const [existingConfig, name] of sinkNameMap) {
493
- if (JSON.stringify(existingConfig) === JSON.stringify(config)) {
494
- return name;
495
- }
496
- }
497
- return "";
498
- }).filter(Boolean);
499
- };
500
- const defaultSinkNames = getSinkNames(defaultSinkConfigs);
501
- const loggers = SHOVEL_CATEGORIES.map((category) => {
502
- const categoryConfig = categories[category];
503
- const categoryLevel = categoryConfig?.level || level;
504
- const categorySinks = categoryConfig?.sinks ? getSinkNames(categoryConfig.sinks) : defaultSinkNames;
505
- return {
506
- category: [category],
507
- level: categoryLevel,
508
- sinks: categorySinks
509
- };
510
- });
511
- loggers.push({
512
- category: ["logtape", "meta"],
513
- level: "warning",
514
- sinks: []
515
- });
516
- await configure({
517
- reset,
518
- sinks,
519
- loggers
520
- });
521
- }
522
- function getCacheConfig(config, name) {
523
- return matchPattern(name, config.caches) || {};
524
- }
525
- function getBucketConfig(config, name) {
526
- return matchPattern(name, config.buckets) || {};
527
- }
528
- var WELL_KNOWN_BUCKET_PATHS = {
529
- static: (baseDir) => resolve(baseDir, "../static"),
530
- server: (baseDir) => baseDir
531
- };
532
- var BUILTIN_BUCKET_PROVIDERS = {
533
- node: "@b9g/filesystem/node.js",
534
- memory: "@b9g/filesystem/memory.js",
535
- s3: "@b9g/filesystem-s3"
536
- };
537
- function createBucketFactory(options) {
538
- const { baseDir, config } = options;
539
- return async (name) => {
540
- const bucketConfig = config ? getBucketConfig(config, name) : {};
541
- let bucketPath;
542
- if (bucketConfig.path) {
543
- bucketPath = String(bucketConfig.path);
544
- } else if (WELL_KNOWN_BUCKET_PATHS[name]) {
545
- bucketPath = WELL_KNOWN_BUCKET_PATHS[name](baseDir);
546
- } else {
547
- bucketPath = resolve(baseDir, `../${name}`);
548
- }
549
- const provider = String(bucketConfig.provider || "node");
550
- const modulePath = BUILTIN_BUCKET_PROVIDERS[provider] || provider;
551
- if (modulePath === "@b9g/filesystem/node.js") {
552
- const { NodeBucket } = await import("@b9g/filesystem/node.js");
553
- return new NodeBucket(bucketPath);
554
- }
555
- if (modulePath === "@b9g/filesystem/memory.js") {
556
- const { MemoryBucket } = await import("@b9g/filesystem/memory.js");
557
- return new MemoryBucket(name);
558
- }
559
- try {
560
- const module = await import(modulePath);
561
- const BucketClass = module.default || // Default export
562
- module.S3Bucket || // Named export for s3
563
- module.Bucket || // Generic Bucket export
564
- Object.values(module).find(
565
- (v) => typeof v === "function" && v.name?.includes("Bucket")
566
- );
567
- if (!BucketClass) {
568
- throw new Error(
569
- `Bucket module "${modulePath}" does not export a valid bucket class. Expected a default export or named export (S3Bucket, Bucket).`
570
- );
571
- }
572
- const { provider: _, path: __, ...bucketOptions } = bucketConfig;
573
- return new BucketClass(name, { path: bucketPath, ...bucketOptions });
574
- } catch (error) {
575
- if (error.code === "ERR_MODULE_NOT_FOUND" || error.code === "MODULE_NOT_FOUND") {
576
- throw new Error(
577
- `Bucket provider "${provider}" not found. Make sure the module "${modulePath}" is installed.
578
- For S3: npm install @b9g/filesystem-s3`
579
- );
580
- }
581
- throw error;
582
- }
583
- };
584
- }
585
- var BUILTIN_CACHE_PROVIDERS = {
586
- memory: "@b9g/cache/memory.js",
587
- redis: "@b9g/cache-redis"
588
- };
589
- function createCacheFactory(options = {}) {
590
- const { config, defaultProvider = "memory" } = options;
591
- return async (name) => {
592
- const cacheConfig = config ? getCacheConfig(config, name) : {};
593
- const provider = String(cacheConfig.provider || defaultProvider);
594
- if (provider === "cloudflare") {
595
- const nativeCaches = globalThis.__cloudflareCaches ?? globalThis.caches;
596
- if (!nativeCaches) {
597
- throw new Error(
598
- "Cloudflare cache provider requires native caches API. This provider only works in Cloudflare Workers environment."
599
- );
600
- }
601
- return nativeCaches.open(name);
602
- }
603
- const { provider: _, ...cacheOptions } = cacheConfig;
604
- const modulePath = BUILTIN_CACHE_PROVIDERS[provider] || provider;
605
- try {
606
- const module = await import(modulePath);
607
- const CacheClass = module.default || // Default export
608
- module.RedisCache || // Named export for redis
609
- module.MemoryCache || // Named export for memory
610
- module.Cache || // Generic Cache export
611
- Object.values(module).find(
612
- (v) => typeof v === "function" && v.prototype instanceof Cache
613
- );
614
- if (!CacheClass) {
615
- throw new Error(
616
- `Cache module "${modulePath}" does not export a valid cache class. Expected a default export or named export (RedisCache, Cache) that extends Cache.`
617
- );
618
- }
619
- return new CacheClass(name, cacheOptions);
620
- } catch (error) {
621
- if (error.code === "ERR_MODULE_NOT_FOUND" || error.code === "MODULE_NOT_FOUND") {
622
- throw new Error(
623
- `Cache provider "${provider}" not found. Make sure the module "${modulePath}" is installed.
624
- For redis: npm install @b9g/cache-redis`
625
- );
626
- }
627
- throw error;
628
- }
629
- };
630
- }
631
- export {
632
- configureLogging,
633
- createBucketFactory,
634
- createCacheFactory,
635
- getBucketConfig,
636
- getCacheConfig,
637
- loadConfig,
638
- matchPattern,
639
- parseConfigExpr,
640
- processConfigValue
641
- };