vue-fate 1.6.2 → 1.7.1

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 CHANGED
@@ -430,6 +430,96 @@ const PostDetail = ({ post: postRef }: { post: ViewRef<'Post'> }) => {
430
430
 
431
431
  ViewRefs carry a set of view names they can resolve. `useView` throws if a ref does not include the required view.
432
432
 
433
+ ### Parameters and Conditional Selections
434
+
435
+ A view can take parameters. Define the selection with a function, then bind its parameters where you request or compose the view:
436
+
437
+ ```tsx
438
+ import { alias, useRequest, useView, view, when, type ViewRef } from 'react-fate';
439
+ import type { User } from '@your-org/server/views';
440
+
441
+ const AccountView = view<User>()({ email: true });
442
+
443
+ const ProfileView = view<User>()(({ isViewer }: { isViewer: boolean }) => ({
444
+ name: true,
445
+ account: when(isViewer, alias(AccountView)),
446
+ }));
447
+
448
+ function Profile({ userId, isViewer }: { userId: string; isViewer: boolean }) {
449
+ const { user } = useRequest({
450
+ user: { id: userId, view: ProfileView({ isViewer }) },
451
+ });
452
+ const profile = useView(ProfileView, user);
453
+ const account = useView(AccountView, profile.account);
454
+
455
+ return (
456
+ <div>
457
+ <h2>{profile.name}</h2>
458
+ {account && <p>{account.email}</p>}
459
+ </div>
460
+ );
461
+ }
462
+ ```
463
+
464
+ `alias(AccountView)` gives the selection a named ref to the **same User**. The name `account` belongs to your result; it does not need to exist in the server schema. Read the ref with `useView` to access the fragment's selected fields. The existing `alias('field', selection)` form still selects a schema field under another name.
465
+
466
+ `when(condition, selection)` works with named fragments and ordinary fields:
467
+
468
+ ```tsx
469
+ const ProfileView = view<User>()(({ isViewer }: { isViewer: boolean }) => ({
470
+ name: true,
471
+ email: when(isViewer, true),
472
+ }));
473
+ ```
474
+
475
+ A false condition returns `undefined` and excludes that branch from fetching and subscriptions, even if its data is already cached. A selected field whose server value is `null` still returns `null`. TypeScript preserves that distinction: a boolean condition adds `undefined` to the result type; literal `true` keeps the selected type; literal `false` produces `undefined`.
476
+
477
+ Both `useView` and `useLiveView` accept `undefined` refs and return `undefined` without fetching or subscribing. Call the hooks unconditionally, as in the example above.
478
+
479
+ #### Carrying Parameters with Refs
480
+
481
+ Bindings travel with refs. Child components read the view definition without reconstructing its parameters:
482
+
483
+ ```tsx
484
+ const LocalizedNameView = view<User>()(({ locale }: { locale: string }) => ({
485
+ name: { args: { locale } },
486
+ }));
487
+
488
+ const NamesView = view<User>()({
489
+ english: alias(LocalizedNameView({ locale: 'en' })),
490
+ japanese: alias(LocalizedNameView({ locale: 'ja' })),
491
+ });
492
+
493
+ function Names({ user }: { user: ViewRef<'User'> }) {
494
+ const names = useView(NamesView, user);
495
+ const english = useView(LocalizedNameView, names.english);
496
+ const japanese = useView(LocalizedNameView, names.japanese);
497
+
498
+ return (
499
+ <p>
500
+ {english.name} / {japanese.name}
501
+ </p>
502
+ );
503
+ }
504
+ ```
505
+
506
+ This example assumes the server's `name` field accepts a `locale` argument. The two refs identify the same entity with independent bindings. Connections inside named views likewise keep their own arguments and pagination state.
507
+
508
+ Declare view definitions at module scope. Binding an equivalent parameter object again preserves the logical request and ref identity; no `useMemo` is needed. Parameter objects use the same serializable values as field arguments, and fate snapshots them when binding. Keep selection functions pure, with all varying inputs passed as parameters.
509
+
510
+ Bind a parameterized definition before using it in a request, nested selection, alias, or spread. If a single ref contains multiple bindings of one definition, use named aliases to give each binding its own ref. Conditional anonymous spreads are not supported; use `when(condition, alias(View))` instead.
511
+
512
+ #### Vue
513
+
514
+ The same `view`, `alias`, and `when` API is available from `vue-fate`. Pass reactive refs or getters to the view composables:
515
+
516
+ ```ts
517
+ const profile = useView(ProfileView, () => request.value?.user);
518
+ const account = useView(AccountView, () => profile.value?.account);
519
+ ```
520
+
521
+ When the ref is `undefined`, the resource's value and `ready()` result are `undefined`, and the view has no active subscriptions.
522
+
433
523
  ## Requests
434
524
 
435
525
  ### Requesting Lists
@@ -613,6 +703,94 @@ fate.hydrate(loaderData.fate, { merge: 'replace' });
613
703
 
614
704
  Do not reuse request-scoped snapshots across users. Dehydrate after awaited route preloading: snapshots are point-in-time values and do not stream cache patches for data that resolves later. Hydration and dehydration reject clients with in-flight requests, so hydrate the initial snapshot before rendering.
615
705
 
706
+ ### Observing Request State
707
+
708
+ React's `useRequestState(request, options)` observes a request without suspending.
709
+ It returns `status`, `data`, `error`, and `isFetching`. Status is `pending`, `ready`,
710
+ or `error` for an enabled network request. An initial failure exposes the original
711
+ error rather than throwing during render. Request data contains the same refs as
712
+ `useRequest`; resolve them with `useView`.
713
+
714
+ Use `{ enabled: false }` for an empty search or an inactive screen. The status is
715
+ `disabled`, data is undefined, and no request starts. Changing `enabled` to true
716
+ starts the request. Equivalent inline request objects do not cause repeat fetches.
717
+
718
+ Use `{ mode: 'cache-only' }` to observe already-cached data. A complete selection
719
+ has status `ready`; an incomplete selection has status `missing` and undefined
720
+ data. A successful nullable root remains `ready` with a null value. Cache-only
721
+ requests never fetch, and their view refs cannot fetch missing fields either.
722
+ Mounted observers retain their request's records until unmount.
723
+
724
+ Outside React, `client.observeRequest(request, options)` exposes `getSnapshot()`
725
+ and `subscribe(listener)`. The first subscriber starts an enabled request;
726
+ unsubscribing releases its retention. Reading a snapshot alone never starts work.
727
+
728
+ Every request state includes a stable `refetch()` function. It forces a network
729
+ request, returns a promise for the completed result, and rejects with the original
730
+ error on failure. Concurrent calls share pending work. During refresh, complete
731
+ cached data stays available with `status: 'ready'` and `isFetching: true`. A failed
732
+ refresh keeps that data and exposes `error`; the next attempt clears the error.
733
+ `stale-while-revalidate` observers track the entire background request the same
734
+ way, including failures. Disabled and cache-only observers reject explicit
735
+ refetches without fetching; enable the request or change its mode first.
736
+
737
+ ```ts
738
+ const state = useRequestState(request, { mode: 'stale-while-revalidate' });
739
+ // Render state.data, state.isFetching, and state.error independently.
740
+ await state.refetch(); // Also usable from a refresh button or retry action.
741
+ ```
742
+
743
+ Overlapping reads use their network start order when normalizing responses. An
744
+ older response cannot overwrite a field already supplied by a newer response or
745
+ a later confirmed write. It may still fill other fields. Root results and list
746
+ replacements also retain the newer result. Optimistic layers remain visible over
747
+ incoming confirmed data; rolling them back reveals that data, while committing a
748
+ mutation protects its confirmed fields from older requests.
749
+
750
+ #### Aliases
751
+
752
+ Use `alias(sourceField, selection)` to select one schema field under multiple
753
+ result names, with independent arguments:
754
+
755
+ ```ts
756
+ const GamesView = view<User>()({
757
+ activeGames: alias('games', {
758
+ args: { first: 20, status: 'Active' },
759
+ items: { node: GameView },
760
+ pagination: { hasNext: true, nextCursor: true },
761
+ }),
762
+ waitingGames: alias('games', {
763
+ args: { first: 20, status: 'Waiting' },
764
+ items: { node: GameView },
765
+ }),
766
+ displayName: alias('name', true),
767
+ });
768
+
769
+ const request = { currentUser: alias('viewer', { view: GamesView }) };
770
+ ```
771
+
772
+ Source fields, nested selections, result types, and generated root argument
773
+ contracts remain checked. Alias names affect the returned shape; normalized
774
+ identity uses the source field and its arguments. An ordinary selection with the
775
+ same arguments shares the cached data. Connection filters identify separate
776
+ cache slots and pagination state. Conflicting response names or arguments in a
777
+ composed view fail during planning. `id` and `__typename` are reserved result
778
+ names because fate needs them for entity identity.
779
+
780
+ GraphQL emits native aliases, including inside mutation selections, so multiple
781
+ variants execute in one operation. Native HTTP, tRPC, and custom transports
782
+ receive compatible reads with ordinary schema field names; conflicting variants
783
+ may require multiple reads or subscriptions. These fallback reads are independent
784
+ server operations. A mutation that needs multiple variants requires native alias
785
+ support and otherwise rejects before executing. A simple rename is lowered for
786
+ native mutations without repeating the action.
787
+
788
+ Custom transports can set `supportsAliases: true` to handle the full selection in
789
+ one operation. Selection path segments and corresponding argument keys then use
790
+ `resultName:schemaField`, and returned records must use those same encoded keys.
791
+ GraphQL performs that conversion automatically. Applications consume the declared
792
+ result names, never the encoded transport names.
793
+
616
794
  ## Deferred Views
617
795
 
618
796
  Use `defer` when a field should not block the parent view. The parent view receives a deferred handle immediately after the eager fields are available, and the component that reads that handle with `useView`, `useListView`, or `useLiveListView` decides which `Suspense` boundary handles the loading state.
@@ -1525,7 +1703,7 @@ Native HTTP sends durable mutations and receipt-only recovery using protocol ver
1525
1703
 
1526
1704
  ### tRPC, GraphQL, and Custom Transports
1527
1705
 
1528
- For tRPC, GraphQL, or a custom transport, provide a `mutateDurably(name, input, select, identity)` method that sends the identity to an endpoint with server deduplication. Regular calls continue to use `mutate(name, input, select)`.
1706
+ For tRPC, GraphQL, or a custom transport, provide a `mutateDurably(name, input, select, identity)` method that sends the identity to an endpoint with server deduplication. Regular calls continue to use `mutate(name, input, select)`. GraphQL passes selected field arguments as an optional fifth parameter to `mutateDurably` (fourth for `mutate`), separate from the mutation input. Forward that parameter if the durable response selects fields with arguments.
1529
1707
 
1530
1708
  Both `createTRPCTransport` and `createGraphQLTransport`, including their generated clients, accept `mutateDurably`. For example, you can use a native fate endpoint for durable mutations alongside your existing transport:
1531
1709
 
@@ -1934,7 +2112,7 @@ The client-only template is the smallest reference for the integration. It conta
1934
2112
  The GraphQL transport expects a schema with Relay-style object identity and pagination:
1935
2113
 
1936
2114
  - Entity objects include `id` and `__typename`.
1937
- - Object fetches go through a `nodes(ids:)` field.
2115
+ - Object fetches use `nodes(ids:)` by default, or configured per-type query fields.
1938
2116
  - List fields return Relay connections with `edges`, `cursor`, `node`, and `pageInfo`.
1939
2117
  - Root queries and mutations return the entity type selected by the fate view.
1940
2118
 
@@ -2113,6 +2291,31 @@ GraphQL operations issued in the same microtask are batched into a single GraphQ
2113
2291
 
2114
2292
  Deferred view fields work with the GraphQL transport through the same normalized cache flow as native HTTP: the eager query omits `defer(...)` fields, and `useView`, `useListView`, or `useLiveListView` fetches the missing selection through `nodes(ids:)` when the deferred handle is read. GraphQL `@defer` is the natural wire format for this feature, but fate's GraphQL transport currently expects one JSON result per operation and does not consume incremental multipart patches yet.
2115
2293
 
2294
+ ### Refetch Fields
2295
+
2296
+ For servers without `nodes(ids:)`, configure a query field for each refetchable entity type:
2297
+
2298
+ ```tsx
2299
+ export const fateGraphQL = {
2300
+ byId: {
2301
+ Game: { field: 'fetch__Game' },
2302
+ User: { field: 'fetch__User', idArg: 'id' },
2303
+ },
2304
+ nodes: false,
2305
+ // Keep your schema, roots, and mutations here.
2306
+ } as const;
2307
+ ```
2308
+
2309
+ `idArg` defaults to `id`. Each mapped field must return one entity of the named type, optionally nullable. The adapter batches multiple IDs into aliased query fields in one request, preserves input order for returned records, and omits null results. Node ID encoding and decoding apply to mapped fetches too.
2310
+
2311
+ Mappings take precedence over the default `nodes(ids:)` fallback. Set `nodes: false` to disable that fallback; fetching an unmapped type then reports a configuration error without issuing a request. Existing clients need no configuration changes. Direct users of `createGraphQLTransport` can pass the same `byId` and `nodes` options.
2312
+
2313
+ When `schema` is supplied, codegen and transport construction validate the mapping: the field must exist, its ID argument must accept a single `ID`, `String`, or `Int`, its result must match the concrete entity type, and any other arguments must be optional or have defaults. Schema metadata also supplies the wire variable types. Without a schema, identifiers are validated and arguments use GraphQL literals.
2314
+
2315
+ Nested pagination uses the owner ID only for the mapped query field. Pagination and filter arguments remain on the connection field. For example, fetching another page of a user's games produces `fetch__User(id: $owner) { games(first: $count, after: $cursor) { ... } }`.
2316
+
2317
+ Choose fields with the appropriate authorization rules for the consuming views. The adapter does not switch between public and authenticated lookup fields automatically.
2318
+
2116
2319
  ### Object IDs
2117
2320
 
2118
2321
  The transport converts between fate entity IDs and GraphQL node IDs. By default, it sends IDs as `${type}-${id}` and strips that prefix from returned IDs. Override this if your schema uses Relay global IDs, raw database IDs, or another encoding:
@@ -2253,6 +2456,80 @@ The GraphQL transport is intentionally a mapping layer. It does not require `cre
2253
2456
 
2254
2457
  Use data views to expose only the fields the client should be able to select, keep GraphQL schema authorization in your server, and treat `src/fate/graphql.ts` as the contract between your GraphQL API and fate's client.
2255
2458
 
2459
+ ### Schema-Aware Arguments
2460
+
2461
+ Provide your server's schema in the mapping module to generate GraphQL argument contracts and send arguments as typed variables. The `schema` option accepts SDL text or a `GraphQLSchema` object and is evaluated during client generation:
2462
+
2463
+ ```tsx
2464
+ import { readFileSync } from 'node:fs';
2465
+
2466
+ export const fateGraphQL = {
2467
+ schema: readFileSync(new URL('./schema.graphql', import.meta.url), 'utf8'),
2468
+ roots: {
2469
+ maps: { field: 'maps' },
2470
+ },
2471
+ mutations: {
2472
+ 'map.update': graphqlMutation<Map, UpdateMapInput, Map>('Map', {
2473
+ field: 'updateMap',
2474
+ inputArg: false,
2475
+ }),
2476
+ },
2477
+ };
2478
+ ```
2479
+
2480
+ Install `graphql` in the package performing generation when passing SDL. Run `fate generate` again after updating the schema. Generated browser code contains argument metadata, not the SDL or GraphQL parser. Existing clients without a schema retain literal argument serialization; use schema-aware generation for enums.
2481
+
2482
+ SDL text must include definitions for every directive it uses. Some server-generated or Relay-oriented schema files contain annotations such as `@auth` or `@fetchable` without declaring them; strict SDL parsing rejects those files. Prefer adding the directive definitions to the exported SDL. If the omissions are intentional, pass a schema object instead:
2483
+
2484
+ ```tsx
2485
+ import { readFileSync } from 'node:fs';
2486
+ import { buildSchema } from 'graphql';
2487
+
2488
+ const sdl = readFileSync(new URL('./schema.graphql', import.meta.url), 'utf8');
2489
+ export const fateGraphQL = {
2490
+ schema: buildSchema(sdl, { assumeValidSDL: true }),
2491
+ // ...roots and mutations...
2492
+ };
2493
+ ```
2494
+
2495
+ `assumeValidSDL` skips SDL validation, so use it only when the annotations are known and the schema is validated elsewhere. Fate still extracts argument metadata at generation time; the browser bundle does not include the schema object.
2496
+
2497
+ For example, given `maps(biome: Biome)`, an argument `{ biome: 'Grassland' }` is sent as a variable declared with type `Biome`, rather than an invalid quoted enum literal. Variables also work for nested field arguments, input objects, lists, node IDs, and mutations. Batched operations use distinct variable names and separate query and mutation payloads. Mutation selection arguments are kept out of mutation inputs. A GraphQL mutation argument named `args` belongs in `input` like any other wire argument; use the mutation's `view` or its separate `args` option for selected field arguments. When calling `transport.mutate` directly, pass selected field arguments as its optional fourth parameter.
2498
+
2499
+ The generated client enforces root arguments in `client.request` and the React/Vue `useRequest` adapters. Mutation inputs are derived from the schema using the configured `inputArg`, replacing the manually declared input type for generated clients. Non-null arguments are required unless the schema supplies a default. Omitting an optional/defaulted value preserves its server default; explicit `null` remains distinct.
2500
+
2501
+ Generation also exports `GraphQLInputs`, `GraphQLFieldArguments`, and `GraphQLSelectionArguments` from `.fate/client.generated.ts`. Use field contracts to check arguments inside reusable views:
2502
+
2503
+ ```tsx
2504
+ import type { GraphQLFieldArguments } from '../.fate/client.generated.ts';
2505
+
2506
+ const stateArgs = {
2507
+ biome: 'Grassland',
2508
+ } satisfies GraphQLFieldArguments['Map']['state'];
2509
+
2510
+ const MapView = view<Map>()({
2511
+ id: true,
2512
+ state: { args: stateArgs },
2513
+ });
2514
+ ```
2515
+
2516
+ At runtime the transport checks selected field names, argument names, enum values, required inputs, built-in scalar types, and nested input objects before issuing an HTTP request. Custom scalar values have TypeScript type `unknown` and remain subject to server validation. These contracts cover arguments; they do not add support for GraphQL result shapes that the transport otherwise does not support.
2517
+
2518
+ For a manually constructed transport, extract metadata on the build/server side with `createGraphQLArgumentSchema` from `@nkzw/fate/vite`, then pass the serialized result as the transport's `schema` option. Keep that build-time helper out of browser modules.
2519
+
2520
+ ### Errors and Partial Results
2521
+
2522
+ A failed GraphQL operation rejects with `GraphQLRequestError`. It preserves all
2523
+ GraphQL `errors`, the first error's `path` and `extensions`, and the HTTP `status`
2524
+ when available. HTTP errors use the same error type; network failures retain the
2525
+ original fetch error.
2526
+
2527
+ Partial response data is available as `error.data` for explicit application
2528
+ handling. It is unnormalized diagnostic data, not a successful typed result, and
2529
+ fate does not write it to the cache. Successful sibling operations in the same
2530
+ batch still resolve normally, including legitimate `null` results. Mutation
2531
+ functions retain their `{ result, error }` result contract.
2532
+
2256
2533
  ## Server Integration
2257
2534
 
2258
2535
  Until now, we have focused on the client-side API of fate. You'll need a backend that can be wired into fate's typed request model so the Vite plugin can connect the typed fate APIs to your app. _fate_ currently ships three integration paths:
package/lib/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { ConnectionRef, Deferred, Deferred as Deferred$1, FateClient as FateClient$1, FateDehydratedState, FateMutations, FateRoots, GraphQLMutationDefinition, GraphQLMutationInput, GraphQLMutationMap, GraphQLMutationOutput, GraphQLTransportOptions, HydrateOptions, HydrationLimits, InferFateAPI, Pagination, Pagination as Pagination$1, Persistence, PersistenceSession, PersistenceSnapshot, Request, RequestOptions, RequestResult, View, ViewData, ViewEntity, ViewEntityName, ViewRef, ViewRef as ViewRef$1, ViewSelection, clientRoot, createClient, createGraphQLTransport, createHTTPTransport, createTRPCTransport, defer, graphqlMutation, mutation, toEntityId, view } from "@nkzw/fate";
1
+ import { AliasedSelection, AliasedView, CheckedRequest, ConditionalSelection, ConnectionRef, Deferred, Deferred as Deferred$1, FateClient as FateClient$1, FateDehydratedState, FateMutations, FateRoots, GraphQLMutationDefinition, GraphQLMutationInput, GraphQLMutationMap, GraphQLMutationOutput, GraphQLTransportOptions, HydrateOptions, HydrationLimits, InferFateAPI, Pagination, Pagination as Pagination$1, ParameterizedView, Persistence, PersistenceSession, PersistenceSnapshot, Request, RequestOptions, RequestResult, View, ViewData, ViewEntity, ViewEntityName, ViewRef, ViewRef as ViewRef$1, ViewSelection, alias, clientRoot, createClient, createGraphQLTransport, createHTTPTransport, createTRPCTransport, defer, graphqlMutation, mutation, toEntityId, view, when } from "@nkzw/fate";
2
2
  import { ComputedRef, InjectionKey, MaybeRefOrGetter, Plugin, PropType, Ref, ShallowRef } from "vue";
3
3
  //#region src/useRequest.d.ts
4
4
  type GeneratedFateClient$1 = ReturnType<typeof import('vue-fate/client').createFateClient>;
@@ -14,7 +14,7 @@ type RequestResource<T> = {
14
14
  /**
15
15
  * Declares the data a component or screen needs and exposes it as a Vue resource.
16
16
  */
17
- export declare function useRequest<R extends Request, O extends FateRoots = Roots>(request: MaybeRefOrGetter<R>, options?: RequestOptions): RequestResource<RequestResult<O, R>>;
17
+ export declare function useRequest<const R extends Request, O extends FateRoots = Roots>(request: MaybeRefOrGetter<CheckedRequest<O, R>>, options?: RequestOptions): RequestResource<RequestResult<O, R>>;
18
18
  //#endregion
19
19
  //#region src/context.d.ts
20
20
  type GeneratedFateClient = ReturnType<typeof import('vue-fate/client').createFateClient>;
@@ -97,9 +97,9 @@ type ViewResource<T> = ShallowRef<T> & {
97
97
  /**
98
98
  * Resolves a reference against a view and subscribes to updates for that selection.
99
99
  */
100
- export declare function useView<V extends View<any, any>, R extends ViewRef$1<ViewEntityName<V>> | null>(view: V, ref: MaybeRefOrGetter<R>): ViewResource<R extends null ? null : ViewData<ViewEntityWithTypename$1<V>, ViewSelection<V>>>;
101
- export declare function useView<V extends View<any, any>, R extends Deferred$1<ViewRef$1<ViewEntityName<V>>> | null>(view: V, ref: MaybeRefOrGetter<R>): ViewResource<R extends null ? null : ViewData<ViewEntityWithTypename$1<V>, ViewSelection<V>>>;
102
- export declare function useView<V extends View<any, any>>(view: V, ref: MaybeRefOrGetter<Deferred$1<ViewRef$1<ViewEntityName<V>>> | ViewRef$1<ViewEntityName<V>> | null>): ViewResource<ViewData<ViewEntityWithTypename$1<V>, ViewSelection<V>> | null>;
100
+ export declare function useView<V extends View<any, any>, R extends ViewRef$1<ViewEntityName<V>> | null | undefined>(view: V, ref: MaybeRefOrGetter<R>): ViewResource<R extends null | undefined ? R : ViewData<ViewEntityWithTypename$1<V>, ViewSelection<V>>>;
101
+ export declare function useView<V extends View<any, any>, R extends Deferred$1<ViewRef$1<ViewEntityName<V>>> | null | undefined>(view: V, ref: MaybeRefOrGetter<R>): ViewResource<R extends null | undefined ? R : ViewData<ViewEntityWithTypename$1<V>, ViewSelection<V>>>;
102
+ export declare function useView<V extends View<any, any>>(view: V, ref: MaybeRefOrGetter<Deferred$1<ViewRef$1<ViewEntityName<V>>> | ViewRef$1<ViewEntityName<V>> | null | undefined>): ViewResource<ViewData<ViewEntityWithTypename$1<V>, ViewSelection<V>> | null | undefined>;
103
103
  //#endregion
104
104
  //#region src/useLiveView.d.ts
105
105
  type ViewEntityWithTypename<V extends View<any, any>> = ViewEntity<V> & {
@@ -108,8 +108,8 @@ type ViewEntityWithTypename<V extends View<any, any>> = ViewEntity<V> & {
108
108
  /**
109
109
  * Resolves a reference against a view and subscribes to live server updates for that selection.
110
110
  */
111
- export declare function useLiveView<V extends View<any, any>, R extends ViewRef$1<ViewEntityName<V>> | null>(view: V, ref: MaybeRefOrGetter<R>): ViewResource<R extends null ? null : ViewData<ViewEntityWithTypename<V>, ViewSelection<V>>>;
112
- export declare function useLiveView<V extends View<any, any>, R extends Deferred$1<ViewRef$1<ViewEntityName<V>>> | null>(view: V, ref: MaybeRefOrGetter<R>): ViewResource<R extends null ? null : ViewData<ViewEntityWithTypename<V>, ViewSelection<V>>>;
113
- export declare function useLiveView<V extends View<any, any>>(view: V, ref: MaybeRefOrGetter<Deferred$1<ViewRef$1<ViewEntityName<V>>> | ViewRef$1<ViewEntityName<V>> | null>): ViewResource<ViewData<ViewEntityWithTypename<V>, ViewSelection<V>> | null>;
111
+ export declare function useLiveView<V extends View<any, any>, R extends ViewRef$1<ViewEntityName<V>> | null | undefined>(view: V, ref: MaybeRefOrGetter<R>): ViewResource<R extends null | undefined ? R : ViewData<ViewEntityWithTypename<V>, ViewSelection<V>>>;
112
+ export declare function useLiveView<V extends View<any, any>, R extends Deferred$1<ViewRef$1<ViewEntityName<V>>> | null | undefined>(view: V, ref: MaybeRefOrGetter<R>): ViewResource<R extends null | undefined ? R : ViewData<ViewEntityWithTypename<V>, ViewSelection<V>>>;
113
+ export declare function useLiveView<V extends View<any, any>>(view: V, ref: MaybeRefOrGetter<Deferred$1<ViewRef$1<ViewEntityName<V>>> | ViewRef$1<ViewEntityName<V>> | null | undefined>): ViewResource<ViewData<ViewEntityWithTypename<V>, ViewSelection<V>> | null | undefined>;
114
114
  //#endregion
115
- export { type ConnectionRef, type Deferred, type FateDehydratedState, type GraphQLMutationDefinition, type GraphQLMutationInput, type GraphQLMutationMap, type GraphQLMutationOutput, type GraphQLTransportOptions, type HydrateOptions, type HydrationLimits, type InferFateAPI, type ListViewState, type Pagination, type Persistence, type PersistenceSession, type PersistenceSnapshot, type RequestResource, type ViewRef, type ViewResource, clientRoot, createClient, createGraphQLTransport, createHTTPTransport, createTRPCTransport, defer, graphqlMutation, mutation, toEntityId, view };
115
+ export { type AliasedSelection, type AliasedView, type ConditionalSelection, type ConnectionRef, type Deferred, type FateDehydratedState, type GraphQLMutationDefinition, type GraphQLMutationInput, type GraphQLMutationMap, type GraphQLMutationOutput, type GraphQLTransportOptions, type HydrateOptions, type HydrationLimits, type InferFateAPI, type ListViewState, type Pagination, type ParameterizedView, type Persistence, type PersistenceSession, type PersistenceSnapshot, type RequestResource, type ViewRef, type ViewResource, alias, clientRoot, createClient, createGraphQLTransport, createHTTPTransport, createTRPCTransport, defer, graphqlMutation, mutation, toEntityId, view, when };
package/lib/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { ConnectionTag, clientRoot, createClient, createGraphQLTransport, createHTTPTransport, createTRPCTransport, defer, graphqlMutation, isDeferred, isViewTag, mutation, toEntityId, view } from "@nkzw/fate";
1
+ import { ConnectionTag, alias, clientRoot, createClient, createGraphQLTransport, createHTTPTransport, createTRPCTransport, defer, graphqlMutation, isDeferred, isViewTag, mutation, resolveView, toEntityId, view, when } from "@nkzw/fate";
2
2
  import { computed, defineComponent, inject, isRef, markRaw, onScopeDispose, onServerPrefetch, provide, shallowRef, toValue, watch } from "vue";
3
3
  import { getListEntries } from "@nkzw/fate/list";
4
4
  //#region src/context.ts
@@ -273,9 +273,10 @@ const isFulfilledThenable = (value) => "status" in value && value.status === "fu
273
273
  //#endregion
274
274
  //#region src/useView.ts
275
275
  const nullSnapshot = fulfilledThenable(null);
276
+ const undefinedSnapshot = fulfilledThenable(void 0);
276
277
  function useView(view, ref) {
277
278
  const clientSource = getFateClientSource(useFateClient());
278
- const data = shallowRef(null);
279
+ const data = shallowRef(toValue(ref) === void 0 ? void 0 : null);
279
280
  const errorState = shallowRef(null);
280
281
  const pending = shallowRef(false);
281
282
  let disposed = false;
@@ -309,7 +310,7 @@ function useView(view, ref) {
309
310
  };
310
311
  const getSnapshot = () => {
311
312
  const currentRef = toValue(ref);
312
- if (currentRef === null) return nullSnapshot;
313
+ if (currentRef == null) return currentRef === void 0 ? undefinedSnapshot : nullSnapshot;
313
314
  if (!isDeferred(currentRef)) return readViewSnapshot(currentRef);
314
315
  const deferredSnapshot = activeClient.readDeferred(currentRef);
315
316
  if (isFulfilledThenable(deferredSnapshot)) {
@@ -318,7 +319,7 @@ function useView(view, ref) {
318
319
  coverage: deferredSnapshot.value.coverage,
319
320
  data: null
320
321
  });
321
- return readViewSnapshot(activeClient.ref(resolvedRef.__typename, resolvedRef.id, view), deferredSnapshot.value.coverage);
322
+ return readViewSnapshot(activeClient.ref(resolvedRef.__typename, resolvedRef.id, resolveView(view, resolvedRef)), deferredSnapshot.value.coverage);
322
323
  }
323
324
  return Promise.resolve(deferredSnapshot).then((deferredValue) => {
324
325
  const resolvedRef = deferredValue.data;
@@ -326,7 +327,7 @@ function useView(view, ref) {
326
327
  coverage: deferredValue.coverage,
327
328
  data: null
328
329
  };
329
- return readViewSnapshot(activeClient.ref(resolvedRef.__typename, resolvedRef.id, view), deferredValue.coverage);
330
+ return readViewSnapshot(activeClient.ref(resolvedRef.__typename, resolvedRef.id, resolveView(view, resolvedRef)), deferredValue.coverage);
330
331
  });
331
332
  };
332
333
  function refresh() {
@@ -342,12 +343,12 @@ function useView(view, ref) {
342
343
  const nextSnapshot = getSnapshot();
343
344
  currentPromise = Promise.resolve(nextSnapshot).then((value) => {
344
345
  if (!disposed && currentToken === token) {
345
- snapshot = value;
346
- data.value = value ? markRaw(value.data) : null;
346
+ snapshot = value ?? null;
347
+ data.value = value ? markRaw(value.data) : value;
347
348
  pending.value = false;
348
349
  updateSubscriptions();
349
350
  }
350
- return value ? value.data : null;
351
+ return value ? value.data : value;
351
352
  }, (error) => {
352
353
  if (!disposed && currentToken === token) {
353
354
  errorState.value = error;
@@ -401,8 +402,7 @@ function useLiveView(view, ref) {
401
402
  resolveRef().then((resolvedRef) => {
402
403
  if (disposed || currentToken !== token || !resolvedRef) return;
403
404
  const client = clientSource.value;
404
- client.assertLiveViewSupport();
405
- liveUnsubscribe = client.subscribeLiveView(view, client.ref(resolvedRef.__typename, resolvedRef.id, view));
405
+ liveUnsubscribe = client.subscribeLiveView(view, client.ref(resolvedRef.__typename, resolvedRef.id, resolveView(view, resolvedRef)));
406
406
  }).catch((error) => {
407
407
  if (!disposed && currentToken === token) {
408
408
  viewResource.error.value = error;
@@ -499,4 +499,4 @@ function useRequest(request, options) {
499
499
  };
500
500
  }
501
501
  //#endregion
502
- export { FateClient, FateClientKey, clientRoot, createClient, createFatePlugin, createGraphQLTransport, createHTTPTransport, createTRPCTransport, defer, graphqlMutation, mutation, provideFateClient, toEntityId, useFateClient, useListView, useLiveListView, useLiveView, useRequest, useView, view };
502
+ export { FateClient, FateClientKey, alias, clientRoot, createClient, createFatePlugin, createGraphQLTransport, createHTTPTransport, createTRPCTransport, defer, graphqlMutation, mutation, provideFateClient, toEntityId, useFateClient, useListView, useLiveListView, useLiveView, useRequest, useView, view, when };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vue-fate",
3
- "version": "1.6.2",
3
+ "version": "1.7.1",
4
4
  "description": "fate is a modern data client for Vue.",
5
5
  "homepage": "https://github.com/nkzw-tech/fate",
6
6
  "license": "MIT",
@@ -40,10 +40,10 @@
40
40
  }
41
41
  },
42
42
  "dependencies": {
43
- "@nkzw/fate": "1.6.2"
43
+ "@nkzw/fate": "1.7.1"
44
44
  },
45
45
  "devDependencies": {
46
- "vue": "^3.5.42"
46
+ "vue": "^3.5.43"
47
47
  },
48
48
  "peerDependencies": {
49
49
  "vue": "^3.5.0"