alepha 0.20.2 → 0.20.4
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/README.md +0 -1
- package/assets/swagger-ui/swagger-ui-bundle.js +1 -1
- package/assets/swagger-ui/swagger-ui.css +1 -1
- package/dist/api/audits/index.browser.js +49 -0
- package/dist/api/audits/index.browser.js.map +1 -1
- package/dist/api/audits/index.js +49 -0
- package/dist/api/audits/index.js.map +1 -1
- package/dist/api/files/index.js.map +1 -1
- package/dist/api/jobs/index.d.ts +2 -61
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/jobs/index.js.map +1 -1
- package/dist/api/keys/index.d.ts +4 -4
- package/dist/api/keys/index.js.map +1 -1
- package/dist/api/notifications/index.d.ts +1 -10
- package/dist/api/notifications/index.d.ts.map +1 -1
- package/dist/api/parameters/index.browser.js +37 -0
- package/dist/api/parameters/index.browser.js.map +1 -1
- package/dist/api/parameters/index.d.ts +12 -68
- package/dist/api/parameters/index.d.ts.map +1 -1
- package/dist/api/parameters/index.js +57 -4
- package/dist/api/parameters/index.js.map +1 -1
- package/dist/api/payments/index.js.map +1 -1
- package/dist/api/users/index.browser.js +6 -0
- package/dist/api/users/index.browser.js.map +1 -1
- package/dist/api/users/index.d.ts +148 -227
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +60 -14
- package/dist/api/users/index.js.map +1 -1
- package/dist/api/verifications/index.d.ts.map +1 -1
- package/dist/api/verifications/index.js +2 -1
- package/dist/api/verifications/index.js.map +1 -1
- package/dist/bucket/index.d.ts +77 -107
- package/dist/bucket/index.d.ts.map +1 -1
- package/dist/bucket/index.js +153 -5
- package/dist/bucket/index.js.map +1 -1
- package/dist/bucket/index.workerd.js +12 -2
- package/dist/bucket/index.workerd.js.map +1 -1
- package/dist/cache/core/index.d.ts +26 -0
- package/dist/cache/core/index.d.ts.map +1 -1
- package/dist/cache/core/index.js +11 -1
- package/dist/cache/core/index.js.map +1 -1
- package/dist/cache/core/index.workerd.js +11 -1
- package/dist/cache/core/index.workerd.js.map +1 -1
- package/dist/captcha/index.js.map +1 -1
- package/dist/cli/config/index.d.ts +7 -5
- package/dist/cli/config/index.d.ts.map +1 -1
- package/dist/cli/config/index.js +2 -3
- package/dist/cli/config/index.js.map +1 -1
- package/dist/cli/core/index.d.ts +637 -11660
- package/dist/cli/core/index.d.ts.map +1 -1
- package/dist/cli/core/index.js +707 -532
- package/dist/cli/core/index.js.map +1 -1
- package/dist/cli/devtools/index.d.ts +4 -8
- package/dist/cli/devtools/index.d.ts.map +1 -1
- package/dist/cli/devtools/index.js +20 -16
- package/dist/cli/devtools/index.js.map +1 -1
- package/dist/cli/platform/index.d.ts +51 -77
- package/dist/cli/platform/index.d.ts.map +1 -1
- package/dist/cli/platform/index.js +65 -15
- package/dist/cli/platform/index.js.map +1 -1
- package/dist/cli/vendor/index.d.ts +10 -13
- package/dist/cli/vendor/index.d.ts.map +1 -1
- package/dist/cli/vendor/index.js +30 -12
- package/dist/cli/vendor/index.js.map +1 -1
- package/dist/command/index.js +1 -1
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js +27 -3
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +8 -11
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +27 -3
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +27 -3
- package/dist/core/index.native.js.map +1 -1
- package/dist/core/index.workerd.js +27 -3
- package/dist/core/index.workerd.js.map +1 -1
- package/dist/crypto/index.js.map +1 -1
- package/dist/datetime/index.d.ts +69 -10
- package/dist/datetime/index.d.ts.map +1 -1
- package/dist/datetime/index.js +135 -13
- package/dist/datetime/index.js.map +1 -1
- package/dist/email/core/index.js.map +1 -1
- package/dist/email/smtp/index.js +130 -16
- package/dist/email/smtp/index.js.map +1 -1
- package/dist/fake/index.js.map +1 -1
- package/dist/lock/core/index.d.ts +30 -2
- package/dist/lock/core/index.d.ts.map +1 -1
- package/dist/lock/core/index.js +35 -12
- package/dist/lock/core/index.js.map +1 -1
- package/dist/lock/redis/index.js.map +1 -1
- package/dist/logger/index.js +32 -1
- package/dist/logger/index.js.map +1 -1
- package/dist/mcp/index.d.ts +238 -31
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +198 -67
- package/dist/mcp/index.js.map +1 -1
- package/dist/orm/core/index.browser.js +2 -362
- package/dist/orm/core/index.browser.js.map +1 -1
- package/dist/orm/core/index.bun.js +18 -409
- package/dist/orm/core/index.bun.js.map +1 -1
- package/dist/orm/core/index.d.ts +41 -194
- package/dist/orm/core/index.d.ts.map +1 -1
- package/dist/orm/core/index.js +27 -422
- package/dist/orm/core/index.js.map +1 -1
- package/dist/orm/postgres/index.bun.js +17 -20
- package/dist/orm/postgres/index.bun.js.map +1 -1
- package/dist/orm/postgres/index.d.ts +1 -5
- package/dist/orm/postgres/index.d.ts.map +1 -1
- package/dist/orm/postgres/index.js +17 -20
- package/dist/orm/postgres/index.js.map +1 -1
- package/dist/react/core/index.d.ts +102 -1
- package/dist/react/core/index.d.ts.map +1 -1
- package/dist/react/core/index.js +65 -1
- package/dist/react/core/index.js.map +1 -1
- package/dist/react/form/index.d.ts +6 -0
- package/dist/react/form/index.d.ts.map +1 -1
- package/dist/react/form/index.js +7 -7
- package/dist/react/form/index.js.map +1 -1
- package/dist/react/i18n/index.d.ts +7 -1
- package/dist/react/i18n/index.d.ts.map +1 -1
- package/dist/react/i18n/index.js +6 -0
- package/dist/react/i18n/index.js.map +1 -1
- package/dist/react/intro/index.js +22 -17
- package/dist/react/intro/index.js.map +1 -1
- package/dist/react/router/index.browser.js +98 -4
- package/dist/react/router/index.browser.js.map +1 -1
- package/dist/react/router/index.d.ts +58 -5
- package/dist/react/router/index.d.ts.map +1 -1
- package/dist/react/router/index.js +122 -6
- package/dist/react/router/index.js.map +1 -1
- package/dist/react/testing/{chunk-DBEY4PJZ.js → chunk-6Ep1yQYe.js} +1 -1
- package/dist/react/testing/index.js +1 -1
- package/dist/react/testing/index.js.map +1 -1
- package/dist/react/ui/index.d.ts +195 -1
- package/dist/react/ui/index.d.ts.map +1 -1
- package/dist/react/ui/index.js +64 -1
- package/dist/react/ui/index.js.map +1 -1
- package/dist/react/websocket/index.js.map +1 -1
- package/dist/redis/index.js.map +1 -1
- package/dist/scheduler/index.d.ts +1 -2
- package/dist/scheduler/index.d.ts.map +1 -1
- package/dist/scheduler/index.js +1 -1
- package/dist/scheduler/index.js.map +1 -1
- package/dist/scheduler/index.workerd.js +1 -1
- package/dist/scheduler/index.workerd.js.map +1 -1
- package/dist/security/index.browser.js.map +1 -1
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +2 -2
- package/dist/security/index.js.map +1 -1
- package/dist/server/auth/index.d.ts.map +1 -1
- package/dist/server/auth/index.js +24 -10
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/cookies/index.js.map +1 -1
- package/dist/server/core/index.browser.js +10 -3
- package/dist/server/core/index.browser.js.map +1 -1
- package/dist/server/core/index.d.ts +1 -4
- package/dist/server/core/index.d.ts.map +1 -1
- package/dist/server/core/index.js +47 -9
- package/dist/server/core/index.js.map +1 -1
- package/dist/server/links/index.browser.js.map +1 -1
- package/dist/server/links/index.js.map +1 -1
- package/dist/server/metrics/index.js +19 -1
- package/dist/server/metrics/index.js.map +1 -1
- package/dist/server/rate-limit/index.js.map +1 -1
- package/dist/server/static/index.js.map +1 -1
- package/dist/server/swagger/index.d.ts.map +1 -1
- package/dist/server/swagger/index.js +4 -5
- package/dist/server/swagger/index.js.map +1 -1
- package/dist/sms/index.js.map +1 -1
- package/dist/system/index.browser.js.map +1 -1
- package/dist/system/index.js.map +1 -1
- package/dist/system/index.workerd.js.map +1 -1
- package/dist/topic/core/index.js.map +1 -1
- package/dist/websocket/index.browser.js +32 -5
- package/dist/websocket/index.browser.js.map +1 -1
- package/dist/websocket/index.d.ts +3 -1
- package/dist/websocket/index.d.ts.map +1 -1
- package/dist/websocket/index.js +42 -6
- package/dist/websocket/index.js.map +1 -1
- package/package.json +685 -274
- package/src/api/files/__tests__/FileController.spec.ts +1 -1
- package/src/api/jobs/__tests__/$job.spec.ts +5 -1
- package/src/api/parameters/services/ParameterProvider.ts +21 -4
- package/src/api/users/__tests__/SessionService.spec.ts +99 -0
- package/src/api/users/__tests__/UserJobs.spec.ts +67 -0
- package/src/api/users/atoms/realmAuthSettingsAtom.ts +15 -0
- package/src/api/users/entities/sessions.ts +6 -0
- package/src/api/users/jobs/UserJobs.ts +44 -17
- package/src/api/users/providers/RealmProvider.ts +4 -0
- package/src/api/users/schemas/userQuerySchema.ts +0 -1
- package/src/api/users/services/SessionService.ts +27 -0
- package/src/api/users/services/UserService.ts +1 -5
- package/src/api/verifications/__tests__/CodeVerification.spec.ts +14 -0
- package/src/api/verifications/__tests__/LinkVerification.spec.ts +14 -0
- package/src/api/verifications/services/VerificationService.ts +1 -0
- package/src/bucket/__tests__/NodeS3BucketProvider.spec.ts +74 -0
- package/src/bucket/index.ts +19 -2
- package/src/bucket/primitives/$bucket.ts +9 -1
- package/src/bucket/providers/CloudflareR2Provider.ts +2 -137
- package/src/bucket/providers/NodeS3BucketProvider.ts +218 -0
- package/src/cache/core/index.ts +29 -0
- package/src/cache/core/primitives/$cache.ts +14 -1
- package/src/cli/config/defineConfig.ts +13 -15
- package/src/cli/core/__tests__/init.spec.ts +214 -7
- package/src/cli/core/commands/init.ts +12 -0
- package/src/cli/core/services/PackageManagerUtils.ts +23 -6
- package/src/cli/core/services/ProjectScaffolder.ts +315 -33
- package/src/cli/core/tasks/BuildCloudflareTask.ts +5 -0
- package/src/cli/core/tasks/BuildDockerTask.ts +9 -10
- package/src/cli/core/tasks/BuildServerTask.ts +8 -0
- package/src/cli/core/templates/agentMd.ts +2 -10
- package/src/cli/core/templates/apiIndexTs.ts +23 -1
- package/src/cli/core/templates/componentsJsonTs.ts +39 -0
- package/src/cli/core/templates/mainCss.ts +1 -0
- package/src/cli/core/templates/saasAdminLayoutTsx.ts +77 -0
- package/src/cli/core/templates/saasAdminPagesTsx.ts +26 -0
- package/src/cli/core/templates/saasAuthLayoutTsx.ts +20 -0
- package/src/cli/core/templates/saasAuthPagesTsx.ts +62 -0
- package/src/cli/core/templates/saasRealmProviderTs.ts +46 -0
- package/src/cli/core/templates/webAppRouterTs.ts +104 -1
- package/src/cli/core/templates/webIndexTs.ts +23 -1
- package/src/cli/devtools/index.ts +12 -26
- package/src/cli/platform/__tests__/SecretsCommand.spec.ts +2 -0
- package/src/cli/platform/index.ts +15 -24
- package/src/cli/vendor/atoms/vendorOptions.ts +1 -1
- package/src/cli/vendor/index.ts +14 -23
- package/src/command/providers/CliProvider.ts +1 -1
- package/src/core/Alepha.ts +11 -1
- package/src/core/helpers/ref.ts +18 -0
- package/src/core/index.shared.ts +1 -0
- package/src/core/interfaces/Service.ts +3 -1
- package/src/core/providers/SchemaValidator.ts +9 -1
- package/src/core/providers/TypeProvider.ts +2 -3
- package/src/datetime/REFACTORING.md +118 -0
- package/src/datetime/providers/DateTimeProvider.ts +203 -24
- package/src/lock/core/index.ts +31 -0
- package/src/lock/core/primitives/$lock.ts +14 -1
- package/src/logger/services/Logger.ts +1 -1
- package/src/mcp/__tests__/$resource.spec.ts +1 -1
- package/src/mcp/__tests__/$tool.spec.ts +1 -1
- package/src/mcp/__tests__/McpServerProvider.spec.ts +1 -1
- package/src/mcp/__tests__/jsonrpc.spec.ts +1 -1
- package/src/mcp/helpers/jsonrpc.ts +26 -1
- package/src/mcp/index.ts +10 -5
- package/src/mcp/interfaces/McpTypes.ts +83 -6
- package/src/mcp/primitives/$prompt.ts +18 -1
- package/src/mcp/primitives/$resource.ts +18 -1
- package/src/mcp/primitives/$tool.ts +83 -7
- package/src/mcp/providers/McpServerProvider.ts +74 -16
- package/src/mcp/transports/StreamableHttpMcpTransport.ts +226 -0
- package/src/orm/REFACTORING.md +330 -0
- package/src/orm/__tests__/$repository-tests.ts +1 -0
- package/src/orm/__tests__/orm-next-tests.ts +2 -67
- package/src/orm/__tests__/orm-next.spec.ts +0 -21
- package/src/orm/core/index.shared.ts +0 -2
- package/src/orm/core/index.ts +1 -2
- package/src/orm/core/primitives/$repository.ts +3 -6
- package/src/orm/core/primitives/$transactional.ts +11 -0
- package/src/orm/core/providers/drivers/DatabaseProvider.ts +0 -5
- package/src/orm/core/providers/drivers/NodeSqliteProvider.ts +11 -13
- package/src/orm/core/schemas/updateSchema.ts +1 -1
- package/src/orm/core/services/ModelBuilder.ts +1 -13
- package/src/orm/core/services/PgRelationManager.ts +4 -2
- package/src/orm/core/services/Repository.ts +1 -42
- package/src/orm/core/services/SqliteModelBuilder.ts +2 -33
- package/src/orm/postgres/services/PostgresModelBuilder.ts +10 -45
- package/src/react/core/__tests__/useQuery.browser.spec.tsx +86 -0
- package/src/react/core/hooks/useQuery.ts +153 -0
- package/src/react/core/index.ts +1 -0
- package/src/react/form/services/FormModel.ts +15 -6
- package/src/react/form/services/parseField.ts +8 -0
- package/src/react/i18n/providers/I18nProvider.ts +8 -2
- package/src/react/intro/components/GettingStartedAuthSlide.tsx +11 -4
- package/src/react/router/__tests__/$page.spec.tsx +0 -16
- package/src/react/router/__tests__/ReactBrowserProvider.browser.spec.ts +213 -2
- package/src/react/router/__tests__/ssr.spec.tsx +339 -0
- package/src/react/router/primitives/$page.ts +28 -4
- package/src/react/router/providers/ReactBrowserProvider.ts +73 -0
- package/src/react/router/providers/ReactBrowserRouterProvider.ts +1 -1
- package/src/react/router/providers/ReactPageProvider.ts +27 -9
- package/src/react/router/providers/ReactPreloadProvider.ts +1 -1
- package/src/react/router/providers/ReactServerProvider.ts +1 -0
- package/src/react/ui/atoms/uiThemeListAtom.ts +36 -0
- package/src/react/ui/index.ts +6 -0
- package/src/react/ui/services/SchemaControl.ts +209 -0
- package/src/scheduler/providers/CronProvider.ts +1 -1
- package/src/security/primitives/$basicAuth.ts +1 -1
- package/src/security/primitives/$issuer.ts +6 -3
- package/src/server/auth/providers/ServerAuthProvider.ts +5 -1
- package/src/server/core/__tests__/ServerRouterProvider-serializationError.spec.ts +75 -0
- package/src/server/core/__tests__/ServerRouterProvider-validationError.spec.ts +306 -0
- package/src/server/core/errors/ValidationError.ts +13 -1
- package/src/server/core/interfaces/ServerRequest.ts +1 -0
- package/src/server/core/primitives/$action.ts +16 -5
- package/src/server/core/providers/ServerProvider.ts +1 -1
- package/src/server/core/providers/ServerRouterProvider.ts +28 -6
- package/src/server/core/services/HttpClient.ts +1 -1
- package/src/server/swagger/providers/ServerSwaggerProvider.ts +6 -8
- package/src/websocket/providers/NodeWebSocketServerProvider.ts +10 -4
- package/src/websocket/services/WebSocketClient.ts +11 -5
- package/src/mcp/transports/SseMcpTransport.ts +0 -182
- package/src/orm/core/__tests__/parseQueryString.spec.ts +0 -196
- package/src/orm/core/helpers/parseQueryString.ts +0 -502
- package/src/orm/core/primitives/$view.ts +0 -88
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import type { Async } from "alepha";
|
|
2
|
+
import type { DurationLike } from "alepha/datetime";
|
|
3
|
+
import { type DependencyList, useCallback, useState } from "react";
|
|
4
|
+
import type { ActionContext } from "./useAction.ts";
|
|
5
|
+
import { useAction } from "./useAction.ts";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Hook for declarative data fetching with automatic execution and refetch.
|
|
9
|
+
*
|
|
10
|
+
* Thin wrapper over {@link useAction}: it pre-applies `runOnInit: true`,
|
|
11
|
+
* exposes the last result as `data`, and provides a stable `refetch()` to
|
|
12
|
+
* re-run the query on demand. For optimistic mutations and side-effects,
|
|
13
|
+
* use {@link useAction} directly — `useQuery` is for the read path.
|
|
14
|
+
*
|
|
15
|
+
* Caching, request deduplication, and AbortSignal cancellation come from
|
|
16
|
+
* `useAction` + `HttpClient`. There is no separate cache layer — pass
|
|
17
|
+
* `localCache` to your `HttpClient.fetch()`/`fetchAction()` call inside
|
|
18
|
+
* the query handler if you want per-call caching.
|
|
19
|
+
*
|
|
20
|
+
* @example Basic
|
|
21
|
+
* ```tsx
|
|
22
|
+
* const client = useInject(HttpClient);
|
|
23
|
+
* const { data, loading, error, refetch } = useQuery({
|
|
24
|
+
* handler: async ({ signal }) => {
|
|
25
|
+
* const res = await client.fetch("/api/users", { request: { signal } });
|
|
26
|
+
* return res.data;
|
|
27
|
+
* },
|
|
28
|
+
* }, []);
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @example Re-fetch when a dep changes
|
|
32
|
+
* ```tsx
|
|
33
|
+
* const { data } = useQuery({
|
|
34
|
+
* handler: async () => api.getUser(userId),
|
|
35
|
+
* }, [userId]);
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* @example Polling
|
|
39
|
+
* ```tsx
|
|
40
|
+
* const { data } = useQuery({
|
|
41
|
+
* handler: async () => api.getStatus(),
|
|
42
|
+
* runEvery: [5, "seconds"],
|
|
43
|
+
* }, []);
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export function useQuery<Result>(
|
|
47
|
+
options: UseQueryOptions<Result>,
|
|
48
|
+
deps: DependencyList,
|
|
49
|
+
): UseQueryReturn<Result> {
|
|
50
|
+
const [data, setData] = useState<Result | undefined>(options.initialData);
|
|
51
|
+
|
|
52
|
+
const action = useAction<[], Result>(
|
|
53
|
+
{
|
|
54
|
+
id: options.id,
|
|
55
|
+
handler: options.handler,
|
|
56
|
+
runOnInit: options.enabled !== false,
|
|
57
|
+
runEvery: options.runEvery,
|
|
58
|
+
debounce: options.debounce,
|
|
59
|
+
onError: options.onError,
|
|
60
|
+
onSuccess: async (result) => {
|
|
61
|
+
setData(result);
|
|
62
|
+
if (options.onSuccess) {
|
|
63
|
+
await options.onSuccess(result);
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
deps,
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const refetch = useCallback(() => action.run(), [action.run]);
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
data,
|
|
74
|
+
loading: action.loading,
|
|
75
|
+
error: action.error,
|
|
76
|
+
refetch,
|
|
77
|
+
cancel: action.cancel,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
|
82
|
+
|
|
83
|
+
export interface UseQueryOptions<Result> {
|
|
84
|
+
/**
|
|
85
|
+
* Async query handler. Receives an {@link ActionContext} with an
|
|
86
|
+
* AbortSignal that fires on unmount, dependency change, or `cancel()`.
|
|
87
|
+
*/
|
|
88
|
+
handler: (context: ActionContext) => Async<Result>;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Optional identifier (used in lifecycle events for debugging/analytics).
|
|
92
|
+
*/
|
|
93
|
+
id?: string;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* If `false`, skip automatic execution on mount and dep change. Use
|
|
97
|
+
* `refetch()` to trigger manually. Defaults to `true`.
|
|
98
|
+
*/
|
|
99
|
+
enabled?: boolean;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Initial value for `data` before the first successful fetch.
|
|
103
|
+
*/
|
|
104
|
+
initialData?: Result;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Re-run periodically. See {@link useAction} `runEvery`.
|
|
108
|
+
*/
|
|
109
|
+
runEvery?: DurationLike;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Debounce delay in milliseconds. See {@link useAction} `debounce`.
|
|
113
|
+
*/
|
|
114
|
+
debounce?: number;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Called on success with the resolved value.
|
|
118
|
+
*/
|
|
119
|
+
onSuccess?: (result: Result) => void | Promise<void>;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Custom error handler. If provided, prevents default error re-throw.
|
|
123
|
+
*/
|
|
124
|
+
onError?: (error: Error) => void | Promise<void>;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface UseQueryReturn<Result> {
|
|
128
|
+
/**
|
|
129
|
+
* The last successful result. `undefined` until the first fetch resolves
|
|
130
|
+
* (or the value of `initialData` if provided).
|
|
131
|
+
*/
|
|
132
|
+
data: Result | undefined;
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Loading state — `true` while a fetch is in flight.
|
|
136
|
+
*/
|
|
137
|
+
loading: boolean;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Error from the last failed fetch, if any.
|
|
141
|
+
*/
|
|
142
|
+
error?: Error;
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Re-run the query. The previous in-flight request, if any, is aborted.
|
|
146
|
+
*/
|
|
147
|
+
refetch: () => Promise<Result | undefined>;
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Abort the in-flight request without scheduling another.
|
|
151
|
+
*/
|
|
152
|
+
cancel: () => void;
|
|
153
|
+
}
|
package/src/react/core/index.ts
CHANGED
|
@@ -13,6 +13,7 @@ export * from "./hooks/useAlepha.ts";
|
|
|
13
13
|
export * from "./hooks/useClient.ts";
|
|
14
14
|
export * from "./hooks/useEvents.ts";
|
|
15
15
|
export * from "./hooks/useInject.ts";
|
|
16
|
+
export * from "./hooks/useQuery.ts";
|
|
16
17
|
export * from "./hooks/useStore.ts";
|
|
17
18
|
|
|
18
19
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
@@ -140,15 +140,23 @@ export class FormModel<T extends TObject> {
|
|
|
140
140
|
|
|
141
141
|
public readonly reset = (event?: FormEventLike) => {
|
|
142
142
|
event?.preventDefault?.();
|
|
143
|
+
// Snapshot all keys that need notification — both keys present
|
|
144
|
+
// before reset (so subscribers learn the cleared value) and keys
|
|
145
|
+
// restored from initialValues. Without the union, fields that were
|
|
146
|
+
// typed but absent from initialValues stay visually stale.
|
|
147
|
+
const keys = new Set<string>([
|
|
148
|
+
...Object.keys(this.values),
|
|
149
|
+
...Object.keys(this.initialValues),
|
|
150
|
+
]);
|
|
143
151
|
for (const key in this.values) {
|
|
144
152
|
delete this.values[key];
|
|
145
153
|
}
|
|
146
154
|
Object.assign(this.values, { ...this.initialValues });
|
|
147
|
-
for (const
|
|
155
|
+
for (const key of keys) {
|
|
148
156
|
const path = `/${key.replaceAll(".", "/")}`;
|
|
149
157
|
this.alepha.events.emit(
|
|
150
158
|
"form:change",
|
|
151
|
-
{ id: this.id, path, value },
|
|
159
|
+
{ id: this.id, path, value: this.values[key] },
|
|
152
160
|
{ catch: true },
|
|
153
161
|
);
|
|
154
162
|
}
|
|
@@ -356,10 +364,11 @@ export class FormModel<T extends TObject> {
|
|
|
356
364
|
name: key,
|
|
357
365
|
};
|
|
358
366
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
}
|
|
367
|
+
// Use the form's runtime id (always set — comes from `useId()` when
|
|
368
|
+
// no explicit `options.id` was provided). This guarantees stable
|
|
369
|
+
// per-field DOM ids without forcing callers to pass `id`.
|
|
370
|
+
attr.id = `${this.id}-${key}`;
|
|
371
|
+
(attr as any)["data-testid"] = attr.id;
|
|
363
372
|
|
|
364
373
|
if (t.schema.isString(field)) {
|
|
365
374
|
if (field.maxLength != null) {
|
|
@@ -45,6 +45,12 @@ export interface FieldMeta {
|
|
|
45
45
|
constraints: FieldConstraints;
|
|
46
46
|
testId?: string;
|
|
47
47
|
schema: TSchema;
|
|
48
|
+
/**
|
|
49
|
+
* Raw `$control` value from the schema, untyped here. The UI layer
|
|
50
|
+
* (`alepha/react/ui`) provides the strict {@link SchemaControl} type and
|
|
51
|
+
* a `resolveSchemaControl` helper to evaluate the function form.
|
|
52
|
+
*/
|
|
53
|
+
control?: unknown;
|
|
48
54
|
}
|
|
49
55
|
|
|
50
56
|
export interface ParseFieldOptions {
|
|
@@ -77,6 +83,7 @@ export const parseField = (
|
|
|
77
83
|
pattern?: string;
|
|
78
84
|
properties?: unknown;
|
|
79
85
|
items?: { properties?: unknown };
|
|
86
|
+
$control?: unknown;
|
|
80
87
|
};
|
|
81
88
|
|
|
82
89
|
const label =
|
|
@@ -132,6 +139,7 @@ export const parseField = (
|
|
|
132
139
|
| string
|
|
133
140
|
| undefined,
|
|
134
141
|
schema: input.schema,
|
|
142
|
+
control: schema.$control,
|
|
135
143
|
};
|
|
136
144
|
};
|
|
137
145
|
|
|
@@ -230,15 +230,21 @@ export class I18nProvider<
|
|
|
230
230
|
return value;
|
|
231
231
|
};
|
|
232
232
|
|
|
233
|
+
/**
|
|
234
|
+
* Look up `key` in the registered dictionaries. The `(string & {})` arm
|
|
235
|
+
* keeps autocomplete for the typed dictionary keys while allowing shared
|
|
236
|
+
* library components to pass arbitrary string keys (with a `default`
|
|
237
|
+
* fallback) without casting to `as never`.
|
|
238
|
+
*/
|
|
233
239
|
public readonly tr = (
|
|
234
|
-
key: keyof ServiceDictionary<S>[K],
|
|
240
|
+
key: keyof ServiceDictionary<S>[K] | (string & {}),
|
|
235
241
|
options: {
|
|
236
242
|
args?: string[];
|
|
237
243
|
default?: string;
|
|
238
244
|
} = {},
|
|
239
245
|
) => {
|
|
240
246
|
const translation = this.translate(key as string, options.args || []);
|
|
241
|
-
if (translation === key && options.default) {
|
|
247
|
+
if (translation === (key as string) && options.default) {
|
|
242
248
|
return options.default;
|
|
243
249
|
}
|
|
244
250
|
return translation;
|
|
@@ -8,7 +8,7 @@ import type { GettingStartedSlide } from "./GettingStarted.tsx";
|
|
|
8
8
|
* Returns undefined if auth routes are not configured.
|
|
9
9
|
*/
|
|
10
10
|
export const useAuthSlide = (): GettingStartedSlide | undefined => {
|
|
11
|
-
const { user } = useAuth();
|
|
11
|
+
const { user, logout } = useAuth();
|
|
12
12
|
const router = useRouter();
|
|
13
13
|
|
|
14
14
|
// Check if auth routes exist
|
|
@@ -19,8 +19,6 @@ export const useAuthSlide = (): GettingStartedSlide | undefined => {
|
|
|
19
19
|
|
|
20
20
|
// User is logged in - show user info and logout option
|
|
21
21
|
if (user) {
|
|
22
|
-
const logoutAnchorProps = router.anchor(router.path("logout"));
|
|
23
|
-
|
|
24
22
|
return {
|
|
25
23
|
text: "Welcome back!",
|
|
26
24
|
sub: `You're signed in as ${user.email || user.username || "user"}.`,
|
|
@@ -33,7 +31,16 @@ export const useAuthSlide = (): GettingStartedSlide | undefined => {
|
|
|
33
31
|
num: "→",
|
|
34
32
|
text: (
|
|
35
33
|
<>
|
|
36
|
-
<a
|
|
34
|
+
<a
|
|
35
|
+
href="#"
|
|
36
|
+
onClick={(e) => {
|
|
37
|
+
e.preventDefault();
|
|
38
|
+
logout();
|
|
39
|
+
}}
|
|
40
|
+
>
|
|
41
|
+
Sign out
|
|
42
|
+
</a>{" "}
|
|
43
|
+
to test the login flow
|
|
37
44
|
</>
|
|
38
45
|
),
|
|
39
46
|
},
|
|
@@ -419,22 +419,6 @@ describe("$page primitive tests", () => {
|
|
|
419
419
|
expect(await app.staticPage.fetch().then((it) => it.html)).toBe(html);
|
|
420
420
|
});
|
|
421
421
|
|
|
422
|
-
test("$page - client-side only rendering", async ({ expect }) => {
|
|
423
|
-
class App {
|
|
424
|
-
clientOnly = $page({
|
|
425
|
-
path: "/client",
|
|
426
|
-
client: true,
|
|
427
|
-
component: () => "Client only",
|
|
428
|
-
});
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
const app = alepha.inject(App);
|
|
432
|
-
await alepha.start();
|
|
433
|
-
|
|
434
|
-
const clientRendered = await app.clientOnly.fetch();
|
|
435
|
-
expect(clientRendered.html).toBe("");
|
|
436
|
-
});
|
|
437
|
-
|
|
438
422
|
test("$page - server response handler", async ({ expect }) => {
|
|
439
423
|
const mockHandler = vi.fn();
|
|
440
424
|
|
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
import { Alepha } from "alepha";
|
|
2
|
-
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
-
import {
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
import {
|
|
4
|
+
ReactBrowserProvider,
|
|
5
|
+
type RouterPushOptions,
|
|
6
|
+
reactBrowserOptions,
|
|
7
|
+
} from "../providers/ReactBrowserProvider.ts";
|
|
4
8
|
|
|
5
9
|
class TestReactBrowserProvider extends ReactBrowserProvider {
|
|
6
10
|
public testGetHydrationState = this.getHydrationState.bind(this);
|
|
11
|
+
public testAttachAnchorInterceptor = this.attachAnchorInterceptor.bind(this);
|
|
12
|
+
public pushCalls: Array<{ url: string; options?: RouterPushOptions }> = [];
|
|
13
|
+
|
|
14
|
+
public override async push(
|
|
15
|
+
url: string,
|
|
16
|
+
options?: RouterPushOptions,
|
|
17
|
+
): Promise<void> {
|
|
18
|
+
this.pushCalls.push({ url, options });
|
|
19
|
+
}
|
|
7
20
|
}
|
|
8
21
|
|
|
9
22
|
describe("ReactBrowserProvider", () => {
|
|
@@ -102,4 +115,202 @@ describe("ReactBrowserProvider", () => {
|
|
|
102
115
|
expect(result?.["alepha.i18n.locale"]).toBe("en");
|
|
103
116
|
});
|
|
104
117
|
});
|
|
118
|
+
|
|
119
|
+
describe("attachAnchorInterceptor", () => {
|
|
120
|
+
let alepha: Alepha;
|
|
121
|
+
let provider: TestReactBrowserProvider;
|
|
122
|
+
let detach: () => void;
|
|
123
|
+
let container: HTMLDivElement;
|
|
124
|
+
|
|
125
|
+
const createAnchor = (
|
|
126
|
+
attrs: Record<string, string>,
|
|
127
|
+
inner?: HTMLElement,
|
|
128
|
+
): HTMLAnchorElement => {
|
|
129
|
+
const a = document.createElement("a");
|
|
130
|
+
for (const [k, v] of Object.entries(attrs)) {
|
|
131
|
+
a.setAttribute(k, v);
|
|
132
|
+
}
|
|
133
|
+
if (inner) {
|
|
134
|
+
a.appendChild(inner);
|
|
135
|
+
} else {
|
|
136
|
+
a.textContent = "link";
|
|
137
|
+
}
|
|
138
|
+
container.appendChild(a);
|
|
139
|
+
return a;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const click = (
|
|
143
|
+
target: HTMLElement,
|
|
144
|
+
init: MouseEventInit = {},
|
|
145
|
+
): MouseEvent => {
|
|
146
|
+
const ev = new MouseEvent("click", {
|
|
147
|
+
bubbles: true,
|
|
148
|
+
cancelable: true,
|
|
149
|
+
button: 0,
|
|
150
|
+
...init,
|
|
151
|
+
});
|
|
152
|
+
target.dispatchEvent(ev);
|
|
153
|
+
return ev;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
beforeEach(() => {
|
|
157
|
+
alepha = Alepha.create();
|
|
158
|
+
provider = alepha.inject(TestReactBrowserProvider);
|
|
159
|
+
container = document.createElement("div");
|
|
160
|
+
document.body.appendChild(container);
|
|
161
|
+
detach = provider.testAttachAnchorInterceptor();
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
afterEach(() => {
|
|
165
|
+
detach();
|
|
166
|
+
container.remove();
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("intercepts plain internal /foo anchor clicks", () => {
|
|
170
|
+
const a = createAnchor({ href: "/foo" });
|
|
171
|
+
|
|
172
|
+
const ev = click(a);
|
|
173
|
+
|
|
174
|
+
expect(provider.pushCalls).toHaveLength(1);
|
|
175
|
+
expect(provider.pushCalls[0].url).toBe("/foo");
|
|
176
|
+
expect(ev.defaultPrevented).toBe(true);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("preserves query and hash when intercepting", () => {
|
|
180
|
+
const a = createAnchor({ href: "/foo?x=1#bar" });
|
|
181
|
+
|
|
182
|
+
click(a);
|
|
183
|
+
|
|
184
|
+
expect(provider.pushCalls[0].url).toBe("/foo?x=1#bar");
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("ignores cmd-click (metaKey)", () => {
|
|
188
|
+
const a = createAnchor({ href: "/foo" });
|
|
189
|
+
|
|
190
|
+
const ev = click(a, { metaKey: true });
|
|
191
|
+
|
|
192
|
+
expect(provider.pushCalls).toHaveLength(0);
|
|
193
|
+
expect(ev.defaultPrevented).toBe(false);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("ignores ctrl-click", () => {
|
|
197
|
+
const a = createAnchor({ href: "/foo" });
|
|
198
|
+
|
|
199
|
+
click(a, { ctrlKey: true });
|
|
200
|
+
|
|
201
|
+
expect(provider.pushCalls).toHaveLength(0);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("ignores shift-click", () => {
|
|
205
|
+
const a = createAnchor({ href: "/foo" });
|
|
206
|
+
|
|
207
|
+
click(a, { shiftKey: true });
|
|
208
|
+
|
|
209
|
+
expect(provider.pushCalls).toHaveLength(0);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it("ignores alt-click", () => {
|
|
213
|
+
const a = createAnchor({ href: "/foo" });
|
|
214
|
+
|
|
215
|
+
click(a, { altKey: true });
|
|
216
|
+
|
|
217
|
+
expect(provider.pushCalls).toHaveLength(0);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it("ignores non-primary mouse buttons", () => {
|
|
221
|
+
const a = createAnchor({ href: "/foo" });
|
|
222
|
+
|
|
223
|
+
click(a, { button: 1 });
|
|
224
|
+
|
|
225
|
+
expect(provider.pushCalls).toHaveLength(0);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it("ignores anchors with target='_blank'", () => {
|
|
229
|
+
const a = createAnchor({ href: "/foo", target: "_blank" });
|
|
230
|
+
|
|
231
|
+
click(a);
|
|
232
|
+
|
|
233
|
+
expect(provider.pushCalls).toHaveLength(0);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it("ignores anchors with download attribute", () => {
|
|
237
|
+
const a = createAnchor({ href: "/foo", download: "" });
|
|
238
|
+
|
|
239
|
+
click(a);
|
|
240
|
+
|
|
241
|
+
expect(provider.pushCalls).toHaveLength(0);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it("ignores anchors with data-no-router attribute", () => {
|
|
245
|
+
const a = createAnchor({ href: "/foo", "data-no-router": "" });
|
|
246
|
+
|
|
247
|
+
click(a);
|
|
248
|
+
|
|
249
|
+
expect(provider.pushCalls).toHaveLength(0);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it("ignores mailto: hrefs", () => {
|
|
253
|
+
const a = createAnchor({ href: "mailto:foo@bar.com" });
|
|
254
|
+
|
|
255
|
+
click(a);
|
|
256
|
+
|
|
257
|
+
expect(provider.pushCalls).toHaveLength(0);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it("ignores tel: hrefs", () => {
|
|
261
|
+
const a = createAnchor({ href: "tel:+15555555" });
|
|
262
|
+
|
|
263
|
+
click(a);
|
|
264
|
+
|
|
265
|
+
expect(provider.pushCalls).toHaveLength(0);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it("ignores hrefs to external origins", () => {
|
|
269
|
+
const a = createAnchor({ href: "https://example.com/foo" });
|
|
270
|
+
|
|
271
|
+
click(a);
|
|
272
|
+
|
|
273
|
+
expect(provider.pushCalls).toHaveLength(0);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it("ignores hash-only #section hrefs", () => {
|
|
277
|
+
const a = createAnchor({ href: "#section" });
|
|
278
|
+
|
|
279
|
+
click(a);
|
|
280
|
+
|
|
281
|
+
expect(provider.pushCalls).toHaveLength(0);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it("intercepts when click target is nested inside the anchor", () => {
|
|
285
|
+
const span = document.createElement("span");
|
|
286
|
+
span.textContent = "inner";
|
|
287
|
+
createAnchor({ href: "/foo" }, span);
|
|
288
|
+
|
|
289
|
+
click(span);
|
|
290
|
+
|
|
291
|
+
expect(provider.pushCalls).toHaveLength(1);
|
|
292
|
+
expect(provider.pushCalls[0].url).toBe("/foo");
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it("skips when defaultPrevented is already true", () => {
|
|
296
|
+
const a = createAnchor({ href: "/foo" });
|
|
297
|
+
a.addEventListener("click", (ev) => ev.preventDefault());
|
|
298
|
+
|
|
299
|
+
click(a);
|
|
300
|
+
|
|
301
|
+
expect(provider.pushCalls).toHaveLength(0);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it("respects interceptAnchorClicks=false at runtime", () => {
|
|
305
|
+
alepha.store.set(reactBrowserOptions.key, {
|
|
306
|
+
...alepha.store.get(reactBrowserOptions.key)!,
|
|
307
|
+
interceptAnchorClicks: false,
|
|
308
|
+
});
|
|
309
|
+
const a = createAnchor({ href: "/foo" });
|
|
310
|
+
|
|
311
|
+
click(a);
|
|
312
|
+
|
|
313
|
+
expect(provider.pushCalls).toHaveLength(0);
|
|
314
|
+
});
|
|
315
|
+
});
|
|
105
316
|
});
|