@bakery-framework/core 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.
Files changed (91) hide show
  1. package/LICENSE +19 -0
  2. package/README.md +89 -0
  3. package/package.json +69 -0
  4. package/src/cache/index.ts +8 -0
  5. package/src/cache/lru.ts +41 -0
  6. package/src/cache/shared-db.ts +51 -0
  7. package/src/cache/string.ts +150 -0
  8. package/src/cache/tiered.ts +493 -0
  9. package/src/client/globals.d.ts +74 -0
  10. package/src/client/livereload.ts +437 -0
  11. package/src/client/utils.ts +315 -0
  12. package/src/compiler/compiler.ts +263 -0
  13. package/src/compiler/dev-service.ts +660 -0
  14. package/src/compiler/index.ts +2 -0
  15. package/src/compiler/prompt-tracker.ts +36 -0
  16. package/src/compiler/tsconfig-sync.ts +71 -0
  17. package/src/core/bakery.ts +96 -0
  18. package/src/core/cache-version.ts +119 -0
  19. package/src/core/config.ts +296 -0
  20. package/src/core/context.ts +121 -0
  21. package/src/core/index.ts +61 -0
  22. package/src/core/init.ts +90 -0
  23. package/src/core/jsx.ts +152 -0
  24. package/src/core/paths.ts +24 -0
  25. package/src/core/plugins.ts +120 -0
  26. package/src/core/port.ts +73 -0
  27. package/src/global.d.ts +374 -0
  28. package/src/handlers/assets/google-font.ts +225 -0
  29. package/src/handlers/assets/image.ts +136 -0
  30. package/src/handlers/assets/nm.ts +73 -0
  31. package/src/handlers/assets/public.ts +17 -0
  32. package/src/handlers/assets/static.ts +86 -0
  33. package/src/handlers/assets/ts.ts +61 -0
  34. package/src/handlers/assets/tsx.ts +106 -0
  35. package/src/handlers/assets/virtual-asset.ts +104 -0
  36. package/src/handlers/core/$base.ts +256 -0
  37. package/src/handlers/core/$dynamic.ts +285 -0
  38. package/src/handlers/core/$error.ts +301 -0
  39. package/src/handlers/core/$middleware.ts +71 -0
  40. package/src/handlers/core/$mounts.ts +84 -0
  41. package/src/handlers/core/$registry.ts +153 -0
  42. package/src/handlers/core/$routing.ts +205 -0
  43. package/src/handlers/core/$static.ts +100 -0
  44. package/src/handlers/core/$websocket.ts +52 -0
  45. package/src/handlers/index.ts +21 -0
  46. package/src/handlers/routes/api.ts +95 -0
  47. package/src/handlers/routes/html.ts +95 -0
  48. package/src/handlers/routes/livereload.ts +54 -0
  49. package/src/handlers/routes/proxy.ts +74 -0
  50. package/src/logger/clients.ts +12 -0
  51. package/src/logger/index.ts +3 -0
  52. package/src/logger/logger.ts +375 -0
  53. package/src/logger/serve-log.ts +206 -0
  54. package/src/plugins/index.ts +15 -0
  55. package/src/plugins/routes.ts +110 -0
  56. package/src/plugins/types.ts +19 -0
  57. package/src/router.ts +351 -0
  58. package/src/session.ts +556 -0
  59. package/src/shared.d.ts +63 -0
  60. package/src/startup.ts +154 -0
  61. package/src/types.d.ts +111 -0
  62. package/src/utils/common/case.ts +11 -0
  63. package/src/utils/common/index.ts +5 -0
  64. package/src/utils/common/json.ts +35 -0
  65. package/src/utils/common/match.ts +6 -0
  66. package/src/utils/common/misc.ts +53 -0
  67. package/src/utils/common/try.ts +6 -0
  68. package/src/utils/constants.ts +153 -0
  69. package/src/utils/fs.ts +621 -0
  70. package/src/utils/http/body.ts +65 -0
  71. package/src/utils/http/csrf.ts +111 -0
  72. package/src/utils/http/dom.ts +238 -0
  73. package/src/utils/http/escape.ts +8 -0
  74. package/src/utils/http/etag.ts +318 -0
  75. package/src/utils/http/html.ts +525 -0
  76. package/src/utils/http/index.ts +8 -0
  77. package/src/utils/http/ip.ts +32 -0
  78. package/src/utils/http/response.ts +129 -0
  79. package/src/utils/index.ts +4 -0
  80. package/src/utils/isomorphic/case.ts +52 -0
  81. package/src/utils/isomorphic/escape.ts +43 -0
  82. package/src/utils/isomorphic/index.ts +15 -0
  83. package/src/utils/isomorphic/is.ts +36 -0
  84. package/src/utils/isomorphic/match.ts +50 -0
  85. package/src/utils/isomorphic/math.ts +11 -0
  86. package/src/utils/isomorphic/misc.ts +22 -0
  87. package/src/utils/isomorphic/stringify.ts +42 -0
  88. package/src/utils/isomorphic/try.ts +94 -0
  89. package/src/utils/jsonc.ts +10 -0
  90. package/src/utils/shared-pool.ts +193 -0
  91. package/tsconfig.app.json +34 -0
