Skip to content

Commit

Permalink
BREAKING use Typescript strict mode
Browse files Browse the repository at this point in the history
This is breaking because:

- `ApolloBase.client` throws an error if no client has been created
  beforehand. The behavior now matches the typing that always declared a
  client existed. In most cases, you should pass either `apolloOptions`
  or `apolloNamedOptions` to `Apollo.constructor` to create the
  client immediately upon construction.
- `ApolloBase.query()`, `ApolloBase.mutate()` and
  `ApolloBase.subscribe()` all have a new constraint on `V`. If you
  inherit from this class, you might need to adjust your typing.
- Classes that inherit `Query`, `Mutation` and `Subscription` must
  declare the `document` member. This requirement always existed at
  runtime but was not enforced at compile time until now. If you
  generated code, you have nothing to do.
- `QueryRef.getLastResult()` and `QueryRef.getLastError()` might return
  `undefined`. This was always the case, but was typed incorrectly until
  now.
- `pickFlag()` was dropped without any replacement.
- `createPersistedQueryLink()` requires options. This was always the
  case but was typed incorrectly until now.
  • Loading branch information
PowerKiKi committed Apr 5, 2024
1 parent 632de2d commit 712205f
Show file tree
Hide file tree
Showing 20 changed files with 135 additions and 92 deletions.
26 changes: 26 additions & 0 deletions .changeset/chilly-spiders-protect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
"apollo-angular": major
---

BREAKING use Typescript strict mode

This is breaking because:

- `ApolloBase.client` throws an error if no client has been created
beforehand. The behavior now matches the typing that always declared a
client existed. In most cases, you should pass either `apolloOptions`
or `apolloNamedOptions` to `Apollo.constructor` to create the
client immediately upon construction.
- `ApolloBase.query()`, `ApolloBase.mutate()` and
`ApolloBase.subscribe()` all have a new constraint on `V`. If you
inherit from this class, you might need to adjust your typing.
- Classes that inherit `Query`, `Mutation` and `Subscription` must
declare the `document` member. This requirement always existed at
runtime but was not enforced at compile time until now. If you generated
code, you have nothing to do.
- `QueryRef.getLastResult()` and `QueryRef.getLastError()` might return
`undefined`. This was always the case, but was typed incorrectly until
now.
- `pickFlag()` was dropped without any replacement.
- `createPersistedQueryLink()` requires options. This was always the
case but was typed incorrectly until now.
33 changes: 25 additions & 8 deletions packages/apollo-angular/http/src/http-batch-link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,27 @@ import { BatchHandler, BatchLink } from '@apollo/client/link/batch';
import { BatchOptions, Body, Context, OperationPrinter, Options, Request } from './types';
import { createHeadersWithClientAwareness, fetch, mergeHeaders, prioritize } from './utils';

const defaults = {
export const defaults = {
batchInterval: 10,
batchMax: 10,
uri: 'graphql',
method: 'POST',
};
withCredentials: false,
includeQuery: true,
includeExtensions: false,
useMultipart: false,
} as const;

/**
* Decides which value to pick from Context, Options or defaults
*/
export function pick<K extends keyof Omit<typeof defaults, 'batchInterval' | 'batchMax'>>(
context: Context,
options: Options,
key: K,
): ReturnType<typeof prioritize<Context[K] | Options[K] | (typeof defaults)[K]>> {
return prioritize(context[key], options[key], defaults[key]);
}

export class HttpBatchLinkHandler extends ApolloLink {
public batcher: ApolloLink;
Expand Down Expand Up @@ -87,13 +102,15 @@ export class HttpBatchLinkHandler extends ApolloLink {
});
}

private createOptions(operations: Operation[]): Options {
private createOptions(
operations: Operation[],
): Required<Pick<Options, 'method' | 'uri' | 'withCredentials'>> {
const context: Context = operations[0].getContext();

return {
method: prioritize(context.method, this.options.method, defaults.method),
uri: prioritize(context.uri, this.options.uri, defaults.uri),
withCredentials: prioritize(context.withCredentials, this.options.withCredentials),
method: pick(context, this.options, 'method'),
uri: pick(context, this.options, 'uri'),
withCredentials: pick(context, this.options, 'withCredentials'),
};
}

Expand Down Expand Up @@ -147,15 +164,15 @@ export class HttpBatchLinkHandler extends ApolloLink {
}

const headers =
context.headers && context.headers.keys().map((k: string) => context.headers.get(k));
context.headers && context.headers.keys().map((k: string) => context.headers!.get(k));

const opts = JSON.stringify({
includeQuery: context.includeQuery,
includeExtensions: context.includeExtensions,
headers,
});

return prioritize(context.uri, this.options.uri) + opts;
return prioritize(context.uri, this.options.uri, '') + opts;
}

