@buenojs/bueno 0.8.3 → 0.8.5

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 (218) hide show
  1. package/README.md +136 -16
  2. package/dist/cli/{index.js → bin.js} +3036 -1421
  3. package/dist/container/index.js +250 -0
  4. package/dist/context/index.js +219 -0
  5. package/dist/database/index.js +493 -0
  6. package/dist/frontend/index.js +7697 -0
  7. package/dist/health/index.js +364 -0
  8. package/dist/i18n/index.js +345 -0
  9. package/dist/index.js +11043 -6482
  10. package/dist/jobs/index.js +819 -0
  11. package/dist/lock/index.js +367 -0
  12. package/dist/logger/index.js +281 -0
  13. package/dist/metrics/index.js +289 -0
  14. package/dist/middleware/index.js +77 -0
  15. package/dist/migrations/index.js +571 -0
  16. package/dist/modules/index.js +3346 -0
  17. package/dist/notification/index.js +484 -0
  18. package/dist/observability/index.js +331 -0
  19. package/dist/openapi/index.js +776 -0
  20. package/dist/orm/index.js +1356 -0
  21. package/dist/router/index.js +886 -0
  22. package/dist/rpc/index.js +691 -0
  23. package/dist/schema/index.js +400 -0
  24. package/dist/telemetry/index.js +595 -0
  25. package/dist/template/index.js +640 -0
  26. package/dist/templates/index.js +640 -0
  27. package/dist/testing/index.js +1111 -0
  28. package/dist/types/index.js +60 -0
  29. package/package.json +121 -27
  30. package/src/cache/index.ts +2 -1
  31. package/src/cli/bin.ts +2 -2
  32. package/src/cli/commands/build.ts +183 -165
  33. package/src/cli/commands/dev.ts +96 -89
  34. package/src/cli/commands/generate.ts +142 -111
  35. package/src/cli/commands/help.ts +20 -16
  36. package/src/cli/commands/index.ts +3 -6
  37. package/src/cli/commands/migration.ts +124 -105
  38. package/src/cli/commands/new.ts +392 -438
  39. package/src/cli/commands/start.ts +81 -79
  40. package/src/cli/core/args.ts +68 -50
  41. package/src/cli/core/console.ts +89 -95
  42. package/src/cli/core/index.ts +4 -4
  43. package/src/cli/core/prompt.ts +65 -62
  44. package/src/cli/core/spinner.ts +23 -20
  45. package/src/cli/index.ts +46 -38
  46. package/src/cli/templates/database/index.ts +61 -0
  47. package/src/cli/templates/database/mysql.ts +14 -0
  48. package/src/cli/templates/database/none.ts +16 -0
  49. package/src/cli/templates/database/postgresql.ts +14 -0
  50. package/src/cli/templates/database/sqlite.ts +14 -0
  51. package/src/cli/templates/deploy.ts +29 -26
  52. package/src/cli/templates/docker.ts +41 -30
  53. package/src/cli/templates/frontend/index.ts +63 -0
  54. package/src/cli/templates/frontend/none.ts +17 -0
  55. package/src/cli/templates/frontend/react.ts +140 -0
  56. package/src/cli/templates/frontend/solid.ts +134 -0
  57. package/src/cli/templates/frontend/svelte.ts +131 -0
  58. package/src/cli/templates/frontend/vue.ts +130 -0
  59. package/src/cli/templates/generators/index.ts +339 -0
  60. package/src/cli/templates/generators/types.ts +56 -0
  61. package/src/cli/templates/index.ts +35 -2
  62. package/src/cli/templates/project/api.ts +81 -0
  63. package/src/cli/templates/project/default.ts +140 -0
  64. package/src/cli/templates/project/fullstack.ts +111 -0
  65. package/src/cli/templates/project/index.ts +95 -0
  66. package/src/cli/templates/project/minimal.ts +45 -0
  67. package/src/cli/templates/project/types.ts +94 -0
  68. package/src/cli/templates/project/website.ts +263 -0
  69. package/src/cli/utils/fs.ts +55 -41
  70. package/src/cli/utils/index.ts +3 -2
  71. package/src/cli/utils/strings.ts +47 -33
  72. package/src/cli/utils/version.ts +47 -0
  73. package/src/config/env-validation.ts +100 -0
  74. package/src/config/env.ts +169 -41
  75. package/src/config/index.ts +28 -20
  76. package/src/config/loader.ts +25 -16
  77. package/src/config/merge.ts +21 -10
  78. package/src/config/types.ts +545 -25
  79. package/src/config/validation.ts +215 -7
  80. package/src/container/forward-ref.ts +22 -22
  81. package/src/container/index.ts +34 -12
  82. package/src/context/index.ts +11 -1
  83. package/src/database/index.ts +7 -190
  84. package/src/database/orm/builder.ts +457 -0
  85. package/src/database/orm/casts/index.ts +130 -0
  86. package/src/database/orm/casts/types.ts +25 -0
  87. package/src/database/orm/compiler.ts +304 -0
  88. package/src/database/orm/hooks/index.ts +114 -0
  89. package/src/database/orm/index.ts +61 -0
  90. package/src/database/orm/model-registry.ts +59 -0
  91. package/src/database/orm/model.ts +821 -0
  92. package/src/database/orm/relationships/base.ts +146 -0
  93. package/src/database/orm/relationships/belongs-to-many.ts +179 -0
  94. package/src/database/orm/relationships/belongs-to.ts +56 -0
  95. package/src/database/orm/relationships/has-many.ts +45 -0
  96. package/src/database/orm/relationships/has-one.ts +41 -0
  97. package/src/database/orm/relationships/index.ts +11 -0
  98. package/src/database/orm/scopes/index.ts +55 -0
  99. package/src/events/__tests__/event-system.test.ts +235 -0
  100. package/src/events/config.ts +238 -0
  101. package/src/events/example-usage.ts +185 -0
  102. package/src/events/index.ts +278 -0
  103. package/src/events/manager.ts +385 -0
  104. package/src/events/registry.ts +182 -0
  105. package/src/events/types.ts +124 -0
  106. package/src/frontend/api-routes.ts +65 -23
  107. package/src/frontend/bundler.ts +76 -34
  108. package/src/frontend/console-client.ts +2 -2
  109. package/src/frontend/console-stream.ts +94 -38
  110. package/src/frontend/dev-server.ts +94 -46
  111. package/src/frontend/file-router.ts +61 -19
  112. package/src/frontend/frameworks/index.ts +37 -10
  113. package/src/frontend/frameworks/react.ts +10 -8
  114. package/src/frontend/frameworks/solid.ts +11 -9
  115. package/src/frontend/frameworks/svelte.ts +15 -9
  116. package/src/frontend/frameworks/vue.ts +13 -11
  117. package/src/frontend/hmr-client.ts +12 -10
  118. package/src/frontend/hmr.ts +146 -103
  119. package/src/frontend/index.ts +14 -5
  120. package/src/frontend/islands.ts +41 -22
  121. package/src/frontend/isr.ts +59 -37
  122. package/src/frontend/layout.ts +36 -21
  123. package/src/frontend/ssr/react.ts +74 -27
  124. package/src/frontend/ssr/solid.ts +54 -20
  125. package/src/frontend/ssr/svelte.ts +48 -14
  126. package/src/frontend/ssr/vue.ts +50 -18
  127. package/src/frontend/ssr.ts +83 -39
  128. package/src/frontend/types.ts +91 -56
  129. package/src/health/index.ts +21 -9
  130. package/src/i18n/engine.ts +305 -0
  131. package/src/i18n/index.ts +38 -0
  132. package/src/i18n/loader.ts +218 -0
  133. package/src/i18n/middleware.ts +164 -0
  134. package/src/i18n/negotiator.ts +162 -0
  135. package/src/i18n/types.ts +158 -0
  136. package/src/index.ts +179 -27
  137. package/src/jobs/drivers/memory.ts +315 -0
  138. package/src/jobs/drivers/redis.ts +459 -0
  139. package/src/jobs/index.ts +30 -0
  140. package/src/jobs/queue.ts +281 -0
  141. package/src/jobs/types.ts +295 -0
  142. package/src/jobs/worker.ts +380 -0
  143. package/src/logger/index.ts +1 -3
  144. package/src/logger/transports/index.ts +62 -22
  145. package/src/metrics/index.ts +25 -16
  146. package/src/migrations/index.ts +9 -0
  147. package/src/modules/filters.ts +13 -17
  148. package/src/modules/guards.ts +49 -26
  149. package/src/modules/index.ts +409 -298
  150. package/src/modules/interceptors.ts +58 -20
  151. package/src/modules/lazy.ts +11 -19
  152. package/src/modules/lifecycle.ts +15 -7
  153. package/src/modules/metadata.ts +15 -5
  154. package/src/modules/pipes.ts +94 -72
  155. package/src/notification/channels/base.ts +68 -0
  156. package/src/notification/channels/email.ts +105 -0
  157. package/src/notification/channels/push.ts +104 -0
  158. package/src/notification/channels/sms.ts +105 -0
  159. package/src/notification/channels/whatsapp.ts +104 -0
  160. package/src/notification/index.ts +48 -0
  161. package/src/notification/service.ts +354 -0
  162. package/src/notification/types.ts +344 -0
  163. package/src/observability/__tests__/observability.test.ts +483 -0
  164. package/src/observability/breadcrumbs.ts +114 -0
  165. package/src/observability/index.ts +136 -0
  166. package/src/observability/interceptor.ts +85 -0
  167. package/src/observability/service.ts +303 -0
  168. package/src/observability/trace.ts +37 -0
  169. package/src/observability/types.ts +196 -0
  170. package/src/openapi/__tests__/decorators.test.ts +335 -0
  171. package/src/openapi/__tests__/document-builder.test.ts +285 -0
  172. package/src/openapi/__tests__/route-scanner.test.ts +334 -0
  173. package/src/openapi/__tests__/schema-generator.test.ts +275 -0
  174. package/src/openapi/decorators.ts +328 -0
  175. package/src/openapi/document-builder.ts +274 -0
  176. package/src/openapi/index.ts +112 -0
  177. package/src/openapi/metadata.ts +112 -0
  178. package/src/openapi/route-scanner.ts +289 -0
  179. package/src/openapi/schema-generator.ts +256 -0
  180. package/src/openapi/swagger-module.ts +166 -0
  181. package/src/openapi/types.ts +398 -0
  182. package/src/orm/index.ts +10 -0
  183. package/src/rpc/index.ts +3 -1
  184. package/src/schema/index.ts +9 -0
  185. package/src/security/index.ts +15 -6
  186. package/src/ssg/index.ts +9 -8
  187. package/src/telemetry/index.ts +76 -22
  188. package/src/template/index.ts +7 -0
  189. package/src/templates/engine.ts +224 -0
  190. package/src/templates/index.ts +9 -0
  191. package/src/templates/loader.ts +331 -0
  192. package/src/templates/renderers/markdown.ts +212 -0
  193. package/src/templates/renderers/simple.ts +269 -0
  194. package/src/templates/types.ts +154 -0
  195. package/src/testing/index.ts +100 -27
  196. package/src/types/optional-deps.d.ts +347 -187
  197. package/src/validation/index.ts +92 -2
  198. package/src/validation/schemas.ts +536 -0
  199. package/tests/integration/fullstack.test.ts +4 -4
  200. package/tests/unit/database.test.ts +2 -72
  201. package/tests/unit/env-validation.test.ts +166 -0
  202. package/tests/unit/events.test.ts +910 -0
  203. package/tests/unit/i18n.test.ts +455 -0
  204. package/tests/unit/jobs.test.ts +493 -0
  205. package/tests/unit/notification.test.ts +988 -0
  206. package/tests/unit/observability.test.ts +453 -0
  207. package/tests/unit/orm/builder.test.ts +323 -0
  208. package/tests/unit/orm/casts.test.ts +179 -0
  209. package/tests/unit/orm/compiler.test.ts +220 -0
  210. package/tests/unit/orm/eager-loading.test.ts +285 -0
  211. package/tests/unit/orm/hooks.test.ts +191 -0
  212. package/tests/unit/orm/model.test.ts +373 -0
  213. package/tests/unit/orm/relationships.test.ts +303 -0
  214. package/tests/unit/orm/scopes.test.ts +74 -0
  215. package/tests/unit/templates-simple.test.ts +53 -0
  216. package/tests/unit/templates.test.ts +454 -0
  217. package/tests/unit/validation.test.ts +18 -24
  218. package/tsconfig.json +11 -3