@@ -0,0 +1,52 @@
1
+ function toKebabCase(str: string): string {
2
+ return str
3
+ .replace(/([a-z])([A-Z])/g, '$1-$2')
4
+ .replace(/[\s_]+/g, '-')
5
+ .toLowerCase()
6
+ }
7
+
8
+ function toCamelCase(str: string): string {
9
+ return str
10
+ .replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : ''))
11
+ .replace(/^[A-Z]/, match => match.toLowerCase())
12
+ }
13
+
14
+ function toPascalCase(str: string): string {
15
+ const camel = toCamelCase(str)
16
+ return camel.charAt(0).toUpperCase() + camel.slice(1)
17
+ }
18
+
19
+ function toSnakeCase(str: string): string {
20
+ return str
21
+ .replace(/([a-z])([A-Z])/g, '$1_$2')
22
+ .replace(/[\s-]+/g, '_')
23
+ .toLowerCase()
24
+ }
25
+
26
+ type CaseType = 'kebab' | 'camel' | 'pascal' | 'snake'
27
+
28
+ export const Case = Object.assign(
29
+ function Case(type: CaseType, str: string): string {
30
+ switch (type) {
31
+ case 'kebab':
32
+ return toKebabCase(str)
33
+ case 'camel':
34
+ return toCamelCase(str)
35
+ case 'pascal':
36
+ return toPascalCase(str)
37
+ case 'snake':
38
+ return toSnakeCase(str)
39
+ default:
40
+ return str
41
+ }
42
+ },
43
+ {
44
+ kebab: toKebabCase,
45
+ camel: toCamelCase,
46
+ pascal: toPascalCase,
47
+ snake: toSnakeCase,
48
+ upper: (str: string) => str.toUpperCase(),
49
+ lower: (str: string) => str.toLowerCase(),
50
+ caps: (str: string) => str.toUpperCase().replace(/[\s_-]+/g, ''),
51
+ },
52
+ )
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Escaping primitives shared by the server pipeline and the browser runtime.
3
+ *
4
+ * These live in `isomorphic/` because both sides need them: the HTML pipeline
5
+ * and the Vue plugin escape on the way out, and the client bundle escapes when
6
+ * building DOM strings. Nothing here may reference `Bun.*`, node builtins, or
7
+ * DOM globals — this module is compiled into the browser bundle as-is.
8
+ */
9
+
10
+ const HTML_ESCAPES: Record<string, string> = {
11
+ '&': '&amp;',
12
+ '<': '&lt;',
13
+ '>': '&gt;',
14
+ '"': '&quot;',
15
+ "'": '&#39;',
16
+ }
17
+
18
+ /**
19
+ * Escape the five HTML-significant characters. Nullish input yields an empty
20
+ * string rather than throwing, so it is safe to call on optional values.
21
+ */
22
+ export function escapeHtml(value: unknown): string {
23
+ if (value === null || value === undefined) return ''
24
+ return String(value).replace(/[&<>"']/g, char => HTML_ESCAPES[char] || char)
25
+ }
26
+
27
+ /**
28
+ * Serialize for embedding inside a `<script>` tag. Escaping `<` stops a
29
+ * `</script>` in the data from closing the tag; U+2028/2029 are line terminators
30
+ * to a JS parser but legal raw inside JSON.
31
+ *
32
+ * `utils/http/dom.ts` used to carry its own copy of this and is now a caller.
33
+ */
34
+ export function escapeScriptJson(value: unknown): string {
35
+ const json = JSON.stringify(value, (_key, val) =>
36
+ val === undefined ? null : val,
37
+ )
38
+ if (json === undefined) return 'null'
39
+ return json
40
+ .replace(/</g, '\\u003c')
41
+ .replace(/\u2028/g, '\\u2028')
42
+ .replace(/\u2029/g, '\\u2029')
43
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Pure utilities shared by the server and the browser bundle.
3
+ *
4
+ * Rule for this directory: no `Bun.*`, no node builtins, no DOM globals. These
5
+ * modules are compiled into the client bundle as-is, so anything that reaches
6
+ * for a runtime API belongs one layer up in `utils/` or in `client/`.
7
+ */
8
+ export * from './case'
9
+ export * from './escape'
10
+ export * from './is'
11
+ export * from './match'
12
+ export * from './math'
13
+ export * from './misc'
14
+ export * from './stringify'
15
+ export * from './try'
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Type predicates, shared by the server and the browser bundle.
3
+ *
4
+ * Two implementations of this used to exist. This one keeps the *callable*
5
+ * shape from the client copy (`is(x, 'string')` as well as `is.string(x)`,
6
+ * which is what the global `ISFunction` type has always described) and the
7
+ * *semantics* of the server copy — notably `is.object([]) === true`, which
8
+ * `misc.test.ts` asserts and which callers like `router.ts` rely on to
9
+ * JSON-encode array response bodies.
10
+ */
11
+
12
+ const checks = {
13
+ string: (v: any): v is string => typeof v === 'string',
14
+ number: (v: any): v is number => typeof v === 'number',
15
+ boolean: (v: any): v is boolean => typeof v === 'boolean',
16
+ bigint: (v: any): v is bigint => typeof v === 'bigint',
17
+ symbol: (v: any): v is symbol => typeof v === 'symbol',
18
+ function: (v: any): v is Function => typeof v === 'function',
19
+ /** Arrays count as objects here — see the note above before changing this. */
20
+ object: (v: any): v is Record<string, any> =>
21
+ v !== null && typeof v === 'object',
22
+ array: Array.isArray,
23
+ null: (v: any): v is null => v === null,
24
+ undefined: (v: any): v is undefined => v === undefined,
25
+ }
26
+
27
+ export const is: ISFunction = Object.assign(function is(
28
+ value: any,
29
+ type?: string,
30
+ ) {
31
+ if (!type) return value !== null && value !== undefined
32
+ // Delegate to the table rather than falling through to `typeof`, so that
33
+ // is(null, 'object') agrees with is.object(null) instead of contradicting it.
34
+ const check = (checks as any)[type]
35
+ return check ? check(value) : typeof value === type
36
+ }, checks) as ISFunction
@@ -0,0 +1,50 @@
1
+ import type { Match } from '../../types'
2
+
3
+ const matchDefault = Symbol('matchDefault')
4
+
5
+ function handleStringCases(value: any, cases: any): any {
6
+ // hasOwn, not `in`: `in` walks the prototype chain, so match('toString', {…})
7
+ // would find and invoke Object.prototype.toString.
8
+ if (Object.hasOwn(cases, value)) {
9
+ const handler = cases[value]
10
+ return typeof handler === 'function' ? handler(value) : handler
11
+ }
12
+ if (matchDefault in cases) {
13
+ const handler = cases[matchDefault]
14
+ return typeof handler === 'function' ? handler(value) : handler
15
+ }
16
+ return undefined
17
+ }
18
+
19
+ function handleArrayCases(value: any, cases: any[]): any {
20
+ for (const [predicate, result] of cases) {
21
+ const isMatch =
22
+ predicate === match ||
23
+ predicate === matchDefault ||
24
+ predicate === value ||
25
+ (typeof predicate === 'function' && Boolean(predicate(value)))
26
+
27
+ if (isMatch) {
28
+ return typeof result === 'function' ? result(value) : result
29
+ }
30
+ }
31
+ return undefined
32
+ }
33
+
34
+ export const match: Match<typeof matchDefault> = ((value: any, cases: any) => {
35
+ const isString = typeof value === 'string'
36
+ const isArray = Array.isArray(cases)
37
+
38
+ if (isString && !isArray) {
39
+ return handleStringCases(value, cases)
40
+ }
41
+ if (isArray) {
42
+ return handleArrayCases(value, cases)
43
+ }
44
+ return undefined
45
+ }) as any
46
+
47
+ match.default = matchDefault
48
+ match[Symbol.toPrimitive] = () => matchDefault
49
+
50
+ export { matchDefault }
@@ -0,0 +1,11 @@
1
+ export const Math2 = {
2
+ clamp(value: number, min?: number, max?: number): number {
3
+ min ??= -Infinity
4
+ max ??= Infinity
5
+ return Math.min(Math.max(value, min), max)
6
+ },
7
+
8
+ step(value: number, step: number): number {
9
+ return Math.round(value / step) * step
10
+ },
11
+ }
@@ -0,0 +1,22 @@
1
+ import { is } from './is'
2
+
3
+ export function throws(message: string | Error): never {
4
+ throw is.string(message) ? new Error(message) : message
5
+ }
6
+
7
+ export function assert(condition: any, message?: string): asserts condition {
8
+ if (!condition) {
9
+ throw new Error(message || 'Assertion failed')
10
+ }
11
+ }
12
+
13
+ /** Escape hatch for casting through `any` without spelling it out each time. */
14
+ export function any<T = any>(value: any): T {
15
+ return value
16
+ }
17
+
18
+ export function repeat(n: number): number[]
19
+ export function repeat<T>(n: number, fn: (i: number) => T): T[]
20
+ export function repeat<T>(n: number, fn?: (i: number) => T): unknown[] {
21
+ return Array.from({ length: n }, (_, k) => (fn ? fn(k) : k))
22
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Circular-safe JSON helpers.
3
+ *
4
+ * Three copies of this logic existed: the client `$fmt` formatter, the
5
+ * livereload log serializer, and ad-hoc replacers on the server. They drifted —
6
+ * the `$fmt` one shipped with a one-parameter replacer that made every object
7
+ * render as `""` — so the kernel lives here once and callers layer their own
8
+ * extras on top.
9
+ */
10
+
11
+ /**
12
+ * Build a `JSON.stringify` replacer that substitutes `'[Circular]'` for repeat
13
+ * references. `extra` runs after the cycle check and can rewrite values further.
14
+ *
15
+ * Note the two-parameter signature: `JSON.stringify` calls a replacer as
16
+ * `(key, value)`, so a single-parameter function silently binds to *key*.
17
+ */
18
+ export function circularReplacer(
19
+ extra?: (key: string, value: any) => any,
20
+ ): (key: string, value: any) => any {
21
+ const seen = new WeakSet<object>()
22
+ return (key, value) => {
23
+ if (typeof value === 'object' && value !== null) {
24
+ if (seen.has(value)) return '[Circular]'
25
+ seen.add(value)
26
+ }
27
+ return extra ? extra(key, value) : value
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Best-effort string form of any value, safe against cycles and throwing
33
+ * `toJSON`/getters. Primitives pass through `String()` unchanged.
34
+ */
35
+ export function safeStringify(value: unknown): string {
36
+ if (typeof value !== 'object' || value === null) return String(value)
37
+ try {
38
+ return JSON.stringify(value, circularReplacer()) ?? String(value)
39
+ } catch {
40
+ return Object.prototype.toString.call(value)
41
+ }
42
+ }
@@ -0,0 +1,94 @@
1
+ import type { MixedPromise, Wrapped } from '../../types'
2
+ import { is } from './is'
3
+
4
+ // These were declared again, here, byte-for-byte identical to `types.d.ts` —
5
+ // the second copy convention 5 exists to prevent. `isomorphic.test.ts` catches
6
+ // a duplicated *value* by reference identity, and a type has no runtime
7
+ // identity to compare, so nothing saw it.
8
+ //
9
+ // A type-only import is safe in an isomorphic module: `verbatimModuleSyntax`
10
+ // makes the compiler reject a value-position import of these, so it cannot
11
+ // become a runtime module edge into the browser bundle.
12
+
13
+ type CatchReturn<T extends MixedPromise<any>> =
14
+ T extends Promise<infer V>
15
+ ? Promise<[Error, null] | [null, V]>
16
+ : MixedPromise<[Error, null] | [null, T]>
17
+
18
+ function tryThrow<T>(
19
+ callback: () => MixedPromise<T>,
20
+ error?: string | Error,
21
+ ): Promise<T> {
22
+ return Promise.try(callback).catch((err: any) => {
23
+ throw is.string(error) ? new Error(error) : error || err
24
+ })
25
+ }
26
+
27
+ function tryReturn<T extends MixedPromise<any>, D extends MixedPromise<any>>(
28
+ value: Wrapped<T>,
29
+ defaultValue: Wrapped<D, [Error]>,
30
+ ): T | D {
31
+ try {
32
+ const unwrapped = is.function(value) ? (value as any)() : value
33
+ if (
34
+ unwrapped instanceof Promise ||
35
+ (is.object(unwrapped) && is.function((unwrapped as any).catch))
36
+ ) {
37
+ return (unwrapped as any).catch((error: any) =>
38
+ is.function(defaultValue) ? (defaultValue as any)(error) : defaultValue,
39
+ ) as any
40
+ }
41
+ return unwrapped
42
+ } catch (error: any) {
43
+ const unwrappedDefault = is.function(defaultValue)
44
+ ? (defaultValue as any)(error)
45
+ : defaultValue
46
+ return unwrappedDefault
47
+ }
48
+ }
49
+
50
+ function trySilent<T extends MixedPromise<any>>(value: Wrapped<T>): T | null {
51
+ return tryReturn(value, null as any)
52
+ }
53
+
54
+ type TryType = {
55
+ <T extends MixedPromise<any>>(value: Wrapped<T>): T | null
56
+ catch<T extends MixedPromise<any>>(value: Wrapped<T>): CatchReturn<T>
57
+ return<T extends MixedPromise<any>, D extends MixedPromise<any>>(
58
+ value: Wrapped<T>,
59
+ defaultValue: Wrapped<D, [Error]>,
60
+ ): T | D
61
+ throw: typeof tryThrow
62
+ /**
63
+ * Alias for calling `Try` directly. Kept because the browser global has
64
+ * always exposed it; unifying the two copies is not the place to shrink a
65
+ * published surface.
66
+ */
67
+ silent<T extends MixedPromise<any>>(value: Wrapped<T>): T | null
68
+ }
69
+
70
+ export const Try: TryType = Object.assign(
71
+ function Try<T extends MixedPromise<any>>(value: Wrapped<T>): T | null {
72
+ return trySilent(value)
73
+ },
74
+ {
75
+ catch<T extends MixedPromise<any>>(value: Wrapped<T>): CatchReturn<T> {
76
+ if (is.function(value)) {
77
+ return Promise.try(value as any)
78
+ .then(data => [null, data])
79
+ .catch(error => [error, null]) as any
80
+ }
81
+ if (value instanceof Promise) {
82
+ return value
83
+ .then(data => [null, data])
84
+ .catch(error => [error, null]) as any
85
+ }
86
+ return [null, value] as any
87
+ },
88
+ return: tryReturn,
89
+ throw: tryThrow,
90
+ silent: trySilent,
91
+ },
92
+ )
93
+
94
+ export const tryCatch = Try.catch
@@ -0,0 +1,10 @@
1
+ export function parseJSONC(jsonc: string): any {
2
+ if (!jsonc.trim()) return null
3
+
4
+ const clean = jsonc.replace(
5
+ /("(?:\\.|[^"\\])*")|(\/\/.*|\/\*[\s\S]*?\*\/)|,\s*(?=[\]}])/g,
6
+ (_, stringLiteral) => (stringLiteral ? stringLiteral : ''),
7
+ )
8
+
9
+ return JSON.parse(clean)
10
+ }
@@ -0,0 +1,193 @@
1
+ const HEADER_INT_COUNT = 16
2
+ const COUNTERS_INT_COUNT = 256
3
+ const RATE_LIMIT_SLOT_COUNT = 1024
4
+ const HEADER_BYTES = HEADER_INT_COUNT * 4
5
+ const COUNTERS_BYTES = COUNTERS_INT_COUNT * 4
6
+ const RATE_LIMIT_BYTES = RATE_LIMIT_SLOT_COUNT * 2 * 4
7
+ const BUFFER_START_OFFSET = HEADER_BYTES + COUNTERS_BYTES + RATE_LIMIT_BYTES
8
+
9
+ export const COUNTER_SLOTS = {
10
+ TOTAL_REQUESTS: 0,
11
+ TOTAL_ERRORS: 1,
12
+ ACTIVE_CONNECTIONS: 2,
13
+ LATENCY_SUM_MS: 3,
14
+ } as const
15
+
16
+ export class SharedMemoryPool {
17
+ // Definite-assignment assertions because the adopt-an-existing-buffer path
18
+ // assigns all five through `bind()`, and TypeScript's initialization
19
+ // analysis does not follow a method call out of the constructor. Both
20
+ // constructor paths do assign every one of them before returning.
21
+ buffer!: SharedArrayBuffer
22
+ header!: Int32Array
23
+ counters!: Int32Array
24
+ rateLimits!: Int32Array
25
+ dataPool!: Uint8Array
26
+
27
+ constructor(sizeOrBuffer: number | SharedArrayBuffer = 1024 * 1024) {
28
+ if (typeof sizeOrBuffer === 'number') {
29
+ const size = Math.max(sizeOrBuffer, BUFFER_START_OFFSET + 1024)
30
+ this.buffer = new SharedArrayBuffer(size)
31
+ this.header = new Int32Array(this.buffer, 0, HEADER_INT_COUNT)
32
+ this.counters = new Int32Array(
33
+ this.buffer,
34
+ HEADER_BYTES,
35
+ COUNTERS_INT_COUNT,
36
+ )
37
+ this.rateLimits = new Int32Array(
38
+ this.buffer,
39
+ HEADER_BYTES + COUNTERS_BYTES,
40
+ RATE_LIMIT_SLOT_COUNT * 2,
41
+ )
42
+ this.dataPool = new Uint8Array(
43
+ this.buffer,
44
+ BUFFER_START_OFFSET,
45
+ size - BUFFER_START_OFFSET,
46
+ )
47
+
48
+ Atomics.store(this.header, 0, 0x42414b45)
49
+ Atomics.store(this.header, 1, size)
50
+ Atomics.store(this.header, 2, BUFFER_START_OFFSET)
51
+ } else {
52
+ // Adopting a buffer someone else laid out is exactly what `bind` does —
53
+ // it read the header for the size rather than trusting `byteLength`, and
54
+ // so did the copy that used to sit here, character for character.
55
+ this.bind(sizeOrBuffer)
56
+ }
57
+ }
58
+
59
+ /** Point every view at `buffer`, taking its size from the header it carries. */
60
+ bind(buffer: SharedArrayBuffer): void {
61
+ this.buffer = buffer
62
+ this.header = new Int32Array(this.buffer, 0, HEADER_INT_COUNT)
63
+ this.counters = new Int32Array(
64
+ this.buffer,
65
+ HEADER_BYTES,
66
+ COUNTERS_INT_COUNT,
67
+ )
68
+ this.rateLimits = new Int32Array(
69
+ this.buffer,
70
+ HEADER_BYTES + COUNTERS_BYTES,
71
+ RATE_LIMIT_SLOT_COUNT * 2,
72
+ )
73
+ const size = Atomics.load(this.header, 1) || this.buffer.byteLength
74
+ this.dataPool = new Uint8Array(
75
+ this.buffer,
76
+ BUFFER_START_OFFSET,
77
+ size - BUFFER_START_OFFSET,
78
+ )
79
+ }
80
+
81
+ incrementCounter(slot: number, delta = 1): number {
82
+ if (slot < 0 || slot >= COUNTERS_INT_COUNT) return 0
83
+ return Atomics.add(this.counters, slot, delta) + delta
84
+ }
85
+
86
+ decrementCounter(slot: number, delta = 1): number {
87
+ if (slot < 0 || slot >= COUNTERS_INT_COUNT) return 0
88
+ return Atomics.sub(this.counters, slot, delta) - delta
89
+ }
90
+
91
+ getCounter(slot: number): number {
92
+ if (slot < 0 || slot >= COUNTERS_INT_COUNT) return 0
93
+ return Atomics.load(this.counters, slot)
94
+ }
95
+
96
+ setCounter(slot: number, value: number): number {
97
+ if (slot < 0 || slot >= COUNTERS_INT_COUNT) return 0
98
+ return Atomics.store(this.counters, slot, value)
99
+ }
100
+
101
+ consumeToken(
102
+ slot: number,
103
+ maxTokens: number,
104
+ refillRatePerSec: number,
105
+ tokensRequested = 1,
106
+ ): boolean {
107
+ if (slot < 0 || slot >= RATE_LIMIT_SLOT_COUNT) return false
108
+ const tokenIndex = slot * 2
109
+ const timeIndex = slot * 2 + 1
110
+
111
+ const nowSec = Math.floor(Date.now() / 1000)
112
+
113
+ while (true) {
114
+ const currentTokens = Atomics.load(this.rateLimits, tokenIndex)
115
+ const lastTime = Atomics.load(this.rateLimits, timeIndex)
116
+
117
+ if (lastTime === 0 && currentTokens === 0) {
118
+ const initTokens = Math.max(0, maxTokens - tokensRequested)
119
+ if (
120
+ Atomics.compareExchange(this.rateLimits, timeIndex, 0, nowSec) === 0
121
+ ) {
122
+ Atomics.store(this.rateLimits, tokenIndex, initTokens)
123
+ return tokensRequested <= maxTokens
124
+ }
125
+ continue
126
+ }
127
+
128
+ let availableTokens = currentTokens
129
+ const elapsed = Math.max(0, nowSec - lastTime)
130
+ if (elapsed > 0) {
131
+ availableTokens = Math.min(
132
+ maxTokens,
133
+ currentTokens + elapsed * refillRatePerSec,
134
+ )
135
+ }
136
+
137
+ if (availableTokens < tokensRequested) {
138
+ if (elapsed > 0) {
139
+ Atomics.compareExchange(this.rateLimits, timeIndex, lastTime, nowSec)
140
+ Atomics.compareExchange(
141
+ this.rateLimits,
142
+ tokenIndex,
143
+ currentTokens,
144
+ availableTokens,
145
+ )
146
+ }
147
+ return false
148
+ }
149
+
150
+ const newTokens = availableTokens - tokensRequested
151
+ if (
152
+ Atomics.compareExchange(
153
+ this.rateLimits,
154
+ tokenIndex,
155
+ currentTokens,
156
+ newTokens,
157
+ ) === currentTokens
158
+ ) {
159
+ if (elapsed > 0) {
160
+ Atomics.compareExchange(this.rateLimits, timeIndex, lastTime, nowSec)
161
+ }
162
+ return true
163
+ }
164
+ }
165
+ }
166
+
167
+ allocateBuffer(length: number): { offset: number; view: Uint8Array } | null {
168
+ if (length <= 0) return null
169
+ const totalSize = Atomics.load(this.header, 1)
170
+ while (true) {
171
+ const currentOffset = Atomics.load(this.header, 2)
172
+ if (currentOffset + length > totalSize) {
173
+ return null
174
+ }
175
+ const newOffset = currentOffset + length
176
+ if (
177
+ Atomics.compareExchange(this.header, 2, currentOffset, newOffset) ===
178
+ currentOffset
179
+ ) {
180
+ return {
181
+ offset: currentOffset,
182
+ view: new Uint8Array(this.buffer, currentOffset, length),
183
+ }
184
+ }
185
+ }
186
+ }
187
+
188
+ getBufferView(offset: number, length: number): Uint8Array | null {
189
+ const totalSize = Atomics.load(this.header, 1)
190
+ if (offset < BUFFER_START_OFFSET || offset + length > totalSize) return null
191
+ return new Uint8Array(this.buffer, offset, length)
192
+ }
193
+ }
@@ -0,0 +1,34 @@
1
+ {
2
+ "$comment": [
3
+ "Base tsconfig for applications built on Bakery. Extend it:",
4
+ " { \"extends\": \"@bakery-framework/core/tsconfig.app.json\" }",
5
+ "",
6
+ "The JSX settings are not optional. Bakery renders JSX server-side to a",
7
+ "string through its own `createElement`, so without these Bun falls back to",
8
+ "the automatic runtime and every .tsx page fails with",
9
+ "'Cannot find module react/jsx-dev-runtime'."
10
+ ],
11
+ "_files_note": "Paths in an extended config resolve relative to THIS file, so extending packages inherit core ambient declarations (createElement, Fragment, the JSX namespace, Bakery, AppConfig) without naming a node_modules path. shared.d.ts carries the handful of types the browser runtime needs too (MapOf, Wrapped, JsonResponse, ISFunction) and is declared there exactly once.",
12
+ "files": ["./src/global.d.ts", "./src/shared.d.ts", "./src/types.d.ts"],
13
+ "compilerOptions": {
14
+ "lib": ["ESNext", "DOM", "DOM.Iterable"],
15
+ "target": "ESNext",
16
+ "module": "Preserve",
17
+ "moduleDetection": "force",
18
+ "moduleResolution": "bundler",
19
+ "types": ["bun-types"],
20
+
21
+ "jsx": "react",
22
+ "jsxFactory": "createElement",
23
+ "jsxFragmentFactory": "Fragment",
24
+
25
+ "allowImportingTsExtensions": true,
26
+ "verbatimModuleSyntax": true,
27
+ "noEmit": true,
28
+ "resolveJsonModule": true,
29
+ "strict": true,
30
+ "skipLibCheck": true,
31
+ "esModuleInterop": true,
32
+ "forceConsistentCasingInFileNames": true
33
+ }
34
+ }