public request(op: Operation): LinkObservable<FetchResult> | null {
Expand Down
23 changes: 8 additions & 15 deletions packages/apollo-angular/http/src/http-link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import {
Observable as LinkObservable,
Operation,
} from '@apollo/client/core';
import { pick } from './http-batch-link';
import { Body, Context, OperationPrinter, Options, Request } from './types';
import { createHeadersWithClientAwareness, fetch, mergeHeaders, prioritize } from './utils';
import { createHeadersWithClientAwareness, fetch, mergeHeaders } from './utils';

// XXX find a better name for it
export class HttpLinkHandler extends ApolloLink {
Expand All @@ -29,20 +30,12 @@ export class HttpLinkHandler extends ApolloLink {
new LinkObservable((observer: any) => {
const context: Context = operation.getContext();

// decides which value to pick, Context, Options or to just use the default
const pick = <K extends keyof Context>(
key: K,
init?: Context[K] | Options[K],
): Context[K] | Options[K] => {
return prioritize(context[key], this.options[key], init);
};

let method = pick('method', 'POST');
const includeQuery = pick('includeQuery', true);
const includeExtensions = pick('includeExtensions', false);
const url = pick('uri', 'graphql');
const withCredentials = pick('withCredentials');
const useMultipart = pick('useMultipart');
let method = pick(context, this.options, 'method');
const includeQuery = pick(context, this.options, 'includeQuery');
const includeExtensions = pick(context, this.options, 'includeExtensions');
const url = pick(context, this.options, 'uri');
const withCredentials = pick(context, this.options, 'withCredentials');
const useMultipart = pick(context, this.options, 'useMultipart');
const useGETForQueries = this.options.useGETForQueries === true;

const isQuery = operation.query.definitions.some(
Expand Down
19 changes: 9 additions & 10 deletions packages/apollo-angular/http/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,26 +109,25 @@ export const fetch = (
});
};

export const mergeHeaders = (source: HttpHeaders, destination: HttpHeaders): HttpHeaders => {
export const mergeHeaders = (
source: HttpHeaders | undefined,
destination: HttpHeaders,
): HttpHeaders => {
if (source && destination) {
const merged = destination
.keys()
.reduce((headers, name) => headers.set(name, destination.getAll(name)), source);
.reduce((headers, name) => headers.set(name, destination.getAll(name)!), source);

return merged;
}

return destination || source;
};

export function prioritize<T>(...values: T[]): T {
const picked = values.find(val => typeof val !== 'undefined');

if (typeof picked === 'undefined') {
return values[values.length - 1];
}

return picked;
export function prioritize<T>(
...values: [NonNullable<T>, ...T[]] | [...T[], NonNullable<T>]
): NonNullable<T> {
return values.find(val => typeof val !== 'undefined') as NonNullable<T>;
}

export function createHeadersWithClientAwareness(context: Record<string, any>) {
Expand Down
2 changes: 1 addition & 1 deletion packages/apollo-angular/persisted-queries/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@ const transformLink = setContext((_, context) => {
return ctx;
});

export const createPersistedQueryLink = (options?: Options) =>
export const createPersistedQueryLink = (options: Options) =>
ApolloLink.from([_createPersistedQueryLink(options), transformLink as any]);
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ApolloLink, execute, gql, Observable, Operation } from '@apollo/client/core';
import { ApolloLink, execute, FetchResult, gql, Observable, Operation } from '@apollo/client/core';
import { createPersistedQueryLink } from '../src';

const query = gql`
Expand All @@ -23,7 +23,7 @@ class MockLink extends ApolloLink {
}

public request(operation: Operation) {
return new Observable(observer => {
return new Observable<FetchResult>(observer => {
const request: any = {};

if (operation.getContext().includeQuery) {
Expand Down
8 changes: 6 additions & 2 deletions packages/apollo-angular/schematics/install/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ function addDependencies(options: Schema): Rule {
function includeAsyncIterableLib(): Rule {
const requiredLib = 'esnext.asynciterable';

function updateFn(tsconfig: any) {
function updateFn(tsconfig: any): boolean {
const compilerOptions: CompilerOptions = tsconfig.compilerOptions;

if (
Expand All @@ -85,6 +85,8 @@ function includeAsyncIterableLib(): Rule {
compilerOptions.lib.push(requiredLib);
return true;
}

return false;
}

return (host: Tree) => {
Expand Down Expand Up @@ -127,7 +129,7 @@ function updateTSConfig(
}

function allowSyntheticDefaultImports(): Rule {
function updateFn(tsconfig: any) {
function updateFn(tsconfig: any): boolean {
if (
tsconfig?.compilerOptions &&
tsconfig?.compilerOptions?.lib &&
Expand All @@ -136,6 +138,8 @@ function allowSyntheticDefaultImports(): Rule {
tsconfig.compilerOptions.allowSyntheticDefaultImports = true;
return true;
}

return false;
}

return (host: Tree) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/apollo-angular/schematics/install/schema.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export interface Schema {
/** Name of the project to target. */
project?: string;
project: string;
/** Url to your GraphQL endpoint */
endpoint?: string;
/** Version of GraphQL (16 by default) */
Expand Down
2 changes: 2 additions & 0 deletions packages/apollo-angular/schematics/tests/ng-add.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ describe('ng-add with module', () => {
const { dependencies } = packageJson;

const dependenciesMap = createDependenciesMap({
project: 'my-project',
graphql: '16',
});

Expand Down Expand Up @@ -72,6 +73,7 @@ describe('ng-add with standalone', () => {
const { dependencies } = packageJson;

const dependenciesMap = createDependenciesMap({
project: 'my-project',
graphql: '16',
});

Expand Down
10 changes: 5 additions & 5 deletions packages/apollo-angular/schematics/utils/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export async function addModuleImportToRootModule(
host: Tree,
importedModuleName: string,
importedModulePath: string,
projectName?: string,
projectName: string,
) {
const mainPath = await getMainFilePath(host, projectName);
if (isStandaloneApp(host, mainPath)) {
Expand Down Expand Up @@ -67,7 +67,7 @@ function addModuleImportToModule(
}

changes
.filter((change: Change) => change instanceof InsertChange)
.filter((change: Change): change is InsertChange => change instanceof InsertChange)
.forEach((change: InsertChange) => recorder.insertLeft(change.pos, change.toAdd));

host.commitUpdate(recorder);
Expand Down Expand Up @@ -98,7 +98,7 @@ function _addSymbolToNgModuleMetadata(
const matchingProperties: ts.ObjectLiteralElement[] = (
node as ts.ObjectLiteralExpression
).properties
.filter(prop => prop.kind == ts.SyntaxKind.PropertyAssignment)
.filter((prop): prop is ts.PropertyAssignment => prop.kind == ts.SyntaxKind.PropertyAssignment)
// Filter out every fields that's not "metadataField". Also handles string literals
// (but not expressions).
.filter((prop: ts.PropertyAssignment) => {
Expand Down Expand Up @@ -218,7 +218,7 @@ function getDecoratorMetadata(
source,
ts.SyntaxKind.ImportDeclaration,
)
.map((node: ts.ImportDeclaration) => _angularImportsFromNode(node, source))
.map(node => _angularImportsFromNode(node as ts.ImportDeclaration, source))
.reduce((acc: { [name: string]: string }, current: { [name: string]: string }) => {
for (const key of Object.keys(current)) {
acc[key] = current[key];
Expand Down Expand Up @@ -418,7 +418,7 @@ export function insertImport(

// no such import declaration exists
const useStrict = findNodes(rootNode, ts.SyntaxKind.StringLiteral).filter(
(n: ts.StringLiteral) => n.text === 'use strict',
n => (n as ts.StringLiteral).text === 'use strict',
);
let fallbackPos = 0;
if (useStrict.length > 0) {
Expand Down
Loading

0 comments on commit 712205f

Please sign in to comment.