@vladimirdev635/gql-client-vue 0.0.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/.npmignore ADDED
@@ -0,0 +1 @@
1
+ tests/
package/index.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { type OperationLoadingState, type OperationSuccessState, type OperationFailureState, type OperationState, useOperation, } from './useOperation.jsx';
2
+ export { type LazyOperationInitialState, type LazyOperationState, type UseLazyOperationReturnType, type LazyOperationExecuteReturnType, useLazyOperation, } from './use-lazy-operation/index.js';
3
+ export { useSubscription } from './useSubscription.jsx';
package/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { useOperation, } from './useOperation.jsx';
2
+ export { useLazyOperation, } from './use-lazy-operation/index.js';
3
+ export { useSubscription } from './useSubscription.jsx';
@@ -0,0 +1,3 @@
1
+ export declare const loadingState: Readonly<{
2
+ readonly state: "loading";
3
+ }>;
@@ -0,0 +1,3 @@
1
+ export const loadingState = Object.freeze({
2
+ state: 'loading',
3
+ });
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@vladimirdev635/gql-client-vue",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "main": "./index.js",
6
+ "types": "./index.d.ts",
7
+ "files": [
8
+ "./"
9
+ ],
10
+ "homepage": "https://github.com/Voldemat/graphql-plus-plus#readme",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/Voldemat/graphql-plus-plus.git"
14
+ },
15
+ "scripts": {
16
+ "lint": "oxlint .",
17
+ "lint:fix": "oxlint --fix .",
18
+ "fmt": "oxfmt .",
19
+ "fmt:check": "oxfmt --check .",
20
+ "lint:ci": "npm run lint && npm run fmt:check",
21
+ "test": "vitest",
22
+ "build": "tsc && cp package.json .npmignore ./dist/"
23
+ },
24
+ "keywords": [],
25
+ "author": "",
26
+ "license": "ISC",
27
+ "description": "",
28
+ "devDependencies": {
29
+ "@testing-library/vue": "^8.1.0",
30
+ "@types/node": "^24.0.1",
31
+ "@types/object-hash": "^3.0.6",
32
+ "@vitejs/plugin-vue": "^5.2.1",
33
+ "@vue/test-utils": "^2.4.6",
34
+ "@vue/tsconfig": "^0.7.0",
35
+ "jiti": "^2.4.2",
36
+ "jsdom": "^26.1.0",
37
+ "oxfmt": "^0.64.0",
38
+ "oxlint": "^1.79.0",
39
+ "typescript": "<7.0.0",
40
+ "typescript-language-server": "^5.3.0",
41
+ "vue": "^3.5.13",
42
+ "vue-tsc": "^2.2.0",
43
+ "vitest": "^3.2.4"
44
+ },
45
+ "peerDependencies": {
46
+ "vue": "^3.0.0",
47
+ "zod": ">=4.0.5"
48
+ },
49
+ "optionalDependencies": {
50
+ "@rollup/rollup-linux-x64-gnu": "4.9.5"
51
+ },
52
+ "allowScripts": {}
53
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,49 @@
1
+ import assert from 'assert';
2
+ import { describe, expect, it } from 'vitest';
3
+ import { nextTick } from 'vue';
4
+ import { useLazyOperation } from '../use-lazy-operation/index.js';
5
+ import { mountComposable, testOperation } from './utils.js';
6
+ describe('useLazyOperation', () => {
7
+ it('Should preserve initial state if no one calls', async () => {
8
+ const [, { state }] = mountComposable(() => useLazyOperation({}, testOperation));
9
+ expect(state.value.state).toBe('initial');
10
+ await nextTick();
11
+ expect(state.value.state).toBe('initial');
12
+ });
13
+ it('Should return loading state after invoking, then success state', async () => {
14
+ const executor = {
15
+ executeSync: async () => ({
16
+ result: { a: 1 },
17
+ response: new Response(),
18
+ }),
19
+ };
20
+ const [execute, { state }] = mountComposable(() => useLazyOperation(executor, testOperation));
21
+ const promise = execute({}, {});
22
+ // Immediately after invocation, state ref transitions to 'loading'
23
+ expect(state.value.state).toBe('loading');
24
+ const newState = await promise;
25
+ expect(newState.state).toBe('success');
26
+ assert(newState.state === 'success');
27
+ expect(newState.result).toStrictEqual({ a: 1 });
28
+ await nextTick();
29
+ expect(state.value).toStrictEqual(newState);
30
+ });
31
+ it('Should return loading state after invoking, then failure state', async () => {
32
+ const error = new Error('Network error');
33
+ const executor = {
34
+ executeSync: async () => {
35
+ throw error;
36
+ },
37
+ };
38
+ const [execute, { state }] = mountComposable(() => useLazyOperation(executor, testOperation));
39
+ const promise = execute({}, {});
40
+ // Immediately after invocation, state ref transitions to 'loading'
41
+ expect(state.value.state).toBe('loading');
42
+ const newState = await promise;
43
+ expect(newState.state).toBe('failure');
44
+ assert(newState.state === 'failure');
45
+ expect(newState.error).toBe(error);
46
+ await nextTick();
47
+ expect(state.value).toStrictEqual(newState);
48
+ });
49
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,39 @@
1
+ import { useOperation } from '../useOperation.js';
2
+ import { describe, expect, it } from 'vitest';
3
+ import { flushPromises } from '@vue/test-utils';
4
+ import assert from 'assert';
5
+ import { mountComposable, testOperation } from './utils.js';
6
+ describe('useOperation', () => {
7
+ it('Should return loading state and then success state', async () => {
8
+ const executor = {
9
+ executeSync: async () => ({
10
+ result: { a: 1 },
11
+ response: new Response(),
12
+ }),
13
+ };
14
+ const state = mountComposable(() => useOperation(executor, testOperation, {}, {}));
15
+ // Synchronously upon invocation, state is 'loading'
16
+ expect(state.value.state).toBe('loading');
17
+ // Wait for async executeSync promise to resolve and flush watchers
18
+ await flushPromises();
19
+ expect(state.value.state).toBe('success');
20
+ assert(state.value.state === 'success');
21
+ expect(state.value.result).toStrictEqual({ a: 1 });
22
+ });
23
+ it('Should return loading state and then failure state', async () => {
24
+ const error = new Error('Network error');
25
+ const executor = {
26
+ executeSync: async () => {
27
+ throw error;
28
+ },
29
+ };
30
+ const state = mountComposable(() => useOperation(executor, testOperation, {}, {}));
31
+ // Synchronously upon invocation, state is 'loading'
32
+ expect(state.value.state).toBe('loading');
33
+ // Wait for rejected executeSync promise to catch and flush watchers
34
+ await flushPromises();
35
+ expect(state.value.state).toBe('failure');
36
+ assert(state.value.state === 'failure');
37
+ expect(state.value.error).toBe(error);
38
+ });
39
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,54 @@
1
+ import { useSubscription } from '../useSubscription.js';
2
+ import { describe, expect, it } from 'vitest';
3
+ import { flushPromises } from '@vue/test-utils';
4
+ import assert from 'assert';
5
+ import { mountComposable, testSubscription } from './utils.js';
6
+ describe('useSubscription', () => {
7
+ it('Should return loading state and then success state', async () => {
8
+ const executor = {
9
+ executeSubscription: async () => {
10
+ const readableStream = new ReadableStream();
11
+ const response = new Response(readableStream);
12
+ return {
13
+ result: {
14
+ stream: (async function* () {
15
+ yield { number: 1 };
16
+ yield { number: 2 };
17
+ yield { number: 3 };
18
+ })(),
19
+ close: () => { },
20
+ },
21
+ response,
22
+ };
23
+ },
24
+ };
25
+ const state = mountComposable(() => useSubscription(executor, testSubscription, {}, {}));
26
+ // Synchronously upon invocation, state is 'loading'
27
+ expect(state.value.state).toBe('loading');
28
+ // Wait for async executeSubscription promise to resolve and flush watchers
29
+ await flushPromises();
30
+ expect(state.value.state).toBe('success');
31
+ assert(state.value.state === 'success');
32
+ let number = 1;
33
+ for await (const value of state.value.result.stream) {
34
+ expect(value.number).toBe(number);
35
+ number++;
36
+ }
37
+ });
38
+ it('Should return loading state and then failure state', async () => {
39
+ const error = new Error('Network error');
40
+ const executor = {
41
+ executeSubscription: async () => {
42
+ throw error;
43
+ },
44
+ };
45
+ const state = mountComposable(() => useSubscription(executor, testSubscription, {}, {}));
46
+ // Synchronously upon invocation, state is 'loading'
47
+ expect(state.value.state).toBe('loading');
48
+ // Wait for rejected executeSubscription promise to catch and flush watchers
49
+ await flushPromises();
50
+ expect(state.value.state).toBe('failure');
51
+ assert(state.value.state === 'failure');
52
+ expect(state.value.error).toBe(error);
53
+ });
54
+ });
@@ -0,0 +1,21 @@
1
+ import { OperationResult } from '@/types.js';
2
+ import { z } from 'zod/v4';
3
+ export declare const testOperation: {
4
+ readonly document: "";
5
+ readonly name: "";
6
+ readonly type: "QUERY";
7
+ readonly variablesSchema: z.ZodObject<{}, z.core.$strip>;
8
+ readonly resultSchema: z.ZodObject<{}, z.core.$strip>;
9
+ };
10
+ export type TestOperationResult = OperationResult<typeof testOperation>;
11
+ export declare const testSubscription: {
12
+ readonly document: "";
13
+ readonly name: "";
14
+ readonly type: "SUBSCRIPTION";
15
+ readonly variablesSchema: z.ZodObject<{}, z.core.$strip>;
16
+ readonly resultSchema: z.ZodObject<{
17
+ number: z.ZodNumber;
18
+ }, z.core.$strip>;
19
+ };
20
+ export type TestSubscriptionResult = OperationResult<typeof testSubscription>;
21
+ export declare function mountComposable<T>(composable: () => T): NonNullable<T>;
package/tests/utils.js ADDED
@@ -0,0 +1,27 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import { z } from 'zod/v4';
3
+ export const testOperation = {
4
+ document: '',
5
+ name: '',
6
+ type: 'QUERY',
7
+ variablesSchema: z.object({}),
8
+ resultSchema: z.object({}),
9
+ };
10
+ export const testSubscription = {
11
+ document: '',
12
+ name: '',
13
+ type: 'SUBSCRIPTION',
14
+ variablesSchema: z.object({}),
15
+ resultSchema: z.object({ number: z.number() }),
16
+ };
17
+ export function mountComposable(composable) {
18
+ let result;
19
+ mount({
20
+ setup() {
21
+ result = composable();
22
+ return () => { };
23
+ },
24
+ render() { },
25
+ });
26
+ return result;
27
+ }
package/types.d.ts ADDED
@@ -0,0 +1,33 @@
1
+ import { z } from 'zod/v4';
2
+ export type SchemaFor<T> = z.ZodType<T>;
3
+ interface BaseOperation<V, R> {
4
+ name: string;
5
+ document: string;
6
+ variablesSchema: SchemaFor<V>;
7
+ resultSchema: SchemaFor<R>;
8
+ }
9
+ export interface SyncOperation<V, R> extends BaseOperation<V, R> {
10
+ type: 'QUERY' | 'MUTATION';
11
+ }
12
+ export interface SubscriptionOperation<V, R> extends BaseOperation<V, R> {
13
+ type: 'SUBSCRIPTION';
14
+ }
15
+ export type Operation<V, R> = SyncOperation<V, R> | SubscriptionOperation<V, R>;
16
+ export interface RequestContext {
17
+ fetchOptions?: RequestInit;
18
+ }
19
+ export type OperationVariables<T extends Operation<unknown, unknown>> = T extends Operation<infer V, unknown> ? V : never;
20
+ export type OperationResult<T extends Operation<unknown, unknown>> = T extends Operation<unknown, infer R> ? R : never;
21
+ export interface ExecuteResult<TResult> {
22
+ result: TResult;
23
+ response: Response;
24
+ }
25
+ export interface SubOpAsyncIterable<TResult> extends Disposable {
26
+ stream: AsyncIterable<TResult, void, unknown>;
27
+ close: () => void;
28
+ }
29
+ export interface IExecutor<TRequestContext extends RequestContext> {
30
+ executeSync<T extends SyncOperation<unknown, unknown>>(operation: T, variables: OperationVariables<T>, requestContext: TRequestContext): Promise<ExecuteResult<OperationResult<T>>>;
31
+ executeSubscription<T extends SubscriptionOperation<unknown, unknown>>(operation: T, variables: OperationVariables<T>, requestContext: TRequestContext, controller: AbortController): Promise<ExecuteResult<SubOpAsyncIterable<OperationResult<T>>>>;
32
+ }
33
+ export {};
package/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,10 @@
1
+ import { type ShallowRef } from 'vue';
2
+ import type { IExecutor, OperationResult, OperationVariables, RequestContext, SyncOperation } from '../types.js';
3
+ import { type LazyOperationExecuteReturnType, type LazyOperationState } from './types.js';
4
+ export declare function useLazyOperation<T extends SyncOperation<unknown, unknown>, TRequestContext extends RequestContext>(executor: IExecutor<TRequestContext>, operation: T): [
5
+ (variables: OperationVariables<T>, requestContext: TRequestContext) => LazyOperationExecuteReturnType<OperationResult<T>>,
6
+ {
7
+ state: ShallowRef<LazyOperationState<OperationResult<T>>>;
8
+ reset: () => void;
9
+ }
10
+ ];
@@ -0,0 +1,28 @@
1
+ import { shallowRef } from 'vue';
2
+ import { loadingState } from '../loading-state.js';
3
+ const lazyInitialState = Object.freeze({
4
+ state: 'initial',
5
+ });
6
+ async function execute(executor, operation, variables, requestContext, stateRef) {
7
+ let newState;
8
+ try {
9
+ const result = await executor.executeSync(operation, variables, requestContext);
10
+ newState = { state: 'success', ...result };
11
+ }
12
+ catch (error) {
13
+ newState = { state: 'failure', error: error };
14
+ }
15
+ stateRef.value = newState;
16
+ return newState;
17
+ }
18
+ export function useLazyOperation(executor, operation) {
19
+ const state = shallowRef(lazyInitialState);
20
+ const executeCallback = (variables, requestContext) => {
21
+ state.value = loadingState;
22
+ return execute(executor, operation, variables, requestContext, state);
23
+ };
24
+ const reset = () => {
25
+ state.value = lazyInitialState;
26
+ };
27
+ return [executeCallback, { state, reset }];
28
+ }
@@ -0,0 +1,2 @@
1
+ export { useLazyOperation } from './hook.js';
2
+ export type { LazyOperationInitialState, LazyOperationState, LazyOperationExecuteReturnType, UseLazyOperationReturnType, } from './types.js';
@@ -0,0 +1 @@
1
+ export { useLazyOperation } from './hook.js';
@@ -0,0 +1,14 @@
1
+ import type { RequestContext } from '../types.js';
2
+ import { type OperationFailureState, type OperationState, type OperationSuccessState } from '../useOperation.jsx';
3
+ export interface LazyOperationInitialState {
4
+ state: 'initial';
5
+ }
6
+ export type LazyOperationState<TResult> = OperationState<TResult> | LazyOperationInitialState;
7
+ export type LazyOperationExecuteReturnType<TResult> = Promise<OperationSuccessState<TResult> | OperationFailureState>;
8
+ export type UseLazyOperationReturnType<TVariables, TResult, TRequestContext extends RequestContext> = [
9
+ (variables: TVariables, requestContext: TRequestContext) => LazyOperationExecuteReturnType<TResult>,
10
+ {
11
+ state: LazyOperationState<TResult>;
12
+ reset: () => void;
13
+ }
14
+ ];
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,14 @@
1
+ import { ShallowRef, type Ref } from 'vue';
2
+ import type { ExecuteResult, IExecutor, OperationResult, OperationVariables, RequestContext, SyncOperation } from './types.js';
3
+ export interface OperationLoadingState {
4
+ state: 'loading';
5
+ }
6
+ export interface OperationSuccessState<TResult> extends ExecuteResult<TResult> {
7
+ state: 'success';
8
+ }
9
+ export interface OperationFailureState {
10
+ state: 'failure';
11
+ error: Error;
12
+ }
13
+ export type OperationState<TResult> = OperationLoadingState | OperationSuccessState<TResult> | OperationFailureState;
14
+ export declare function useOperation<T extends SyncOperation<unknown, unknown>, TRequestContext extends RequestContext>(executor: IExecutor<TRequestContext>, operation: T, variables: OperationVariables<T> | Ref<OperationVariables<T>>, requestContext: TRequestContext | Ref<TRequestContext>): ShallowRef<OperationState<OperationResult<T>>>;
@@ -0,0 +1,23 @@
1
+ import { isRef, onUnmounted, shallowRef, watch, } from 'vue';
2
+ import { loadingState } from './loading-state.js';
3
+ export function useOperation(executor, operation, variables, requestContext) {
4
+ const state = shallowRef(loadingState);
5
+ watch([
6
+ () => executor,
7
+ () => operation,
8
+ () => (isRef(variables) ? variables.value : variables),
9
+ () => isRef(requestContext) ? requestContext.value : requestContext,
10
+ ], ([exec, op, vars, ctx]) => {
11
+ exec.executeSync(op, vars, ctx)
12
+ .then((result) => {
13
+ state.value = { state: 'success', ...result };
14
+ })
15
+ .catch((error) => {
16
+ state.value = { state: 'failure', error: error };
17
+ });
18
+ }, { immediate: true, deep: true });
19
+ onUnmounted(() => {
20
+ state.value = loadingState;
21
+ });
22
+ return state;
23
+ }
@@ -0,0 +1,4 @@
1
+ import { type Ref, type ShallowRef } from 'vue';
2
+ import type { IExecutor, OperationResult, OperationVariables, RequestContext, SubOpAsyncIterable, SubscriptionOperation } from './types.js';
3
+ import { type OperationState } from './useOperation.js';
4
+ export declare function useSubscription<T extends SubscriptionOperation<unknown, unknown>, TRequestContext extends RequestContext>(executor: IExecutor<TRequestContext>, operation: T, variables: OperationVariables<T> | Ref<OperationVariables<T>>, requestContext: TRequestContext | Ref<TRequestContext>): ShallowRef<OperationState<SubOpAsyncIterable<OperationResult<T>>>>;
@@ -0,0 +1,43 @@
1
+ import { isRef, onUnmounted, shallowRef, watch, } from 'vue';
2
+ import { loadingState } from './loading-state.js';
3
+ export function useSubscription(executor, operation, variables, requestContext) {
4
+ const state = shallowRef(loadingState);
5
+ watch([
6
+ () => executor,
7
+ () => operation,
8
+ () => (isRef(variables) ? variables.value : variables),
9
+ () => isRef(requestContext) ? requestContext.value : requestContext,
10
+ ], ([exec, op, vars, ctx], _oldValues, onCleanup) => {
11
+ const controller = new AbortController();
12
+ onCleanup(() => {
13
+ if (state.value.state === 'success') {
14
+ state.value.result.close();
15
+ }
16
+ else {
17
+ controller.abort();
18
+ }
19
+ state.value = loadingState;
20
+ });
21
+ exec.executeSubscription(op, vars, ctx, controller)
22
+ .then((result) => {
23
+ if (controller.signal.aborted)
24
+ return;
25
+ state.value = { state: 'success', ...result };
26
+ })
27
+ .catch((error) => {
28
+ if (controller.signal.aborted)
29
+ return;
30
+ state.value = {
31
+ state: 'failure',
32
+ error: error,
33
+ };
34
+ });
35
+ }, { immediate: true, deep: true });
36
+ onUnmounted(() => {
37
+ if (state.value.state === 'success') {
38
+ state.value.result.close();
39
+ }
40
+ state.value = loadingState;
41
+ });
42
+ return state;
43
+ }