@@ -0,0 +1,691 @@
1
+ // @bun
2
+ var __defProp = Object.defineProperty;
3
+ var __export = (target, all) => {
4
+ for (var name in all)
5
+ __defProp(target, name, {
6
+ get: all[name],
7
+ enumerable: true,
8
+ configurable: true,
9
+ set: (newValue) => all[name] = () => newValue
10
+ });
11
+ };
12
+ var __legacyDecorateClassTS = function(decorators, target, key, desc) {
13
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
14
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
15
+ r = Reflect.decorate(decorators, target, key, desc);
16
+ else
17
+ for (var i = decorators.length - 1;i >= 0; i--)
18
+ if (d = decorators[i])
19
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
20
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
21
+ };
22
+ var __legacyMetadataTS = (k, v) => {
23
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function")
24
+ return Reflect.metadata(k, v);
25
+ };
26
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
27
+ var __require = import.meta.require;
28
+
29
+ // src/rpc/index.ts
30
+ class DeduplicationStore {
31
+ defaultTTL;
32
+ pending = new Map;
33
+ cache = new Map;
34
+ cleanupInterval;
35
+ constructor(defaultTTL = 5000) {
36
+ this.defaultTTL = defaultTTL;
37
+ this.cleanupInterval = setInterval(() => this.cleanup(), 1e4);
38
+ }
39
+ cleanup() {
40
+ const now = Date.now();
41
+ for (const [key, entry] of this.cache.entries()) {
42
+ if (now - entry.timestamp > entry.ttl) {
43
+ this.cache.delete(key);
44
+ }
45
+ }
46
+ for (const [key, entry] of this.pending.entries()) {
47
+ if (now - entry.timestamp > 60000) {
48
+ this.pending.delete(key);
49
+ }
50
+ }
51
+ }
52
+ getPending(key, body) {
53
+ const pending = this.pending.get(key);
54
+ if (pending && (body === undefined || pending.body === body)) {
55
+ return pending;
56
+ }
57
+ return;
58
+ }
59
+ setPending(key, promise, body) {
60
+ this.pending.set(key, { promise, timestamp: Date.now(), body });
61
+ }
62
+ removePending(key) {
63
+ this.pending.delete(key);
64
+ }
65
+ getCached(key, ttl) {
66
+ const cached = this.cache.get(key);
67
+ if (cached && Date.now() - cached.timestamp < ttl) {
68
+ return cached.response.clone();
69
+ }
70
+ return;
71
+ }
72
+ setCached(key, response, ttl) {
73
+ this.cache.set(key, {
74
+ response: response.clone(),
75
+ timestamp: Date.now(),
76
+ ttl
77
+ });
78
+ }
79
+ getCachedData(key) {
80
+ return this.cache.get(key)?.data;
81
+ }
82
+ setCachedData(key, data, ttl) {
83
+ this.cache.set(key, {
84
+ response: new Response(JSON.stringify(data)),
85
+ timestamp: Date.now(),
86
+ ttl
87
+ });
88
+ }
89
+ invalidate(key) {
90
+ this.cache.delete(key);
91
+ }
92
+ clear() {
93
+ this.pending.clear();
94
+ this.cache.clear();
95
+ }
96
+ destroy() {
97
+ if (this.cleanupInterval) {
98
+ clearInterval(this.cleanupInterval);
99
+ }
100
+ this.clear();
101
+ }
102
+ getStats() {
103
+ return {
104
+ pending: this.pending.size,
105
+ cached: this.cache.size
106
+ };
107
+ }
108
+ }
109
+
110
+ class OptimisticStore {
111
+ pending = new Map;
112
+ idCounter = 0;
113
+ create(cacheKey, optimisticData, previousData, callbacks) {
114
+ const id = `optimistic-${++this.idCounter}`;
115
+ this.pending.set(id, {
116
+ id,
117
+ cacheKey,
118
+ optimisticData,
119
+ previousData,
120
+ timestamp: Date.now(),
121
+ status: "pending",
122
+ onRollback: callbacks?.onRollback,
123
+ onConfirm: callbacks?.onConfirm
124
+ });
125
+ return id;
126
+ }
127
+ confirm(id, serverData) {
128
+ const update = this.pending.get(id);
129
+ if (update) {
130
+ update.status = "confirmed";
131
+ if (update.onConfirm && serverData !== undefined) {
132
+ update.onConfirm(serverData);
133
+ }
134
+ this.pending.delete(id);
135
+ }
136
+ }
137
+ rollback(id) {
138
+ const update = this.pending.get(id);
139
+ if (update) {
140
+ update.status = "rolled_back";
141
+ const previousData = update.previousData;
142
+ if (update.onRollback) {
143
+ update.onRollback(previousData);
144
+ }
145
+ this.pending.delete(id);
146
+ return previousData;
147
+ }
148
+ return;
149
+ }
150
+ get(id) {
151
+ return this.pending.get(id);
152
+ }
153
+ getByCacheKey(cacheKey) {
154
+ for (const update of this.pending.values()) {
155
+ if (update.cacheKey === cacheKey) {
156
+ return update;
157
+ }
158
+ }
159
+ return;
160
+ }
161
+ hasPending(cacheKey) {
162
+ for (const update of this.pending.values()) {
163
+ if (update.cacheKey === cacheKey && update.status === "pending") {
164
+ return true;
165
+ }
166
+ }
167
+ return false;
168
+ }
169
+ getOptimisticData(cacheKey) {
170
+ const update = this.getByCacheKey(cacheKey);
171
+ if (update && update.status === "pending") {
172
+ return update.optimisticData;
173
+ }
174
+ return;
175
+ }
176
+ clear() {
177
+ this.pending.clear();
178
+ }
179
+ getStats() {
180
+ return { pending: this.pending.size };
181
+ }
182
+ }
183
+ function defaultKeyGenerator(method, url, body) {
184
+ const bodyHash = body ? JSON.stringify(body) : "";
185
+ return `${method}:${url}:${bodyHash}`;
186
+ }
187
+ var DEFAULT_RETRYABLE_STATUS_CODES = [408, 429, 500, 502, 503, 504];
188
+ var DEFAULT_RETRYABLE_ERRORS = [
189
+ "ECONNRESET",
190
+ "ETIMEDOUT",
191
+ "ENOTFOUND",
192
+ "EAI_AGAIN"
193
+ ];
194
+ function defaultShouldRetry(response, error, attempt, config) {
195
+ const maxAttempts = config?.maxAttempts ?? 3;
196
+ const retryableStatusCodes = config?.retryableStatusCodes ?? DEFAULT_RETRYABLE_STATUS_CODES;
197
+ const retryableErrors = config?.retryableErrors ?? DEFAULT_RETRYABLE_ERRORS;
198
+ if (attempt >= maxAttempts) {
199
+ return false;
200
+ }
201
+ if (error) {
202
+ if (retryableErrors.length > 0) {
203
+ return retryableErrors.some((code) => error.message.includes(code) || error.name === code);
204
+ }
205
+ return DEFAULT_RETRYABLE_ERRORS.some((code) => error.message.includes(code) || error.name === code);
206
+ }
207
+ if (response) {
208
+ if (retryableStatusCodes.length > 0) {
209
+ return retryableStatusCodes.includes(response.status);
210
+ }
211
+ return DEFAULT_RETRYABLE_STATUS_CODES.includes(response.status);
212
+ }
213
+ return false;
214
+ }
215
+ function calculateDelay(attempt, config) {
216
+ const delay = config.initialDelay * config.backoffMultiplier ** (attempt - 1);
217
+ return Math.min(delay, config.maxDelay);
218
+ }
219
+ function sleep(ms) {
220
+ return new Promise((resolve) => setTimeout(resolve, ms));
221
+ }
222
+
223
+ class RPCClient {
224
+ baseUrl;
225
+ defaultHeaders;
226
+ defaultTimeout;
227
+ requestInterceptors = [];
228
+ responseInterceptors = [];
229
+ errorInterceptors = [];
230
+ deduplicationConfig;
231
+ optimisticConfig;
232
+ retryConfig;
233
+ deduplicationStore;
234
+ optimisticStore;
235
+ constructor(options) {
236
+ this.baseUrl = options.baseUrl.replace(/\/$/, "");
237
+ this.defaultHeaders = {
238
+ "Content-Type": "application/json",
239
+ ...options.headers
240
+ };
241
+ this.defaultTimeout = options.timeout ?? 30000;
242
+ if (options.interceptors?.request) {
243
+ this.requestInterceptors = Array.isArray(options.interceptors.request) ? options.interceptors.request : [options.interceptors.request];
244
+ }
245
+ if (options.interceptors?.response) {
246
+ this.responseInterceptors = Array.isArray(options.interceptors.response) ? options.interceptors.response : [options.interceptors.response];
247
+ }
248
+ if (options.interceptors?.error) {
249
+ this.errorInterceptors = Array.isArray(options.interceptors.error) ? options.interceptors.error : [options.interceptors.error];
250
+ }
251
+ this.deduplicationConfig = {
252
+ enabled: options.deduplication?.enabled ?? true,
253
+ ttl: options.deduplication?.ttl ?? 5000,
254
+ keyGenerator: options.deduplication?.keyGenerator ?? defaultKeyGenerator
255
+ };
256
+ this.optimisticConfig = {
257
+ enabled: options.optimisticUpdates?.enabled ?? true,
258
+ autoRollback: options.optimisticUpdates?.autoRollback ?? true,
259
+ onConflict: options.optimisticUpdates?.onConflict ?? "rollback"
260
+ };
261
+ this.retryConfig = {
262
+ enabled: options.retry?.enabled ?? true,
263
+ maxAttempts: options.retry?.maxAttempts ?? 3,
264
+ initialDelay: options.retry?.initialDelay ?? 1000,
265
+ maxDelay: options.retry?.maxDelay ?? 30000,
266
+ backoffMultiplier: options.retry?.backoffMultiplier ?? 2,
267
+ retryableStatusCodes: options.retry?.retryableStatusCodes ?? [
268
+ ...DEFAULT_RETRYABLE_STATUS_CODES
269
+ ],
270
+ retryableErrors: options.retry?.retryableErrors ?? [
271
+ ...DEFAULT_RETRYABLE_ERRORS
272
+ ],
273
+ onRetry: options.retry?.onRetry ?? (() => {}),
274
+ shouldRetry: options.retry?.shouldRetry ?? ((response, error, attempt) => defaultShouldRetry(response, error, attempt, undefined))
275
+ };
276
+ this.deduplicationStore = new DeduplicationStore(this.deduplicationConfig.ttl);
277
+ this.optimisticStore = new OptimisticStore;
278
+ }
279
+ async get(path, options) {
280
+ return this.request("GET", path, undefined, options);
281
+ }
282
+ async post(path, body, options) {
283
+ return this.request("POST", path, body, options);
284
+ }
285
+ async put(path, body, options) {
286
+ return this.request("PUT", path, body, options);
287
+ }
288
+ async patch(path, body, options) {
289
+ return this.request("PATCH", path, body, options);
290
+ }
291
+ async delete(path, options) {
292
+ return this.request("DELETE", path, undefined, options);
293
+ }
294
+ async head(path, options) {
295
+ return this.request("HEAD", path, undefined, options);
296
+ }
297
+ async options(path, options) {
298
+ return this.request("OPTIONS", path, undefined, options);
299
+ }
300
+ async request(method, path, body, options) {
301
+ let url = `${this.baseUrl}${path.startsWith("/") ? path : `/${path}`}`;
302
+ if (options?.query) {
303
+ const searchParams = new URLSearchParams(options.query);
304
+ url += `?${searchParams.toString()}`;
305
+ }
306
+ const skipDeduplication = options?.skipDeduplication || options?.retry?.skipRetry;
307
+ if (this.deduplicationConfig.enabled && method === "GET" && !skipDeduplication) {
308
+ const cacheKey = this.deduplicationConfig.keyGenerator(method, url);
309
+ const cached = this.deduplicationStore.getCached(cacheKey, this.deduplicationConfig.ttl);
310
+ if (cached) {
311
+ return cached;
312
+ }
313
+ }
314
+ if (this.deduplicationConfig.enabled && !skipDeduplication) {
315
+ const bodyStr = body ? JSON.stringify(body) : undefined;
316
+ const dedupeKey = this.deduplicationConfig.keyGenerator(method, url, body);
317
+ const pending = this.deduplicationStore.getPending(dedupeKey, bodyStr);
318
+ if (pending) {
319
+ return pending.promise;
320
+ }
321
+ const requestPromise = this.executeWithRetry(method, url, body, options, dedupeKey);
322
+ this.deduplicationStore.setPending(dedupeKey, requestPromise, bodyStr);
323
+ try {
324
+ const response = await requestPromise;
325
+ return response;
326
+ } finally {
327
+ this.deduplicationStore.removePending(dedupeKey);
328
+ }
329
+ }
330
+ return this.executeWithRetry(method, url, body, options);
331
+ }
332
+ async executeWithRetry(method, url, body, options, cacheKey) {
333
+ const retryOptions = options?.retry;
334
+ const retryEnabled = retryOptions?.enabled ?? this.retryConfig.enabled;
335
+ const skipRetry = retryOptions?.skipRetry ?? false;
336
+ if (!retryEnabled || skipRetry) {
337
+ return this.executeRequest(method, url, body, options, cacheKey);
338
+ }
339
+ const maxAttempts = retryOptions?.maxAttempts ?? this.retryConfig.maxAttempts;
340
+ const initialDelay = retryOptions?.initialDelay ?? this.retryConfig.initialDelay;
341
+ const state = {
342
+ attempt: 0,
343
+ lastError: null,
344
+ totalDelay: 0
345
+ };
346
+ while (state.attempt < maxAttempts) {
347
+ state.attempt++;
348
+ try {
349
+ const response = await this.executeRequest(method, url, body, options, cacheKey);
350
+ if (response.ok || !this.shouldRetryResponse(response, null, state.attempt, maxAttempts)) {
351
+ return response;
352
+ }
353
+ if (state.attempt < maxAttempts) {
354
+ const delay = calculateDelay(state.attempt, {
355
+ ...this.retryConfig,
356
+ initialDelay,
357
+ maxAttempts
358
+ });
359
+ this.retryConfig.onRetry(state.attempt, null, delay);
360
+ await sleep(delay);
361
+ state.totalDelay += delay;
362
+ } else {
363
+ return response;
364
+ }
365
+ } catch (error) {
366
+ state.lastError = error instanceof Error ? error : new Error(String(error));
367
+ if (state.attempt < maxAttempts && this.shouldRetryError(state.lastError, state.attempt, maxAttempts)) {
368
+ const delay = calculateDelay(state.attempt, {
369
+ ...this.retryConfig,
370
+ initialDelay,
371
+ maxAttempts
372
+ });
373
+ this.retryConfig.onRetry(state.attempt, state.lastError, delay);
374
+ await sleep(delay);
375
+ state.totalDelay += delay;
376
+ } else {
377
+ throw state.lastError;
378
+ }
379
+ }
380
+ }
381
+ throw state.lastError || new Error("Max retry attempts exceeded");
382
+ }
383
+ shouldRetryResponse(response, error, attempt, maxAttempts) {
384
+ if (attempt >= maxAttempts) {
385
+ return false;
386
+ }
387
+ return this.retryConfig.shouldRetry(response, error, attempt);
388
+ }
389
+ shouldRetryError(error, attempt, maxAttempts) {
390
+ if (attempt >= maxAttempts) {
391
+ return false;
392
+ }
393
+ return this.retryConfig.shouldRetry(null, error, attempt);
394
+ }
395
+ async executeRequest(method, url, body, options, cacheKey) {
396
+ let config = {
397
+ method,
398
+ headers: {
399
+ ...this.defaultHeaders,
400
+ ...options?.headers
401
+ }
402
+ };
403
+ if (body && !["GET", "HEAD", "OPTIONS"].includes(method)) {
404
+ config.body = JSON.stringify(body);
405
+ }
406
+ const context = { url, method, requestInit: config };
407
+ for (const interceptor of this.requestInterceptors) {
408
+ const interceptorContext = {
409
+ ...config,
410
+ url,
411
+ method
412
+ };
413
+ const result = await interceptor(interceptorContext);
414
+ config = result;
415
+ context.requestInit = config;
416
+ }
417
+ const controller = new AbortController;
418
+ const timeout = options?.timeout ?? this.defaultTimeout;
419
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
420
+ config.signal = controller.signal;
421
+ try {
422
+ let response = await fetch(url, config);
423
+ if (cacheKey && method === "GET" && response.ok) {
424
+ this.deduplicationStore.setCached(cacheKey, response, this.deduplicationConfig.ttl);
425
+ }
426
+ for (const interceptor of this.responseInterceptors) {
427
+ response = await interceptor(response, context);
428
+ }
429
+ return response;
430
+ } catch (error) {
431
+ const processedError = error instanceof Error ? error : new Error(String(error));
432
+ for (const interceptor of this.errorInterceptors) {
433
+ const result = await interceptor(processedError, context);
434
+ if (result instanceof Response) {
435
+ return result;
436
+ }
437
+ }
438
+ if (processedError.name === "AbortError") {
439
+ throw new Error(`Request timeout after ${timeout}ms`);
440
+ }
441
+ throw processedError;
442
+ } finally {
443
+ clearTimeout(timeoutId);
444
+ }
445
+ }
446
+ async optimistic(method, path, options) {
447
+ if (!this.optimisticConfig.enabled) {
448
+ const response = await this.request(method, path, options?.body, options);
449
+ return { response };
450
+ }
451
+ const cacheKey = options?.cacheKey ?? path;
452
+ const previousData = this.deduplicationStore.getCachedData(cacheKey);
453
+ const optimisticData = options?.optimisticData;
454
+ let rollbackId;
455
+ if (optimisticData !== undefined) {
456
+ rollbackId = this.optimisticStore.create(cacheKey, optimisticData, previousData, { onRollback: options?.onRollback, onConfirm: options?.onConfirm });
457
+ this.deduplicationStore.setCachedData(cacheKey, optimisticData, this.deduplicationConfig.ttl);
458
+ }
459
+ try {
460
+ const response = await this.request(method, path, options?.body, {
461
+ query: options?.query,
462
+ headers: options?.headers,
463
+ skipDeduplication: true
464
+ });
465
+ if (response.ok) {
466
+ if (rollbackId) {
467
+ const responseData = await response.clone().json().catch(() => {
468
+ return;
469
+ });
470
+ this.optimisticStore.confirm(rollbackId, responseData);
471
+ }
472
+ return { response, rollbackId };
473
+ }
474
+ if (rollbackId && this.optimisticConfig.autoRollback) {
475
+ this.rollback(rollbackId);
476
+ }
477
+ return { response, rollbackId };
478
+ } catch (error) {
479
+ if (rollbackId && this.optimisticConfig.autoRollback) {
480
+ this.rollback(rollbackId);
481
+ }
482
+ throw error;
483
+ }
484
+ }
485
+ async optimisticPost(path, body, options) {
486
+ return this.optimistic("POST", path, { body, ...options });
487
+ }
488
+ async optimisticPut(path, body, options) {
489
+ return this.optimistic("PUT", path, { body, ...options });
490
+ }
491
+ async optimisticPatch(path, body, options) {
492
+ return this.optimistic("PATCH", path, { body, ...options });
493
+ }
494
+ async optimisticDelete(path, options) {
495
+ return this.optimistic("DELETE", path, options);
496
+ }
497
+ rollback(rollbackId) {
498
+ const previousData = this.optimisticStore.rollback(rollbackId);
499
+ if (previousData !== undefined) {
500
+ const update = this.optimisticStore.get(rollbackId);
501
+ if (update) {
502
+ this.deduplicationStore.setCachedData(update.cacheKey, previousData, this.deduplicationConfig.ttl);
503
+ }
504
+ }
505
+ return previousData;
506
+ }
507
+ confirm(rollbackId, serverData) {
508
+ this.optimisticStore.confirm(rollbackId, serverData);
509
+ }
510
+ hasPendingOptimisticUpdate(cacheKey) {
511
+ return this.optimisticStore.hasPending(cacheKey);
512
+ }
513
+ getOptimisticData(cacheKey) {
514
+ return this.optimisticStore.getOptimisticData(cacheKey);
515
+ }
516
+ getPendingOptimisticCount() {
517
+ return this.optimisticStore.getStats().pending;
518
+ }
519
+ clearOptimisticUpdates() {
520
+ this.optimisticStore.clear();
521
+ }
522
+ async withRetry(method, path, body, retryOptions) {
523
+ return this.request(method, path, body, { retry: retryOptions });
524
+ }
525
+ isRetryEnabled() {
526
+ return this.retryConfig.enabled;
527
+ }
528
+ getMaxRetryAttempts() {
529
+ return this.retryConfig.maxAttempts;
530
+ }
531
+ getRetryConfig() {
532
+ return { ...this.retryConfig };
533
+ }
534
+ addRequestInterceptor(interceptor) {
535
+ this.requestInterceptors.push(interceptor);
536
+ }
537
+ addResponseInterceptor(interceptor) {
538
+ this.responseInterceptors.push(interceptor);
539
+ }
540
+ addErrorInterceptor(interceptor) {
541
+ this.errorInterceptors.push(interceptor);
542
+ }
543
+ removeRequestInterceptor(interceptor) {
544
+ const index = this.requestInterceptors.indexOf(interceptor);
545
+ if (index > -1) {
546
+ this.requestInterceptors.splice(index, 1);
547
+ return true;
548
+ }
549
+ return false;
550
+ }
551
+ removeResponseInterceptor(interceptor) {
552
+ const index = this.responseInterceptors.indexOf(interceptor);
553
+ if (index > -1) {
554
+ this.responseInterceptors.splice(index, 1);
555
+ return true;
556
+ }
557
+ return false;
558
+ }
559
+ removeErrorInterceptor(interceptor) {
560
+ const index = this.errorInterceptors.indexOf(interceptor);
561
+ if (index > -1) {
562
+ this.errorInterceptors.splice(index, 1);
563
+ return true;
564
+ }
565
+ return false;
566
+ }
567
+ clearInterceptors() {
568
+ this.requestInterceptors = [];
569
+ this.responseInterceptors = [];
570
+ this.errorInterceptors = [];
571
+ }
572
+ getInterceptorStats() {
573
+ return {
574
+ request: this.requestInterceptors.length,
575
+ response: this.responseInterceptors.length,
576
+ error: this.errorInterceptors.length
577
+ };
578
+ }
579
+ withInterceptors(interceptors) {
580
+ return new RPCClient({
581
+ baseUrl: this.baseUrl,
582
+ headers: this.defaultHeaders,
583
+ timeout: this.defaultTimeout,
584
+ deduplication: this.deduplicationConfig,
585
+ optimisticUpdates: this.optimisticConfig,
586
+ retry: this.retryConfig,
587
+ interceptors
588
+ });
589
+ }
590
+ withBaseUrl(baseUrl) {
591
+ const interceptors = {};
592
+ if (this.requestInterceptors.length > 0)
593
+ interceptors.request = this.requestInterceptors;
594
+ if (this.responseInterceptors.length > 0)
595
+ interceptors.response = this.responseInterceptors;
596
+ if (this.errorInterceptors.length > 0)
597
+ interceptors.error = this.errorInterceptors;
598
+ return new RPCClient({
599
+ baseUrl,
600
+ headers: this.defaultHeaders,
601
+ timeout: this.defaultTimeout,
602
+ deduplication: this.deduplicationConfig,
603
+ optimisticUpdates: this.optimisticConfig,
604
+ retry: this.retryConfig,
605
+ interceptors
606
+ });
607
+ }
608
+ withHeaders(headers) {
609
+ const interceptors = {};
610
+ if (this.requestInterceptors.length > 0)
611
+ interceptors.request = this.requestInterceptors;
612
+ if (this.responseInterceptors.length > 0)
613
+ interceptors.response = this.responseInterceptors;
614
+ if (this.errorInterceptors.length > 0)
615
+ interceptors.error = this.errorInterceptors;
616
+ return new RPCClient({
617
+ baseUrl: this.baseUrl,
618
+ headers: { ...this.defaultHeaders, ...headers },
619
+ timeout: this.defaultTimeout,
620
+ deduplication: this.deduplicationConfig,
621
+ optimisticUpdates: this.optimisticConfig,
622
+ retry: this.retryConfig,
623
+ interceptors
624
+ });
625
+ }
626
+ clearCache() {
627
+ this.deduplicationStore.clear();
628
+ }
629
+ clearAllCaches() {
630
+ this.deduplicationStore.clear();
631
+ this.optimisticStore.clear();
632
+ }
633
+ invalidateCache(cacheKey) {
634
+ this.deduplicationStore.invalidate(cacheKey);
635
+ }
636
+ getDeduplicationStats() {
637
+ return this.deduplicationStore.getStats();
638
+ }
639
+ isDeduplicationEnabled() {
640
+ return this.deduplicationConfig.enabled;
641
+ }
642
+ getDeduplicationTTL() {
643
+ return this.deduplicationConfig.ttl;
644
+ }
645
+ isOptimisticUpdatesEnabled() {
646
+ return this.optimisticConfig.enabled;
647
+ }
648
+ }
649
+ function createRPClient(options) {
650
+ return new RPCClient(options);
651
+ }
652
+ function bc(options) {
653
+ return createRPClient(options);
654
+ }
655
+ function extractRouteTypes(router) {
656
+ const routes = router.getRoutes();
657
+ return routes.map((r) => ({
658
+ method: r.method,
659
+ path: r.pattern
660
+ }));
661
+ }
662
+ async function parseJSON(response) {
663
+ return response.json();
664
+ }
665
+ async function parseText(response) {
666
+ return response.text();
667
+ }
668
+ function isOK(response) {
669
+ return response.ok;
670
+ }
671
+ function isStatus(response, status) {
672
+ return response.status === status;
673
+ }
674
+ async function throwIfNotOK(response) {
675
+ if (!response.ok) {
676
+ const error = await response.text();
677
+ throw new Error(`HTTP ${response.status}: ${error}`);
678
+ }
679
+ return response;
680
+ }
681
+ export {
682
+ throwIfNotOK,
683
+ parseText,
684
+ parseJSON,
685
+ isStatus,
686
+ isOK,
687
+ extractRouteTypes,
688
+ createRPClient,
689
+ bc,
690
+ RPCClient
691
+ };