alepha 0.20.6 → 0.20.8
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/AGENTS.md +0 -1
- package/CLAUDE.md +0 -1
- package/assets/agents-template.md +0 -1
- package/dist/api/audits/index.browser.js +1 -0
- package/dist/api/audits/index.browser.js.map +1 -1
- package/dist/api/audits/index.d.ts +370 -355
- package/dist/api/audits/index.d.ts.map +1 -1
- package/dist/api/audits/index.js +1 -0
- package/dist/api/audits/index.js.map +1 -1
- package/dist/api/files/index.browser.js +1 -0
- package/dist/api/files/index.browser.js.map +1 -1
- package/dist/api/files/index.d.ts +179 -170
- package/dist/api/files/index.d.ts.map +1 -1
- package/dist/api/files/index.js +1 -0
- package/dist/api/files/index.js.map +1 -1
- package/dist/api/jobs/index.browser.js +7 -0
- package/dist/api/jobs/index.browser.js.map +1 -1
- package/dist/api/jobs/index.d.ts +259 -250
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/jobs/index.js +21 -3
- package/dist/api/jobs/index.js.map +1 -1
- package/dist/api/keys/index.d.ts +198 -192
- package/dist/api/keys/index.d.ts.map +1 -1
- package/dist/api/keys/index.js +1 -0
- package/dist/api/keys/index.js.map +1 -1
- package/dist/api/notifications/index.d.ts +246 -245
- package/dist/api/notifications/index.d.ts.map +1 -1
- package/dist/api/organizations/index.d.ts +100 -97
- package/dist/api/organizations/index.d.ts.map +1 -1
- package/dist/api/parameters/index.d.ts +323 -320
- package/dist/api/parameters/index.d.ts.map +1 -1
- package/dist/api/payments/index.d.ts +431 -376
- package/dist/api/payments/index.d.ts.map +1 -1
- package/dist/api/payments/index.js +202 -87
- package/dist/api/payments/index.js.map +1 -1
- package/dist/api/subscriptions/index.d.ts +1695 -0
- package/dist/api/subscriptions/index.d.ts.map +1 -0
- package/dist/api/subscriptions/index.js +1919 -0
- package/dist/api/subscriptions/index.js.map +1 -0
- package/dist/api/users/index.d.ts +857 -841
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/verifications/index.d.ts +128 -127
- package/dist/api/verifications/index.d.ts.map +1 -1
- package/dist/bucket/index.d.ts +3 -2
- package/dist/bucket/index.d.ts.map +1 -1
- package/dist/cache/core/index.d.ts +114 -4
- package/dist/cache/core/index.d.ts.map +1 -1
- package/dist/cache/core/index.js +181 -15
- package/dist/cache/core/index.js.map +1 -1
- package/dist/cache/core/index.workerd.js +181 -15
- package/dist/cache/core/index.workerd.js.map +1 -1
- package/dist/cache/database/index.d.ts +20 -19
- package/dist/cache/database/index.d.ts.map +1 -1
- package/dist/cache/redis/index.d.ts +3 -2
- package/dist/cache/redis/index.d.ts.map +1 -1
- package/dist/cli/core/index.d.ts +116 -132
- package/dist/cli/core/index.d.ts.map +1 -1
- package/dist/cli/core/index.js +75 -7
- package/dist/cli/core/index.js.map +1 -1
- package/dist/cli/devtools/index.d.ts +3 -2
- package/dist/cli/devtools/index.d.ts.map +1 -1
- package/dist/cli/platform/index.d.ts +346 -290
- package/dist/cli/platform/index.d.ts.map +1 -1
- package/dist/cli/platform/index.js +105 -6
- package/dist/cli/platform/index.js.map +1 -1
- package/dist/cli/vendor/index.d.ts +12 -11
- package/dist/cli/vendor/index.d.ts.map +1 -1
- package/dist/command/index.d.ts +5 -4
- package/dist/command/index.d.ts.map +1 -1
- package/dist/core/index.browser.js +1 -1
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +119 -118
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +1 -1
- package/dist/core/index.native.js.map +1 -1
- package/dist/core/index.workerd.js +1 -1
- package/dist/core/index.workerd.js.map +1 -1
- package/dist/crypto/index.d.ts +3 -2
- package/dist/crypto/index.d.ts.map +1 -1
- package/dist/email/core/index.d.ts +3 -2
- package/dist/email/core/index.d.ts.map +1 -1
- package/dist/email/smtp/index.d.ts +7 -6
- package/dist/email/smtp/index.d.ts.map +1 -1
- package/dist/lock/core/index.d.ts +5 -4
- package/dist/lock/core/index.d.ts.map +1 -1
- package/dist/logger/index.d.ts +10 -9
- package/dist/logger/index.d.ts.map +1 -1
- package/dist/mcp/index.d.ts +9 -8
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +1 -1
- package/dist/mcp/index.js.map +1 -1
- package/dist/orm/core/index.browser.js +9 -3
- package/dist/orm/core/index.browser.js.map +1 -1
- package/dist/orm/core/index.bun.js +31 -10
- package/dist/orm/core/index.bun.js.map +1 -1
- package/dist/orm/core/index.d.ts +33 -14
- package/dist/orm/core/index.d.ts.map +1 -1
- package/dist/orm/core/index.js +31 -10
- package/dist/orm/core/index.js.map +1 -1
- package/dist/orm/postgres/index.d.ts +6 -5
- package/dist/orm/postgres/index.d.ts.map +1 -1
- package/dist/queue/core/index.d.ts +5 -4
- package/dist/queue/core/index.d.ts.map +1 -1
- package/dist/queue/redis/index.d.ts +3 -2
- package/dist/queue/redis/index.d.ts.map +1 -1
- package/dist/react/form/index.d.ts +5 -0
- package/dist/react/form/index.d.ts.map +1 -1
- package/dist/react/form/index.js +6 -4
- package/dist/react/form/index.js.map +1 -1
- package/dist/react/i18n/index.d.ts +2 -1
- package/dist/react/i18n/index.d.ts.map +1 -1
- package/dist/react/router/index.d.ts +206 -205
- package/dist/react/router/index.d.ts.map +1 -1
- package/dist/react/ui/index.d.ts +11 -11
- package/dist/react/ui/index.d.ts.map +1 -1
- package/dist/scheduler/index.d.ts +3 -2
- package/dist/scheduler/index.d.ts.map +1 -1
- package/dist/security/index.browser.js +29 -1
- package/dist/security/index.browser.js.map +1 -1
- package/dist/security/index.d.ts +82 -35
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +56 -3
- package/dist/security/index.js.map +1 -1
- package/dist/server/auth/index.d.ts +163 -158
- package/dist/server/auth/index.d.ts.map +1 -1
- package/dist/server/auth/index.js +16 -4
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/core/index.d.ts +35 -34
- package/dist/server/core/index.d.ts.map +1 -1
- package/dist/server/cors/index.d.ts +7 -6
- package/dist/server/cors/index.d.ts.map +1 -1
- package/dist/server/health/index.d.ts +16 -15
- package/dist/server/health/index.d.ts.map +1 -1
- package/dist/server/links/index.d.ts +51 -50
- package/dist/server/links/index.d.ts.map +1 -1
- package/dist/server/rate-limit/index.d.ts +6 -5
- package/dist/server/rate-limit/index.d.ts.map +1 -1
- package/dist/server/swagger/index.d.ts +2 -1
- package/dist/server/swagger/index.d.ts.map +1 -1
- package/dist/topic/redis/index.d.ts +3 -2
- package/dist/topic/redis/index.d.ts.map +1 -1
- package/package.json +16 -32
- package/src/api/audits/entities/audits.ts +1 -0
- package/src/api/files/entities/files.ts +1 -0
- package/src/api/jobs/__tests__/$job.spec.ts +92 -40
- package/src/api/jobs/entities/jobExecutionEntity.ts +1 -0
- package/src/api/jobs/providers/JobProvider.ts +20 -5
- package/src/api/jobs/schemas/jobConfigAtom.ts +5 -0
- package/src/api/keys/entities/apiKeyEntity.ts +1 -0
- package/src/api/payments/controllers/MockCheckoutController.ts +146 -0
- package/src/api/payments/index.ts +3 -0
- package/src/api/payments/providers/MemoryPaymentProvider.ts +9 -4
- package/src/api/payments/providers/PaymentProvider.ts +25 -9
- package/src/api/payments/services/PaymentService.ts +3 -0
- package/src/api/subscriptions/__tests__/BillingService.spec.ts +218 -0
- package/src/api/subscriptions/__tests__/SubscriptionService.spec.ts +278 -0
- package/src/api/subscriptions/controllers/AdminSubscriptionController.ts +212 -0
- package/src/api/subscriptions/controllers/SubscriptionController.ts +189 -0
- package/src/api/subscriptions/entities/subscriptionEvents.ts +54 -0
- package/src/api/subscriptions/entities/subscriptions.ts +68 -0
- package/src/api/subscriptions/index.ts +133 -0
- package/src/api/subscriptions/jobs/SubscriptionJobs.ts +382 -0
- package/src/api/subscriptions/middleware/$requireLimit.ts +50 -0
- package/src/api/subscriptions/middleware/$requirePlan.ts +49 -0
- package/src/api/subscriptions/notifications/SubscriptionNotifications.ts +110 -0
- package/src/api/subscriptions/schemas/cancelSubscriptionSchema.ts +8 -0
- package/src/api/subscriptions/schemas/changePlanSchema.ts +9 -0
- package/src/api/subscriptions/schemas/createSubscriptionSchema.ts +11 -0
- package/src/api/subscriptions/schemas/entitlementsSchema.ts +21 -0
- package/src/api/subscriptions/schemas/mrrSchema.ts +13 -0
- package/src/api/subscriptions/schemas/planDefinitionSchema.ts +71 -0
- package/src/api/subscriptions/schemas/planResourceSchema.ts +25 -0
- package/src/api/subscriptions/schemas/subscriptionEventResourceSchema.ts +8 -0
- package/src/api/subscriptions/schemas/subscriptionQuerySchema.ts +19 -0
- package/src/api/subscriptions/schemas/subscriptionResourceSchema.ts +6 -0
- package/src/api/subscriptions/schemas/subscriptionSettingsSchema.ts +32 -0
- package/src/api/subscriptions/schemas/subscriptionStatsSchema.ts +23 -0
- package/src/api/subscriptions/services/BillingService.ts +437 -0
- package/src/api/subscriptions/services/SubscriptionConfig.ts +56 -0
- package/src/api/subscriptions/services/SubscriptionService.ts +867 -0
- package/src/api/subscriptions/services/UsageService.ts +118 -0
- package/src/cache/core/__tests__/$cache.memory.spec.ts +450 -0
- package/src/cache/core/__tests__/$cache.swr.spec.ts +394 -0
- package/src/cache/core/index.ts +16 -0
- package/src/cache/core/primitives/$cache.ts +347 -21
- package/src/cli/core/tasks/BuildCloudflareTask.ts +16 -0
- package/src/cli/core/templates/agentMd.ts +39 -4
- package/src/cli/core/templates/biomeJson.ts +25 -1
- package/src/cli/core/templates/saasAdminLayoutTsx.ts +2 -2
- package/src/cli/platform/__tests__/CloudflareAdapter.spec.ts +117 -0
- package/src/cli/platform/adapters/CloudflareAdapter.ts +104 -7
- package/src/cli/platform/atoms/platformOptions.ts +13 -0
- package/src/cli/platform/schemas/platform.ts +1 -0
- package/src/cli/platform/services/CloudflareApi.ts +61 -0
- package/src/cli/platform/services/PlatformOrchestrator.ts +9 -4
- package/src/core/__tests__/$module.spec.ts +2 -2
- package/src/core/primitives/$module.ts +4 -4
- package/src/mcp/providers/McpServerProvider.ts +1 -1
- package/src/orm/core/providers/DatabaseTypeProvider.ts +9 -3
- package/src/orm/core/providers/drivers/DatabaseProvider.ts +1 -1
- package/src/orm/core/schemas/insertSchema.ts +10 -2
- package/src/orm/core/services/Repository.ts +27 -7
- package/src/react/form/hooks/useFormState.ts +8 -1
- package/src/react/form/index.ts +10 -1
- package/src/react/form/services/FormModel.ts +9 -3
- package/src/security/atoms/currentTenantAtom.ts +34 -0
- package/src/security/index.browser.ts +1 -0
- package/src/security/index.ts +12 -1
- package/src/security/primitives/$issuer.ts +17 -1
- package/src/security/providers/SecurityProvider.ts +37 -0
- package/src/server/auth/__tests__/validateRedirectUri.spec.ts +78 -0
- package/src/server/auth/providers/ServerAuthProvider.ts +21 -5
- package/tsconfig.base.json +2 -1
- package/dist/react/websocket/index.d.ts +0 -117
- package/dist/react/websocket/index.d.ts.map +0 -1
- package/dist/react/websocket/index.js +0 -108
- package/dist/react/websocket/index.js.map +0 -1
- package/dist/websocket/index.browser.js +0 -848
- package/dist/websocket/index.browser.js.map +0 -1
- package/dist/websocket/index.d.ts +0 -876
- package/dist/websocket/index.d.ts.map +0 -1
- package/dist/websocket/index.js +0 -1185
- package/dist/websocket/index.js.map +0 -1
- package/src/react/websocket/hooks/useRoom.tsx +0 -251
- package/src/react/websocket/index.ts +0 -7
- package/src/websocket/__tests__/$channel.spec.ts +0 -30
- package/src/websocket/__tests__/$websocket-new.spec.ts +0 -195
- package/src/websocket/__tests__/RoomManager.spec.ts +0 -146
- package/src/websocket/__tests__/websocket-integration.spec.ts +0 -951
- package/src/websocket/errors/WebSocketError.ts +0 -34
- package/src/websocket/index.browser.ts +0 -25
- package/src/websocket/index.shared.ts +0 -8
- package/src/websocket/index.ts +0 -85
- package/src/websocket/interfaces/WebSocketInterfaces.ts +0 -252
- package/src/websocket/primitives/$channel.ts +0 -131
- package/src/websocket/primitives/$websocket.ts +0 -107
- package/src/websocket/providers/NodeWebSocketServerProvider.ts +0 -617
- package/src/websocket/providers/WebSocketServerProvider.ts +0 -56
- package/src/websocket/services/RoomManager.ts +0 -160
- package/src/websocket/services/WebSocketClient.ts +0 -642
- package/src/websocket/services/WebSocketTopicService.ts +0 -108
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
import { Alepha } from "alepha";
|
|
2
|
+
import { DateTimeProvider } from "alepha/datetime";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { $cache, CacheProvider, MemoryCacheProvider } from "../index.ts";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Tests for stale-while-revalidate (`stale` option).
|
|
8
|
+
*
|
|
9
|
+
* After `ttl` expires, the cached value remains servable for `stale`
|
|
10
|
+
* longer; reads in the grace window return the stale value immediately
|
|
11
|
+
* and trigger ONE background refresh (single-flight per key).
|
|
12
|
+
*/
|
|
13
|
+
describe("$cache stale-while-revalidate (SWR)", () => {
|
|
14
|
+
it("returns fresh value without scheduling a refresh", async () => {
|
|
15
|
+
let calls = 0;
|
|
16
|
+
class App {
|
|
17
|
+
cache = $cache({
|
|
18
|
+
ttl: [5, "seconds"],
|
|
19
|
+
stale: [1, "hour"],
|
|
20
|
+
handler: async () => {
|
|
21
|
+
calls++;
|
|
22
|
+
return `v:${calls}`;
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const alepha = Alepha.create().with({
|
|
28
|
+
provide: CacheProvider,
|
|
29
|
+
use: MemoryCacheProvider,
|
|
30
|
+
});
|
|
31
|
+
const app = alepha.inject(App);
|
|
32
|
+
const time = alepha.inject(DateTimeProvider);
|
|
33
|
+
await alepha.start();
|
|
34
|
+
|
|
35
|
+
expect(await app.cache()).toBe("v:1");
|
|
36
|
+
await time.travel([3, "seconds"]); // still fresh
|
|
37
|
+
expect(await app.cache()).toBe("v:1");
|
|
38
|
+
expect(await app.cache()).toBe("v:1");
|
|
39
|
+
expect(calls).toBe(1);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("returns stale value and schedules a background refresh", async () => {
|
|
43
|
+
let calls = 0;
|
|
44
|
+
class App {
|
|
45
|
+
cache = $cache({
|
|
46
|
+
ttl: [5, "seconds"],
|
|
47
|
+
stale: [1, "hour"],
|
|
48
|
+
handler: async () => {
|
|
49
|
+
calls++;
|
|
50
|
+
return `v:${calls}`;
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const alepha = Alepha.create().with({
|
|
56
|
+
provide: CacheProvider,
|
|
57
|
+
use: MemoryCacheProvider,
|
|
58
|
+
});
|
|
59
|
+
const app = alepha.inject(App);
|
|
60
|
+
const time = alepha.inject(DateTimeProvider);
|
|
61
|
+
await alepha.start();
|
|
62
|
+
|
|
63
|
+
expect(await app.cache()).toBe("v:1");
|
|
64
|
+
await time.travel([10, "seconds"]); // past ttl, within stale
|
|
65
|
+
|
|
66
|
+
// Stale value served immediately
|
|
67
|
+
expect(await app.cache()).toBe("v:1");
|
|
68
|
+
|
|
69
|
+
// Wait for background refresh to complete
|
|
70
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
71
|
+
|
|
72
|
+
expect(calls).toBe(2);
|
|
73
|
+
// Next read returns the refreshed value
|
|
74
|
+
expect(await app.cache()).toBe("v:2");
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("single-flight: concurrent stale reads trigger one refresh", async () => {
|
|
78
|
+
let calls = 0;
|
|
79
|
+
class App {
|
|
80
|
+
cache = $cache({
|
|
81
|
+
ttl: [5, "seconds"],
|
|
82
|
+
stale: [1, "hour"],
|
|
83
|
+
handler: async () => {
|
|
84
|
+
calls++;
|
|
85
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
86
|
+
return `v:${calls}`;
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const alepha = Alepha.create().with({
|
|
92
|
+
provide: CacheProvider,
|
|
93
|
+
use: MemoryCacheProvider,
|
|
94
|
+
});
|
|
95
|
+
const app = alepha.inject(App);
|
|
96
|
+
const time = alepha.inject(DateTimeProvider);
|
|
97
|
+
await alepha.start();
|
|
98
|
+
|
|
99
|
+
expect(await app.cache()).toBe("v:1");
|
|
100
|
+
await time.travel([10, "seconds"]);
|
|
101
|
+
|
|
102
|
+
// 10 concurrent reads
|
|
103
|
+
const results = await Promise.all(
|
|
104
|
+
Array.from({ length: 10 }, () => app.cache()),
|
|
105
|
+
);
|
|
106
|
+
expect(results.every((r) => r === "v:1")).toBe(true);
|
|
107
|
+
|
|
108
|
+
await new Promise((r) => setTimeout(r, 30));
|
|
109
|
+
expect(calls).toBe(2); // 1 original + 1 background refresh
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("single-flight: concurrent cold reads trigger one handler call", async () => {
|
|
113
|
+
let calls = 0;
|
|
114
|
+
class App {
|
|
115
|
+
cache = $cache({
|
|
116
|
+
ttl: [5, "seconds"],
|
|
117
|
+
stale: [1, "hour"],
|
|
118
|
+
handler: async () => {
|
|
119
|
+
calls++;
|
|
120
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
121
|
+
return `v:${calls}`;
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const alepha = Alepha.create().with({
|
|
127
|
+
provide: CacheProvider,
|
|
128
|
+
use: MemoryCacheProvider,
|
|
129
|
+
});
|
|
130
|
+
const app = alepha.inject(App);
|
|
131
|
+
await alepha.start();
|
|
132
|
+
|
|
133
|
+
const results = await Promise.all(
|
|
134
|
+
Array.from({ length: 10 }, () => app.cache()),
|
|
135
|
+
);
|
|
136
|
+
expect(results.every((r) => r === "v:1")).toBe(true);
|
|
137
|
+
expect(calls).toBe(1);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("preserves stale value when background refresh fails", async () => {
|
|
141
|
+
let calls = 0;
|
|
142
|
+
class App {
|
|
143
|
+
cache = $cache({
|
|
144
|
+
ttl: [5, "seconds"],
|
|
145
|
+
stale: [1, "hour"],
|
|
146
|
+
handler: async () => {
|
|
147
|
+
calls++;
|
|
148
|
+
if (calls > 1) {
|
|
149
|
+
throw new Error("upstream down");
|
|
150
|
+
}
|
|
151
|
+
return `v:${calls}`;
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const alepha = Alepha.create().with({
|
|
157
|
+
provide: CacheProvider,
|
|
158
|
+
use: MemoryCacheProvider,
|
|
159
|
+
});
|
|
160
|
+
const app = alepha.inject(App);
|
|
161
|
+
const time = alepha.inject(DateTimeProvider);
|
|
162
|
+
await alepha.start();
|
|
163
|
+
|
|
164
|
+
expect(await app.cache()).toBe("v:1");
|
|
165
|
+
await time.travel([10, "seconds"]);
|
|
166
|
+
|
|
167
|
+
// Stale read should succeed even though refresh fails in background.
|
|
168
|
+
expect(await app.cache()).toBe("v:1");
|
|
169
|
+
|
|
170
|
+
// Wait for failed refresh
|
|
171
|
+
await new Promise((r) => setTimeout(r, 30));
|
|
172
|
+
|
|
173
|
+
// Next read returns stale value again, attempts another refresh.
|
|
174
|
+
expect(await app.cache()).toBe("v:1");
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("treats values past ttl + stale as miss (synchronous handler call)", async () => {
|
|
178
|
+
let calls = 0;
|
|
179
|
+
class App {
|
|
180
|
+
cache = $cache({
|
|
181
|
+
ttl: [5, "seconds"],
|
|
182
|
+
stale: [10, "seconds"],
|
|
183
|
+
handler: async () => {
|
|
184
|
+
calls++;
|
|
185
|
+
return `v:${calls}`;
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const alepha = Alepha.create().with({
|
|
191
|
+
provide: CacheProvider,
|
|
192
|
+
use: MemoryCacheProvider,
|
|
193
|
+
});
|
|
194
|
+
const app = alepha.inject(App);
|
|
195
|
+
const time = alepha.inject(DateTimeProvider);
|
|
196
|
+
await alepha.start();
|
|
197
|
+
|
|
198
|
+
expect(await app.cache()).toBe("v:1");
|
|
199
|
+
|
|
200
|
+
// Past ttl + stale -> provider expires the entry entirely
|
|
201
|
+
await time.travel([20, "seconds"]);
|
|
202
|
+
|
|
203
|
+
expect(await app.cache()).toBe("v:2");
|
|
204
|
+
expect(calls).toBe(2);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it("set() resets the fresh deadline and clears pending refresh", async () => {
|
|
208
|
+
let calls = 0;
|
|
209
|
+
class App {
|
|
210
|
+
cache = $cache<string>({
|
|
211
|
+
ttl: [5, "seconds"],
|
|
212
|
+
stale: [1, "hour"],
|
|
213
|
+
handler: () => {
|
|
214
|
+
calls++;
|
|
215
|
+
return `v:${calls}`;
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const alepha = Alepha.create().with({
|
|
221
|
+
provide: CacheProvider,
|
|
222
|
+
use: MemoryCacheProvider,
|
|
223
|
+
});
|
|
224
|
+
const app = alepha.inject(App);
|
|
225
|
+
const time = alepha.inject(DateTimeProvider);
|
|
226
|
+
await alepha.start();
|
|
227
|
+
|
|
228
|
+
expect(await app.cache()).toBe("v:1");
|
|
229
|
+
await time.travel([10, "seconds"]); // stale
|
|
230
|
+
|
|
231
|
+
await app.cache.set(app.cache.key(), "manual");
|
|
232
|
+
// After manual set, fresh again -> no refresh needed
|
|
233
|
+
expect(await app.cache()).toBe("manual");
|
|
234
|
+
expect(await app.cache()).toBe("manual");
|
|
235
|
+
expect(calls).toBe(1); // handler never called for refresh
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it("composes with L1 memory tier", async () => {
|
|
239
|
+
let calls = 0;
|
|
240
|
+
class App {
|
|
241
|
+
cache = $cache({
|
|
242
|
+
ttl: [10, "minutes"],
|
|
243
|
+
stale: [1, "hour"],
|
|
244
|
+
memory: { ttl: [5, "seconds"] },
|
|
245
|
+
handler: async () => {
|
|
246
|
+
calls++;
|
|
247
|
+
return `v:${calls}`;
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const alepha = Alepha.create().with({
|
|
253
|
+
provide: CacheProvider,
|
|
254
|
+
use: MemoryCacheProvider,
|
|
255
|
+
});
|
|
256
|
+
const app = alepha.inject(App);
|
|
257
|
+
const provider = alepha.inject(CacheProvider) as MemoryCacheProvider;
|
|
258
|
+
await alepha.start();
|
|
259
|
+
|
|
260
|
+
expect(await app.cache()).toBe("v:1");
|
|
261
|
+
const getsAfterFirst = provider.getCalls.length;
|
|
262
|
+
|
|
263
|
+
// L1 serves the next reads.
|
|
264
|
+
expect(await app.cache()).toBe("v:1");
|
|
265
|
+
expect(await app.cache()).toBe("v:1");
|
|
266
|
+
expect(provider.getCalls.length).toBe(getsAfterFirst);
|
|
267
|
+
expect(calls).toBe(1);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it("works in middleware mode (handler from the pipeline)", async () => {
|
|
271
|
+
let calls = 0;
|
|
272
|
+
class App {
|
|
273
|
+
cache = $cache<string>({
|
|
274
|
+
name: "mw-swr",
|
|
275
|
+
ttl: [5, "seconds"],
|
|
276
|
+
stale: [1, "hour"],
|
|
277
|
+
});
|
|
278
|
+
fn = this.cache(async (id: string) => {
|
|
279
|
+
calls++;
|
|
280
|
+
return `v:${id}:${calls}`;
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const alepha = Alepha.create().with({
|
|
285
|
+
provide: CacheProvider,
|
|
286
|
+
use: MemoryCacheProvider,
|
|
287
|
+
});
|
|
288
|
+
const app = alepha.inject(App);
|
|
289
|
+
const time = alepha.inject(DateTimeProvider);
|
|
290
|
+
await alepha.start();
|
|
291
|
+
|
|
292
|
+
expect(await app.fn("a")).toBe("v:a:1");
|
|
293
|
+
await time.travel([10, "seconds"]);
|
|
294
|
+
|
|
295
|
+
// Stale served + background refresh
|
|
296
|
+
expect(await app.fn("a")).toBe("v:a:1");
|
|
297
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
298
|
+
expect(calls).toBe(2);
|
|
299
|
+
expect(await app.fn("a")).toBe("v:a:2");
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it("emits cache:stale and cache:revalidate events", async () => {
|
|
303
|
+
let calls = 0;
|
|
304
|
+
class App {
|
|
305
|
+
cache = $cache({
|
|
306
|
+
ttl: [5, "seconds"],
|
|
307
|
+
stale: [1, "hour"],
|
|
308
|
+
handler: async () => {
|
|
309
|
+
calls++;
|
|
310
|
+
return `v:${calls}`;
|
|
311
|
+
},
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const alepha = Alepha.create().with({
|
|
316
|
+
provide: CacheProvider,
|
|
317
|
+
use: MemoryCacheProvider,
|
|
318
|
+
});
|
|
319
|
+
const app = alepha.inject(App);
|
|
320
|
+
const time = alepha.inject(DateTimeProvider);
|
|
321
|
+
await alepha.start();
|
|
322
|
+
|
|
323
|
+
const staleEvents: string[] = [];
|
|
324
|
+
const revalidateEvents: string[] = [];
|
|
325
|
+
alepha.events.on("cache:stale", (e) => {
|
|
326
|
+
staleEvents.push(e.key);
|
|
327
|
+
});
|
|
328
|
+
alepha.events.on("cache:revalidate", (e) => {
|
|
329
|
+
revalidateEvents.push(e.key);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
await app.cache();
|
|
333
|
+
await time.travel([10, "seconds"]);
|
|
334
|
+
await app.cache();
|
|
335
|
+
|
|
336
|
+
await new Promise((r) => setTimeout(r, 30));
|
|
337
|
+
|
|
338
|
+
expect(staleEvents.length).toBeGreaterThanOrEqual(1);
|
|
339
|
+
expect(revalidateEvents.length).toBeGreaterThanOrEqual(1);
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it("manual mode (cache.get without handler) returns stale value but does not refresh", async () => {
|
|
343
|
+
class App {
|
|
344
|
+
cache = $cache<string>({
|
|
345
|
+
ttl: [5, "seconds"],
|
|
346
|
+
stale: [1, "hour"],
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const alepha = Alepha.create().with({
|
|
351
|
+
provide: CacheProvider,
|
|
352
|
+
use: MemoryCacheProvider,
|
|
353
|
+
});
|
|
354
|
+
const app = alepha.inject(App);
|
|
355
|
+
const time = alepha.inject(DateTimeProvider);
|
|
356
|
+
await alepha.start();
|
|
357
|
+
|
|
358
|
+
await app.cache.set("k", "value");
|
|
359
|
+
await time.travel([10, "seconds"]);
|
|
360
|
+
|
|
361
|
+
// Stale served. No refresh possible (no handler).
|
|
362
|
+
expect(await app.cache.get("k")).toBe("value");
|
|
363
|
+
expect(await app.cache.get("k")).toBe("value");
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it("stale option off behaves like a regular cache", async () => {
|
|
367
|
+
let calls = 0;
|
|
368
|
+
class App {
|
|
369
|
+
cache = $cache({
|
|
370
|
+
ttl: [5, "seconds"],
|
|
371
|
+
handler: async () => {
|
|
372
|
+
calls++;
|
|
373
|
+
return `v:${calls}`;
|
|
374
|
+
},
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const alepha = Alepha.create().with({
|
|
379
|
+
provide: CacheProvider,
|
|
380
|
+
use: MemoryCacheProvider,
|
|
381
|
+
});
|
|
382
|
+
const app = alepha.inject(App);
|
|
383
|
+
const time = alepha.inject(DateTimeProvider);
|
|
384
|
+
await alepha.start();
|
|
385
|
+
|
|
386
|
+
expect(await app.cache()).toBe("v:1");
|
|
387
|
+
await time.travel([3, "seconds"]);
|
|
388
|
+
expect(await app.cache()).toBe("v:1");
|
|
389
|
+
|
|
390
|
+
// After ttl, no stale window -> immediate handler call
|
|
391
|
+
await time.travel([3, "seconds"]);
|
|
392
|
+
expect(await app.cache()).toBe("v:2");
|
|
393
|
+
});
|
|
394
|
+
});
|
package/src/cache/core/index.ts
CHANGED
|
@@ -36,6 +36,22 @@ declare module "alepha" {
|
|
|
36
36
|
key: string;
|
|
37
37
|
ttlMs?: number;
|
|
38
38
|
};
|
|
39
|
+
/**
|
|
40
|
+
* Fires when a stale value (SWR grace window) is served and a
|
|
41
|
+
* background refresh is scheduled.
|
|
42
|
+
*/
|
|
43
|
+
"cache:stale": {
|
|
44
|
+
container: string;
|
|
45
|
+
key: string;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Fires when a background SWR refresh completes successfully and
|
|
49
|
+
* the value has been written back to the cache.
|
|
50
|
+
*/
|
|
51
|
+
"cache:revalidate": {
|
|
52
|
+
container: string;
|
|
53
|
+
key: string;
|
|
54
|
+
};
|
|
39
55
|
}
|
|
40
56
|
}
|
|
41
57
|
|