Skip to content

Commit 398b20f

Browse files
committed
adds the extracted @gadgetinc/core package
1 parent 1f933b1 commit 398b20f

23 files changed

+2988
-21
lines changed

packages/api-client-core/src/types.ts

Lines changed: 74 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,17 @@ import type {
1111
ViewFunctionWithVariables,
1212
} from "./GadgetFunctions.js";
1313

14+
/**
15+
* Allows detecting an any type, this is rather tricky:
16+
* The type constraint 0 extends 1 is not satisfied (0 is not assignable to 1),
17+
* so it should be impossible for 0 extends (1 & T) to be satisfied either, since (1 & T) should be even narrower than 1.
18+
* However, when T is any, it reduces 0 extends (1 & any) to 0 extends any, which is satisfied.
19+
* That's because any is intentionally unsound and acts as both a supertype and subtype of almost every other type.
20+
* source: https://stackoverflow.com/questions/49927523/disallow-call-with-any/49928360#49928360
21+
*/
22+
23+
type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;
24+
1425
/**
1526
* Limit the keys in T to only those that also exist in U. AKA Subset or Intersection.
1627
*/
@@ -33,10 +44,25 @@ export type VariablesOptions = Record<string, VariableOptions>;
3344
* Given an options object from a find method, default the type of the selection to a default if no selection is passed
3445
*/
3546
export type DefaultSelection<
36-
SelectionType,
37-
Options extends { select?: SelectionType | null },
38-
Defaults extends SelectionType
39-
> = Options["select"] extends SelectionType ? Options["select"] : Defaults;
47+
Available extends FieldSelection,
48+
Options extends { select?: Available | null },
49+
Defaults extends SomeFieldsSelected<Available>
50+
> = IfAny<Options, Defaults, IfAny<Options["select"], Defaults, Options["select"] extends Available ? Options["select"] : Defaults>>;
51+
52+
/**
53+
* Take a FieldSelection type and construct a type with all its fields required and selected.
54+
*/
55+
export type AllFieldsSelected<Selection extends FieldSelection> = {
56+
[K in keyof Selection]-?: NonNullable<Selection[K]> extends FieldSelection ? AllFieldsSelected<NonNullable<Selection[K]>> : true;
57+
};
58+
59+
/**
60+
* Take a FieldSelection type and construct a type with its fields set to true
61+
* rather than (boolean | null | undefined)
62+
*/
63+
export type SomeFieldsSelected<Selection extends FieldSelection> = {
64+
[K in keyof Selection]?: NonNullable<Selection[K]> extends FieldSelection ? SomeFieldsSelected<NonNullable<Selection[K]>> : true;
65+
};
4066

