diff --git a/.changeset/strange-rice-march.md b/.changeset/strange-rice-march.md new file mode 100644 index 00000000..536b3100 --- /dev/null +++ b/.changeset/strange-rice-march.md @@ -0,0 +1,14 @@ +--- +"@zemble/auth-anonymous": patch +"@zemble/auth-api-token": patch +"@zemble/auth-apple": patch +"@zemble/auth-expo": patch +"@zemble/auth-otp": patch +"@zemble/auth": patch +"@zemble/bull": patch +"@zemble/cms": patch +"create-zemble-app": patch +"supplement-stack": patch +--- + +Strict up some types diff --git a/.env.github b/.env.github index 15f74560..cd30a828 100644 --- a/.env.github +++ b/.env.github @@ -1,2 +1,2 @@ -BUN_VERSION=1.1.5 +BUN_VERSION=1.1.7 MONGOMS_PREFER_GLOBAL_PATH=true \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index ec4faa0a..661c3df5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -37,7 +37,10 @@ "cSpell.words": [ "composability", "dataloaders", + "pino", "pucelle", + "SENDGRID", + "supabase", "tldr", "zemble" ], diff --git a/apps/supplement-stack/app.ts b/apps/supplement-stack/app.ts index 9dadd587..45a0d0ea 100644 --- a/apps/supplement-stack/app.ts +++ b/apps/supplement-stack/app.ts @@ -27,7 +27,7 @@ void bunRunner({ }), AuthOTP.configure({ from: { email: 'robert@herber.me' }, - generateTokenContents: async ({ email }): Promise => { + generateTokenContents: async ({ email }) => { const user = await Users.findOneAndUpdate({ email }, { $set: { lastLoginAt: new Date(), @@ -41,12 +41,12 @@ void bunRunner({ returnDocument: 'after', }) - const ret = ({ + const ret = { email, type: 'AuthOtp' as const, userId: user!._id.toHexString(), sub: user!._id.toHexString(), - }) + } return ret }, diff --git a/apps/supplement-stack/clients/papr.ts b/apps/supplement-stack/clients/papr.ts index f4a03d86..103603bb 100644 --- a/apps/supplement-stack/clients/papr.ts +++ b/apps/supplement-stack/clients/papr.ts @@ -9,7 +9,7 @@ export let client: MongoClient | undefined const papr = new Papr() export async function connect({ logger }: {readonly logger: IStandardLogger}) { - const mongoUrl = process.env.MONGO_URL + const mongoUrl = process.env['MONGO_URL'] if (!mongoUrl) throw new Error('MONGO_URL not set') diff --git a/apps/supplement-stack/graphql/Mutation/addIngredient.ts b/apps/supplement-stack/graphql/Mutation/addIngredient.ts index 0b107f31..4c4684a1 100644 --- a/apps/supplement-stack/graphql/Mutation/addIngredient.ts +++ b/apps/supplement-stack/graphql/Mutation/addIngredient.ts @@ -8,7 +8,7 @@ import type { } from '../schema.generated' import type { WithoutId } from 'mongodb' -export const addIngredient: NonNullable = async (parent, { +export const addIngredient: NonNullable = async (_, { title, imageUrls, nutrientsPer100g, servingSizes, ingredientId, }, { logger }) => { const _id = ingredientId ? new ObjectId(ingredientId) : new ObjectId() diff --git a/apps/supplement-stack/graphql/Mutation/addSupplement.ts b/apps/supplement-stack/graphql/Mutation/addSupplement.ts index 8dd53d43..c0aa1c8d 100644 --- a/apps/supplement-stack/graphql/Mutation/addSupplement.ts +++ b/apps/supplement-stack/graphql/Mutation/addSupplement.ts @@ -8,13 +8,13 @@ import type { } from '../schema.generated' import type { DocumentForInsert } from 'papr' -export const addSupplement: NonNullable = async (parent, { +export const addSupplement: NonNullable = async (_, { amountInGrams, foodId, intakeTime, supplementId, }, { decodedToken }) => { const supplement: DocumentForInsert = { amountInGrams, foodId: new ObjectId(foodId), - userId: new ObjectId(decodedToken!.userId), + userId: new ObjectId(decodedToken!.sub), intakeTime, } diff --git a/apps/supplement-stack/graphql/Query/mySupplementIntakes.ts b/apps/supplement-stack/graphql/Query/mySupplementIntakes.ts index e7924691..4d369272 100644 --- a/apps/supplement-stack/graphql/Query/mySupplementIntakes.ts +++ b/apps/supplement-stack/graphql/Query/mySupplementIntakes.ts @@ -6,7 +6,7 @@ import type { QueryResolvers } from '../schema.generated' export const mySupplementIntakes: NonNullable = async (_, __, { decodedToken }) => { const results = await Supplements.find({ - userId: new ObjectId(decodedToken!.userId), + userId: new ObjectId(decodedToken!.sub), }) return results diff --git a/apps/supplement-stack/graphql/Type/SupplementIntake.ts b/apps/supplement-stack/graphql/Type/SupplementIntake.ts index bdd341c2..f36286f3 100644 --- a/apps/supplement-stack/graphql/Type/SupplementIntake.ts +++ b/apps/supplement-stack/graphql/Type/SupplementIntake.ts @@ -3,7 +3,7 @@ import { Eatables } from '../../models' import type { SupplementIntakeResolvers } from '../schema.generated' const SupplementIntake: SupplementIntakeResolvers = { - food: async ({ foodId }, args, context) => { + food: async ({ foodId }) => { const eatable = await Eatables.findOne({ _id: foodId }) return eatable! diff --git a/build.ts b/build.ts index 523169cc..97450b58 100644 --- a/build.ts +++ b/build.ts @@ -3,7 +3,7 @@ import { readdirSync, statSync } from 'node:fs' import { join } from 'node:path' const target = process.argv.slice(2)[0] ?? 'node' -if (process.env.DEBUG) { +if (process.env['DEBUG']) { console.log(`Target: ${target}`) } @@ -36,7 +36,7 @@ void Bun.build({ if (stdout.logs.length > 0) { console.log(stdout.logs) } - if (process.env.DEBUG) { + if (process.env['DEBUG']) { if (stdout.success) { console.log('success!') } diff --git a/bun.lockb b/bun.lockb index e8479962..9be91043 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/packages/auth-anonymous/graphql/Mutation/login.ts b/packages/auth-anonymous/graphql/Mutation/login.ts index 3a709a51..b7d97b21 100644 --- a/packages/auth-anonymous/graphql/Mutation/login.ts +++ b/packages/auth-anonymous/graphql/Mutation/login.ts @@ -8,11 +8,11 @@ import plugin from '../../plugin' import type { MutationResolvers } from '../schema.generated' -const login: MutationResolvers['login'] = async (_: unknown, __, { honoContext }) => { +const login: MutationResolvers['loginAnonymous'] = async (_: unknown, __, { honoContext }) => { const userId = plugin.config.generateUserId(), tokenData = plugin.config.generateTokenContents(userId), bearerToken = await encodeToken(tokenData, userId), - refreshToken = await generateRefreshToken({ sub: tokenData.sub }) + refreshToken = await generateRefreshToken({ sub: tokenData.userId }) if (authPlugin.config.cookies.isEnabled) { setTokenCookies(honoContext, bearerToken, refreshToken) diff --git a/packages/auth-anonymous/plugin.ts b/packages/auth-anonymous/plugin.ts index d85309b3..be07e419 100644 --- a/packages/auth-anonymous/plugin.ts +++ b/packages/auth-anonymous/plugin.ts @@ -1,21 +1,23 @@ /* eslint-disable @typescript-eslint/no-namespace */ import Auth from '@zemble/auth' -import { Plugin, type BaseToken } from '@zemble/core' +import { Plugin } from '@zemble/core' declare global { namespace Zemble { + interface AnonymousAuthToken { + readonly type: 'AnonymousAuth', + readonly userId: string, + } + interface TokenRegistry { - readonly AnonymousAuth: { - readonly type: 'AnonymousAuth', - readonly userId: string, - } & BaseToken + readonly AnonymousAuth: AnonymousAuthToken } } } interface AnonymousConfig extends Zemble.GlobalConfig { - readonly generateTokenContents?: (userId: string) => Zemble.TokenRegistry[keyof Zemble.TokenRegistry], + readonly generateTokenContents?: (userId: string) => Zemble.AnonymousAuthToken, readonly generateUserId?: () => string, } diff --git a/packages/auth-api-token/plugin.ts b/packages/auth-api-token/plugin.ts index 0847f901..1333d87e 100644 --- a/packages/auth-api-token/plugin.ts +++ b/packages/auth-api-token/plugin.ts @@ -2,8 +2,8 @@ import Auth from '@zemble/auth' import { Plugin } from '@zemble/core' import GraphQLYoga from '@zemble/graphql' -const API_KEY_SECRET = process.env.API_KEY_SECRET ?? 'top-secret' -const INVALIDATE_API_KEYS_IAT_BEFORE = process.env.INVALIDATE_API_KEYS_IAT_BEFORE ? parseInt(process.env.INVALIDATE_API_KEYS_IAT_BEFORE, 10) : 0 +const API_KEY_SECRET = process.env['API_KEY_SECRET'] ?? 'top-secret' +const INVALIDATE_API_KEYS_IAT_BEFORE = process.env['INVALIDATE_API_KEYS_IAT_BEFORE'] ? parseInt(process.env['INVALIDATE_API_KEYS_IAT_BEFORE'], 10) : 0 interface AuthConfig extends Zemble.GlobalConfig { readonly API_KEY_SECRET?: string; diff --git a/packages/auth-api-token/utils/isAPIKeyValid.ts b/packages/auth-api-token/utils/isAPIKeyValid.ts index fbe7898c..3ca3a488 100644 --- a/packages/auth-api-token/utils/isAPIKeyValid.ts +++ b/packages/auth-api-token/utils/isAPIKeyValid.ts @@ -6,7 +6,7 @@ import plugin from '../plugin' const { INVALIDATE_API_KEYS_IAT_BEFORE } = plugin.config export const isAPIKeyValid = async (token: string) => { - const keyContents = await verifyJwt(token) + const keyContents = await verifyJwt(token) as { readonly isAPIKey: boolean, readonly iat: number } return 'isAPIKey' in keyContents && !!keyContents.isAPIKey && keyContents.iat && INVALIDATE_API_KEYS_IAT_BEFORE < keyContents.iat } diff --git a/packages/auth-apple/graphql/schema.generated.ts b/packages/auth-apple/graphql/schema.generated.ts index ed00cd32..2b51dbd3 100644 --- a/packages/auth-apple/graphql/schema.generated.ts +++ b/packages/auth-apple/graphql/schema.generated.ts @@ -16,6 +16,7 @@ export type Scalars = { Boolean: { input: boolean; output: boolean; } Int: { input: number; output: number; } Float: { input: number; output: number; } + DateTime: { input: any; output: any; } JSONObject: { input: any; output: any; } }; @@ -45,9 +46,16 @@ export type AuthOr = { readonly match?: InputMaybe; }; +export type Error = { + readonly message: Scalars['String']['output']; +}; + export type Mutation = { readonly __typename?: 'Mutation'; readonly loginWithApple: AppleLoginResponse; + readonly logout: Scalars['DateTime']['output']; + readonly logoutFromAllDevices: Scalars['DateTime']['output']; + readonly refreshToken: NewTokenResponse; }; @@ -61,9 +69,41 @@ export type MutationLoginWithAppleArgs = { userUUID: Scalars['String']['input']; }; + +export type MutationRefreshTokenArgs = { + bearerToken: Scalars['String']['input']; + refreshToken: Scalars['String']['input']; +}; + +export type NewTokenResponse = NewTokenSuccessResponse | RefreshTokenInvalidError; + +export type NewTokenSuccessResponse = { + readonly __typename?: 'NewTokenSuccessResponse'; + readonly bearerToken: Scalars['String']['output']; + readonly refreshToken: Scalars['String']['output']; +}; + export type Query = { readonly __typename?: 'Query'; + readonly publicKey?: Maybe; + readonly readJWT: Scalars['JSONObject']['output']; readonly state: Scalars['String']['output']; + readonly validateJWT: Scalars['Boolean']['output']; +}; + + +export type QueryReadJwtArgs = { + token: Scalars['String']['input']; +}; + + +export type QueryValidateJwtArgs = { + token: Scalars['String']['input']; +}; + +export type RefreshTokenInvalidError = { + readonly __typename?: 'RefreshTokenInvalidError'; + readonly message: Scalars['String']['output']; }; export type WithIndex = TObject & Record; @@ -134,7 +174,15 @@ export type DirectiveResolverFn TResult | Promise; +/** Mapping of union types */ +export type ResolversUnionTypes> = ResolversObject<{ + NewTokenResponse: ( NewTokenSuccessResponse ) | ( RefreshTokenInvalidError ); +}>; +/** Mapping of interface types */ +export type ResolversInterfaceTypes> = ResolversObject<{ + Error: never; +}>; /** Mapping between all available schema types and the resolvers types */ export type ResolversTypes = ResolversObject<{ @@ -143,9 +191,14 @@ export type ResolversTypes = ResolversObject<{ AppleLoginResponse: ResolverTypeWrapper; AuthOr: AuthOr; Boolean: ResolverTypeWrapper; + DateTime: ResolverTypeWrapper; + Error: ResolverTypeWrapper['Error']>; JSONObject: ResolverTypeWrapper; Mutation: ResolverTypeWrapper<{}>; + NewTokenResponse: ResolverTypeWrapper['NewTokenResponse']>; + NewTokenSuccessResponse: ResolverTypeWrapper; Query: ResolverTypeWrapper<{}>; + RefreshTokenInvalidError: ResolverTypeWrapper; String: ResolverTypeWrapper; }>; @@ -155,9 +208,14 @@ export type ResolversParentTypes = ResolversObject<{ AppleLoginResponse: AppleLoginResponse; AuthOr: AuthOr; Boolean: Scalars['Boolean']['output']; + DateTime: Scalars['DateTime']['output']; + Error: ResolversInterfaceTypes['Error']; JSONObject: Scalars['JSONObject']['output']; Mutation: {}; + NewTokenResponse: ResolversUnionTypes['NewTokenResponse']; + NewTokenSuccessResponse: NewTokenSuccessResponse; Query: {}; + RefreshTokenInvalidError: RefreshTokenInvalidError; String: Scalars['String']['output']; }>; @@ -176,23 +234,58 @@ export type AppleLoginResponseResolvers; }>; +export interface DateTimeScalarConfig extends GraphQLScalarTypeConfig { + name: 'DateTime'; +} + +export type ErrorResolvers = ResolversObject<{ + __resolveType: TypeResolveFn; + message?: Resolver>; +}>; + export interface JsonObjectScalarConfig extends GraphQLScalarTypeConfig { name: 'JSONObject'; } export type MutationResolvers = ResolversObject<{ loginWithApple?: Resolver, RequireFields>; + logout?: Resolver; + logoutFromAllDevices?: Resolver; + refreshToken?: Resolver, RequireFields>; +}>; + +export type NewTokenResponseResolvers = ResolversObject<{ + __resolveType: TypeResolveFn<'NewTokenSuccessResponse' | 'RefreshTokenInvalidError', ParentType, ContextType>; +}>; + +export type NewTokenSuccessResponseResolvers = ResolversObject<{ + bearerToken?: Resolver>; + refreshToken?: Resolver>; + __isTypeOf?: IsTypeOfResolverFn; }>; export type QueryResolvers = ResolversObject<{ + publicKey?: Resolver, ParentType, Zemble.AuthContextWithToken>; + readJWT?: Resolver, RequireFields>; state?: Resolver>; + validateJWT?: Resolver, RequireFields>; +}>; + +export type RefreshTokenInvalidErrorResolvers = ResolversObject<{ + message?: Resolver>; + __isTypeOf?: IsTypeOfResolverFn; }>; export type Resolvers = ResolversObject<{ AppleLoginResponse?: AppleLoginResponseResolvers; + DateTime?: GraphQLScalarType; + Error?: ErrorResolvers; JSONObject?: GraphQLScalarType; Mutation?: MutationResolvers; + NewTokenResponse?: NewTokenResponseResolvers; + NewTokenSuccessResponse?: NewTokenSuccessResponseResolvers; Query?: QueryResolvers; + RefreshTokenInvalidError?: RefreshTokenInvalidErrorResolvers; }>; export type DirectiveResolvers = ResolversObject<{ diff --git a/packages/auth-apple/plugin.ts b/packages/auth-apple/plugin.ts index 2f02a1d7..d55d8481 100644 --- a/packages/auth-apple/plugin.ts +++ b/packages/auth-apple/plugin.ts @@ -56,12 +56,12 @@ function generateTokenContents(jwtContents: AppleJwtContents): Zemble.AppleToken const defaultConfig = { generateTokenContents, - AUTHENTICATED_REDIRECT_URL: process.env.AUTH_LOGGED_IN_REDIRECT_URL ?? '/', - UNAUTHENTICATED_REDIRECT_URL: process.env.AUTH_LOGIN_REDIRECT_URL ?? '/login', - INTERNAL_URL: process.env.INTERNAL_URL ?? 'http://localhost:3000', - APPLE_CLIENT_ID: process.env.APPLE_CLIENT_ID, - PRIVATE_KEY: process.env.PRIVATE_KEY, - PUBLIC_KEY: process.env.PUBLIC_KEY, + AUTHENTICATED_REDIRECT_URL: process.env['AUTH_LOGGED_IN_REDIRECT_URL'] ?? '/', + UNAUTHENTICATED_REDIRECT_URL: process.env['AUTH_LOGIN_REDIRECT_URL'] ?? '/login', + INTERNAL_URL: process.env['INTERNAL_URL'] ?? 'http://localhost:3000', + APPLE_CLIENT_ID: process.env['APPLE_CLIENT_ID'], + PRIVATE_KEY: process.env['PRIVATE_KEY'], + PUBLIC_KEY: process.env['PUBLIC_KEY'], appleAuthInitializePath: '/auth/apple', appleAuthCallbackPath: '/auth/apple/callback', } satisfies AppleAuthConfig diff --git a/packages/auth-expo/config.ts b/packages/auth-expo/config.ts index 671764a3..2ae7c657 100644 --- a/packages/auth-expo/config.ts +++ b/packages/auth-expo/config.ts @@ -1 +1 @@ -export const TOKEN_KEY = process.env.EXPO_PUBLIC_TOKEN_KEY ?? 'auth-token' +export const TOKEN_KEY = process.env['EXPO_PUBLIC_TOKEN_KEY'] ?? 'auth-token' diff --git a/packages/auth-expo/tsconfig.json b/packages/auth-expo/tsconfig.json index b9567f60..9a7f0e5c 100644 --- a/packages/auth-expo/tsconfig.json +++ b/packages/auth-expo/tsconfig.json @@ -1,6 +1,7 @@ { "extends": "expo/tsconfig.base", "compilerOptions": { - "strict": true + "strict": true, + "noUncheckedIndexedAccess": true } } diff --git a/packages/auth-otp/graphql/schema.generated.ts b/packages/auth-otp/graphql/schema.generated.ts index 9625025e..cbba6f00 100644 --- a/packages/auth-otp/graphql/schema.generated.ts +++ b/packages/auth-otp/graphql/schema.generated.ts @@ -16,6 +16,8 @@ export type Scalars = { Boolean: { input: boolean; output: boolean; } Int: { input: number; output: number; } Float: { input: number; output: number; } + DateTime: { input: any; output: any; } + JSON: { input: any; output: any; } JSONObject: { input: any; output: any; } }; @@ -60,8 +62,25 @@ export type LoginRequestSuccessResponse = { export type Mutation = { readonly __typename?: 'Mutation'; + readonly clear: Scalars['Boolean']['output']; + readonly delete: Scalars['Boolean']['output']; readonly loginConfirm: LoginConfirmResponse; readonly loginRequest: LoginRequestResponse; + readonly logout: Scalars['DateTime']['output']; + readonly logoutFromAllDevices: Scalars['DateTime']['output']; + readonly refreshToken: NewTokenResponse; + readonly set: Scalars['Boolean']['output']; +}; + + +export type MutationClearArgs = { + prefix: Scalars['String']['input']; +}; + + +export type MutationDeleteArgs = { + key: Scalars['String']['input']; + prefix: Scalars['String']['input']; }; @@ -75,6 +94,88 @@ export type MutationLoginRequestArgs = { email: Scalars['String']['input']; }; + +export type MutationRefreshTokenArgs = { + bearerToken: Scalars['String']['input']; + refreshToken: Scalars['String']['input']; +}; + + +export type MutationSetArgs = { + expireAfterSeconds?: InputMaybe; + key: Scalars['String']['input']; + prefix: Scalars['String']['input']; + value: Scalars['JSON']['input']; +}; + +export type NewTokenResponse = NewTokenSuccessResponse | RefreshTokenInvalidError; + +export type NewTokenSuccessResponse = { + readonly __typename?: 'NewTokenSuccessResponse'; + readonly bearerToken: Scalars['String']['output']; + readonly refreshToken: Scalars['String']['output']; +}; + +export type Query = { + readonly __typename?: 'Query'; + readonly entries: ReadonlyArray; + readonly get?: Maybe; + readonly has: Scalars['Boolean']['output']; + readonly keys: ReadonlyArray; + readonly publicKey?: Maybe; + readonly readJWT: Scalars['JSONObject']['output']; + readonly size: Scalars['Int']['output']; + readonly validateJWT: Scalars['Boolean']['output']; + readonly values: ReadonlyArray; +}; + + +export type QueryEntriesArgs = { + prefix: Scalars['String']['input']; +}; + + +export type QueryGetArgs = { + key: Scalars['String']['input']; + prefix: Scalars['String']['input']; +}; + + +export type QueryHasArgs = { + key: Scalars['String']['input']; + prefix: Scalars['String']['input']; +}; + + +export type QueryKeysArgs = { + prefix: Scalars['String']['input']; +}; + + +export type QueryReadJwtArgs = { + token: Scalars['String']['input']; +}; + + +export type QuerySizeArgs = { + prefix: Scalars['String']['input']; +}; + + +export type QueryValidateJwtArgs = { + token: Scalars['String']['input']; +}; + + +export type QueryValuesArgs = { + prefix: Scalars['String']['input']; +}; + +export type RefreshTokenInvalidError = { + readonly __typename?: 'RefreshTokenInvalidError'; + readonly message: Scalars['String']['output']; +}; + export type WithIndex = TObject & Record; export type ResolversObject = WithIndex; @@ -147,6 +248,7 @@ export type DirectiveResolverFn> = ResolversObject<{ LoginConfirmResponse: ( CodeNotValidError ) | ( EmailNotValidError ) | ( LoginConfirmSuccessfulResponse ) | ( LoginFailedError ); LoginRequestResponse: ( EmailNotValidError ) | ( LoginRequestSuccessResponse ); + NewTokenResponse: ( NewTokenSuccessResponse ) | ( RefreshTokenInvalidError ); }>; /** Mapping of interface types */ @@ -159,8 +261,11 @@ export type ResolversTypes = ResolversObject<{ AuthOr: AuthOr; Boolean: ResolverTypeWrapper; CodeNotValidError: ResolverTypeWrapper; + DateTime: ResolverTypeWrapper; EmailNotValidError: ResolverTypeWrapper; Error: ResolverTypeWrapper['Error']>; + Int: ResolverTypeWrapper; + JSON: ResolverTypeWrapper; JSONObject: ResolverTypeWrapper; LoginConfirmResponse: ResolverTypeWrapper['LoginConfirmResponse']>; LoginConfirmSuccessfulResponse: ResolverTypeWrapper; @@ -168,6 +273,10 @@ export type ResolversTypes = ResolversObject<{ LoginRequestResponse: ResolverTypeWrapper['LoginRequestResponse']>; LoginRequestSuccessResponse: ResolverTypeWrapper; Mutation: ResolverTypeWrapper<{}>; + NewTokenResponse: ResolverTypeWrapper['NewTokenResponse']>; + NewTokenSuccessResponse: ResolverTypeWrapper; + Query: ResolverTypeWrapper<{}>; + RefreshTokenInvalidError: ResolverTypeWrapper; String: ResolverTypeWrapper; }>; @@ -176,8 +285,11 @@ export type ResolversParentTypes = ResolversObject<{ AuthOr: AuthOr; Boolean: Scalars['Boolean']['output']; CodeNotValidError: CodeNotValidError; + DateTime: Scalars['DateTime']['output']; EmailNotValidError: EmailNotValidError; Error: ResolversInterfaceTypes['Error']; + Int: Scalars['Int']['output']; + JSON: Scalars['JSON']['output']; JSONObject: Scalars['JSONObject']['output']; LoginConfirmResponse: ResolversUnionTypes['LoginConfirmResponse']; LoginConfirmSuccessfulResponse: LoginConfirmSuccessfulResponse; @@ -185,6 +297,10 @@ export type ResolversParentTypes = ResolversObject<{ LoginRequestResponse: ResolversUnionTypes['LoginRequestResponse']; LoginRequestSuccessResponse: LoginRequestSuccessResponse; Mutation: {}; + NewTokenResponse: ResolversUnionTypes['NewTokenResponse']; + NewTokenSuccessResponse: NewTokenSuccessResponse; + Query: {}; + RefreshTokenInvalidError: RefreshTokenInvalidError; String: Scalars['String']['output']; }>; @@ -202,6 +318,10 @@ export type CodeNotValidErrorResolvers; }>; +export interface DateTimeScalarConfig extends GraphQLScalarTypeConfig { + name: 'DateTime'; +} + export type EmailNotValidErrorResolvers = ResolversObject<{ message?: Resolver>; __isTypeOf?: IsTypeOfResolverFn; @@ -212,6 +332,10 @@ export type ErrorResolvers>; }>; +export interface JsonScalarConfig extends GraphQLScalarTypeConfig { + name: 'JSON'; +} + export interface JsonObjectScalarConfig extends GraphQLScalarTypeConfig { name: 'JSONObject'; } @@ -241,14 +365,49 @@ export type LoginRequestSuccessResponseResolvers; export type MutationResolvers = ResolversObject<{ + clear?: Resolver>; + delete?: Resolver>; loginConfirm?: Resolver, RequireFields>; loginRequest?: Resolver, RequireFields>; + logout?: Resolver; + logoutFromAllDevices?: Resolver; + refreshToken?: Resolver, RequireFields>; + set?: Resolver>; +}>; + +export type NewTokenResponseResolvers = ResolversObject<{ + __resolveType: TypeResolveFn<'NewTokenSuccessResponse' | 'RefreshTokenInvalidError', ParentType, ContextType>; +}>; + +export type NewTokenSuccessResponseResolvers = ResolversObject<{ + bearerToken?: Resolver>; + refreshToken?: Resolver>; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type QueryResolvers = ResolversObject<{ + entries?: Resolver, ParentType, ContextType, RequireFields>; + get?: Resolver, ParentType, ContextType, RequireFields>; + has?: Resolver>; + keys?: Resolver, ParentType, ContextType, RequireFields>; + publicKey?: Resolver, ParentType, Zemble.AuthContextWithToken>; + readJWT?: Resolver, RequireFields>; + size?: Resolver>; + validateJWT?: Resolver, RequireFields>; + values?: Resolver, ParentType, ContextType, RequireFields>; +}>; + +export type RefreshTokenInvalidErrorResolvers = ResolversObject<{ + message?: Resolver>; + __isTypeOf?: IsTypeOfResolverFn; }>; export type Resolvers = ResolversObject<{ CodeNotValidError?: CodeNotValidErrorResolvers; + DateTime?: GraphQLScalarType; EmailNotValidError?: EmailNotValidErrorResolvers; Error?: ErrorResolvers; + JSON?: GraphQLScalarType; JSONObject?: GraphQLScalarType; LoginConfirmResponse?: LoginConfirmResponseResolvers; LoginConfirmSuccessfulResponse?: LoginConfirmSuccessfulResponseResolvers; @@ -256,6 +415,10 @@ export type Resolvers = ResolversObject<{ LoginRequestResponse?: LoginRequestResponseResolvers; LoginRequestSuccessResponse?: LoginRequestSuccessResponseResolvers; Mutation?: MutationResolvers; + NewTokenResponse?: NewTokenResponseResolvers; + NewTokenSuccessResponse?: NewTokenSuccessResponseResolvers; + Query?: QueryResolvers; + RefreshTokenInvalidError?: RefreshTokenInvalidErrorResolvers; }>; export type DirectiveResolvers = ResolversObject<{ diff --git a/packages/auth-otp/plugin.ts b/packages/auth-otp/plugin.ts index d2a15d84..ead151b1 100644 --- a/packages/auth-otp/plugin.ts +++ b/packages/auth-otp/plugin.ts @@ -32,7 +32,7 @@ interface OtpAuthConfig extends Zemble.GlobalConfig { */ readonly emailHtml?: string readonly handleAuthRequest?: (email: IEmail, twoFactorCode: string, context: Zemble.GlobalContext) => Promise | void - readonly generateTokenContents: ({ email }: {readonly email: string}) => Promise | Zemble.OtpToken + readonly generateTokenContents: ({ email }: {readonly email: string}) => Promise> | Omit } export interface DefaultOtpToken { diff --git a/packages/auth/graphql/schema.generated.ts b/packages/auth/graphql/schema.generated.ts index 3ee72e08..9fab3cd4 100644 --- a/packages/auth/graphql/schema.generated.ts +++ b/packages/auth/graphql/schema.generated.ts @@ -17,6 +17,7 @@ export type Scalars = { Int: { input: number; output: number; } Float: { input: number; output: number; } DateTime: { input: any; output: any; } + JSON: { input: any; output: any; } JSONObject: { input: any; output: any; } }; @@ -31,9 +32,23 @@ export type Error = { export type Mutation = { readonly __typename?: 'Mutation'; + readonly clear: Scalars['Boolean']['output']; + readonly delete: Scalars['Boolean']['output']; readonly logout: Scalars['DateTime']['output']; readonly logoutFromAllDevices: Scalars['DateTime']['output']; readonly refreshToken: NewTokenResponse; + readonly set: Scalars['Boolean']['output']; +}; + + +export type MutationClearArgs = { + prefix: Scalars['String']['input']; +}; + + +export type MutationDeleteArgs = { + key: Scalars['String']['input']; + prefix: Scalars['String']['input']; }; @@ -42,6 +57,14 @@ export type MutationRefreshTokenArgs = { refreshToken: Scalars['String']['input']; }; + +export type MutationSetArgs = { + expireAfterSeconds?: InputMaybe; + key: Scalars['String']['input']; + prefix: Scalars['String']['input']; + value: Scalars['JSON']['input']; +}; + export type NewTokenResponse = NewTokenSuccessResponse | RefreshTokenInvalidError; export type NewTokenSuccessResponse = { @@ -53,13 +76,19 @@ export type NewTokenSuccessResponse = { export type Query = { readonly __typename?: 'Query'; readonly advancedWithOr: Scalars['String']['output']; + readonly entries: ReadonlyArray; + readonly get?: Maybe; + readonly has: Scalars['Boolean']['output']; readonly includes: Scalars['String']['output']; + readonly keys: ReadonlyArray; readonly privateShit: Scalars['String']['output']; readonly privateShitWithRole: Scalars['String']['output']; readonly publicKey?: Maybe; readonly publicShit: Scalars['String']['output']; readonly readJWT: Scalars['JSONObject']['output']; + readonly size: Scalars['Int']['output']; readonly validateJWT: Scalars['Boolean']['output']; + readonly values: ReadonlyArray; readonly variableReference: Scalars['String']['output']; }; @@ -69,21 +98,53 @@ export type QueryAdvancedWithOrArgs = { }; +export type QueryEntriesArgs = { + prefix: Scalars['String']['input']; +}; + + +export type QueryGetArgs = { + key: Scalars['String']['input']; + prefix: Scalars['String']['input']; +}; + + +export type QueryHasArgs = { + key: Scalars['String']['input']; + prefix: Scalars['String']['input']; +}; + + export type QueryIncludesArgs = { organisationId: Scalars['String']['input']; }; +export type QueryKeysArgs = { + prefix: Scalars['String']['input']; +}; + + export type QueryReadJwtArgs = { token: Scalars['String']['input']; }; +export type QuerySizeArgs = { + prefix: Scalars['String']['input']; +}; + + export type QueryValidateJwtArgs = { token: Scalars['String']['input']; }; +export type QueryValuesArgs = { + prefix: Scalars['String']['input']; +}; + + export type QueryVariableReferenceArgs = { organisationId: Scalars['String']['input']; }; @@ -177,6 +238,8 @@ export type ResolversTypes = ResolversObject<{ Boolean: ResolverTypeWrapper; DateTime: ResolverTypeWrapper; Error: ResolverTypeWrapper['Error']>; + Int: ResolverTypeWrapper; + JSON: ResolverTypeWrapper; JSONObject: ResolverTypeWrapper; Mutation: ResolverTypeWrapper<{}>; NewTokenResponse: ResolverTypeWrapper['NewTokenResponse']>; @@ -192,6 +255,8 @@ export type ResolversParentTypes = ResolversObject<{ Boolean: Scalars['Boolean']['output']; DateTime: Scalars['DateTime']['output']; Error: ResolversInterfaceTypes['Error']; + Int: Scalars['Int']['output']; + JSON: Scalars['JSON']['output']; JSONObject: Scalars['JSONObject']['output']; Mutation: {}; NewTokenResponse: ResolversUnionTypes['NewTokenResponse']; @@ -219,14 +284,21 @@ export type ErrorResolvers>; }>; +export interface JsonScalarConfig extends GraphQLScalarTypeConfig { + name: 'JSON'; +} + export interface JsonObjectScalarConfig extends GraphQLScalarTypeConfig { name: 'JSONObject'; } export type MutationResolvers = ResolversObject<{ + clear?: Resolver>; + delete?: Resolver>; logout?: Resolver; logoutFromAllDevices?: Resolver; refreshToken?: Resolver, RequireFields>; + set?: Resolver>; }>; export type NewTokenResponseResolvers = ResolversObject<{ @@ -241,13 +313,19 @@ export type NewTokenSuccessResponseResolvers = ResolversObject<{ advancedWithOr?: Resolver, RequireFields>; + entries?: Resolver, ParentType, ContextType, RequireFields>; + get?: Resolver, ParentType, ContextType, RequireFields>; + has?: Resolver>; includes?: Resolver, RequireFields>; + keys?: Resolver, ParentType, ContextType, RequireFields>; privateShit?: Resolver; privateShitWithRole?: Resolver>; publicKey?: Resolver, ParentType, Zemble.AuthContextWithToken>; publicShit?: Resolver>; readJWT?: Resolver, RequireFields>; + size?: Resolver>; validateJWT?: Resolver, RequireFields>; + values?: Resolver, ParentType, ContextType, RequireFields>; variableReference?: Resolver, RequireFields>; }>; @@ -259,6 +337,7 @@ export type RefreshTokenInvalidErrorResolvers = ResolversObject<{ DateTime?: GraphQLScalarType; Error?: ErrorResolvers; + JSON?: GraphQLScalarType; JSONObject?: GraphQLScalarType; Mutation?: MutationResolvers; NewTokenResponse?: NewTokenResponseResolvers; diff --git a/packages/auth/plugin.ts b/packages/auth/plugin.ts index a708852b..9e273895 100644 --- a/packages/auth/plugin.ts +++ b/packages/auth/plugin.ts @@ -1,4 +1,4 @@ -import { useExtendContext } from '@envelop/core' +import { useExtendContext, type PromiseOrValue } from '@envelop/core' import { useGenericAuth } from '@envelop/generic-auth' import { Plugin, type TokenContents } from '@zemble/core' import graphqlYoga from '@zemble/graphql' @@ -22,7 +22,7 @@ import type { Context } from 'hono' import type { CookieOptions } from 'hono/utils/cookie' import type { JWTPayload } from 'jose' -const ISSUER = process.env.ISSUER ?? '@zemble/auth' +const ISSUER = process.env['ISSUER'] ?? '@zemble/auth' interface AuthConfig extends Zemble.GlobalConfig { readonly bearerTokenExpiryInSeconds?: number @@ -37,13 +37,13 @@ interface AuthConfig extends Zemble.GlobalConfig { * @param bearerToken * @returns */ - readonly checkIfBearerTokenIsValid?: (bearerToken: TokenContents) => Promise | true | GraphQLError - readonly invalidateToken?: (sub: string, token: string) => Promise | void - readonly invalidateAllTokens?: (sub: string) => Promise | void - readonly checkTokenValidity?: (token: string, decodedToken: TokenContents) => Promise | boolean + readonly checkIfBearerTokenIsValid?: (bearerToken: TokenContents) => PromiseOrValue + readonly invalidateToken?: (sub: string, token: string) => PromiseOrValue + readonly invalidateAllTokens?: (sub: string) => PromiseOrValue + readonly checkTokenValidity?: (token: string, decodedToken: TokenContents) => PromiseOrValue readonly reissueBearerToken?: ( bearerToken: TokenContents - ) => Promise | TokenContents + ) => PromiseOrValue readonly cookies?: { readonly bearerTokenCookieName?: string readonly refreshTokenCookieName?: string @@ -198,11 +198,8 @@ const plugin = new Plugin( decodeToken: config.decodeToken, }) - let bearerToken = token - if (config.cookies.isEnabled && token && refreshToken) { const { bearerToken: newBearerToken, refreshToken: newRefreshToken } = await refreshTokensFromPrevious(token, refreshToken) - bearerToken = newBearerToken setTokenCookies(context, newBearerToken, newRefreshToken) } diff --git a/packages/auth/utils/signJwt.ts b/packages/auth/utils/signJwt.ts index 53ba7fad..dd2f75a5 100644 --- a/packages/auth/utils/signJwt.ts +++ b/packages/auth/utils/signJwt.ts @@ -6,7 +6,7 @@ export async function signJwt({ data, expiresInSeconds, privateKey, sub, }: { readonly data: T, readonly expiresInSeconds?: number, readonly privateKey?: string, readonly sub: string }) { const { PRIVATE_KEY, ISSUER } = plugin.config - const actualPrivateKey = privateKey ?? PRIVATE_KEY ?? process.env.PRIVATE_KEY + const actualPrivateKey = privateKey ?? PRIVATE_KEY ?? process.env['PRIVATE_KEY'] if (!actualPrivateKey) { throw new Error('[@zemble/auth] PRIVATE_KEY is not set, please set it as an environment variable or in the plugin config. You can run `bunx zemble-generate-keys` to add it to your .env') diff --git a/packages/auth/utils/verifyJwt.ts b/packages/auth/utils/verifyJwt.ts index 3f84d326..dabe8090 100644 --- a/packages/auth/utils/verifyJwt.ts +++ b/packages/auth/utils/verifyJwt.ts @@ -5,7 +5,7 @@ import plugin from '../plugin' export async function verifyJwt(token: string, publicKey?: string, opts?: jose.JWTVerifyOptions) { try { - const actualKey = publicKey ?? plugin.config.PUBLIC_KEY ?? process.env.PUBLIC_KEY + const actualKey = publicKey ?? plugin.config.PUBLIC_KEY ?? process.env['PUBLIC_KEY'] if (!actualKey) { throw new GraphQLError('[@zemble/auth] Missing public key, specify it as an environment variable PUBLIC_KEY or in the plugin config to @zemble/auth') diff --git a/packages/bull/ZembleQueueMock.ts b/packages/bull/ZembleQueueMock.ts index 18873a65..ac119860 100644 --- a/packages/bull/ZembleQueueMock.ts +++ b/packages/bull/ZembleQueueMock.ts @@ -16,15 +16,15 @@ interface IZembleQueue { class ZembleQueueMock implements IZembleQueue { constructor( readonly worker: ZembleWorker, - config?: ZembleQueueConfig, + _?: ZembleQueueConfig, ) { this.#worker = worker - this.#config = config + // this.#config = config } readonly #worker: ZembleWorker - readonly #config?: ZembleQueueConfig + // readonly #config?: ZembleQueueConfig #queueInternal: Queue | undefined diff --git a/packages/bull/plugin.ts b/packages/bull/plugin.ts index d0d68727..186444ce 100644 --- a/packages/bull/plugin.ts +++ b/packages/bull/plugin.ts @@ -45,7 +45,7 @@ export interface BullPluginConfig extends Zemble.GlobalConfig { } const defaults = { - redisUrl: process.env.REDIS_URL, + redisUrl: process.env['REDIS_URL'], middleware: { '@zemble/graphql': { disable: true }, '@zemble/bull': { disable: true }, diff --git a/packages/bull/utils/setupQueues.ts b/packages/bull/utils/setupQueues.ts index d80f733e..fb136815 100644 --- a/packages/bull/utils/setupQueues.ts +++ b/packages/bull/utils/setupQueues.ts @@ -53,7 +53,7 @@ const setupQueues = async ( if (hasQueues) { plugin.debug('Initializing queues from ', queuePath) - const redisUrl = config?.redisUrl ?? process.env.REDIS_URL + const redisUrl = config?.redisUrl ?? process.env['REDIS_URL'] if (redisUrl || process.env.NODE_ENV === 'test') { const filenames = readDir(queuePath) diff --git a/packages/cms/graphql/schema.generated.ts b/packages/cms/graphql/schema.generated.ts index 13e40aa5..4216dccb 100644 --- a/packages/cms/graphql/schema.generated.ts +++ b/packages/cms/graphql/schema.generated.ts @@ -18,7 +18,9 @@ export type Scalars = { Boolean: { input: boolean; output: boolean; } Int: { input: number; output: number; } Float: { input: number; output: number; } + /** A date string, such as 2007-12-03, compliant with the `full-date` format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar. */ Date: { input: any; output: any; } + /** A date-time string at UTC, such as 2007-12-03T10:15:30Z, compliant with the `date-time` format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar. */ DateTime: { input: any; output: any; } JSONObject: { input: any; output: any; } }; @@ -174,6 +176,10 @@ export type EntityRelationFieldInput = { readonly name: Scalars['String']['input']; }; +export type Error = { + readonly message: Scalars['String']['output']; +}; + export type Field = { readonly isRequired: Scalars['Boolean']['output']; readonly isRequiredInput: Scalars['Boolean']['output']; @@ -208,6 +214,9 @@ export type Mutation = { readonly createEntity: Entity; readonly deleteAuthor: Scalars['Boolean']['output']; readonly deleteBook: Scalars['Boolean']['output']; + readonly logout: Scalars['DateTime']['output']; + readonly logoutFromAllDevices: Scalars['DateTime']['output']; + readonly refreshToken: NewTokenResponse; readonly removeEntity: Scalars['Boolean']['output']; readonly removeFieldsFromEntity: Entity; readonly renameEntity: Entity; @@ -254,6 +263,12 @@ export type MutationDeleteBookArgs = { }; +export type MutationRefreshTokenArgs = { + bearerToken: Scalars['String']['input']; + refreshToken: Scalars['String']['input']; +}; + + export type MutationRemoveEntityArgs = { namePlural: Scalars['String']['input']; }; @@ -271,6 +286,14 @@ export type MutationRenameEntityArgs = { toNameSingular?: InputMaybe; }; +export type NewTokenResponse = NewTokenSuccessResponse | RefreshTokenInvalidError; + +export type NewTokenSuccessResponse = { + readonly __typename?: 'NewTokenSuccessResponse'; + readonly bearerToken: Scalars['String']['output']; + readonly refreshToken: Scalars['String']['output']; +}; + export type NumberField = Field & { readonly __typename?: 'NumberField'; readonly defaultValue?: Maybe; @@ -303,8 +326,11 @@ export type Query = { readonly getBooksById: ReadonlyArray; readonly getEntityByNamePlural?: Maybe; readonly getEntityByNameSingular?: Maybe; + readonly publicKey?: Maybe; + readonly readJWT: Scalars['JSONObject']['output']; readonly searchAuthors: ReadonlyArray; readonly searchBooks: ReadonlyArray; + readonly validateJWT: Scalars['Boolean']['output']; }; @@ -354,6 +380,11 @@ export type QueryGetEntityByNameSingularArgs = { }; +export type QueryReadJwtArgs = { + token: Scalars['String']['input']; +}; + + export type QuerySearchAuthorsArgs = { caseSensitive?: InputMaybe; diacriticSensitive?: InputMaybe; @@ -369,6 +400,16 @@ export type QuerySearchBooksArgs = { query: Scalars['String']['input']; }; + +export type QueryValidateJwtArgs = { + token: Scalars['String']['input']; +}; + +export type RefreshTokenInvalidError = { + readonly __typename?: 'RefreshTokenInvalidError'; + readonly message: Scalars['String']['output']; +}; + export type StringField = Field & { readonly __typename?: 'StringField'; readonly defaultValue?: Maybe; @@ -461,10 +502,12 @@ export type DirectiveResolverFn> = ResolversObject<{ BookContributorsUnion: ( BookContributorsAuthor ) | ( BookContributorsEditor ); + NewTokenResponse: ( NewTokenSuccessResponse ) | ( RefreshTokenInvalidError ); }>; /** Mapping of interface types */ export type ResolversInterfaceTypes> = ResolversObject<{ + Error: never; Field: ( ArrayField ) | ( BooleanField ) | ( Omit & { entity: RefType['Entity'] } ) | ( IdField ) | ( NumberField ) | ( StringField ); }>; @@ -497,6 +540,7 @@ export type ResolversTypes = ResolversObject<{ EntityPermission: ResolverTypeWrapper; EntityRelationField: ResolverTypeWrapper & { entity: ResolversTypes['Entity'] }>; EntityRelationFieldInput: EntityRelationFieldInput; + Error: ResolverTypeWrapper['Error']>; Field: ResolverTypeWrapper['Field']>; FieldInput: FieldInput; FieldInputWithoutArray: FieldInputWithoutArray; @@ -506,9 +550,12 @@ export type ResolversTypes = ResolversObject<{ Int: ResolverTypeWrapper; JSONObject: ResolverTypeWrapper; Mutation: ResolverTypeWrapper<{}>; + NewTokenResponse: ResolverTypeWrapper['NewTokenResponse']>; + NewTokenSuccessResponse: ResolverTypeWrapper; NumberField: ResolverTypeWrapper; NumberFieldInput: NumberFieldInput; Query: ResolverTypeWrapper<{}>; + RefreshTokenInvalidError: ResolverTypeWrapper; String: ResolverTypeWrapper; StringField: ResolverTypeWrapper; StringFieldInput: StringFieldInput; @@ -543,6 +590,7 @@ export type ResolversParentTypes = ResolversObject<{ EntityPermission: EntityPermission; EntityRelationField: Omit & { entity: ResolversParentTypes['Entity'] }; EntityRelationFieldInput: EntityRelationFieldInput; + Error: ResolversInterfaceTypes['Error']; Field: ResolversInterfaceTypes['Field']; FieldInput: FieldInput; FieldInputWithoutArray: FieldInputWithoutArray; @@ -552,9 +600,12 @@ export type ResolversParentTypes = ResolversObject<{ Int: Scalars['Int']['output']; JSONObject: Scalars['JSONObject']['output']; Mutation: {}; + NewTokenResponse: ResolversUnionTypes['NewTokenResponse']; + NewTokenSuccessResponse: NewTokenSuccessResponse; NumberField: NumberField; NumberFieldInput: NumberFieldInput; Query: {}; + RefreshTokenInvalidError: RefreshTokenInvalidError; String: Scalars['String']['output']; StringField: StringField; StringFieldInput: StringFieldInput; @@ -670,6 +721,11 @@ export type EntityRelationFieldResolvers; }>; +export type ErrorResolvers = ResolversObject<{ + __resolveType: TypeResolveFn; + message?: Resolver>; +}>; + export type FieldResolvers = ResolversObject<{ __resolveType: TypeResolveFn<'ArrayField' | 'BooleanField' | 'EntityRelationField' | 'IDField' | 'NumberField' | 'StringField', ParentType, ContextType>; isRequired?: Resolver; @@ -695,11 +751,24 @@ export type MutationResolvers, RequireFields>; deleteAuthor?: Resolver>; deleteBook?: Resolver>; + logout?: Resolver; + logoutFromAllDevices?: Resolver; + refreshToken?: Resolver, RequireFields>; removeEntity?: Resolver, RequireFields>; removeFieldsFromEntity?: Resolver, RequireFields>; renameEntity?: Resolver, RequireFields>; }>; +export type NewTokenResponseResolvers = ResolversObject<{ + __resolveType: TypeResolveFn<'NewTokenSuccessResponse' | 'RefreshTokenInvalidError', ParentType, ContextType>; +}>; + +export type NewTokenSuccessResponseResolvers = ResolversObject<{ + bearerToken?: Resolver>; + refreshToken?: Resolver>; + __isTypeOf?: IsTypeOfResolverFn; +}>; + export type NumberFieldResolvers = ResolversObject<{ defaultValue?: Resolver, ParentType, ContextType>; isRequired?: Resolver; @@ -722,8 +791,16 @@ export type QueryResolvers, ParentType, ContextType, RequireFields>; getEntityByNamePlural?: Resolver, ParentType, Zemble.AuthContextWithToken, RequireFields>; getEntityByNameSingular?: Resolver, ParentType, Zemble.AuthContextWithToken, RequireFields>; + publicKey?: Resolver, ParentType, Zemble.AuthContextWithToken>; + readJWT?: Resolver, RequireFields>; searchAuthors?: Resolver, ParentType, ContextType, RequireFields>; searchBooks?: Resolver, ParentType, ContextType, RequireFields>; + validateJWT?: Resolver, RequireFields>; +}>; + +export type RefreshTokenInvalidErrorResolvers = ResolversObject<{ + message?: Resolver>; + __isTypeOf?: IsTypeOfResolverFn; }>; export type StringFieldResolvers = ResolversObject<{ @@ -751,12 +828,16 @@ export type Resolvers = ResolversObject<{ Entity?: EntityResolvers; EntityPermission?: EntityPermissionResolvers; EntityRelationField?: EntityRelationFieldResolvers; + Error?: ErrorResolvers; Field?: FieldResolvers; IDField?: IdFieldResolvers; JSONObject?: GraphQLScalarType; Mutation?: MutationResolvers; + NewTokenResponse?: NewTokenResponseResolvers; + NewTokenSuccessResponse?: NewTokenSuccessResponseResolvers; NumberField?: NumberFieldResolvers; Query?: QueryResolvers; + RefreshTokenInvalidError?: RefreshTokenInvalidErrorResolvers; StringField?: StringFieldResolvers; }>; @@ -772,7 +853,9 @@ export type Scalars = { Boolean: { input: boolean; output: boolean; } Int: { input: number; output: number; } Float: { input: number; output: number; } + /** A date string, such as 2007-12-03, compliant with the `full-date` format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar. */ Date: { input: any; output: any; } + /** A date-time string at UTC, such as 2007-12-03T10:15:30Z, compliant with the `date-time` format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar. */ DateTime: { input: any; output: any; } JSONObject: { input: any; output: any; } }; @@ -928,6 +1011,10 @@ export type EntityRelationFieldInput = { readonly name: Scalars['String']['input']; }; +export type Error = { + readonly message: Scalars['String']['output']; +}; + export type Field = { readonly isRequired: Scalars['Boolean']['output']; readonly isRequiredInput: Scalars['Boolean']['output']; @@ -962,6 +1049,9 @@ export type Mutation = { readonly createEntity: Entity; readonly deleteAuthor: Scalars['Boolean']['output']; readonly deleteBook: Scalars['Boolean']['output']; + readonly logout: Scalars['DateTime']['output']; + readonly logoutFromAllDevices: Scalars['DateTime']['output']; + readonly refreshToken: NewTokenResponse; readonly removeEntity: Scalars['Boolean']['output']; readonly removeFieldsFromEntity: Entity; readonly renameEntity: Entity; @@ -1008,6 +1098,12 @@ export type MutationDeleteBookArgs = { }; +export type MutationRefreshTokenArgs = { + bearerToken: Scalars['String']['input']; + refreshToken: Scalars['String']['input']; +}; + + export type MutationRemoveEntityArgs = { namePlural: Scalars['String']['input']; }; @@ -1025,6 +1121,14 @@ export type MutationRenameEntityArgs = { toNameSingular?: InputMaybe; }; +export type NewTokenResponse = NewTokenSuccessResponse | RefreshTokenInvalidError; + +export type NewTokenSuccessResponse = { + readonly __typename?: 'NewTokenSuccessResponse'; + readonly bearerToken: Scalars['String']['output']; + readonly refreshToken: Scalars['String']['output']; +}; + export type NumberField = Field & { readonly __typename?: 'NumberField'; readonly defaultValue?: Maybe; @@ -1057,8 +1161,11 @@ export type Query = { readonly getBooksById: ReadonlyArray; readonly getEntityByNamePlural?: Maybe; readonly getEntityByNameSingular?: Maybe; + readonly publicKey?: Maybe; + readonly readJWT: Scalars['JSONObject']['output']; readonly searchAuthors: ReadonlyArray; readonly searchBooks: ReadonlyArray; + readonly validateJWT: Scalars['Boolean']['output']; }; @@ -1108,6 +1215,11 @@ export type QueryGetEntityByNameSingularArgs = { }; +export type QueryReadJwtArgs = { + token: Scalars['String']['input']; +}; + + export type QuerySearchAuthorsArgs = { caseSensitive?: InputMaybe; diacriticSensitive?: InputMaybe; @@ -1123,6 +1235,16 @@ export type QuerySearchBooksArgs = { query: Scalars['String']['input']; }; + +export type QueryValidateJwtArgs = { + token: Scalars['String']['input']; +}; + +export type RefreshTokenInvalidError = { + readonly __typename?: 'RefreshTokenInvalidError'; + readonly message: Scalars['String']['output']; +}; + export type StringField = Field & { readonly __typename?: 'StringField'; readonly defaultValue?: Maybe; @@ -1215,10 +1337,12 @@ export type DirectiveResolverFn> = ResolversObject<{ BookContributorsUnion: ( BookContributorsAuthor ) | ( BookContributorsEditor ); + NewTokenResponse: ( NewTokenSuccessResponse ) | ( RefreshTokenInvalidError ); }>; /** Mapping of interface types */ export type ResolversInterfaceTypes> = ResolversObject<{ + Error: never; Field: ( ArrayField ) | ( BooleanField ) | ( Omit & { entity: RefType['Entity'] } ) | ( IdField ) | ( NumberField ) | ( StringField ); }>; @@ -1251,6 +1375,7 @@ export type ResolversTypes = ResolversObject<{ EntityPermission: ResolverTypeWrapper; EntityRelationField: ResolverTypeWrapper & { entity: ResolversTypes['Entity'] }>; EntityRelationFieldInput: EntityRelationFieldInput; + Error: ResolverTypeWrapper['Error']>; Field: ResolverTypeWrapper['Field']>; FieldInput: FieldInput; FieldInputWithoutArray: FieldInputWithoutArray; @@ -1260,9 +1385,12 @@ export type ResolversTypes = ResolversObject<{ Int: ResolverTypeWrapper; JSONObject: ResolverTypeWrapper; Mutation: ResolverTypeWrapper<{}>; + NewTokenResponse: ResolverTypeWrapper['NewTokenResponse']>; + NewTokenSuccessResponse: ResolverTypeWrapper; NumberField: ResolverTypeWrapper; NumberFieldInput: NumberFieldInput; Query: ResolverTypeWrapper<{}>; + RefreshTokenInvalidError: ResolverTypeWrapper; String: ResolverTypeWrapper; StringField: ResolverTypeWrapper; StringFieldInput: StringFieldInput; @@ -1297,6 +1425,7 @@ export type ResolversParentTypes = ResolversObject<{ EntityPermission: EntityPermission; EntityRelationField: Omit & { entity: ResolversParentTypes['Entity'] }; EntityRelationFieldInput: EntityRelationFieldInput; + Error: ResolversInterfaceTypes['Error']; Field: ResolversInterfaceTypes['Field']; FieldInput: FieldInput; FieldInputWithoutArray: FieldInputWithoutArray; @@ -1306,9 +1435,12 @@ export type ResolversParentTypes = ResolversObject<{ Int: Scalars['Int']['output']; JSONObject: Scalars['JSONObject']['output']; Mutation: {}; + NewTokenResponse: ResolversUnionTypes['NewTokenResponse']; + NewTokenSuccessResponse: NewTokenSuccessResponse; NumberField: NumberField; NumberFieldInput: NumberFieldInput; Query: {}; + RefreshTokenInvalidError: RefreshTokenInvalidError; String: Scalars['String']['output']; StringField: StringField; StringFieldInput: StringFieldInput; @@ -1424,6 +1556,11 @@ export type EntityRelationFieldResolvers; }>; +export type ErrorResolvers = ResolversObject<{ + __resolveType: TypeResolveFn; + message?: Resolver>; +}>; + export type FieldResolvers = ResolversObject<{ __resolveType: TypeResolveFn<'ArrayField' | 'BooleanField' | 'EntityRelationField' | 'IDField' | 'NumberField' | 'StringField', ParentType, ContextType>; isRequired?: Resolver; @@ -1449,11 +1586,24 @@ export type MutationResolvers, RequireFields>; deleteAuthor?: Resolver>; deleteBook?: Resolver>; + logout?: Resolver; + logoutFromAllDevices?: Resolver; + refreshToken?: Resolver, RequireFields>; removeEntity?: Resolver, RequireFields>; removeFieldsFromEntity?: Resolver, RequireFields>; renameEntity?: Resolver, RequireFields>; }>; +export type NewTokenResponseResolvers = ResolversObject<{ + __resolveType: TypeResolveFn<'NewTokenSuccessResponse' | 'RefreshTokenInvalidError', ParentType, ContextType>; +}>; + +export type NewTokenSuccessResponseResolvers = ResolversObject<{ + bearerToken?: Resolver>; + refreshToken?: Resolver>; + __isTypeOf?: IsTypeOfResolverFn; +}>; + export type NumberFieldResolvers = ResolversObject<{ defaultValue?: Resolver, ParentType, ContextType>; isRequired?: Resolver; @@ -1476,8 +1626,16 @@ export type QueryResolvers, ParentType, ContextType, RequireFields>; getEntityByNamePlural?: Resolver, ParentType, Zemble.AuthContextWithToken, RequireFields>; getEntityByNameSingular?: Resolver, ParentType, Zemble.AuthContextWithToken, RequireFields>; + publicKey?: Resolver, ParentType, Zemble.AuthContextWithToken>; + readJWT?: Resolver, RequireFields>; searchAuthors?: Resolver, ParentType, ContextType, RequireFields>; searchBooks?: Resolver, ParentType, ContextType, RequireFields>; + validateJWT?: Resolver, RequireFields>; +}>; + +export type RefreshTokenInvalidErrorResolvers = ResolversObject<{ + message?: Resolver>; + __isTypeOf?: IsTypeOfResolverFn; }>; export type StringFieldResolvers = ResolversObject<{ @@ -1505,12 +1663,16 @@ export type Resolvers = ResolversObject<{ Entity?: EntityResolvers; EntityPermission?: EntityPermissionResolvers; EntityRelationField?: EntityRelationFieldResolvers; + Error?: ErrorResolvers; Field?: FieldResolvers; IDField?: IdFieldResolvers; JSONObject?: GraphQLScalarType; Mutation?: MutationResolvers; + NewTokenResponse?: NewTokenResponseResolvers; + NewTokenSuccessResponse?: NewTokenSuccessResponseResolvers; NumberField?: NumberFieldResolvers; Query?: QueryResolvers; + RefreshTokenInvalidError?: RefreshTokenInvalidErrorResolvers; StringField?: StringFieldResolvers; }>; diff --git a/packages/cms/utils/fs.ts b/packages/cms/utils/fs.ts index 1c8135e1..fabe69b0 100644 --- a/packages/cms/utils/fs.ts +++ b/packages/cms/utils/fs.ts @@ -1,4 +1,3 @@ -import memfs from 'memfs' import * as fs from 'node:fs' import type { EntitySchemaType } from '../types' diff --git a/packages/create-zemble-app/cli.test.ts b/packages/create-zemble-app/cli.test.ts index b37f43f8..2dc44334 100644 --- a/packages/create-zemble-app/cli.test.ts +++ b/packages/create-zemble-app/cli.test.ts @@ -26,7 +26,7 @@ const testTemplate = (template: string) => { if (createRes.error) { zembleContext.logger.error(createRes.error.message) } - if (createRes.stdout && process.env.DEBUG) { + if (createRes.stdout && process.env['DEBUG']) { zembleContext.logger.info(createRes.stdout.toString('utf-8')) } expect(createRes.status).toBe(0) @@ -35,7 +35,7 @@ const testTemplate = (template: string) => { if (testRes.error) { zembleContext.logger.error(testRes.error.message) } - if (testRes.stdout && process.env.DEBUG) { + if (testRes.stdout && process.env['DEBUG']) { zembleContext.logger.info(testRes.stdout.toString('utf-8')) } expect(testRes.status).toBe(0) diff --git a/packages/create-zemble-app/tsconfig.json b/packages/create-zemble-app/tsconfig.json index 4082f16a..e3a3ad6e 100644 --- a/packages/create-zemble-app/tsconfig.json +++ b/packages/create-zemble-app/tsconfig.json @@ -1,3 +1,6 @@ { - "extends": "../../tsconfig.json" + "extends": "../../tsconfig.json", + "compilerOptions": { + "strict": true + } } diff --git a/packages/create-zemble-plugin/cli.test.ts b/packages/create-zemble-plugin/cli.test.ts index 764c78f6..9a9d13a8 100644 --- a/packages/create-zemble-plugin/cli.test.ts +++ b/packages/create-zemble-plugin/cli.test.ts @@ -25,7 +25,7 @@ const testTemplate = (template: string) => { if (createRes.error) { console.error(createRes.error.message) } - if (createRes.stdout && process.env.DEBUG) { + if (createRes.stdout && process.env['DEBUG']) { console.log(createRes.stdout.toString('utf-8')) } expect(createRes.status).toBe(0) @@ -34,7 +34,7 @@ const testTemplate = (template: string) => { if (testRes.error) { console.error(testRes.error.message) } - if (testRes.stdout && process.env.DEBUG) { + if (testRes.stdout && process.env['DEBUG']) { console.log(testRes.stdout.toString('utf-8')) } expect(testRes.status).toBe(0) diff --git a/packages/email-resend/plugin.ts b/packages/email-resend/plugin.ts index b4e7cc15..6b63ecc6 100644 --- a/packages/email-resend/plugin.ts +++ b/packages/email-resend/plugin.ts @@ -34,7 +34,7 @@ declare global { } const defaultConfig = { - RESEND_API_KEY: process.env.RESEND_API_KEY, + RESEND_API_KEY: process.env['RESEND_API_KEY'], disable: process.env.NODE_ENV === 'test', middleware: { '@zemble/graphql': { @@ -91,7 +91,7 @@ const plugin = new Plugin(import.meta.d ], defaultConfig, additionalConfigWhenRunningLocally: { - RESEND_API_KEY: process.env.RESEND_API_KEY, + RESEND_API_KEY: process.env['RESEND_API_KEY'], middleware: { '@zemble/graphql': { disable: false, diff --git a/packages/email-sendgrid/plugin.ts b/packages/email-sendgrid/plugin.ts index ee80e49f..49940103 100644 --- a/packages/email-sendgrid/plugin.ts +++ b/packages/email-sendgrid/plugin.ts @@ -31,7 +31,7 @@ declare global { } const defaultConfig = { - SENDGRID_API_KEY: process.env.SENDGRID_API_KEY, + SENDGRID_API_KEY: process.env['SENDGRID_API_KEY'], disable: process.env.NODE_ENV === 'test', middleware: { '@zemble/graphql': { @@ -91,7 +91,7 @@ const plugin = new Plugin(import.meta ], defaultConfig, additionalConfigWhenRunningLocally: { - SENDGRID_API_KEY: process.env.SENDGRID_API_KEY, + SENDGRID_API_KEY: process.env['SENDGRID_API_KEY'], middleware: { '@zemble/graphql': { disable: false, diff --git a/packages/eslint/package.json b/packages/eslint/package.json index 39508d53..400ab4b7 100644 --- a/packages/eslint/package.json +++ b/packages/eslint/package.json @@ -36,19 +36,19 @@ ], "peerDependencies": { "@graphql-eslint/eslint-plugin": ">=3", - "@typescript-eslint/eslint-plugin": "^7.7.1", - "@typescript-eslint/parser": "^7.7.1", + "@typescript-eslint/eslint-plugin": ">=7.7.1", + "@typescript-eslint/parser": ">=7.7.1", "eslint": "8", "eslint-config-airbnb-base": ">=15", - "eslint-import-resolver-typescript": "^3.6.1", - "eslint-plugin-comment-length": "^1.7.3", - "eslint-plugin-functional": "^6.4.0", - "eslint-plugin-import": "^2.29.1", - "eslint-plugin-jsonc": "^2.15.0", - "eslint-plugin-unicorn": "^52.0.0", - "eslint-plugin-yml": "^1.14.0", - "graphql": "^16.8.1", - "typescript": "^5.4.2" + "eslint-import-resolver-typescript": ">=3.6.1", + "eslint-plugin-comment-length": ">=1.7.3", + "eslint-plugin-functional": ">=6.4.0", + "eslint-plugin-import": ">=2.29.1", + "eslint-plugin-jsonc": ">=2.15.0", + "eslint-plugin-unicorn": ">=53.0.0", + "eslint-plugin-yml": ">=1.14.0", + "graphql": ">=16.8.1", + "typescript": ">=5.4.2" }, "optionalPeerDependencies": { "@babel/core": ">=7", @@ -63,7 +63,6 @@ "devDependencies": { "@babel/core": "^7.24.4", "@graphql-eslint/eslint-plugin": "^3.20.1", - "@types/node": "^20.12.7", "@typescript-eslint/eslint-plugin": "^7.7.1", "@typescript-eslint/parser": "^7.7.1", "eslint": "8", diff --git a/packages/eslint/tsconfig.base.json b/packages/eslint/tsconfig.base.json index b5ece243..fd88614c 100644 --- a/packages/eslint/tsconfig.base.json +++ b/packages/eslint/tsconfig.base.json @@ -3,13 +3,10 @@ "compilerOptions": { "noUncheckedIndexedAccess": true, "strictNullChecks": true, - "noImplicitAny": false, "verbatimModuleSyntax": true, "allowSyntheticDefaultImports": true, - "noPropertyAccessFromIndexSignature": false, - "noUnusedLocals": false, "resolveJsonModule": true, - "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature":true, "types": [ "bun-types" ] }, "exclude": [ diff --git a/packages/eslint/typescript.js b/packages/eslint/typescript.js index b7597d98..fe84d2dc 100644 --- a/packages/eslint/typescript.js +++ b/packages/eslint/typescript.js @@ -15,6 +15,7 @@ module.exports = { '@typescript-eslint/explicit-function-return-type': 0, '@typescript-eslint/explicit-module-boundary-types': 0, 'indent': 0, + 'dot-notation': 0, '@typescript-eslint/indent': [ 'error', 2, { VariableDeclarator: { const: 3, let: 2, var: 2 }, diff --git a/packages/firebase-auth/plugin.ts b/packages/firebase-auth/plugin.ts index 8ac94981..bc78fb32 100644 --- a/packages/firebase-auth/plugin.ts +++ b/packages/firebase-auth/plugin.ts @@ -36,8 +36,8 @@ declare global { } const defaultConfig = { - FIREBASE_ADMIN_SERVICE_ACCOUNT: process.env.FIREBASE_ADMIN_SERVICE_ACCOUNT ? parseEnvJSON('FIREBASE_ADMIN_SERVICE_ACCOUNT', undefined) : undefined, - FIREBASE_CLIENT_CONFIG: process.env.FIREBASE_CLIENT_CONFIG ? parseEnvJSON('FIREBASE_CLIENT_CONFIG', undefined) : undefined, + FIREBASE_ADMIN_SERVICE_ACCOUNT: process.env['FIREBASE_ADMIN_SERVICE_ACCOUNT'] ? parseEnvJSON('FIREBASE_ADMIN_SERVICE_ACCOUNT', undefined) : undefined, + FIREBASE_CLIENT_CONFIG: process.env['FIREBASE_CLIENT_CONFIG'] ? parseEnvJSON('FIREBASE_CLIENT_CONFIG', undefined) : undefined, generateTokenContents: (args) => ({ type: '@zemble/auth-firebase', ...args }), } satisfies Partial diff --git a/packages/graphql/codegen.ts b/packages/graphql/codegen.ts index 688f40ba..bcee881f 100644 --- a/packages/graphql/codegen.ts +++ b/packages/graphql/codegen.ts @@ -99,7 +99,7 @@ export function createServerConfig> => ({ onPluginInit: ({ addPlugin }) => { if (plugin.config.enableServerTiming) { - const opCounts = {} + const opCounts: Record = {} addPlugin( useOnResolve>(({ info, context }) => { const c = context.honoContext @@ -17,7 +17,7 @@ export const useServerTiming = (): EnvelopPlugin {}).finally(async () => { +void mkdir(join(process.cwd(), targetFolder)).catch((_) => {}).finally(async () => { const args = process.argv.slice(2) const migrationName = args[0] diff --git a/packages/migrations/plugin.ts b/packages/migrations/plugin.ts index b9aeaf0b..1c9f46b4 100644 --- a/packages/migrations/plugin.ts +++ b/packages/migrations/plugin.ts @@ -1,5 +1,4 @@ import { Plugin } from '@zemble/core' -import createLogger from '@zemble/core/createLogger' import { readdir } from 'node:fs/promises' import { join } from 'node:path' @@ -98,7 +97,7 @@ let downMigrationsRemaining = [] as readonly MigrationToProcess[] export const migrateDown = async ( opts?: { readonly migrateDownCount?: number, readonly logger?: IStandardLogger }, ) => { - const { migrateDownCount = 1, logger = defaultLogger() } = opts ?? {} + const { migrateDownCount = 1 } = opts ?? {} plugin.debug('migrateDown: %d migrations to process', upMigrationsRemaining.length) @@ -122,11 +121,11 @@ export const migrateDown = async ( }, Promise.resolve()) } -let defaultLoggerInternal: IStandardLogger | undefined -const defaultLogger = () => { - defaultLoggerInternal ??= createLogger({ pluginName: '@zemble/migrations' }) - return defaultLoggerInternal -} +// let defaultLoggerInternal: IStandardLogger | undefined +// const defaultLogger = () => { +// defaultLoggerInternal ??= createLogger({ pluginName: '@zemble/migrations' }) +// return defaultLoggerInternal +// } export const migrateUp = async (opts?: { readonly logger?: IStandardLogger, readonly migrateUpCount?: number }) => { const { migrateUpCount = Infinity } = opts ?? {} diff --git a/packages/mongodb/plugin.ts b/packages/mongodb/plugin.ts index 5d8ead14..98035b15 100644 --- a/packages/mongodb/plugin.ts +++ b/packages/mongodb/plugin.ts @@ -37,7 +37,7 @@ declare global { } const defaultConfig = { - url: process.env.MONGO_URL ?? 'mongodb://localhost:27017', + url: process.env['MONGO_URL'] ?? 'mongodb://localhost:27017', } satisfies MongodbClientConfig const regexToHidePassword = /(?<=mongodb\+srv:\/\/[^:]+:)[^@]+/ @@ -46,7 +46,7 @@ export default new Plugin( import.meta.dir, { middleware: async ({ - app, config, logger, self, + app, config, logger, }) => { logger.info('Connecting to MongoDB', config.url.replace(regexToHidePassword, '***')) diff --git a/packages/mongodb/routes/status/db.ts b/packages/mongodb/routes/status/db.ts index 78f7c005..013f1b92 100644 --- a/packages/mongodb/routes/status/db.ts +++ b/packages/mongodb/routes/status/db.ts @@ -3,7 +3,7 @@ import plugin from '../../plugin' export default async (ctx: Zemble.RouteContext) => { try { const pong = await plugin.providers.mongodb?.db.command({ ping: 1 }) - if (pong?.ok === 1) { + if (pong?.['ok'] === 1) { return ctx.json({ status: 'ok' }, 200) } return ctx.json({ status: 'error', message: 'ping failed' }, 503) diff --git a/packages/pino/plugin.ts b/packages/pino/plugin.ts index 94bdbb88..76f647e7 100644 --- a/packages/pino/plugin.ts +++ b/packages/pino/plugin.ts @@ -4,6 +4,7 @@ import { Plugin } from '@zemble/core' import mergeDeep from '@zemble/core/utils/mergeDeep' import pino from 'pino' // eslint-disable-next-line import/no-extraneous-dependencies +// @ts-expect-error pino-debug does not have types import pinoDebug from 'pino-debug' import type pinopretty from 'pino-pretty' @@ -27,7 +28,7 @@ interface LoggerConfig extends Zemble.GlobalConfig { } export const defaultProdConfig = { - level: process.env.LOG_LEVEL ?? 'info', + level: process.env['LOG_LEVEL'] ?? 'info', } satisfies pino.LoggerOptions export const defaultDevConfig = { @@ -40,7 +41,7 @@ export const defaultDevConfig = { targets: [ { target: 'pino-pretty', - level: process.env.LOG_LEVEL ?? 'debug', + level: process.env['LOG_LEVEL'] ?? 'debug', options: { translateTime: 'HH:MM:ss', messageFormat: '{if pluginName}[{pluginName}@{pluginVersion}]{end} {if middlewarePluginName}({middlewarePluginName}@{middlewarePluginVersion}){end} {msg}', @@ -57,7 +58,7 @@ export const defaultTestConfig = { ...defaultDevConfig.transport, targets: defaultDevConfig.transport.targets.map((target) => ({ ...target, - level: process.env.LOG_LEVEL ?? 'warn', + level: process.env['LOG_LEVEL'] ?? 'warn', options: { ...target.options, colorize: false, diff --git a/packages/react/contexts/Urql.tsx b/packages/react/contexts/Urql.tsx index f19b6938..10a963f7 100644 --- a/packages/react/contexts/Urql.tsx +++ b/packages/react/contexts/Urql.tsx @@ -1,4 +1,4 @@ -import React, { +import { useMemo, createContext, useContext, useState, } from 'react' import { Provider } from 'urql' diff --git a/packages/react/example/Component.tsx b/packages/react/example/Component.tsx index ada6f2bd..cc7a4df6 100644 --- a/packages/react/example/Component.tsx +++ b/packages/react/example/Component.tsx @@ -1,5 +1,3 @@ -import React from 'react' - import { createThemedStylesHook } from '..' import { Row, Text } from '../primitives' diff --git a/packages/react/example/Hooks.tsx b/packages/react/example/Hooks.tsx index 06bf518a..e571cb47 100644 --- a/packages/react/example/Hooks.tsx +++ b/packages/react/example/Hooks.tsx @@ -1,5 +1,3 @@ -import React from 'react' - import useEvent from '../hooks/useEvent' export const useOnClick = () => { diff --git a/packages/react/stories/Alerts.stories.tsx b/packages/react/stories/Alerts.stories.tsx index 02e7bdd6..7127b3c3 100644 --- a/packages/react/stories/Alerts.stories.tsx +++ b/packages/react/stories/Alerts.stories.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react' +import { useState } from 'react' import { Button, Text, View } from 'react-native' import { useAlert, useConfirm } from '../hooks' diff --git a/packages/supabase/clients/createSupabaseServerClient.ts b/packages/supabase/clients/createSupabaseServerClient.ts index 906b4cf2..fde73d2e 100644 --- a/packages/supabase/clients/createSupabaseServerClient.ts +++ b/packages/supabase/clients/createSupabaseServerClient.ts @@ -6,7 +6,7 @@ import plugin from '../plugin' import type { CookieOptions } from '@supabase/ssr' import type { SupabaseClient } from '@supabase/supabase-js' -const testSupabaseUrl = process.env.CI ? 'http://localhost:54321' : 'http://127.0.0.1:54321' +const testSupabaseUrl = process.env['CI'] ? 'http://localhost:54321' : 'http://127.0.0.1:54321' const testSupabaseAnonKey = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im5wdHFtbWF4bXluYWhzZ2Z1dmhuIiwicm9sZSI6ImFub24iLCJpYXQiOjE2OTkwMzc3MzEsImV4cCI6MjAxNDYxMzczMX0.TEyzdP13ogqp6NGhSQYwtzOkwmuJDMkXUjU3gUVmN1c' const testConfig = { supabaseUrl: testSupabaseUrl, @@ -22,13 +22,13 @@ export const createSupabaseServerClient = (): SupabaseClient supabaseAnonKey as string, { cookies: { - get(name: string) { + get(_: string) { return undefined }, - set(name: string, value: string, options: CookieOptions) { + set(_: string, __: string, ___: CookieOptions) { // storage[name] = value }, - remove(name: string, options: CookieOptions) { + remove(_: string, __: CookieOptions) { // delete storage[name] }, }, diff --git a/packages/supabase/plugin.ts b/packages/supabase/plugin.ts index ce8aeee6..712b84f3 100644 --- a/packages/supabase/plugin.ts +++ b/packages/supabase/plugin.ts @@ -15,8 +15,8 @@ interface SupabaseConfig extends Zemble.GlobalConfig { } const defaultConfig = { - supabaseUrl: process.env.SUPABASE_URL, - supabaseAnonKey: process.env.SUPABASE_ANON_KEY, + supabaseUrl: process.env['SUPABASE_URL'], + supabaseAnonKey: process.env['SUPABASE_ANON_KEY'], } declare global { diff --git a/packages/supabase/test-utils.ts b/packages/supabase/test-utils.ts index 0cbab9d0..db7482c4 100644 --- a/packages/supabase/test-utils.ts +++ b/packages/supabase/test-utils.ts @@ -4,7 +4,7 @@ import { createClient } from '@supabase/supabase-js' import plugin from './plugin' -const testSupabaseUrl = process.env.CI ? 'http://localhost:54321' : 'http://127.0.0.1:54321' +const testSupabaseUrl = process.env['CI'] ? 'http://localhost:54321' : 'http://127.0.0.1:54321' const testSupabaseAnonKey = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im5wdHFtbWF4bXluYWhzZ2Z1dmhuIiwicm9sZSI6ImFub24iLCJpYXQiOjE2OTkwMzc3MzEsImV4cCI6MjAxNDYxMzczMX0.TEyzdP13ogqp6NGhSQYwtzOkwmuJDMkXUjU3gUVmN1c' const testConfig = { supabaseUrl: testSupabaseUrl, diff --git a/packages/todo-ui/config.ts b/packages/todo-ui/config.ts index f058955a..040a90b8 100644 --- a/packages/todo-ui/config.ts +++ b/packages/todo-ui/config.ts @@ -1 +1 @@ -export const GRAPHQL_ENDPOINT = process.env.EXPO_PUBLIC_GRAPHQL_ENDPOINT ?? 'http://robmax.local:3000/graphql' +export const GRAPHQL_ENDPOINT = process.env['EXPO_PUBLIC_GRAPHQL_ENDPOINT'] ?? 'http://robmax.local:3000/graphql' diff --git a/packages/todo-ui/tsconfig.json b/packages/todo-ui/tsconfig.json index 7c97a57d..e67326e5 100644 --- a/packages/todo-ui/tsconfig.json +++ b/packages/todo-ui/tsconfig.json @@ -3,6 +3,7 @@ "compilerOptions": { "strict": true, "baseUrl": "../..", + "noPropertyAccessFromIndexSignature": true, "paths": { "@zemble/auth-anonymous-expo/*": [ "packages/simple-anonymous-auth-ui/*" ], "@zemble/auth-anonymous-expo": [ "packages/simple-anonymous-auth-ui" ] diff --git a/packages/todo/graphql/Mutation/createTodo.ts b/packages/todo/graphql/Mutation/createTodo.ts index fda696dc..4353b774 100644 --- a/packages/todo/graphql/Mutation/createTodo.ts +++ b/packages/todo/graphql/Mutation/createTodo.ts @@ -4,9 +4,9 @@ import type { MutationResolvers } from '../schema.generated' export const createTodo: NonNullable = async (_, { title }, { pubsub, decodedToken }) => { const id = Math.random().toString(36).substring(7) - const { userId } = decodedToken! + const { sub } = decodedToken! const todo = { title, id, completed: false } - await plugin.providers.kv(userId).set(id, { title, id, completed: false }) + await plugin.providers.kv(sub).set(id, { title, id, completed: false }) pubsub.publish('todoCreated', todo) return todo diff --git a/packages/todo/graphql/Mutation/updateTodoStatus.ts b/packages/todo/graphql/Mutation/updateTodoStatus.ts index d630561b..3b3f6813 100644 --- a/packages/todo/graphql/Mutation/updateTodoStatus.ts +++ b/packages/todo/graphql/Mutation/updateTodoStatus.ts @@ -5,14 +5,14 @@ import type { MutationResolvers, Todo } from '../schema.generated' export const updateTodoStatus: NonNullable = async (_, { id, completed, }, { pubsub, decodedToken }) => { - const { userId } = decodedToken! - const todoIdWithUser = `${userId}_${id}` - const previous = await plugin.providers.kv(userId).get(todoIdWithUser) + const { sub } = decodedToken! + const todoIdWithUser = `${sub}_${id}` + const previous = await plugin.providers.kv(sub).get(todoIdWithUser) if (previous) { const todo = { ...previous, completed } pubsub.publish('todoUpdated', todo) - await plugin.providers.kv(userId).set(todoIdWithUser, todo) + await plugin.providers.kv(sub).set(todoIdWithUser, todo) return todo } return null diff --git a/packages/todo/graphql/Query/todos.ts b/packages/todo/graphql/Query/todos.ts index 0c9d6082..1f7af0e8 100644 --- a/packages/todo/graphql/Query/todos.ts +++ b/packages/todo/graphql/Query/todos.ts @@ -6,8 +6,8 @@ import type { QueryResolvers, Todo } from '../schema.generated' export const todos: NonNullable = async (_, __, { decodedToken }) => { if (decodedToken?.type === 'AnonymousAuth') { - const { userId } = decodedToken - const allTodos = await plugin.providers.kv(userId).values() + const { sub } = decodedToken + const allTodos = await plugin.providers.kv(sub).values() return allTodos } diff --git a/packages/urql-expo/config.ts b/packages/urql-expo/config.ts index f058955a..040a90b8 100644 --- a/packages/urql-expo/config.ts +++ b/packages/urql-expo/config.ts @@ -1 +1 @@ -export const GRAPHQL_ENDPOINT = process.env.EXPO_PUBLIC_GRAPHQL_ENDPOINT ?? 'http://robmax.local:3000/graphql' +export const GRAPHQL_ENDPOINT = process.env['EXPO_PUBLIC_GRAPHQL_ENDPOINT'] ?? 'http://robmax.local:3000/graphql' diff --git a/packages/urql-expo/tsconfig.json b/packages/urql-expo/tsconfig.json index b9567f60..5ea0aaa6 100644 --- a/packages/urql-expo/tsconfig.json +++ b/packages/urql-expo/tsconfig.json @@ -1,6 +1,7 @@ { "extends": "expo/tsconfig.base", "compilerOptions": { - "strict": true + "strict": true, + "noPropertyAccessFromIndexSignature": true } } diff --git a/packages/utils/createKeyMap.ts b/packages/utils/createKeyMap.ts index a637a2b9..df3ecb59 100644 --- a/packages/utils/createKeyMap.ts +++ b/packages/utils/createKeyMap.ts @@ -1,5 +1,10 @@ export function createKeyMap(items: readonly T[], idKeyOrFn: keyof T | ((item: T) => number | string | symbol)) { - return new Map(items.map((item) => [idKeyOrFn instanceof Function ? idKeyOrFn(item) : item[idKeyOrFn], item])) + return new Map(items.map((item) => { + if (idKeyOrFn instanceof Function) { + return [idKeyOrFn(item), item] as const + } + return [item[idKeyOrFn] as number | string | symbol, item] as const + })) } export default createKeyMap diff --git a/packages/utils/fastify/timeoutifyPlugin.test.ts b/packages/utils/fastify/timeoutifyPlugin.test.ts index d0b0b275..dfe712e9 100644 --- a/packages/utils/fastify/timeoutifyPlugin.test.ts +++ b/packages/utils/fastify/timeoutifyPlugin.test.ts @@ -6,7 +6,7 @@ import { timeoutifyPlugin } from './timeoutifyPlugin' import type { onRequestHookHandler } from 'fastify' -const on = (evt: string, handler: () => void) => { +const on = (_: string, handler: () => void) => { handler() } @@ -25,7 +25,7 @@ describe('timeoutifyPlugin', () => { const done = jest.fn() let callback: onRequestHookHandler const fakeServer = { - addHook: (eventType: string, handler: onRequestHookHandler) => { + addHook: (_: string, handler: onRequestHookHandler) => { callback = handler }, } diff --git a/packages/utils/node/SuperDataLoader.big.test.ts b/packages/utils/node/SuperDataLoader.big.test.ts index 12ce10c0..cfcca730 100644 --- a/packages/utils/node/SuperDataLoader.big.test.ts +++ b/packages/utils/node/SuperDataLoader.big.test.ts @@ -29,8 +29,13 @@ function issuerById() { } } +type DataLoaders = { + readonly instrumentById: DataLoader + readonly issuerById: DataLoader +} + function initDataLoaders() { - const dls: Record> = { + const dls: DataLoaders = { instrumentById: new DataLoader(instrumentById(), { cacheKeyFn: (_id) => _id.toString() }), issuerById: new DataLoader(issuerById(), { cacheKeyFn: (_id) => _id.toString() }), } @@ -52,14 +57,14 @@ describe('SuperDataLoader.big', () => { it('trivial', async () => { const dataloaders = initDataLoaders() - const instrument = await dataloaders.instrumentById?.load('558ba89433d865236cb94bda') + const instrument = await dataloaders['instrumentById']?.load('558ba89433d865236cb94bda') expect(instrument).toStrictEqual({ _id: '558ba89433d865236cb94bda', issuerId: '5c4700e90a40e1000171e371', }) - const issuer = await dataloaders.issuerById?.load('561a62f35548753344e7252f') + const issuer = await dataloaders['issuerById']?.load('561a62f35548753344e7252f') expect(issuer).toStrictEqual({ _id: '561a62f35548753344e7252f', @@ -72,7 +77,7 @@ describe('SuperDataLoader.big', () => { const instrumentIds: readonly string[] = TRANSACTION_ITEMS.map((item) => item.instrumentId) const startDataLoader = performance.now() - const loadedInstruments = await dataloaders.instrumentById?.loadMany(instrumentIds) + const loadedInstruments = await dataloaders['instrumentById']?.loadMany(instrumentIds) const endDataLoader = performance.now() expect(loadedInstruments).toHaveLength(95022) @@ -123,11 +128,11 @@ describe('SuperDataLoader.big', () => { }, TransactionItem: { // eslint-disable-next-line @typescript-eslint/no-shadow - instrument: async ({ instrumentId }, _, { dataloaders }) => dataloaders.instrumentById.load(instrumentId), + instrument: async ({ instrumentId }: { readonly instrumentId: string }, _: unknown, { dataloaders }: {readonly dataloaders: DataLoaders}) => dataloaders.instrumentById.load(instrumentId), }, Instrument: { // eslint-disable-next-line @typescript-eslint/no-shadow - issuer: ({ issuerId }, _, { dataloaders }) => (issuerId ? dataloaders.issuerById.load(issuerId) : null), + issuer: async ({ issuerId }: { readonly issuerId: string }, _: unknown, { dataloaders }: {readonly dataloaders: DataLoaders}) => (issuerId ? dataloaders.issuerById.load(issuerId) : null), }, } @@ -160,7 +165,7 @@ describe('SuperDataLoader.big', () => { const endDataLoader = performance.now() expect(result.errors).toBeUndefined() - let transactionItems = result?.data?.transactionItems + let transactionItems = result?.data?.['transactionItems'] expect(transactionItems).toBeDefined() expect(transactionItems).toHaveLength(95022) @@ -179,7 +184,7 @@ describe('SuperDataLoader.big', () => { const endSuperDataLoader = performance.now() expect(result.errors).toBeUndefined() - transactionItems = result?.data?.transactionItems + transactionItems = result?.data?.['transactionItems'] expect(transactionItems).toBeDefined() expect(transactionItems).toHaveLength(95022) diff --git a/packages/utils/node/Timeoutify.ts b/packages/utils/node/Timeoutify.ts index f6464eab..b0404a71 100644 --- a/packages/utils/node/Timeoutify.ts +++ b/packages/utils/node/Timeoutify.ts @@ -40,7 +40,7 @@ export class Timeoutify { if (timeoutMS > 0) { this.handle = setTimeout(() => { - if (process.env.DEBUG) { + if (process.env['DEBUG']) { this.logger.debug(`${this.logPrefix} setTimeout called`) } @@ -103,7 +103,7 @@ export class Timeoutify { * This ensures the MongoDB Operation is never running for longer than the timeout. * */ async runMongoOpWithTimeout(cursor: AbstractCursor): Promise { - if (process.env.DEBUG) { + if (process.env['DEBUG']) { this.logger.debug(`${this.logPrefix} runMongoOpWithTimeout called`) } if (this.status === TimeoutifyStatus.Aborted || this.status === TimeoutifyStatus.TimedOut) { diff --git a/packages/utils/object.map.ts b/packages/utils/object.map.ts index 80d4cc73..3deb60a0 100644 --- a/packages/utils/object.map.ts +++ b/packages/utils/object.map.ts @@ -1,10 +1,12 @@ -interface Object { - map(this: T, mapper: (value: T) => TOut): TOut; +declare global { + interface Object { + map(this: T, mapper: (value: T) => TOut): TOut; + } } // eslint-disable-next-line functional/immutable-data, no-extend-native Object.defineProperty(Object.prototype, 'map', { - value(mapper) { + value(mapper: (value: unknown) => unknown) { return mapper(this) }, configurable: true, diff --git a/packages/utils/pipe.ts b/packages/utils/pipe.ts index 2dec3ee6..a4f29a40 100644 --- a/packages/utils/pipe.ts +++ b/packages/utils/pipe.ts @@ -1,10 +1,12 @@ -interface Object { - pipeTo(this: T, mapper: (value: T) => TOut): TOut; +declare global { + interface Object { + pipeTo(this: T, mapper: (value: T) => TOut): TOut; + } } // eslint-disable-next-line functional/immutable-data, no-extend-native Object.defineProperty(Object.prototype, 'pipeTo', { - value(mapper) { + value(mapper: (value: unknown) => unknown) { return mapper(this) }, configurable: true, diff --git a/packages/utils/promisify.ts b/packages/utils/promisify.ts index c9ea9248..22ffcaf8 100644 --- a/packages/utils/promisify.ts +++ b/packages/utils/promisify.ts @@ -1,5 +1,5 @@ -type Argument = T extends (arg: infer U, callback: infer X) => unknown ? U : unknown; -type Callback = T extends (arg: infer U, callback: infer X) => unknown ? X : unknown; +type Argument = T extends (arg: infer U, callback: infer _) => unknown ? U : unknown; +type Callback = T extends (arg: infer _, callback: infer X) => unknown ? X : unknown; export function promisify< V, diff --git a/packages/utils/statsPipeline.ts b/packages/utils/statsPipeline.ts index 0225847f..fe2d6b5b 100644 --- a/packages/utils/statsPipeline.ts +++ b/packages/utils/statsPipeline.ts @@ -158,7 +158,7 @@ export class StatsPipeline< getScalarValue(entry: Result | null | undefined, stat: TS): number | null { const val = match(stat as Statistic) .with('AVG', () => entry?.avg ?? null) - .with('MEDIAN', () => entry?.p50 ?? null) + .with('MEDIAN', () => entry?.['p50'] ?? null) .with('COUNT', () => entry?.count ?? null) .with('MAX', () => entry?.max ?? null) .with('MIN', () => entry?.min ?? null) @@ -166,9 +166,9 @@ export class StatsPipeline< .with('STD_DEV_POP', () => entry?.stdDevPop ?? null) .with('STD_DEV_SAMPLE', () => entry?.stdDevSamp ?? null) .with('TOP_3_AVG', () => { - const top1 = entry?.top1 ?? null - const top2 = entry?.top2 ?? null - const top3 = entry?.top3 ?? null + const top1 = entry?.['top1'] ?? null + const top2 = entry?.['top2'] ?? null + const top3 = entry?.['top3'] ?? null if (top1 !== null && top2 !== null && top3 !== null) { return (top1 + top2 + top3) / 3 diff --git a/packages/utils/types.ts b/packages/utils/types.ts new file mode 100644 index 00000000..4b9d90be --- /dev/null +++ b/packages/utils/types.ts @@ -0,0 +1 @@ +export type PromiseOrValue = Promise | T