4167
/**
4268
* Describes an option set that accepts a selection
@@ -56,6 +82,16 @@ export interface BaseFindOptions<SelectionType = any> {
5682
live?: boolean;
5783
}
5884

85+
/**
86+
* Filter out any keys in `T` that are mapped to `never` recursively. Any nested objects that are empty after having never valued keys removed are also removed.
87+
*
88+
* ```typescript
89+
* type Thing = DeepFilterNever<
90+
* { a: { b: never }, c: string }
91+
* >; // { c: string; }
92+
* ```
93+
*/
94+
5995
/**
6096
* Get any keys of `Selection` that are not mapped to `never`
6197
*/
@@ -66,21 +102,36 @@ export type NonNeverKeys<Selection> = {
66102
/**
67103
* Filter out any keys in `T` that are mapped to `never`.
68104
*/
105+
69106
export type FilterNever<T extends Record<string, unknown>> = NonNeverKeys<T> extends never ? never : { [Key in NonNeverKeys<T>]: T[Key] };
70107

71-
type InnerSelect<Schema, Selection extends FieldSelection | null | undefined> = Selection extends null | undefined
72-
? never
73-
: Schema extends (infer T)[]
74-
? InnerSelect<T, Selection>[]
75-
: Schema extends null
76-
? InnerSelect<Exclude<Schema, null>, Selection> | null
77-
: {
78-
[Key in keyof Selection & keyof Schema]: Selection[Key] extends true
79-
? Schema[Key]
80-
: Selection[Key] extends FieldSelection
81-
? InnerSelect<Schema[Key], Selection[Key]>
82-
: never;
83-
};
108+
/**
109+
* Extract a subset of a schema given a selection
110+
*
111+
* ```typescript
112+
* type Selection = Select<
113+
* { apple: "red", banana: "yellow", orange: "orange" },
114+
* { apple: true, banana: false }
115+
* >; // { apple: "red" }
116+
* ```
117+
*/
118+
type InnerSelect<Schema, Selection extends FieldSelection | null | undefined> = IfAny<
119+
Selection,
120+
never,
121+
Selection extends null | undefined
122+
? never
123+
: Schema extends (infer T)[]
124+
? InnerSelect<T, Selection>[]
125+
: Schema extends null
126+
? InnerSelect<Exclude<Schema, null>, Selection> | null
127+
: {
128+
[Key in keyof Selection & keyof Schema]: Selection[Key] extends true
129+
? Schema[Key]
130+
: Selection[Key] extends FieldSelection
131+
? InnerSelect<Schema[Key], Selection[Key]>
132+
: never;
133+
}
134+
>;
84135

85136
/**
86137
* Filter out any keys in `T` that are mapped to `never` recursively. Any nested objects that are empty after having never valued keys removed are also removed.
@@ -757,6 +808,7 @@ export type PaginateOptions = {
757808
select?: AnySelection | InternalFieldSelection | null;
758809
};
759810

811+
/**
760812
/**
761813
* Convert a schema type into the type that a selection of it must extend
762814
*
@@ -780,9 +832,11 @@ export type PaginateOptions = {
780832
* }
781833
* }
782834
*/
783-
export type AvailableSelection<Schema> = Schema extends string | number | bigint | null | undefined
784-
? boolean | null | undefined
785-
: { [key in keyof Schema]?: AvailableSelection<Schema[key]> };
835+
export type AvailableSelection<Schema> = Schema extends Array<infer T>
836+
? AvailableSelection<T>
837+
: Schema extends object
838+
? { [key in keyof Schema]?: AvailableSelection<Schema[key]> }
839+
: boolean | null | undefined;
786840

787841
/** Options for configuring the queue for a background action */
788842
export interface BackgroundActionQueue {

packages/core/package.json

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
"name": "@gadgetinc/core",
3+
"version": "0.1.0",
4+
"files": [
5+
"README.md",
6+
"dist/**/*"
7+
],
8+
"license": "MIT",
9+
"repository": "github:gadget-inc/js-clients",
10+
"homepage": "https://github.com/gadget-inc/js-clients/tree/main/packages/core",
11+
"type": "module",
12+
"exports": {
13+
"./package.json": "./package.json",
14+
".": {
15+
"import": "./dist/esm/index.js",
16+
"require": "./dist/cjs/index.js",
17+
"default": "./dist/esm/index.js"
18+
}
19+
},
20+
"source": "src/index.ts",
21+
"main": "dist/cjs/index.js",
22+
"sideEffects": false,
23+
"scripts": {
24+
"typecheck:main": "tsc --noEmit",
25+
"typecheck": "tsc --noEmit",
26+
"clean": "rimraf dist/ *.tsbuildinfo **/*.tsbuildinfo",
27+
"prebuild": "mkdir -p dist/cjs dist/esm && echo '{\"type\": \"commonjs\"}' > dist/cjs/package.json && echo '{\"type\": \"module\"}' > dist/esm/package.json",
28+
"build": "pnpm clean && pnpm prebuild && tsc -b tsconfig.cjs.json tsconfig.esm.json",
29+
"prepublishOnly": "pnpm build",
30+
"prerelease": "gitpkg publish"
31+
},
32+
"dependencies": {
33+
"klona": "^2.0.6"
34+
},
35+
"peerDependencies": {
36+
"@urql/core": "*",
37+
"graphql": "*",
38+
"graphql-ws": "*"
39+
},
40+
"devDependencies": {
41+
"type-fest": "^3.13.1"
42+
}
43+
}

packages/core/src/AnyClient.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import type { AnyConnection } from "./AnyConnection.js";
2+
import type { GadgetTransaction } from "./GadgetTransaction.js";
3+
import type { AnyInternalModelManager } from "./InternalModelManager.js";
4+
5+
export const $modelRelationships = Symbol.for("gadget/modelRelationships");
6+
7+
export type InternalModelManagerNamespace = {
8+
// internal model managers can be maps of model names to model managers, subnamespaces, or utility functions
9+
[key: string]: AnyInternalModelManager | InternalModelManagerNamespace | ((...args: any[]) => any);
10+
};
11+
12+
/**
13+
* An instance of any Gadget app's API client object
14+
*/
15+
export interface AnyClient {
16+
connection: AnyConnection;
17+
query(graphQL: string, variables?: Record<string, any>): Promise<any>;
18+
mutate(graphQL: string, variables?: Record<string, any>): Promise<any>;
19+
transaction<T>(callback: (transaction: GadgetTransaction) => Promise<T>): Promise<T>;
20+
internal: InternalModelManagerNamespace;
21+
apiClientCoreVersion?: string;
22+
[$modelRelationships]?: { [modelName: string]: { [apiIdentifier: string]: { type: string; model: string } } };
23+
}
24+
25+
/**
26+
* Checks if the given object is an instance of any Gadget app's generated JS client object
27+
*/
28+
export const isGadgetClient = (client: any): client is AnyClient => {
29+
return client && "connection" in client && client.connection && "endpoint" in client.connection;
30+
};

packages/core/src/AnyConnection.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import type { Client, ClientOptions } from "@urql/core";
2+
import type { ClientOptions as SubscriptionClientOptions, createClient as createSubscriptionClient } from "graphql-ws";
3+
import type { AuthenticationModeOptions, Exchanges } from "./ClientOptions.js";
4+
import { GadgetTransaction } from "./GadgetTransaction.js";
5+
6+
export interface GadgetSubscriptionClientOptions extends Partial<SubscriptionClientOptions> {
7+
urlParams?: Record<string, string | null | undefined>;
8+
connectionAttempts?: number;
9+
connectionGlobalTimeoutMs?: number;
10+
}
11+
12+
/**
13+
* Represents the current strategy for authenticating with the Gadget platform.
14+
* For individual users in web browsers, we authenticate using a session token stored client side, like a cookie, but with cross domain support.
15+
* For server to server communication, or traceable access from the browser, we use pre shared secrets called API Keys
16+
* And when within the Gadget platform itself, we use a private secret token called an Internal Auth Token. Internal Auth Tokens are managed by Gadget and should never be used by external developers.
17+
**/
18+
export enum AuthenticationMode {
19+
BrowserSession = "browser-session",
20+
APIKey = "api-key",
21+
Internal = "internal",
22+
InternalAuthToken = "internal-auth-token",
23+
Anonymous = "anonymous",
24+
Custom = "custom",
25+
}
26+
27+
export interface GadgetConnectionOptions {
28+
endpoint: string;
29+
authenticationMode?: AuthenticationModeOptions;
30+
websocketsEndpoint?: string;
31+
subscriptionClientOptions?: GadgetSubscriptionClientOptions;
32+
websocketImplementation?: typeof globalThis.WebSocket;
33+
fetchImplementation?: typeof globalThis.fetch;
34+
environment?: string;
35+
requestPolicy?: ClientOptions["requestPolicy"];
36+
applicationId?: string;
37+
baseRouteURL?: string;
38+
exchanges?: Exchanges;
39+
createSubscriptionClient?: typeof createSubscriptionClient;
40+
}
41+
42+
export type TransactionRun<T> = (transaction: GadgetTransaction) => Promise<T>;
43+
44+
export interface AnyConnection {
45+
endpoint: string;
46+
authenticationMode: AuthenticationMode;
47+
createSubscriptionClient: typeof createSubscriptionClient;
48+
options: GadgetConnectionOptions;
49+
get currentClient(): Client;
50+
transaction: {
51+
<T>(options: GadgetSubscriptionClientOptions, run: TransactionRun<T>): Promise<T>;
52+
<T>(run: TransactionRun<T>): Promise<T>;
53+
};
54+
close(): void;
55+
fetch: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
56+
}

0 commit comments

Comments
 (0)