From 6ed290f7c03c97835002969a86af90870cf34bae Mon Sep 17 00:00:00 2001 From: Michael Silva Date: Wed, 18 Sep 2024 08:56:36 -0400 Subject: [PATCH 01/29] refill day working on dashboard --- apps/api/src/routes/schema.ts | 9 +- apps/api/src/routes/v1_keys_createKey.ts | 10 +- apps/api/src/routes/v1_keys_getKey.ts | 1 + apps/api/src/routes/v1_keys_updateKey.ts | 5 + .../api/src/routes/v1_migrations_createKey.ts | 8 + .../src/routes/v1_migrations_enqueueKeys.ts | 4 + .../[keyId]/settings/update-key-remaining.tsx | 41 + .../[apiId]/keys/[keyAuthId]/new/client.tsx | 43 + apps/dashboard/lib/trpc/routers/key/create.ts | 3 + .../lib/trpc/routers/key/updateRemaining.ts | 3 + internal/db/src/schema/keys.ts | 1 + packages/api/src/openapi.d.ts | 1105 ++++++++--------- 12 files changed, 666 insertions(+), 567 deletions(-) diff --git a/apps/api/src/routes/schema.ts b/apps/api/src/routes/schema.ts index 1652e8e92f..1756eb3d68 100644 --- a/apps/api/src/routes/schema.ts +++ b/apps/api/src/routes/schema.ts @@ -66,6 +66,10 @@ export const keySchema = z description: "Resets `remaining` to this value every interval.", example: 100, }), + dayOfMonth: z.number().min(3).max(31).default(1).nullable().openapi({ + description: + "The day verifications will refill each month, when interval is set to 'monthly'", + }), lastRefillAt: z.number().int().optional().openapi({ description: "The unix timestamp in miliseconds when the key was last refilled.", example: 100, @@ -76,10 +80,13 @@ export const keySchema = z description: "Unkey allows you to refill remaining verifications on a key on a regular interval.", example: { - interval: "daily", + interval: "monthly", amount: 10, + dayOfMonth: 10, + }, }), + ratelimit: z .object({ async: z.boolean().openapi({ diff --git a/apps/api/src/routes/v1_keys_createKey.ts b/apps/api/src/routes/v1_keys_createKey.ts index 006feb7caf..a1de41a79e 100644 --- a/apps/api/src/routes/v1_keys_createKey.ts +++ b/apps/api/src/routes/v1_keys_createKey.ts @@ -117,16 +117,23 @@ When validating a key, we will return this back to you, so you can clearly ident description: "The number of verifications to refill for each occurrence is determined individually for each key.", }), + dayOfMonth: z.number().min(3).max(31).optional().openapi({ + description: + "The day verifications will refill each month, when interval is set to 'monthly'", + }), }) .optional() .openapi({ description: "Unkey enables you to refill verifications for each key at regular intervals.", example: { - interval: "daily", + interval: "monthly", amount: 100, + dayOfMonth: 15 }, }), + + ratelimit: z .object({ async: z @@ -356,6 +363,7 @@ export const registerV1KeysCreateKey = (app: App) => ratelimitDuration: req.ratelimit?.duration ?? req.ratelimit?.refillInterval, remaining: req.remaining, refillInterval: req.refill?.interval, + refillDay: req.refill?.interval === "monthly" ? req.refill.dayOfMonth : null, refillAmount: req.refill?.amount, lastRefillAt: req.refill?.interval ? new Date() : null, deletedAt: null, diff --git a/apps/api/src/routes/v1_keys_getKey.ts b/apps/api/src/routes/v1_keys_getKey.ts index a1fd3efb23..e95d316de7 100644 --- a/apps/api/src/routes/v1_keys_getKey.ts +++ b/apps/api/src/routes/v1_keys_getKey.ts @@ -155,6 +155,7 @@ export const registerV1KeysGetKey = (app: App) => ? { interval: key.refillInterval, amount: key.refillAmount, + dayOfMonth: key.refillDay, lastRefillAt: key.lastRefillAt?.getTime(), } : undefined, diff --git a/apps/api/src/routes/v1_keys_updateKey.ts b/apps/api/src/routes/v1_keys_updateKey.ts index 566176da56..e8e6a91f41 100644 --- a/apps/api/src/routes/v1_keys_updateKey.ts +++ b/apps/api/src/routes/v1_keys_updateKey.ts @@ -136,6 +136,10 @@ This field will become required in a future version.`, description: "The amount of verifications to refill for each occurrence is determined individually for each key.", }), + dayOfMonth: z.number().min(3).max(31).optional().openapi({ + description: + "The day verifications will refill each month, when interval is set to 'monthly'", + }), }) .nullable() .optional() @@ -353,6 +357,7 @@ export const registerV1KeysUpdate = (app: App) => : req.ratelimit?.duration ?? req.ratelimit?.refillInterval ?? null, refillInterval: req.refill === null ? null : req.refill?.interval, refillAmount: req.refill === null ? null : req.refill?.amount, + refillDay: req.refill?.interval !== "monthly" ? null : req.refill.dayOfMonth, lastRefillAt: req.refill == null || req.refill?.amount == null ? null : new Date(), enabled: req.enabled, }) diff --git a/apps/api/src/routes/v1_migrations_createKey.ts b/apps/api/src/routes/v1_migrations_createKey.ts index bd8a201f84..a930c0c516 100644 --- a/apps/api/src/routes/v1_migrations_createKey.ts +++ b/apps/api/src/routes/v1_migrations_createKey.ts @@ -130,6 +130,10 @@ When validating a key, we will return this back to you, so you can clearly ident description: "The number of verifications to refill for each occurrence is determined individually for each key.", }), + dayOfMonth: z.number().min(3).max(31).optional().openapi({ + description: + "The day verifications will refill each month, when interval is set to 'monthly'", + }), }) .optional() .openapi({ @@ -412,6 +416,10 @@ export const registerV1MigrationsCreateKeys = (app: App) => remaining: key.remaining ?? null, refillInterval: key.refill?.interval ?? null, refillAmount: key.refill?.amount ?? null, + refillDay: + key.refill?.interval === "monthly" && key.refill.dayOfMonth + ? key.refill.dayOfMonth + : null, lastRefillAt: key.refill?.interval ? new Date() : null, deletedAt: null, enabled: key.enabled ?? true, diff --git a/apps/api/src/routes/v1_migrations_enqueueKeys.ts b/apps/api/src/routes/v1_migrations_enqueueKeys.ts index efecfe80cc..ec01032578 100644 --- a/apps/api/src/routes/v1_migrations_enqueueKeys.ts +++ b/apps/api/src/routes/v1_migrations_enqueueKeys.ts @@ -131,6 +131,10 @@ When validating a key, we will return this back to you, so you can clearly ident description: "The number of verifications to refill for each occurrence is determined individually for each key.", }), + dayOfMonth: z.number().min(3).max(31).optional().openapi({ + description: + "The day verifications will refill each month, when interval is set to 'monthly'", + }), }) .optional() .openapi({ diff --git a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/update-key-remaining.tsx b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/update-key-remaining.tsx index 8054500e03..575bbb02ab 100644 --- a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/update-key-remaining.tsx +++ b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/update-key-remaining.tsx @@ -51,6 +51,20 @@ const formSchema = z.object({ }) .positive() .optional(), + dayOfMonth: z.coerce + .number({ + errorMap: (issue, { defaultError }) => ({ + message: + issue.code === "invalid_type" + ? "Refill day must be greater than 0 and a integer 31 or less" + : defaultError, + }), + }) + .int() + .min(1) + .max(31) + .positive() + .optional(), }) .optional(), }); @@ -61,6 +75,7 @@ type Props = { remaining: number | null; refillInterval: "daily" | "monthly" | null; refillAmount: number | null; + refillDay: number | null; }; }; @@ -78,6 +93,7 @@ export const UpdateKeyRemaining: React.FC = ({ apiKey }) => { refill: { interval: apiKey.refillInterval === null ? "none" : apiKey.refillInterval, amount: apiKey.refillAmount ? apiKey.refillAmount : undefined, + dayOfMonth: apiKey.refillInterval === "monthly" && apiKey.refillDay ? apiKey.refillDay : undefined, }, }, }); @@ -212,6 +228,31 @@ export const UpdateKeyRemaining: React.FC = ({ apiKey }) => { )} /> + ( + + Day of the month to refill uses + + + + Enter the day to refill monthly. + + + )} + /> diff --git a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/new/client.tsx b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/new/client.tsx index 6e4af59efb..72b83306b3 100644 --- a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/new/client.tsx +++ b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/new/client.tsx @@ -109,6 +109,20 @@ const formSchema = z.object({ .min(1) .positive() .optional(), + dayOfMonth: z.coerce + .number({ + errorMap: (issue, { defaultError }) => ({ + message: + issue.code === "invalid_type" + ? "Refill day must be greater than 0 and a integer 31 or less" + : defaultError, + }), + }) + .int() + .min(1) + .max(31) + .positive() + .optional(), }) .optional(), }) @@ -630,6 +644,35 @@ export const CreateKey: React.FC = ({ apiId, keyAuthId }) => { )} + /> + ( + + Day of the month to refill uses + + + + + Enter the day to refill monthly. + + + + )} /> How many requests may be performed in a given interval diff --git a/apps/dashboard/lib/trpc/routers/key/create.ts b/apps/dashboard/lib/trpc/routers/key/create.ts index fd1b4b0bed..ed6e4fe835 100644 --- a/apps/dashboard/lib/trpc/routers/key/create.ts +++ b/apps/dashboard/lib/trpc/routers/key/create.ts @@ -20,8 +20,10 @@ export const createKey = t.procedure .object({ interval: z.enum(["daily", "monthly"]), amount: z.coerce.number().int().min(1), + dayOfMonth: z.number().int().min(1).max(31).default(1), }) .optional(), + expires: z.number().int().nullish(), // unix timestamp in milliseconds name: z.string().optional(), ratelimit: z @@ -87,6 +89,7 @@ export const createKey = t.procedure ratelimitDuration: input.ratelimit?.duration, remaining: input.remaining, refillInterval: input.refill?.interval ?? null, + refillDay: input.refill?.interval === "monthly" ? input.refill.dayOfMonth : undefined, refillAmount: input.refill?.amount ?? null, lastRefillAt: input.refill?.interval ? new Date() : null, deletedAt: null, diff --git a/apps/dashboard/lib/trpc/routers/key/updateRemaining.ts b/apps/dashboard/lib/trpc/routers/key/updateRemaining.ts index 53dd7dec15..daa0617acd 100644 --- a/apps/dashboard/lib/trpc/routers/key/updateRemaining.ts +++ b/apps/dashboard/lib/trpc/routers/key/updateRemaining.ts @@ -15,8 +15,10 @@ export const updateKeyRemaining = t.procedure .object({ interval: z.enum(["daily", "monthly", "none"]), amount: z.number().int().min(1).optional(), + dayOfMonth: z.number().int().min(1).max(31).optional(), }) .optional(), + }), ) .mutation(async ({ input, ctx }) => { @@ -50,6 +52,7 @@ export const updateKeyRemaining = t.procedure input.refill?.interval === "none" || input.refill?.interval === undefined ? null : input.refill?.interval, + refillDay: input.refill?.interval === "monthly" ? input.refill.dayOfMonth : undefined, refillAmount: input.refill?.amount ?? null, lastRefillAt: input.refill?.interval ? new Date() : null, }) diff --git a/internal/db/src/schema/keys.ts b/internal/db/src/schema/keys.ts index 5eeb701f65..9411def357 100644 --- a/internal/db/src/schema/keys.ts +++ b/internal/db/src/schema/keys.ts @@ -63,6 +63,7 @@ export const keys = mysqlTable( * You can refill uses to keys at a desired interval */ refillInterval: mysqlEnum("refill_interval", ["daily", "monthly"]), + refillDay: int("refill_day"), refillAmount: int("refill_amount"), lastRefillAt: datetime("last_refill_at", { fsp: 3 }), /** diff --git a/packages/api/src/openapi.d.ts b/packages/api/src/openapi.d.ts index fce1bc5bb0..d4a8918768 100644 --- a/packages/api/src/openapi.d.ts +++ b/packages/api/src/openapi.d.ts @@ -3,14 +3,11 @@ * Do not make direct changes to the file. */ + /** OneOf type helpers */ type Without = { [P in Exclude]?: never }; -type XOR = T | U extends object ? (Without & U) | (Without & T) : T | U; -type OneOf = T extends [infer Only] - ? Only - : T extends [infer A, infer B, ...infer Rest] - ? OneOf<[XOR, ...Rest]> - : never; +type XOR = (T | U) extends object ? (Without & U) | (Without & T) : T | U; +type OneOf = T extends [infer Only] ? Only : T extends [infer A, infer B, ...infer Rest] ? OneOf<[XOR, ...Rest]> : never; export interface paths { "/v1/liveness": { @@ -521,16 +518,7 @@ export interface components { * * @enum {string} */ - code: - | "VALID" - | "NOT_FOUND" - | "FORBIDDEN" - | "USAGE_EXCEEDED" - | "RATE_LIMITED" - | "UNAUTHORIZED" - | "DISABLED" - | "INSUFFICIENT_PERMISSIONS" - | "EXPIRED"; + code: "VALID" | "NOT_FOUND" | "FORBIDDEN" | "USAGE_EXCEEDED" | "RATE_LIMITED" | "UNAUTHORIZED" | "DISABLED" | "INSUFFICIENT_PERMISSIONS" | "EXPIRED"; /** @description Sets the key to be enabled or disabled. Disabled keys will not verify. */ enabled?: boolean; /** @@ -556,18 +544,11 @@ export interface components { }; }; /** @description A query for which permissions you require */ - PermissionQuery: OneOf< - [ - string, - { - and: components["schemas"]["PermissionQuery"][]; - }, - { - or: components["schemas"]["PermissionQuery"][]; - }, - null, - ] - >; + PermissionQuery: OneOf<[string, { + and: components["schemas"]["PermissionQuery"][]; + }, { + or: components["schemas"]["PermissionQuery"][]; + }, null]>; V1KeysVerifyKeyRequest: { /** * @description The id of the api where the key belongs to. This is optional for now but will be required soon. @@ -612,21 +593,21 @@ export interface components { * ] */ ratelimits?: { - /** - * @description The name of the ratelimit. - * @example tokens - */ - name: string; - /** - * @description Optionally override how expensive this operation is and how many tokens are deducted from the current limit. - * @default 1 - */ - cost?: number; - /** @description Optionally override the limit. */ - limit?: number; - /** @description Optionally override the ratelimit window duration. */ - duration?: number; - }[]; + /** + * @description The name of the ratelimit. + * @example tokens + */ + name: string; + /** + * @description Optionally override how expensive this operation is and how many tokens are deducted from the current limit. + * @default 1 + */ + cost?: number; + /** @description Optionally override the limit. */ + limit?: number; + /** @description Optionally override the ratelimit window duration. */ + duration?: number; + }[]; }; ErrDeleteProtected: { error: { @@ -652,7 +633,8 @@ export interface components { }; }; responses: never; - parameters: {}; + parameters: { + }; requestBodies: never; headers: never; pathItems: never; @@ -663,6 +645,7 @@ export type $defs = Record; export type external = Record; export interface operations { + "v1.liveness": { responses: { /** @description The configured services and their status */ @@ -1181,7 +1164,7 @@ export interface operations { * "refillInterval": 60 * } */ - ratelimit?: { + ratelimit?: ({ /** * @deprecated * @description Fast ratelimiting doesn't add latency, while consistent ratelimiting is more accurate. @@ -1213,7 +1196,7 @@ export interface operations { * This field will become required in a future version. */ duration?: number; - } | null; + }) | null; /** * @description The number of requests that can be made with this key before it becomes invalid. Set `null` to disable. * @example 1000 @@ -1226,7 +1209,7 @@ export interface operations { * "amount": 100 * } */ - refill?: { + refill?: ({ /** * @description Unkey will automatically refill verifications at the set interval. If null is used the refill functionality will be removed from the key. * @enum {string} @@ -1234,7 +1217,7 @@ export interface operations { interval: "daily" | "monthly"; /** @description The amount of verifications to refill for each occurrence is determined individually for each key. */ amount: number; - } | null; + }) | null; /** * @description Set if key is enabled or disabled. If disabled, the key cannot be used to verify. * @example true @@ -1257,16 +1240,16 @@ export interface operations { * ] */ roles?: { - /** @description The id of the role. Provide either `id` or `name`. If both are provided `id` is used. */ - id?: string; - /** @description Identify the role via its name. Provide either `id` or `name`. If both are provided `id` is used. */ - name?: string; - /** - * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. - * Autocreating roles requires your root key to have the `rbac.*.create_role` permission, otherwise the request will get rejected - */ - create?: boolean; - }[]; + /** @description The id of the role. Provide either `id` or `name`. If both are provided `id` is used. */ + id?: string; + /** @description Identify the role via its name. Provide either `id` or `name`. If both are provided `id` is used. */ + name?: string; + /** + * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. + * Autocreating roles requires your root key to have the `rbac.*.create_role` permission, otherwise the request will get rejected + */ + create?: boolean; + }[]; /** * @description The permissions you want to set for this key. This overwrites all existing permissions. * Setting permissions requires the `rbac.*.add_permission_to_key` permission. @@ -1284,16 +1267,16 @@ export interface operations { * ] */ permissions?: { - /** @description The id of the permission. Provide either `id` or `name`. If both are provided `id` is used. */ - id?: string; - /** @description Identify the permission via its name. Provide either `id` or `name`. If both are provided `id` is used. */ - name?: string; - /** - * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. - * Autocreating permissions requires your root key to have the `rbac.*.create_permission` permission, otherwise the request will get rejected - */ - create?: boolean; - }[]; + /** @description The id of the permission. Provide either `id` or `name`. If both are provided `id` is used. */ + id?: string; + /** @description Identify the permission via its name. Provide either `id` or `name`. If both are provided `id` is used. */ + name?: string; + /** + * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. + * Autocreating permissions requires your root key to have the `rbac.*.create_permission` permission, otherwise the request will get rejected + */ + create?: boolean; + }[]; }; }; }; @@ -1443,27 +1426,27 @@ export interface operations { content: { "application/json": { verifications: { - /** - * @description The timestamp of the usage data - * @example 1620000000000 - */ - time: number; - /** - * @description The number of successful requests - * @example 100 - */ - success: number; - /** - * @description The number of requests that were rate limited - * @example 10 - */ - rateLimited: number; - /** - * @description The number of requests that exceeded the usage limit - * @example 0 - */ - usageExceeded: number; - }[]; + /** + * @description The timestamp of the usage data + * @example 1620000000000 + */ + time: number; + /** + * @description The number of successful requests + * @example 100 + */ + success: number; + /** + * @description The number of requests that were rate limited + * @example 10 + */ + rateLimited: number; + /** + * @description The number of requests that exceeded the usage limit + * @example 0 + */ + usageExceeded: number; + }[]; }; }; }; @@ -1519,16 +1502,16 @@ export interface operations { keyId: string; /** @description The permissions you want to add to this key */ permissions: { - /** @description The id of the permission. Provide either `id` or `name`. If both are provided `id` is used. */ - id?: string; - /** @description Identify the permission via its name. Provide either `id` or `name`. If both are provided `id` is used. */ - name?: string; - /** - * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. - * Autocreating permissions requires your root key to have the `rbac.*.create_permission` permission, otherwise the request will get rejected - */ - create?: boolean; - }[]; + /** @description The id of the permission. Provide either `id` or `name`. If both are provided `id` is used. */ + id?: string; + /** @description Identify the permission via its name. Provide either `id` or `name`. If both are provided `id` is used. */ + name?: string; + /** + * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. + * Autocreating permissions requires your root key to have the `rbac.*.create_permission` permission, otherwise the request will get rejected + */ + create?: boolean; + }[]; }; }; }; @@ -1537,17 +1520,17 @@ export interface operations { 200: { content: { "application/json": { - /** - * @description The id of the permission. This is used internally - * @example perm_123 - */ - id: string; - /** - * @description The name of the permission - * @example dns.record.create - */ - name: string; - }[]; + /** + * @description The id of the permission. This is used internally + * @example perm_123 + */ + id: string; + /** + * @description The name of the permission + * @example dns.record.create + */ + name: string; + }[]; }; }; /** @description The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). */ @@ -1612,11 +1595,11 @@ export interface operations { * ] */ permissions: { - /** @description The id of the permission. Provide either `id` or `name`. If both are provided `id` is used. */ - id?: string; - /** @description Identify the permission via its name. Provide either `id` or `name`. If both are provided `id` is used. */ - name?: string; - }[]; + /** @description The id of the permission. Provide either `id` or `name`. If both are provided `id` is used. */ + id?: string; + /** @description Identify the permission via its name. Provide either `id` or `name`. If both are provided `id` is used. */ + name?: string; + }[]; }; }; }; @@ -1694,16 +1677,16 @@ export interface operations { * ] */ permissions: { - /** @description The id of the permission. Provide either `id` or `name`. If both are provided `id` is used. */ - id?: string; - /** @description Identify the permission via its name. Provide either `id` or `name`. If both are provided `id` is used. */ - name?: string; - /** - * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. - * Autocreating permissions requires your root key to have the `rbac.*.create_permission` permission, otherwise the request will get rejected - */ - create?: boolean; - }[]; + /** @description The id of the permission. Provide either `id` or `name`. If both are provided `id` is used. */ + id?: string; + /** @description Identify the permission via its name. Provide either `id` or `name`. If both are provided `id` is used. */ + name?: string; + /** + * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. + * Autocreating permissions requires your root key to have the `rbac.*.create_permission` permission, otherwise the request will get rejected + */ + create?: boolean; + }[]; }; }; }; @@ -1712,17 +1695,17 @@ export interface operations { 200: { content: { "application/json": { - /** - * @description The id of the permission. This is used internally - * @example perm_123 - */ - id: string; - /** - * @description The name of the permission - * @example dns.record.create - */ - name: string; - }[]; + /** + * @description The id of the permission. This is used internally + * @example perm_123 + */ + id: string; + /** + * @description The name of the permission + * @example dns.record.create + */ + name: string; + }[]; }; }; /** @description The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). */ @@ -1792,16 +1775,16 @@ export interface operations { * ] */ roles: { - /** @description The id of the role. Provide either `id` or `name`. If both are provided `id` is used. */ - id?: string; - /** @description Identify the role via its name. Provide either `id` or `name`. If both are provided `id` is used. */ - name?: string; - /** - * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. - * Autocreating roles requires your root key to have the `rbac.*.create_role` permission, otherwise the request will get rejected - */ - create?: boolean; - }[]; + /** @description The id of the role. Provide either `id` or `name`. If both are provided `id` is used. */ + id?: string; + /** @description Identify the role via its name. Provide either `id` or `name`. If both are provided `id` is used. */ + name?: string; + /** + * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. + * Autocreating roles requires your root key to have the `rbac.*.create_role` permission, otherwise the request will get rejected + */ + create?: boolean; + }[]; }; }; }; @@ -1810,17 +1793,17 @@ export interface operations { 200: { content: { "application/json": { - /** - * @description The id of the role. This is used internally - * @example role_123 - */ - id: string; - /** - * @description The name of the role - * @example dns.record.create - */ - name: string; - }[]; + /** + * @description The id of the role. This is used internally + * @example role_123 + */ + id: string; + /** + * @description The name of the role + * @example dns.record.create + */ + name: string; + }[]; }; }; /** @description The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). */ @@ -1885,11 +1868,11 @@ export interface operations { * ] */ roles: { - /** @description The id of the role. Provide either `id` or `name`. If both are provided `id` is used. */ - id?: string; - /** @description Identify the role via its name. Provide either `id` or `name`. If both are provided `id` is used. */ - name?: string; - }[]; + /** @description The id of the role. Provide either `id` or `name`. If both are provided `id` is used. */ + id?: string; + /** @description Identify the role via its name. Provide either `id` or `name`. If both are provided `id` is used. */ + name?: string; + }[]; }; }; }; @@ -1967,16 +1950,16 @@ export interface operations { * ] */ roles: { - /** @description The id of the role. Provide either `id` or `name`. If both are provided `id` is used. */ - id?: string; - /** @description Identify the role via its name. Provide either `id` or `name`. If both are provided `id` is used. */ - name?: string; - /** - * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. - * Autocreating roles requires your root key to have the `rbac.*.create_role` permission, otherwise the request will get rejected - */ - create?: boolean; - }[]; + /** @description The id of the role. Provide either `id` or `name`. If both are provided `id` is used. */ + id?: string; + /** @description Identify the role via its name. Provide either `id` or `name`. If both are provided `id` is used. */ + name?: string; + /** + * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. + * Autocreating roles requires your root key to have the `rbac.*.create_role` permission, otherwise the request will get rejected + */ + create?: boolean; + }[]; }; }; }; @@ -1985,17 +1968,17 @@ export interface operations { 200: { content: { "application/json": { - /** - * @description The id of the role. This is used internally - * @example role_123 - */ - id: string; - /** - * @description The name of the role - * @example dns.record.create - */ - name: string; - }[]; + /** + * @description The id of the role. This is used internally + * @example role_123 + */ + id: string; + /** + * @description The name of the role + * @example dns.record.create + */ + name: string; + }[]; }; }; /** @description The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). */ @@ -2444,26 +2427,26 @@ export interface operations { * ] */ resources?: { - /** - * @description The type of resource - * @example organization - */ - type: string; - /** - * @description The unique identifier for the resource - * @example org_123 - */ - id: string; - /** - * @description A human readable name for this resource - * @example unkey - */ - name?: string; - /** @description Attach any metadata to this resources */ - meta?: { - [key: string]: unknown; - }; - }[]; + /** + * @description The type of resource + * @example organization + */ + type: string; + /** + * @description The unique identifier for the resource + * @example org_123 + */ + id: string; + /** + * @description A human readable name for this resource + * @example unkey + */ + name?: string; + /** @description Attach any metadata to this resources */ + meta?: { + [key: string]: unknown; + }; + }[]; }; }; }; @@ -2541,230 +2524,18 @@ export interface operations { "v1.migrations.createKeys": { requestBody: { content: { - "application/json": { - /** - * @description Choose an `API` where this key should be created. - * @example api_123 - */ - apiId: string; - /** - * @description To make it easier for your users to understand which product an api key belongs to, you can add prefix them. - * - * For example Stripe famously prefixes their customer ids with cus_ or their api keys with sk_live_. - * - * The underscore is automatically added if you are defining a prefix, for example: "prefix": "abc" will result in a key like abc_xxxxxxxxx - */ - prefix?: string; - /** - * @description The name for your Key. This is not customer facing. - * @example my key - */ - name?: string; - /** @description The raw key in plaintext. If provided, unkey encrypts this value and stores it securely. Provide either `hash` or `plaintext` */ - plaintext?: string; - /** @description Provide either `hash` or `plaintext` */ - hash?: { - /** @description The hashed and encoded key */ - value: string; + "application/json": ({ /** - * @description The algorithm for hashing and encoding, currently only sha256 and base64 are supported - * @enum {string} + * @description Choose an `API` where this key should be created. + * @example api_123 */ - variant: "sha256_base64"; - }; - /** - * @description The first 4 characters of the key. If a prefix is used, it should be the prefix plus 4 characters. - * @example unkey_32kq - */ - start?: string; - /** - * @description Your user’s Id. This will provide a link between Unkey and your customer record. - * When validating a key, we will return this back to you, so you can clearly identify your user from their api key. - * @example team_123 - */ - ownerId?: string; - /** - * @description This is a place for dynamic meta data, anything that feels useful for you should go here - * @example { - * "billingTier": "PRO", - * "trialEnds": "2023-06-16T17:16:37.161Z" - * } - */ - meta?: { - [key: string]: unknown; - }; - /** - * @description A list of roles that this key should have. If the role does not exist, an error is thrown - * @example [ - * "admin", - * "finance" - * ] - */ - roles?: string[]; - /** - * @description A list of permissions that this key should have. If the permission does not exist, an error is thrown - * @example [ - * "domains.create_record", - * "say_hello" - * ] - */ - permissions?: string[]; - /** - * @description You can auto expire keys by providing a unix timestamp in milliseconds. Once Keys expire they will automatically be disabled and are no longer valid unless you enable them again. - * @example 1623869797161 - */ - expires?: number; - /** - * @description You can limit the number of requests a key can make. Once a key reaches 0 remaining requests, it will automatically be disabled and is no longer valid unless you update it. - * @example 1000 - */ - remaining?: number; - /** - * @description Unkey enables you to refill verifications for each key at regular intervals. - * @example { - * "interval": "daily", - * "amount": 100 - * } - */ - refill?: { + apiId: string; /** - * @description Unkey will automatically refill verifications at the set interval. - * @enum {string} - */ - interval: "daily" | "monthly"; - /** @description The number of verifications to refill for each occurrence is determined individually for each key. */ - amount: number; - }; - /** - * @description Unkey comes with per-key ratelimiting out of the box. - * @example { - * "type": "fast", - * "limit": 10, - * "refillRate": 1, - * "refillInterval": 60 - * } - */ - ratelimit?: { - /** - * @description Async will return a response immediately, lowering latency at the cost of accuracy. - * @default false - */ - async?: boolean; - /** - * @deprecated - * @description Fast ratelimiting doesn't add latency, while consistent ratelimiting is more accurate. - * @default fast - * @enum {string} - */ - type?: "fast" | "consistent"; - /** @description The total amount of burstable requests. */ - limit: number; - /** - * @deprecated - * @description How many tokens to refill during each refillInterval. - */ - refillRate: number; - /** - * @deprecated - * @description Determines the speed at which tokens are refilled, in milliseconds. - */ - refillInterval: number; - }; - /** - * @description Sets if key is enabled or disabled. Disabled keys are not valid. - * @default true - * @example false - */ - enabled?: boolean; - /** - * @description Environments allow you to divide your keyspace. - * - * Some applications like Stripe, Clerk, WorkOS and others have a concept of "live" and "test" keys to - * give the developer a way to develop their own application without the risk of modifying real world - * resources. - * - * When you set an environment, we will return it back to you when validating the key, so you can - * handle it correctly. - */ - environment?: string; - }[]; - }; - }; - responses: { - /** @description The key ids of all created keys */ - 200: { - content: { - "application/json": { - /** - * @description The ids of the keys. This is not a secret and can be stored as a reference if you wish. You need the keyId to update or delete a key later. - * @example [ - * "key_123", - * "key_456" - * ] - */ - keyIds: string[]; - }; - }; - }; - /** @description The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). */ - 400: { - content: { - "application/json": components["schemas"]["ErrBadRequest"]; - }; - }; - /** @description Although the HTTP standard specifies "unauthorized", semantically this response means "unauthenticated". That is, the client must authenticate itself to get the requested response. */ - 401: { - content: { - "application/json": components["schemas"]["ErrUnauthorized"]; - }; - }; - /** @description The client does not have access rights to the content; that is, it is unauthorized, so the server is refusing to give the requested resource. Unlike 401 Unauthorized, the client's identity is known to the server. */ - 403: { - content: { - "application/json": components["schemas"]["ErrForbidden"]; - }; - }; - /** @description The server cannot find the requested resource. In the browser, this means the URL is not recognized. In an API, this can also mean that the endpoint is valid but the resource itself does not exist. Servers may also send this response instead of 403 Forbidden to hide the existence of a resource from an unauthorized client. This response code is probably the most well known due to its frequent occurrence on the web. */ - 404: { - content: { - "application/json": components["schemas"]["ErrNotFound"]; - }; - }; - /** @description This response is sent when a request conflicts with the current state of the server. */ - 409: { - content: { - "application/json": components["schemas"]["ErrConflict"]; - }; - }; - /** @description The user has sent too many requests in a given amount of time ("rate limiting") */ - 429: { - content: { - "application/json": components["schemas"]["ErrTooManyRequests"]; - }; - }; - /** @description The server has encountered a situation it does not know how to handle. */ - 500: { - content: { - "application/json": components["schemas"]["ErrInternalServerError"]; - }; - }; - }; - }; - "v1.migrations.enqueueKeys": { - requestBody: { - content: { - "application/json": { - /** @description Contact support@unkey.dev to receive your migration id. */ - migrationId: string; - /** @description The id of the api, you want to migrate keys to */ - apiId: string; - keys: { - /** - * @description To make it easier for your users to understand which product an api key belongs to, you can add prefix them. - * - * For example Stripe famously prefixes their customer ids with cus_ or their api keys with sk_live_. - * - * The underscore is automatically added if you are defining a prefix, for example: "prefix": "abc" will result in a key like abc_xxxxxxxxx + * @description To make it easier for your users to understand which product an api key belongs to, you can add prefix them. + * + * For example Stripe famously prefixes their customer ids with cus_ or their api keys with sk_live_. + * + * The underscore is automatically added if you are defining a prefix, for example: "prefix": "abc" will result in a key like abc_xxxxxxxxx */ prefix?: string; /** @@ -2848,43 +2619,39 @@ export interface operations { amount: number; }; /** - * @description Unkey comes with per-key fixed-window ratelimiting out of the box. + * @description Unkey comes with per-key ratelimiting out of the box. * @example { * "type": "fast", * "limit": 10, - * "duration": 60000 + * "refillRate": 1, + * "refillInterval": 60 * } */ ratelimit?: { /** * @description Async will return a response immediately, lowering latency at the cost of accuracy. - * @default true + * @default false */ async?: boolean; /** * @deprecated - * @description Deprecated, use `async`. Fast ratelimiting doesn't add latency, while consistent ratelimiting is more accurate. + * @description Fast ratelimiting doesn't add latency, while consistent ratelimiting is more accurate. * @default fast * @enum {string} */ type?: "fast" | "consistent"; - /** @description The total amount of requests in a given interval. */ + /** @description The total amount of burstable requests. */ limit: number; - /** - * @description The window duration in milliseconds - * @example 60000 - */ - duration: number; /** * @deprecated * @description How many tokens to refill during each refillInterval. */ - refillRate?: number; + refillRate: number; /** * @deprecated - * @description The refill timeframe, in milliseconds. + * @description Determines the speed at which tokens are refilled, in milliseconds. */ - refillInterval?: number; + refillInterval: number; }; /** * @description Sets if key is enabled or disabled. Disabled keys are not valid. @@ -2903,7 +2670,223 @@ export interface operations { * handle it correctly. */ environment?: string; - }[]; + })[]; + }; + }; + responses: { + /** @description The key ids of all created keys */ + 200: { + content: { + "application/json": { + /** + * @description The ids of the keys. This is not a secret and can be stored as a reference if you wish. You need the keyId to update or delete a key later. + * @example [ + * "key_123", + * "key_456" + * ] + */ + keyIds: string[]; + }; + }; + }; + /** @description The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). */ + 400: { + content: { + "application/json": components["schemas"]["ErrBadRequest"]; + }; + }; + /** @description Although the HTTP standard specifies "unauthorized", semantically this response means "unauthenticated". That is, the client must authenticate itself to get the requested response. */ + 401: { + content: { + "application/json": components["schemas"]["ErrUnauthorized"]; + }; + }; + /** @description The client does not have access rights to the content; that is, it is unauthorized, so the server is refusing to give the requested resource. Unlike 401 Unauthorized, the client's identity is known to the server. */ + 403: { + content: { + "application/json": components["schemas"]["ErrForbidden"]; + }; + }; + /** @description The server cannot find the requested resource. In the browser, this means the URL is not recognized. In an API, this can also mean that the endpoint is valid but the resource itself does not exist. Servers may also send this response instead of 403 Forbidden to hide the existence of a resource from an unauthorized client. This response code is probably the most well known due to its frequent occurrence on the web. */ + 404: { + content: { + "application/json": components["schemas"]["ErrNotFound"]; + }; + }; + /** @description This response is sent when a request conflicts with the current state of the server. */ + 409: { + content: { + "application/json": components["schemas"]["ErrConflict"]; + }; + }; + /** @description The user has sent too many requests in a given amount of time ("rate limiting") */ + 429: { + content: { + "application/json": components["schemas"]["ErrTooManyRequests"]; + }; + }; + /** @description The server has encountered a situation it does not know how to handle. */ + 500: { + content: { + "application/json": components["schemas"]["ErrInternalServerError"]; + }; + }; + }; + }; + "v1.migrations.enqueueKeys": { + requestBody: { + content: { + "application/json": { + /** @description Contact support@unkey.dev to receive your migration id. */ + migrationId: string; + /** @description The id of the api, you want to migrate keys to */ + apiId: string; + keys: ({ + /** + * @description To make it easier for your users to understand which product an api key belongs to, you can add prefix them. + * + * For example Stripe famously prefixes their customer ids with cus_ or their api keys with sk_live_. + * + * The underscore is automatically added if you are defining a prefix, for example: "prefix": "abc" will result in a key like abc_xxxxxxxxx + */ + prefix?: string; + /** + * @description The name for your Key. This is not customer facing. + * @example my key + */ + name?: string; + /** @description The raw key in plaintext. If provided, unkey encrypts this value and stores it securely. Provide either `hash` or `plaintext` */ + plaintext?: string; + /** @description Provide either `hash` or `plaintext` */ + hash?: { + /** @description The hashed and encoded key */ + value: string; + /** + * @description The algorithm for hashing and encoding, currently only sha256 and base64 are supported + * @enum {string} + */ + variant: "sha256_base64"; + }; + /** + * @description The first 4 characters of the key. If a prefix is used, it should be the prefix plus 4 characters. + * @example unkey_32kq + */ + start?: string; + /** + * @description Your user’s Id. This will provide a link between Unkey and your customer record. + * When validating a key, we will return this back to you, so you can clearly identify your user from their api key. + * @example team_123 + */ + ownerId?: string; + /** + * @description This is a place for dynamic meta data, anything that feels useful for you should go here + * @example { + * "billingTier": "PRO", + * "trialEnds": "2023-06-16T17:16:37.161Z" + * } + */ + meta?: { + [key: string]: unknown; + }; + /** + * @description A list of roles that this key should have. If the role does not exist, an error is thrown + * @example [ + * "admin", + * "finance" + * ] + */ + roles?: string[]; + /** + * @description A list of permissions that this key should have. If the permission does not exist, an error is thrown + * @example [ + * "domains.create_record", + * "say_hello" + * ] + */ + permissions?: string[]; + /** + * @description You can auto expire keys by providing a unix timestamp in milliseconds. Once Keys expire they will automatically be disabled and are no longer valid unless you enable them again. + * @example 1623869797161 + */ + expires?: number; + /** + * @description You can limit the number of requests a key can make. Once a key reaches 0 remaining requests, it will automatically be disabled and is no longer valid unless you update it. + * @example 1000 + */ + remaining?: number; + /** + * @description Unkey enables you to refill verifications for each key at regular intervals. + * @example { + * "interval": "daily", + * "amount": 100 + * } + */ + refill?: { + /** + * @description Unkey will automatically refill verifications at the set interval. + * @enum {string} + */ + interval: "daily" | "monthly"; + /** @description The number of verifications to refill for each occurrence is determined individually for each key. */ + amount: number; + }; + /** + * @description Unkey comes with per-key fixed-window ratelimiting out of the box. + * @example { + * "type": "fast", + * "limit": 10, + * "duration": 60000 + * } + */ + ratelimit?: { + /** + * @description Async will return a response immediately, lowering latency at the cost of accuracy. + * @default true + */ + async?: boolean; + /** + * @deprecated + * @description Deprecated, use `async`. Fast ratelimiting doesn't add latency, while consistent ratelimiting is more accurate. + * @default fast + * @enum {string} + */ + type?: "fast" | "consistent"; + /** @description The total amount of requests in a given interval. */ + limit: number; + /** + * @description The window duration in milliseconds + * @example 60000 + */ + duration: number; + /** + * @deprecated + * @description How many tokens to refill during each refillInterval. + */ + refillRate?: number; + /** + * @deprecated + * @description The refill timeframe, in milliseconds. + */ + refillInterval?: number; + }; + /** + * @description Sets if key is enabled or disabled. Disabled keys are not valid. + * @default true + * @example false + */ + enabled?: boolean; + /** + * @description Environments allow you to divide your keyspace. + * + * Some applications like Stripe, Clerk, WorkOS and others have a concept of "live" and "test" keys to + * give the developer a way to develop their own application without the risk of modifying real world + * resources. + * + * When you set an environment, we will return it back to you when validating the key, so you can + * handle it correctly. + */ + environment?: string; + })[]; }; }; }; @@ -3174,22 +3157,22 @@ export interface operations { 200: { content: { "application/json": { - /** - * @description The id of the permission - * @example perm_123 - */ - id: string; - /** - * @description The name of the permission. - * @example domain.record.manager - */ - name: string; - /** - * @description The description of what this permission does. This is just for your team, your users will not see this. - * @example Can manage dns records - */ - description?: string; - }[]; + /** + * @description The id of the permission + * @example perm_123 + */ + id: string; + /** + * @description The name of the permission. + * @example domain.record.manager + */ + name: string; + /** + * @description The description of what this permission does. This is just for your team, your users will not see this. + * @example Can manage dns records + */ + description?: string; + }[]; }; }; /** @description The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). */ @@ -3452,22 +3435,22 @@ export interface operations { 200: { content: { "application/json": { - /** - * @description The id of the role - * @example role_1234 - */ - id: string; - /** - * @description The name of the role. - * @example domain.record.manager - */ - name: string; - /** - * @description The description of what this role does. This is just for your team, your users will not see this. - * @example Can manage dns records - */ - description?: string; - }[]; + /** + * @description The id of the role + * @example role_1234 + */ + id: string; + /** + * @description The name of the role. + * @example domain.record.manager + */ + name: string; + /** + * @description The description of what this role does. This is just for your team, your users will not see this. + * @example Can manage dns records + */ + description?: string; + }[]; }; }; /** @description The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). */ @@ -3541,22 +3524,22 @@ export interface operations { * When verifying keys, you can specify which limits you want to use and all keys attached to this identity, will share the limits. */ ratelimits?: { - /** - * @description The name of this limit. You will need to use this again when verifying a key. - * @example tokens - */ - name: string; - /** - * @description How many requests may pass within a given window before requests are rejected. - * @example 10 - */ - limit: number; - /** - * @description The duration for each ratelimit window in milliseconds. - * @example 1000 - */ - duration: number; - }[]; + /** + * @description The name of this limit. You will need to use this again when verifying a key. + * @example tokens + */ + name: string; + /** + * @description How many requests may pass within a given window before requests are rejected. + * @example 10 + */ + limit: number; + /** + * @description The duration for each ratelimit window in milliseconds. + * @example 1000 + */ + duration: number; + }[]; }; }; }; @@ -3639,22 +3622,22 @@ export interface operations { }; /** @description When verifying keys, you can specify which limits you want to use and all keys attached to this identity, will share the limits. */ ratelimits: { - /** - * @description The name of this limit. You will need to use this again when verifying a key. - * @example tokens - */ - name: string; - /** - * @description How many requests may pass within a given window before requests are rejected. - * @example 10 - */ - limit: number; - /** - * @description The duration for each ratelimit window in milliseconds. - * @example 1000 - */ - duration: number; - }[]; + /** + * @description The name of this limit. You will need to use this again when verifying a key. + * @example tokens + */ + name: string; + /** + * @description How many requests may pass within a given window before requests are rejected. + * @example 10 + */ + limit: number; + /** + * @description The duration for each ratelimit window in milliseconds. + * @example 1000 + */ + duration: number; + }[]; }; }; }; @@ -3717,29 +3700,29 @@ export interface operations { "application/json": { /** @description A list of identities. */ identities: { - /** @description The id of this identity. Used to interact with unkey's API */ - id: string; - /** @description The id in your system */ - externalId: string; - /** @description When verifying keys, you can specify which limits you want to use and all keys attached to this identity, will share the limits. */ - ratelimits: { - /** - * @description The name of this limit. You will need to use this again when verifying a key. - * @example tokens - */ - name: string; - /** - * @description How many requests may pass within a given window before requests are rejected. - * @example 10 - */ - limit: number; - /** - * @description The duration for each ratelimit window in milliseconds. - * @example 1000 - */ - duration: number; + /** @description The id of this identity. Used to interact with unkey's API */ + id: string; + /** @description The id in your system */ + externalId: string; + /** @description When verifying keys, you can specify which limits you want to use and all keys attached to this identity, will share the limits. */ + ratelimits: { + /** + * @description The name of this limit. You will need to use this again when verifying a key. + * @example tokens + */ + name: string; + /** + * @description How many requests may pass within a given window before requests are rejected. + * @example 10 + */ + limit: number; + /** + * @description The duration for each ratelimit window in milliseconds. + * @example 1000 + */ + duration: number; + }[]; }[]; - }[]; /** * @description The cursor to use for the next page of results, if no cursor is returned, there are no more results * @example eyJrZXkiOiJrZXlfMTIzNCJ9 @@ -3832,22 +3815,22 @@ export interface operations { * When verifying keys, you can specify which limits you want to use and all keys attached to this identity, will share the limits. */ ratelimits?: { - /** - * @description The name of this limit. You will need to use this again when verifying a key. - * @example tokens - */ - name: string; - /** - * @description How many requests may pass within a given window before requests are rejected. - * @example 10 - */ - limit: number; - /** - * @description The duration for each ratelimit window in milliseconds. - * @example 1000 - */ - duration: number; - }[]; + /** + * @description The name of this limit. You will need to use this again when verifying a key. + * @example tokens + */ + name: string; + /** + * @description How many requests may pass within a given window before requests are rejected. + * @example 10 + */ + limit: number; + /** + * @description The duration for each ratelimit window in milliseconds. + * @example 1000 + */ + duration: number; + }[]; }; }; }; @@ -3876,22 +3859,22 @@ export interface operations { [key: string]: unknown; }; ratelimits: { - /** - * @description The name of this limit. - * @example tokens - */ - name: string; - /** - * @description How many requests may pass within a given window before requests are rejected. - * @example 10 - */ - limit: number; - /** - * @description The duration for each ratelimit window in milliseconds. - * @example 1000 - */ - duration: number; - }[]; + /** + * @description The name of this limit. + * @example tokens + */ + name: string; + /** + * @description How many requests may pass within a given window before requests are rejected. + * @example 10 + */ + limit: number; + /** + * @description The duration for each ratelimit window in milliseconds. + * @example 1000 + */ + duration: number; + }[]; }; }; }; @@ -4253,15 +4236,7 @@ export interface operations { * @example NOT_FOUND * @enum {string} */ - code?: - | "NOT_FOUND" - | "FORBIDDEN" - | "USAGE_EXCEEDED" - | "RATE_LIMITED" - | "UNAUTHORIZED" - | "DISABLED" - | "INSUFFICIENT_PERMISSIONS" - | "EXPIRED"; + code?: "NOT_FOUND" | "FORBIDDEN" | "USAGE_EXCEEDED" | "RATE_LIMITED" | "UNAUTHORIZED" | "DISABLED" | "INSUFFICIENT_PERMISSIONS" | "EXPIRED"; }; }; }; From 17e48c1b61cc620bd3f4594e8139041e7f37f10c Mon Sep 17 00:00:00 2001 From: Michael Silva Date: Thu, 19 Sep 2024 09:25:40 -0400 Subject: [PATCH 02/29] First draft refill route changes --- apps/api/src/pkg/key_migration/handler.ts | 1 + apps/api/src/pkg/key_migration/message.ts | 2 +- apps/api/src/routes/v1_apis_listKeys.ts | 1 + apps/api/src/routes/v1_keys_getKey.ts | 2 +- .../[apiId]/keys/[keyAuthId]/new/client.tsx | 8 +- apps/dashboard/lib/trpc/routers/key/create.ts | 1 - .../lib/trpc/routers/key/createRootKey.ts | 1 + apps/dashboard/lib/zod-helper.ts | 14 ++ apps/workflows/jobs/refill-daily.ts | 76 +++++++++-- internal/db/src/schema/key_migrations.ts | 2 +- packages/api/src/openapi.d.ts | 11 +- pnpm-lock.yaml | 122 +++++++----------- 12 files changed, 154 insertions(+), 87 deletions(-) diff --git a/apps/api/src/pkg/key_migration/handler.ts b/apps/api/src/pkg/key_migration/handler.ts index 099058c7ad..9be94dbcd7 100644 --- a/apps/api/src/pkg/key_migration/handler.ts +++ b/apps/api/src/pkg/key_migration/handler.ts @@ -96,6 +96,7 @@ export async function migrateKey( expires: message.expires ? new Date(message.expires) : null, refillInterval: message.refill?.interval, refillAmount: message.refill?.amount, + refillDay: message.refill?.dayOfMonth, enabled: message.enabled, remaining: message.remaining, ratelimitAsync: message.ratelimit?.async, diff --git a/apps/api/src/pkg/key_migration/message.ts b/apps/api/src/pkg/key_migration/message.ts index 33cd34c509..aa5e2bdbf7 100644 --- a/apps/api/src/pkg/key_migration/message.ts +++ b/apps/api/src/pkg/key_migration/message.ts @@ -14,7 +14,7 @@ export type MessageBody = { permissions?: string[]; expires?: number; remaining?: number; - refill?: { interval: "daily" | "monthly"; amount: number }; + refill?: { interval: "daily" | "monthly"; amount: number; dayOfMonth?: number }; ratelimit?: { async: boolean; limit: number; duration: number }; enabled: boolean; environment?: string; diff --git a/apps/api/src/routes/v1_apis_listKeys.ts b/apps/api/src/routes/v1_apis_listKeys.ts index c7c7b51f56..da480c94d0 100644 --- a/apps/api/src/routes/v1_apis_listKeys.ts +++ b/apps/api/src/routes/v1_apis_listKeys.ts @@ -317,6 +317,7 @@ export const registerV1ApisListKeys = (app: App) => ? { interval: k.refillInterval, amount: k.refillAmount, + dayOfMonth: k.refillInterval === "monthly" ? k.refillDay : null, lastRefillAt: k.lastRefillAt?.getTime(), } : undefined, diff --git a/apps/api/src/routes/v1_keys_getKey.ts b/apps/api/src/routes/v1_keys_getKey.ts index e95d316de7..6a6c3bb06b 100644 --- a/apps/api/src/routes/v1_keys_getKey.ts +++ b/apps/api/src/routes/v1_keys_getKey.ts @@ -155,7 +155,7 @@ export const registerV1KeysGetKey = (app: App) => ? { interval: key.refillInterval, amount: key.refillAmount, - dayOfMonth: key.refillDay, + dayOfMonth: key.refillInterval === "monthly" ? key.refillDay : null, lastRefillAt: key.lastRefillAt?.getTime(), } : undefined, diff --git a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/new/client.tsx b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/new/client.tsx index 72b83306b3..0449d4caba 100644 --- a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/new/client.tsx +++ b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/new/client.tsx @@ -209,7 +209,7 @@ export const CreateKey: React.FC = ({ apiId, keyAuthId }) => { if (!values.ratelimitEnabled) { delete values.ratelimit; } - + await key.mutateAsync({ keyAuthId, ...values, @@ -217,7 +217,13 @@ export const CreateKey: React.FC = ({ apiId, keyAuthId }) => { expires: values.expires?.getTime() ?? undefined, ownerId: values.ownerId ?? undefined, remaining: values.limit?.remaining ?? undefined, + refill: values?.limit?.refill?.amount !== undefined && (values.limit?.refill?.interval === "daily" || values.limit?.refill?.interval === "monthly") ? { + interval: values.limit?.refill?.interval, + amount: values.limit.refill.amount, + dayOfMonth: values.limit.refill.dayOfMonth ?? undefined, + } : undefined, enabled: true, + }); router.refresh(); diff --git a/apps/dashboard/lib/trpc/routers/key/create.ts b/apps/dashboard/lib/trpc/routers/key/create.ts index ed6e4fe835..cc55e45ebe 100644 --- a/apps/dashboard/lib/trpc/routers/key/create.ts +++ b/apps/dashboard/lib/trpc/routers/key/create.ts @@ -23,7 +23,6 @@ export const createKey = t.procedure dayOfMonth: z.number().int().min(1).max(31).default(1), }) .optional(), - expires: z.number().int().nullish(), // unix timestamp in milliseconds name: z.string().optional(), ratelimit: z diff --git a/apps/dashboard/lib/trpc/routers/key/createRootKey.ts b/apps/dashboard/lib/trpc/routers/key/createRootKey.ts index f6aaa08a93..9a8f96333b 100644 --- a/apps/dashboard/lib/trpc/routers/key/createRootKey.ts +++ b/apps/dashboard/lib/trpc/routers/key/createRootKey.ts @@ -75,6 +75,7 @@ export const createRootKey = t.procedure remaining: null, refillInterval: null, refillAmount: null, + refillDay: null, lastRefillAt: null, deletedAt: null, enabled: true, diff --git a/apps/dashboard/lib/zod-helper.ts b/apps/dashboard/lib/zod-helper.ts index 0d08868d4a..7c56074e1f 100644 --- a/apps/dashboard/lib/zod-helper.ts +++ b/apps/dashboard/lib/zod-helper.ts @@ -44,6 +44,20 @@ export const formSchema = z.object({ .int() .min(1) .positive(), + dayOfMonth: z.coerce + .number({ + errorMap: (issue, { defaultError }) => ({ + message: + issue.code === "invalid_type" + ? "Refill day must be greater than 0 and a integer 31 or less" + : defaultError, + }), + }) + .int() + .min(1) + .max(31) + .positive() + .optional(), }) .optional(), }) diff --git a/apps/workflows/jobs/refill-daily.ts b/apps/workflows/jobs/refill-daily.ts index 21b319abd0..6808011517 100644 --- a/apps/workflows/jobs/refill-daily.ts +++ b/apps/workflows/jobs/refill-daily.ts @@ -1,4 +1,4 @@ -import { connectDatabase, eq, lte, schema } from "@/lib/db"; +import { connectDatabase, eq, lte, schema, gt } from "@/lib/db"; import { env } from "@/lib/env"; import { Tinybird } from "@/lib/tinybird"; import { client } from "@/trigger"; @@ -13,12 +13,16 @@ client.defineJob({ }), run: async (_payload, io, _ctx) => { + const date = new Date(Date.now()); + const today = date.getDate(); const db = connectDatabase(); const tb = new Tinybird(env().TINYBIRD_TOKEN); - const t = new Date(); - t.setUTCHours(t.getUTCHours() - 24); + const tDay = new Date(); + const tMonth = new Date(); + tDay.setUTCHours(tDay.getUTCHours() - 24); + tMonth.setUTCMonth(tMonth.getUTCMonth() - 1); - const keys = await io.runTask("list keys", () => + const dailyKeys = await io.runTask("list keys Daily", () => db.query.keys.findMany({ where: (table, { isNotNull, isNull, eq, and, gt, or }) => and( @@ -29,14 +33,32 @@ client.defineJob({ gt(table.refillAmount, table.remaining), or( isNull(table.lastRefillAt), - lte(table.lastRefillAt, t), // Check if more than 24 hours have passed + lte(table.lastRefillAt, tDay), // Check if more than 24 hours have passed ), ), }), ); - io.logger.info(`found ${keys.length} keys with daily refill set`); + const monthlyKeys = await io.runTask("list keys Monthly", () => + db.query.keys.findMany({ + where: (table, { isNotNull, isNull, eq, and, or }) => + and( + isNull(table.deletedAt), + isNotNull(table.refillInterval), + isNotNull(table.refillAmount), + eq(table.refillInterval, "monthly"), + eq(table.refillDay, today), + gt(table.refillAmount, table.remaining), + or( + isNull(table.lastRefillAt), + lte(table.lastRefillAt, tMonth), // Check if more than 1 Month has passed + ), + ), + }), + ); + io.logger.info(`found ${dailyKeys.length} keys with daily refill set`); + io.logger.info(`found ${monthlyKeys.length} keys with monthly refill set`); // const keysWithRefill = keys.length; - for (const key of keys) { + for (const key of dailyKeys) { await io.runTask(`refill for ${key.id}`, async () => { await db .update(schema.keys) @@ -72,8 +94,46 @@ client.defineJob({ }); }); } + for (const key of monthlyKeys) { + await io.runTask(`refill for ${key.id}`, async () => { + await db + .update(schema.keys) + .set({ + remaining: key.refillAmount, + lastRefillAt: new Date(), + }) + .where(eq(schema.keys.id, key.id)); + }); + await io.runTask(`create audit log refilling ${key.id}`, async () => { + await tb.ingestAuditLogs({ + workspaceId: key.workspaceId, + event: "key.update", + actor: { + type: "system", + id: "trigger", + }, + description: `Refilled ${key.id} to ${key.refillAmount}`, + resources: [ + { + type: "workspace", + id: key.workspaceId, + }, + { + type: "key", + id: key.id, + }, + ], + context: { + location: "trigger", + }, + }); + }); + } + return { - keyIds: keys.map((k) => k.id), + daileyKeyIds: dailyKeys.map((k) => k.id), + monthlyKeyIds: monthlyKeys.map((k) => k.id) }; + }, }); diff --git a/internal/db/src/schema/key_migrations.ts b/internal/db/src/schema/key_migrations.ts index 3d12548dc5..02a13e3af3 100644 --- a/internal/db/src/schema/key_migrations.ts +++ b/internal/db/src/schema/key_migrations.ts @@ -30,7 +30,7 @@ export const keyMigrationErrors = mysqlTable("key_migration_errors", { permissions?: string[]; expires?: number; remaining?: number; - refill?: { interval: "daily" | "monthly"; amount: number }; + refill?: { interval: "daily" | "monthly"; amount: number; dayOfMonth: number }; ratelimit?: { async: boolean; limit: number; duration: number }; enabled: boolean; environment?: string; diff --git a/packages/api/src/openapi.d.ts b/packages/api/src/openapi.d.ts index d4a8918768..94a7894477 100644 --- a/packages/api/src/openapi.d.ts +++ b/packages/api/src/openapi.d.ts @@ -352,8 +352,9 @@ export interface components { /** * @description Unkey allows you to refill remaining verifications on a key on a regular interval. * @example { - * "interval": "daily", - * "amount": 10 + * "interval": "monthly", + * "amount": 100, + * "dayOfMonth":15, * } */ refill?: { @@ -372,6 +373,11 @@ export interface components { * @description The unix timestamp in miliseconds when the key was last refilled. * @example 100 */ + dayOfMonth: number; + /** + * @description the day each month refill triggers if interval is 'monthly'. + * @example 20 + */ lastRefillAt?: number; }; /** @@ -1217,6 +1223,7 @@ export interface operations { interval: "daily" | "monthly"; /** @description The amount of verifications to refill for each occurrence is determined individually for each key. */ amount: number; + dayOfMonth: number; }) | null; /** * @description Set if key is enabled or disabled. If disabled, the key cannot be used to verify. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ab3167927b..95cca0ba2c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1223,7 +1223,7 @@ importers: devDependencies: checkly: specifier: latest - version: 4.9.0(@types/node@20.14.9)(typescript@5.5.3) + version: 4.6.3(@types/node@20.14.9)(typescript@5.5.3) ts-node: specifier: 10.9.1 version: 10.9.1(@types/node@20.14.9)(typescript@5.5.3) @@ -6080,24 +6080,35 @@ packages: - typescript dev: true - /@oclif/core@4.0.19: - resolution: {integrity: sha512-VXnsYNVfmucXp5BdOA/OcWi8F/h2h8ofW1GxQDdspodnmnUgALEpqrxXBl5NFuA+iEihtAJeXzX260ICHYDaBg==} + /@oclif/core@3.27.0: + resolution: {integrity: sha512-Fg93aNFvXzBq5L7ztVHFP2nYwWU1oTCq48G0TjF/qC1UN36KWa2H5Hsm72kERd5x/sjy2M2Tn4kDEorUlpXOlw==} engines: {node: '>=18.0.0'} dependencies: + '@types/cli-progress': 3.11.6 ansi-escapes: 4.3.2 - ansis: 3.3.2 + ansi-styles: 4.3.0 + cardinal: 2.1.1 + chalk: 4.1.2 clean-stack: 3.0.1 - cli-spinners: 2.9.2 + cli-progress: 3.12.0 + color: 4.2.3 debug: 4.3.6(supports-color@8.1.1) ejs: 3.1.10 get-package-type: 0.1.0 globby: 11.1.0 + hyperlinker: 1.0.0 indent-string: 4.0.0 is-wsl: 2.2.0 - lilconfig: 3.1.2 + js-yaml: 3.14.1 minimatch: 9.0.5 + natural-orderby: 2.0.3 + object-treeify: 1.1.33 + password-prompt: 1.1.3 + slice-ansi: 4.0.0 string-width: 4.2.3 + strip-ansi: 6.0.1 supports-color: 8.1.1 + supports-hyperlinks: 2.3.0 widest-line: 3.1.0 wordwrap: 1.0.0 wrap-ansi: 7.0.0 @@ -6129,20 +6140,18 @@ packages: - typescript dev: true - /@oclif/plugin-plugins@5.4.4: - resolution: {integrity: sha512-p30fo3JPtbOqTJOX9A/8qKV/14XWt8xFgG/goVfIkuKBAO+cdY78ag8pYatlpzsYzJhO27X1MFn0WkkPWo36Ww==} + /@oclif/plugin-plugins@4.1.12: + resolution: {integrity: sha512-lYNoqoQJz+p4AOMZ9N5k7OkLk/HZJSdaybJ4rWfDZ76pQ7AcrLEPtREi2wLfcwcrzKoBMsrwBoMTf3PnmpW7ZQ==} engines: {node: '>=18.0.0'} dependencies: - '@oclif/core': 4.0.19 - ansis: 3.3.2 + '@oclif/core': 3.27.0 + chalk: 5.3.0 debug: 4.3.6(supports-color@8.1.1) - npm: 10.8.3 - npm-package-arg: 11.0.3 - npm-run-path: 5.3.0 - object-treeify: 4.0.1 + npm: 10.2.3 + npm-run-path: 4.0.1 semver: 7.6.3 + shelljs: 0.8.5 validate-npm-package-name: 5.0.1 - which: 4.0.0 yarn: 1.22.22 transitivePeerDependencies: - supports-color @@ -12552,11 +12561,6 @@ packages: resolution: {integrity: sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==} dev: true - /ansis@3.3.2: - resolution: {integrity: sha512-cFthbBlt+Oi0i9Pv/j6YdVWJh54CtjGACaMPCIrEV4Ha7HWsIjXDwseYV79TIL0B4+KfSwD5S70PeQDkPUd1rA==} - engines: {node: '>=15'} - dev: true - /any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} @@ -12689,6 +12693,11 @@ packages: tslib: 2.7.0 dev: true + /astral-regex@2.0.0: + resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} + engines: {node: '>=8'} + dev: true + /astring@1.9.0: resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} hasBin: true @@ -12761,8 +12770,8 @@ packages: resolution: {integrity: sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==} engines: {node: '>= 6.0.0'} - /axios@1.7.4: - resolution: {integrity: sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==} + /axios@1.6.2: + resolution: {integrity: sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==} dependencies: follow-redirects: 1.15.6 form-data: 4.0.0 @@ -13152,21 +13161,21 @@ packages: get-func-name: 2.0.2 dev: true - /checkly@4.9.0(@types/node@20.14.9)(typescript@5.5.3): - resolution: {integrity: sha512-LqohEntErF7dJaJPsEpjvr/O9wUfzBRac6DOXgFDMEw+dNi19oBAcspdOqVGjPjMoCZ9/s5b5tSJI1pusY4mJQ==} + /checkly@4.6.3(@types/node@20.14.9)(typescript@5.5.3): + resolution: {integrity: sha512-xWYkHcgqrN8bjjV3CJEPD/S5VU/qW3TMshgQAJrPdOuy8YkwCKUCIBajuFqH9e9HQdCFYIROwFhahmPdMAZnJQ==} engines: {node: '>=16.0.0'} hasBin: true dependencies: '@oclif/core': 2.8.11(@types/node@20.14.9)(typescript@5.5.3) '@oclif/plugin-help': 5.1.20 '@oclif/plugin-not-found': 2.3.23(@types/node@20.14.9)(typescript@5.5.3) - '@oclif/plugin-plugins': 5.4.4 + '@oclif/plugin-plugins': 4.1.12 '@oclif/plugin-warn-if-update-available': 2.0.24(@types/node@20.14.9)(typescript@5.5.3) '@typescript-eslint/typescript-estree': 6.19.0(typescript@5.5.3) acorn: 8.8.1 acorn-walk: 8.2.0 async-mqtt: 2.6.3 - axios: 1.7.4 + axios: 1.6.2 chalk: 4.1.2 ci-info: 3.8.0 conf: 10.2.0 @@ -16894,13 +16903,6 @@ packages: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} dev: true - /hosted-git-info@7.0.2: - resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} - engines: {node: ^16.14.0 || >=18.0.0} - dependencies: - lru-cache: 10.4.3 - dev: true - /html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} dev: false @@ -17162,7 +17164,6 @@ packages: /interpret@1.4.0: resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==} engines: {node: '>= 0.10'} - dev: false /invariant@2.2.4: resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} @@ -17554,11 +17555,6 @@ packages: /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - /isexe@3.1.1: - resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} - engines: {node: '>=16'} - dev: true - /isomorphic-fetch@3.0.0: resolution: {integrity: sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==} dependencies: @@ -20123,16 +20119,6 @@ packages: engines: {node: '>=14.16'} dev: true - /npm-package-arg@11.0.3: - resolution: {integrity: sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==} - engines: {node: ^16.14.0 || >=18.0.0} - dependencies: - hosted-git-info: 7.0.2 - proc-log: 4.2.0 - semver: 7.6.3 - validate-npm-package-name: 5.0.1 - dev: true - /npm-run-path@4.0.1: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} @@ -20151,8 +20137,8 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: false - /npm@10.8.3: - resolution: {integrity: sha512-0IQlyAYvVtQ7uOhDFYZCGK8kkut2nh8cpAdA9E6FvRSJaTgtZRZgNjlC5ZCct//L73ygrpY93CxXpRJDtNqPVg==} + /npm@10.2.3: + resolution: {integrity: sha512-GbUui/rHTl0mW8HhJSn4A0Xg89yCR3I9otgJT1i0z1QBPOVlgbh6rlcUTpHT8Gut9O1SJjWRUU0nEcAymhG2tQ==} engines: {node: ^18.17.0 || >=20.5.0} hasBin: true dev: true @@ -20164,7 +20150,6 @@ packages: - '@npmcli/map-workspaces' - '@npmcli/package-json' - '@npmcli/promise-spawn' - - '@npmcli/redact' - '@npmcli/run-script' - '@sigstore/tuf' - abbrev @@ -20173,6 +20158,8 @@ packages: - chalk - ci-info - cli-columns + - cli-table3 + - columnify - fastest-levenshtein - fs-minipass - glob @@ -20208,6 +20195,7 @@ packages: - npm-profile - npm-registry-fetch - npm-user-validate + - npmlog - p-map - pacote - parse-conflict-json @@ -20217,6 +20205,7 @@ packages: - semver - spdx-expression-parse - ssri + - strip-ansi - supports-color - tar - text-table @@ -20292,11 +20281,6 @@ packages: engines: {node: '>= 10'} dev: true - /object-treeify@4.0.1: - resolution: {integrity: sha512-Y6tg5rHfsefSkfKujv2SwHulInROy/rCL5F4w0QOWxut8AnxYxf0YmNhTh95Zfyxpsudo66uqkux0ACFnyMSgQ==} - engines: {node: '>= 16'} - dev: true - /object-values@1.0.0: resolution: {integrity: sha512-+8hwcz/JnQ9EpLIXzN0Rs7DLsBpJNT/xYehtB/jU93tHYr5BFEO8E+JGQNOSqE7opVzz5cGksKFHt7uUJVLSjQ==} engines: {node: '>=0.10.0'} @@ -21048,11 +21032,6 @@ packages: '@probe.gl/stats': 3.6.0 dev: false - /proc-log@4.2.0: - resolution: {integrity: sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - dev: true - /process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} dev: true @@ -21804,7 +21783,6 @@ packages: engines: {node: '>= 0.10'} dependencies: resolve: 1.22.8 - dev: false /redent@3.0.0: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} @@ -22657,7 +22635,6 @@ packages: glob: 7.2.3 interpret: 1.4.0 rechoir: 0.6.2 - dev: false /shiki@0.14.7: resolution: {integrity: sha512-dNPAPrxSc87ua2sKJ3H5dQ/6ZaY8RNnaAqK+t0eG7p0Soi2ydiqbGOTaZCqaYvA/uZYfS1LJnemt3Q+mSfcPCg==} @@ -22745,6 +22722,15 @@ packages: engines: {node: '>=8'} dev: true + /slice-ansi@4.0.0: + resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + astral-regex: 2.0.0 + is-fullwidth-code-point: 3.0.0 + dev: true + /slice-ansi@5.0.0: resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} engines: {node: '>=12'} @@ -25128,14 +25114,6 @@ packages: dependencies: isexe: 2.0.0 - /which@4.0.0: - resolution: {integrity: sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==} - engines: {node: ^16.13.0 || >=18.0.0} - hasBin: true - dependencies: - isexe: 3.1.1 - dev: true - /why-is-node-running@2.3.0: resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} From b14c926551818b8b97fd5f7e247c5af3bcf57ebb Mon Sep 17 00:00:00 2001 From: Michael Silva Date: Thu, 19 Sep 2024 09:26:33 -0400 Subject: [PATCH 03/29] fmt --- apps/api/src/routes/schema.ts | 3 +- apps/api/src/routes/v1_keys_createKey.ts | 5 +- .../[keyId]/settings/update-key-remaining.tsx | 3 +- .../[apiId]/keys/[keyAuthId]/new/client.tsx | 45 +- .../lib/trpc/routers/key/updateRemaining.ts | 1 - apps/workflows/jobs/refill-daily.ts | 7 +- packages/api/src/openapi.d.ts | 1135 +++++++++-------- 7 files changed, 612 insertions(+), 587 deletions(-) diff --git a/apps/api/src/routes/schema.ts b/apps/api/src/routes/schema.ts index 1756eb3d68..283395c3a9 100644 --- a/apps/api/src/routes/schema.ts +++ b/apps/api/src/routes/schema.ts @@ -83,10 +83,9 @@ export const keySchema = z interval: "monthly", amount: 10, dayOfMonth: 10, - }, }), - + ratelimit: z .object({ async: z.boolean().openapi({ diff --git a/apps/api/src/routes/v1_keys_createKey.ts b/apps/api/src/routes/v1_keys_createKey.ts index a1de41a79e..d7dfe93751 100644 --- a/apps/api/src/routes/v1_keys_createKey.ts +++ b/apps/api/src/routes/v1_keys_createKey.ts @@ -129,11 +129,10 @@ When validating a key, we will return this back to you, so you can clearly ident example: { interval: "monthly", amount: 100, - dayOfMonth: 15 + dayOfMonth: 15, }, }), - - + ratelimit: z .object({ async: z diff --git a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/update-key-remaining.tsx b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/update-key-remaining.tsx index 575bbb02ab..78cd53bfce 100644 --- a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/update-key-remaining.tsx +++ b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/update-key-remaining.tsx @@ -93,7 +93,8 @@ export const UpdateKeyRemaining: React.FC = ({ apiKey }) => { refill: { interval: apiKey.refillInterval === null ? "none" : apiKey.refillInterval, amount: apiKey.refillAmount ? apiKey.refillAmount : undefined, - dayOfMonth: apiKey.refillInterval === "monthly" && apiKey.refillDay ? apiKey.refillDay : undefined, + dayOfMonth: + apiKey.refillInterval === "monthly" && apiKey.refillDay ? apiKey.refillDay : undefined, }, }, }); diff --git a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/new/client.tsx b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/new/client.tsx index 0449d4caba..c352fea784 100644 --- a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/new/client.tsx +++ b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/new/client.tsx @@ -110,19 +110,19 @@ const formSchema = z.object({ .positive() .optional(), dayOfMonth: z.coerce - .number({ - errorMap: (issue, { defaultError }) => ({ - message: - issue.code === "invalid_type" - ? "Refill day must be greater than 0 and a integer 31 or less" - : defaultError, - }), - }) - .int() - .min(1) - .max(31) - .positive() - .optional(), + .number({ + errorMap: (issue, { defaultError }) => ({ + message: + issue.code === "invalid_type" + ? "Refill day must be greater than 0 and a integer 31 or less" + : defaultError, + }), + }) + .int() + .min(1) + .max(31) + .positive() + .optional(), }) .optional(), }) @@ -209,7 +209,7 @@ export const CreateKey: React.FC = ({ apiId, keyAuthId }) => { if (!values.ratelimitEnabled) { delete values.ratelimit; } - + await key.mutateAsync({ keyAuthId, ...values, @@ -217,13 +217,16 @@ export const CreateKey: React.FC = ({ apiId, keyAuthId }) => { expires: values.expires?.getTime() ?? undefined, ownerId: values.ownerId ?? undefined, remaining: values.limit?.remaining ?? undefined, - refill: values?.limit?.refill?.amount !== undefined && (values.limit?.refill?.interval === "daily" || values.limit?.refill?.interval === "monthly") ? { - interval: values.limit?.refill?.interval, - amount: values.limit.refill.amount, - dayOfMonth: values.limit.refill.dayOfMonth ?? undefined, - } : undefined, + refill: + values?.limit?.refill?.amount !== undefined && + (values.limit?.refill?.interval === "daily" || values.limit?.refill?.interval === "monthly") + ? { + interval: values.limit?.refill?.interval, + amount: values.limit.refill.amount, + dayOfMonth: values.limit.refill.dayOfMonth ?? undefined, + } + : undefined, enabled: true, - }); router.refresh(); @@ -651,7 +654,7 @@ export const CreateKey: React.FC = ({ apiId, keyAuthId }) => { )} /> - { diff --git a/apps/workflows/jobs/refill-daily.ts b/apps/workflows/jobs/refill-daily.ts index 6808011517..ca0dcf0718 100644 --- a/apps/workflows/jobs/refill-daily.ts +++ b/apps/workflows/jobs/refill-daily.ts @@ -1,4 +1,4 @@ -import { connectDatabase, eq, lte, schema, gt } from "@/lib/db"; +import { connectDatabase, eq, gt, lte, schema } from "@/lib/db"; import { env } from "@/lib/env"; import { Tinybird } from "@/lib/tinybird"; import { client } from "@/trigger"; @@ -129,11 +129,10 @@ client.defineJob({ }); }); } - + return { daileyKeyIds: dailyKeys.map((k) => k.id), - monthlyKeyIds: monthlyKeys.map((k) => k.id) + monthlyKeyIds: monthlyKeys.map((k) => k.id), }; - }, }); diff --git a/packages/api/src/openapi.d.ts b/packages/api/src/openapi.d.ts index 94a7894477..c089bf85a8 100644 --- a/packages/api/src/openapi.d.ts +++ b/packages/api/src/openapi.d.ts @@ -3,11 +3,14 @@ * Do not make direct changes to the file. */ - /** OneOf type helpers */ type Without = { [P in Exclude]?: never }; -type XOR = (T | U) extends object ? (Without & U) | (Without & T) : T | U; -type OneOf = T extends [infer Only] ? Only : T extends [infer A, infer B, ...infer Rest] ? OneOf<[XOR, ...Rest]> : never; +type XOR = T | U extends object ? (Without & U) | (Without & T) : T | U; +type OneOf = T extends [infer Only] + ? Only + : T extends [infer A, infer B, ...infer Rest] + ? OneOf<[XOR, ...Rest]> + : never; export interface paths { "/v1/liveness": { @@ -354,7 +357,7 @@ export interface components { * @example { * "interval": "monthly", * "amount": 100, - * "dayOfMonth":15, + * "dayOfMonth":15, * } */ refill?: { @@ -524,7 +527,16 @@ export interface components { * * @enum {string} */ - code: "VALID" | "NOT_FOUND" | "FORBIDDEN" | "USAGE_EXCEEDED" | "RATE_LIMITED" | "UNAUTHORIZED" | "DISABLED" | "INSUFFICIENT_PERMISSIONS" | "EXPIRED"; + code: + | "VALID" + | "NOT_FOUND" + | "FORBIDDEN" + | "USAGE_EXCEEDED" + | "RATE_LIMITED" + | "UNAUTHORIZED" + | "DISABLED" + | "INSUFFICIENT_PERMISSIONS" + | "EXPIRED"; /** @description Sets the key to be enabled or disabled. Disabled keys will not verify. */ enabled?: boolean; /** @@ -550,11 +562,18 @@ export interface components { }; }; /** @description A query for which permissions you require */ - PermissionQuery: OneOf<[string, { - and: components["schemas"]["PermissionQuery"][]; - }, { - or: components["schemas"]["PermissionQuery"][]; - }, null]>; + PermissionQuery: OneOf< + [ + string, + { + and: components["schemas"]["PermissionQuery"][]; + }, + { + or: components["schemas"]["PermissionQuery"][]; + }, + null, + ] + >; V1KeysVerifyKeyRequest: { /** * @description The id of the api where the key belongs to. This is optional for now but will be required soon. @@ -599,21 +618,21 @@ export interface components { * ] */ ratelimits?: { - /** - * @description The name of the ratelimit. - * @example tokens - */ - name: string; - /** - * @description Optionally override how expensive this operation is and how many tokens are deducted from the current limit. - * @default 1 - */ - cost?: number; - /** @description Optionally override the limit. */ - limit?: number; - /** @description Optionally override the ratelimit window duration. */ - duration?: number; - }[]; + /** + * @description The name of the ratelimit. + * @example tokens + */ + name: string; + /** + * @description Optionally override how expensive this operation is and how many tokens are deducted from the current limit. + * @default 1 + */ + cost?: number; + /** @description Optionally override the limit. */ + limit?: number; + /** @description Optionally override the ratelimit window duration. */ + duration?: number; + }[]; }; ErrDeleteProtected: { error: { @@ -639,8 +658,7 @@ export interface components { }; }; responses: never; - parameters: { - }; + parameters: {}; requestBodies: never; headers: never; pathItems: never; @@ -651,7 +669,6 @@ export type $defs = Record; export type external = Record; export interface operations { - "v1.liveness": { responses: { /** @description The configured services and their status */ @@ -1170,7 +1187,7 @@ export interface operations { * "refillInterval": 60 * } */ - ratelimit?: ({ + ratelimit?: { /** * @deprecated * @description Fast ratelimiting doesn't add latency, while consistent ratelimiting is more accurate. @@ -1202,7 +1219,7 @@ export interface operations { * This field will become required in a future version. */ duration?: number; - }) | null; + } | null; /** * @description The number of requests that can be made with this key before it becomes invalid. Set `null` to disable. * @example 1000 @@ -1215,7 +1232,7 @@ export interface operations { * "amount": 100 * } */ - refill?: ({ + refill?: { /** * @description Unkey will automatically refill verifications at the set interval. If null is used the refill functionality will be removed from the key. * @enum {string} @@ -1224,7 +1241,7 @@ export interface operations { /** @description The amount of verifications to refill for each occurrence is determined individually for each key. */ amount: number; dayOfMonth: number; - }) | null; + } | null; /** * @description Set if key is enabled or disabled. If disabled, the key cannot be used to verify. * @example true @@ -1247,16 +1264,16 @@ export interface operations { * ] */ roles?: { - /** @description The id of the role. Provide either `id` or `name`. If both are provided `id` is used. */ - id?: string; - /** @description Identify the role via its name. Provide either `id` or `name`. If both are provided `id` is used. */ - name?: string; - /** - * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. - * Autocreating roles requires your root key to have the `rbac.*.create_role` permission, otherwise the request will get rejected - */ - create?: boolean; - }[]; + /** @description The id of the role. Provide either `id` or `name`. If both are provided `id` is used. */ + id?: string; + /** @description Identify the role via its name. Provide either `id` or `name`. If both are provided `id` is used. */ + name?: string; + /** + * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. + * Autocreating roles requires your root key to have the `rbac.*.create_role` permission, otherwise the request will get rejected + */ + create?: boolean; + }[]; /** * @description The permissions you want to set for this key. This overwrites all existing permissions. * Setting permissions requires the `rbac.*.add_permission_to_key` permission. @@ -1274,16 +1291,16 @@ export interface operations { * ] */ permissions?: { - /** @description The id of the permission. Provide either `id` or `name`. If both are provided `id` is used. */ - id?: string; - /** @description Identify the permission via its name. Provide either `id` or `name`. If both are provided `id` is used. */ - name?: string; - /** - * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. - * Autocreating permissions requires your root key to have the `rbac.*.create_permission` permission, otherwise the request will get rejected - */ - create?: boolean; - }[]; + /** @description The id of the permission. Provide either `id` or `name`. If both are provided `id` is used. */ + id?: string; + /** @description Identify the permission via its name. Provide either `id` or `name`. If both are provided `id` is used. */ + name?: string; + /** + * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. + * Autocreating permissions requires your root key to have the `rbac.*.create_permission` permission, otherwise the request will get rejected + */ + create?: boolean; + }[]; }; }; }; @@ -1433,27 +1450,27 @@ export interface operations { content: { "application/json": { verifications: { - /** - * @description The timestamp of the usage data - * @example 1620000000000 - */ - time: number; - /** - * @description The number of successful requests - * @example 100 - */ - success: number; - /** - * @description The number of requests that were rate limited - * @example 10 - */ - rateLimited: number; - /** - * @description The number of requests that exceeded the usage limit - * @example 0 - */ - usageExceeded: number; - }[]; + /** + * @description The timestamp of the usage data + * @example 1620000000000 + */ + time: number; + /** + * @description The number of successful requests + * @example 100 + */ + success: number; + /** + * @description The number of requests that were rate limited + * @example 10 + */ + rateLimited: number; + /** + * @description The number of requests that exceeded the usage limit + * @example 0 + */ + usageExceeded: number; + }[]; }; }; }; @@ -1509,16 +1526,16 @@ export interface operations { keyId: string; /** @description The permissions you want to add to this key */ permissions: { - /** @description The id of the permission. Provide either `id` or `name`. If both are provided `id` is used. */ - id?: string; - /** @description Identify the permission via its name. Provide either `id` or `name`. If both are provided `id` is used. */ - name?: string; - /** - * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. - * Autocreating permissions requires your root key to have the `rbac.*.create_permission` permission, otherwise the request will get rejected - */ - create?: boolean; - }[]; + /** @description The id of the permission. Provide either `id` or `name`. If both are provided `id` is used. */ + id?: string; + /** @description Identify the permission via its name. Provide either `id` or `name`. If both are provided `id` is used. */ + name?: string; + /** + * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. + * Autocreating permissions requires your root key to have the `rbac.*.create_permission` permission, otherwise the request will get rejected + */ + create?: boolean; + }[]; }; }; }; @@ -1527,17 +1544,17 @@ export interface operations { 200: { content: { "application/json": { - /** - * @description The id of the permission. This is used internally - * @example perm_123 - */ - id: string; - /** - * @description The name of the permission - * @example dns.record.create - */ - name: string; - }[]; + /** + * @description The id of the permission. This is used internally + * @example perm_123 + */ + id: string; + /** + * @description The name of the permission + * @example dns.record.create + */ + name: string; + }[]; }; }; /** @description The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). */ @@ -1602,11 +1619,11 @@ export interface operations { * ] */ permissions: { - /** @description The id of the permission. Provide either `id` or `name`. If both are provided `id` is used. */ - id?: string; - /** @description Identify the permission via its name. Provide either `id` or `name`. If both are provided `id` is used. */ - name?: string; - }[]; + /** @description The id of the permission. Provide either `id` or `name`. If both are provided `id` is used. */ + id?: string; + /** @description Identify the permission via its name. Provide either `id` or `name`. If both are provided `id` is used. */ + name?: string; + }[]; }; }; }; @@ -1684,16 +1701,16 @@ export interface operations { * ] */ permissions: { - /** @description The id of the permission. Provide either `id` or `name`. If both are provided `id` is used. */ - id?: string; - /** @description Identify the permission via its name. Provide either `id` or `name`. If both are provided `id` is used. */ - name?: string; - /** - * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. - * Autocreating permissions requires your root key to have the `rbac.*.create_permission` permission, otherwise the request will get rejected - */ - create?: boolean; - }[]; + /** @description The id of the permission. Provide either `id` or `name`. If both are provided `id` is used. */ + id?: string; + /** @description Identify the permission via its name. Provide either `id` or `name`. If both are provided `id` is used. */ + name?: string; + /** + * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. + * Autocreating permissions requires your root key to have the `rbac.*.create_permission` permission, otherwise the request will get rejected + */ + create?: boolean; + }[]; }; }; }; @@ -1702,17 +1719,17 @@ export interface operations { 200: { content: { "application/json": { - /** - * @description The id of the permission. This is used internally - * @example perm_123 - */ - id: string; - /** - * @description The name of the permission - * @example dns.record.create - */ - name: string; - }[]; + /** + * @description The id of the permission. This is used internally + * @example perm_123 + */ + id: string; + /** + * @description The name of the permission + * @example dns.record.create + */ + name: string; + }[]; }; }; /** @description The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). */ @@ -1782,16 +1799,16 @@ export interface operations { * ] */ roles: { - /** @description The id of the role. Provide either `id` or `name`. If both are provided `id` is used. */ - id?: string; - /** @description Identify the role via its name. Provide either `id` or `name`. If both are provided `id` is used. */ - name?: string; - /** - * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. - * Autocreating roles requires your root key to have the `rbac.*.create_role` permission, otherwise the request will get rejected - */ - create?: boolean; - }[]; + /** @description The id of the role. Provide either `id` or `name`. If both are provided `id` is used. */ + id?: string; + /** @description Identify the role via its name. Provide either `id` or `name`. If both are provided `id` is used. */ + name?: string; + /** + * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. + * Autocreating roles requires your root key to have the `rbac.*.create_role` permission, otherwise the request will get rejected + */ + create?: boolean; + }[]; }; }; }; @@ -1800,17 +1817,17 @@ export interface operations { 200: { content: { "application/json": { - /** - * @description The id of the role. This is used internally - * @example role_123 - */ - id: string; - /** - * @description The name of the role - * @example dns.record.create - */ - name: string; - }[]; + /** + * @description The id of the role. This is used internally + * @example role_123 + */ + id: string; + /** + * @description The name of the role + * @example dns.record.create + */ + name: string; + }[]; }; }; /** @description The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). */ @@ -1875,11 +1892,11 @@ export interface operations { * ] */ roles: { - /** @description The id of the role. Provide either `id` or `name`. If both are provided `id` is used. */ - id?: string; - /** @description Identify the role via its name. Provide either `id` or `name`. If both are provided `id` is used. */ - name?: string; - }[]; + /** @description The id of the role. Provide either `id` or `name`. If both are provided `id` is used. */ + id?: string; + /** @description Identify the role via its name. Provide either `id` or `name`. If both are provided `id` is used. */ + name?: string; + }[]; }; }; }; @@ -1957,16 +1974,16 @@ export interface operations { * ] */ roles: { - /** @description The id of the role. Provide either `id` or `name`. If both are provided `id` is used. */ - id?: string; - /** @description Identify the role via its name. Provide either `id` or `name`. If both are provided `id` is used. */ - name?: string; - /** - * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. - * Autocreating roles requires your root key to have the `rbac.*.create_role` permission, otherwise the request will get rejected - */ - create?: boolean; - }[]; + /** @description The id of the role. Provide either `id` or `name`. If both are provided `id` is used. */ + id?: string; + /** @description Identify the role via its name. Provide either `id` or `name`. If both are provided `id` is used. */ + name?: string; + /** + * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. + * Autocreating roles requires your root key to have the `rbac.*.create_role` permission, otherwise the request will get rejected + */ + create?: boolean; + }[]; }; }; }; @@ -1975,17 +1992,17 @@ export interface operations { 200: { content: { "application/json": { - /** - * @description The id of the role. This is used internally - * @example role_123 - */ - id: string; - /** - * @description The name of the role - * @example dns.record.create - */ - name: string; - }[]; + /** + * @description The id of the role. This is used internally + * @example role_123 + */ + id: string; + /** + * @description The name of the role + * @example dns.record.create + */ + name: string; + }[]; }; }; /** @description The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). */ @@ -2434,26 +2451,26 @@ export interface operations { * ] */ resources?: { - /** - * @description The type of resource - * @example organization - */ - type: string; - /** - * @description The unique identifier for the resource - * @example org_123 - */ - id: string; - /** - * @description A human readable name for this resource - * @example unkey - */ - name?: string; - /** @description Attach any metadata to this resources */ - meta?: { - [key: string]: unknown; - }; - }[]; + /** + * @description The type of resource + * @example organization + */ + type: string; + /** + * @description The unique identifier for the resource + * @example org_123 + */ + id: string; + /** + * @description A human readable name for this resource + * @example unkey + */ + name?: string; + /** @description Attach any metadata to this resources */ + meta?: { + [key: string]: unknown; + }; + }[]; }; }; }; @@ -2531,33 +2548,245 @@ export interface operations { "v1.migrations.createKeys": { requestBody: { content: { - "application/json": ({ - /** - * @description Choose an `API` where this key should be created. - * @example api_123 - */ - apiId: string; + "application/json": { + /** + * @description Choose an `API` where this key should be created. + * @example api_123 + */ + apiId: string; + /** + * @description To make it easier for your users to understand which product an api key belongs to, you can add prefix them. + * + * For example Stripe famously prefixes their customer ids with cus_ or their api keys with sk_live_. + * + * The underscore is automatically added if you are defining a prefix, for example: "prefix": "abc" will result in a key like abc_xxxxxxxxx + */ + prefix?: string; + /** + * @description The name for your Key. This is not customer facing. + * @example my key + */ + name?: string; + /** @description The raw key in plaintext. If provided, unkey encrypts this value and stores it securely. Provide either `hash` or `plaintext` */ + plaintext?: string; + /** @description Provide either `hash` or `plaintext` */ + hash?: { + /** @description The hashed and encoded key */ + value: string; /** - * @description To make it easier for your users to understand which product an api key belongs to, you can add prefix them. - * - * For example Stripe famously prefixes their customer ids with cus_ or their api keys with sk_live_. - * - * The underscore is automatically added if you are defining a prefix, for example: "prefix": "abc" will result in a key like abc_xxxxxxxxx + * @description The algorithm for hashing and encoding, currently only sha256 and base64 are supported + * @enum {string} */ - prefix?: string; + variant: "sha256_base64"; + }; + /** + * @description The first 4 characters of the key. If a prefix is used, it should be the prefix plus 4 characters. + * @example unkey_32kq + */ + start?: string; + /** + * @description Your user’s Id. This will provide a link between Unkey and your customer record. + * When validating a key, we will return this back to you, so you can clearly identify your user from their api key. + * @example team_123 + */ + ownerId?: string; + /** + * @description This is a place for dynamic meta data, anything that feels useful for you should go here + * @example { + * "billingTier": "PRO", + * "trialEnds": "2023-06-16T17:16:37.161Z" + * } + */ + meta?: { + [key: string]: unknown; + }; + /** + * @description A list of roles that this key should have. If the role does not exist, an error is thrown + * @example [ + * "admin", + * "finance" + * ] + */ + roles?: string[]; + /** + * @description A list of permissions that this key should have. If the permission does not exist, an error is thrown + * @example [ + * "domains.create_record", + * "say_hello" + * ] + */ + permissions?: string[]; + /** + * @description You can auto expire keys by providing a unix timestamp in milliseconds. Once Keys expire they will automatically be disabled and are no longer valid unless you enable them again. + * @example 1623869797161 + */ + expires?: number; + /** + * @description You can limit the number of requests a key can make. Once a key reaches 0 remaining requests, it will automatically be disabled and is no longer valid unless you update it. + * @example 1000 + */ + remaining?: number; + /** + * @description Unkey enables you to refill verifications for each key at regular intervals. + * @example { + * "interval": "daily", + * "amount": 100 + * } + */ + refill?: { /** - * @description The name for your Key. This is not customer facing. - * @example my key + * @description Unkey will automatically refill verifications at the set interval. + * @enum {string} */ - name?: string; - /** @description The raw key in plaintext. If provided, unkey encrypts this value and stores it securely. Provide either `hash` or `plaintext` */ - plaintext?: string; - /** @description Provide either `hash` or `plaintext` */ - hash?: { - /** @description The hashed and encoded key */ - value: string; - /** - * @description The algorithm for hashing and encoding, currently only sha256 and base64 are supported + interval: "daily" | "monthly"; + /** @description The number of verifications to refill for each occurrence is determined individually for each key. */ + amount: number; + }; + /** + * @description Unkey comes with per-key ratelimiting out of the box. + * @example { + * "type": "fast", + * "limit": 10, + * "refillRate": 1, + * "refillInterval": 60 + * } + */ + ratelimit?: { + /** + * @description Async will return a response immediately, lowering latency at the cost of accuracy. + * @default false + */ + async?: boolean; + /** + * @deprecated + * @description Fast ratelimiting doesn't add latency, while consistent ratelimiting is more accurate. + * @default fast + * @enum {string} + */ + type?: "fast" | "consistent"; + /** @description The total amount of burstable requests. */ + limit: number; + /** + * @deprecated + * @description How many tokens to refill during each refillInterval. + */ + refillRate: number; + /** + * @deprecated + * @description Determines the speed at which tokens are refilled, in milliseconds. + */ + refillInterval: number; + }; + /** + * @description Sets if key is enabled or disabled. Disabled keys are not valid. + * @default true + * @example false + */ + enabled?: boolean; + /** + * @description Environments allow you to divide your keyspace. + * + * Some applications like Stripe, Clerk, WorkOS and others have a concept of "live" and "test" keys to + * give the developer a way to develop their own application without the risk of modifying real world + * resources. + * + * When you set an environment, we will return it back to you when validating the key, so you can + * handle it correctly. + */ + environment?: string; + }[]; + }; + }; + responses: { + /** @description The key ids of all created keys */ + 200: { + content: { + "application/json": { + /** + * @description The ids of the keys. This is not a secret and can be stored as a reference if you wish. You need the keyId to update or delete a key later. + * @example [ + * "key_123", + * "key_456" + * ] + */ + keyIds: string[]; + }; + }; + }; + /** @description The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). */ + 400: { + content: { + "application/json": components["schemas"]["ErrBadRequest"]; + }; + }; + /** @description Although the HTTP standard specifies "unauthorized", semantically this response means "unauthenticated". That is, the client must authenticate itself to get the requested response. */ + 401: { + content: { + "application/json": components["schemas"]["ErrUnauthorized"]; + }; + }; + /** @description The client does not have access rights to the content; that is, it is unauthorized, so the server is refusing to give the requested resource. Unlike 401 Unauthorized, the client's identity is known to the server. */ + 403: { + content: { + "application/json": components["schemas"]["ErrForbidden"]; + }; + }; + /** @description The server cannot find the requested resource. In the browser, this means the URL is not recognized. In an API, this can also mean that the endpoint is valid but the resource itself does not exist. Servers may also send this response instead of 403 Forbidden to hide the existence of a resource from an unauthorized client. This response code is probably the most well known due to its frequent occurrence on the web. */ + 404: { + content: { + "application/json": components["schemas"]["ErrNotFound"]; + }; + }; + /** @description This response is sent when a request conflicts with the current state of the server. */ + 409: { + content: { + "application/json": components["schemas"]["ErrConflict"]; + }; + }; + /** @description The user has sent too many requests in a given amount of time ("rate limiting") */ + 429: { + content: { + "application/json": components["schemas"]["ErrTooManyRequests"]; + }; + }; + /** @description The server has encountered a situation it does not know how to handle. */ + 500: { + content: { + "application/json": components["schemas"]["ErrInternalServerError"]; + }; + }; + }; + }; + "v1.migrations.enqueueKeys": { + requestBody: { + content: { + "application/json": { + /** @description Contact support@unkey.dev to receive your migration id. */ + migrationId: string; + /** @description The id of the api, you want to migrate keys to */ + apiId: string; + keys: { + /** + * @description To make it easier for your users to understand which product an api key belongs to, you can add prefix them. + * + * For example Stripe famously prefixes their customer ids with cus_ or their api keys with sk_live_. + * + * The underscore is automatically added if you are defining a prefix, for example: "prefix": "abc" will result in a key like abc_xxxxxxxxx + */ + prefix?: string; + /** + * @description The name for your Key. This is not customer facing. + * @example my key + */ + name?: string; + /** @description The raw key in plaintext. If provided, unkey encrypts this value and stores it securely. Provide either `hash` or `plaintext` */ + plaintext?: string; + /** @description Provide either `hash` or `plaintext` */ + hash?: { + /** @description The hashed and encoded key */ + value: string; + /** + * @description The algorithm for hashing and encoding, currently only sha256 and base64 are supported * @enum {string} */ variant: "sha256_base64"; @@ -2626,39 +2855,43 @@ export interface operations { amount: number; }; /** - * @description Unkey comes with per-key ratelimiting out of the box. + * @description Unkey comes with per-key fixed-window ratelimiting out of the box. * @example { * "type": "fast", * "limit": 10, - * "refillRate": 1, - * "refillInterval": 60 + * "duration": 60000 * } */ ratelimit?: { /** * @description Async will return a response immediately, lowering latency at the cost of accuracy. - * @default false + * @default true */ async?: boolean; /** * @deprecated - * @description Fast ratelimiting doesn't add latency, while consistent ratelimiting is more accurate. + * @description Deprecated, use `async`. Fast ratelimiting doesn't add latency, while consistent ratelimiting is more accurate. * @default fast * @enum {string} */ type?: "fast" | "consistent"; - /** @description The total amount of burstable requests. */ + /** @description The total amount of requests in a given interval. */ limit: number; + /** + * @description The window duration in milliseconds + * @example 60000 + */ + duration: number; /** * @deprecated * @description How many tokens to refill during each refillInterval. */ - refillRate: number; + refillRate?: number; /** * @deprecated - * @description Determines the speed at which tokens are refilled, in milliseconds. + * @description The refill timeframe, in milliseconds. */ - refillInterval: number; + refillInterval?: number; }; /** * @description Sets if key is enabled or disabled. Disabled keys are not valid. @@ -2677,223 +2910,7 @@ export interface operations { * handle it correctly. */ environment?: string; - })[]; - }; - }; - responses: { - /** @description The key ids of all created keys */ - 200: { - content: { - "application/json": { - /** - * @description The ids of the keys. This is not a secret and can be stored as a reference if you wish. You need the keyId to update or delete a key later. - * @example [ - * "key_123", - * "key_456" - * ] - */ - keyIds: string[]; - }; - }; - }; - /** @description The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). */ - 400: { - content: { - "application/json": components["schemas"]["ErrBadRequest"]; - }; - }; - /** @description Although the HTTP standard specifies "unauthorized", semantically this response means "unauthenticated". That is, the client must authenticate itself to get the requested response. */ - 401: { - content: { - "application/json": components["schemas"]["ErrUnauthorized"]; - }; - }; - /** @description The client does not have access rights to the content; that is, it is unauthorized, so the server is refusing to give the requested resource. Unlike 401 Unauthorized, the client's identity is known to the server. */ - 403: { - content: { - "application/json": components["schemas"]["ErrForbidden"]; - }; - }; - /** @description The server cannot find the requested resource. In the browser, this means the URL is not recognized. In an API, this can also mean that the endpoint is valid but the resource itself does not exist. Servers may also send this response instead of 403 Forbidden to hide the existence of a resource from an unauthorized client. This response code is probably the most well known due to its frequent occurrence on the web. */ - 404: { - content: { - "application/json": components["schemas"]["ErrNotFound"]; - }; - }; - /** @description This response is sent when a request conflicts with the current state of the server. */ - 409: { - content: { - "application/json": components["schemas"]["ErrConflict"]; - }; - }; - /** @description The user has sent too many requests in a given amount of time ("rate limiting") */ - 429: { - content: { - "application/json": components["schemas"]["ErrTooManyRequests"]; - }; - }; - /** @description The server has encountered a situation it does not know how to handle. */ - 500: { - content: { - "application/json": components["schemas"]["ErrInternalServerError"]; - }; - }; - }; - }; - "v1.migrations.enqueueKeys": { - requestBody: { - content: { - "application/json": { - /** @description Contact support@unkey.dev to receive your migration id. */ - migrationId: string; - /** @description The id of the api, you want to migrate keys to */ - apiId: string; - keys: ({ - /** - * @description To make it easier for your users to understand which product an api key belongs to, you can add prefix them. - * - * For example Stripe famously prefixes their customer ids with cus_ or their api keys with sk_live_. - * - * The underscore is automatically added if you are defining a prefix, for example: "prefix": "abc" will result in a key like abc_xxxxxxxxx - */ - prefix?: string; - /** - * @description The name for your Key. This is not customer facing. - * @example my key - */ - name?: string; - /** @description The raw key in plaintext. If provided, unkey encrypts this value and stores it securely. Provide either `hash` or `plaintext` */ - plaintext?: string; - /** @description Provide either `hash` or `plaintext` */ - hash?: { - /** @description The hashed and encoded key */ - value: string; - /** - * @description The algorithm for hashing and encoding, currently only sha256 and base64 are supported - * @enum {string} - */ - variant: "sha256_base64"; - }; - /** - * @description The first 4 characters of the key. If a prefix is used, it should be the prefix plus 4 characters. - * @example unkey_32kq - */ - start?: string; - /** - * @description Your user’s Id. This will provide a link between Unkey and your customer record. - * When validating a key, we will return this back to you, so you can clearly identify your user from their api key. - * @example team_123 - */ - ownerId?: string; - /** - * @description This is a place for dynamic meta data, anything that feels useful for you should go here - * @example { - * "billingTier": "PRO", - * "trialEnds": "2023-06-16T17:16:37.161Z" - * } - */ - meta?: { - [key: string]: unknown; - }; - /** - * @description A list of roles that this key should have. If the role does not exist, an error is thrown - * @example [ - * "admin", - * "finance" - * ] - */ - roles?: string[]; - /** - * @description A list of permissions that this key should have. If the permission does not exist, an error is thrown - * @example [ - * "domains.create_record", - * "say_hello" - * ] - */ - permissions?: string[]; - /** - * @description You can auto expire keys by providing a unix timestamp in milliseconds. Once Keys expire they will automatically be disabled and are no longer valid unless you enable them again. - * @example 1623869797161 - */ - expires?: number; - /** - * @description You can limit the number of requests a key can make. Once a key reaches 0 remaining requests, it will automatically be disabled and is no longer valid unless you update it. - * @example 1000 - */ - remaining?: number; - /** - * @description Unkey enables you to refill verifications for each key at regular intervals. - * @example { - * "interval": "daily", - * "amount": 100 - * } - */ - refill?: { - /** - * @description Unkey will automatically refill verifications at the set interval. - * @enum {string} - */ - interval: "daily" | "monthly"; - /** @description The number of verifications to refill for each occurrence is determined individually for each key. */ - amount: number; - }; - /** - * @description Unkey comes with per-key fixed-window ratelimiting out of the box. - * @example { - * "type": "fast", - * "limit": 10, - * "duration": 60000 - * } - */ - ratelimit?: { - /** - * @description Async will return a response immediately, lowering latency at the cost of accuracy. - * @default true - */ - async?: boolean; - /** - * @deprecated - * @description Deprecated, use `async`. Fast ratelimiting doesn't add latency, while consistent ratelimiting is more accurate. - * @default fast - * @enum {string} - */ - type?: "fast" | "consistent"; - /** @description The total amount of requests in a given interval. */ - limit: number; - /** - * @description The window duration in milliseconds - * @example 60000 - */ - duration: number; - /** - * @deprecated - * @description How many tokens to refill during each refillInterval. - */ - refillRate?: number; - /** - * @deprecated - * @description The refill timeframe, in milliseconds. - */ - refillInterval?: number; - }; - /** - * @description Sets if key is enabled or disabled. Disabled keys are not valid. - * @default true - * @example false - */ - enabled?: boolean; - /** - * @description Environments allow you to divide your keyspace. - * - * Some applications like Stripe, Clerk, WorkOS and others have a concept of "live" and "test" keys to - * give the developer a way to develop their own application without the risk of modifying real world - * resources. - * - * When you set an environment, we will return it back to you when validating the key, so you can - * handle it correctly. - */ - environment?: string; - })[]; + }[]; }; }; }; @@ -3164,22 +3181,22 @@ export interface operations { 200: { content: { "application/json": { - /** - * @description The id of the permission - * @example perm_123 - */ - id: string; - /** - * @description The name of the permission. - * @example domain.record.manager - */ - name: string; - /** - * @description The description of what this permission does. This is just for your team, your users will not see this. - * @example Can manage dns records - */ - description?: string; - }[]; + /** + * @description The id of the permission + * @example perm_123 + */ + id: string; + /** + * @description The name of the permission. + * @example domain.record.manager + */ + name: string; + /** + * @description The description of what this permission does. This is just for your team, your users will not see this. + * @example Can manage dns records + */ + description?: string; + }[]; }; }; /** @description The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). */ @@ -3442,22 +3459,22 @@ export interface operations { 200: { content: { "application/json": { - /** - * @description The id of the role - * @example role_1234 - */ - id: string; - /** - * @description The name of the role. - * @example domain.record.manager - */ - name: string; - /** - * @description The description of what this role does. This is just for your team, your users will not see this. - * @example Can manage dns records - */ - description?: string; - }[]; + /** + * @description The id of the role + * @example role_1234 + */ + id: string; + /** + * @description The name of the role. + * @example domain.record.manager + */ + name: string; + /** + * @description The description of what this role does. This is just for your team, your users will not see this. + * @example Can manage dns records + */ + description?: string; + }[]; }; }; /** @description The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). */ @@ -3531,22 +3548,22 @@ export interface operations { * When verifying keys, you can specify which limits you want to use and all keys attached to this identity, will share the limits. */ ratelimits?: { - /** - * @description The name of this limit. You will need to use this again when verifying a key. - * @example tokens - */ - name: string; - /** - * @description How many requests may pass within a given window before requests are rejected. - * @example 10 - */ - limit: number; - /** - * @description The duration for each ratelimit window in milliseconds. - * @example 1000 - */ - duration: number; - }[]; + /** + * @description The name of this limit. You will need to use this again when verifying a key. + * @example tokens + */ + name: string; + /** + * @description How many requests may pass within a given window before requests are rejected. + * @example 10 + */ + limit: number; + /** + * @description The duration for each ratelimit window in milliseconds. + * @example 1000 + */ + duration: number; + }[]; }; }; }; @@ -3629,22 +3646,22 @@ export interface operations { }; /** @description When verifying keys, you can specify which limits you want to use and all keys attached to this identity, will share the limits. */ ratelimits: { - /** - * @description The name of this limit. You will need to use this again when verifying a key. - * @example tokens - */ - name: string; - /** - * @description How many requests may pass within a given window before requests are rejected. - * @example 10 - */ - limit: number; - /** - * @description The duration for each ratelimit window in milliseconds. - * @example 1000 - */ - duration: number; - }[]; + /** + * @description The name of this limit. You will need to use this again when verifying a key. + * @example tokens + */ + name: string; + /** + * @description How many requests may pass within a given window before requests are rejected. + * @example 10 + */ + limit: number; + /** + * @description The duration for each ratelimit window in milliseconds. + * @example 1000 + */ + duration: number; + }[]; }; }; }; @@ -3707,29 +3724,29 @@ export interface operations { "application/json": { /** @description A list of identities. */ identities: { - /** @description The id of this identity. Used to interact with unkey's API */ - id: string; - /** @description The id in your system */ - externalId: string; - /** @description When verifying keys, you can specify which limits you want to use and all keys attached to this identity, will share the limits. */ - ratelimits: { - /** - * @description The name of this limit. You will need to use this again when verifying a key. - * @example tokens - */ - name: string; - /** - * @description How many requests may pass within a given window before requests are rejected. - * @example 10 - */ - limit: number; - /** - * @description The duration for each ratelimit window in milliseconds. - * @example 1000 - */ - duration: number; - }[]; + /** @description The id of this identity. Used to interact with unkey's API */ + id: string; + /** @description The id in your system */ + externalId: string; + /** @description When verifying keys, you can specify which limits you want to use and all keys attached to this identity, will share the limits. */ + ratelimits: { + /** + * @description The name of this limit. You will need to use this again when verifying a key. + * @example tokens + */ + name: string; + /** + * @description How many requests may pass within a given window before requests are rejected. + * @example 10 + */ + limit: number; + /** + * @description The duration for each ratelimit window in milliseconds. + * @example 1000 + */ + duration: number; }[]; + }[]; /** * @description The cursor to use for the next page of results, if no cursor is returned, there are no more results * @example eyJrZXkiOiJrZXlfMTIzNCJ9 @@ -3822,22 +3839,22 @@ export interface operations { * When verifying keys, you can specify which limits you want to use and all keys attached to this identity, will share the limits. */ ratelimits?: { - /** - * @description The name of this limit. You will need to use this again when verifying a key. - * @example tokens - */ - name: string; - /** - * @description How many requests may pass within a given window before requests are rejected. - * @example 10 - */ - limit: number; - /** - * @description The duration for each ratelimit window in milliseconds. - * @example 1000 - */ - duration: number; - }[]; + /** + * @description The name of this limit. You will need to use this again when verifying a key. + * @example tokens + */ + name: string; + /** + * @description How many requests may pass within a given window before requests are rejected. + * @example 10 + */ + limit: number; + /** + * @description The duration for each ratelimit window in milliseconds. + * @example 1000 + */ + duration: number; + }[]; }; }; }; @@ -3866,22 +3883,22 @@ export interface operations { [key: string]: unknown; }; ratelimits: { - /** - * @description The name of this limit. - * @example tokens - */ - name: string; - /** - * @description How many requests may pass within a given window before requests are rejected. - * @example 10 - */ - limit: number; - /** - * @description The duration for each ratelimit window in milliseconds. - * @example 1000 - */ - duration: number; - }[]; + /** + * @description The name of this limit. + * @example tokens + */ + name: string; + /** + * @description How many requests may pass within a given window before requests are rejected. + * @example 10 + */ + limit: number; + /** + * @description The duration for each ratelimit window in milliseconds. + * @example 1000 + */ + duration: number; + }[]; }; }; }; @@ -4243,7 +4260,15 @@ export interface operations { * @example NOT_FOUND * @enum {string} */ - code?: "NOT_FOUND" | "FORBIDDEN" | "USAGE_EXCEEDED" | "RATE_LIMITED" | "UNAUTHORIZED" | "DISABLED" | "INSUFFICIENT_PERMISSIONS" | "EXPIRED"; + code?: + | "NOT_FOUND" + | "FORBIDDEN" + | "USAGE_EXCEEDED" + | "RATE_LIMITED" + | "UNAUTHORIZED" + | "DISABLED" + | "INSUFFICIENT_PERMISSIONS" + | "EXPIRED"; }; }; }; From 463f07dba935cbe3640ded7540824097238f8e99 Mon Sep 17 00:00:00 2001 From: Michael Silva Date: Mon, 23 Sep 2024 09:11:12 -0400 Subject: [PATCH 04/29] working create --- .../[apiId]/keys/[keyAuthId]/new/client.tsx | 98 +++++++++++-------- apps/dashboard/lib/trpc/routers/key/create.ts | 6 +- 2 files changed, 61 insertions(+), 43 deletions(-) diff --git a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/new/client.tsx b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/new/client.tsx index aea98c817e..fde395435c 100644 --- a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/new/client.tsx +++ b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/new/client.tsx @@ -18,6 +18,7 @@ import { FormMessage, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; import { Select, SelectContent, @@ -25,6 +26,7 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; + import { Separator } from "@/components/ui/separator"; import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; @@ -95,7 +97,7 @@ const formSchema = z.object({ .optional(), refill: z .object({ - interval: z.enum(["none", "daily", "monthly"]).default("none"), + interval: z.enum(["daily", "monthly"]).default("monthly"), amount: z.coerce .number({ errorMap: (issue, { defaultError }) => ({ @@ -107,8 +109,7 @@ const formSchema = z.object({ }) .int() .min(1) - .positive() - .optional(), + .positive(), dayOfMonth: z.coerce .number({ errorMap: (issue, { defaultError }) => ({ @@ -121,8 +122,7 @@ const formSchema = z.object({ .int() .min(1) .max(31) - .positive() - .optional(), + .positive().optional(), }) .optional(), }) @@ -164,7 +164,6 @@ type Props = { export const CreateKey: React.FC = ({ apiId, keyAuthId }) => { const router = useRouter(); - const form = useForm>({ resolver: async (data, context, options) => { return zodResolver(formSchema)(data, context, options); @@ -181,6 +180,7 @@ export const CreateKey: React.FC = ({ apiId, keyAuthId }) => { }, }); + const key = trpc.key.create.useMutation({ onSuccess() { toast.success("Key Created", { @@ -209,7 +209,15 @@ export const CreateKey: React.FC = ({ apiId, keyAuthId }) => { if (!values.ratelimitEnabled) { delete values.ratelimit; } + const refill = values.limit?.refill; + if(refill?.interval === "daily"){ + refill?.dayOfMonth === undefined; + } + if(refill?.interval === "monthly" && !refill.dayOfMonth){ + refill.dayOfMonth = 1; + } + await key.mutateAsync({ keyAuthId, ...values, @@ -217,15 +225,7 @@ export const CreateKey: React.FC = ({ apiId, keyAuthId }) => { expires: values.expires?.getTime() ?? undefined, ownerId: values.ownerId ?? undefined, remaining: values.limit?.remaining ?? undefined, - refill: - values?.limit?.refill?.amount !== undefined && - (values.limit?.refill?.interval === "daily" || values.limit?.refill?.interval === "monthly") - ? { - interval: values.limit?.refill?.interval, - amount: values.limit.refill.amount, - dayOfMonth: values.limit.refill.dayOfMonth ?? undefined, - } - : undefined, + refill: refill, enabled: true, }); @@ -456,9 +456,7 @@ export const CreateKey: React.FC = ({ apiId, keyAuthId }) => { )} /> - -
@@ -549,12 +547,10 @@ export const CreateKey: React.FC = ({ apiId, keyAuthId }) => { ) : null} -
Limited Use - = ({ apiId, keyAuthId }) => { )} /> - ( @@ -630,13 +626,9 @@ export const CreateKey: React.FC = ({ apiId, keyAuthId }) => { )} - /> + /> */} ( @@ -659,27 +651,53 @@ export const CreateKey: React.FC = ({ apiId, keyAuthId }) => { )} /> + ( + + Refill Rate + + + + )} + /> ( - - Day of the month to refill uses + + {/* Refill day or daily */} - +
+ +
Enter the day to refill monthly. @@ -867,7 +885,7 @@ export const CreateKey: React.FC = ({ apiId, keyAuthId }) => {
@@ -724,6 +701,7 @@ export const CreateKey: React.FC = ({ apiId, keyAuthId }) => { ( @@ -885,7 +863,7 @@ export const CreateKey: React.FC = ({ apiId, keyAuthId }) => { className="w-full" disabled={key.isLoading || !form.formState.isValid} type="submit" - variant={key.isLoading || !form.formState.isValid ? "disabled" : "primary"} + variant="primary" > {key.isLoading ? : "Create"} diff --git a/apps/dashboard/lib/trpc/routers/key/updateRemaining.ts b/apps/dashboard/lib/trpc/routers/key/updateRemaining.ts index 5e8bdbc809..e2d58ea468 100644 --- a/apps/dashboard/lib/trpc/routers/key/updateRemaining.ts +++ b/apps/dashboard/lib/trpc/routers/key/updateRemaining.ts @@ -50,6 +50,7 @@ export const updateKeyRemaining = rateLimitedProcedure(ratelimit.update) code: "NOT_FOUND", }); } + const isMonthlyInterval = input.refill?.interval === "monthly"; await db .update(schema.keys) .set({ @@ -58,7 +59,7 @@ export const updateKeyRemaining = rateLimitedProcedure(ratelimit.update) input.refill?.interval === "none" || input.refill?.interval === undefined ? null : input.refill?.interval, - refillDay: input.refill?.interval === "monthly" ? input.refill.refillDay : undefined, + refillDay: isMonthlyInterval ? input.refill?.refillDay : null, refillAmount: input.refill?.amount ?? null, lastRefillAt: input.refill?.interval ? new Date() : null, }) diff --git a/apps/dashboard/lib/zod-helper.ts b/apps/dashboard/lib/zod-helper.ts index 732a7f949f..c5a5a72804 100644 --- a/apps/dashboard/lib/zod-helper.ts +++ b/apps/dashboard/lib/zod-helper.ts @@ -49,14 +49,13 @@ export const formSchema = z.object({ errorMap: (issue, { defaultError }) => ({ message: issue.code === "invalid_type" - ? "Refill day must be greater than 0 and a integer 31 or less" + ? "Refill day must be an integer between 1 and 31" : defaultError, }), }) .int() .min(1) .max(31) - .positive() .optional(), }) .optional(), diff --git a/apps/docs/libraries/ts/sdk/keys/get.mdx b/apps/docs/libraries/ts/sdk/keys/get.mdx index 111a8f2d63..43adb57384 100644 --- a/apps/docs/libraries/ts/sdk/keys/get.mdx +++ b/apps/docs/libraries/ts/sdk/keys/get.mdx @@ -59,9 +59,12 @@ The number of requests that can be made with this key before it becomes invalid. Unkey allows you to refill remaining verifications on a key on a regular interval. - -Either `daily` or `monthly`. + Determines the rate at which verifications will be refilled. + +Available options: +- `daily`: Refills occur every day +- `monthly`: Refills occur once a month (see `refillDay` for specific day) Resets `remaining` to this value every interval. diff --git a/apps/workflows/jobs/index.ts b/apps/workflows/jobs/index.ts index d09538aa3e..e05bd37deb 100644 --- a/apps/workflows/jobs/index.ts +++ b/apps/workflows/jobs/index.ts @@ -4,5 +4,4 @@ export * from "./end-trials"; export * from "./create-invoice"; // export * from "./invoicing"; export * from "./refill-daily"; -export * from "./refill-monthly"; export * from "./downgrade-requests"; diff --git a/apps/workflows/jobs/refill-daily.ts b/apps/workflows/jobs/refill-daily.ts index ca0dcf0718..0961000667 100644 --- a/apps/workflows/jobs/refill-daily.ts +++ b/apps/workflows/jobs/refill-daily.ts @@ -1,4 +1,4 @@ -import { connectDatabase, eq, gt, lte, schema } from "@/lib/db"; +import { connectDatabase, eq, schema } from "@/lib/db"; import { env } from "@/lib/env"; import { Tinybird } from "@/lib/tinybird"; import { client } from "@/trigger"; @@ -13,88 +13,28 @@ client.defineJob({ }), run: async (_payload, io, _ctx) => { - const date = new Date(Date.now()); - const today = date.getDate(); + const date = _payload.ts; + const today = date.getUTCDate(); const db = connectDatabase(); const tb = new Tinybird(env().TINYBIRD_TOKEN); - const tDay = new Date(); - const tMonth = new Date(); - tDay.setUTCHours(tDay.getUTCHours() - 24); - tMonth.setUTCMonth(tMonth.getUTCMonth() - 1); - const dailyKeys = await io.runTask("list keys Daily", () => + const keys = await io.runTask("list keys for refill", () => db.query.keys.findMany({ where: (table, { isNotNull, isNull, eq, and, gt, or }) => and( isNull(table.deletedAt), - isNotNull(table.refillInterval), isNotNull(table.refillAmount), - eq(table.refillInterval, "daily"), gt(table.refillAmount, table.remaining), or( - isNull(table.lastRefillAt), - lte(table.lastRefillAt, tDay), // Check if more than 24 hours have passed - ), + isNull(table.refillDay), + eq(table.refillDay, today), + ) ), }), ); - const monthlyKeys = await io.runTask("list keys Monthly", () => - db.query.keys.findMany({ - where: (table, { isNotNull, isNull, eq, and, or }) => - and( - isNull(table.deletedAt), - isNotNull(table.refillInterval), - isNotNull(table.refillAmount), - eq(table.refillInterval, "monthly"), - eq(table.refillDay, today), - gt(table.refillAmount, table.remaining), - or( - isNull(table.lastRefillAt), - lte(table.lastRefillAt, tMonth), // Check if more than 1 Month has passed - ), - ), - }), - ); - io.logger.info(`found ${dailyKeys.length} keys with daily refill set`); - io.logger.info(`found ${monthlyKeys.length} keys with monthly refill set`); - // const keysWithRefill = keys.length; - for (const key of dailyKeys) { - await io.runTask(`refill for ${key.id}`, async () => { - await db - .update(schema.keys) - .set({ - remaining: key.refillAmount, - lastRefillAt: new Date(), - }) - .where(eq(schema.keys.id, key.id)); - }); - await io.runTask(`create audit log refilling ${key.id}`, async () => { - await tb.ingestAuditLogs({ - workspaceId: key.workspaceId, - event: "key.update", - actor: { - type: "system", - id: "trigger", - }, - description: `Refilled ${key.id} to ${key.refillAmount}`, - resources: [ - { - type: "workspace", - id: key.workspaceId, - }, - { - type: "key", - id: key.id, - }, - ], - context: { - location: "trigger", - }, - }); - }); - } - for (const key of monthlyKeys) { + io.logger.info(`found ${keys.length} keys with refill set for today`); + for (const key of keys) { await io.runTask(`refill for ${key.id}`, async () => { await db .update(schema.keys) @@ -104,6 +44,7 @@ client.defineJob({ }) .where(eq(schema.keys.id, key.id)); }); + await io.runTask(`create audit log refilling ${key.id}`, async () => { await tb.ingestAuditLogs({ workspaceId: key.workspaceId, @@ -129,10 +70,8 @@ client.defineJob({ }); }); } - return { - daileyKeyIds: dailyKeys.map((k) => k.id), - monthlyKeyIds: monthlyKeys.map((k) => k.id), + refillKeyIds: keys.map((k) => k.id), }; }, }); diff --git a/apps/workflows/jobs/refill-monthly.ts b/apps/workflows/jobs/refill-monthly.ts deleted file mode 100644 index 8b1e95eb2c..0000000000 --- a/apps/workflows/jobs/refill-monthly.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { connectDatabase, eq, gt, lte, schema } from "@/lib/db"; -import { env } from "@/lib/env"; -import { Tinybird } from "@/lib/tinybird"; -import { client } from "@/trigger"; -import { cronTrigger } from "@trigger.dev/sdk"; - -client.defineJob({ - id: "refill.monthly", - name: "Monthly refill", - version: "0.0.1", - trigger: cronTrigger({ - cron: "0 0 0 1 1/1 ? *", // First of each month at 00:00 UTC - }), - - run: async (_payload, io, _ctx) => { - const db = connectDatabase(); - const tb = new Tinybird(env().TINYBIRD_TOKEN); - const t = new Date(); - t.setUTCMonth(t.getUTCMonth() - 1); - - const keys = await io.runTask("list keys", () => - db.query.keys.findMany({ - where: (table, { isNotNull, isNull, eq, and, or }) => - and( - isNull(table.deletedAt), - isNotNull(table.refillInterval), - isNotNull(table.refillAmount), - eq(table.refillInterval, "monthly"), - gt(table.refillAmount, table.remaining), - or( - isNull(table.lastRefillAt), - lte(table.lastRefillAt, t), // Check if more than 1 Month has passed - ), - ), - }), - ); - io.logger.info(`found ${keys.length} keys with monthly refill set`); - // const keysWithRefill = keys.length; - for (const key of keys) { - await io.runTask(`refill for ${key.id}`, async () => { - await db - .update(schema.keys) - .set({ - remaining: key.refillAmount, - lastRefillAt: new Date(), - }) - .where(eq(schema.keys.id, key.id)); - }); - await io.runTask(`create audit log refilling ${key.id}`, async () => { - await tb.ingestAuditLogs({ - workspaceId: key.workspaceId, - event: "key.update", - actor: { - type: "system", - id: "trigger", - }, - description: `Refilled ${key.id} to ${key.refillAmount}`, - resources: [ - { - type: "workspace", - id: key.workspaceId, - }, - { - type: "key", - id: key.id, - }, - ], - context: { - location: "trigger", - }, - }); - }); - } - return { - keyIds: keys.map((k) => k.id), - }; - }, -}); diff --git a/apps/workflows/package.json b/apps/workflows/package.json index bcdd1307e6..729754f08a 100644 --- a/apps/workflows/package.json +++ b/apps/workflows/package.json @@ -12,8 +12,8 @@ "@chronark/zod-bird": "0.3.9", "@clerk/nextjs": "^4.29.10", "@planetscale/database": "^1.16.0", - "@trigger.dev/nextjs": "^3.0.6", - "@trigger.dev/sdk": "^3.0.6", + "@trigger.dev/nextjs": "^3.0.7", + "@trigger.dev/sdk": "^3.0.7", "@trigger.dev/slack": "^2.3.18", "@unkey/billing": "workspace:^", "@unkey/db": "workspace:^", @@ -26,7 +26,7 @@ "react-dom": "^18", "stripe": "^14.23.0", "zod": "^3.23.5", - "@trigger.dev/react": "^3.0.6" + "@trigger.dev/react": "^3.0.7" }, "devDependencies": { "@types/node": "^20.14.9", diff --git a/internal/db/src/schema/keys.ts b/internal/db/src/schema/keys.ts index 9411def357..afba30984f 100644 --- a/internal/db/src/schema/keys.ts +++ b/internal/db/src/schema/keys.ts @@ -8,6 +8,7 @@ import { mysqlEnum, mysqlTable, text, + tinyint, uniqueIndex, varchar, } from "drizzle-orm/mysql-core"; @@ -63,7 +64,7 @@ export const keys = mysqlTable( * You can refill uses to keys at a desired interval */ refillInterval: mysqlEnum("refill_interval", ["daily", "monthly"]), - refillDay: int("refill_day"), + refillDay: tinyint("refill_day"), refillAmount: int("refill_amount"), lastRefillAt: datetime("last_refill_at", { fsp: 3 }), /** diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b2055f9a08..00e32984a7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -961,11 +961,14 @@ importers: specifier: ^1.16.0 version: 1.18.0 '@trigger.dev/nextjs': - specifier: ^2.3.18 - version: 2.3.18(@trigger.dev/sdk@2.3.18)(next@14.2.10) + specifier: ^3.0.7 + version: 3.0.7(@trigger.dev/sdk@3.0.7)(next@14.2.10) + '@trigger.dev/react': + specifier: ^3.0.7 + version: 3.0.7(react-dom@18.3.1)(react@18.3.1) '@trigger.dev/sdk': - specifier: ^3.0.6 - version: 3.0.6 + specifier: ^3.0.7 + version: 3.0.7 '@trigger.dev/slack': specifier: ^2.3.18 version: 2.3.18 @@ -7202,7 +7205,7 @@ packages: resolution: {integrity: sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==} engines: {node: '>=8.0.0'} dependencies: - tslib: 2.4.1 + tslib: 2.7.0 dev: false /@peculiar/webcrypto@1.4.1: @@ -7212,7 +7215,7 @@ packages: '@peculiar/asn1-schema': 2.3.13 '@peculiar/json-schema': 1.1.12 pvtsutils: 1.3.5 - tslib: 2.4.1 + tslib: 2.7.0 webcrypto-core: 1.8.0 dev: false @@ -11191,16 +11194,59 @@ packages: - supports-color - utf-8-validate dev: false - /@trigger.dev/nextjs@2.3.18(@trigger.dev/sdk@2.3.18)(next@14.2.10): - resolution: {integrity: sha512-ZS0RTZNrzGEKfOLQLYt3iqlNquD7pd39Hpd/+2tvRCaPSQ3qPYQvdjBSueW0OURZSQSiNno5VUYR5vbVBcAaXA==} - engines: {node: '>=18.0.0'} - peerDependencies: - '@trigger.dev/sdk': ^2.3.18 - next: '>=12.0.0' + + /@trigger.dev/core@3.0.6: + resolution: {integrity: sha512-NpRRF4WKuUpX40YHQyhlouAe4LYMU3Vm74SB3fnHU4tKoRGAgtAc8zLX1HwzaWxItK0l3DBlgXaxB14SJaB/gw==} + engines: {node: '>=18.20.0'} dependencies: - '@trigger.dev/sdk': 2.3.18 - debug: 4.3.7(supports-color@8.1.1) - next: 14.2.10(@babel/core@7.25.2)(@opentelemetry/api@1.4.1)(react-dom@18.3.1)(react@18.3.1) + '@google-cloud/precise-date': 4.0.0 + '@opentelemetry/api': 1.4.1 + '@opentelemetry/api-logs': 0.52.1 + '@opentelemetry/exporter-logs-otlp-http': 0.52.1(@opentelemetry/api@1.4.1) + '@opentelemetry/exporter-trace-otlp-http': 0.52.1(@opentelemetry/api@1.4.1) + '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.4.1) + '@opentelemetry/resources': 1.13.0(@opentelemetry/api@1.4.1) + '@opentelemetry/sdk-logs': 0.52.1(@opentelemetry/api@1.4.1) + '@opentelemetry/sdk-node': 0.52.1(@opentelemetry/api@1.4.1) + '@opentelemetry/sdk-trace-base': 1.13.0(@opentelemetry/api@1.4.1) + '@opentelemetry/sdk-trace-node': 1.13.0(@opentelemetry/api@1.4.1) + '@opentelemetry/semantic-conventions': 1.13.0 + execa: 8.0.1 + humanize-duration: 3.32.1 + socket.io-client: 4.7.5 + superjson: 2.2.1 + zod: 3.22.3 + zod-error: 1.5.0 + zod-validation-error: 1.5.0(zod@3.22.3) + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: false + + /@trigger.dev/core@3.0.7: + resolution: {integrity: sha512-3HfnAseLvGvCcK8zEdYiblESPiwfwfGdiWRXW84dtR2jCl5g8/e42STZ31qvg4J8+nHegCpTVfgIaV8iEpDZZA==} + engines: {node: '>=18.20.0'} + dependencies: + '@google-cloud/precise-date': 4.0.0 + '@opentelemetry/api': 1.4.1 + '@opentelemetry/api-logs': 0.52.1 + '@opentelemetry/exporter-logs-otlp-http': 0.52.1(@opentelemetry/api@1.4.1) + '@opentelemetry/exporter-trace-otlp-http': 0.52.1(@opentelemetry/api@1.4.1) + '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.4.1) + '@opentelemetry/resources': 1.13.0(@opentelemetry/api@1.4.1) + '@opentelemetry/sdk-logs': 0.52.1(@opentelemetry/api@1.4.1) + '@opentelemetry/sdk-node': 0.52.1(@opentelemetry/api@1.4.1) + '@opentelemetry/sdk-trace-base': 1.13.0(@opentelemetry/api@1.4.1) + '@opentelemetry/sdk-trace-node': 1.13.0(@opentelemetry/api@1.4.1) + '@opentelemetry/semantic-conventions': 1.13.0 + execa: 8.0.1 + humanize-duration: 3.32.1 + socket.io-client: 4.7.5 + superjson: 2.2.1 + zod: 3.22.3 + zod-error: 1.5.0 + zod-validation-error: 1.5.0(zod@3.22.3) transitivePeerDependencies: - bufferutil - supports-color @@ -11221,36 +11267,34 @@ packages: - supports-color dev: false - /@trigger.dev/nextjs@3.0.6(@trigger.dev/sdk@3.0.6)(next@14.1.4): - resolution: {integrity: sha512-F+bo8h0TXk9RHwZaufkpMYs019If7/pBuGNDNkLEPsGTvecD/81yvs5ZwvdhZd6sTJi5LzPKRaa9c3GyXtmrDQ==} + /@trigger.dev/nextjs@3.0.7(@trigger.dev/sdk@3.0.7)(next@14.2.10): + resolution: {integrity: sha512-7O9nxvi7q1jw3ap0rQiGl+kc+eFTNnSwjU0ZYCJeGJ3JDoIwi8DxqQnE1/jpqNpLPHc++szgzK8KjMEY39IVAA==} engines: {node: '>=18.0.0'} peerDependencies: '@trigger.dev/sdk': ~2.3.0 || ^3.0.0 next: '>=12.0.0' dependencies: - '@trigger.dev/sdk': 3.0.6 + '@trigger.dev/sdk': 3.0.7 debug: 4.3.7(supports-color@8.1.1) - next: 14.1.4(@babel/core@7.25.2)(@opentelemetry/api@1.4.1)(react-dom@18.3.1)(react@18.3.1) + next: 14.2.10(@babel/core@7.25.2)(@opentelemetry/api@1.4.1)(react-dom@18.3.1)(react@18.3.1) transitivePeerDependencies: - supports-color dev: false - /@trigger.dev/react@3.0.6(react-dom@18.3.1)(react@18.3.1): - resolution: {integrity: sha512-3BcvQaCuqWIDBIowCLsDR/g6kjI64SASuaMaHe/P0VByvPfBoCbJ4tyuu2oD1FN67TXVNgq0xMte6Rw5E0JIxw==} + /@trigger.dev/react@3.0.7(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-rF0XKkRFgbqp2LxeKNOqpXW8VKTnDmDkLYmZEMXdlNIgA4e4TCbsEuhanPuTdVtx5OOdptnB2TYDaufVhvv0IQ==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18 dependencies: '@tanstack/react-query': 5.0.0-beta.2(react-dom@18.3.1)(react@18.3.1) - '@trigger.dev/core': 3.0.6 + '@trigger.dev/core': 2.3.19 debug: 4.3.7(supports-color@8.1.1) react: 18.3.1 zod: 3.22.3 transitivePeerDependencies: - - bufferutil - react-dom - react-native - supports-color - - utf-8-validate dev: false /@trigger.dev/sdk@2.3.19: @@ -11332,6 +11376,30 @@ packages: - utf-8-validate dev: false + /@trigger.dev/sdk@3.0.7: + resolution: {integrity: sha512-8ZQieOmibq4B6yl574G1zDJNfR34GcZXPG5hh0qpI9+CmTsGdnR8LMO68MJSiAooPWTcMqvZAL6ymIfUyP0+/g==} + engines: {node: '>=18.20.0'} + dependencies: + '@opentelemetry/api': 1.4.1 + '@opentelemetry/api-logs': 0.52.1 + '@opentelemetry/semantic-conventions': 1.13.0 + '@trigger.dev/core': 3.0.7 + chalk: 5.3.0 + cronstrue: 2.50.0 + debug: 4.3.7(supports-color@8.1.1) + evt: 2.5.7 + slug: 6.1.0 + terminal-link: 3.0.0 + ulid: 2.3.0 + uuid: 9.0.1 + ws: 8.18.0 + zod: 3.22.3 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: false + /@trigger.dev/slack@2.3.18: resolution: {integrity: sha512-YyamKAIxfEbAaMXRZeWJ0k7HzvCermRjWweiHovY61j13Q+JmALcxvLXOYqtEZXS1p84T/Kmkk2iJ53VHLDsAA==} engines: {node: '>=16.8.0'} @@ -14768,7 +14836,7 @@ packages: resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} dependencies: no-case: 3.0.4 - tslib: 2.4.1 + tslib: 2.7.0 dev: false /dot-prop@6.0.1: @@ -15996,7 +16064,7 @@ packages: engines: {node: '>= 10.17.0'} hasBin: true dependencies: - debug: 4.3.4 + debug: 4.3.7(supports-color@8.1.1) get-stream: 5.2.0 yauzl: 2.10.0 optionalDependencies: @@ -18471,7 +18539,7 @@ packages: /lower-case@2.0.2: resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} dependencies: - tslib: 2.4.1 + tslib: 2.7.0 dev: false /lowercase-keys@3.0.0: @@ -19867,6 +19935,7 @@ packages: /minipass@6.0.2: resolution: {integrity: sha512-MzWSV5nYVT7mVyWCwn2o7JH13w2TBRmmSqSRCKzTw+lmft9X4z+3wjvs06Tzijo5z4W/kahUCDpRXTF+ZrmF/w==} engines: {node: '>=16 || 14 >=14.17'} + dev: true /minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} @@ -20253,86 +20322,6 @@ packages: - babel-plugin-macros dev: false - /next@14.1.4(@babel/core@7.25.2)(@opentelemetry/api@1.4.1)(react-dom@18.3.1)(react@18.3.1): - resolution: {integrity: sha512-1WTaXeSrUwlz/XcnhGTY7+8eiaFvdet5z9u3V2jb+Ek1vFo0VhHKSAIJvDWfQpttWjnyw14kBeq28TPq7bTeEQ==} - engines: {node: '>=18.17.0'} - hasBin: true - peerDependencies: - '@opentelemetry/api': ^1.1.0 - react: ^18.2.0 - react-dom: ^18.2.0 - sass: ^1.3.0 - peerDependenciesMeta: - '@opentelemetry/api': - optional: true - sass: - optional: true - dependencies: - '@next/env': 14.1.4 - '@opentelemetry/api': 1.4.1 - '@swc/helpers': 0.5.2 - busboy: 1.6.0 - caniuse-lite: 1.0.30001663 - graceful-fs: 4.2.11 - postcss: 8.4.31 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - styled-jsx: 5.1.1(@babel/core@7.25.2)(react@18.3.1) - optionalDependencies: - '@next/swc-darwin-arm64': 14.1.4 - '@next/swc-darwin-x64': 14.1.4 - '@next/swc-linux-arm64-gnu': 14.1.4 - '@next/swc-linux-arm64-musl': 14.1.4 - '@next/swc-linux-x64-gnu': 14.1.4 - '@next/swc-linux-x64-musl': 14.1.4 - '@next/swc-win32-arm64-msvc': 14.1.4 - '@next/swc-win32-ia32-msvc': 14.1.4 - '@next/swc-win32-x64-msvc': 14.1.4 - transitivePeerDependencies: - - '@babel/core' - - babel-plugin-macros - - /next@14.2.3(@babel/core@7.25.2)(react-dom@18.3.1)(react@18.3.1): - resolution: {integrity: sha512-dowFkFTR8v79NPJO4QsBUtxv0g9BrS/phluVpMAt2ku7H+cbcBJlopXjkWlwxrk/xGqMemr7JkGPGemPrLLX7A==} - engines: {node: '>=18.17.0'} - hasBin: true - peerDependencies: - '@opentelemetry/api': ^1.1.0 - '@playwright/test': ^1.41.2 - react: ^18.2.0 - react-dom: ^18.2.0 - sass: ^1.3.0 - peerDependenciesMeta: - '@opentelemetry/api': - optional: true - '@playwright/test': - optional: true - sass: - optional: true - dependencies: - '@next/env': 14.2.3 - '@swc/helpers': 0.5.5 - busboy: 1.6.0 - caniuse-lite: 1.0.30001663 - graceful-fs: 4.2.11 - postcss: 8.4.31 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - styled-jsx: 5.1.1(@babel/core@7.25.2)(react@18.3.1) - optionalDependencies: - '@next/swc-darwin-arm64': 14.2.3 - '@next/swc-darwin-x64': 14.2.3 - '@next/swc-linux-arm64-gnu': 14.2.3 - '@next/swc-linux-arm64-musl': 14.2.3 - '@next/swc-linux-x64-gnu': 14.2.3 - '@next/swc-linux-x64-musl': 14.2.3 - '@next/swc-win32-arm64-msvc': 14.2.3 - '@next/swc-win32-ia32-msvc': 14.2.3 - '@next/swc-win32-x64-msvc': 14.2.3 - transitivePeerDependencies: - - '@babel/core' - - babel-plugin-macros - dev: false /next@14.2.10(@babel/core@7.25.2)(@opentelemetry/api@1.4.1)(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-sDDExXnh33cY3RkS9JuFEKaS4HmlWmDKP1VJioucCG6z5KuA008DPsDZOzi8UfqEk3Ii+2NCQSJrfbEWtZZfww==} engines: {node: '>=18.17.0'} @@ -20385,7 +20374,7 @@ packages: resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} dependencies: lower-case: 2.0.2 - tslib: 2.4.1 + tslib: 2.7.0 dev: false /node-addon-api@7.1.1: @@ -21069,7 +21058,7 @@ packages: engines: {node: '>=16 || 14 >=14.18'} dependencies: lru-cache: 10.4.3 - minipass: 6.0.2 + minipass: 7.1.2 /path-to-regexp@0.1.10: resolution: {integrity: sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==} @@ -23160,7 +23149,7 @@ packages: resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} dependencies: dot-case: 3.0.4 - tslib: 2.4.1 + tslib: 2.7.0 dev: false /snakecase-keys@3.2.1: From 29558b56274d819ee9954f485f815ba3e7a363e2 Mon Sep 17 00:00:00 2001 From: Michael Silva Date: Thu, 26 Sep 2024 09:44:31 -0400 Subject: [PATCH 07/29] tests added --- .../routes/v1_keys_createKey.error.test.ts | 9 +- .../routes/v1_keys_createKey.happy.test.ts | 117 +++++++++++------- apps/api/src/routes/v1_keys_createKey.ts | 26 +++- .../routes/v1_keys_updateKey.error.test.ts | 31 +++++ .../routes/v1_keys_updateKey.happy.test.ts | 48 +++++++ apps/api/src/routes/v1_keys_updateKey.ts | 17 ++- .../[keyId]/settings/update-key-remaining.tsx | 6 +- .../[apiId]/keys/[keyAuthId]/new/client.tsx | 2 +- apps/dashboard/lib/zod-helper.ts | 2 +- apps/workflows/jobs/refill-daily.ts | 104 ++++++++++------ apps/workflows/package.json | 2 +- 11 files changed, 263 insertions(+), 101 deletions(-) diff --git a/apps/api/src/routes/v1_keys_createKey.error.test.ts b/apps/api/src/routes/v1_keys_createKey.error.test.ts index 2bcfc72b0c..bf776200b3 100644 --- a/apps/api/src/routes/v1_keys_createKey.error.test.ts +++ b/apps/api/src/routes/v1_keys_createKey.error.test.ts @@ -11,7 +11,6 @@ import type { V1KeysCreateKeyRequest, V1KeysCreateKeyResponse } from "./v1_keys_ test("when the api does not exist", async (t) => { const h = await IntegrationHarness.init(t); const apiId = newId("api"); - const root = await h.createRootKey([`api.${apiId}.create_key`]); /* The code snippet is making a POST request to the "/v1/keys.createKey" endpoint with the specified headers. It is using the `h.post` method from the `Harness` instance to send the request. The generic types `` specify the request payload and response types respectively. */ @@ -139,9 +138,8 @@ test("reject invalid refill config", async (t) => { refill: { amount: 100, refillDay: 4, - interval: "daily" - - } + interval: "daily", + }, }, }); expect(res.status).toEqual(400); @@ -149,8 +147,7 @@ test("reject invalid refill config", async (t) => { error: { code: "BAD_REQUEST", docs: "https://unkey.dev/docs/api-reference/errors/code/BAD_REQUEST", - message: "refillDay must be null if interval is daily", + message: "when interval is set to 'daily', 'refillDay' must be null.", }, }); }); - diff --git a/apps/api/src/routes/v1_keys_createKey.happy.test.ts b/apps/api/src/routes/v1_keys_createKey.happy.test.ts index 8b02cec8e5..8f671fcd3d 100644 --- a/apps/api/src/routes/v1_keys_createKey.happy.test.ts +++ b/apps/api/src/routes/v1_keys_createKey.happy.test.ts @@ -241,48 +241,48 @@ describe("permissions", () => { }); }); -describe("with encryption", () => { - test("encrypts a key", async (t) => { - const h = await IntegrationHarness.init(t); - - await h.db.primary - .update(schema.keyAuth) - .set({ - storeEncryptedKeys: true, - }) - .where(eq(schema.keyAuth.id, h.resources.userKeyAuth.id)); - - const root = await h.createRootKey([ - `api.${h.resources.userApi.id}.create_key`, - `api.${h.resources.userApi.id}.encrypt_key`, - ]); - - const res = await h.post({ - url: "/v1/keys.createKey", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${root.key}`, - }, - body: { - apiId: h.resources.userApi.id, - recoverable: true, - }, - }); - - expect(res.status, `expected 200, received: ${JSON.stringify(res, null, 2)}`).toBe(200); - - const key = await h.db.primary.query.keys.findFirst({ - where: (table, { eq }) => eq(table.id, res.body.keyId), - with: { - encrypted: true, - }, - }); - expect(key).toBeDefined(); - expect(key!.encrypted).toBeDefined(); - expect(typeof key?.encrypted?.encrypted).toBe("string"); - expect(typeof key?.encrypted?.encryptionKeyId).toBe("string"); - }); -}); +// describe("with encryption", () => { +// test("encrypts a key", async (t) => { +// const h = await IntegrationHarness.init(t); + +// await h.db.primary +// .update(schema.keyAuth) +// .set({ +// storeEncryptedKeys: true, +// }) +// .where(eq(schema.keyAuth.id, h.resources.userKeyAuth.id)); + +// const root = await h.createRootKey([ +// `api.${h.resources.userApi.id}.create_key`, +// `api.${h.resources.userApi.id}.encrypt_key`, +// ]); + +// const res = await h.post({ +// url: "/v1/keys.createKey", +// headers: { +// "Content-Type": "application/json", +// Authorization: `Bearer ${root.key}`, +// }, +// body: { +// apiId: h.resources.userApi.id, +// recoverable: true, +// }, +// }); + +// expect(res.status, `expected 200, received: ${JSON.stringify(res, null, 2)}`).toBe(200); + +// const key = await h.db.primary.query.keys.findFirst({ +// where: (table, { eq }) => eq(table.id, res.body.keyId), +// with: { +// encrypted: true, +// }, +// }); +// expect(key).toBeDefined(); +// expect(key!.encrypted).toBeDefined(); +// expect(typeof key?.encrypted?.encrypted).toBe("string"); +// expect(typeof key?.encrypted?.encryptionKeyId).toBe("string"); +// }); +// }); test("creates a key with environment", async (t) => { const h = await IntegrationHarness.init(t); @@ -467,4 +467,37 @@ describe("with externalId", () => { expect(key!.identity!.id).toEqual(identity.id); }); }); + describe("Should default last day of month if none provided", () => { + test("should provide default value", async (t) => { + const h = await IntegrationHarness.init(t); + const date = new Date(); + const lastDate = date.getMonth() + 1; + const root = await h.createRootKey([`api.${h.resources.userApi.id}.create_key`]); + + const res = await h.post({ + url: "/v1/keys.createKey", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${root.key}`, + }, + body: { + apiId: h.resources.userApi.id, + remaining: 10, + refill: { + interval: "monthly", + amount: 20, + refillDay: undefined, + }, + }, + }); + + expect(res.status, `expected 200, received: ${JSON.stringify(res, null, 2)}`).toBe(200); + + const key = await h.db.primary.query.keys.findFirst({ + where: (table, { eq }) => eq(table.id, res.body.keyId), + }); + expect(key).toBeDefined(); + expect(key!.refillDay).toEqual(lastDate); + }); + }); }); diff --git a/apps/api/src/routes/v1_keys_createKey.ts b/apps/api/src/routes/v1_keys_createKey.ts index cb682b5824..dbe5d2a147 100644 --- a/apps/api/src/routes/v1_keys_createKey.ts +++ b/apps/api/src/routes/v1_keys_createKey.ts @@ -117,11 +117,15 @@ When validating a key, we will return this back to you, so you can clearly ident description: "The number of verifications to refill for each occurrence is determined individually for each key.", }), - refillDay: z.number().min(1).max(31).optional().openapi({ - description: - `The day of the month, when we will refill the remaining verifications. To refill on the 15th of each month, set 'refillDay': 15. + refillDay: z + .number() + .min(1) + .max(31) + .optional() + .openapi({ + description: `The day of the month, when we will refill the remaining verifications. To refill on the 15th of each month, set 'refillDay': 15. If the day does not exist, for example you specified the 30th and it's february, we will refill them on the last day of the month instead.`, - }), + }), }) .optional() .openapi({ @@ -315,6 +319,12 @@ export const registerV1KeysCreateKey = (app: App) => message: "remaining must be set if you are using refill.", }); } + if (req.refill?.refillDay && req.refill.interval === "daily") { + throw new UnkeyApiError({ + code: "BAD_REQUEST", + message: "when interval is set to 'daily', 'refillDay' must be null.", + }); + } /** * Set up an api for production */ @@ -332,6 +342,12 @@ export const registerV1KeysCreateKey = (app: App) => : Promise.resolve(null), ]); const newKey = await retry(5, async (attempt) => { + const date = new Date(); + let lastDayOfMonth = new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate(); + if (req.refill?.refillDay && req?.refill?.refillDay <= lastDayOfMonth) { + lastDayOfMonth = req.refill.refillDay; + } + if (attempt > 1) { logger.warn("retrying key creation", { attempt, @@ -363,7 +379,7 @@ export const registerV1KeysCreateKey = (app: App) => ratelimitDuration: req.ratelimit?.duration ?? req.ratelimit?.refillInterval, remaining: req.remaining, refillInterval: req.refill?.interval, - refillDay: req.refill?.interval === "monthly" ? req.refill.refillDay : null, + refillDay: req.refill?.interval === "monthly" ? lastDayOfMonth : null, refillAmount: req.refill?.amount, lastRefillAt: req.refill?.interval ? new Date() : null, deletedAt: null, diff --git a/apps/api/src/routes/v1_keys_updateKey.error.test.ts b/apps/api/src/routes/v1_keys_updateKey.error.test.ts index b9954a3642..9a231fe01b 100644 --- a/apps/api/src/routes/v1_keys_updateKey.error.test.ts +++ b/apps/api/src/routes/v1_keys_updateKey.error.test.ts @@ -31,3 +31,34 @@ test("when the key does not exist", async (t) => { }, }); }); +test("reject invalid refill config", async (t) => { + const h = await IntegrationHarness.init(t); + const keyId = newId("test"); + const root = await h.createRootKey([`api.${h.resources.userApi.id}.create_key`]); + /* The code snippet is making a POST request to the "/v1/keys.createKey" endpoint with the specified headers. It is using the `h.post` method from the `Harness` instance to send the request. The generic types `` specify the request payload and response types respectively. */ + + const res = await h.post({ + url: "/v1/keys.updateKey", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${root.key}`, + }, + body: { + keyId, + remaining: 10, + refill: { + amount: 100, + refillDay: 4, + interval: "daily", + }, + }, + }); + expect(res.status).toEqual(400); + expect(res.body).toMatchObject({ + error: { + code: "BAD_REQUEST", + docs: "https://unkey.dev/docs/api-reference/errors/code/BAD_REQUEST", + message: "when interval is set to 'daily', 'refillDay' must be null.", + }, + }); +}); diff --git a/apps/api/src/routes/v1_keys_updateKey.happy.test.ts b/apps/api/src/routes/v1_keys_updateKey.happy.test.ts index 30c1d555e1..54b580adf8 100644 --- a/apps/api/src/routes/v1_keys_updateKey.happy.test.ts +++ b/apps/api/src/routes/v1_keys_updateKey.happy.test.ts @@ -1013,3 +1013,51 @@ test("update ratelimit should not disable it", async (t) => { expect(verify.body.ratelimit!.limit).toBe(5); expect(verify.body.ratelimit!.remaining).toBe(4); }); +describe("Should default last day of month if none provided", () => { + test("should provide default value", async (t) => { + const h = await IntegrationHarness.init(t); + + const key = { + id: newId("test"), + keyAuthId: h.resources.userKeyAuth.id, + workspaceId: h.resources.userWorkspace.id, + start: "test", + name: "test", + hash: await sha256(new KeyV1({ byteLength: 16 }).toString()), + + createdAt: new Date(), + }; + await h.db.primary.insert(schema.keys).values(key); + const root = await h.createRootKey([`api.${h.resources.userApi.id}.update_key`]); + const date = new Date(); + const lastDate = date.getMonth() + 1; + const res = await h.post({ + url: "/v1/keys.updateKey", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${root.key}`, + }, + body: { + keyId: key.id, + remaining: 10, + refill: { + interval: "monthly", + amount: 130, + refillDay: undefined, + }, + enabled: true, + }, + }); + + expect(res.status, `expected 200, received: ${JSON.stringify(res, null, 2)}`).toBe(200); + + const found = await h.db.primary.query.keys.findFirst({ + where: (table, { eq }) => eq(table.id, key.id), + }); + expect(found).toBeDefined(); + expect(found?.remaining).toEqual(10); + expect(found?.refillAmount).toEqual(1030); + expect(found?.refillInterval).toEqual("monthly"); + expect(found?.refillDay).toEqual(lastDate); + }); +}); diff --git a/apps/api/src/routes/v1_keys_updateKey.ts b/apps/api/src/routes/v1_keys_updateKey.ts index 5856fa2a34..c238245654 100644 --- a/apps/api/src/routes/v1_keys_updateKey.ts +++ b/apps/api/src/routes/v1_keys_updateKey.ts @@ -279,9 +279,7 @@ export const registerV1KeysUpdate = (app: App) => app.openapi(route, async (c) => { const req = c.req.valid("json"); const { cache, db, usageLimiter, analytics, rbac } = c.get("services"); - const auth = await rootKeyAuth(c); - const key = await db.primary.query.keys.findFirst({ where: (table, { eq }) => eq(table.id, req.keyId), with: { @@ -338,7 +336,12 @@ export const registerV1KeysUpdate = (app: App) => message: "Cannot set refill on a key with unlimited requests", }); } - + if (req.refill?.refillDay && req.refill.interval === "daily") { + throw new UnkeyApiError({ + code: "BAD_REQUEST", + message: "when interval is set to 'daily', 'refillDay' must be null.", + }); + } const authorizedWorkspaceId = auth.authorizedWorkspaceId; const rootKeyId = auth.key.id; @@ -349,7 +352,11 @@ export const registerV1KeysUpdate = (app: App) => : externalId === null ? null : (await upsertIdentity(db.primary, authorizedWorkspaceId, externalId)).id; - + const date = new Date(); + let lastDayOfMonth = new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate(); + if (req.refill?.refillDay && req?.refill?.refillDay <= lastDayOfMonth) { + lastDayOfMonth = req.refill.refillDay; + } await db.primary .update(schema.keys) .set({ @@ -380,7 +387,7 @@ export const registerV1KeysUpdate = (app: App) => : req.ratelimit?.duration ?? req.ratelimit?.refillInterval ?? null, refillInterval: req.refill === null ? null : req.refill?.interval, refillAmount: req.refill === null ? null : req.refill?.amount, - refillDay: req.refill?.interval !== "monthly" ? null : req.refill.refillDay, + refillDay: req.refill?.interval === "monthly" ? lastDayOfMonth : null, lastRefillAt: req.refill == null || req.refill?.amount == null ? null : new Date(), enabled: req.enabled, }) diff --git a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/update-key-remaining.tsx b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/update-key-remaining.tsx index efd6a0a858..7db2199c2a 100644 --- a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/update-key-remaining.tsx +++ b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/settings/update-key-remaining.tsx @@ -51,7 +51,7 @@ const formSchema = z.object({ }) .positive() .optional(), - refillDay: z.coerce + refillDay: z.coerce .number({ errorMap: (issue, { defaultError }) => ({ message: @@ -231,9 +231,7 @@ export const UpdateKeyRemaining: React.FC = ({ apiKey }) => { /> ( diff --git a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/new/client.tsx b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/new/client.tsx index b74673aa71..5176f096ee 100644 --- a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/new/client.tsx +++ b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/new/client.tsx @@ -648,7 +648,7 @@ export const CreateKey: React.FC = ({ apiId, keyAuthId }) => { )} /> - + ({ message: diff --git a/apps/workflows/jobs/refill-daily.ts b/apps/workflows/jobs/refill-daily.ts index 0961000667..ca6de94f35 100644 --- a/apps/workflows/jobs/refill-daily.ts +++ b/apps/workflows/jobs/refill-daily.ts @@ -1,4 +1,4 @@ -import { connectDatabase, eq, schema } from "@/lib/db"; +import { type Key, connectDatabase, eq, schema } from "@/lib/db"; import { env } from "@/lib/env"; import { Tinybird } from "@/lib/tinybird"; import { client } from "@/trigger"; @@ -14,24 +14,48 @@ client.defineJob({ run: async (_payload, io, _ctx) => { const date = _payload.ts; + const lastDayOfMonth = new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate(); + const newDate = date.getDate(); const today = date.getUTCDate(); const db = connectDatabase(); const tb = new Tinybird(env().TINYBIRD_TOKEN); + let keys: Key[]; - const keys = await io.runTask("list keys for refill", () => - db.query.keys.findMany({ - where: (table, { isNotNull, isNull, eq, and, gt, or }) => - and( - isNull(table.deletedAt), - isNotNull(table.refillAmount), - gt(table.refillAmount, table.remaining), - or( - isNull(table.refillDay), - eq(table.refillDay, today), - ) - ), - }), - ); + if (newDate !== lastDayOfMonth) { + keys = await io.runTask("list keys for refill", () => + db.query.keys + .findMany({ + where: (table, { isNotNull, isNull, eq, and, gt, or }) => + and( + isNull(table.deletedAt), + isNotNull(table.refillAmount), + gt(table.refillAmount, table.remaining), + or(isNull(table.refillDay), eq(table.refillDay, today)), + ), + }) + .catch((error) => { + console.error(`Query on keys failed, ${error}`); + throw new Error(`Query on keys failed, ${error}`); + }), + ); + } else { + keys = await io.runTask("list keys for end of month refill", () => + db.query.keys + .findMany({ + where: (table, { isNotNull, isNull, gte, and, gt, or }) => + and( + isNull(table.deletedAt), + isNotNull(table.refillAmount), + gt(table.refillAmount, table.remaining), + or(isNull(table.refillDay), gte(table.refillDay, lastDayOfMonth)), + ), + }) + .catch((error) => { + console.error(`Query on keys failed, ${error}`); + throw new Error(`Query on keys failed, ${error}`); + }), + ); + } io.logger.info(`found ${keys.length} keys with refill set for today`); for (const key of keys) { @@ -42,32 +66,40 @@ client.defineJob({ remaining: key.refillAmount, lastRefillAt: new Date(), }) - .where(eq(schema.keys.id, key.id)); + .where(eq(schema.keys.id, key.id)) + .catch((error) => { + console.error(`Failed to update remaining ${key.id}`); + console.error(`With error ${error}`); + }); }); await io.runTask(`create audit log refilling ${key.id}`, async () => { - await tb.ingestAuditLogs({ - workspaceId: key.workspaceId, - event: "key.update", - actor: { - type: "system", - id: "trigger", - }, - description: `Refilled ${key.id} to ${key.refillAmount}`, - resources: [ - { - type: "workspace", - id: key.workspaceId, + await tb + .ingestAuditLogs({ + workspaceId: key.workspaceId, + event: "key.update", + actor: { + type: "system", + id: "trigger", }, - { - type: "key", - id: key.id, + description: `Refilled ${key.id} to ${key.refillAmount}`, + resources: [ + { + type: "workspace", + id: key.workspaceId, + }, + { + type: "key", + id: key.id, + }, + ], + context: { + location: "trigger", }, - ], - context: { - location: "trigger", - }, - }); + }) + .catch((_error) => { + console.error(`failed to update Audit Logs for ${key.id}`); + }); }); } return { diff --git a/apps/workflows/package.json b/apps/workflows/package.json index 729754f08a..6971750931 100644 --- a/apps/workflows/package.json +++ b/apps/workflows/package.json @@ -37,4 +37,4 @@ "trigger.dev": { "endpointId": "workflows-6eiv" } -} \ No newline at end of file +} From 71c668c47a37d0dc4a4a8454b6012775d467c3aa Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Thu, 26 Sep 2024 13:56:06 +0000 Subject: [PATCH 08/29] [autofix.ci] apply automated fixes --- apps/dashboard/lib/trpc/routers/key/updateRemaining.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dashboard/lib/trpc/routers/key/updateRemaining.ts b/apps/dashboard/lib/trpc/routers/key/updateRemaining.ts index 6449321b97..4d1d941573 100644 --- a/apps/dashboard/lib/trpc/routers/key/updateRemaining.ts +++ b/apps/dashboard/lib/trpc/routers/key/updateRemaining.ts @@ -38,7 +38,7 @@ export const updateKeyRemaining = rateLimitedProcedure(ratelimit.update) workspace: true, }, }); - const isMonthlyInterval = input.refill?.interval === "monthly"; + const isMonthlyInterval = input.refill?.interval === "monthly"; if (!key || key.workspace.tenantId !== ctx.tenant.id) { throw new TRPCError({ message: From da8b292c1df03494567a4b4e53af16a5325ff0cf Mon Sep 17 00:00:00 2001 From: Michael Silva Date: Thu, 26 Sep 2024 11:46:34 -0400 Subject: [PATCH 09/29] Fixed errors made changes from comments --- apps/api/src/routes/v1_keys_createKey.error.test.ts | 3 +-- apps/api/src/routes/v1_keys_updateKey.happy.test.ts | 2 +- apps/workflows/jobs/refill-daily.ts | 7 ++++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/api/src/routes/v1_keys_createKey.error.test.ts b/apps/api/src/routes/v1_keys_createKey.error.test.ts index bf776200b3..f9a197f956 100644 --- a/apps/api/src/routes/v1_keys_createKey.error.test.ts +++ b/apps/api/src/routes/v1_keys_createKey.error.test.ts @@ -119,11 +119,10 @@ test("when key recovery is not enabled", async (t) => { }); }); -test("reject invalid refill config", async (t) => { +test("reject invalid refill config when daily interval has non-null refillDay", async (t) => { const h = await IntegrationHarness.init(t); const root = await h.createRootKey([`api.${h.resources.userApi.id}.create_key`]); - /* The code snippet is making a POST request to the "/v1/keys.createKey" endpoint with the specified headers. It is using the `h.post` method from the `Harness` instance to send the request. The generic types `` specify the request payload and response types respectively. */ const res = await h.post({ url: "/v1/keys.createKey", diff --git a/apps/api/src/routes/v1_keys_updateKey.happy.test.ts b/apps/api/src/routes/v1_keys_updateKey.happy.test.ts index 54b580adf8..2b993bad65 100644 --- a/apps/api/src/routes/v1_keys_updateKey.happy.test.ts +++ b/apps/api/src/routes/v1_keys_updateKey.happy.test.ts @@ -1030,7 +1030,7 @@ describe("Should default last day of month if none provided", () => { await h.db.primary.insert(schema.keys).values(key); const root = await h.createRootKey([`api.${h.resources.userApi.id}.update_key`]); const date = new Date(); - const lastDate = date.getMonth() + 1; + const lastDate = new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate(); const res = await h.post({ url: "/v1/keys.updateKey", headers: { diff --git a/apps/workflows/jobs/refill-daily.ts b/apps/workflows/jobs/refill-daily.ts index ca6de94f35..6e561f7821 100644 --- a/apps/workflows/jobs/refill-daily.ts +++ b/apps/workflows/jobs/refill-daily.ts @@ -15,13 +15,14 @@ client.defineJob({ run: async (_payload, io, _ctx) => { const date = _payload.ts; const lastDayOfMonth = new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate(); - const newDate = date.getDate(); - const today = date.getUTCDate(); + const today = date.getDate(); const db = connectDatabase(); const tb = new Tinybird(env().TINYBIRD_TOKEN); let keys: Key[]; + console.log("today", today); + console.log("last day of month", lastDayOfMonth); - if (newDate !== lastDayOfMonth) { + if (today !== lastDayOfMonth) { keys = await io.runTask("list keys for refill", () => db.query.keys .findMany({ From cb283dc9e7d1ee94180efa221aeddee9a3e355cd Mon Sep 17 00:00:00 2001 From: Michael Silva Date: Thu, 26 Sep 2024 11:49:25 -0400 Subject: [PATCH 10/29] missed file --- apps/api/src/routes/v1_keys_updateKey.error.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/api/src/routes/v1_keys_updateKey.error.test.ts b/apps/api/src/routes/v1_keys_updateKey.error.test.ts index 9a231fe01b..e38ab22282 100644 --- a/apps/api/src/routes/v1_keys_updateKey.error.test.ts +++ b/apps/api/src/routes/v1_keys_updateKey.error.test.ts @@ -34,7 +34,7 @@ test("when the key does not exist", async (t) => { test("reject invalid refill config", async (t) => { const h = await IntegrationHarness.init(t); const keyId = newId("test"); - const root = await h.createRootKey([`api.${h.resources.userApi.id}.create_key`]); + const root = await h.createRootKey([`api.${h.resources.userApi.id}.update_key`]); /* The code snippet is making a POST request to the "/v1/keys.createKey" endpoint with the specified headers. It is using the `h.post` method from the `Harness` instance to send the request. The generic types `` specify the request payload and response types respectively. */ const res = await h.post({ From 6476ba5d293602b58cf32e1e5bccc5cc4019e4ce Mon Sep 17 00:00:00 2001 From: Michael Silva Date: Fri, 27 Sep 2024 13:26:20 -0400 Subject: [PATCH 11/29] store changes for review not complete --- apps/api/src/routes/schema.ts | 3 +- apps/api/src/routes/v1_apis_listKeys.ts | 2 +- .../routes/v1_keys_createKey.happy.test.ts | 86 ++--- apps/api/src/routes/v1_keys_createKey.ts | 8 +- .../routes/v1_keys_updateKey.error.test.ts | 3 +- .../routes/v1_keys_updateKey.happy.test.ts | 8 +- apps/api/src/routes/v1_keys_updateKey.ts | 2 +- .../v1_migrations_createKey.error.test.ts | 36 ++ .../v1_migrations_createKey.happy.test.ts | 44 +++ .../api/src/routes/v1_migrations_createKey.ts | 6 + .../[apiId]/keys/[keyAuthId]/new/client.tsx | 1 - apps/dashboard/lib/trpc/routers/key/create.ts | 2 +- apps/docs/libraries/ts/sdk/keys/get.mdx | 2 +- apps/docs/libraries/ts/sdk/keys/update.mdx | 2 +- apps/workflows/jobs/refill-daily.ts | 15 +- apps/workflows/package.json | 7 +- internal/db/src/schema/key_migrations.ts | 2 +- pnpm-lock.yaml | 327 +----------------- tools/migrate/auditlog-import.ts | 4 +- tools/migrate/main.ts | 2 + tools/migrate/tinybird-export.ts | 2 + 21 files changed, 175 insertions(+), 389 deletions(-) diff --git a/apps/api/src/routes/schema.ts b/apps/api/src/routes/schema.ts index a4917e2d9f..ba8c5f0686 100644 --- a/apps/api/src/routes/schema.ts +++ b/apps/api/src/routes/schema.ts @@ -68,7 +68,8 @@ export const keySchema = z }), refillDay: z.number().min(1).max(31).default(1).nullable().openapi({ description: - "The day verifications will refill each month, when interval is set to 'monthly'. Value is not zero-indexed making 1 the first day of the month.", + "The day verifications will refill each month, when interval is set to 'monthly'. Value is not zero-indexed making 1 the first day of the month. If left blank it will default to the last day of the month.", + example: 15, }), lastRefillAt: z.number().int().optional().openapi({ description: "The unix timestamp in miliseconds when the key was last refilled.", diff --git a/apps/api/src/routes/v1_apis_listKeys.ts b/apps/api/src/routes/v1_apis_listKeys.ts index 0eb9e65cfc..ab38e76085 100644 --- a/apps/api/src/routes/v1_apis_listKeys.ts +++ b/apps/api/src/routes/v1_apis_listKeys.ts @@ -317,7 +317,7 @@ export const registerV1ApisListKeys = (app: App) => ? { interval: k.refillInterval, amount: k.refillAmount, - refillDay: k.refillInterval === "monthly" ? k.refillDay : null, + refillDay: k.refillInterval === "monthly" && k.refillDay ? k.refillDay : null, lastRefillAt: k.lastRefillAt?.getTime(), } : undefined, diff --git a/apps/api/src/routes/v1_keys_createKey.happy.test.ts b/apps/api/src/routes/v1_keys_createKey.happy.test.ts index 8f671fcd3d..91eb63e5f3 100644 --- a/apps/api/src/routes/v1_keys_createKey.happy.test.ts +++ b/apps/api/src/routes/v1_keys_createKey.happy.test.ts @@ -241,48 +241,48 @@ describe("permissions", () => { }); }); -// describe("with encryption", () => { -// test("encrypts a key", async (t) => { -// const h = await IntegrationHarness.init(t); - -// await h.db.primary -// .update(schema.keyAuth) -// .set({ -// storeEncryptedKeys: true, -// }) -// .where(eq(schema.keyAuth.id, h.resources.userKeyAuth.id)); - -// const root = await h.createRootKey([ -// `api.${h.resources.userApi.id}.create_key`, -// `api.${h.resources.userApi.id}.encrypt_key`, -// ]); - -// const res = await h.post({ -// url: "/v1/keys.createKey", -// headers: { -// "Content-Type": "application/json", -// Authorization: `Bearer ${root.key}`, -// }, -// body: { -// apiId: h.resources.userApi.id, -// recoverable: true, -// }, -// }); - -// expect(res.status, `expected 200, received: ${JSON.stringify(res, null, 2)}`).toBe(200); - -// const key = await h.db.primary.query.keys.findFirst({ -// where: (table, { eq }) => eq(table.id, res.body.keyId), -// with: { -// encrypted: true, -// }, -// }); -// expect(key).toBeDefined(); -// expect(key!.encrypted).toBeDefined(); -// expect(typeof key?.encrypted?.encrypted).toBe("string"); -// expect(typeof key?.encrypted?.encryptionKeyId).toBe("string"); -// }); -// }); +describe("with encryption", () => { + test("encrypts a key", async (t) => { + const h = await IntegrationHarness.init(t); + + await h.db.primary + .update(schema.keyAuth) + .set({ + storeEncryptedKeys: true, + }) + .where(eq(schema.keyAuth.id, h.resources.userKeyAuth.id)); + + const root = await h.createRootKey([ + `api.${h.resources.userApi.id}.create_key`, + `api.${h.resources.userApi.id}.encrypt_key`, + ]); + + const res = await h.post({ + url: "/v1/keys.createKey", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${root.key}`, + }, + body: { + apiId: h.resources.userApi.id, + recoverable: true, + }, + }); + + expect(res.status, `expected 200, received: ${JSON.stringify(res, null, 2)}`).toBe(200); + + const key = await h.db.primary.query.keys.findFirst({ + where: (table, { eq }) => eq(table.id, res.body.keyId), + with: { + encrypted: true, + }, + }); + expect(key).toBeDefined(); + expect(key!.encrypted).toBeDefined(); + expect(typeof key?.encrypted?.encrypted).toBe("string"); + expect(typeof key?.encrypted?.encryptionKeyId).toBe("string"); + }); +}); test("creates a key with environment", async (t) => { const h = await IntegrationHarness.init(t); @@ -471,7 +471,7 @@ describe("with externalId", () => { test("should provide default value", async (t) => { const h = await IntegrationHarness.init(t); const date = new Date(); - const lastDate = date.getMonth() + 1; + const lastDate = new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate(); const root = await h.createRootKey([`api.${h.resources.userApi.id}.create_key`]); const res = await h.post({ diff --git a/apps/api/src/routes/v1_keys_createKey.ts b/apps/api/src/routes/v1_keys_createKey.ts index 36f03525dc..8a53fad15d 100644 --- a/apps/api/src/routes/v1_keys_createKey.ts +++ b/apps/api/src/routes/v1_keys_createKey.ts @@ -342,11 +342,13 @@ export const registerV1KeysCreateKey = (app: App) => ? upsertIdentity(db.primary, authorizedWorkspaceId, externalId) : Promise.resolve(null), ]); + const date = new Date(); + let lastDayOfMonth = new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate(); const newKey = await retry(5, async (attempt) => { - const date = new Date(); - let lastDayOfMonth = new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate(); if (req.refill?.refillDay && req?.refill?.refillDay <= lastDayOfMonth) { - lastDayOfMonth = req.refill.refillDay; + if (req.refill.interval === "monthly") { + lastDayOfMonth = req.refill.refillDay; + } } if (attempt > 1) { diff --git a/apps/api/src/routes/v1_keys_updateKey.error.test.ts b/apps/api/src/routes/v1_keys_updateKey.error.test.ts index e38ab22282..e305fc642f 100644 --- a/apps/api/src/routes/v1_keys_updateKey.error.test.ts +++ b/apps/api/src/routes/v1_keys_updateKey.error.test.ts @@ -3,6 +3,7 @@ import { expect, test } from "vitest"; import { newId } from "@unkey/id"; import { IntegrationHarness } from "src/pkg/testutil/integration-harness"; +import type { ErrorResponse } from "@/pkg/errors"; import type { V1KeysUpdateKeyRequest, V1KeysUpdateKeyResponse } from "./v1_keys_updateKey"; test("when the key does not exist", async (t) => { @@ -37,7 +38,7 @@ test("reject invalid refill config", async (t) => { const root = await h.createRootKey([`api.${h.resources.userApi.id}.update_key`]); /* The code snippet is making a POST request to the "/v1/keys.createKey" endpoint with the specified headers. It is using the `h.post` method from the `Harness` instance to send the request. The generic types `` specify the request payload and response types respectively. */ - const res = await h.post({ + const res = await h.post({ url: "/v1/keys.updateKey", headers: { "Content-Type": "application/json", diff --git a/apps/api/src/routes/v1_keys_updateKey.happy.test.ts b/apps/api/src/routes/v1_keys_updateKey.happy.test.ts index 2b993bad65..4ea3fa1874 100644 --- a/apps/api/src/routes/v1_keys_updateKey.happy.test.ts +++ b/apps/api/src/routes/v1_keys_updateKey.happy.test.ts @@ -1013,7 +1013,7 @@ test("update ratelimit should not disable it", async (t) => { expect(verify.body.ratelimit!.limit).toBe(5); expect(verify.body.ratelimit!.remaining).toBe(4); }); -describe("Should default last day of month if none provided", () => { +describe("When refillDay is omitted.", () => { test("should provide default value", async (t) => { const h = await IntegrationHarness.init(t); @@ -1029,8 +1029,6 @@ describe("Should default last day of month if none provided", () => { }; await h.db.primary.insert(schema.keys).values(key); const root = await h.createRootKey([`api.${h.resources.userApi.id}.update_key`]); - const date = new Date(); - const lastDate = new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate(); const res = await h.post({ url: "/v1/keys.updateKey", headers: { @@ -1056,8 +1054,8 @@ describe("Should default last day of month if none provided", () => { }); expect(found).toBeDefined(); expect(found?.remaining).toEqual(10); - expect(found?.refillAmount).toEqual(1030); + expect(found?.refillAmount).toEqual(130); expect(found?.refillInterval).toEqual("monthly"); - expect(found?.refillDay).toEqual(lastDate); + expect(found?.refillDay).toEqual(1); }); }); diff --git a/apps/api/src/routes/v1_keys_updateKey.ts b/apps/api/src/routes/v1_keys_updateKey.ts index 9d022e45a9..ffbd298e89 100644 --- a/apps/api/src/routes/v1_keys_updateKey.ts +++ b/apps/api/src/routes/v1_keys_updateKey.ts @@ -151,7 +151,7 @@ This field will become required in a future version.`, description: "The amount of verifications to refill for each occurrence is determined individually for each key.", }), - refillDay: z.number().min(3).max(31).optional().openapi({ + refillDay: z.number().min(1).max(31).optional().openapi({ description: "The day verifications will refill each month, when interval is set to 'monthly'", }), diff --git a/apps/api/src/routes/v1_migrations_createKey.error.test.ts b/apps/api/src/routes/v1_migrations_createKey.error.test.ts index 45523eaec5..14562b30e1 100644 --- a/apps/api/src/routes/v1_migrations_createKey.error.test.ts +++ b/apps/api/src/routes/v1_migrations_createKey.error.test.ts @@ -112,3 +112,39 @@ test("reject invalid ratelimit config", async (t) => { expect(res.status).toEqual(400); expect(res.body.error.code).toEqual("BAD_REQUEST"); }); +test("reject invalid refill config when daily interval has non-null refillDay", async (t) => { + const h = await IntegrationHarness.init(t); + const { key } = await h.createRootKey(["*"]); + + const res = await h.post({ + url: "/v1/migrations.createKeys", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${key}`, + }, + body: [ + { + start: "x", + hash: { + value: "x", + variant: "sha256_base64", + }, + apiId: h.resources.userApi.id, + remaining: 10, + refill: { + amount: 100, + refillDay: 4, + interval: "daily", + }, + }, + ], + }); + expect(res.status).toEqual(400); + expect(res.body).toMatchObject({ + error: { + code: "BAD_REQUEST", + docs: "https://unkey.dev/docs/api-reference/errors/code/BAD_REQUEST", + message: "when interval is set to 'daily', 'refillDay' must be null.", + }, + }); +}); diff --git a/apps/api/src/routes/v1_migrations_createKey.happy.test.ts b/apps/api/src/routes/v1_migrations_createKey.happy.test.ts index 6fe8f27b48..b92f5d3ebe 100644 --- a/apps/api/src/routes/v1_migrations_createKey.happy.test.ts +++ b/apps/api/src/routes/v1_migrations_createKey.happy.test.ts @@ -496,3 +496,47 @@ test("migrate and verify a key", async (t) => { expect(verifyRes.status).toBe(200); expect(verifyRes.body.valid).toEqual(true); }); + +describe("Should default last day of month if none provided", () => { + test("should provide default value", async (t) => { + const h = await IntegrationHarness.init(t); + const root = await h.createRootKey([`api.${h.resources.userApi.id}.create_key`]); + + const hash = await sha256(randomUUID()); + const res = await h.post({ + url: "/v1/migrations.createKeys", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${root.key}`, + }, + body: [ + { + start: "start_", + hash: { + value: hash, + variant: "sha256_base64", + }, + apiId: h.resources.userApi.id, + enabled: true, + remaining: 10, + refill: { + interval: "monthly", + amount: 100, + refillDay: undefined, + }, + }, + ], + }); + + expect(res.status, `expected 200, received: ${JSON.stringify(res, null, 2)}`).toBe(200); + + const found = await h.db.primary.query.keys.findFirst({ + where: (table, { eq }) => eq(table.id, res.body.keyIds[0]), + }); + expect(found).toBeDefined(); + expect(found?.remaining).toEqual(10); + expect(found?.refillAmount).toEqual(130); + expect(found?.refillInterval).toEqual("monthly"); + expect(found?.refillDay).toEqual(1); + }); +}); diff --git a/apps/api/src/routes/v1_migrations_createKey.ts b/apps/api/src/routes/v1_migrations_createKey.ts index 6061b9eabb..cca7838527 100644 --- a/apps/api/src/routes/v1_migrations_createKey.ts +++ b/apps/api/src/routes/v1_migrations_createKey.ts @@ -392,6 +392,12 @@ export const registerV1MigrationsCreateKeys = (app: App) => message: "provide either `hash` or `plaintext`", }); } + if (key.refill?.refillDay && key.refill.interval === "daily") { + throw new UnkeyApiError({ + code: "BAD_REQUEST", + message: "when interval is set to 'daily', 'refillDay' must be null.", + }); + } /** * Set up an api for production */ diff --git a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/new/client.tsx b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/new/client.tsx index 5176f096ee..d41c4c1e63 100644 --- a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/new/client.tsx +++ b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/new/client.tsx @@ -33,7 +33,6 @@ import { Textarea } from "@/components/ui/textarea"; import { toast } from "@/components/ui/toaster"; import { trpc } from "@/lib/trpc/client"; import { zodResolver } from "@hookform/resolvers/zod"; -import { min } from "d3-array"; import { AlertCircle } from "lucide-react"; import Link from "next/link"; import { useRouter } from "next/navigation"; diff --git a/apps/dashboard/lib/trpc/routers/key/create.ts b/apps/dashboard/lib/trpc/routers/key/create.ts index 5c4c71e3a6..d8a52ab586 100644 --- a/apps/dashboard/lib/trpc/routers/key/create.ts +++ b/apps/dashboard/lib/trpc/routers/key/create.ts @@ -19,7 +19,7 @@ export const createKey = rateLimitedProcedure(ratelimit.create) refill: z .object({ interval: z.enum(["daily", "monthly"]), - amount: z.coerce.number().int().positive().min(1), + amount: z.coerce.number().int().min(1), refillDay: z.number().int().min(1).max(31).optional(), }) .optional(), diff --git a/apps/docs/libraries/ts/sdk/keys/get.mdx b/apps/docs/libraries/ts/sdk/keys/get.mdx index 43adb57384..b39a98e004 100644 --- a/apps/docs/libraries/ts/sdk/keys/get.mdx +++ b/apps/docs/libraries/ts/sdk/keys/get.mdx @@ -69,7 +69,7 @@ Available options: Resets `remaining` to this value every interval. - + value from `1` to `31`. The day each month to refill 'remaining'. If no value is given, The 1st will be used as a default. diff --git a/apps/docs/libraries/ts/sdk/keys/update.mdx b/apps/docs/libraries/ts/sdk/keys/update.mdx index 5324208305..447cd7b48e 100644 --- a/apps/docs/libraries/ts/sdk/keys/update.mdx +++ b/apps/docs/libraries/ts/sdk/keys/update.mdx @@ -81,7 +81,7 @@ Unkey allows automatic refill on 'remaining' on a 'daily' or 'monthly' interval. Read more [here](/apis/features/refill) - + value from `1` to `31`. The day each month to refill 'remaining'. If no value is given, The 1st will be used as a default. diff --git a/apps/workflows/jobs/refill-daily.ts b/apps/workflows/jobs/refill-daily.ts index 6e561f7821..968145c1ac 100644 --- a/apps/workflows/jobs/refill-daily.ts +++ b/apps/workflows/jobs/refill-daily.ts @@ -12,15 +12,15 @@ client.defineJob({ cron: "0 0 * * *", // Daily at midnight UTC }), - run: async (_payload, io, _ctx) => { - const date = _payload.ts; + run: async (payload, io, _ctx) => { + const date = payload.ts; + // Set up last day of month so if refillDay is after last day of month, Key will be refilled today. const lastDayOfMonth = new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate(); const today = date.getDate(); + const db = connectDatabase(); const tb = new Tinybird(env().TINYBIRD_TOKEN); let keys: Key[]; - console.log("today", today); - console.log("last day of month", lastDayOfMonth); if (today !== lastDayOfMonth) { keys = await io.runTask("list keys for refill", () => @@ -69,8 +69,7 @@ client.defineJob({ }) .where(eq(schema.keys.id, key.id)) .catch((error) => { - console.error(`Failed to update remaining ${key.id}`); - console.error(`With error ${error}`); + throw error; }); }); @@ -98,8 +97,8 @@ client.defineJob({ location: "trigger", }, }) - .catch((_error) => { - console.error(`failed to update Audit Logs for ${key.id}`); + .catch((error) => { + throw error; }); }); } diff --git a/apps/workflows/package.json b/apps/workflows/package.json index 6971750931..acd99e3ba5 100644 --- a/apps/workflows/package.json +++ b/apps/workflows/package.json @@ -12,8 +12,8 @@ "@chronark/zod-bird": "0.3.9", "@clerk/nextjs": "^4.29.10", "@planetscale/database": "^1.16.0", - "@trigger.dev/nextjs": "^3.0.7", - "@trigger.dev/sdk": "^3.0.7", + "@trigger.dev/nextjs": "^2.3.18", + "@trigger.dev/sdk": "^2.3.18", "@trigger.dev/slack": "^2.3.18", "@unkey/billing": "workspace:^", "@unkey/db": "workspace:^", @@ -25,8 +25,7 @@ "react": "^18", "react-dom": "^18", "stripe": "^14.23.0", - "zod": "^3.23.5", - "@trigger.dev/react": "^3.0.7" + "zod": "^3.23.5" }, "devDependencies": { "@types/node": "^20.14.9", diff --git a/internal/db/src/schema/key_migrations.ts b/internal/db/src/schema/key_migrations.ts index 32b47d7f0b..0c3470435b 100644 --- a/internal/db/src/schema/key_migrations.ts +++ b/internal/db/src/schema/key_migrations.ts @@ -30,7 +30,7 @@ export const keyMigrationErrors = mysqlTable("key_migration_errors", { permissions?: string[]; expires?: number; remaining?: number; - refill?: { interval: "daily" | "monthly"; amount: number; refillDay: number }; + refill?: { interval: "daily" | "monthly"; amount: number; refillDay?: number | undefined }; ratelimit?: { async: boolean; limit: number; duration: number }; enabled: boolean; environment?: string; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3706983fac..26049cd2fa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -847,14 +847,11 @@ importers: specifier: ^1.16.0 version: 1.18.0 '@trigger.dev/nextjs': - specifier: ^3.0.7 - version: 3.0.7(@trigger.dev/sdk@3.0.7)(next@14.2.10) - '@trigger.dev/react': - specifier: ^3.0.7 - version: 3.0.7(react-dom@18.3.1)(react@18.3.1) + specifier: ^2.3.18 + version: 2.3.18(@trigger.dev/sdk@2.3.19)(next@14.2.10) '@trigger.dev/sdk': - specifier: ^3.0.7 - version: 3.0.7 + specifier: ^2.3.18 + version: 2.3.19 '@trigger.dev/slack': specifier: ^2.3.18 version: 2.3.18 @@ -5246,35 +5243,6 @@ packages: - webpack-sources dev: true - /@mintlify/cli@4.0.160(openapi-types@12.1.3)(react-dom@18.3.1)(react@18.3.1): - resolution: {integrity: sha512-uXI2aB96YxXGRAgPHGcOdpayqvzSKSyWeyhWn327mluYW14QQgaJKdKJEWjQF5GMJB0tfgzWsyNKK41yh+Irmg==} - engines: {node: '>=18.0.0'} - hasBin: true - dependencies: - '@apidevtools/swagger-parser': 10.1.0(openapi-types@12.1.3) - '@mintlify/link-rot': 3.0.168(react-dom@18.3.1)(react@18.3.1) - '@mintlify/models': 0.0.89 - '@mintlify/prebuild': 1.0.168(react-dom@18.3.1)(react@18.3.1) - '@mintlify/previewing': 4.0.157(react-dom@18.3.1)(react@18.3.1) - '@mintlify/validation': 0.1.151 - chalk: 5.3.0 - detect-port: 1.6.1 - fs-extra: 11.2.0 - gray-matter: 4.0.3 - ora: 6.3.1 - unist-util-visit: 4.1.2 - yargs: 17.7.2 - transitivePeerDependencies: - - bufferutil - - debug - - encoding - - openapi-types - - react - - react-dom - - supports-color - - utf-8-validate - dev: true - /@mintlify/cli@4.0.182(openapi-types@12.1.3)(react-dom@18.3.1)(react@18.3.1)(typescript@5.5.3): resolution: {integrity: sha512-mHYbReR/lte5Eenr2bs4HZsuJ63y89fGZvjreCYRTHsu3rIb6pZolTEAh2euVsbZiXN4jVi80shV6kDRtH4B1A==} engines: {node: '>=18.0.0'} @@ -5305,55 +5273,6 @@ packages: - utf-8-validate dev: true - /@mintlify/common@1.0.100(react-dom@18.3.1)(react@18.3.1): - resolution: {integrity: sha512-7PIBC7dCW22W+19eSjJxY1vH2qvePLSI4o5pttnIXRwFj5GEtxP55c1Pq1yDLjoOIQYJ9LRsy63nAaUe24sxlA==} - dependencies: - '@apidevtools/swagger-parser': 10.1.0(openapi-types@12.1.3) - '@mintlify/mdx': 0.0.44(react-dom@18.3.1)(react@18.3.1) - '@mintlify/models': 0.0.89 - '@mintlify/validation': 0.1.151 - '@sindresorhus/slugify': 2.2.1 - acorn: 8.12.1 - acorn-jsx: 5.3.2(acorn@8.12.1) - esast-util-from-js: 2.0.1 - estree-util-to-js: 2.0.0 - estree-walker: 3.0.3 - gray-matter: 4.0.3 - hast-util-from-html: 1.0.2 - hast-util-from-html-isomorphic: 2.0.0 - hast-util-to-html: 8.0.4 - hast-util-to-text: 4.0.2 - is-absolute-url: 4.0.1 - lodash: 4.17.21 - mdast: 3.0.0 - mdast-util-from-markdown: 1.3.1 - mdast-util-gfm: 2.0.2 - mdast-util-mdx: 2.0.1 - mdast-util-mdx-jsx: 2.1.4 - mdast-util-mdxjs-esm: 1.3.1 - micromark-extension-mdx-jsx: 1.0.5 - micromark-extension-mdxjs: 1.0.1 - micromark-extension-mdxjs-esm: 1.0.5 - openapi-types: 12.1.3 - remark: 14.0.3 - remark-frontmatter: 4.0.1 - remark-gfm: 3.0.1 - remark-math: 5.1.1 - remark-mdx: 2.3.0 - unist-builder: 3.0.1 - unist-util-map: 3.1.3 - unist-util-remove: 3.1.1 - unist-util-remove-position: 4.0.2 - unist-util-visit: 4.1.2 - unist-util-visit-parents: 5.1.3 - vfile: 5.3.7 - transitivePeerDependencies: - - debug - - react - - react-dom - - supports-color - dev: true - /@mintlify/common@1.0.118(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-3LlH6G6yVI1/taGBvR/IYc/9N9oMeNreV3yxuT52vOtNvRYipS1f75wFOFvSHiqsx8qf2c4Fwl4bRGmLT6rx+w==} dependencies: @@ -5403,26 +5322,6 @@ packages: - supports-color dev: true - /@mintlify/link-rot@3.0.168(react-dom@18.3.1)(react@18.3.1): - resolution: {integrity: sha512-cS8F81w6b7r29hh52/t+hdG28ZBeq7fHuk6b8U6AIjbzydBLWHc1crIJWpI5FS6z0oGsQSPSEClgSaV8J6s1nw==} - engines: {node: '>=18.0.0'} - dependencies: - '@apidevtools/swagger-parser': 10.1.0(openapi-types@12.1.3) - '@mintlify/common': 1.0.100(react-dom@18.3.1)(react@18.3.1) - '@mintlify/prebuild': 1.0.168(react-dom@18.3.1)(react@18.3.1) - chalk: 5.3.0 - fs-extra: 11.2.0 - gray-matter: 4.0.3 - is-absolute-url: 4.0.1 - openapi-types: 12.1.3 - unist-util-visit: 4.1.2 - transitivePeerDependencies: - - debug - - react - - react-dom - - supports-color - dev: true - /@mintlify/link-rot@3.0.188(react-dom@18.3.1)(react@18.3.1)(typescript@5.5.3): resolution: {integrity: sha512-MClTVR9WwiTn3WmcDaFOoUib5DbPe/vzP1d3EJUO8oq+WsDj/1Z9ZdGcKEHToBqVDpGc40JG6s/OfQnkUECK7g==} engines: {node: '>=18.0.0'} @@ -5447,23 +5346,6 @@ packages: - utf-8-validate dev: true - /@mintlify/mdx@0.0.44(react-dom@18.3.1)(react@18.3.1): - resolution: {integrity: sha512-lvgadIgVxvqJUC4OeRugQGDfbNE7WnxoVaJk4Wvd2EFSB+3o9b1/XvXLrsOcD9uwyFgK94YhpmZQbftHu8ol0A==} - dependencies: - hast-util-to-string: 2.0.0 - next-mdx-remote: 4.4.1(react-dom@18.3.1)(react@18.3.1) - refractor: 4.8.1 - rehype-katex: 6.0.3 - remark-gfm: 3.0.1 - remark-math: 5.1.1 - remark-smartypants: 2.1.0 - unist-util-visit: 4.1.2 - transitivePeerDependencies: - - react - - react-dom - - supports-color - dev: true - /@mintlify/mdx@0.0.46(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-8BZsTvFhek4Z6//+kWKvlgnKy+dOjbtHqPnhiYaNlvZvGFU5BkLjOtFFP2jG+7vluvfI2yWOS8g3mtPwntWrVg==} dependencies: @@ -5491,36 +5373,6 @@ packages: - debug dev: true - /@mintlify/models@0.0.89: - resolution: {integrity: sha512-rmepcGtn67fddV80gP3GU/7kYSUQpBYDUqVWXKuBGJWMimaVa0g2RCROPGk9562Xgd16z2jEOyGNoh0pslyBCQ==} - engines: {node: '>=18.0.0'} - dependencies: - axios: 1.7.7 - openapi-types: 12.1.3 - transitivePeerDependencies: - - debug - dev: true - - /@mintlify/prebuild@1.0.168(react-dom@18.3.1)(react@18.3.1): - resolution: {integrity: sha512-mpbptIHVnGeM/muPIK/vwcx/wB5fwgr29Ya5sE2XOWi11KG3Ryfv5X8lXc1czwi6Qfvl7U/+CKzP4O2BVZqHfg==} - dependencies: - '@apidevtools/swagger-parser': 10.1.0(openapi-types@12.1.3) - '@mintlify/common': 1.0.100(react-dom@18.3.1)(react@18.3.1) - '@mintlify/validation': 0.1.151 - favicons: 7.2.0 - fs-extra: 11.2.0 - gray-matter: 4.0.3 - is-absolute-url: 4.0.1 - js-yaml: 4.1.0 - openapi-types: 12.1.3 - unist-util-visit: 4.1.2 - transitivePeerDependencies: - - debug - - react - - react-dom - - supports-color - dev: true - /@mintlify/prebuild@1.0.188(react-dom@18.3.1)(react@18.3.1)(typescript@5.5.3): resolution: {integrity: sha512-KlAIWxvb+pQj9+9Wo+DNfz4cE120/vnSF/uq7DDEcLwa0U4z6Wz+W/Kh/MSKJwCRjsiCxrCRmQju+zvod9Oo8A==} dependencies: @@ -5547,40 +5399,6 @@ packages: - utf-8-validate dev: true - /@mintlify/previewing@4.0.157(react-dom@18.3.1)(react@18.3.1): - resolution: {integrity: sha512-mZzCbKyNyEUfP5sogORMs1y/2IMRObYJP1h67ZNcKXq71opoDiTDxT4pSdRknfOK2Fr6AISwL4askBXkvl0ZMA==} - engines: {node: '>=18.0.0'} - dependencies: - '@apidevtools/swagger-parser': 10.1.0(openapi-types@12.1.3) - '@mintlify/common': 1.0.100(react-dom@18.3.1)(react@18.3.1) - '@mintlify/prebuild': 1.0.168(react-dom@18.3.1)(react@18.3.1) - '@mintlify/validation': 0.1.151 - '@octokit/rest': 19.0.13 - chalk: 5.3.0 - chokidar: 3.6.0 - express: 4.21.0 - fs-extra: 11.2.0 - got: 13.0.0 - gray-matter: 4.0.3 - is-absolute-url: 4.0.1 - is-online: 10.0.0 - open: 8.4.2 - openapi-types: 12.1.3 - ora: 6.3.1 - socket.io: 4.8.0 - tar: 6.2.1 - unist-util-visit: 4.1.2 - yargs: 17.7.2 - transitivePeerDependencies: - - bufferutil - - debug - - encoding - - react - - react-dom - - supports-color - - utf-8-validate - dev: true - /@mintlify/previewing@4.0.179(react-dom@18.3.1)(react@18.3.1)(typescript@5.5.3): resolution: {integrity: sha512-KTLJjY10q0WLhhy/q/2+Ei9qPHI9252ClmD5SBHxuRM+NsJNqWsl/LGi8hXgxefd42h+K/o7nNEBDlbOgW87Fg==} engines: {node: '>=18.0.0'} @@ -5642,19 +5460,6 @@ packages: - utf-8-validate dev: true - /@mintlify/validation@0.1.151: - resolution: {integrity: sha512-+S8SJk8Zv9HMb8O5m6ZyC7hOfXr1D7dWDUxhljls/A4xCNxlo8wlgYlOpz0zJgwZa3c6JO6VnHwmHSi9LAcaPw==} - dependencies: - '@mintlify/models': 0.0.89 - lcm: 0.0.3 - lodash: 4.17.21 - openapi-types: 12.1.3 - zod: 3.23.8 - zod-to-json-schema: 3.23.3(zod@3.23.8) - transitivePeerDependencies: - - debug - dev: true - /@mintlify/validation@0.1.166: resolution: {integrity: sha512-Xot8996tJoqeTfmdoQ3x6HrjfobozqcEF9cYqpJ0z1KUg0u4yGcbJit2++DCBV9qmUzc8M2UM9lNBcYPAxe1nw==} dependencies: @@ -10944,10 +10749,6 @@ packages: resolution: {integrity: sha512-DJSilV5+ytBP1FbFcEJovv4rnnm/CokuVvrBEtW/Va9DvuJ3HksbXUJEpI0aV1KtuL4ZoO9AVE6PyNLzF7tLeA==} dev: false - /@tanstack/query-core@5.0.0-beta.0: - resolution: {integrity: sha512-VGq/H3PuRoj0shOcg1S5Flv3YD2qNz2ttk8w5xe5AHQE1I8NO9EHSBUxezIpk4dD6M7bQDtwHBMqqU2EwMwyUw==} - dev: false - /@tanstack/react-query@4.36.1(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-y7ySVHFyyQblPl3J3eQBWpXZkliroki3ARnBKsdJchlgt7yJLRDUcf4B8soufgiYt3pEQIkBWBx1N9/ZPIeUWw==} peerDependencies: @@ -10966,24 +10767,6 @@ packages: use-sync-external-store: 1.2.2(react@18.3.1) dev: false - /@tanstack/react-query@5.0.0-beta.2(react-dom@18.3.1)(react@18.3.1): - resolution: {integrity: sha512-JdK1HRw20tuwg3GfT3QZTkuS7s2KDa9FeozuJ7jZULlwPczZagouqYmM6+PL0ad6jfCnw8NzmLFtZdlBx6cTmA==} - peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - react-native: '*' - peerDependenciesMeta: - react-dom: - optional: true - react-native: - optional: true - dependencies: - '@tanstack/query-core': 5.0.0-beta.0 - client-only: 0.0.1 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - dev: false - /@tanstack/react-table@8.16.0(react-dom@18.3.1)(react@18.3.1): resolution: {integrity: sha512-rKRjnt8ostqN2fercRVOIH/dq7MAmOENCMvVlKx6P9Iokhh6woBGnIZEkqsY/vEJf1jN3TqLOb34xQGLVRuhAg==} engines: {node: '>=12'} @@ -11088,79 +10871,34 @@ packages: - utf-8-validate dev: false - /@trigger.dev/core@3.0.7: - resolution: {integrity: sha512-3HfnAseLvGvCcK8zEdYiblESPiwfwfGdiWRXW84dtR2jCl5g8/e42STZ31qvg4J8+nHegCpTVfgIaV8iEpDZZA==} - engines: {node: '>=18.20.0'} - dependencies: - '@google-cloud/precise-date': 4.0.0 - '@opentelemetry/api': 1.4.1 - '@opentelemetry/api-logs': 0.52.1 - '@opentelemetry/exporter-logs-otlp-http': 0.52.1(@opentelemetry/api@1.4.1) - '@opentelemetry/exporter-trace-otlp-http': 0.52.1(@opentelemetry/api@1.4.1) - '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.4.1) - '@opentelemetry/resources': 1.13.0(@opentelemetry/api@1.4.1) - '@opentelemetry/sdk-logs': 0.52.1(@opentelemetry/api@1.4.1) - '@opentelemetry/sdk-node': 0.52.1(@opentelemetry/api@1.4.1) - '@opentelemetry/sdk-trace-base': 1.13.0(@opentelemetry/api@1.4.1) - '@opentelemetry/sdk-trace-node': 1.13.0(@opentelemetry/api@1.4.1) - '@opentelemetry/semantic-conventions': 1.13.0 - execa: 8.0.1 - humanize-duration: 3.32.1 - socket.io-client: 4.7.5 - superjson: 2.2.1 - zod: 3.22.3 - zod-error: 1.5.0 - zod-validation-error: 1.5.0(zod@3.22.3) - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - dev: false - - /@trigger.dev/nextjs@3.0.0-beta.23(@trigger.dev/sdk@3.0.0-beta.23)(next@14.2.10): - resolution: {integrity: sha512-uJc0qFPqllkIcXZtrePgT5TiDjD+irPM4uIBvkDmn8Durny5UvHClzOKrrW46BdqyJ0VCH5LrtbOowUcNwfe/w==} + /@trigger.dev/nextjs@2.3.18(@trigger.dev/sdk@2.3.19)(next@14.2.10): + resolution: {integrity: sha512-ZS0RTZNrzGEKfOLQLYt3iqlNquD7pd39Hpd/+2tvRCaPSQ3qPYQvdjBSueW0OURZSQSiNno5VUYR5vbVBcAaXA==} engines: {node: '>=18.0.0'} peerDependencies: - '@trigger.dev/sdk': ^3.0.0-beta.23 + '@trigger.dev/sdk': ^2.3.18 next: '>=12.0.0' dependencies: - '@trigger.dev/sdk': 3.0.0-beta.23(typescript@5.5.3) + '@trigger.dev/sdk': 2.3.19 debug: 4.3.7(supports-color@8.1.1) next: 14.2.10(@babel/core@7.25.2)(@opentelemetry/api@1.4.1)(react-dom@18.3.1)(react@18.3.1) transitivePeerDependencies: - supports-color dev: false - /@trigger.dev/nextjs@3.0.7(@trigger.dev/sdk@3.0.7)(next@14.2.10): - resolution: {integrity: sha512-7O9nxvi7q1jw3ap0rQiGl+kc+eFTNnSwjU0ZYCJeGJ3JDoIwi8DxqQnE1/jpqNpLPHc++szgzK8KjMEY39IVAA==} + /@trigger.dev/nextjs@3.0.0-beta.23(@trigger.dev/sdk@3.0.0-beta.23)(next@14.2.10): + resolution: {integrity: sha512-uJc0qFPqllkIcXZtrePgT5TiDjD+irPM4uIBvkDmn8Durny5UvHClzOKrrW46BdqyJ0VCH5LrtbOowUcNwfe/w==} engines: {node: '>=18.0.0'} peerDependencies: - '@trigger.dev/sdk': ~2.3.0 || ^3.0.0 + '@trigger.dev/sdk': ^3.0.0-beta.23 next: '>=12.0.0' dependencies: - '@trigger.dev/sdk': 3.0.7 + '@trigger.dev/sdk': 3.0.0-beta.23(typescript@5.5.3) debug: 4.3.7(supports-color@8.1.1) next: 14.2.10(@babel/core@7.25.2)(@opentelemetry/api@1.4.1)(react-dom@18.3.1)(react@18.3.1) transitivePeerDependencies: - supports-color dev: false - /@trigger.dev/react@3.0.7(react-dom@18.3.1)(react@18.3.1): - resolution: {integrity: sha512-rF0XKkRFgbqp2LxeKNOqpXW8VKTnDmDkLYmZEMXdlNIgA4e4TCbsEuhanPuTdVtx5OOdptnB2TYDaufVhvv0IQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18 - dependencies: - '@tanstack/react-query': 5.0.0-beta.2(react-dom@18.3.1)(react@18.3.1) - '@trigger.dev/core': 2.3.19 - debug: 4.3.7(supports-color@8.1.1) - react: 18.3.1 - zod: 3.22.3 - transitivePeerDependencies: - - react-dom - - react-native - - supports-color - dev: false - /@trigger.dev/sdk@2.3.19: resolution: {integrity: sha512-NjX5vEK3ydx5Bac0rwQbHqOZWJJqT6qQ+/GkTErKDs59v+qD+s1g/fB/TtEGpsAjdZK9FIPm4ooFemYVmpIm/A==} engines: {node: '>=18.0.0'} @@ -11240,30 +10978,6 @@ packages: - utf-8-validate dev: false - /@trigger.dev/sdk@3.0.7: - resolution: {integrity: sha512-8ZQieOmibq4B6yl574G1zDJNfR34GcZXPG5hh0qpI9+CmTsGdnR8LMO68MJSiAooPWTcMqvZAL6ymIfUyP0+/g==} - engines: {node: '>=18.20.0'} - dependencies: - '@opentelemetry/api': 1.4.1 - '@opentelemetry/api-logs': 0.52.1 - '@opentelemetry/semantic-conventions': 1.13.0 - '@trigger.dev/core': 3.0.7 - chalk: 5.3.0 - cronstrue: 2.50.0 - debug: 4.3.7(supports-color@8.1.1) - evt: 2.5.7 - slug: 6.1.0 - terminal-link: 3.0.0 - ulid: 2.3.0 - uuid: 9.0.1 - ws: 8.18.0 - zod: 3.22.3 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - dev: false - /@trigger.dev/slack@2.3.18: resolution: {integrity: sha512-YyamKAIxfEbAaMXRZeWJ0k7HzvCermRjWweiHovY61j13Q+JmALcxvLXOYqtEZXS1p84T/Kmkk2iJ53VHLDsAA==} engines: {node: '>=16.8.0'} @@ -19774,23 +19488,6 @@ packages: yallist: 4.0.0 dev: true - /mintlify@4.0.160(openapi-types@12.1.3)(react-dom@18.3.1)(react@18.3.1): - resolution: {integrity: sha512-me/GeZ5k2Y4UqeXLoJknIlBfGm/ybQna2f6t/xsvV1DsO7h3CT4QcQxJb7NKPyoc5gXH9hJ/Dj0I0VJOFlCDpw==} - engines: {node: '>=18.0.0'} - hasBin: true - dependencies: - '@mintlify/cli': 4.0.160(openapi-types@12.1.3)(react-dom@18.3.1)(react@18.3.1) - transitivePeerDependencies: - - bufferutil - - debug - - encoding - - openapi-types - - react - - react-dom - - supports-color - - utf-8-validate - dev: true - /mintlify@4.0.182(openapi-types@12.1.3)(react-dom@18.3.1)(react@18.3.1)(typescript@5.5.3): resolution: {integrity: sha512-WHNbjdd+te3NEQqnqF1ERv9pNtc13E0C9XByxMOTd+09Fpr3/2ZAFnBgX2EcxnDbmvGXaMbC47octWbuvml8iw==} engines: {node: '>=18.0.0'} diff --git a/tools/migrate/auditlog-import.ts b/tools/migrate/auditlog-import.ts index c37e187410..09aed1cbcc 100644 --- a/tools/migrate/auditlog-import.ts +++ b/tools/migrate/auditlog-import.ts @@ -38,7 +38,7 @@ async function main() { location: string; meta: string | null; }; - + //biome-ignore lint/suspicious/noConsoleLog: Used for tracking console.log(log); let bucketId = ""; const bucket = await db.query.auditLogBucket.findFirst({ @@ -98,7 +98,7 @@ async function main() { } buffer = lines[0]; } - + //biome-ignore lint/suspicious/noConsoleLog: Used for tracking console.log("END"); } diff --git a/tools/migrate/main.ts b/tools/migrate/main.ts index e56c35d71b..bfcd2a6620 100644 --- a/tools/migrate/main.ts +++ b/tools/migrate/main.ts @@ -28,6 +28,7 @@ async function main() { }); if (!identity) { const id = newId("identity"); + //biome-ignore lint/suspicious/noConsoleLog: Used for tracking console.log("Creating new identity", id, key.ownerId); await db.insert(schema.identities).values({ id, @@ -38,6 +39,7 @@ async function main() { id, }; } + //biome-ignore lint/suspicious/noConsoleLog: Used for tracking console.log("connecting", identity.id, key.id); await db .update(schema.keys) diff --git a/tools/migrate/tinybird-export.ts b/tools/migrate/tinybird-export.ts index 248cf2e276..47556f797f 100644 --- a/tools/migrate/tinybird-export.ts +++ b/tools/migrate/tinybird-export.ts @@ -3,6 +3,7 @@ async function main() { const writer = exportFile.writer(); let cursor = ""; do { + //biome-ignore lint/suspicious/noConsoleLog: Used for tracking console.log({ cursor }); const res = await fetch( `https://api.tinybird.co/v0/pipes/export_audit_log.json?cursor=${cursor}`, @@ -13,6 +14,7 @@ async function main() { }, ); const body = (await res.json()) as { data: { auditLogId: string }[] }; + //biome-ignore lint/suspicious/noConsoleLog: Used for tracking console.log("found", body.data.length); for (const row of body.data) { writer.write(JSON.stringify(row)); From e17617b8c857831028421701924c34b150c2bd09 Mon Sep 17 00:00:00 2001 From: Michael Silva Date: Fri, 27 Sep 2024 14:46:12 -0400 Subject: [PATCH 12/29] test changes --- apps/api/src/routes/v1_keys_createKey.ts | 11 ++--------- .../src/routes/v1_keys_updateKey.error.test.ts | 17 ++++++++++++++++- .../src/routes/v1_keys_updateKey.happy.test.ts | 3 +-- apps/api/src/routes/v1_keys_updateKey.ts | 18 ++++-------------- 4 files changed, 23 insertions(+), 26 deletions(-) diff --git a/apps/api/src/routes/v1_keys_createKey.ts b/apps/api/src/routes/v1_keys_createKey.ts index 8a53fad15d..3870d7d68c 100644 --- a/apps/api/src/routes/v1_keys_createKey.ts +++ b/apps/api/src/routes/v1_keys_createKey.ts @@ -342,15 +342,8 @@ export const registerV1KeysCreateKey = (app: App) => ? upsertIdentity(db.primary, authorizedWorkspaceId, externalId) : Promise.resolve(null), ]); - const date = new Date(); - let lastDayOfMonth = new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate(); - const newKey = await retry(5, async (attempt) => { - if (req.refill?.refillDay && req?.refill?.refillDay <= lastDayOfMonth) { - if (req.refill.interval === "monthly") { - lastDayOfMonth = req.refill.refillDay; - } - } + const newKey = await retry(5, async (attempt) => { if (attempt > 1) { logger.warn("retrying key creation", { attempt, @@ -382,7 +375,7 @@ export const registerV1KeysCreateKey = (app: App) => ratelimitDuration: req.ratelimit?.duration ?? req.ratelimit?.refillInterval, remaining: req.remaining, refillInterval: req.refill?.interval, - refillDay: req.refill?.interval === "monthly" ? lastDayOfMonth : null, + refillDay: req.refill?.interval === "daily" ? null : req?.refill?.refillDay ?? 1, refillAmount: req.refill?.amount, lastRefillAt: req.refill?.interval ? new Date() : null, deletedAt: null, diff --git a/apps/api/src/routes/v1_keys_updateKey.error.test.ts b/apps/api/src/routes/v1_keys_updateKey.error.test.ts index e305fc642f..3aa1590a33 100644 --- a/apps/api/src/routes/v1_keys_updateKey.error.test.ts +++ b/apps/api/src/routes/v1_keys_updateKey.error.test.ts @@ -4,6 +4,9 @@ import { newId } from "@unkey/id"; import { IntegrationHarness } from "src/pkg/testutil/integration-harness"; import type { ErrorResponse } from "@/pkg/errors"; +import { schema } from "@unkey/db"; +import { sha256 } from "@unkey/hash"; +import { KeyV1 } from "@unkey/keys"; import type { V1KeysUpdateKeyRequest, V1KeysUpdateKeyResponse } from "./v1_keys_updateKey"; test("when the key does not exist", async (t) => { @@ -37,6 +40,18 @@ test("reject invalid refill config", async (t) => { const keyId = newId("test"); const root = await h.createRootKey([`api.${h.resources.userApi.id}.update_key`]); /* The code snippet is making a POST request to the "/v1/keys.createKey" endpoint with the specified headers. It is using the `h.post` method from the `Harness` instance to send the request. The generic types `` specify the request payload and response types respectively. */ + const key = { + id: keyId, + keyAuthId: h.resources.userKeyAuth.id, + workspaceId: h.resources.userWorkspace.id, + start: "test", + name: "test", + remaining: 10, + hash: await sha256(new KeyV1({ byteLength: 16 }).toString()), + + createdAt: new Date(), + }; + await h.db.primary.insert(schema.keys).values(key); const res = await h.post({ url: "/v1/keys.updateKey", @@ -59,7 +74,7 @@ test("reject invalid refill config", async (t) => { error: { code: "BAD_REQUEST", docs: "https://unkey.dev/docs/api-reference/errors/code/BAD_REQUEST", - message: "when interval is set to 'daily', 'refillDay' must be null.", + message: "Connot set 'refillDay' if 'interval' is 'daily'", }, }); }); diff --git a/apps/api/src/routes/v1_keys_updateKey.happy.test.ts b/apps/api/src/routes/v1_keys_updateKey.happy.test.ts index 4ea3fa1874..f51d034a19 100644 --- a/apps/api/src/routes/v1_keys_updateKey.happy.test.ts +++ b/apps/api/src/routes/v1_keys_updateKey.happy.test.ts @@ -1023,6 +1023,7 @@ describe("When refillDay is omitted.", () => { workspaceId: h.resources.userWorkspace.id, start: "test", name: "test", + remaining: 10, hash: await sha256(new KeyV1({ byteLength: 16 }).toString()), createdAt: new Date(), @@ -1037,11 +1038,9 @@ describe("When refillDay is omitted.", () => { }, body: { keyId: key.id, - remaining: 10, refill: { interval: "monthly", amount: 130, - refillDay: undefined, }, enabled: true, }, diff --git a/apps/api/src/routes/v1_keys_updateKey.ts b/apps/api/src/routes/v1_keys_updateKey.ts index ffbd298e89..8076105e2e 100644 --- a/apps/api/src/routes/v1_keys_updateKey.ts +++ b/apps/api/src/routes/v1_keys_updateKey.ts @@ -331,16 +331,10 @@ export const registerV1KeysUpdate = (app: App) => message: "Cannot set refill on a key with unlimited requests", }); } - if (req.refill && key.remaining === null) { + if (req.refill?.interval === "daily" && req.refill.refillDay) { throw new UnkeyApiError({ code: "BAD_REQUEST", - message: "Cannot set refill on a key with unlimited requests", - }); - } - if (req.refill?.refillDay && req.refill.interval === "daily") { - throw new UnkeyApiError({ - code: "BAD_REQUEST", - message: "when interval is set to 'daily', 'refillDay' must be null.", + message: "Connot set 'refillDay' if 'interval' is 'daily'", }); } const authorizedWorkspaceId = auth.authorizedWorkspaceId; @@ -353,11 +347,7 @@ export const registerV1KeysUpdate = (app: App) => : externalId === null ? null : (await upsertIdentity(db.primary, authorizedWorkspaceId, externalId)).id; - const date = new Date(); - let lastDayOfMonth = new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate(); - if (req.refill?.refillDay && req?.refill?.refillDay <= lastDayOfMonth) { - lastDayOfMonth = req.refill.refillDay; - } + await db.primary .update(schema.keys) .set({ @@ -388,7 +378,7 @@ export const registerV1KeysUpdate = (app: App) => : req.ratelimit?.duration ?? req.ratelimit?.refillInterval ?? null, refillInterval: req.refill === null ? null : req.refill?.interval, refillAmount: req.refill === null ? null : req.refill?.amount, - refillDay: req.refill?.interval === "monthly" ? lastDayOfMonth : null, + refillDay: req.refill?.interval === "daily" ? null : req?.refill?.refillDay ?? 1, lastRefillAt: req.refill == null || req.refill?.amount == null ? null : new Date(), enabled: req.enabled, }) From eb4df2e68623906cf03d47a6451659f9e6053ce6 Mon Sep 17 00:00:00 2001 From: Michael Silva Date: Fri, 27 Sep 2024 15:18:50 -0400 Subject: [PATCH 13/29] test Fixes --- apps/api/src/routes/v1_keys_createKey.happy.test.ts | 6 ++---- apps/api/src/routes/v1_migrations_createKey.ts | 7 ++----- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/apps/api/src/routes/v1_keys_createKey.happy.test.ts b/apps/api/src/routes/v1_keys_createKey.happy.test.ts index 91eb63e5f3..d8a8be632d 100644 --- a/apps/api/src/routes/v1_keys_createKey.happy.test.ts +++ b/apps/api/src/routes/v1_keys_createKey.happy.test.ts @@ -467,11 +467,9 @@ describe("with externalId", () => { expect(key!.identity!.id).toEqual(identity.id); }); }); - describe("Should default last day of month if none provided", () => { + describe("Should default first day of month if none provided", () => { test("should provide default value", async (t) => { const h = await IntegrationHarness.init(t); - const date = new Date(); - const lastDate = new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate(); const root = await h.createRootKey([`api.${h.resources.userApi.id}.create_key`]); const res = await h.post({ @@ -497,7 +495,7 @@ describe("with externalId", () => { where: (table, { eq }) => eq(table.id, res.body.keyId), }); expect(key).toBeDefined(); - expect(key!.refillDay).toEqual(lastDate); + expect(key!.refillDay).toEqual(1); }); }); }); diff --git a/apps/api/src/routes/v1_migrations_createKey.ts b/apps/api/src/routes/v1_migrations_createKey.ts index cca7838527..b966c23c89 100644 --- a/apps/api/src/routes/v1_migrations_createKey.ts +++ b/apps/api/src/routes/v1_migrations_createKey.ts @@ -422,18 +422,15 @@ export const registerV1MigrationsCreateKeys = (app: App) => ratelimitDuration: key.ratelimit?.refillInterval ?? key.ratelimit?.refillInterval ?? null, remaining: key.remaining ?? null, refillInterval: key.refill?.interval ?? null, + refillDay: key.refill?.interval === "daily" ? null : key?.refill?.refillDay ?? 1, refillAmount: key.refill?.amount ?? null, - refillDay: - key.refill?.interval === "monthly" && key.refill.refillDay - ? key.refill.refillDay - : null, - lastRefillAt: key.refill?.interval ? new Date() : null, deletedAt: null, enabled: key.enabled ?? true, environment: key.environment ?? null, createdAtM: Date.now(), updatedAtM: null, deletedAtM: null, + lastRefillAt: null }); for (const role of key.roles ?? []) { From 666c6a3ba93ac046b3f4676f62d7d790c2e1efa4 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 27 Sep 2024 19:22:33 +0000 Subject: [PATCH 14/29] [autofix.ci] apply automated fixes --- apps/api/src/routes/v1_migrations_createKey.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/api/src/routes/v1_migrations_createKey.ts b/apps/api/src/routes/v1_migrations_createKey.ts index b966c23c89..311c54fb1c 100644 --- a/apps/api/src/routes/v1_migrations_createKey.ts +++ b/apps/api/src/routes/v1_migrations_createKey.ts @@ -430,7 +430,7 @@ export const registerV1MigrationsCreateKeys = (app: App) => createdAtM: Date.now(), updatedAtM: null, deletedAtM: null, - lastRefillAt: null + lastRefillAt: null, }); for (const role of key.roles ?? []) { From 7446529325e640e92e676d4167d0a7552789168b Mon Sep 17 00:00:00 2001 From: Michael Silva Date: Fri, 27 Sep 2024 15:41:55 -0400 Subject: [PATCH 15/29] api test fix --- apps/api/src/routes/v1_migrations_createKey.happy.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/api/src/routes/v1_migrations_createKey.happy.test.ts b/apps/api/src/routes/v1_migrations_createKey.happy.test.ts index b92f5d3ebe..08bfdace1a 100644 --- a/apps/api/src/routes/v1_migrations_createKey.happy.test.ts +++ b/apps/api/src/routes/v1_migrations_createKey.happy.test.ts @@ -497,7 +497,7 @@ test("migrate and verify a key", async (t) => { expect(verifyRes.body.valid).toEqual(true); }); -describe("Should default last day of month if none provided", () => { +describe("Should default first day of month if none provided", () => { test("should provide default value", async (t) => { const h = await IntegrationHarness.init(t); const root = await h.createRootKey([`api.${h.resources.userApi.id}.create_key`]); @@ -535,7 +535,7 @@ describe("Should default last day of month if none provided", () => { }); expect(found).toBeDefined(); expect(found?.remaining).toEqual(10); - expect(found?.refillAmount).toEqual(130); + expect(found?.refillAmount).toEqual(100); expect(found?.refillInterval).toEqual("monthly"); expect(found?.refillDay).toEqual(1); }); From 13529b168fb842a35d2163ed5f03e8c31867f367 Mon Sep 17 00:00:00 2001 From: Michael Silva Date: Sun, 29 Sep 2024 16:47:43 -0400 Subject: [PATCH 16/29] wording --- apps/api/src/routes/v1_migrations_createKey.happy.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/api/src/routes/v1_migrations_createKey.happy.test.ts b/apps/api/src/routes/v1_migrations_createKey.happy.test.ts index 08bfdace1a..6e633b90a3 100644 --- a/apps/api/src/routes/v1_migrations_createKey.happy.test.ts +++ b/apps/api/src/routes/v1_migrations_createKey.happy.test.ts @@ -497,7 +497,7 @@ test("migrate and verify a key", async (t) => { expect(verifyRes.body.valid).toEqual(true); }); -describe("Should default first day of month if none provided", () => { +describe("Should default to first day of month if none provided", () => { test("should provide default value", async (t) => { const h = await IntegrationHarness.init(t); const root = await h.createRootKey([`api.${h.resources.userApi.id}.create_key`]); @@ -538,5 +538,6 @@ describe("Should default first day of month if none provided", () => { expect(found?.refillAmount).toEqual(100); expect(found?.refillInterval).toEqual("monthly"); expect(found?.refillDay).toEqual(1); + expect(found?.hash).toEqual(hash); }); }); From 159869a560659ca815c89419df9d88d520e4053c Mon Sep 17 00:00:00 2001 From: Michael Silva Date: Mon, 30 Sep 2024 09:39:01 -0400 Subject: [PATCH 17/29] added permissions --- .../root-keys/[keyId]/permissions/permissions.ts | 16 +++++++++++++--- packages/rbac/src/permissions.ts | 16 +++++----------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/apps/dashboard/app/(app)/settings/root-keys/[keyId]/permissions/permissions.ts b/apps/dashboard/app/(app)/settings/root-keys/[keyId]/permissions/permissions.ts index 7f0e049bda..97792d5023 100644 --- a/apps/dashboard/app/(app)/settings/root-keys/[keyId]/permissions/permissions.ts +++ b/apps/dashboard/app/(app)/settings/root-keys/[keyId]/permissions/permissions.ts @@ -1,12 +1,11 @@ -import type { UnkeyPermission } from "@unkey/rbac"; +import type { UnkeyPermission } from "@unkey/rbac"; export type UnkeyPermissions = { [action: string]: { description: string; permission: UnkeyPermission; }; }; - export const workspacePermissions = { API: { create_api: { @@ -73,6 +72,18 @@ export const workspacePermissions = { description: "Delete namespaces in this workspace.", permission: "ratelimit.*.delete_namespace", }, + set_override: { + description: "Set a ratelimit override for an identifier.", + permission: "ratelimit.*.set_override", + }, + read_override: { + description: "read ratelimit override for an identifier.", + permission: "ratelimit.*.read_override", + }, + delete_override: { + description: "Delete ratelimit override for an identifier.", + permission: "ratelimit.*.delete_override", + }, }, Permissions: { create_role: { @@ -135,7 +146,6 @@ export const workspacePermissions = { }, }, } satisfies Record; - export function apiPermissions(apiId: string): { [category: string]: UnkeyPermissions } { return { API: { diff --git a/packages/rbac/src/permissions.ts b/packages/rbac/src/permissions.ts index 0f7e9b7f08..f27294a20a 100644 --- a/packages/rbac/src/permissions.ts +++ b/packages/rbac/src/permissions.ts @@ -1,3 +1,4 @@ + /** * The database takes care of isolating roles between workspaces. * That's why we can assume the highest scope of a role is an `api` or later `gateway` @@ -7,10 +8,8 @@ * - `gateway_id.xxx` * */ - import { z } from "zod"; import type { Flatten } from "./types"; - export function buildIdSchema(prefix: string) { return z.string().refine((s) => { if (s === "*") { @@ -26,7 +25,6 @@ const apiId = buildIdSchema("api"); const ratelimitNamespaceId = buildIdSchema("rl"); const rbacId = buildIdSchema("rbac"); const identityEnvId = z.string(); - export const apiActions = z.enum([ "read_api", "create_api", @@ -39,15 +37,16 @@ export const apiActions = z.enum([ "decrypt_key", "read_key", ]); - export const ratelimitActions = z.enum([ "limit", "create_namespace", "read_namespace", "update_namespace", "delete_namespace", + "set_override", + "read_override", + "delete_override", ]); - export const rbacActions = z.enum([ "create_permission", "update_permission", @@ -64,14 +63,12 @@ export const rbacActions = z.enum([ "add_permission_to_role", "remove_permission_from_role", ]); - export const identityActions = z.enum([ "create_identity", "read_identity", "update_identity", "delete_identity", ]); - export type Resources = { [resourceId in `api.${z.infer}`]: z.infer; } & { @@ -83,9 +80,7 @@ export type Resources = { } & { [resourceId in `identity.${z.infer}`]: z.infer; }; - export type UnkeyPermission = Flatten | "*"; - /** * Validation for roles used for our root keys */ @@ -117,9 +112,8 @@ export const unkeyPermissionValidation = z.custom().refine((s) case "identity": { return identityEnvId.safeParse(id).success && identityActions.safeParse(action).success; } - default: { return false; } } -}); +}); \ No newline at end of file From a46d8ea3e2367061a55cc0a644a540d5bfe17527 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Mon, 30 Sep 2024 13:41:23 +0000 Subject: [PATCH 18/29] [autofix.ci] apply automated fixes --- .../settings/root-keys/[keyId]/permissions/permissions.ts | 1 - packages/rbac/src/permissions.ts | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/dashboard/app/(app)/settings/root-keys/[keyId]/permissions/permissions.ts b/apps/dashboard/app/(app)/settings/root-keys/[keyId]/permissions/permissions.ts index 97792d5023..3fd38752e6 100644 --- a/apps/dashboard/app/(app)/settings/root-keys/[keyId]/permissions/permissions.ts +++ b/apps/dashboard/app/(app)/settings/root-keys/[keyId]/permissions/permissions.ts @@ -1,4 +1,3 @@ - import type { UnkeyPermission } from "@unkey/rbac"; export type UnkeyPermissions = { [action: string]: { diff --git a/packages/rbac/src/permissions.ts b/packages/rbac/src/permissions.ts index f27294a20a..e387617183 100644 --- a/packages/rbac/src/permissions.ts +++ b/packages/rbac/src/permissions.ts @@ -1,4 +1,3 @@ - /** * The database takes care of isolating roles between workspaces. * That's why we can assume the highest scope of a role is an `api` or later `gateway` @@ -116,4 +115,4 @@ export const unkeyPermissionValidation = z.custom().refine((s) return false; } } -}); \ No newline at end of file +}); From 3834fd7d6d4ba150c89bc1c445b7249c7829c628 Mon Sep 17 00:00:00 2001 From: Michael Silva Date: Sat, 5 Oct 2024 16:00:37 -0400 Subject: [PATCH 19/29] Git comment changes --- apps/api/src/routes/schema.ts | 6 +- apps/api/src/routes/v1_keys_updateKey.ts | 2 +- .../[keyId]/permissions/permissions.ts | 12 - apps/workflows/jobs/refill-daily.ts | 63 +- packages/api/src/openapi.d.ts | 1116 ++++++++--------- pnpm-lock.yaml | 667 +++------- 6 files changed, 765 insertions(+), 1101 deletions(-) diff --git a/apps/api/src/routes/schema.ts b/apps/api/src/routes/schema.ts index ba8c5f0686..d0c8ebc6ef 100644 --- a/apps/api/src/routes/schema.ts +++ b/apps/api/src/routes/schema.ts @@ -59,7 +59,7 @@ export const keySchema = z refill: z .object({ interval: z.enum(["daily", "monthly"]).openapi({ - description: "Determines the rate at which verifications will be refilled.", + description: "Determines the rate at which verifications will be refilled. When 'daily' is set for 'interval' 'refillDay' will be set to null.", example: "daily", }), amount: z.number().int().openapi({ @@ -68,8 +68,8 @@ export const keySchema = z }), refillDay: z.number().min(1).max(31).default(1).nullable().openapi({ description: - "The day verifications will refill each month, when interval is set to 'monthly'. Value is not zero-indexed making 1 the first day of the month. If left blank it will default to the last day of the month.", - example: 15, + "The day verifications will refill each month, when interval is set to 'monthly'. Value is not zero-indexed making 1 the first day of the month. If left blank it will default to the first day of the month. When 'daily' is set for 'interval' 'refillDay' will be set to null.", + example: 15, }), lastRefillAt: z.number().int().optional().openapi({ description: "The unix timestamp in miliseconds when the key was last refilled.", diff --git a/apps/api/src/routes/v1_keys_updateKey.ts b/apps/api/src/routes/v1_keys_updateKey.ts index 8076105e2e..c28c0d3e0e 100644 --- a/apps/api/src/routes/v1_keys_updateKey.ts +++ b/apps/api/src/routes/v1_keys_updateKey.ts @@ -334,7 +334,7 @@ export const registerV1KeysUpdate = (app: App) => if (req.refill?.interval === "daily" && req.refill.refillDay) { throw new UnkeyApiError({ code: "BAD_REQUEST", - message: "Connot set 'refillDay' if 'interval' is 'daily'", + message: "Cannot set 'refillDay' if 'interval' is 'daily'", }); } const authorizedWorkspaceId = auth.authorizedWorkspaceId; diff --git a/apps/dashboard/app/(app)/settings/root-keys/[keyId]/permissions/permissions.ts b/apps/dashboard/app/(app)/settings/root-keys/[keyId]/permissions/permissions.ts index 3fd38752e6..c0964b033a 100644 --- a/apps/dashboard/app/(app)/settings/root-keys/[keyId]/permissions/permissions.ts +++ b/apps/dashboard/app/(app)/settings/root-keys/[keyId]/permissions/permissions.ts @@ -71,18 +71,6 @@ export const workspacePermissions = { description: "Delete namespaces in this workspace.", permission: "ratelimit.*.delete_namespace", }, - set_override: { - description: "Set a ratelimit override for an identifier.", - permission: "ratelimit.*.set_override", - }, - read_override: { - description: "read ratelimit override for an identifier.", - permission: "ratelimit.*.read_override", - }, - delete_override: { - description: "Delete ratelimit override for an identifier.", - permission: "ratelimit.*.delete_override", - }, }, Permissions: { create_role: { diff --git a/apps/workflows/jobs/refill-daily.ts b/apps/workflows/jobs/refill-daily.ts index 968145c1ac..dc1e4f2438 100644 --- a/apps/workflows/jobs/refill-daily.ts +++ b/apps/workflows/jobs/refill-daily.ts @@ -17,46 +17,33 @@ client.defineJob({ // Set up last day of month so if refillDay is after last day of month, Key will be refilled today. const lastDayOfMonth = new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate(); const today = date.getDate(); - const db = connectDatabase(); const tb = new Tinybird(env().TINYBIRD_TOKEN); - let keys: Key[]; - - if (today !== lastDayOfMonth) { - keys = await io.runTask("list keys for refill", () => - db.query.keys - .findMany({ - where: (table, { isNotNull, isNull, eq, and, gt, or }) => - and( - isNull(table.deletedAt), - isNotNull(table.refillAmount), - gt(table.refillAmount, table.remaining), - or(isNull(table.refillDay), eq(table.refillDay, today)), - ), - }) - .catch((error) => { - console.error(`Query on keys failed, ${error}`); - throw new Error(`Query on keys failed, ${error}`); - }), - ); - } else { - keys = await io.runTask("list keys for end of month refill", () => - db.query.keys - .findMany({ - where: (table, { isNotNull, isNull, gte, and, gt, or }) => - and( - isNull(table.deletedAt), - isNotNull(table.refillAmount), - gt(table.refillAmount, table.remaining), - or(isNull(table.refillDay), gte(table.refillDay, lastDayOfMonth)), - ), - }) - .catch((error) => { - console.error(`Query on keys failed, ${error}`); - throw new Error(`Query on keys failed, ${error}`); - }), - ); - } + let sql = "" + + + // If refillDay is after last day of month, refillDay will be today. + const keys = await db.query.keys.findMany({ + where: (table, { isNotNull, isNull, gte, and, gt, or, eq }) => { + const baseConditions = and( + isNull(table.deletedAt), + isNotNull(table.refillAmount), + gt(table.refillAmount, table.remaining), + or( + isNull(table.refillDay), + eq(table.refillDay, today) + ) + ); + + if (today === lastDayOfMonth) { + return and( + baseConditions, + gt(table.refillDay, today) + ); + } + return baseConditions; + } + }) io.logger.info(`found ${keys.length} keys with refill set for today`); for (const key of keys) { diff --git a/packages/api/src/openapi.d.ts b/packages/api/src/openapi.d.ts index 842d71cbbf..8968c7629c 100644 --- a/packages/api/src/openapi.d.ts +++ b/packages/api/src/openapi.d.ts @@ -3,14 +3,11 @@ * Do not make direct changes to the file. */ + /** OneOf type helpers */ type Without = { [P in Exclude]?: never }; -type XOR = T | U extends object ? (Without & U) | (Without & T) : T | U; -type OneOf = T extends [infer Only] - ? Only - : T extends [infer A, infer B, ...infer Rest] - ? OneOf<[XOR, ...Rest]> - : never; +type XOR = (T | U) extends object ? (Without & U) | (Without & T) : T | U; +type OneOf = T extends [infer Only] ? Only : T extends [infer A, infer B, ...infer Rest] ? OneOf<[XOR, ...Rest]> : never; export interface paths { "/v1/liveness": { @@ -355,9 +352,8 @@ export interface components { /** * @description Unkey allows you to refill remaining verifications on a key on a regular interval. * @example { - * "interval": "monthly", - * "amount": 100, - * "refillDay":15, + * "interval": "daily", + * "amount": 10 * } */ refill?: { @@ -376,11 +372,6 @@ export interface components { * @description The unix timestamp in miliseconds when the key was last refilled. * @example 100 */ - refillDay: number; - /** - * @description the day each month refill triggers if interval is 'monthly'. - * @example 20 - */ lastRefillAt?: number; }; /** @@ -527,16 +518,7 @@ export interface components { * * @enum {string} */ - code: - | "VALID" - | "NOT_FOUND" - | "FORBIDDEN" - | "USAGE_EXCEEDED" - | "RATE_LIMITED" - | "UNAUTHORIZED" - | "DISABLED" - | "INSUFFICIENT_PERMISSIONS" - | "EXPIRED"; + code: "VALID" | "NOT_FOUND" | "FORBIDDEN" | "USAGE_EXCEEDED" | "RATE_LIMITED" | "UNAUTHORIZED" | "DISABLED" | "INSUFFICIENT_PERMISSIONS" | "EXPIRED"; /** @description Sets the key to be enabled or disabled. Disabled keys will not verify. */ enabled?: boolean; /** @@ -562,18 +544,11 @@ export interface components { }; }; /** @description A query for which permissions you require */ - PermissionQuery: OneOf< - [ - string, - { - and: components["schemas"]["PermissionQuery"][]; - }, - { - or: components["schemas"]["PermissionQuery"][]; - }, - null, - ] - >; + PermissionQuery: OneOf<[string, { + and: components["schemas"]["PermissionQuery"][]; + }, { + or: components["schemas"]["PermissionQuery"][]; + }, null]>; V1KeysVerifyKeyRequest: { /** * @description The id of the api where the key belongs to. This is optional for now but will be required soon. @@ -618,21 +593,21 @@ export interface components { * ] */ ratelimits?: { - /** - * @description The name of the ratelimit. - * @example tokens - */ - name: string; - /** - * @description Optionally override how expensive this operation is and how many tokens are deducted from the current limit. - * @default 1 - */ - cost?: number; - /** @description Optionally override the limit. */ - limit?: number; - /** @description Optionally override the ratelimit window duration. */ - duration?: number; - }[]; + /** + * @description The name of the ratelimit. + * @example tokens + */ + name: string; + /** + * @description Optionally override how expensive this operation is and how many tokens are deducted from the current limit. + * @default 1 + */ + cost?: number; + /** @description Optionally override the limit. */ + limit?: number; + /** @description Optionally override the ratelimit window duration. */ + duration?: number; + }[]; }; ErrDeleteProtected: { error: { @@ -658,7 +633,8 @@ export interface components { }; }; responses: never; - parameters: {}; + parameters: { + }; requestBodies: never; headers: never; pathItems: never; @@ -669,6 +645,7 @@ export type $defs = Record; export type external = Record; export interface operations { + "v1.liveness": { responses: { /** @description The configured services and their status */ @@ -1196,7 +1173,7 @@ export interface operations { * "refillInterval": 60 * } */ - ratelimit?: { + ratelimit?: ({ /** * @deprecated * @description Fast ratelimiting doesn't add latency, while consistent ratelimiting is more accurate. @@ -1228,7 +1205,7 @@ export interface operations { * This field will become required in a future version. */ duration?: number; - } | null; + }) | null; /** * @description The number of requests that can be made with this key before it becomes invalid. Set `null` to disable. * @example 1000 @@ -1241,7 +1218,7 @@ export interface operations { * "amount": 100 * } */ - refill?: { + refill?: ({ /** * @description Unkey will automatically refill verifications at the set interval. If null is used the refill functionality will be removed from the key. * @enum {string} @@ -1249,8 +1226,7 @@ export interface operations { interval: "daily" | "monthly"; /** @description The amount of verifications to refill for each occurrence is determined individually for each key. */ amount: number; - refillDay: number; - } | null; + }) | null; /** * @description Set if key is enabled or disabled. If disabled, the key cannot be used to verify. * @example true @@ -1273,16 +1249,16 @@ export interface operations { * ] */ roles?: { - /** @description The id of the role. Provide either `id` or `name`. If both are provided `id` is used. */ - id?: string; - /** @description Identify the role via its name. Provide either `id` or `name`. If both are provided `id` is used. */ - name?: string; - /** - * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. - * Autocreating roles requires your root key to have the `rbac.*.create_role` permission, otherwise the request will get rejected - */ - create?: boolean; - }[]; + /** @description The id of the role. Provide either `id` or `name`. If both are provided `id` is used. */ + id?: string; + /** @description Identify the role via its name. Provide either `id` or `name`. If both are provided `id` is used. */ + name?: string; + /** + * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. + * Autocreating roles requires your root key to have the `rbac.*.create_role` permission, otherwise the request will get rejected + */ + create?: boolean; + }[]; /** * @description The permissions you want to set for this key. This overwrites all existing permissions. * Setting permissions requires the `rbac.*.add_permission_to_key` permission. @@ -1300,16 +1276,16 @@ export interface operations { * ] */ permissions?: { - /** @description The id of the permission. Provide either `id` or `name`. If both are provided `id` is used. */ - id?: string; - /** @description Identify the permission via its name. Provide either `id` or `name`. If both are provided `id` is used. */ - name?: string; - /** - * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. - * Autocreating permissions requires your root key to have the `rbac.*.create_permission` permission, otherwise the request will get rejected - */ - create?: boolean; - }[]; + /** @description The id of the permission. Provide either `id` or `name`. If both are provided `id` is used. */ + id?: string; + /** @description Identify the permission via its name. Provide either `id` or `name`. If both are provided `id` is used. */ + name?: string; + /** + * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. + * Autocreating permissions requires your root key to have the `rbac.*.create_permission` permission, otherwise the request will get rejected + */ + create?: boolean; + }[]; }; }; }; @@ -1459,27 +1435,27 @@ export interface operations { content: { "application/json": { verifications: { - /** - * @description The timestamp of the usage data - * @example 1620000000000 - */ - time: number; - /** - * @description The number of successful requests - * @example 100 - */ - success: number; - /** - * @description The number of requests that were rate limited - * @example 10 - */ - rateLimited: number; - /** - * @description The number of requests that exceeded the usage limit - * @example 0 - */ - usageExceeded: number; - }[]; + /** + * @description The timestamp of the usage data + * @example 1620000000000 + */ + time: number; + /** + * @description The number of successful requests + * @example 100 + */ + success: number; + /** + * @description The number of requests that were rate limited + * @example 10 + */ + rateLimited: number; + /** + * @description The number of requests that exceeded the usage limit + * @example 0 + */ + usageExceeded: number; + }[]; }; }; }; @@ -1535,16 +1511,16 @@ export interface operations { keyId: string; /** @description The permissions you want to add to this key */ permissions: { - /** @description The id of the permission. Provide either `id` or `name`. If both are provided `id` is used. */ - id?: string; - /** @description Identify the permission via its name. Provide either `id` or `name`. If both are provided `id` is used. */ - name?: string; - /** - * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. - * Autocreating permissions requires your root key to have the `rbac.*.create_permission` permission, otherwise the request will get rejected - */ - create?: boolean; - }[]; + /** @description The id of the permission. Provide either `id` or `name`. If both are provided `id` is used. */ + id?: string; + /** @description Identify the permission via its name. Provide either `id` or `name`. If both are provided `id` is used. */ + name?: string; + /** + * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. + * Autocreating permissions requires your root key to have the `rbac.*.create_permission` permission, otherwise the request will get rejected + */ + create?: boolean; + }[]; }; }; }; @@ -1553,17 +1529,17 @@ export interface operations { 200: { content: { "application/json": { - /** - * @description The id of the permission. This is used internally - * @example perm_123 - */ - id: string; - /** - * @description The name of the permission - * @example dns.record.create - */ - name: string; - }[]; + /** + * @description The id of the permission. This is used internally + * @example perm_123 + */ + id: string; + /** + * @description The name of the permission + * @example dns.record.create + */ + name: string; + }[]; }; }; /** @description The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). */ @@ -1628,11 +1604,11 @@ export interface operations { * ] */ permissions: { - /** @description The id of the permission. Provide either `id` or `name`. If both are provided `id` is used. */ - id?: string; - /** @description Identify the permission via its name. Provide either `id` or `name`. If both are provided `id` is used. */ - name?: string; - }[]; + /** @description The id of the permission. Provide either `id` or `name`. If both are provided `id` is used. */ + id?: string; + /** @description Identify the permission via its name. Provide either `id` or `name`. If both are provided `id` is used. */ + name?: string; + }[]; }; }; }; @@ -1710,16 +1686,16 @@ export interface operations { * ] */ permissions: { - /** @description The id of the permission. Provide either `id` or `name`. If both are provided `id` is used. */ - id?: string; - /** @description Identify the permission via its name. Provide either `id` or `name`. If both are provided `id` is used. */ - name?: string; - /** - * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. - * Autocreating permissions requires your root key to have the `rbac.*.create_permission` permission, otherwise the request will get rejected - */ - create?: boolean; - }[]; + /** @description The id of the permission. Provide either `id` or `name`. If both are provided `id` is used. */ + id?: string; + /** @description Identify the permission via its name. Provide either `id` or `name`. If both are provided `id` is used. */ + name?: string; + /** + * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. + * Autocreating permissions requires your root key to have the `rbac.*.create_permission` permission, otherwise the request will get rejected + */ + create?: boolean; + }[]; }; }; }; @@ -1728,17 +1704,17 @@ export interface operations { 200: { content: { "application/json": { - /** - * @description The id of the permission. This is used internally - * @example perm_123 - */ - id: string; - /** - * @description The name of the permission - * @example dns.record.create - */ - name: string; - }[]; + /** + * @description The id of the permission. This is used internally + * @example perm_123 + */ + id: string; + /** + * @description The name of the permission + * @example dns.record.create + */ + name: string; + }[]; }; }; /** @description The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). */ @@ -1808,16 +1784,16 @@ export interface operations { * ] */ roles: { - /** @description The id of the role. Provide either `id` or `name`. If both are provided `id` is used. */ - id?: string; - /** @description Identify the role via its name. Provide either `id` or `name`. If both are provided `id` is used. */ - name?: string; - /** - * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. - * Autocreating roles requires your root key to have the `rbac.*.create_role` permission, otherwise the request will get rejected - */ - create?: boolean; - }[]; + /** @description The id of the role. Provide either `id` or `name`. If both are provided `id` is used. */ + id?: string; + /** @description Identify the role via its name. Provide either `id` or `name`. If both are provided `id` is used. */ + name?: string; + /** + * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. + * Autocreating roles requires your root key to have the `rbac.*.create_role` permission, otherwise the request will get rejected + */ + create?: boolean; + }[]; }; }; }; @@ -1826,17 +1802,17 @@ export interface operations { 200: { content: { "application/json": { - /** - * @description The id of the role. This is used internally - * @example role_123 - */ - id: string; - /** - * @description The name of the role - * @example dns.record.create - */ - name: string; - }[]; + /** + * @description The id of the role. This is used internally + * @example role_123 + */ + id: string; + /** + * @description The name of the role + * @example dns.record.create + */ + name: string; + }[]; }; }; /** @description The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). */ @@ -1901,11 +1877,11 @@ export interface operations { * ] */ roles: { - /** @description The id of the role. Provide either `id` or `name`. If both are provided `id` is used. */ - id?: string; - /** @description Identify the role via its name. Provide either `id` or `name`. If both are provided `id` is used. */ - name?: string; - }[]; + /** @description The id of the role. Provide either `id` or `name`. If both are provided `id` is used. */ + id?: string; + /** @description Identify the role via its name. Provide either `id` or `name`. If both are provided `id` is used. */ + name?: string; + }[]; }; }; }; @@ -1983,16 +1959,16 @@ export interface operations { * ] */ roles: { - /** @description The id of the role. Provide either `id` or `name`. If both are provided `id` is used. */ - id?: string; - /** @description Identify the role via its name. Provide either `id` or `name`. If both are provided `id` is used. */ - name?: string; - /** - * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. - * Autocreating roles requires your root key to have the `rbac.*.create_role` permission, otherwise the request will get rejected - */ - create?: boolean; - }[]; + /** @description The id of the role. Provide either `id` or `name`. If both are provided `id` is used. */ + id?: string; + /** @description Identify the role via its name. Provide either `id` or `name`. If both are provided `id` is used. */ + name?: string; + /** + * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. + * Autocreating roles requires your root key to have the `rbac.*.create_role` permission, otherwise the request will get rejected + */ + create?: boolean; + }[]; }; }; }; @@ -2001,17 +1977,17 @@ export interface operations { 200: { content: { "application/json": { - /** - * @description The id of the role. This is used internally - * @example role_123 - */ - id: string; - /** - * @description The name of the role - * @example dns.record.create - */ - name: string; - }[]; + /** + * @description The id of the role. This is used internally + * @example role_123 + */ + id: string; + /** + * @description The name of the role + * @example dns.record.create + */ + name: string; + }[]; }; }; /** @description The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). */ @@ -2460,26 +2436,26 @@ export interface operations { * ] */ resources?: { - /** - * @description The type of resource - * @example organization - */ - type: string; - /** - * @description The unique identifier for the resource - * @example org_123 - */ - id: string; - /** - * @description A human readable name for this resource - * @example unkey - */ - name?: string; - /** @description Attach any metadata to this resources */ - meta?: { - [key: string]: unknown; - }; - }[]; + /** + * @description The type of resource + * @example organization + */ + type: string; + /** + * @description The unique identifier for the resource + * @example org_123 + */ + id: string; + /** + * @description A human readable name for this resource + * @example unkey + */ + name?: string; + /** @description Attach any metadata to this resources */ + meta?: { + [key: string]: unknown; + }; + }[]; }; }; }; @@ -2557,230 +2533,18 @@ export interface operations { "v1.migrations.createKeys": { requestBody: { content: { - "application/json": { - /** - * @description Choose an `API` where this key should be created. - * @example api_123 - */ - apiId: string; - /** - * @description To make it easier for your users to understand which product an api key belongs to, you can add prefix them. - * - * For example Stripe famously prefixes their customer ids with cus_ or their api keys with sk_live_. - * - * The underscore is automatically added if you are defining a prefix, for example: "prefix": "abc" will result in a key like abc_xxxxxxxxx - */ - prefix?: string; - /** - * @description The name for your Key. This is not customer facing. - * @example my key - */ - name?: string; - /** @description The raw key in plaintext. If provided, unkey encrypts this value and stores it securely. Provide either `hash` or `plaintext` */ - plaintext?: string; - /** @description Provide either `hash` or `plaintext` */ - hash?: { - /** @description The hashed and encoded key */ - value: string; + "application/json": ({ /** - * @description The algorithm for hashing and encoding, currently only sha256 and base64 are supported - * @enum {string} + * @description Choose an `API` where this key should be created. + * @example api_123 */ - variant: "sha256_base64"; - }; - /** - * @description The first 4 characters of the key. If a prefix is used, it should be the prefix plus 4 characters. - * @example unkey_32kq - */ - start?: string; - /** - * @description Your user’s Id. This will provide a link between Unkey and your customer record. - * When validating a key, we will return this back to you, so you can clearly identify your user from their api key. - * @example team_123 - */ - ownerId?: string; - /** - * @description This is a place for dynamic meta data, anything that feels useful for you should go here - * @example { - * "billingTier": "PRO", - * "trialEnds": "2023-06-16T17:16:37.161Z" - * } - */ - meta?: { - [key: string]: unknown; - }; - /** - * @description A list of roles that this key should have. If the role does not exist, an error is thrown - * @example [ - * "admin", - * "finance" - * ] - */ - roles?: string[]; - /** - * @description A list of permissions that this key should have. If the permission does not exist, an error is thrown - * @example [ - * "domains.create_record", - * "say_hello" - * ] - */ - permissions?: string[]; - /** - * @description You can auto expire keys by providing a unix timestamp in milliseconds. Once Keys expire they will automatically be disabled and are no longer valid unless you enable them again. - * @example 1623869797161 - */ - expires?: number; - /** - * @description You can limit the number of requests a key can make. Once a key reaches 0 remaining requests, it will automatically be disabled and is no longer valid unless you update it. - * @example 1000 - */ - remaining?: number; - /** - * @description Unkey enables you to refill verifications for each key at regular intervals. - * @example { - * "interval": "daily", - * "amount": 100 - * } - */ - refill?: { + apiId: string; /** - * @description Unkey will automatically refill verifications at the set interval. - * @enum {string} - */ - interval: "daily" | "monthly"; - /** @description The number of verifications to refill for each occurrence is determined individually for each key. */ - amount: number; - }; - /** - * @description Unkey comes with per-key ratelimiting out of the box. - * @example { - * "type": "fast", - * "limit": 10, - * "refillRate": 1, - * "refillInterval": 60 - * } - */ - ratelimit?: { - /** - * @description Async will return a response immediately, lowering latency at the cost of accuracy. - * @default false - */ - async?: boolean; - /** - * @deprecated - * @description Fast ratelimiting doesn't add latency, while consistent ratelimiting is more accurate. - * @default fast - * @enum {string} - */ - type?: "fast" | "consistent"; - /** @description The total amount of burstable requests. */ - limit: number; - /** - * @deprecated - * @description How many tokens to refill during each refillInterval. - */ - refillRate: number; - /** - * @deprecated - * @description Determines the speed at which tokens are refilled, in milliseconds. - */ - refillInterval: number; - }; - /** - * @description Sets if key is enabled or disabled. Disabled keys are not valid. - * @default true - * @example false - */ - enabled?: boolean; - /** - * @description Environments allow you to divide your keyspace. - * - * Some applications like Stripe, Clerk, WorkOS and others have a concept of "live" and "test" keys to - * give the developer a way to develop their own application without the risk of modifying real world - * resources. - * - * When you set an environment, we will return it back to you when validating the key, so you can - * handle it correctly. - */ - environment?: string; - }[]; - }; - }; - responses: { - /** @description The key ids of all created keys */ - 200: { - content: { - "application/json": { - /** - * @description The ids of the keys. This is not a secret and can be stored as a reference if you wish. You need the keyId to update or delete a key later. - * @example [ - * "key_123", - * "key_456" - * ] - */ - keyIds: string[]; - }; - }; - }; - /** @description The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). */ - 400: { - content: { - "application/json": components["schemas"]["ErrBadRequest"]; - }; - }; - /** @description Although the HTTP standard specifies "unauthorized", semantically this response means "unauthenticated". That is, the client must authenticate itself to get the requested response. */ - 401: { - content: { - "application/json": components["schemas"]["ErrUnauthorized"]; - }; - }; - /** @description The client does not have access rights to the content; that is, it is unauthorized, so the server is refusing to give the requested resource. Unlike 401 Unauthorized, the client's identity is known to the server. */ - 403: { - content: { - "application/json": components["schemas"]["ErrForbidden"]; - }; - }; - /** @description The server cannot find the requested resource. In the browser, this means the URL is not recognized. In an API, this can also mean that the endpoint is valid but the resource itself does not exist. Servers may also send this response instead of 403 Forbidden to hide the existence of a resource from an unauthorized client. This response code is probably the most well known due to its frequent occurrence on the web. */ - 404: { - content: { - "application/json": components["schemas"]["ErrNotFound"]; - }; - }; - /** @description This response is sent when a request conflicts with the current state of the server. */ - 409: { - content: { - "application/json": components["schemas"]["ErrConflict"]; - }; - }; - /** @description The user has sent too many requests in a given amount of time ("rate limiting") */ - 429: { - content: { - "application/json": components["schemas"]["ErrTooManyRequests"]; - }; - }; - /** @description The server has encountered a situation it does not know how to handle. */ - 500: { - content: { - "application/json": components["schemas"]["ErrInternalServerError"]; - }; - }; - }; - }; - "v1.migrations.enqueueKeys": { - requestBody: { - content: { - "application/json": { - /** @description Contact support@unkey.dev to receive your migration id. */ - migrationId: string; - /** @description The id of the api, you want to migrate keys to */ - apiId: string; - keys: { - /** - * @description To make it easier for your users to understand which product an api key belongs to, you can add prefix them. - * - * For example Stripe famously prefixes their customer ids with cus_ or their api keys with sk_live_. - * - * The underscore is automatically added if you are defining a prefix, for example: "prefix": "abc" will result in a key like abc_xxxxxxxxx + * @description To make it easier for your users to understand which product an api key belongs to, you can add prefix them. + * + * For example Stripe famously prefixes their customer ids with cus_ or their api keys with sk_live_. + * + * The underscore is automatically added if you are defining a prefix, for example: "prefix": "abc" will result in a key like abc_xxxxxxxxx */ prefix?: string; /** @@ -2864,43 +2628,39 @@ export interface operations { amount: number; }; /** - * @description Unkey comes with per-key fixed-window ratelimiting out of the box. + * @description Unkey comes with per-key ratelimiting out of the box. * @example { * "type": "fast", * "limit": 10, - * "duration": 60000 + * "refillRate": 1, + * "refillInterval": 60 * } */ ratelimit?: { /** * @description Async will return a response immediately, lowering latency at the cost of accuracy. - * @default true + * @default false */ async?: boolean; /** * @deprecated - * @description Deprecated, use `async`. Fast ratelimiting doesn't add latency, while consistent ratelimiting is more accurate. + * @description Fast ratelimiting doesn't add latency, while consistent ratelimiting is more accurate. * @default fast * @enum {string} */ type?: "fast" | "consistent"; - /** @description The total amount of requests in a given interval. */ + /** @description The total amount of burstable requests. */ limit: number; - /** - * @description The window duration in milliseconds - * @example 60000 - */ - duration: number; /** * @deprecated * @description How many tokens to refill during each refillInterval. */ - refillRate?: number; + refillRate: number; /** * @deprecated - * @description The refill timeframe, in milliseconds. + * @description Determines the speed at which tokens are refilled, in milliseconds. */ - refillInterval?: number; + refillInterval: number; }; /** * @description Sets if key is enabled or disabled. Disabled keys are not valid. @@ -2919,7 +2679,223 @@ export interface operations { * handle it correctly. */ environment?: string; - }[]; + })[]; + }; + }; + responses: { + /** @description The key ids of all created keys */ + 200: { + content: { + "application/json": { + /** + * @description The ids of the keys. This is not a secret and can be stored as a reference if you wish. You need the keyId to update or delete a key later. + * @example [ + * "key_123", + * "key_456" + * ] + */ + keyIds: string[]; + }; + }; + }; + /** @description The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). */ + 400: { + content: { + "application/json": components["schemas"]["ErrBadRequest"]; + }; + }; + /** @description Although the HTTP standard specifies "unauthorized", semantically this response means "unauthenticated". That is, the client must authenticate itself to get the requested response. */ + 401: { + content: { + "application/json": components["schemas"]["ErrUnauthorized"]; + }; + }; + /** @description The client does not have access rights to the content; that is, it is unauthorized, so the server is refusing to give the requested resource. Unlike 401 Unauthorized, the client's identity is known to the server. */ + 403: { + content: { + "application/json": components["schemas"]["ErrForbidden"]; + }; + }; + /** @description The server cannot find the requested resource. In the browser, this means the URL is not recognized. In an API, this can also mean that the endpoint is valid but the resource itself does not exist. Servers may also send this response instead of 403 Forbidden to hide the existence of a resource from an unauthorized client. This response code is probably the most well known due to its frequent occurrence on the web. */ + 404: { + content: { + "application/json": components["schemas"]["ErrNotFound"]; + }; + }; + /** @description This response is sent when a request conflicts with the current state of the server. */ + 409: { + content: { + "application/json": components["schemas"]["ErrConflict"]; + }; + }; + /** @description The user has sent too many requests in a given amount of time ("rate limiting") */ + 429: { + content: { + "application/json": components["schemas"]["ErrTooManyRequests"]; + }; + }; + /** @description The server has encountered a situation it does not know how to handle. */ + 500: { + content: { + "application/json": components["schemas"]["ErrInternalServerError"]; + }; + }; + }; + }; + "v1.migrations.enqueueKeys": { + requestBody: { + content: { + "application/json": { + /** @description Contact support@unkey.dev to receive your migration id. */ + migrationId: string; + /** @description The id of the api, you want to migrate keys to */ + apiId: string; + keys: ({ + /** + * @description To make it easier for your users to understand which product an api key belongs to, you can add prefix them. + * + * For example Stripe famously prefixes their customer ids with cus_ or their api keys with sk_live_. + * + * The underscore is automatically added if you are defining a prefix, for example: "prefix": "abc" will result in a key like abc_xxxxxxxxx + */ + prefix?: string; + /** + * @description The name for your Key. This is not customer facing. + * @example my key + */ + name?: string; + /** @description The raw key in plaintext. If provided, unkey encrypts this value and stores it securely. Provide either `hash` or `plaintext` */ + plaintext?: string; + /** @description Provide either `hash` or `plaintext` */ + hash?: { + /** @description The hashed and encoded key */ + value: string; + /** + * @description The algorithm for hashing and encoding, currently only sha256 and base64 are supported + * @enum {string} + */ + variant: "sha256_base64"; + }; + /** + * @description The first 4 characters of the key. If a prefix is used, it should be the prefix plus 4 characters. + * @example unkey_32kq + */ + start?: string; + /** + * @description Your user’s Id. This will provide a link between Unkey and your customer record. + * When validating a key, we will return this back to you, so you can clearly identify your user from their api key. + * @example team_123 + */ + ownerId?: string; + /** + * @description This is a place for dynamic meta data, anything that feels useful for you should go here + * @example { + * "billingTier": "PRO", + * "trialEnds": "2023-06-16T17:16:37.161Z" + * } + */ + meta?: { + [key: string]: unknown; + }; + /** + * @description A list of roles that this key should have. If the role does not exist, an error is thrown + * @example [ + * "admin", + * "finance" + * ] + */ + roles?: string[]; + /** + * @description A list of permissions that this key should have. If the permission does not exist, an error is thrown + * @example [ + * "domains.create_record", + * "say_hello" + * ] + */ + permissions?: string[]; + /** + * @description You can auto expire keys by providing a unix timestamp in milliseconds. Once Keys expire they will automatically be disabled and are no longer valid unless you enable them again. + * @example 1623869797161 + */ + expires?: number; + /** + * @description You can limit the number of requests a key can make. Once a key reaches 0 remaining requests, it will automatically be disabled and is no longer valid unless you update it. + * @example 1000 + */ + remaining?: number; + /** + * @description Unkey enables you to refill verifications for each key at regular intervals. + * @example { + * "interval": "daily", + * "amount": 100 + * } + */ + refill?: { + /** + * @description Unkey will automatically refill verifications at the set interval. + * @enum {string} + */ + interval: "daily" | "monthly"; + /** @description The number of verifications to refill for each occurrence is determined individually for each key. */ + amount: number; + }; + /** + * @description Unkey comes with per-key fixed-window ratelimiting out of the box. + * @example { + * "type": "fast", + * "limit": 10, + * "duration": 60000 + * } + */ + ratelimit?: { + /** + * @description Async will return a response immediately, lowering latency at the cost of accuracy. + * @default true + */ + async?: boolean; + /** + * @deprecated + * @description Deprecated, use `async`. Fast ratelimiting doesn't add latency, while consistent ratelimiting is more accurate. + * @default fast + * @enum {string} + */ + type?: "fast" | "consistent"; + /** @description The total amount of requests in a given interval. */ + limit: number; + /** + * @description The window duration in milliseconds + * @example 60000 + */ + duration: number; + /** + * @deprecated + * @description How many tokens to refill during each refillInterval. + */ + refillRate?: number; + /** + * @deprecated + * @description The refill timeframe, in milliseconds. + */ + refillInterval?: number; + }; + /** + * @description Sets if key is enabled or disabled. Disabled keys are not valid. + * @default true + * @example false + */ + enabled?: boolean; + /** + * @description Environments allow you to divide your keyspace. + * + * Some applications like Stripe, Clerk, WorkOS and others have a concept of "live" and "test" keys to + * give the developer a way to develop their own application without the risk of modifying real world + * resources. + * + * When you set an environment, we will return it back to you when validating the key, so you can + * handle it correctly. + */ + environment?: string; + })[]; }; }; }; @@ -3190,22 +3166,22 @@ export interface operations { 200: { content: { "application/json": { - /** - * @description The id of the permission - * @example perm_123 - */ - id: string; - /** - * @description The name of the permission. - * @example domain.record.manager - */ - name: string; - /** - * @description The description of what this permission does. This is just for your team, your users will not see this. - * @example Can manage dns records - */ - description?: string; - }[]; + /** + * @description The id of the permission + * @example perm_123 + */ + id: string; + /** + * @description The name of the permission. + * @example domain.record.manager + */ + name: string; + /** + * @description The description of what this permission does. This is just for your team, your users will not see this. + * @example Can manage dns records + */ + description?: string; + }[]; }; }; /** @description The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). */ @@ -3468,22 +3444,22 @@ export interface operations { 200: { content: { "application/json": { - /** - * @description The id of the role - * @example role_1234 - */ - id: string; - /** - * @description The name of the role. - * @example domain.record.manager - */ - name: string; - /** - * @description The description of what this role does. This is just for your team, your users will not see this. - * @example Can manage dns records - */ - description?: string; - }[]; + /** + * @description The id of the role + * @example role_1234 + */ + id: string; + /** + * @description The name of the role. + * @example domain.record.manager + */ + name: string; + /** + * @description The description of what this role does. This is just for your team, your users will not see this. + * @example Can manage dns records + */ + description?: string; + }[]; }; }; /** @description The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). */ @@ -3557,22 +3533,22 @@ export interface operations { * When verifying keys, you can specify which limits you want to use and all keys attached to this identity, will share the limits. */ ratelimits?: { - /** - * @description The name of this limit. You will need to use this again when verifying a key. - * @example tokens - */ - name: string; - /** - * @description How many requests may pass within a given window before requests are rejected. - * @example 10 - */ - limit: number; - /** - * @description The duration for each ratelimit window in milliseconds. - * @example 1000 - */ - duration: number; - }[]; + /** + * @description The name of this limit. You will need to use this again when verifying a key. + * @example tokens + */ + name: string; + /** + * @description How many requests may pass within a given window before requests are rejected. + * @example 10 + */ + limit: number; + /** + * @description The duration for each ratelimit window in milliseconds. + * @example 1000 + */ + duration: number; + }[]; }; }; }; @@ -3655,22 +3631,22 @@ export interface operations { }; /** @description When verifying keys, you can specify which limits you want to use and all keys attached to this identity, will share the limits. */ ratelimits: { - /** - * @description The name of this limit. You will need to use this again when verifying a key. - * @example tokens - */ - name: string; - /** - * @description How many requests may pass within a given window before requests are rejected. - * @example 10 - */ - limit: number; - /** - * @description The duration for each ratelimit window in milliseconds. - * @example 1000 - */ - duration: number; - }[]; + /** + * @description The name of this limit. You will need to use this again when verifying a key. + * @example tokens + */ + name: string; + /** + * @description How many requests may pass within a given window before requests are rejected. + * @example 10 + */ + limit: number; + /** + * @description The duration for each ratelimit window in milliseconds. + * @example 1000 + */ + duration: number; + }[]; }; }; }; @@ -3733,29 +3709,29 @@ export interface operations { "application/json": { /** @description A list of identities. */ identities: { - /** @description The id of this identity. Used to interact with unkey's API */ - id: string; - /** @description The id in your system */ - externalId: string; - /** @description When verifying keys, you can specify which limits you want to use and all keys attached to this identity, will share the limits. */ - ratelimits: { - /** - * @description The name of this limit. You will need to use this again when verifying a key. - * @example tokens - */ - name: string; - /** - * @description How many requests may pass within a given window before requests are rejected. - * @example 10 - */ - limit: number; - /** - * @description The duration for each ratelimit window in milliseconds. - * @example 1000 - */ - duration: number; + /** @description The id of this identity. Used to interact with unkey's API */ + id: string; + /** @description The id in your system */ + externalId: string; + /** @description When verifying keys, you can specify which limits you want to use and all keys attached to this identity, will share the limits. */ + ratelimits: { + /** + * @description The name of this limit. You will need to use this again when verifying a key. + * @example tokens + */ + name: string; + /** + * @description How many requests may pass within a given window before requests are rejected. + * @example 10 + */ + limit: number; + /** + * @description The duration for each ratelimit window in milliseconds. + * @example 1000 + */ + duration: number; + }[]; }[]; - }[]; /** * @description The cursor to use for the next page of results, if no cursor is returned, there are no more results * @example eyJrZXkiOiJrZXlfMTIzNCJ9 @@ -3848,22 +3824,22 @@ export interface operations { * When verifying keys, you can specify which limits you want to use and all keys attached to this identity, will share the limits. */ ratelimits?: { - /** - * @description The name of this limit. You will need to use this again when verifying a key. - * @example tokens - */ - name: string; - /** - * @description How many requests may pass within a given window before requests are rejected. - * @example 10 - */ - limit: number; - /** - * @description The duration for each ratelimit window in milliseconds. - * @example 1000 - */ - duration: number; - }[]; + /** + * @description The name of this limit. You will need to use this again when verifying a key. + * @example tokens + */ + name: string; + /** + * @description How many requests may pass within a given window before requests are rejected. + * @example 10 + */ + limit: number; + /** + * @description The duration for each ratelimit window in milliseconds. + * @example 1000 + */ + duration: number; + }[]; }; }; }; @@ -3892,22 +3868,22 @@ export interface operations { [key: string]: unknown; }; ratelimits: { - /** - * @description The name of this limit. - * @example tokens - */ - name: string; - /** - * @description How many requests may pass within a given window before requests are rejected. - * @example 10 - */ - limit: number; - /** - * @description The duration for each ratelimit window in milliseconds. - * @example 1000 - */ - duration: number; - }[]; + /** + * @description The name of this limit. + * @example tokens + */ + name: string; + /** + * @description How many requests may pass within a given window before requests are rejected. + * @example 10 + */ + limit: number; + /** + * @description The duration for each ratelimit window in milliseconds. + * @example 1000 + */ + duration: number; + }[]; }; }; }; @@ -4269,15 +4245,7 @@ export interface operations { * @example NOT_FOUND * @enum {string} */ - code?: - | "NOT_FOUND" - | "FORBIDDEN" - | "USAGE_EXCEEDED" - | "RATE_LIMITED" - | "UNAUTHORIZED" - | "DISABLED" - | "INSUFFICIENT_PERMISSIONS" - | "EXPIRED"; + code?: "NOT_FOUND" | "FORBIDDEN" | "USAGE_EXCEEDED" | "RATE_LIMITED" | "UNAUTHORIZED" | "DISABLED" | "INSUFFICIENT_PERMISSIONS" | "EXPIRED"; }; }; }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 17012c55f4..3f1163ac97 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -199,7 +199,7 @@ importers: version: link:../../internal/schema ai: specifier: ^3.4.7 - version: 3.4.7(react@18.3.1)(svelte@4.2.19)(vue@3.5.10)(zod@3.23.8) + version: 3.4.7(react@18.3.1)(svelte@4.2.19)(vue@3.5.7)(zod@3.23.8) drizzle-orm: specifier: ^0.33.0 version: 0.33.0(@opentelemetry/api@1.4.1)(@planetscale/database@1.18.0)(@types/react@18.2.79)(react@18.3.1) @@ -860,7 +860,7 @@ importers: version: link:../../internal/worker-logging ai: specifier: ^3.0.23 - version: 3.0.23(react@18.3.1)(solid-js@1.9.1)(svelte@4.2.19)(vue@3.5.10)(zod@3.23.8) + version: 3.0.23(react@18.3.1)(solid-js@1.8.22)(svelte@4.2.19)(vue@3.5.7)(zod@3.23.8) drizzle-orm: specifier: generated version: 0.32.0-aaf764c(@cloudflare/workers-types@4.20240603.0)(@planetscale/database@1.18.0)(react@18.3.1) @@ -879,7 +879,7 @@ importers: devDependencies: '@cloudflare/vitest-pool-workers': specifier: ^0.4.1 - version: 0.4.5(@cloudflare/workers-types@4.20240603.0)(@vitest/runner@1.5.3)(@vitest/snapshot@1.5.3)(vitest@1.5.3) + version: 0.4.5(@cloudflare/workers-types@4.20240603.0)(@vitest/runner@1.5.3)(@vitest/snapshot@1.5.3)(vitest@1.5.0) '@cloudflare/workers-types': specifier: ^4.20240603.0 version: 4.20240603.0 @@ -912,10 +912,10 @@ importers: version: 1.18.0 '@trigger.dev/nextjs': specifier: ^2.3.18 - version: 2.3.18(@trigger.dev/sdk@2.3.19)(next@14.2.10) + version: 2.3.18(@trigger.dev/sdk@2.3.18)(next@14.2.10) '@trigger.dev/sdk': specifier: ^2.3.18 - version: 2.3.19 + version: 2.3.18 '@trigger.dev/slack': specifier: ^2.3.18 version: 2.3.18 @@ -1166,7 +1166,7 @@ importers: version: link:../../packages/error ai: specifier: ^3.0.23 - version: 3.4.7(react@18.3.1)(svelte@4.2.19)(vue@3.5.10)(zod@3.23.8) + version: 3.0.23(react@18.3.1)(solid-js@1.8.22)(svelte@4.2.19)(vue@3.5.7)(zod@3.23.8) zod: specifier: ^3.23.5 version: 3.23.8 @@ -1409,7 +1409,7 @@ importers: version: 18.3.1 react-email: specifier: 2.1.1 - version: 2.1.1(@babel/core@7.25.2)(eslint@9.11.1)(ts-node@10.9.2) + version: 2.1.1(@babel/core@7.25.2)(eslint@9.11.0)(ts-node@10.9.2) resend: specifier: ^4.0.0 version: 4.0.0(react-dom@18.3.1)(react@18.3.1) @@ -1901,7 +1901,7 @@ packages: zod-to-json-schema: 3.23.2(zod@3.23.8) dev: false - /@ai-sdk/vue@0.0.53(vue@3.5.10)(zod@3.23.8): + /@ai-sdk/vue@0.0.53(vue@3.5.7)(zod@3.23.8): resolution: {integrity: sha512-FNScuIvM8N4Pj4Xto11blgI97c5cjjTelyk0M0MkyU+sLSbpQNDE78CRq5cW1oeVkJzzdv63+xh8jaFNe+2vnQ==} engines: {node: '>=18'} peerDependencies: @@ -1912,8 +1912,8 @@ packages: dependencies: '@ai-sdk/provider-utils': 1.0.20(zod@3.23.8) '@ai-sdk/ui-utils': 0.0.46(zod@3.23.8) - swrv: 1.0.4(vue@3.5.10) - vue: 3.5.10(typescript@5.5.3) + swrv: 1.0.4(vue@3.5.7) + vue: 3.5.7(typescript@5.5.3) transitivePeerDependencies: - zod dev: false @@ -2485,20 +2485,10 @@ packages: resolution: {integrity: sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==} engines: {node: '>=6.9.0'} - /@babel/helper-string-parser@7.25.7: - resolution: {integrity: sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==} - engines: {node: '>=6.9.0'} - dev: false - /@babel/helper-validator-identifier@7.24.7: resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} engines: {node: '>=6.9.0'} - /@babel/helper-validator-identifier@7.25.7: - resolution: {integrity: sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==} - engines: {node: '>=6.9.0'} - dev: false - /@babel/helper-validator-option@7.24.8: resolution: {integrity: sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==} engines: {node: '>=6.9.0'} @@ -2534,14 +2524,6 @@ packages: dependencies: '@babel/types': 7.25.6 - /@babel/parser@7.25.7: - resolution: {integrity: sha512-aZn7ETtQsjjGG5HruveUK06cU3Hljuhd9Iojm4M8WWv3wLE6OkE5PWbDUkItmMgegmccaITudyuW5RPYrYlgWw==} - engines: {node: '>=6.0.0'} - hasBin: true - dependencies: - '@babel/types': 7.25.7 - dev: false - /@babel/runtime@7.25.6: resolution: {integrity: sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==} engines: {node: '>=6.9.0'} @@ -2578,15 +2560,6 @@ packages: '@babel/helper-validator-identifier': 7.24.7 to-fast-properties: 2.0.0 - /@babel/types@7.25.7: - resolution: {integrity: sha512-vwIVdXG+j+FOpkwqHRcBgHLYNL7XMkufrlaFvL9o6Ai9sJn9+PdyIL5qa0XzTZw084c+u9LOls53eoZWP/W5WQ==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-string-parser': 7.25.7 - '@babel/helper-validator-identifier': 7.25.7 - to-fast-properties: 2.0.0 - dev: false - /@bany/curl-to-json@1.2.8: resolution: {integrity: sha512-hPt9KUM2sGZ5Ojx3O9utjzUgjRZI3CZPAlLf+cRY9EUzVs7tWt1OpA0bhEUTX2PEEkOeyZ6sC0tAQMOHh9ld+Q==} dependencies: @@ -3017,7 +2990,7 @@ packages: mime: 3.0.0 dev: true - /@cloudflare/vitest-pool-workers@0.4.5(@cloudflare/workers-types@4.20240603.0)(@vitest/runner@1.5.3)(@vitest/snapshot@1.5.3)(vitest@1.5.3): + /@cloudflare/vitest-pool-workers@0.4.5(@cloudflare/workers-types@4.20240603.0)(@vitest/runner@1.5.3)(@vitest/snapshot@1.5.3)(vitest@1.5.0): resolution: {integrity: sha512-YpYPw6iwGr3sWITZaYnh3bH92Khq+dhBOCyqTf1dwzRxNDYCUtaInLD+uEfZnv9A9sizs5oH1a7ptdZOd0oS/g==} peerDependencies: '@vitest/runner': 1.3.x - 1.5.x @@ -3032,7 +3005,7 @@ packages: esbuild: 0.17.19 miniflare: 3.20240610.0 semver: 7.6.3 - vitest: 1.5.3(@types/node@20.14.9)(@vitest/ui@1.6.0) + vitest: 1.5.0(@types/node@20.14.9)(@vitest/ui@1.6.0) wrangler: 3.60.3(@cloudflare/workers-types@4.20240603.0) zod: 3.23.8 transitivePeerDependencies: @@ -4816,13 +4789,13 @@ packages: dev: false optional: true - /@eslint-community/eslint-utils@4.4.0(eslint@9.11.1): + /@eslint-community/eslint-utils@4.4.0(eslint@9.11.0): resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 dependencies: - eslint: 9.11.1 + eslint: 9.11.0 eslint-visitor-keys: 3.4.3 dev: false @@ -4842,18 +4815,13 @@ packages: - supports-color dev: false - /@eslint/core@0.6.0: - resolution: {integrity: sha512-8I2Q8ykA4J0x0o7cg67FPVnehcqWTBehu/lmY+bolPFHGjh49YzGBMXTvpqVgEbBdvNCSxj6iFgiIyHzf03lzg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - dev: false - /@eslint/eslintrc@3.1.0: resolution: {integrity: sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: ajv: 6.12.6 debug: 4.3.7(supports-color@8.1.1) - espree: 10.2.0 + espree: 10.1.0 globals: 14.0.0 ignore: 5.3.2 import-fresh: 3.3.0 @@ -4864,8 +4832,8 @@ packages: - supports-color dev: false - /@eslint/js@9.11.1: - resolution: {integrity: sha512-/qu+TWz8WwPWc7/HcIJKi+c+MOm46GdVaSlTTQcaqaL53+GsoA6MxWp5PtTx48qbSP7ylM1Kn7nhvkugfJvRSA==} + /@eslint/js@9.11.0: + resolution: {integrity: sha512-LPkkenkDqyzTFauZLLAPhIb48fj6drrfMvRGSL9tS3AcZBSVTllemLSNyCvHNNL2t797S/6DJNSIwRwXgMO/eQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dev: false @@ -5711,7 +5679,7 @@ packages: '@babel/types': 7.25.6 '@radix-ui/react-dialog': 1.1.1(react-dom@18.3.1)(react@18.3.1) '@radix-ui/react-tooltip': 1.1.2(react-dom@18.3.1)(react@18.3.1) - '@rollup/pluginutils': 5.1.1 + '@rollup/pluginutils': 5.1.0 cmdk: 0.2.1(react-dom@18.3.1)(react@18.3.1) esbuild: 0.20.2 escalade: 3.2.0 @@ -5910,7 +5878,7 @@ packages: open: 8.4.2 openapi-types: 12.1.3 ora: 6.3.1 - socket.io: 4.8.0 + socket.io: 4.7.5 tar: 6.2.1 unist-util-visit: 4.1.2 yargs: 17.7.2 @@ -6278,8 +6246,8 @@ packages: - typescript dev: true - /@oclif/core@4.0.26: - resolution: {integrity: sha512-Gtgj7l+XYYk4wqJ7fTd+fsuKusuQESrSbeue+pC1UGJWt9tyrn3TNZu/lGJSSvEQVcxZY+y09hTET+1e1bmULA==} + /@oclif/core@3.27.0: + resolution: {integrity: sha512-Fg93aNFvXzBq5L7ztVHFP2nYwWU1oTCq48G0TjF/qC1UN36KWa2H5Hsm72kERd5x/sjy2M2Tn4kDEorUlpXOlw==} engines: {node: '>=18.0.0'} dependencies: '@types/cli-progress': 3.11.6 @@ -6296,10 +6264,13 @@ packages: globby: 11.1.0 hyperlinker: 1.0.0 indent-string: 4.0.0 - is-wsl: 3.1.0 - lilconfig: 3.1.2 + is-wsl: 2.2.0 + js-yaml: 3.14.1 minimatch: 9.0.5 - semver: 7.6.3 + natural-orderby: 2.0.3 + object-treeify: 1.1.33 + password-prompt: 1.1.3 + slice-ansi: 4.0.0 string-width: 4.2.3 strip-ansi: 6.0.1 supports-color: 8.1.1 @@ -6339,8 +6310,8 @@ packages: resolution: {integrity: sha512-lYNoqoQJz+p4AOMZ9N5k7OkLk/HZJSdaybJ4rWfDZ76pQ7AcrLEPtREi2wLfcwcrzKoBMsrwBoMTf3PnmpW7ZQ==} engines: {node: '>=18.0.0'} dependencies: - '@oclif/core': 4.0.26 - ansis: 3.3.2 + '@oclif/core': 3.27.0 + chalk: 5.3.0 debug: 4.3.7(supports-color@8.1.1) npm: 10.2.3 npm-run-path: 4.0.1 @@ -6850,9 +6821,11 @@ packages: peerDependencies: '@opentelemetry/api': ^1.0.0 dependencies: + '@grpc/grpc-js': 1.11.3 '@opentelemetry/api': 1.4.1 '@opentelemetry/core': 1.13.0(@opentelemetry/api@1.4.1) - '@opentelemetry/otlp-transformer': 0.52.1(@opentelemetry/api@1.4.1) + '@opentelemetry/otlp-exporter-base': 0.39.1(@opentelemetry/api@1.4.1) + protobufjs: 7.4.0 dev: false /@opentelemetry/otlp-transformer@0.39.1(@opentelemetry/api@1.4.1): @@ -11266,8 +11239,8 @@ packages: react: 18.3.1 dev: true - /@rollup/pluginutils@5.1.1: - resolution: {integrity: sha512-bVRmQqBIyGD+VMihdEV2IBurfIrdW9tD9yzJUL3CBRDbyPBVzQnBSMSgyUZHl1E335rpMRj7r4o683fXLYw8iw==} + /@rollup/pluginutils@5.1.0: + resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==} engines: {node: '>=14.0.0'} peerDependencies: rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 @@ -11288,14 +11261,6 @@ packages: dev: true optional: true - /@rollup/rollup-android-arm-eabi@4.24.0: - resolution: {integrity: sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==} - cpu: [arm] - os: [android] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-android-arm64@4.22.2: resolution: {integrity: sha512-I+B1v0a4iqdS9DvYt1RJZ3W+Oh9EVWjbY6gp79aAYipIbxSLEoQtFQlZEnUuwhDXCqMxJ3hluxKAdPD+GiluFQ==} cpu: [arm64] @@ -11304,14 +11269,6 @@ packages: dev: true optional: true - /@rollup/rollup-android-arm64@4.24.0: - resolution: {integrity: sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==} - cpu: [arm64] - os: [android] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-darwin-arm64@4.22.2: resolution: {integrity: sha512-BTHO7rR+LC67OP7I8N8GvdvnQqzFujJYWo7qCQ8fGdQcb8Gn6EQY+K1P+daQLnDCuWKbZ+gHAQZuKiQkXkqIYg==} cpu: [arm64] @@ -11320,14 +11277,6 @@ packages: dev: true optional: true - /@rollup/rollup-darwin-arm64@4.24.0: - resolution: {integrity: sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==} - cpu: [arm64] - os: [darwin] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-darwin-x64@4.22.2: resolution: {integrity: sha512-1esGwDNFe2lov4I6GsEeYaAMHwkqk0IbuGH7gXGdBmd/EP9QddJJvTtTF/jv+7R8ZTYPqwcdLpMTxK8ytP6k6Q==} cpu: [x64] @@ -11336,14 +11285,6 @@ packages: dev: true optional: true - /@rollup/rollup-darwin-x64@4.24.0: - resolution: {integrity: sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==} - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-arm-gnueabihf@4.22.2: resolution: {integrity: sha512-GBHuY07x96OTEM3OQLNaUSUwrOhdMea/LDmlFHi/HMonrgF6jcFrrFFwJhhe84XtA1oK/Qh4yFS+VMREf6dobg==} cpu: [arm] @@ -11352,14 +11293,6 @@ packages: dev: true optional: true - /@rollup/rollup-linux-arm-gnueabihf@4.24.0: - resolution: {integrity: sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-arm-musleabihf@4.22.2: resolution: {integrity: sha512-Dbfa9Sc1G1lWxop0gNguXOfGhaXQWAGhZUcqA0Vs6CnJq8JW/YOw/KvyGtQFmz4yDr0H4v9X248SM7bizYj4yQ==} cpu: [arm] @@ -11368,14 +11301,6 @@ packages: dev: true optional: true - /@rollup/rollup-linux-arm-musleabihf@4.24.0: - resolution: {integrity: sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-arm64-gnu@4.22.2: resolution: {integrity: sha512-Z1YpgBvFYhZIyBW5BoopwSg+t7yqEhs5HCei4JbsaXnhz/eZehT18DaXl957aaE9QK7TRGFryCAtStZywcQe1A==} cpu: [arm64] @@ -11384,14 +11309,6 @@ packages: dev: true optional: true - /@rollup/rollup-linux-arm64-gnu@4.24.0: - resolution: {integrity: sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-arm64-musl@4.22.2: resolution: {integrity: sha512-66Zszr7i/JaQ0u/lefcfaAw16wh3oT72vSqubIMQqWzOg85bGCPhoeykG/cC5uvMzH80DQa2L539IqKht6twVA==} cpu: [arm64] @@ -11400,14 +11317,6 @@ packages: dev: true optional: true - /@rollup/rollup-linux-arm64-musl@4.24.0: - resolution: {integrity: sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-powerpc64le-gnu@4.22.2: resolution: {integrity: sha512-HpJCMnlMTfEhwo19bajvdraQMcAq3FX08QDx3OfQgb+414xZhKNf3jNvLFYKbbDSGBBrQh5yNwWZrdK0g0pokg==} cpu: [ppc64] @@ -11416,14 +11325,6 @@ packages: dev: true optional: true - /@rollup/rollup-linux-powerpc64le-gnu@4.24.0: - resolution: {integrity: sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==} - cpu: [ppc64] - os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-riscv64-gnu@4.22.2: resolution: {integrity: sha512-/egzQzbOSRef2vYCINKITGrlwkzP7uXRnL+xU2j75kDVp3iPdcF0TIlfwTRF8woBZllhk3QaxNOEj2Ogh3t9hg==} cpu: [riscv64] @@ -11432,14 +11333,6 @@ packages: dev: true optional: true - /@rollup/rollup-linux-riscv64-gnu@4.24.0: - resolution: {integrity: sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==} - cpu: [riscv64] - os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-s390x-gnu@4.22.2: resolution: {integrity: sha512-qgYbOEbrPfEkH/OnUJd1/q4s89FvNJQIUldx8X2F/UM5sEbtkqZpf2s0yly2jSCKr1zUUOY1hnTP2J1WOzMAdA==} cpu: [s390x] @@ -11448,14 +11341,6 @@ packages: dev: true optional: true - /@rollup/rollup-linux-s390x-gnu@4.24.0: - resolution: {integrity: sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==} - cpu: [s390x] - os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-x64-gnu@4.22.2: resolution: {integrity: sha512-a0lkvNhFLhf+w7A95XeBqGQaG0KfS3hPFJnz1uraSdUe/XImkp/Psq0Ca0/UdD5IEAGoENVmnYrzSC9Y2a2uKQ==} cpu: [x64] @@ -11464,14 +11349,6 @@ packages: dev: true optional: true - /@rollup/rollup-linux-x64-gnu@4.24.0: - resolution: {integrity: sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-linux-x64-musl@4.22.2: resolution: {integrity: sha512-sSWBVZgzwtsuG9Dxi9kjYOUu/wKW+jrbzj4Cclabqnfkot8Z3VEHcIgyenA3lLn/Fu11uDviWjhctulkhEO60g==} cpu: [x64] @@ -11480,14 +11357,6 @@ packages: dev: true optional: true - /@rollup/rollup-linux-x64-musl@4.24.0: - resolution: {integrity: sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-win32-arm64-msvc@4.22.2: resolution: {integrity: sha512-t/YgCbZ638R/r7IKb9yCM6nAek1RUvyNdfU0SHMDLOf6GFe/VG1wdiUAsxTWHKqjyzkRGg897ZfCpdo1bsCSsA==} cpu: [arm64] @@ -11496,14 +11365,6 @@ packages: dev: true optional: true - /@rollup/rollup-win32-arm64-msvc@4.24.0: - resolution: {integrity: sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==} - cpu: [arm64] - os: [win32] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-win32-ia32-msvc@4.22.2: resolution: {integrity: sha512-kTmX5uGs3WYOA+gYDgI6ITkZng9SP71FEMoHNkn+cnmb9Zuyyay8pf0oO5twtTwSjNGy1jlaWooTIr+Dw4tIbw==} cpu: [ia32] @@ -11512,14 +11373,6 @@ packages: dev: true optional: true - /@rollup/rollup-win32-ia32-msvc@4.24.0: - resolution: {integrity: sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==} - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: true - optional: true - /@rollup/rollup-win32-x64-msvc@4.22.2: resolution: {integrity: sha512-Yy8So+SoRz8I3NS4Bjh91BICPOSVgdompTIPYTByUqU66AXSIOgmW3Lv1ke3NORPqxdF+RdrZET+8vYai6f4aA==} cpu: [x64] @@ -11528,14 +11381,6 @@ packages: dev: true optional: true - /@rollup/rollup-win32-x64-msvc@4.24.0: - resolution: {integrity: sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: true - optional: true - /@sec-ant/readable-stream@0.4.1: resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} dev: false @@ -11970,43 +11815,14 @@ packages: - utf-8-validate dev: false - /@trigger.dev/core@3.0.6: - resolution: {integrity: sha512-NpRRF4WKuUpX40YHQyhlouAe4LYMU3Vm74SB3fnHU4tKoRGAgtAc8zLX1HwzaWxItK0l3DBlgXaxB14SJaB/gw==} - engines: {node: '>=18.20.0'} - dependencies: - '@google-cloud/precise-date': 4.0.0 - '@opentelemetry/api': 1.4.1 - '@opentelemetry/api-logs': 0.52.1 - '@opentelemetry/exporter-logs-otlp-http': 0.52.1(@opentelemetry/api@1.4.1) - '@opentelemetry/exporter-trace-otlp-http': 0.52.1(@opentelemetry/api@1.4.1) - '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.4.1) - '@opentelemetry/resources': 1.13.0(@opentelemetry/api@1.4.1) - '@opentelemetry/sdk-logs': 0.52.1(@opentelemetry/api@1.4.1) - '@opentelemetry/sdk-node': 0.52.1(@opentelemetry/api@1.4.1) - '@opentelemetry/sdk-trace-base': 1.13.0(@opentelemetry/api@1.4.1) - '@opentelemetry/sdk-trace-node': 1.13.0(@opentelemetry/api@1.4.1) - '@opentelemetry/semantic-conventions': 1.13.0 - execa: 8.0.1 - humanize-duration: 3.32.1 - socket.io-client: 4.7.5 - superjson: 2.2.1 - zod: 3.22.3 - zod-error: 1.5.0 - zod-validation-error: 1.5.0(zod@3.22.3) - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - dev: false - - /@trigger.dev/nextjs@2.3.18(@trigger.dev/sdk@2.3.19)(next@14.2.10): + /@trigger.dev/nextjs@2.3.18(@trigger.dev/sdk@2.3.18)(next@14.2.10): resolution: {integrity: sha512-ZS0RTZNrzGEKfOLQLYt3iqlNquD7pd39Hpd/+2tvRCaPSQ3qPYQvdjBSueW0OURZSQSiNno5VUYR5vbVBcAaXA==} engines: {node: '>=18.0.0'} peerDependencies: '@trigger.dev/sdk': ^2.3.18 next: '>=12.0.0' dependencies: - '@trigger.dev/sdk': 2.3.19 + '@trigger.dev/sdk': 2.3.18 debug: 4.3.7(supports-color@8.1.1) next: 14.2.10(@babel/core@7.25.2)(@opentelemetry/api@1.4.1)(react-dom@18.3.1)(react@18.3.1) transitivePeerDependencies: @@ -12027,8 +11843,8 @@ packages: - supports-color dev: false - /@trigger.dev/sdk@2.3.19: - resolution: {integrity: sha512-NjX5vEK3ydx5Bac0rwQbHqOZWJJqT6qQ+/GkTErKDs59v+qD+s1g/fB/TtEGpsAjdZK9FIPm4ooFemYVmpIm/A==} + /@trigger.dev/sdk@2.3.18: + resolution: {integrity: sha512-Bjxgl4BbWOAL8rhxeBkl7SzvLLRBMJjiftq/7W7u96MDyPRFUoZZvVMSZzTJufnLBf/xS2JTi8LWU8gzhDJDvw==} engines: {node: '>=18.0.0'} dependencies: '@trigger.dev/core': 2.3.19 @@ -12081,7 +11897,7 @@ packages: engines: {node: '>=16.8.0'} dependencies: '@slack/web-api': 6.12.1 - '@trigger.dev/sdk': 2.3.19 + '@trigger.dev/sdk': 2.3.18 zod: 3.22.3 transitivePeerDependencies: - bufferutil @@ -12987,11 +12803,11 @@ packages: internmap: 2.0.3 dev: false - /@vitest/expect@1.5.3: - resolution: {integrity: sha512-y+waPz31pOFr3rD7vWTbwiLe5+MgsMm40jTZbQE8p8/qXyBX3CQsIXRx9XK12IbY7q/t5a5aM/ckt33b4PxK2g==} + /@vitest/expect@1.5.0: + resolution: {integrity: sha512-0pzuCI6KYi2SIC3LQezmxujU9RK/vwC1U9R0rLuGlNGcOuDWxqWKu6nUdFsX9tH1WU0SXtAxToOsEjeUn1s3hA==} dependencies: - '@vitest/spy': 1.5.3 - '@vitest/utils': 1.5.3 + '@vitest/spy': 1.5.0 + '@vitest/utils': 1.5.0 chai: 4.5.0 dev: true @@ -13003,6 +12819,14 @@ packages: chai: 4.5.0 dev: true + /@vitest/runner@1.5.0: + resolution: {integrity: sha512-7HWwdxXP5yDoe7DTpbif9l6ZmDwCzcSIK38kTSIt6CFEpMjX4EpCgT6wUmS0xTXqMI6E/ONmfgRKmaujpabjZQ==} + dependencies: + '@vitest/utils': 1.5.0 + p-limit: 5.0.0 + pathe: 1.1.2 + dev: true + /@vitest/runner@1.5.3: resolution: {integrity: sha512-7PlfuReN8692IKQIdCxwir1AOaP5THfNkp0Uc4BKr2na+9lALNit7ub9l3/R7MP8aV61+mHKRGiqEKRIwu6iiQ==} dependencies: @@ -13019,6 +12843,14 @@ packages: pathe: 1.1.2 dev: true + /@vitest/snapshot@1.5.0: + resolution: {integrity: sha512-qpv3fSEuNrhAO3FpH6YYRdaECnnRjg9VxbhdtPwPRnzSfHVXnNzzrpX4cJxqiwgRMo7uRMWDFBlsBq4Cr+rO3A==} + dependencies: + magic-string: 0.30.11 + pathe: 1.1.2 + pretty-format: 29.7.0 + dev: true + /@vitest/snapshot@1.5.3: resolution: {integrity: sha512-K3mvIsjyKYBhNIDujMD2gfQEzddLe51nNOAf45yKRt/QFJcUIeTQd2trRvv6M6oCBHNVnZwFWbQ4yj96ibiDsA==} dependencies: @@ -13035,8 +12867,8 @@ packages: pretty-format: 29.7.0 dev: true - /@vitest/spy@1.5.3: - resolution: {integrity: sha512-Llj7Jgs6lbnL55WoshJUUacdJfjU2honvGcAJBxhra5TPEzTJH8ZuhI3p/JwqqfnTr4PmP7nDmOXP53MS7GJlg==} + /@vitest/spy@1.5.0: + resolution: {integrity: sha512-vu6vi6ew5N5MMHJjD5PoakMRKYdmIrNJmyfkhRpQt5d9Ewhw9nZ5Aqynbi3N61bvk9UvZ5UysMT6ayIrZ8GA9w==} dependencies: tinyspy: 2.2.1 dev: true @@ -13062,6 +12894,15 @@ packages: vitest: 1.6.0(@types/node@20.14.9)(@vitest/ui@1.6.0) dev: true + /@vitest/utils@1.5.0: + resolution: {integrity: sha512-BDU0GNL8MWkRkSRdNFvCUCAVOeHaUlVJ9Tx0TYBZyXaaOTmGtUFObzchCivIBrIwKzvZA7A9sCejVhXM2aY98A==} + dependencies: + diff-sequences: 29.6.3 + estree-walker: 3.0.3 + loupe: 2.3.7 + pretty-format: 29.7.0 + dev: true + /@vitest/utils@1.5.3: resolution: {integrity: sha512-rE9DTN1BRhzkzqNQO+kw8ZgfeEBCLXiHJwetk668shmNBpSagQxneT5eSqEBLP+cqSiAeecvQmbpFfdMyLcIQA==} dependencies: @@ -13080,78 +12921,78 @@ packages: pretty-format: 29.7.0 dev: true - /@vue/compiler-core@3.5.10: - resolution: {integrity: sha512-iXWlk+Cg/ag7gLvY0SfVucU8Kh2CjysYZjhhP70w9qI4MvSox4frrP+vDGvtQuzIcgD8+sxM6lZvCtdxGunTAA==} + /@vue/compiler-core@3.5.7: + resolution: {integrity: sha512-A0gay3lK71MddsSnGlBxRPOugIVdACze9L/rCo5X5srCyjQfZOfYtSFMJc3aOZCM+xN55EQpb4R97rYn/iEbSw==} dependencies: - '@babel/parser': 7.25.7 - '@vue/shared': 3.5.10 + '@babel/parser': 7.25.6 + '@vue/shared': 3.5.7 entities: 4.5.0 estree-walker: 2.0.2 source-map-js: 1.2.1 dev: false - /@vue/compiler-dom@3.5.10: - resolution: {integrity: sha512-DyxHC6qPcktwYGKOIy3XqnHRrrXyWR2u91AjP+nLkADko380srsC2DC3s7Y1Rk6YfOlxOlvEQKa9XXmLI+W4ZA==} + /@vue/compiler-dom@3.5.7: + resolution: {integrity: sha512-GYWl3+gO8/g0ZdYaJ18fYHdI/WVic2VuuUd1NsPp60DWXKy+XjdhFsDW7FbUto8siYYZcosBGn9yVBkjhq1M8Q==} dependencies: - '@vue/compiler-core': 3.5.10 - '@vue/shared': 3.5.10 + '@vue/compiler-core': 3.5.7 + '@vue/shared': 3.5.7 dev: false - /@vue/compiler-sfc@3.5.10: - resolution: {integrity: sha512-to8E1BgpakV7224ZCm8gz1ZRSyjNCAWEplwFMWKlzCdP9DkMKhRRwt0WkCjY7jkzi/Vz3xgbpeig5Pnbly4Tow==} + /@vue/compiler-sfc@3.5.7: + resolution: {integrity: sha512-EjOJtCWJrC7HqoCEzOwpIYHm+JH7YmkxC1hG6VkqIukYRqj8KFUlTLK6hcT4nGgtVov2+ZfrdrRlcaqS78HnBA==} dependencies: - '@babel/parser': 7.25.7 - '@vue/compiler-core': 3.5.10 - '@vue/compiler-dom': 3.5.10 - '@vue/compiler-ssr': 3.5.10 - '@vue/shared': 3.5.10 + '@babel/parser': 7.25.6 + '@vue/compiler-core': 3.5.7 + '@vue/compiler-dom': 3.5.7 + '@vue/compiler-ssr': 3.5.7 + '@vue/shared': 3.5.7 estree-walker: 2.0.2 magic-string: 0.30.11 postcss: 8.4.47 source-map-js: 1.2.1 dev: false - /@vue/compiler-ssr@3.5.10: - resolution: {integrity: sha512-hxP4Y3KImqdtyUKXDRSxKSRkSm1H9fCvhojEYrnaoWhE4w/y8vwWhnosJoPPe2AXm5sU7CSbYYAgkt2ZPhDz+A==} + /@vue/compiler-ssr@3.5.7: + resolution: {integrity: sha512-oZx+jXP2k5arV/8Ly3TpQbfFyimMw2ANrRqvHJoKjPqtEzazxQGZjCLOfq8TnZ3wy2TOXdqfmVp4q7FyYeHV4g==} dependencies: - '@vue/compiler-dom': 3.5.10 - '@vue/shared': 3.5.10 + '@vue/compiler-dom': 3.5.7 + '@vue/shared': 3.5.7 dev: false - /@vue/reactivity@3.5.10: - resolution: {integrity: sha512-kW08v06F6xPSHhid9DJ9YjOGmwNDOsJJQk0ax21wKaUYzzuJGEuoKNU2Ujux8FLMrP7CFJJKsHhXN9l2WOVi2g==} + /@vue/reactivity@3.5.7: + resolution: {integrity: sha512-yF0EpokpOHRNXyn/h6abXc9JFIzfdAf0MJHIi92xxCWS0mqrXH6+2aZ+A6EbSrspGzX5MHTd5N8iBA28HnXu9g==} dependencies: - '@vue/shared': 3.5.10 + '@vue/shared': 3.5.7 dev: false - /@vue/runtime-core@3.5.10: - resolution: {integrity: sha512-9Q86I5Qq3swSkFfzrZ+iqEy7Vla325M7S7xc1NwKnRm/qoi1Dauz0rT6mTMmscqx4qz0EDJ1wjB+A36k7rl8mA==} + /@vue/runtime-core@3.5.7: + resolution: {integrity: sha512-OzLpBpKbZEaZVSNfd+hQbfBrDKux+b7Yl5hYhhWWWhHD7fEpF+CdI3Brm5k5GsufHEfvMcjruPxwQZuBN6nFYQ==} dependencies: - '@vue/reactivity': 3.5.10 - '@vue/shared': 3.5.10 + '@vue/reactivity': 3.5.7 + '@vue/shared': 3.5.7 dev: false - /@vue/runtime-dom@3.5.10: - resolution: {integrity: sha512-t3x7ht5qF8ZRi1H4fZqFzyY2j+GTMTDxRheT+i8M9Ph0oepUxoadmbwlFwMoW7RYCpNQLpP2Yx3feKs+fyBdpA==} + /@vue/runtime-dom@3.5.7: + resolution: {integrity: sha512-fL7cETfE27U2jyTgqzE382IGFY6a6uyznErn27KbbEzNctzxxUWYDbaN3B55l9nXh0xW2LRWPuWKOvjtO2UewQ==} dependencies: - '@vue/reactivity': 3.5.10 - '@vue/runtime-core': 3.5.10 - '@vue/shared': 3.5.10 + '@vue/reactivity': 3.5.7 + '@vue/runtime-core': 3.5.7 + '@vue/shared': 3.5.7 csstype: 3.1.3 dev: false - /@vue/server-renderer@3.5.10(vue@3.5.10): - resolution: {integrity: sha512-IVE97tt2kGKwHNq9yVO0xdh1IvYfZCShvDSy46JIh5OQxP1/EXSpoDqetVmyIzL7CYOWnnmMkVqd7YK2QSWkdw==} + /@vue/server-renderer@3.5.7(vue@3.5.7): + resolution: {integrity: sha512-peRypij815eIDjpPpPXvYQGYqPH6QXwLJGWraJYPPn8JqWGl29A8QXnS7/Mh3TkMiOcdsJNhbFCoW2Agc2NgAQ==} peerDependencies: - vue: 3.5.10 + vue: 3.5.7 dependencies: - '@vue/compiler-ssr': 3.5.10 - '@vue/shared': 3.5.10 - vue: 3.5.10(typescript@5.5.3) + '@vue/compiler-ssr': 3.5.7 + '@vue/shared': 3.5.7 + vue: 3.5.7(typescript@5.5.3) dev: false - /@vue/shared@3.5.10: - resolution: {integrity: sha512-VkkBhU97Ki+XJ0xvl4C9YJsIZ2uIlQ7HqPpZOS3m9VCvmROPaChZU6DexdMJqvz9tbgG+4EtFVrSuailUq5KGQ==} + /@vue/shared@3.5.7: + resolution: {integrity: sha512-NBE1PBIvzIedxIc2RZiKXvGbJkrZ2/hLf3h8GlS4/sP9xcXEZMFWOazFkNd6aGeUCMaproe5MHVYB3/4AW9q9g==} dev: false /@webassemblyjs/ast@1.12.1: @@ -13373,7 +13214,7 @@ packages: indent-string: 5.0.0 dev: true - /ai@3.0.23(react@18.3.1)(solid-js@1.9.1)(svelte@4.2.19)(vue@3.5.10)(zod@3.23.8): + /ai@3.0.23(react@18.3.1)(solid-js@1.8.22)(svelte@4.2.19)(vue@3.5.7)(zod@3.23.8): resolution: {integrity: sha512-VL8Fx9euEtffzIu0BpLDZkACB+oU6zj4vHXSsSoT5VfwAzE009FJedOMPK1M4u60RpYw/DgwlD7OLN7XQfvSHw==} engines: {node: '>=18'} peerDependencies: @@ -13402,19 +13243,19 @@ packages: nanoid: 3.3.6 react: 18.3.1 secure-json-parse: 2.7.0 - solid-js: 1.9.1 - solid-swr-store: 0.10.7(solid-js@1.9.1)(swr-store@0.10.6) + solid-js: 1.8.22 + solid-swr-store: 0.10.7(solid-js@1.8.22)(swr-store@0.10.6) sswr: 2.0.0(svelte@4.2.19) svelte: 4.2.19 swr: 2.2.0(react@18.3.1) swr-store: 0.10.6 - swrv: 1.0.4(vue@3.5.10) - vue: 3.5.10(typescript@5.5.3) + swrv: 1.0.4(vue@3.5.7) + vue: 3.5.7(typescript@5.5.3) zod: 3.23.8 zod-to-json-schema: 3.22.5(zod@3.23.8) dev: false - /ai@3.4.7(react@18.3.1)(svelte@4.2.19)(vue@3.5.10)(zod@3.23.8): + /ai@3.4.7(react@18.3.1)(svelte@4.2.19)(vue@3.5.7)(zod@3.23.8): resolution: {integrity: sha512-SutkVjFE86+xNql7fJERJkSEwpILEuiQvCoogJef6ZX/PGHvu3yepwHwVwedgABXe9SudOIKN48EQESrXX/xCw==} engines: {node: '>=18'} peerDependencies: @@ -13441,7 +13282,7 @@ packages: '@ai-sdk/solid': 0.0.49(zod@3.23.8) '@ai-sdk/svelte': 0.0.51(svelte@4.2.19)(zod@3.23.8) '@ai-sdk/ui-utils': 0.0.46(zod@3.23.8) - '@ai-sdk/vue': 0.0.53(vue@3.5.10)(zod@3.23.8) + '@ai-sdk/vue': 0.0.53(vue@3.5.7)(zod@3.23.8) '@opentelemetry/api': 1.4.1 eventsource-parser: 1.1.2 json-schema: 0.4.0 @@ -13500,7 +13341,7 @@ packages: resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.0.2 + fast-uri: 3.0.1 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 dev: true @@ -13622,8 +13463,8 @@ packages: dependencies: tslib: 2.7.0 - /aria-query@5.3.2: - resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + /aria-query@5.3.1: + resolution: {integrity: sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==} engines: {node: '>= 0.4'} dev: false @@ -13766,7 +13607,7 @@ packages: postcss: ^8.1.0 dependencies: browserslist: 4.23.3 - caniuse-lite: 1.0.30001663 + caniuse-lite: 1.0.30001662 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.1.0 @@ -13782,7 +13623,7 @@ packages: postcss: ^8.1.0 dependencies: browserslist: 4.23.3 - caniuse-lite: 1.0.30001663 + caniuse-lite: 1.0.30001662 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.1.0 @@ -13966,8 +13807,8 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001663 - electron-to-chromium: 1.5.27 + caniuse-lite: 1.0.30001662 + electron-to-chromium: 1.5.26 node-releases: 2.0.18 update-browserslist-db: 1.1.0(browserslist@4.23.3) @@ -14098,8 +13939,8 @@ packages: resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==} dev: false - /caniuse-lite@1.0.30001663: - resolution: {integrity: sha512-o9C3X27GLKbLeTYZ6HBOLU1tsAcBZsLis28wrVzddShCS16RujjHp9GDHKZqrB3meE0YjhawvMFsGb/igqiPzA==} + /caniuse-lite@1.0.30001662: + resolution: {integrity: sha512-sgMUVwLmGseH8ZIrm1d51UbrhqMCH3jvS7gF/M6byuHOnKyLOBL7W8yz5V02OHwgLGA36o/AFhWzzh4uc5aqTA==} /capnp-ts@0.7.0: resolution: {integrity: sha512-XKxXAC3HVPv7r674zP0VC3RTXz+/JKhfyw94ljvF80yynK6VkTnqE3jMuN8b3dUVmmc43TjyxjW4KTsmB3c86g==} @@ -16038,8 +15879,8 @@ packages: jake: 10.9.2 dev: true - /electron-to-chromium@1.5.27: - resolution: {integrity: sha512-o37j1vZqCoEgBuWWXLHQgTN/KDKe7zwpiY5CPeq2RvUqOyJw9xnrULzZAEVQ5p4h+zjMk7hgtOoPdnLxr7m/jw==} + /electron-to-chromium@1.5.26: + resolution: {integrity: sha512-Z+OMe9M/V6Ep9n/52+b7lkvYEps26z4Yz3vjWL1V61W0q+VLF1pOHhMY17sa4roz4AWmULSI8E6SAojZA5L0YQ==} /emoji-regex@10.4.0: resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==} @@ -16102,27 +15943,6 @@ packages: - bufferutil - supports-color - utf-8-validate - dev: false - - /engine.io@6.6.1: - resolution: {integrity: sha512-NEpDCw9hrvBW+hVEOK4T7v0jFJ++KgtPl4jKFwsZVfG1XhS0dCrSb3VMb9gPAd7VAdW52VT1EnaNiU2vM8C0og==} - engines: {node: '>=10.2.0'} - dependencies: - '@types/cookie': 0.4.1 - '@types/cors': 2.8.17 - '@types/node': 20.14.9 - accepts: 1.3.8 - base64id: 2.0.0 - cookie: 0.4.2 - cors: 2.8.5 - debug: 4.3.7(supports-color@8.1.1) - engine.io-parser: 5.2.3 - ws: 8.17.1 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - dev: true /enhanced-resolve@5.17.1: resolution: {integrity: sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==} @@ -16524,31 +16344,31 @@ packages: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} - /eslint-config-prettier@9.0.0(eslint@9.11.1): + /eslint-config-prettier@9.0.0(eslint@9.11.0): resolution: {integrity: sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==} hasBin: true peerDependencies: eslint: '>=7.0.0' dependencies: - eslint: 9.11.1 + eslint: 9.11.0 dev: false - /eslint-config-turbo@1.10.12(eslint@9.11.1): + /eslint-config-turbo@1.10.12(eslint@9.11.0): resolution: {integrity: sha512-z3jfh+D7UGYlzMWGh+Kqz++hf8LOE96q3o5R8X4HTjmxaBWlLAWG+0Ounr38h+JLR2TJno0hU9zfzoPNkR9BdA==} peerDependencies: eslint: '>6.6.0' dependencies: - eslint: 9.11.1 - eslint-plugin-turbo: 1.10.12(eslint@9.11.1) + eslint: 9.11.0 + eslint-plugin-turbo: 1.10.12(eslint@9.11.0) dev: false - /eslint-plugin-turbo@1.10.12(eslint@9.11.1): + /eslint-plugin-turbo@1.10.12(eslint@9.11.0): resolution: {integrity: sha512-uNbdj+ohZaYo4tFJ6dStRXu2FZigwulR1b3URPXe0Q8YaE7thuekKNP+54CHtZPH9Zey9dmDx5btAQl9mfzGOw==} peerDependencies: eslint: '>6.6.0' dependencies: dotenv: 16.0.3 - eslint: 9.11.1 + eslint: 9.11.0 dev: false /eslint-scope@5.1.1: @@ -16559,8 +16379,8 @@ packages: estraverse: 4.3.0 dev: false - /eslint-scope@8.1.0: - resolution: {integrity: sha512-14dSvlhaVhKKsa9Fx1l8A17s7ah7Ef7wCakJ10LYk6+GYmP9yDti2oq2SEwcyndt6knfcZyhyxwY3i9yL78EQw==} + /eslint-scope@8.0.2: + resolution: {integrity: sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: esrecurse: 4.3.0 @@ -16571,13 +16391,13 @@ packages: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - /eslint-visitor-keys@4.1.0: - resolution: {integrity: sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==} + /eslint-visitor-keys@4.0.0: + resolution: {integrity: sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dev: false - /eslint@9.11.1: - resolution: {integrity: sha512-MobhYKIoAO1s1e4VUrgx1l1Sk2JBR/Gqjjgw8+mfgoLE2xwsHur4gdfTxyTgShrhvdVFTaJSgMiQBl1jv/AWxg==} + /eslint@9.11.0: + resolution: {integrity: sha512-yVS6XODx+tMFMDFcG4+Hlh+qG7RM6cCJXtQhCKLSsr3XkLvWggHjCqjfh0XsPPnt1c56oaT6PMgW9XWQQjdHXA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: @@ -16586,26 +16406,23 @@ packages: jiti: optional: true dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.11.1) + '@eslint-community/eslint-utils': 4.4.0(eslint@9.11.0) '@eslint-community/regexpp': 4.11.1 '@eslint/config-array': 0.18.0 - '@eslint/core': 0.6.0 '@eslint/eslintrc': 3.1.0 - '@eslint/js': 9.11.1 + '@eslint/js': 9.11.0 '@eslint/plugin-kit': 0.2.0 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.3.0 '@nodelib/fs.walk': 1.2.8 - '@types/estree': 1.0.6 - '@types/json-schema': 7.0.15 ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 debug: 4.3.7(supports-color@8.1.1) escape-string-regexp: 4.0.0 - eslint-scope: 8.1.0 - eslint-visitor-keys: 4.1.0 - espree: 10.2.0 + eslint-scope: 8.0.2 + eslint-visitor-keys: 4.0.0 + espree: 10.1.0 esquery: 1.6.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 @@ -16627,13 +16444,13 @@ packages: - supports-color dev: false - /espree@10.2.0: - resolution: {integrity: sha512-upbkBJbckcCNBDBDXEbuhjbP68n+scUd3k/U2EkyM9nw+I/jPiL4cLF/Al06CF96wRltFda16sxDFrxsI1v0/g==} + /espree@10.1.0: + resolution: {integrity: sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: acorn: 8.12.1 acorn-jsx: 5.3.2(acorn@8.12.1) - eslint-visitor-keys: 4.1.0 + eslint-visitor-keys: 4.0.0 dev: false /esprima@4.0.1: @@ -16957,8 +16774,8 @@ packages: resolution: {integrity: sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==} dev: false - /fast-uri@3.0.2: - resolution: {integrity: sha512-GR6f0hD7XXyNJa25Tb9BuIdN0tdr+0BMi6/CJPH3wJO1JjNG3n/VsSw38AwRdKZABm8lGbPfakLRkYzx2V9row==} + /fast-uri@3.0.1: + resolution: {integrity: sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==} dev: true /fastest-levenshtein@1.0.16: @@ -17467,7 +17284,7 @@ packages: react-dom: 18.3.1(react@18.3.1) react-medium-image-zoom: 5.2.10(react-dom@18.3.1)(react@18.3.1) swr: 2.2.5(react@18.3.1) - tailwind-merge: 2.5.2 + tailwind-merge: 2.5.3 transitivePeerDependencies: - '@types/react' - '@types/react-dom' @@ -18661,12 +18478,6 @@ packages: hasBin: true dev: true - /is-docker@3.0.0: - resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - hasBin: true - dev: true - /is-electron@2.2.2: resolution: {integrity: sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==} dev: false @@ -18714,14 +18525,6 @@ packages: /is-hexadecimal@2.0.1: resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} - /is-inside-container@1.0.0: - resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} - engines: {node: '>=14.16'} - hasBin: true - dependencies: - is-docker: 3.0.0 - dev: true - /is-interactive@1.0.0: resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} engines: {node: '>=8'} @@ -18929,13 +18732,6 @@ packages: is-docker: 2.2.1 dev: true - /is-wsl@3.1.0: - resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} - engines: {node: '>=16'} - dependencies: - is-inside-container: 1.0.0 - dev: true - /isarray@2.0.5: resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} @@ -21242,7 +21038,7 @@ packages: '@next/env': 14.1.0 '@swc/helpers': 0.5.2 busboy: 1.6.0 - caniuse-lite: 1.0.30001663 + caniuse-lite: 1.0.30001662 graceful-fs: 4.2.11 postcss: 8.4.31 react: 18.3.1 @@ -21285,7 +21081,7 @@ packages: '@opentelemetry/api': 1.4.1 '@swc/helpers': 0.5.5 busboy: 1.6.0 - caniuse-lite: 1.0.30001663 + caniuse-lite: 1.0.30001662 graceful-fs: 4.2.11 postcss: 8.4.31 react: 18.3.1 @@ -21417,16 +21213,6 @@ packages: resolution: {integrity: sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==} engines: {node: '>=14.16'} - /npm-package-arg@11.0.3: - resolution: {integrity: sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==} - engines: {node: ^16.14.0 || >=18.0.0} - dependencies: - hosted-git-info: 7.0.2 - proc-log: 4.2.0 - semver: 7.6.3 - validate-npm-package-name: 5.0.1 - dev: true - /npm-run-path@4.0.1: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} @@ -22685,7 +22471,7 @@ packages: react-dom: 18.3.1(react@18.3.1) dev: true - /react-email@2.1.1(@babel/core@7.25.2)(eslint@9.11.1)(ts-node@10.9.2): + /react-email@2.1.1(@babel/core@7.25.2)(eslint@9.11.0)(ts-node@10.9.2): resolution: {integrity: sha512-09oMVl/jN0/Re0bT0sEqYjyyFSCN/Tg0YmzjC9wfYpnMx02Apk40XXitySDfUBMR9EgTdr6T4lYknACqiLK3mg==} engines: {node: '>=18.0.0'} hasBin: true @@ -22711,8 +22497,8 @@ packages: commander: 11.1.0 debounce: 2.0.0 esbuild: 0.19.11 - eslint-config-prettier: 9.0.0(eslint@9.11.1) - eslint-config-turbo: 1.10.12(eslint@9.11.1) + eslint-config-prettier: 9.0.0(eslint@9.11.0) + eslint-config-turbo: 1.10.12(eslint@9.11.0) framer-motion: 10.17.4(react-dom@18.3.1)(react@18.3.1) glob: 10.3.4 log-symbols: 4.1.0 @@ -23730,8 +23516,8 @@ packages: source-map-support: 0.3.3 dev: false - /rollup@4.22.4: - resolution: {integrity: sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==} + /rollup@4.22.2: + resolution: {integrity: sha512-JWWpTrZmqQGQWt16xvNn6KVIUz16VtZwl984TKw0dfqqRpFwtLJYYk1/4BTgplndMQKWUk/yB4uOShYmMzA2Vg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true dependencies: @@ -23756,32 +23542,6 @@ packages: fsevents: 2.3.3 dev: true - /rollup@4.24.0: - resolution: {integrity: sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - dependencies: - '@types/estree': 1.0.6 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.24.0 - '@rollup/rollup-android-arm64': 4.24.0 - '@rollup/rollup-darwin-arm64': 4.24.0 - '@rollup/rollup-darwin-x64': 4.24.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.24.0 - '@rollup/rollup-linux-arm-musleabihf': 4.24.0 - '@rollup/rollup-linux-arm64-gnu': 4.24.0 - '@rollup/rollup-linux-arm64-musl': 4.24.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.24.0 - '@rollup/rollup-linux-riscv64-gnu': 4.24.0 - '@rollup/rollup-linux-s390x-gnu': 4.24.0 - '@rollup/rollup-linux-x64-gnu': 4.24.0 - '@rollup/rollup-linux-x64-musl': 4.24.0 - '@rollup/rollup-win32-arm64-msvc': 4.24.0 - '@rollup/rollup-win32-ia32-msvc': 4.24.0 - '@rollup/rollup-win32-x64-msvc': 4.24.0 - fsevents: 2.3.3 - dev: true - /run-async@2.4.1: resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} engines: {node: '>=0.12.0'} @@ -24324,15 +24084,15 @@ packages: - utf-8-validate dev: false - /socket.io@4.8.0: - resolution: {integrity: sha512-8U6BEgGjQOfGz3HHTYaC/L1GaxDCJ/KM0XTkJly0EhZ5U/du9uNEZy4ZgYzEzIqlx2CMm25CrCqr1ck899eLNA==} + /socket.io@4.7.5: + resolution: {integrity: sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==} engines: {node: '>=10.2.0'} dependencies: accepts: 1.3.8 base64id: 2.0.0 cors: 2.8.5 debug: 4.3.7(supports-color@8.1.1) - engine.io: 6.6.1 + engine.io: 6.5.5 socket.io-adapter: 2.5.5 socket.io-parser: 4.2.4 transitivePeerDependencies: @@ -24341,22 +24101,22 @@ packages: - utf-8-validate dev: true - /solid-js@1.9.1: - resolution: {integrity: sha512-Gd6QWRFfO2XKKZqVK4YwbhWZkr0jWw1dYHOt+VYebomeyikGP0SuMflf42XcDuU9HAEYDArFJIYsBNjlE7iZsw==} + /solid-js@1.8.22: + resolution: {integrity: sha512-VBzN5j+9Y4rqIKEnK301aBk+S7fvFSTs9ljg+YEdFxjNjH0hkjXPiQRcws9tE5fUzMznSS6KToL5hwMfHDgpLA==} dependencies: csstype: 3.1.3 seroval: 1.1.1 seroval-plugins: 1.1.1(seroval@1.1.1) dev: false - /solid-swr-store@0.10.7(solid-js@1.9.1)(swr-store@0.10.6): + /solid-swr-store@0.10.7(solid-js@1.8.22)(swr-store@0.10.6): resolution: {integrity: sha512-A6d68aJmRP471aWqKKPE2tpgOiR5fH4qXQNfKIec+Vap+MGQm3tvXlT8n0I8UgJSlNAsSAUuw2VTviH2h3Vv5g==} engines: {node: '>=10'} peerDependencies: solid-js: ^1.2 swr-store: ^0.10 dependencies: - solid-js: 1.9.1 + solid-js: 1.8.22 swr-store: 0.10.6 dev: false @@ -24837,7 +24597,7 @@ packages: '@jridgewell/trace-mapping': 0.3.25 '@types/estree': 1.0.6 acorn: 8.12.1 - aria-query: 5.3.2 + aria-query: 5.3.1 axobject-query: 4.1.0 code-red: 1.0.4 css-tree: 2.3.1 @@ -24899,12 +24659,12 @@ packages: resolution: {integrity: sha512-LqVcOHSB4cPGgitD1riJ1Hh4vdmITOp+BkmfmXRh4hSF/t7EnS4iD+SOTmq7w5pPm/SiPeto4ADbKS6dHUDWFA==} dev: false - /swrv@1.0.4(vue@3.5.10): + /swrv@1.0.4(vue@3.5.7): resolution: {integrity: sha512-zjEkcP8Ywmj+xOJW3lIT65ciY/4AL4e/Or7Gj0MzU3zBJNMdJiT8geVZhINavnlHRMMCcJLHhraLTAiDOTmQ9g==} peerDependencies: vue: '>=3.2.26 < 4' dependencies: - vue: 3.5.10(typescript@5.5.3) + vue: 3.5.7(typescript@5.5.3) dev: false /tailwind-merge@2.2.0: @@ -24919,8 +24679,8 @@ packages: '@babel/runtime': 7.25.6 dev: false - /tailwind-merge@2.5.2: - resolution: {integrity: sha512-kjEBm+pvD+6eAwzJL2Bi+02/9LFLal1Gs61+QB7HvTfQQ0aXwC5LGT8PEt1gS0CWKktKe6ysPTAy3cBC5MeiIg==} + /tailwind-merge@2.5.3: + resolution: {integrity: sha512-d9ZolCAIzom1nf/5p4LdD5zvjmgSxY0BGgdSvmXIoMYAiPdAW/dSpP7joCDYFY7r/HkEa2qmPtkgsu0xjQeQtw==} dev: false /tailwind-scrollbar@3.1.0(tailwindcss@3.4.3): @@ -24947,7 +24707,7 @@ packages: dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 - chokidar: 3.5.3 + chokidar: 3.6.0 didyoumean: 1.2.2 dlv: 1.1.3 fast-glob: 3.3.2 @@ -25414,7 +25174,7 @@ packages: joycon: 3.1.1 postcss-load-config: 4.0.2(postcss@8.4.47)(ts-node@10.9.2) resolve-from: 5.0.0 - rollup: 4.22.4 + rollup: 4.22.2 source-map: 0.8.0-beta.0 sucrase: 3.35.0 tree-kill: 1.2.2 @@ -26144,8 +25904,8 @@ packages: '@types/unist': 3.0.3 vfile-message: 4.0.2 - /vite-node@1.5.3(@types/node@20.14.9): - resolution: {integrity: sha512-axFo00qiCpU/JLd8N1gu9iEYL3xTbMbMrbe5nDp9GL0nb6gurIdZLkkFogZXWnE8Oyy5kfSLwNVIcVsnhE7lgQ==} + /vite-node@1.5.0(@types/node@20.14.9): + resolution: {integrity: sha512-tV8h6gMj6vPzVCa7l+VGq9lwoJjW8Y79vst8QZZGiuRAfijU+EEWuc0kFpmndQrWhMMhet1jdSF+40KSZUqIIw==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true dependencies: @@ -26153,7 +25913,7 @@ packages: debug: 4.3.7(supports-color@8.1.1) pathe: 1.1.2 picocolors: 1.1.0 - vite: 5.4.8(@types/node@20.14.9) + vite: 5.4.7(@types/node@20.14.9) transitivePeerDependencies: - '@types/node' - less @@ -26222,59 +25982,20 @@ packages: '@types/node': 20.14.9 esbuild: 0.21.5 postcss: 8.4.47 - rollup: 4.22.4 - optionalDependencies: - fsevents: 2.3.3 - dev: true - - /vite@5.4.8(@types/node@20.14.9): - resolution: {integrity: sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - peerDependencies: - '@types/node': ^18.0.0 || >=20.0.0 - less: '*' - lightningcss: ^1.21.0 - sass: '*' - sass-embedded: '*' - stylus: '*' - sugarss: '*' - terser: ^5.4.0 - peerDependenciesMeta: - '@types/node': - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - dependencies: - '@types/node': 20.14.9 - esbuild: 0.21.5 - postcss: 8.4.47 - rollup: 4.24.0 + rollup: 4.22.2 optionalDependencies: fsevents: 2.3.3 dev: true - /vitest@1.5.3(@types/node@20.14.9)(@vitest/ui@1.6.0): - resolution: {integrity: sha512-2oM7nLXylw3mQlW6GXnRriw+7YvZFk/YNV8AxIC3Z3MfFbuziLGWP9GPxxu/7nRlXhqyxBikpamr+lEEj1sUEw==} + /vitest@1.5.0(@types/node@20.14.9)(@vitest/ui@1.6.0): + resolution: {integrity: sha512-d8UKgR0m2kjdxDWX6911uwxout6GHS0XaGH1cksSIVVG8kRlE7G7aBw7myKQCvDI5dT4j7ZMa+l706BIORMDLw==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': 1.5.3 - '@vitest/ui': 1.5.3 + '@vitest/browser': 1.5.0 + '@vitest/ui': 1.5.0 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -26292,12 +26013,12 @@ packages: optional: true dependencies: '@types/node': 20.14.9 - '@vitest/expect': 1.5.3 - '@vitest/runner': 1.5.3 - '@vitest/snapshot': 1.5.3 - '@vitest/spy': 1.5.3 + '@vitest/expect': 1.5.0 + '@vitest/runner': 1.5.0 + '@vitest/snapshot': 1.5.0 + '@vitest/spy': 1.5.0 '@vitest/ui': 1.6.0(vitest@1.6.0) - '@vitest/utils': 1.5.3 + '@vitest/utils': 1.5.0 acorn-walk: 8.3.4 chai: 4.5.0 debug: 4.3.7(supports-color@8.1.1) @@ -26310,8 +26031,8 @@ packages: strip-literal: 2.1.0 tinybench: 2.9.0 tinypool: 0.8.4 - vite: 5.4.8(@types/node@20.14.9) - vite-node: 1.5.3(@types/node@20.14.9) + vite: 5.4.7(@types/node@20.14.9) + vite-node: 1.5.0(@types/node@20.14.9) why-is-node-running: 2.3.0 transitivePeerDependencies: - less @@ -26394,19 +26115,19 @@ packages: resolution: {integrity: sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==} dev: false - /vue@3.5.10(typescript@5.5.3): - resolution: {integrity: sha512-Vy2kmJwHPlouC/tSnIgXVg03SG+9wSqT1xu1Vehc+ChsXsRd7jLkKgMltVEFOzUdBr3uFwBCG+41LJtfAcBRng==} + /vue@3.5.7(typescript@5.5.3): + resolution: {integrity: sha512-JcFm0f5j8DQO9E07pZRxqZ/ZsNopMVzHYXpKvnfqXFcA4JTi+4YcrikRn9wkzWsdj0YsLzlLIsR0zzGxA2P6Wg==} peerDependencies: typescript: '*' peerDependenciesMeta: typescript: optional: true dependencies: - '@vue/compiler-dom': 3.5.10 - '@vue/compiler-sfc': 3.5.10 - '@vue/runtime-dom': 3.5.10 - '@vue/server-renderer': 3.5.10(vue@3.5.10) - '@vue/shared': 3.5.10 + '@vue/compiler-dom': 3.5.7 + '@vue/compiler-sfc': 3.5.7 + '@vue/runtime-dom': 3.5.7 + '@vue/server-renderer': 3.5.7(vue@3.5.7) + '@vue/shared': 3.5.7 typescript: 5.5.3 dev: false From 5979d61cde062c00ac3601cc855b7b06e96cb1c8 Mon Sep 17 00:00:00 2001 From: Mike Silva Date: Tue, 8 Oct 2024 14:23:19 -0700 Subject: [PATCH 20/29] build issues --- apps/workflows/jobs/index.ts | 2 +- packages/api/src/openapi.d.ts | 120 ++++++++++++++ pnpm-lock.yaml | 284 ++++++++++++++++++++++------------ 3 files changed, 309 insertions(+), 97 deletions(-) diff --git a/apps/workflows/jobs/index.ts b/apps/workflows/jobs/index.ts index 33420897fc..0faac6979f 100644 --- a/apps/workflows/jobs/index.ts +++ b/apps/workflows/jobs/index.ts @@ -1,4 +1,4 @@ // export all your job files here export * from "./refill-daily"; -export * from "./downgrade-requests"; + diff --git a/packages/api/src/openapi.d.ts b/packages/api/src/openapi.d.ts index 2c495e9eb1..b8908d1d73 100644 --- a/packages/api/src/openapi.d.ts +++ b/packages/api/src/openapi.d.ts @@ -16,6 +16,9 @@ export interface paths { "/v1/keys.getKey": { get: operations["getKey"]; }; + "/v1/keys.whoami": { + post: operations["whoami"]; + }; "/v1/keys.deleteKey": { post: operations["deleteKey"]; }; @@ -777,6 +780,123 @@ export interface operations { }; }; }; + whoami: { + requestBody: { + content: { + "application/json": { + /** + * @description The actual key to fetch + * @example sk_123 + */ + key: string; + }; + }; + }; + responses: { + /** @description The configuration for a single key */ + 200: { + content: { + "application/json": { + /** + * @description The ID of the key + * @example key_123 + */ + id: string; + /** + * @description The name of the key + * @example API Key 1 + */ + name?: string; + /** + * @description The remaining number of requests for the key + * @example 1000 + */ + remaining?: number; + /** @description The identity object associated with the key */ + identity?: { + /** + * @description The identity ID associated with the key + * @example id_123 + */ + id: string; + /** + * @description The external identity ID associated with the key + * @example ext123 + */ + externalId: string; + }; + /** + * @description Metadata associated with the key + * @example { + * "role": "admin", + * "plan": "premium" + * } + */ + meta?: { + [key: string]: unknown; + }; + /** + * @description The timestamp in milliseconds when the key was created + * @example 1620000000000 + */ + createdAt: number; + /** + * @description Whether the key is enabled + * @example true + */ + enabled: boolean; + /** + * @description The environment the key is associated with + * @example production + */ + environment?: string; + }; + }; + }; + /** @description The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). */ + 400: { + content: { + "application/json": components["schemas"]["ErrBadRequest"]; + }; + }; + /** @description Although the HTTP standard specifies "unauthorized", semantically this response means "unauthenticated". That is, the client must authenticate itself to get the requested response. */ + 401: { + content: { + "application/json": components["schemas"]["ErrUnauthorized"]; + }; + }; + /** @description The client does not have access rights to the content; that is, it is unauthorized, so the server is refusing to give the requested resource. Unlike 401 Unauthorized, the client's identity is known to the server. */ + 403: { + content: { + "application/json": components["schemas"]["ErrForbidden"]; + }; + }; + /** @description The server cannot find the requested resource. In the browser, this means the URL is not recognized. In an API, this can also mean that the endpoint is valid but the resource itself does not exist. Servers may also send this response instead of 403 Forbidden to hide the existence of a resource from an unauthorized client. This response code is probably the most well known due to its frequent occurrence on the web. */ + 404: { + content: { + "application/json": components["schemas"]["ErrNotFound"]; + }; + }; + /** @description This response is sent when a request conflicts with the current state of the server. */ + 409: { + content: { + "application/json": components["schemas"]["ErrConflict"]; + }; + }; + /** @description The user has sent too many requests in a given amount of time ("rate limiting") */ + 429: { + content: { + "application/json": components["schemas"]["ErrTooManyRequests"]; + }; + }; + /** @description The server has encountered a situation it does not know how to handle. */ + 500: { + content: { + "application/json": components["schemas"]["ErrInternalServerError"]; + }; + }; + }; + }; deleteKey: { requestBody: { content: { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 78946c8308..e3e017a138 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -75,7 +75,7 @@ importers: version: 0.2.1(hono@4.6.3)(zod@3.23.8) '@planetscale/database': specifier: ^1.16.0 - version: 1.18.0 + version: 1.16.0 '@unkey/cache': specifier: workspace:^ version: link:../../packages/cache @@ -169,7 +169,7 @@ importers: version: 1.5.2(ws@8.18.0) '@planetscale/database': specifier: ^1.16.0 - version: 1.18.0 + version: 1.16.0 '@trigger.dev/nextjs': specifier: 3.0.9 version: 3.0.9(@trigger.dev/sdk@3.0.9)(next@14.2.10) @@ -202,7 +202,7 @@ importers: version: 3.4.7(openai@4.52.1)(react@18.3.1)(svelte@4.2.19)(vue@3.5.11)(zod@3.23.8) drizzle-orm: specifier: ^0.33.0 - version: 0.33.0(@opentelemetry/api@1.4.1)(@planetscale/database@1.18.0)(@types/react@18.3.11)(react@18.3.1) + version: 0.33.0(@opentelemetry/api@1.4.1)(@planetscale/database@1.16.0)(@types/react@18.3.11)(react@18.3.1) drizzle-zod: specifier: ^0.5.1 version: 0.5.1(drizzle-orm@0.33.0)(zod@3.23.8) @@ -254,7 +254,7 @@ importers: version: 3.3.4(react-hook-form@7.51.3) '@planetscale/database': specifier: ^1.16.0 - version: 1.18.0 + version: 1.16.0 '@radix-ui/react-accordion': specifier: ^1.2.0 version: 1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) @@ -413,7 +413,7 @@ importers: version: 3.3.0(react-dom@18.3.1)(react@18.3.1) autoprefixer: specifier: ^10.4.19 - version: 10.4.20(postcss@8.4.38) + version: 10.4.19(postcss@8.4.38) class-variance-authority: specifier: ^0.7.0 version: 0.7.0 @@ -434,7 +434,7 @@ importers: version: 3.6.0 drizzle-orm: specifier: generated - version: 0.32.0-aaf764c(@planetscale/database@1.18.0)(@types/react@18.3.11)(react@18.3.1) + version: 0.32.0-aaf764c(@planetscale/database@1.16.0)(@types/react@18.3.11)(react@18.3.1) export-to-csv: specifier: ^1.4.0 version: 1.4.0 @@ -533,7 +533,7 @@ importers: version: 1.21.0 tailwind-merge: specifier: ^2.2.2 - version: 2.3.0 + version: 2.2.2 tailwindcss: specifier: ^3.4.3 version: 3.4.10(ts-node@10.9.2) @@ -697,7 +697,7 @@ importers: version: 18.3.0 autoprefixer: specifier: ^10.4.19 - version: 10.4.20(postcss@8.4.38) + version: 10.4.19(postcss@8.4.38) postcss: specifier: ^8 version: 8.4.38 @@ -909,7 +909,7 @@ importers: version: 4.29.10(next@14.2.10)(react-dom@18.3.1)(react@18.3.1) '@planetscale/database': specifier: ^1.16.0 - version: 1.18.0 + version: 1.16.0 '@trigger.dev/nextjs': specifier: ^2.3.18 version: 2.3.18(@trigger.dev/sdk@2.3.18)(next@14.2.10) @@ -936,7 +936,7 @@ importers: version: link:../../internal/schema drizzle-orm: specifier: generated - version: 0.32.0-aaf764c(@planetscale/database@1.18.0)(@types/react@18.3.11)(react@18.3.1) + version: 0.32.0-aaf764c(@planetscale/database@1.16.0)(@types/react@18.3.11)(react@18.3.1) next: specifier: 14.2.10 version: 14.2.10(@babel/core@7.25.7)(@opentelemetry/api@1.4.1)(react-dom@18.3.1)(react@18.3.1) @@ -1099,7 +1099,7 @@ importers: version: 1.12.0 tailwind-merge: specifier: ^2.2.2 - version: 2.3.0 + version: 2.2.2 tailwindcss-animate: specifier: ^1.0.7 version: 1.0.7(tailwindcss@3.4.10) @@ -4875,8 +4875,8 @@ packages: graphql: 16.9.0 dev: false - /@grpc/grpc-js@1.12.0: - resolution: {integrity: sha512-eWdP97A6xKtZXVP/ze9y8zYRB2t6ugQAuLXFuZXAsyqmyltaAjl4yPkmIfc0wuTFJMOUF1AdvIFQCL7fMtaX6g==} + /@grpc/grpc-js@1.12.1: + resolution: {integrity: sha512-VYrLinJNeW3idIZHTy7BsVL7bsPF9+l89WDYTDV8o1QYJzkf3BSoPXdfaz4c8DyeY12LGSaS8m8BgbT8W66h6Q==} engines: {node: '>=12.10.0'} dependencies: '@grpc/proto-loader': 0.7.13 @@ -5132,7 +5132,7 @@ packages: '@inquirer/figures': 1.0.7 '@inquirer/type': 2.0.0 '@types/mute-stream': 0.0.4 - '@types/node': 22.7.4 + '@types/node': 22.7.5 '@types/wrap-ansi': 3.0.0 ansi-escapes: 4.3.2 cli-width: 4.1.0 @@ -5653,7 +5653,7 @@ packages: '@octokit/rest': 19.0.13 chalk: 5.3.0 chokidar: 3.6.0 - express: 4.21.0 + express: 4.21.1 fs-extra: 11.2.0 got: 13.0.0 gray-matter: 4.0.3 @@ -6034,35 +6034,21 @@ packages: resolution: {integrity: sha512-9j92jHr6k2tjQ6/mIwNi46Gqw+qbPFQ02mxT5T8/nxO2fgsPL3qL0kb9SR1il5AVfqpgLIG3uLUcw87rgaioUg==} engines: {node: '>=18.0.0'} dependencies: - '@types/cli-progress': 3.11.6 ansi-escapes: 4.3.2 - ansi-styles: 4.3.0 ansis: 3.3.2 - cardinal: 2.1.1 - chalk: 4.1.2 clean-stack: 3.0.1 - cli-progress: 3.12.0 cli-spinners: 2.9.2 - color: 4.2.3 debug: 4.3.7(supports-color@8.1.1) ejs: 3.1.10 get-package-type: 0.1.0 globby: 11.1.0 - hyperlinker: 1.0.0 indent-string: 4.0.0 is-wsl: 3.1.0 - js-yaml: 3.14.1 lilconfig: 3.1.2 minimatch: 9.0.5 - natural-orderby: 2.0.3 - object-treeify: 1.1.33 - password-prompt: 1.1.3 semver: 7.6.3 - slice-ansi: 4.0.0 string-width: 4.2.3 - strip-ansi: 6.0.1 supports-color: 8.1.1 - supports-hyperlinks: 2.3.0 widest-line: 3.1.0 wordwrap: 1.0.0 wrap-ansi: 7.0.0 @@ -6517,7 +6503,7 @@ packages: peerDependencies: '@opentelemetry/api': ^1.0.0 dependencies: - '@grpc/grpc-js': 1.12.0 + '@grpc/grpc-js': 1.12.1 '@opentelemetry/api': 1.4.1 '@opentelemetry/core': 1.13.0(@opentelemetry/api@1.4.1) '@opentelemetry/otlp-grpc-exporter-base': 0.39.1(@opentelemetry/api@1.4.1) @@ -6611,7 +6597,7 @@ packages: peerDependencies: '@opentelemetry/api': ^1.0.0 dependencies: - '@grpc/grpc-js': 1.12.0 + '@grpc/grpc-js': 1.12.1 '@opentelemetry/api': 1.4.1 '@opentelemetry/core': 1.13.0(@opentelemetry/api@1.4.1) '@opentelemetry/otlp-exporter-base': 0.39.1(@opentelemetry/api@1.4.1) @@ -6928,7 +6914,7 @@ packages: resolution: {integrity: sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==} engines: {node: '>=8.0.0'} dependencies: - tslib: 2.7.0 + tslib: 2.4.1 dev: false /@peculiar/webcrypto@1.4.1: @@ -6938,8 +6924,8 @@ packages: '@peculiar/asn1-schema': 2.3.13 '@peculiar/json-schema': 1.1.12 pvtsutils: 1.3.5 - tslib: 2.7.0 - webcrypto-core: 1.8.0 + tslib: 2.4.1 + webcrypto-core: 1.8.1 dev: false /@pkgjs/parseargs@0.11.0: @@ -6948,6 +6934,11 @@ packages: requiresBuild: true optional: true + /@planetscale/database@1.16.0: + resolution: {integrity: sha512-HNUrTqrd8aTRZYMDcsoZ62s36sIWkMMmKZBOehoCWR2WrfNPKq+Q1yQef5okl3pSVlldFnu2h/dbHjOsDTHXug==} + engines: {node: '>=16'} + dev: false + /@planetscale/database@1.18.0: resolution: {integrity: sha512-t2XdOfrVgcF7AW791FtdPS27NyNqcE1SpoXgk3HpziousvUMsJi4Q6NL3JyOBpsMOrvk94749o8yyonvX5quPw==} engines: {node: '>=16'} @@ -9387,7 +9378,7 @@ packages: dependencies: '@types/d3': 7.4.3 '@types/d3-drag': 3.0.7 - '@types/d3-selection': 3.0.10 + '@types/d3-selection': 3.0.11 '@types/d3-zoom': 3.0.8 classcat: 5.0.5 d3-drag: 3.0.0 @@ -9408,7 +9399,7 @@ packages: react-dom: '>=17' dependencies: '@reactflow/core': 11.11.1(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) - '@types/d3-selection': 3.0.10 + '@types/d3-selection': 3.0.11 '@types/d3-zoom': 3.0.8 classcat: 5.0.5 d3-selection: 3.0.0 @@ -10256,13 +10247,13 @@ packages: /@types/d3-axis@3.0.6: resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==} dependencies: - '@types/d3-selection': 3.0.10 + '@types/d3-selection': 3.0.11 dev: false /@types/d3-brush@3.0.6: resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==} dependencies: - '@types/d3-selection': 3.0.10 + '@types/d3-selection': 3.0.11 dev: false /@types/d3-chord@3.0.6: @@ -10299,7 +10290,7 @@ packages: /@types/d3-drag@3.0.7: resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==} dependencies: - '@types/d3-selection': 3.0.10 + '@types/d3-selection': 3.0.11 dev: false /@types/d3-dsv@3.0.7: @@ -10386,8 +10377,8 @@ packages: '@types/d3-time': 3.0.3 dev: false - /@types/d3-selection@3.0.10: - resolution: {integrity: sha512-cuHoUgS/V3hLdjJOLTT691+G2QoqAjCVLmr4kJXR4ha56w1Zdu8UUQ5TxLRqudgNjwXeQxKMq4j+lyf9sWuslg==} + /@types/d3-selection@3.0.11: + resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==} dev: false /@types/d3-shape@1.3.12: @@ -10426,17 +10417,17 @@ packages: resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} dev: false - /@types/d3-transition@3.0.8: - resolution: {integrity: sha512-ew63aJfQ/ms7QQ4X7pk5NxQ9fZH/z+i24ZfJ6tJSfqxJMrYLiK01EAs2/Rtw/JreGUsS3pLPNV644qXFGnoZNQ==} + /@types/d3-transition@3.0.9: + resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==} dependencies: - '@types/d3-selection': 3.0.10 + '@types/d3-selection': 3.0.11 dev: false /@types/d3-zoom@3.0.8: resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==} dependencies: '@types/d3-interpolate': 3.0.4 - '@types/d3-selection': 3.0.10 + '@types/d3-selection': 3.0.11 dev: false /@types/d3@7.4.3: @@ -10465,12 +10456,12 @@ packages: '@types/d3-random': 3.0.3 '@types/d3-scale': 4.0.8 '@types/d3-scale-chromatic': 3.0.3 - '@types/d3-selection': 3.0.10 + '@types/d3-selection': 3.0.11 '@types/d3-shape': 3.1.6 '@types/d3-time': 3.0.3 '@types/d3-time-format': 4.0.3 '@types/d3-timer': 3.0.2 - '@types/d3-transition': 3.0.8 + '@types/d3-transition': 3.0.9 '@types/d3-zoom': 3.0.8 dev: false @@ -10634,8 +10625,8 @@ packages: resolution: {integrity: sha512-vmYJF0REqDyyU0gviezF/KHq/fYaUbFhkcNbQCuPGFQj6VTbXuHZoxs/Y7mutWe73C8AC6l9fFu8mSYiBAqkGA==} dev: false - /@types/node@18.19.54: - resolution: {integrity: sha512-+BRgt0G5gYjTvdLac9sIeE0iZcJxi4Jc4PV5EUzqi+88jmQLr+fRZdv2tCTV7IHKSGxM6SaLoOXQWWUiLUItMw==} + /@types/node@18.19.55: + resolution: {integrity: sha512-zzw5Vw52205Zr/nmErSEkN5FLqXPuKX/k5d1D7RKHATGqU7y6YfX9QxZraUzUrFGqH6XzOzG196BC35ltJC4Cw==} dependencies: undici-types: 5.26.5 dev: false @@ -10651,8 +10642,8 @@ packages: undici-types: 6.19.8 dev: true - /@types/node@22.7.4: - resolution: {integrity: sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==} + /@types/node@22.7.5: + resolution: {integrity: sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==} dependencies: undici-types: 6.19.8 dev: true @@ -11489,7 +11480,7 @@ packages: resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.0.1 + fast-uri: 3.0.2 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 dev: true @@ -11616,8 +11607,8 @@ packages: dependencies: tslib: 2.7.0 - /aria-query@5.3.1: - resolution: {integrity: sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==} + /aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} engines: {node: '>= 0.4'} dev: false @@ -11716,11 +11707,6 @@ packages: tslib: 2.7.0 dev: true - /astral-regex@2.0.0: - resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} - engines: {node: '>=8'} - dev: true - /astring@1.9.0: resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} hasBin: true @@ -11768,6 +11754,21 @@ packages: postcss-value-parser: 4.2.0 dev: false + /autoprefixer@10.4.19(postcss@8.4.38): + resolution: {integrity: sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + dependencies: + browserslist: 4.24.0 + caniuse-lite: 1.0.30001667 + fraction.js: 4.3.7 + normalize-range: 0.1.2 + picocolors: 1.1.0 + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + /autoprefixer@10.4.20(postcss@8.4.38): resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==} engines: {node: ^10 || ^12 || >=14} @@ -11782,6 +11783,7 @@ packages: picocolors: 1.1.0 postcss: 8.4.38 postcss-value-parser: 4.2.0 + dev: true /autoprefixer@10.4.20(postcss@8.4.45): resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==} @@ -11961,7 +11963,7 @@ packages: hasBin: true dependencies: caniuse-lite: 1.0.30001667 - electron-to-chromium: 1.5.32 + electron-to-chromium: 1.5.33 node-releases: 2.0.18 update-browserslist-db: 1.1.1(browserslist@4.24.0) @@ -12721,8 +12723,8 @@ packages: resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} engines: {node: '>= 0.6'} - /cookie@0.6.0: - resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} + /cookie@0.7.1: + resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} engines: {node: '>= 0.6'} dev: true @@ -13511,7 +13513,7 @@ packages: resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} dependencies: no-case: 3.0.4 - tslib: 2.7.0 + tslib: 2.4.1 dev: false /dot-prop@6.0.1: @@ -13763,6 +13765,100 @@ packages: react: 18.3.1 dev: false + /drizzle-orm@0.32.0-aaf764c(@planetscale/database@1.16.0)(@types/react@18.3.11)(react@18.3.1): + resolution: {integrity: sha512-TpG2xZhiGUyxWMJTXpVwuuFsh0VrNxPzYOZfsDaFbZS0lBbAN1xC2TBUK4pv1DMGxygq+clvqNMd908RznJZRA==} + peerDependencies: + '@aws-sdk/client-rds-data': '>=3' + '@cloudflare/workers-types': '>=3' + '@electric-sql/pglite': '>=0.1.1' + '@libsql/client': '*' + '@neondatabase/serverless': '>=0.1' + '@op-engineering/op-sqlite': '>=2' + '@opentelemetry/api': ^1.4.1 + '@planetscale/database': '>=1' + '@prisma/client': '*' + '@tidbcloud/serverless': '*' + '@types/better-sqlite3': '*' + '@types/pg': '*' + '@types/react': '>=18' + '@types/sql.js': '*' + '@vercel/postgres': '>=0.8.0' + '@xata.io/client': '*' + better-sqlite3: '>=7' + bun-types: '*' + expo-sqlite: '>=13.2.0' + knex: '*' + kysely: '*' + mysql2: '>=2' + pg: '>=8' + postgres: '>=3' + prisma: '*' + react: '>=18' + sql.js: '>=1' + sqlite3: '>=5' + peerDependenciesMeta: + '@aws-sdk/client-rds-data': + optional: true + '@cloudflare/workers-types': + optional: true + '@electric-sql/pglite': + optional: true + '@libsql/client': + optional: true + '@neondatabase/serverless': + optional: true + '@op-engineering/op-sqlite': + optional: true + '@opentelemetry/api': + optional: true + '@planetscale/database': + optional: true + '@prisma/client': + optional: true + '@tidbcloud/serverless': + optional: true + '@types/better-sqlite3': + optional: true + '@types/pg': + optional: true + '@types/react': + optional: true + '@types/sql.js': + optional: true + '@vercel/postgres': + optional: true + '@xata.io/client': + optional: true + better-sqlite3: + optional: true + bun-types: + optional: true + expo-sqlite: + optional: true + knex: + optional: true + kysely: + optional: true + mysql2: + optional: true + pg: + optional: true + postgres: + optional: true + prisma: + optional: true + react: + optional: true + sql.js: + optional: true + sqlite3: + optional: true + dependencies: + '@planetscale/database': 1.16.0 + '@types/react': 18.3.11 + react: 18.3.1 + dev: false + /drizzle-orm@0.32.0-aaf764c(@planetscale/database@1.18.0)(@types/react@18.3.11)(react@18.3.1): resolution: {integrity: sha512-TpG2xZhiGUyxWMJTXpVwuuFsh0VrNxPzYOZfsDaFbZS0lBbAN1xC2TBUK4pv1DMGxygq+clvqNMd908RznJZRA==} peerDependencies: @@ -13857,7 +13953,7 @@ packages: react: 18.3.1 dev: false - /drizzle-orm@0.33.0(@opentelemetry/api@1.4.1)(@planetscale/database@1.18.0)(@types/react@18.3.11)(react@18.3.1): + /drizzle-orm@0.33.0(@opentelemetry/api@1.4.1)(@planetscale/database@1.16.0)(@types/react@18.3.11)(react@18.3.1): resolution: {integrity: sha512-SHy72R2Rdkz0LEq0PSG/IdvnT3nGiWuRk+2tXZQ90GVq/XQhpCzu/EFT3V2rox+w8MlkBQxifF8pCStNYnERfA==} peerDependencies: '@aws-sdk/client-rds-data': '>=3' @@ -13947,7 +14043,7 @@ packages: optional: true dependencies: '@opentelemetry/api': 1.4.1 - '@planetscale/database': 1.18.0 + '@planetscale/database': 1.16.0 '@types/react': 18.3.11 react: 18.3.1 dev: false @@ -13958,7 +14054,7 @@ packages: drizzle-orm: '>=0.23.13' zod: '*' dependencies: - drizzle-orm: 0.33.0(@opentelemetry/api@1.4.1)(@planetscale/database@1.18.0)(@types/react@18.3.11)(react@18.3.1) + drizzle-orm: 0.33.0(@opentelemetry/api@1.4.1)(@planetscale/database@1.16.0)(@types/react@18.3.11)(react@18.3.1) zod: 3.23.8 dev: false @@ -14009,8 +14105,8 @@ packages: jake: 10.9.2 dev: true - /electron-to-chromium@1.5.32: - resolution: {integrity: sha512-M+7ph0VGBQqqpTT2YrabjNKSQ2fEl9PVx6AK3N558gDH9NO8O6XN9SXXFWRo9u9PbEg/bWq+tjXQr+eXmxubCw==} + /electron-to-chromium@1.5.33: + resolution: {integrity: sha512-+cYTcFB1QqD4j4LegwLfpCNxifb6dDFUAwk6RsLusCwIaZI6or2f+q8rs5tTB2YC53HhOlIbEaqHMAAC8IOIwA==} /emoji-regex@10.4.0: resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==} @@ -14815,8 +14911,8 @@ packages: engines: {node: ^v12.20.0 || >=v14.13.0} dev: false - /express@4.21.0: - resolution: {integrity: sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==} + /express@4.21.1: + resolution: {integrity: sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==} engines: {node: '>= 0.10.0'} dependencies: accepts: 1.3.8 @@ -14824,7 +14920,7 @@ packages: body-parser: 1.20.3 content-disposition: 0.5.4 content-type: 1.0.5 - cookie: 0.6.0 + cookie: 0.7.1 cookie-signature: 1.0.6 debug: 2.6.9 depd: 2.0.0 @@ -14881,7 +14977,7 @@ packages: engines: {node: '>= 10.17.0'} hasBin: true dependencies: - debug: 4.3.7(supports-color@8.1.1) + debug: 4.3.4 get-stream: 5.2.0 yauzl: 2.10.0 optionalDependencies: @@ -14925,8 +15021,8 @@ packages: resolution: {integrity: sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==} dev: false - /fast-uri@3.0.1: - resolution: {integrity: sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==} + /fast-uri@3.0.2: + resolution: {integrity: sha512-GR6f0hD7XXyNJa25Tb9BuIdN0tdr+0BMi6/CJPH3wJO1JjNG3n/VsSw38AwRdKZABm8lGbPfakLRkYzx2V9row==} dev: true /fastest-levenshtein@1.0.16: @@ -17290,7 +17386,7 @@ packages: engines: {node: '>=14'} dependencies: mlly: 1.7.2 - pkg-types: 1.2.0 + pkg-types: 1.2.1 dev: true /locate-character@3.0.0: @@ -17465,7 +17561,7 @@ packages: /lower-case@2.0.2: resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} dependencies: - tslib: 2.7.0 + tslib: 2.4.1 dev: false /lowercase-keys@3.0.0: @@ -18868,7 +18964,6 @@ packages: /minipass@6.0.2: resolution: {integrity: sha512-MzWSV5nYVT7mVyWCwn2o7JH13w2TBRmmSqSRCKzTw+lmft9X4z+3wjvs06Tzijo5z4W/kahUCDpRXTF+ZrmF/w==} engines: {node: '>=16 || 14 >=14.17'} - dev: true /minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} @@ -18961,7 +19056,7 @@ packages: dependencies: acorn: 8.12.1 pathe: 1.1.2 - pkg-types: 1.2.0 + pkg-types: 1.2.1 ufo: 1.5.4 dev: true @@ -19260,7 +19355,7 @@ packages: resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} dependencies: lower-case: 2.0.2 - tslib: 2.7.0 + tslib: 2.4.1 dev: false /node-addon-api@7.1.1: @@ -19609,7 +19704,7 @@ packages: resolution: {integrity: sha512-kv2hevAWZZ3I/vd2t8znGO2rd8wkowncsfcYpo8i+wU9ML+JEcdqiViANXXjWWGjIhajFNixE6gOY1fEgqILAg==} hasBin: true dependencies: - '@types/node': 18.19.54 + '@types/node': 18.19.55 '@types/node-fetch': 2.6.11 abort-controller: 3.0.0 agentkeepalive: 4.5.0 @@ -19966,7 +20061,7 @@ packages: engines: {node: '>=16 || 14 >=14.18'} dependencies: lru-cache: 10.4.3 - minipass: 7.1.2 + minipass: 6.0.2 /path-to-regexp@0.1.10: resolution: {integrity: sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==} @@ -20054,8 +20149,8 @@ packages: find-up: 4.1.0 dev: true - /pkg-types@1.2.0: - resolution: {integrity: sha512-+ifYuSSqOQ8CqP4MbZA5hDpb97n3E8SVWdJe+Wms9kj745lmd3b7EZJiqvmLwAlmRfjrI7Hi5z3kdBJ93lFNPA==} + /pkg-types@1.2.1: + resolution: {integrity: sha512-sQoqa8alT3nHjGuTjuKgOnvjo4cljkufdtLMnO2LBP/wRwuDlo1tkaEdMxCRhyGRPacv/ztlZgDPm2b7FAmEvw==} dependencies: confbox: 0.1.8 mlly: 1.7.2 @@ -21892,15 +21987,6 @@ packages: engines: {node: '>=8'} dev: true - /slice-ansi@4.0.0: - resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} - engines: {node: '>=10'} - dependencies: - ansi-styles: 4.3.0 - astral-regex: 2.0.0 - is-fullwidth-code-point: 3.0.0 - dev: true - /slice-ansi@5.0.0: resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} engines: {node: '>=12'} @@ -21942,7 +22028,7 @@ packages: resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} dependencies: dot-case: 3.0.4 - tslib: 2.7.0 + tslib: 2.4.1 dev: false /snakecase-keys@3.2.1: @@ -22511,7 +22597,7 @@ packages: '@jridgewell/trace-mapping': 0.3.25 '@types/estree': 1.0.6 acorn: 8.12.1 - aria-query: 5.3.1 + aria-query: 5.3.2 axobject-query: 4.1.0 code-red: 1.0.4 css-tree: 2.3.1 @@ -22580,6 +22666,12 @@ packages: '@babel/runtime': 7.25.7 dev: false + /tailwind-merge@2.2.2: + resolution: {integrity: sha512-tWANXsnmJzgw6mQ07nE3aCDkCK4QdT3ThPMCzawoYA2Pws7vSTCvz3Vrjg61jVUGfFZPJzxEP+NimbcW+EdaDw==} + dependencies: + '@babel/runtime': 7.25.7 + dev: false + /tailwind-merge@2.3.0: resolution: {integrity: sha512-vkYrLpIP+lgR0tQCG6AP7zZXCTLc1Lnv/CCRT3BqJ9CZ3ui2++GPaGb1x/ILsINIMSYqqvrpqjUFsMNLlW99EA==} dependencies: @@ -24002,8 +24094,8 @@ packages: engines: {node: '>= 14'} dev: false - /webcrypto-core@1.8.0: - resolution: {integrity: sha512-kR1UQNH8MD42CYuLzvibfakG5Ew5seG85dMMoAM/1LqvckxaF6pUiidLuraIu4V+YCIFabYecUZAW0TuxAoaqw==} + /webcrypto-core@1.8.1: + resolution: {integrity: sha512-P+x1MvlNCXlKbLSOY4cYrdreqPG5hbzkmawbcXLKN/mf6DZW0SdNNkZ+sjwsqVkI4A4Ko2sPZmkZtCKY58w83A==} dependencies: '@peculiar/asn1-schema': 2.3.13 '@peculiar/json-schema': 1.1.12 From 6e9dae99e163c599bdfd8559c1df2f4796bdd440 Mon Sep 17 00:00:00 2001 From: Mike Silva Date: Tue, 8 Oct 2024 15:03:24 -0700 Subject: [PATCH 21/29] planetscale v change --- apps/api/src/pkg/testutil/harness.ts | 2 +- apps/semantic-cache/package.json | 2 +- apps/workflows/jobs/refill-daily.ts | 5 +- pnpm-lock.yaml | 151 +++------------------------ 4 files changed, 19 insertions(+), 141 deletions(-) diff --git a/apps/api/src/pkg/testutil/harness.ts b/apps/api/src/pkg/testutil/harness.ts index 71d8e5a576..541a0e4871 100644 --- a/apps/api/src/pkg/testutil/harness.ts +++ b/apps/api/src/pkg/testutil/harness.ts @@ -345,4 +345,4 @@ export abstract class Harness { await this.db.primary.insert(schema.keyAuth).values(this.resources.userKeyAuth); await this.db.primary.insert(schema.apis).values(this.resources.userApi); } -} +} \ No newline at end of file diff --git a/apps/semantic-cache/package.json b/apps/semantic-cache/package.json index be72cb489b..68cad8b55e 100644 --- a/apps/semantic-cache/package.json +++ b/apps/semantic-cache/package.json @@ -27,7 +27,7 @@ }, "dependencies": { "@chronark/zod-bird": "^0.3.9", - "@planetscale/database": "^1.18.0", + "@planetscale/database": "^1.16.0", "@unkey/cache": "workspace:^", "@unkey/db": "workspace:^", "@unkey/error": "workspace:^", diff --git a/apps/workflows/jobs/refill-daily.ts b/apps/workflows/jobs/refill-daily.ts index 7dce8698cc..49674e16d3 100644 --- a/apps/workflows/jobs/refill-daily.ts +++ b/apps/workflows/jobs/refill-daily.ts @@ -15,14 +15,13 @@ client.defineJob({ const date = payload.ts; // Set up last day of month so if refillDay is after last day of month, Key will be refilled today. const lastDayOfMonth = new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate(); - const today = date.getDate(); + const today = date.getUTCDate(); const db = connectDatabase(); - let sql = "" // If refillDay is after last day of month, refillDay will be today. const keys = await db.query.keys.findMany({ - where: (table, { isNotNull, isNull, gte, and, gt, or, eq }) => { + where: (table, { isNotNull, isNull, and, gt, or, eq }) => { const baseConditions = and( isNull(table.deletedAt), isNotNull(table.refillAmount), diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e875479f7c..a94530e65c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -75,7 +75,7 @@ importers: version: 0.2.1(hono@4.6.3)(zod@3.23.8) '@planetscale/database': specifier: ^1.16.0 - version: 1.16.0 + version: 1.18.0 '@unkey/cache': specifier: workspace:^ version: link:../../packages/cache @@ -169,7 +169,7 @@ importers: version: 1.5.2(ws@8.18.0) '@planetscale/database': specifier: ^1.16.0 - version: 1.16.0 + version: 1.18.0 '@trigger.dev/nextjs': specifier: 3.0.9 version: 3.0.9(@trigger.dev/sdk@3.0.9)(next@14.2.10) @@ -202,7 +202,7 @@ importers: version: 3.4.7(openai@4.52.1)(react@18.3.1)(svelte@4.2.19)(vue@3.5.11)(zod@3.23.8) drizzle-orm: specifier: ^0.33.0 - version: 0.33.0(@opentelemetry/api@1.4.1)(@planetscale/database@1.16.0)(@types/react@18.3.11)(react@18.3.1) + version: 0.33.0(@opentelemetry/api@1.4.1)(@planetscale/database@1.18.0)(@types/react@18.3.11)(react@18.3.1) drizzle-zod: specifier: ^0.5.1 version: 0.5.1(drizzle-orm@0.33.0)(zod@3.23.8) @@ -254,7 +254,7 @@ importers: version: 3.3.4(react-hook-form@7.51.3) '@planetscale/database': specifier: ^1.16.0 - version: 1.16.0 + version: 1.18.0 '@radix-ui/react-accordion': specifier: ^1.2.0 version: 1.2.0(@types/react-dom@18.3.0)(@types/react@18.3.11)(react-dom@18.3.1)(react@18.3.1) @@ -413,7 +413,7 @@ importers: version: 3.3.0(react-dom@18.3.1)(react@18.3.1) autoprefixer: specifier: ^10.4.19 - version: 10.4.19(postcss@8.4.38) + version: 10.4.20(postcss@8.4.38) class-variance-authority: specifier: ^0.7.0 version: 0.7.0 @@ -434,7 +434,7 @@ importers: version: 3.6.0 drizzle-orm: specifier: generated - version: 0.32.0-aaf764c(@planetscale/database@1.16.0)(@types/react@18.3.11)(react@18.3.1) + version: 0.32.0-aaf764c(@planetscale/database@1.18.0)(@types/react@18.3.11)(react@18.3.1) export-to-csv: specifier: ^1.4.0 version: 1.4.0 @@ -533,7 +533,7 @@ importers: version: 1.21.0 tailwind-merge: specifier: ^2.2.2 - version: 2.2.2 + version: 2.3.0 tailwindcss: specifier: ^3.4.3 version: 3.4.10(ts-node@10.9.2) @@ -697,7 +697,7 @@ importers: version: 18.3.0 autoprefixer: specifier: ^10.4.19 - version: 10.4.19(postcss@8.4.38) + version: 10.4.20(postcss@8.4.38) postcss: specifier: ^8 version: 8.4.38 @@ -832,7 +832,7 @@ importers: specifier: ^0.3.9 version: 0.3.9 '@planetscale/database': - specifier: ^1.18.0 + specifier: ^1.16.0 version: 1.18.0 '@unkey/cache': specifier: workspace:^ @@ -909,7 +909,7 @@ importers: version: 4.29.10(next@14.2.10)(react-dom@18.3.1)(react@18.3.1) '@planetscale/database': specifier: ^1.16.0 - version: 1.16.0 + version: 1.18.0 '@trigger.dev/nextjs': specifier: ^2.3.18 version: 2.3.18(@trigger.dev/sdk@2.3.18)(next@14.2.10) @@ -936,7 +936,7 @@ importers: version: link:../../internal/schema drizzle-orm: specifier: generated - version: 0.32.0-aaf764c(@planetscale/database@1.16.0)(@types/react@18.3.11)(react@18.3.1) + version: 0.32.0-aaf764c(@planetscale/database@1.18.0)(@types/react@18.3.11)(react@18.3.1) next: specifier: 14.2.10 version: 14.2.10(@babel/core@7.25.7)(@opentelemetry/api@1.4.1)(react-dom@18.3.1)(react@18.3.1) @@ -1102,7 +1102,7 @@ importers: version: 1.12.0 tailwind-merge: specifier: ^2.2.2 - version: 2.2.2 + version: 2.3.0 tailwindcss-animate: specifier: ^1.0.7 version: 1.0.7(tailwindcss@3.4.10) @@ -6940,11 +6940,6 @@ packages: requiresBuild: true optional: true - /@planetscale/database@1.16.0: - resolution: {integrity: sha512-HNUrTqrd8aTRZYMDcsoZ62s36sIWkMMmKZBOehoCWR2WrfNPKq+Q1yQef5okl3pSVlldFnu2h/dbHjOsDTHXug==} - engines: {node: '>=16'} - dev: false - /@planetscale/database@1.18.0: resolution: {integrity: sha512-t2XdOfrVgcF7AW791FtdPS27NyNqcE1SpoXgk3HpziousvUMsJi4Q6NL3JyOBpsMOrvk94749o8yyonvX5quPw==} engines: {node: '>=16'} @@ -11764,21 +11759,6 @@ packages: postcss-value-parser: 4.2.0 dev: false - /autoprefixer@10.4.19(postcss@8.4.38): - resolution: {integrity: sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==} - engines: {node: ^10 || ^12 || >=14} - hasBin: true - peerDependencies: - postcss: ^8.1.0 - dependencies: - browserslist: 4.24.0 - caniuse-lite: 1.0.30001667 - fraction.js: 4.3.7 - normalize-range: 0.1.2 - picocolors: 1.1.0 - postcss: 8.4.38 - postcss-value-parser: 4.2.0 - /autoprefixer@10.4.20(postcss@8.4.38): resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==} engines: {node: ^10 || ^12 || >=14} @@ -11793,7 +11773,6 @@ packages: picocolors: 1.1.0 postcss: 8.4.38 postcss-value-parser: 4.2.0 - dev: true /autoprefixer@10.4.20(postcss@8.4.45): resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==} @@ -13775,100 +13754,6 @@ packages: react: 18.3.1 dev: false - /drizzle-orm@0.32.0-aaf764c(@planetscale/database@1.16.0)(@types/react@18.3.11)(react@18.3.1): - resolution: {integrity: sha512-TpG2xZhiGUyxWMJTXpVwuuFsh0VrNxPzYOZfsDaFbZS0lBbAN1xC2TBUK4pv1DMGxygq+clvqNMd908RznJZRA==} - peerDependencies: - '@aws-sdk/client-rds-data': '>=3' - '@cloudflare/workers-types': '>=3' - '@electric-sql/pglite': '>=0.1.1' - '@libsql/client': '*' - '@neondatabase/serverless': '>=0.1' - '@op-engineering/op-sqlite': '>=2' - '@opentelemetry/api': ^1.4.1 - '@planetscale/database': '>=1' - '@prisma/client': '*' - '@tidbcloud/serverless': '*' - '@types/better-sqlite3': '*' - '@types/pg': '*' - '@types/react': '>=18' - '@types/sql.js': '*' - '@vercel/postgres': '>=0.8.0' - '@xata.io/client': '*' - better-sqlite3: '>=7' - bun-types: '*' - expo-sqlite: '>=13.2.0' - knex: '*' - kysely: '*' - mysql2: '>=2' - pg: '>=8' - postgres: '>=3' - prisma: '*' - react: '>=18' - sql.js: '>=1' - sqlite3: '>=5' - peerDependenciesMeta: - '@aws-sdk/client-rds-data': - optional: true - '@cloudflare/workers-types': - optional: true - '@electric-sql/pglite': - optional: true - '@libsql/client': - optional: true - '@neondatabase/serverless': - optional: true - '@op-engineering/op-sqlite': - optional: true - '@opentelemetry/api': - optional: true - '@planetscale/database': - optional: true - '@prisma/client': - optional: true - '@tidbcloud/serverless': - optional: true - '@types/better-sqlite3': - optional: true - '@types/pg': - optional: true - '@types/react': - optional: true - '@types/sql.js': - optional: true - '@vercel/postgres': - optional: true - '@xata.io/client': - optional: true - better-sqlite3: - optional: true - bun-types: - optional: true - expo-sqlite: - optional: true - knex: - optional: true - kysely: - optional: true - mysql2: - optional: true - pg: - optional: true - postgres: - optional: true - prisma: - optional: true - react: - optional: true - sql.js: - optional: true - sqlite3: - optional: true - dependencies: - '@planetscale/database': 1.16.0 - '@types/react': 18.3.11 - react: 18.3.1 - dev: false - /drizzle-orm@0.32.0-aaf764c(@planetscale/database@1.18.0)(@types/react@18.3.11)(react@18.3.1): resolution: {integrity: sha512-TpG2xZhiGUyxWMJTXpVwuuFsh0VrNxPzYOZfsDaFbZS0lBbAN1xC2TBUK4pv1DMGxygq+clvqNMd908RznJZRA==} peerDependencies: @@ -13963,7 +13848,7 @@ packages: react: 18.3.1 dev: false - /drizzle-orm@0.33.0(@opentelemetry/api@1.4.1)(@planetscale/database@1.16.0)(@types/react@18.3.11)(react@18.3.1): + /drizzle-orm@0.33.0(@opentelemetry/api@1.4.1)(@planetscale/database@1.18.0)(@types/react@18.3.11)(react@18.3.1): resolution: {integrity: sha512-SHy72R2Rdkz0LEq0PSG/IdvnT3nGiWuRk+2tXZQ90GVq/XQhpCzu/EFT3V2rox+w8MlkBQxifF8pCStNYnERfA==} peerDependencies: '@aws-sdk/client-rds-data': '>=3' @@ -14053,7 +13938,7 @@ packages: optional: true dependencies: '@opentelemetry/api': 1.4.1 - '@planetscale/database': 1.16.0 + '@planetscale/database': 1.18.0 '@types/react': 18.3.11 react: 18.3.1 dev: false @@ -14064,7 +13949,7 @@ packages: drizzle-orm: '>=0.23.13' zod: '*' dependencies: - drizzle-orm: 0.33.0(@opentelemetry/api@1.4.1)(@planetscale/database@1.16.0)(@types/react@18.3.11)(react@18.3.1) + drizzle-orm: 0.33.0(@opentelemetry/api@1.4.1)(@planetscale/database@1.18.0)(@types/react@18.3.11)(react@18.3.1) zod: 3.23.8 dev: false @@ -22695,12 +22580,6 @@ packages: '@babel/runtime': 7.25.7 dev: false - /tailwind-merge@2.2.2: - resolution: {integrity: sha512-tWANXsnmJzgw6mQ07nE3aCDkCK4QdT3ThPMCzawoYA2Pws7vSTCvz3Vrjg61jVUGfFZPJzxEP+NimbcW+EdaDw==} - dependencies: - '@babel/runtime': 7.25.7 - dev: false - /tailwind-merge@2.3.0: resolution: {integrity: sha512-vkYrLpIP+lgR0tQCG6AP7zZXCTLc1Lnv/CCRT3BqJ9CZ3ui2++GPaGb1x/ILsINIMSYqqvrpqjUFsMNLlW99EA==} dependencies: From 2ed73d814d075eba0fbc2a2152d21821280f77b6 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2024 22:05:33 +0000 Subject: [PATCH 22/29] [autofix.ci] apply automated fixes --- apps/api/src/pkg/testutil/harness.ts | 2 +- apps/api/src/routes/schema.ts | 5 +- apps/workflows/jobs/index.ts | 1 - apps/workflows/jobs/refill-daily.ts | 19 +- apps/www/app/feed.xml/route.ts | 4 +- packages/api/src/openapi.d.ts | 1133 +++++++++++++------------- 6 files changed, 591 insertions(+), 573 deletions(-) diff --git a/apps/api/src/pkg/testutil/harness.ts b/apps/api/src/pkg/testutil/harness.ts index 541a0e4871..71d8e5a576 100644 --- a/apps/api/src/pkg/testutil/harness.ts +++ b/apps/api/src/pkg/testutil/harness.ts @@ -345,4 +345,4 @@ export abstract class Harness { await this.db.primary.insert(schema.keyAuth).values(this.resources.userKeyAuth); await this.db.primary.insert(schema.apis).values(this.resources.userApi); } -} \ No newline at end of file +} diff --git a/apps/api/src/routes/schema.ts b/apps/api/src/routes/schema.ts index d0c8ebc6ef..a9d4d4b14b 100644 --- a/apps/api/src/routes/schema.ts +++ b/apps/api/src/routes/schema.ts @@ -59,7 +59,8 @@ export const keySchema = z refill: z .object({ interval: z.enum(["daily", "monthly"]).openapi({ - description: "Determines the rate at which verifications will be refilled. When 'daily' is set for 'interval' 'refillDay' will be set to null.", + description: + "Determines the rate at which verifications will be refilled. When 'daily' is set for 'interval' 'refillDay' will be set to null.", example: "daily", }), amount: z.number().int().openapi({ @@ -69,7 +70,7 @@ export const keySchema = z refillDay: z.number().min(1).max(31).default(1).nullable().openapi({ description: "The day verifications will refill each month, when interval is set to 'monthly'. Value is not zero-indexed making 1 the first day of the month. If left blank it will default to the first day of the month. When 'daily' is set for 'interval' 'refillDay' will be set to null.", - example: 15, + example: 15, }), lastRefillAt: z.number().int().optional().openapi({ description: "The unix timestamp in miliseconds when the key was last refilled.", diff --git a/apps/workflows/jobs/index.ts b/apps/workflows/jobs/index.ts index 0faac6979f..4b574e1e08 100644 --- a/apps/workflows/jobs/index.ts +++ b/apps/workflows/jobs/index.ts @@ -1,4 +1,3 @@ // export all your job files here export * from "./refill-daily"; - diff --git a/apps/workflows/jobs/refill-daily.ts b/apps/workflows/jobs/refill-daily.ts index 49674e16d3..489246647f 100644 --- a/apps/workflows/jobs/refill-daily.ts +++ b/apps/workflows/jobs/refill-daily.ts @@ -17,8 +17,7 @@ client.defineJob({ const lastDayOfMonth = new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate(); const today = date.getUTCDate(); const db = connectDatabase(); - - + // If refillDay is after last day of month, refillDay will be today. const keys = await db.query.keys.findMany({ where: (table, { isNotNull, isNull, and, gt, or, eq }) => { @@ -26,21 +25,15 @@ client.defineJob({ isNull(table.deletedAt), isNotNull(table.refillAmount), gt(table.refillAmount, table.remaining), - or( - isNull(table.refillDay), - eq(table.refillDay, today) - ) + or(isNull(table.refillDay), eq(table.refillDay, today)), ); - + if (today === lastDayOfMonth) { - return and( - baseConditions, - gt(table.refillDay, today) - ); + return and(baseConditions, gt(table.refillDay, today)); } return baseConditions; - } - }) + }, + }); io.logger.info(`found ${keys.length} keys with refill set for today`); for (const key of keys) { diff --git a/apps/www/app/feed.xml/route.ts b/apps/www/app/feed.xml/route.ts index cdca05473f..8f9b8bbee8 100644 --- a/apps/www/app/feed.xml/route.ts +++ b/apps/www/app/feed.xml/route.ts @@ -1,6 +1,6 @@ -import RSS from "rss"; -import { type Post, allPosts } from "content-collections"; import { authors } from "@/content/blog/authors"; +import { type Post, allPosts } from "content-collections"; +import RSS from "rss"; const feed = new RSS({ title: "Unkey", description: "Open Source API Development platform", diff --git a/packages/api/src/openapi.d.ts b/packages/api/src/openapi.d.ts index b8908d1d73..ce8cfafe72 100644 --- a/packages/api/src/openapi.d.ts +++ b/packages/api/src/openapi.d.ts @@ -3,11 +3,14 @@ * Do not make direct changes to the file. */ - /** OneOf type helpers */ type Without = { [P in Exclude]?: never }; -type XOR = (T | U) extends object ? (Without & U) | (Without & T) : T | U; -type OneOf = T extends [infer Only] ? Only : T extends [infer A, infer B, ...infer Rest] ? OneOf<[XOR, ...Rest]> : never; +type XOR = T | U extends object ? (Without & U) | (Without & T) : T | U; +type OneOf = T extends [infer Only] + ? Only + : T extends [infer A, infer B, ...infer Rest] + ? OneOf<[XOR, ...Rest]> + : never; export interface paths { "/v1/liveness": { @@ -521,7 +524,16 @@ export interface components { * * @enum {string} */ - code: "VALID" | "NOT_FOUND" | "FORBIDDEN" | "USAGE_EXCEEDED" | "RATE_LIMITED" | "UNAUTHORIZED" | "DISABLED" | "INSUFFICIENT_PERMISSIONS" | "EXPIRED"; + code: + | "VALID" + | "NOT_FOUND" + | "FORBIDDEN" + | "USAGE_EXCEEDED" + | "RATE_LIMITED" + | "UNAUTHORIZED" + | "DISABLED" + | "INSUFFICIENT_PERMISSIONS" + | "EXPIRED"; /** @description Sets the key to be enabled or disabled. Disabled keys will not verify. */ enabled?: boolean; /** @@ -547,11 +559,18 @@ export interface components { }; }; /** @description A query for which permissions you require */ - PermissionQuery: OneOf<[string, { - and: components["schemas"]["PermissionQuery"][]; - }, { - or: components["schemas"]["PermissionQuery"][]; - }, null]>; + PermissionQuery: OneOf< + [ + string, + { + and: components["schemas"]["PermissionQuery"][]; + }, + { + or: components["schemas"]["PermissionQuery"][]; + }, + null, + ] + >; V1KeysVerifyKeyRequest: { /** * @description The id of the api where the key belongs to. This is optional for now but will be required soon. @@ -596,21 +615,21 @@ export interface components { * ] */ ratelimits?: { - /** - * @description The name of the ratelimit. - * @example tokens - */ - name: string; - /** - * @description Optionally override how expensive this operation is and how many tokens are deducted from the current limit. - * @default 1 - */ - cost?: number; - /** @description Optionally override the limit. */ - limit?: number; - /** @description Optionally override the ratelimit window duration. */ - duration?: number; - }[]; + /** + * @description The name of the ratelimit. + * @example tokens + */ + name: string; + /** + * @description Optionally override how expensive this operation is and how many tokens are deducted from the current limit. + * @default 1 + */ + cost?: number; + /** @description Optionally override the limit. */ + limit?: number; + /** @description Optionally override the ratelimit window duration. */ + duration?: number; + }[]; }; ErrDeleteProtected: { error: { @@ -636,8 +655,7 @@ export interface components { }; }; responses: never; - parameters: { - }; + parameters: {}; requestBodies: never; headers: never; pathItems: never; @@ -648,7 +666,6 @@ export type $defs = Record; export type external = Record; export interface operations { - "v1.liveness": { responses: { /** @description The configured services and their status */ @@ -1293,7 +1310,7 @@ export interface operations { * "refillInterval": 60 * } */ - ratelimit?: ({ + ratelimit?: { /** * @deprecated * @description Fast ratelimiting doesn't add latency, while consistent ratelimiting is more accurate. @@ -1325,7 +1342,7 @@ export interface operations { * This field will become required in a future version. */ duration?: number; - }) | null; + } | null; /** * @description The number of requests that can be made with this key before it becomes invalid. Set `null` to disable. * @example 1000 @@ -1338,7 +1355,7 @@ export interface operations { * "amount": 100 * } */ - refill?: ({ + refill?: { /** * @description Unkey will automatically refill verifications at the set interval. If null is used the refill functionality will be removed from the key. * @enum {string} @@ -1346,7 +1363,7 @@ export interface operations { interval: "daily" | "monthly"; /** @description The amount of verifications to refill for each occurrence is determined individually for each key. */ amount: number; - }) | null; + } | null; /** * @description Set if key is enabled or disabled. If disabled, the key cannot be used to verify. * @example true @@ -1369,16 +1386,16 @@ export interface operations { * ] */ roles?: { - /** @description The id of the role. Provide either `id` or `name`. If both are provided `id` is used. */ - id?: string; - /** @description Identify the role via its name. Provide either `id` or `name`. If both are provided `id` is used. */ - name?: string; - /** - * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. - * Autocreating roles requires your root key to have the `rbac.*.create_role` permission, otherwise the request will get rejected - */ - create?: boolean; - }[]; + /** @description The id of the role. Provide either `id` or `name`. If both are provided `id` is used. */ + id?: string; + /** @description Identify the role via its name. Provide either `id` or `name`. If both are provided `id` is used. */ + name?: string; + /** + * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. + * Autocreating roles requires your root key to have the `rbac.*.create_role` permission, otherwise the request will get rejected + */ + create?: boolean; + }[]; /** * @description The permissions you want to set for this key. This overwrites all existing permissions. * Setting permissions requires the `rbac.*.add_permission_to_key` permission. @@ -1396,16 +1413,16 @@ export interface operations { * ] */ permissions?: { - /** @description The id of the permission. Provide either `id` or `name`. If both are provided `id` is used. */ - id?: string; - /** @description Identify the permission via its name. Provide either `id` or `name`. If both are provided `id` is used. */ - name?: string; - /** - * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. - * Autocreating permissions requires your root key to have the `rbac.*.create_permission` permission, otherwise the request will get rejected - */ - create?: boolean; - }[]; + /** @description The id of the permission. Provide either `id` or `name`. If both are provided `id` is used. */ + id?: string; + /** @description Identify the permission via its name. Provide either `id` or `name`. If both are provided `id` is used. */ + name?: string; + /** + * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. + * Autocreating permissions requires your root key to have the `rbac.*.create_permission` permission, otherwise the request will get rejected + */ + create?: boolean; + }[]; }; }; }; @@ -1555,27 +1572,27 @@ export interface operations { content: { "application/json": { verifications: { - /** - * @description The timestamp of the usage data - * @example 1620000000000 - */ - time: number; - /** - * @description The number of successful requests - * @example 100 - */ - success: number; - /** - * @description The number of requests that were rate limited - * @example 10 - */ - rateLimited: number; - /** - * @description The number of requests that exceeded the usage limit - * @example 0 - */ - usageExceeded: number; - }[]; + /** + * @description The timestamp of the usage data + * @example 1620000000000 + */ + time: number; + /** + * @description The number of successful requests + * @example 100 + */ + success: number; + /** + * @description The number of requests that were rate limited + * @example 10 + */ + rateLimited: number; + /** + * @description The number of requests that exceeded the usage limit + * @example 0 + */ + usageExceeded: number; + }[]; }; }; }; @@ -1631,16 +1648,16 @@ export interface operations { keyId: string; /** @description The permissions you want to add to this key */ permissions: { - /** @description The id of the permission. Provide either `id` or `name`. If both are provided `id` is used. */ - id?: string; - /** @description Identify the permission via its name. Provide either `id` or `name`. If both are provided `id` is used. */ - name?: string; - /** - * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. - * Autocreating permissions requires your root key to have the `rbac.*.create_permission` permission, otherwise the request will get rejected - */ - create?: boolean; - }[]; + /** @description The id of the permission. Provide either `id` or `name`. If both are provided `id` is used. */ + id?: string; + /** @description Identify the permission via its name. Provide either `id` or `name`. If both are provided `id` is used. */ + name?: string; + /** + * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. + * Autocreating permissions requires your root key to have the `rbac.*.create_permission` permission, otherwise the request will get rejected + */ + create?: boolean; + }[]; }; }; }; @@ -1649,17 +1666,17 @@ export interface operations { 200: { content: { "application/json": { - /** - * @description The id of the permission. This is used internally - * @example perm_123 - */ - id: string; - /** - * @description The name of the permission - * @example dns.record.create - */ - name: string; - }[]; + /** + * @description The id of the permission. This is used internally + * @example perm_123 + */ + id: string; + /** + * @description The name of the permission + * @example dns.record.create + */ + name: string; + }[]; }; }; /** @description The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). */ @@ -1724,11 +1741,11 @@ export interface operations { * ] */ permissions: { - /** @description The id of the permission. Provide either `id` or `name`. If both are provided `id` is used. */ - id?: string; - /** @description Identify the permission via its name. Provide either `id` or `name`. If both are provided `id` is used. */ - name?: string; - }[]; + /** @description The id of the permission. Provide either `id` or `name`. If both are provided `id` is used. */ + id?: string; + /** @description Identify the permission via its name. Provide either `id` or `name`. If both are provided `id` is used. */ + name?: string; + }[]; }; }; }; @@ -1806,16 +1823,16 @@ export interface operations { * ] */ permissions: { - /** @description The id of the permission. Provide either `id` or `name`. If both are provided `id` is used. */ - id?: string; - /** @description Identify the permission via its name. Provide either `id` or `name`. If both are provided `id` is used. */ - name?: string; - /** - * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. - * Autocreating permissions requires your root key to have the `rbac.*.create_permission` permission, otherwise the request will get rejected - */ - create?: boolean; - }[]; + /** @description The id of the permission. Provide either `id` or `name`. If both are provided `id` is used. */ + id?: string; + /** @description Identify the permission via its name. Provide either `id` or `name`. If both are provided `id` is used. */ + name?: string; + /** + * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. + * Autocreating permissions requires your root key to have the `rbac.*.create_permission` permission, otherwise the request will get rejected + */ + create?: boolean; + }[]; }; }; }; @@ -1824,17 +1841,17 @@ export interface operations { 200: { content: { "application/json": { - /** - * @description The id of the permission. This is used internally - * @example perm_123 - */ - id: string; - /** - * @description The name of the permission - * @example dns.record.create - */ - name: string; - }[]; + /** + * @description The id of the permission. This is used internally + * @example perm_123 + */ + id: string; + /** + * @description The name of the permission + * @example dns.record.create + */ + name: string; + }[]; }; }; /** @description The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). */ @@ -1904,16 +1921,16 @@ export interface operations { * ] */ roles: { - /** @description The id of the role. Provide either `id` or `name`. If both are provided `id` is used. */ - id?: string; - /** @description Identify the role via its name. Provide either `id` or `name`. If both are provided `id` is used. */ - name?: string; - /** - * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. - * Autocreating roles requires your root key to have the `rbac.*.create_role` permission, otherwise the request will get rejected - */ - create?: boolean; - }[]; + /** @description The id of the role. Provide either `id` or `name`. If both are provided `id` is used. */ + id?: string; + /** @description Identify the role via its name. Provide either `id` or `name`. If both are provided `id` is used. */ + name?: string; + /** + * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. + * Autocreating roles requires your root key to have the `rbac.*.create_role` permission, otherwise the request will get rejected + */ + create?: boolean; + }[]; }; }; }; @@ -1922,17 +1939,17 @@ export interface operations { 200: { content: { "application/json": { - /** - * @description The id of the role. This is used internally - * @example role_123 - */ - id: string; - /** - * @description The name of the role - * @example dns.record.create - */ - name: string; - }[]; + /** + * @description The id of the role. This is used internally + * @example role_123 + */ + id: string; + /** + * @description The name of the role + * @example dns.record.create + */ + name: string; + }[]; }; }; /** @description The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). */ @@ -1997,11 +2014,11 @@ export interface operations { * ] */ roles: { - /** @description The id of the role. Provide either `id` or `name`. If both are provided `id` is used. */ - id?: string; - /** @description Identify the role via its name. Provide either `id` or `name`. If both are provided `id` is used. */ - name?: string; - }[]; + /** @description The id of the role. Provide either `id` or `name`. If both are provided `id` is used. */ + id?: string; + /** @description Identify the role via its name. Provide either `id` or `name`. If both are provided `id` is used. */ + name?: string; + }[]; }; }; }; @@ -2079,16 +2096,16 @@ export interface operations { * ] */ roles: { - /** @description The id of the role. Provide either `id` or `name`. If both are provided `id` is used. */ - id?: string; - /** @description Identify the role via its name. Provide either `id` or `name`. If both are provided `id` is used. */ - name?: string; - /** - * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. - * Autocreating roles requires your root key to have the `rbac.*.create_role` permission, otherwise the request will get rejected - */ - create?: boolean; - }[]; + /** @description The id of the role. Provide either `id` or `name`. If both are provided `id` is used. */ + id?: string; + /** @description Identify the role via its name. Provide either `id` or `name`. If both are provided `id` is used. */ + name?: string; + /** + * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. + * Autocreating roles requires your root key to have the `rbac.*.create_role` permission, otherwise the request will get rejected + */ + create?: boolean; + }[]; }; }; }; @@ -2097,17 +2114,17 @@ export interface operations { 200: { content: { "application/json": { - /** - * @description The id of the role. This is used internally - * @example role_123 - */ - id: string; - /** - * @description The name of the role - * @example dns.record.create - */ - name: string; - }[]; + /** + * @description The id of the role. This is used internally + * @example role_123 + */ + id: string; + /** + * @description The name of the role + * @example dns.record.create + */ + name: string; + }[]; }; }; /** @description The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). */ @@ -2556,26 +2573,26 @@ export interface operations { * ] */ resources?: { - /** - * @description The type of resource - * @example organization - */ - type: string; - /** - * @description The unique identifier for the resource - * @example org_123 - */ - id: string; - /** - * @description A human readable name for this resource - * @example unkey - */ - name?: string; - /** @description Attach any metadata to this resources */ - meta?: { - [key: string]: unknown; - }; - }[]; + /** + * @description The type of resource + * @example organization + */ + type: string; + /** + * @description The unique identifier for the resource + * @example org_123 + */ + id: string; + /** + * @description A human readable name for this resource + * @example unkey + */ + name?: string; + /** @description Attach any metadata to this resources */ + meta?: { + [key: string]: unknown; + }; + }[]; }; }; }; @@ -2653,33 +2670,245 @@ export interface operations { "v1.migrations.createKeys": { requestBody: { content: { - "application/json": ({ - /** - * @description Choose an `API` where this key should be created. - * @example api_123 - */ - apiId: string; + "application/json": { + /** + * @description Choose an `API` where this key should be created. + * @example api_123 + */ + apiId: string; + /** + * @description To make it easier for your users to understand which product an api key belongs to, you can add prefix them. + * + * For example Stripe famously prefixes their customer ids with cus_ or their api keys with sk_live_. + * + * The underscore is automatically added if you are defining a prefix, for example: "prefix": "abc" will result in a key like abc_xxxxxxxxx + */ + prefix?: string; + /** + * @description The name for your Key. This is not customer facing. + * @example my key + */ + name?: string; + /** @description The raw key in plaintext. If provided, unkey encrypts this value and stores it securely. Provide either `hash` or `plaintext` */ + plaintext?: string; + /** @description Provide either `hash` or `plaintext` */ + hash?: { + /** @description The hashed and encoded key */ + value: string; /** - * @description To make it easier for your users to understand which product an api key belongs to, you can add prefix them. - * - * For example Stripe famously prefixes their customer ids with cus_ or their api keys with sk_live_. - * - * The underscore is automatically added if you are defining a prefix, for example: "prefix": "abc" will result in a key like abc_xxxxxxxxx + * @description The algorithm for hashing and encoding, currently only sha256 and base64 are supported + * @enum {string} */ - prefix?: string; + variant: "sha256_base64"; + }; + /** + * @description The first 4 characters of the key. If a prefix is used, it should be the prefix plus 4 characters. + * @example unkey_32kq + */ + start?: string; + /** + * @description Your user’s Id. This will provide a link between Unkey and your customer record. + * When validating a key, we will return this back to you, so you can clearly identify your user from their api key. + * @example team_123 + */ + ownerId?: string; + /** + * @description This is a place for dynamic meta data, anything that feels useful for you should go here + * @example { + * "billingTier": "PRO", + * "trialEnds": "2023-06-16T17:16:37.161Z" + * } + */ + meta?: { + [key: string]: unknown; + }; + /** + * @description A list of roles that this key should have. If the role does not exist, an error is thrown + * @example [ + * "admin", + * "finance" + * ] + */ + roles?: string[]; + /** + * @description A list of permissions that this key should have. If the permission does not exist, an error is thrown + * @example [ + * "domains.create_record", + * "say_hello" + * ] + */ + permissions?: string[]; + /** + * @description You can auto expire keys by providing a unix timestamp in milliseconds. Once Keys expire they will automatically be disabled and are no longer valid unless you enable them again. + * @example 1623869797161 + */ + expires?: number; + /** + * @description You can limit the number of requests a key can make. Once a key reaches 0 remaining requests, it will automatically be disabled and is no longer valid unless you update it. + * @example 1000 + */ + remaining?: number; + /** + * @description Unkey enables you to refill verifications for each key at regular intervals. + * @example { + * "interval": "daily", + * "amount": 100 + * } + */ + refill?: { /** - * @description The name for your Key. This is not customer facing. - * @example my key + * @description Unkey will automatically refill verifications at the set interval. + * @enum {string} */ - name?: string; - /** @description The raw key in plaintext. If provided, unkey encrypts this value and stores it securely. Provide either `hash` or `plaintext` */ - plaintext?: string; - /** @description Provide either `hash` or `plaintext` */ - hash?: { - /** @description The hashed and encoded key */ - value: string; - /** - * @description The algorithm for hashing and encoding, currently only sha256 and base64 are supported + interval: "daily" | "monthly"; + /** @description The number of verifications to refill for each occurrence is determined individually for each key. */ + amount: number; + }; + /** + * @description Unkey comes with per-key ratelimiting out of the box. + * @example { + * "type": "fast", + * "limit": 10, + * "refillRate": 1, + * "refillInterval": 60 + * } + */ + ratelimit?: { + /** + * @description Async will return a response immediately, lowering latency at the cost of accuracy. + * @default false + */ + async?: boolean; + /** + * @deprecated + * @description Fast ratelimiting doesn't add latency, while consistent ratelimiting is more accurate. + * @default fast + * @enum {string} + */ + type?: "fast" | "consistent"; + /** @description The total amount of burstable requests. */ + limit: number; + /** + * @deprecated + * @description How many tokens to refill during each refillInterval. + */ + refillRate: number; + /** + * @deprecated + * @description Determines the speed at which tokens are refilled, in milliseconds. + */ + refillInterval: number; + }; + /** + * @description Sets if key is enabled or disabled. Disabled keys are not valid. + * @default true + * @example false + */ + enabled?: boolean; + /** + * @description Environments allow you to divide your keyspace. + * + * Some applications like Stripe, Clerk, WorkOS and others have a concept of "live" and "test" keys to + * give the developer a way to develop their own application without the risk of modifying real world + * resources. + * + * When you set an environment, we will return it back to you when validating the key, so you can + * handle it correctly. + */ + environment?: string; + }[]; + }; + }; + responses: { + /** @description The key ids of all created keys */ + 200: { + content: { + "application/json": { + /** + * @description The ids of the keys. This is not a secret and can be stored as a reference if you wish. You need the keyId to update or delete a key later. + * @example [ + * "key_123", + * "key_456" + * ] + */ + keyIds: string[]; + }; + }; + }; + /** @description The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). */ + 400: { + content: { + "application/json": components["schemas"]["ErrBadRequest"]; + }; + }; + /** @description Although the HTTP standard specifies "unauthorized", semantically this response means "unauthenticated". That is, the client must authenticate itself to get the requested response. */ + 401: { + content: { + "application/json": components["schemas"]["ErrUnauthorized"]; + }; + }; + /** @description The client does not have access rights to the content; that is, it is unauthorized, so the server is refusing to give the requested resource. Unlike 401 Unauthorized, the client's identity is known to the server. */ + 403: { + content: { + "application/json": components["schemas"]["ErrForbidden"]; + }; + }; + /** @description The server cannot find the requested resource. In the browser, this means the URL is not recognized. In an API, this can also mean that the endpoint is valid but the resource itself does not exist. Servers may also send this response instead of 403 Forbidden to hide the existence of a resource from an unauthorized client. This response code is probably the most well known due to its frequent occurrence on the web. */ + 404: { + content: { + "application/json": components["schemas"]["ErrNotFound"]; + }; + }; + /** @description This response is sent when a request conflicts with the current state of the server. */ + 409: { + content: { + "application/json": components["schemas"]["ErrConflict"]; + }; + }; + /** @description The user has sent too many requests in a given amount of time ("rate limiting") */ + 429: { + content: { + "application/json": components["schemas"]["ErrTooManyRequests"]; + }; + }; + /** @description The server has encountered a situation it does not know how to handle. */ + 500: { + content: { + "application/json": components["schemas"]["ErrInternalServerError"]; + }; + }; + }; + }; + "v1.migrations.enqueueKeys": { + requestBody: { + content: { + "application/json": { + /** @description Contact support@unkey.dev to receive your migration id. */ + migrationId: string; + /** @description The id of the api, you want to migrate keys to */ + apiId: string; + keys: { + /** + * @description To make it easier for your users to understand which product an api key belongs to, you can add prefix them. + * + * For example Stripe famously prefixes their customer ids with cus_ or their api keys with sk_live_. + * + * The underscore is automatically added if you are defining a prefix, for example: "prefix": "abc" will result in a key like abc_xxxxxxxxx + */ + prefix?: string; + /** + * @description The name for your Key. This is not customer facing. + * @example my key + */ + name?: string; + /** @description The raw key in plaintext. If provided, unkey encrypts this value and stores it securely. Provide either `hash` or `plaintext` */ + plaintext?: string; + /** @description Provide either `hash` or `plaintext` */ + hash?: { + /** @description The hashed and encoded key */ + value: string; + /** + * @description The algorithm for hashing and encoding, currently only sha256 and base64 are supported * @enum {string} */ variant: "sha256_base64"; @@ -2748,39 +2977,43 @@ export interface operations { amount: number; }; /** - * @description Unkey comes with per-key ratelimiting out of the box. + * @description Unkey comes with per-key fixed-window ratelimiting out of the box. * @example { * "type": "fast", * "limit": 10, - * "refillRate": 1, - * "refillInterval": 60 + * "duration": 60000 * } */ ratelimit?: { /** * @description Async will return a response immediately, lowering latency at the cost of accuracy. - * @default false + * @default true */ async?: boolean; /** * @deprecated - * @description Fast ratelimiting doesn't add latency, while consistent ratelimiting is more accurate. + * @description Deprecated, use `async`. Fast ratelimiting doesn't add latency, while consistent ratelimiting is more accurate. * @default fast * @enum {string} */ type?: "fast" | "consistent"; - /** @description The total amount of burstable requests. */ + /** @description The total amount of requests in a given interval. */ limit: number; + /** + * @description The window duration in milliseconds + * @example 60000 + */ + duration: number; /** * @deprecated * @description How many tokens to refill during each refillInterval. */ - refillRate: number; + refillRate?: number; /** * @deprecated - * @description Determines the speed at which tokens are refilled, in milliseconds. + * @description The refill timeframe, in milliseconds. */ - refillInterval: number; + refillInterval?: number; }; /** * @description Sets if key is enabled or disabled. Disabled keys are not valid. @@ -2799,223 +3032,7 @@ export interface operations { * handle it correctly. */ environment?: string; - })[]; - }; - }; - responses: { - /** @description The key ids of all created keys */ - 200: { - content: { - "application/json": { - /** - * @description The ids of the keys. This is not a secret and can be stored as a reference if you wish. You need the keyId to update or delete a key later. - * @example [ - * "key_123", - * "key_456" - * ] - */ - keyIds: string[]; - }; - }; - }; - /** @description The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). */ - 400: { - content: { - "application/json": components["schemas"]["ErrBadRequest"]; - }; - }; - /** @description Although the HTTP standard specifies "unauthorized", semantically this response means "unauthenticated". That is, the client must authenticate itself to get the requested response. */ - 401: { - content: { - "application/json": components["schemas"]["ErrUnauthorized"]; - }; - }; - /** @description The client does not have access rights to the content; that is, it is unauthorized, so the server is refusing to give the requested resource. Unlike 401 Unauthorized, the client's identity is known to the server. */ - 403: { - content: { - "application/json": components["schemas"]["ErrForbidden"]; - }; - }; - /** @description The server cannot find the requested resource. In the browser, this means the URL is not recognized. In an API, this can also mean that the endpoint is valid but the resource itself does not exist. Servers may also send this response instead of 403 Forbidden to hide the existence of a resource from an unauthorized client. This response code is probably the most well known due to its frequent occurrence on the web. */ - 404: { - content: { - "application/json": components["schemas"]["ErrNotFound"]; - }; - }; - /** @description This response is sent when a request conflicts with the current state of the server. */ - 409: { - content: { - "application/json": components["schemas"]["ErrConflict"]; - }; - }; - /** @description The user has sent too many requests in a given amount of time ("rate limiting") */ - 429: { - content: { - "application/json": components["schemas"]["ErrTooManyRequests"]; - }; - }; - /** @description The server has encountered a situation it does not know how to handle. */ - 500: { - content: { - "application/json": components["schemas"]["ErrInternalServerError"]; - }; - }; - }; - }; - "v1.migrations.enqueueKeys": { - requestBody: { - content: { - "application/json": { - /** @description Contact support@unkey.dev to receive your migration id. */ - migrationId: string; - /** @description The id of the api, you want to migrate keys to */ - apiId: string; - keys: ({ - /** - * @description To make it easier for your users to understand which product an api key belongs to, you can add prefix them. - * - * For example Stripe famously prefixes their customer ids with cus_ or their api keys with sk_live_. - * - * The underscore is automatically added if you are defining a prefix, for example: "prefix": "abc" will result in a key like abc_xxxxxxxxx - */ - prefix?: string; - /** - * @description The name for your Key. This is not customer facing. - * @example my key - */ - name?: string; - /** @description The raw key in plaintext. If provided, unkey encrypts this value and stores it securely. Provide either `hash` or `plaintext` */ - plaintext?: string; - /** @description Provide either `hash` or `plaintext` */ - hash?: { - /** @description The hashed and encoded key */ - value: string; - /** - * @description The algorithm for hashing and encoding, currently only sha256 and base64 are supported - * @enum {string} - */ - variant: "sha256_base64"; - }; - /** - * @description The first 4 characters of the key. If a prefix is used, it should be the prefix plus 4 characters. - * @example unkey_32kq - */ - start?: string; - /** - * @description Your user’s Id. This will provide a link between Unkey and your customer record. - * When validating a key, we will return this back to you, so you can clearly identify your user from their api key. - * @example team_123 - */ - ownerId?: string; - /** - * @description This is a place for dynamic meta data, anything that feels useful for you should go here - * @example { - * "billingTier": "PRO", - * "trialEnds": "2023-06-16T17:16:37.161Z" - * } - */ - meta?: { - [key: string]: unknown; - }; - /** - * @description A list of roles that this key should have. If the role does not exist, an error is thrown - * @example [ - * "admin", - * "finance" - * ] - */ - roles?: string[]; - /** - * @description A list of permissions that this key should have. If the permission does not exist, an error is thrown - * @example [ - * "domains.create_record", - * "say_hello" - * ] - */ - permissions?: string[]; - /** - * @description You can auto expire keys by providing a unix timestamp in milliseconds. Once Keys expire they will automatically be disabled and are no longer valid unless you enable them again. - * @example 1623869797161 - */ - expires?: number; - /** - * @description You can limit the number of requests a key can make. Once a key reaches 0 remaining requests, it will automatically be disabled and is no longer valid unless you update it. - * @example 1000 - */ - remaining?: number; - /** - * @description Unkey enables you to refill verifications for each key at regular intervals. - * @example { - * "interval": "daily", - * "amount": 100 - * } - */ - refill?: { - /** - * @description Unkey will automatically refill verifications at the set interval. - * @enum {string} - */ - interval: "daily" | "monthly"; - /** @description The number of verifications to refill for each occurrence is determined individually for each key. */ - amount: number; - }; - /** - * @description Unkey comes with per-key fixed-window ratelimiting out of the box. - * @example { - * "type": "fast", - * "limit": 10, - * "duration": 60000 - * } - */ - ratelimit?: { - /** - * @description Async will return a response immediately, lowering latency at the cost of accuracy. - * @default true - */ - async?: boolean; - /** - * @deprecated - * @description Deprecated, use `async`. Fast ratelimiting doesn't add latency, while consistent ratelimiting is more accurate. - * @default fast - * @enum {string} - */ - type?: "fast" | "consistent"; - /** @description The total amount of requests in a given interval. */ - limit: number; - /** - * @description The window duration in milliseconds - * @example 60000 - */ - duration: number; - /** - * @deprecated - * @description How many tokens to refill during each refillInterval. - */ - refillRate?: number; - /** - * @deprecated - * @description The refill timeframe, in milliseconds. - */ - refillInterval?: number; - }; - /** - * @description Sets if key is enabled or disabled. Disabled keys are not valid. - * @default true - * @example false - */ - enabled?: boolean; - /** - * @description Environments allow you to divide your keyspace. - * - * Some applications like Stripe, Clerk, WorkOS and others have a concept of "live" and "test" keys to - * give the developer a way to develop their own application without the risk of modifying real world - * resources. - * - * When you set an environment, we will return it back to you when validating the key, so you can - * handle it correctly. - */ - environment?: string; - })[]; + }[]; }; }; }; @@ -3286,22 +3303,22 @@ export interface operations { 200: { content: { "application/json": { - /** - * @description The id of the permission - * @example perm_123 - */ - id: string; - /** - * @description The name of the permission. - * @example domain.record.manager - */ - name: string; - /** - * @description The description of what this permission does. This is just for your team, your users will not see this. - * @example Can manage dns records - */ - description?: string; - }[]; + /** + * @description The id of the permission + * @example perm_123 + */ + id: string; + /** + * @description The name of the permission. + * @example domain.record.manager + */ + name: string; + /** + * @description The description of what this permission does. This is just for your team, your users will not see this. + * @example Can manage dns records + */ + description?: string; + }[]; }; }; /** @description The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). */ @@ -3564,22 +3581,22 @@ export interface operations { 200: { content: { "application/json": { - /** - * @description The id of the role - * @example role_1234 - */ - id: string; - /** - * @description The name of the role. - * @example domain.record.manager - */ - name: string; - /** - * @description The description of what this role does. This is just for your team, your users will not see this. - * @example Can manage dns records - */ - description?: string; - }[]; + /** + * @description The id of the role + * @example role_1234 + */ + id: string; + /** + * @description The name of the role. + * @example domain.record.manager + */ + name: string; + /** + * @description The description of what this role does. This is just for your team, your users will not see this. + * @example Can manage dns records + */ + description?: string; + }[]; }; }; /** @description The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). */ @@ -3653,22 +3670,22 @@ export interface operations { * When verifying keys, you can specify which limits you want to use and all keys attached to this identity, will share the limits. */ ratelimits?: { - /** - * @description The name of this limit. You will need to use this again when verifying a key. - * @example tokens - */ - name: string; - /** - * @description How many requests may pass within a given window before requests are rejected. - * @example 10 - */ - limit: number; - /** - * @description The duration for each ratelimit window in milliseconds. - * @example 1000 - */ - duration: number; - }[]; + /** + * @description The name of this limit. You will need to use this again when verifying a key. + * @example tokens + */ + name: string; + /** + * @description How many requests may pass within a given window before requests are rejected. + * @example 10 + */ + limit: number; + /** + * @description The duration for each ratelimit window in milliseconds. + * @example 1000 + */ + duration: number; + }[]; }; }; }; @@ -3751,22 +3768,22 @@ export interface operations { }; /** @description When verifying keys, you can specify which limits you want to use and all keys attached to this identity, will share the limits. */ ratelimits: { - /** - * @description The name of this limit. You will need to use this again when verifying a key. - * @example tokens - */ - name: string; - /** - * @description How many requests may pass within a given window before requests are rejected. - * @example 10 - */ - limit: number; - /** - * @description The duration for each ratelimit window in milliseconds. - * @example 1000 - */ - duration: number; - }[]; + /** + * @description The name of this limit. You will need to use this again when verifying a key. + * @example tokens + */ + name: string; + /** + * @description How many requests may pass within a given window before requests are rejected. + * @example 10 + */ + limit: number; + /** + * @description The duration for each ratelimit window in milliseconds. + * @example 1000 + */ + duration: number; + }[]; }; }; }; @@ -3829,29 +3846,29 @@ export interface operations { "application/json": { /** @description A list of identities. */ identities: { - /** @description The id of this identity. Used to interact with unkey's API */ - id: string; - /** @description The id in your system */ - externalId: string; - /** @description When verifying keys, you can specify which limits you want to use and all keys attached to this identity, will share the limits. */ - ratelimits: { - /** - * @description The name of this limit. You will need to use this again when verifying a key. - * @example tokens - */ - name: string; - /** - * @description How many requests may pass within a given window before requests are rejected. - * @example 10 - */ - limit: number; - /** - * @description The duration for each ratelimit window in milliseconds. - * @example 1000 - */ - duration: number; - }[]; + /** @description The id of this identity. Used to interact with unkey's API */ + id: string; + /** @description The id in your system */ + externalId: string; + /** @description When verifying keys, you can specify which limits you want to use and all keys attached to this identity, will share the limits. */ + ratelimits: { + /** + * @description The name of this limit. You will need to use this again when verifying a key. + * @example tokens + */ + name: string; + /** + * @description How many requests may pass within a given window before requests are rejected. + * @example 10 + */ + limit: number; + /** + * @description The duration for each ratelimit window in milliseconds. + * @example 1000 + */ + duration: number; }[]; + }[]; /** * @description The cursor to use for the next page of results, if no cursor is returned, there are no more results * @example eyJrZXkiOiJrZXlfMTIzNCJ9 @@ -3944,22 +3961,22 @@ export interface operations { * When verifying keys, you can specify which limits you want to use and all keys attached to this identity, will share the limits. */ ratelimits?: { - /** - * @description The name of this limit. You will need to use this again when verifying a key. - * @example tokens - */ - name: string; - /** - * @description How many requests may pass within a given window before requests are rejected. - * @example 10 - */ - limit: number; - /** - * @description The duration for each ratelimit window in milliseconds. - * @example 1000 - */ - duration: number; - }[]; + /** + * @description The name of this limit. You will need to use this again when verifying a key. + * @example tokens + */ + name: string; + /** + * @description How many requests may pass within a given window before requests are rejected. + * @example 10 + */ + limit: number; + /** + * @description The duration for each ratelimit window in milliseconds. + * @example 1000 + */ + duration: number; + }[]; }; }; }; @@ -3988,22 +4005,22 @@ export interface operations { [key: string]: unknown; }; ratelimits: { - /** - * @description The name of this limit. - * @example tokens - */ - name: string; - /** - * @description How many requests may pass within a given window before requests are rejected. - * @example 10 - */ - limit: number; - /** - * @description The duration for each ratelimit window in milliseconds. - * @example 1000 - */ - duration: number; - }[]; + /** + * @description The name of this limit. + * @example tokens + */ + name: string; + /** + * @description How many requests may pass within a given window before requests are rejected. + * @example 10 + */ + limit: number; + /** + * @description The duration for each ratelimit window in milliseconds. + * @example 1000 + */ + duration: number; + }[]; }; }; }; @@ -4365,7 +4382,15 @@ export interface operations { * @example NOT_FOUND * @enum {string} */ - code?: "NOT_FOUND" | "FORBIDDEN" | "USAGE_EXCEEDED" | "RATE_LIMITED" | "UNAUTHORIZED" | "DISABLED" | "INSUFFICIENT_PERMISSIONS" | "EXPIRED"; + code?: + | "NOT_FOUND" + | "FORBIDDEN" + | "USAGE_EXCEEDED" + | "RATE_LIMITED" + | "UNAUTHORIZED" + | "DISABLED" + | "INSUFFICIENT_PERMISSIONS" + | "EXPIRED"; }; }; }; From 962324e2d574136134460188d3edad5edfc858de Mon Sep 17 00:00:00 2001 From: Mike Silva Date: Wed, 9 Oct 2024 15:29:47 -0700 Subject: [PATCH 23/29] Fixed bucket cache audit logs --- apps/api/src/pkg/testutil/harness.ts | 2 +- apps/api/src/routes/schema.ts | 5 +- .../[apiId]/keys/[keyAuthId]/new/client.tsx | 4 +- apps/workflows/jobs/index.ts | 1 - apps/workflows/jobs/refill-daily.ts | 70 +- apps/www/app/feed.xml/route.ts | 6 +- packages/api/src/openapi.d.ts | 1133 +++++++++-------- 7 files changed, 635 insertions(+), 586 deletions(-) diff --git a/apps/api/src/pkg/testutil/harness.ts b/apps/api/src/pkg/testutil/harness.ts index 541a0e4871..71d8e5a576 100644 --- a/apps/api/src/pkg/testutil/harness.ts +++ b/apps/api/src/pkg/testutil/harness.ts @@ -345,4 +345,4 @@ export abstract class Harness { await this.db.primary.insert(schema.keyAuth).values(this.resources.userKeyAuth); await this.db.primary.insert(schema.apis).values(this.resources.userApi); } -} \ No newline at end of file +} diff --git a/apps/api/src/routes/schema.ts b/apps/api/src/routes/schema.ts index d0c8ebc6ef..a9d4d4b14b 100644 --- a/apps/api/src/routes/schema.ts +++ b/apps/api/src/routes/schema.ts @@ -59,7 +59,8 @@ export const keySchema = z refill: z .object({ interval: z.enum(["daily", "monthly"]).openapi({ - description: "Determines the rate at which verifications will be refilled. When 'daily' is set for 'interval' 'refillDay' will be set to null.", + description: + "Determines the rate at which verifications will be refilled. When 'daily' is set for 'interval' 'refillDay' will be set to null.", example: "daily", }), amount: z.number().int().openapi({ @@ -69,7 +70,7 @@ export const keySchema = z refillDay: z.number().min(1).max(31).default(1).nullable().openapi({ description: "The day verifications will refill each month, when interval is set to 'monthly'. Value is not zero-indexed making 1 the first day of the month. If left blank it will default to the first day of the month. When 'daily' is set for 'interval' 'refillDay' will be set to null.", - example: 15, + example: 15, }), lastRefillAt: z.number().int().optional().openapi({ description: "The unix timestamp in miliseconds when the key was last refilled.", diff --git a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/new/client.tsx b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/new/client.tsx index c59bcd5ca2..c8fd698234 100644 --- a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/new/client.tsx +++ b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/new/client.tsx @@ -654,8 +654,8 @@ export const CreateKey: React.FC = ({ apiId, keyAuthId, defaultBytes, def ( diff --git a/apps/workflows/jobs/index.ts b/apps/workflows/jobs/index.ts index 0faac6979f..4b574e1e08 100644 --- a/apps/workflows/jobs/index.ts +++ b/apps/workflows/jobs/index.ts @@ -1,4 +1,3 @@ // export all your job files here export * from "./refill-daily"; - diff --git a/apps/workflows/jobs/refill-daily.ts b/apps/workflows/jobs/refill-daily.ts index 49674e16d3..2882b602e0 100644 --- a/apps/workflows/jobs/refill-daily.ts +++ b/apps/workflows/jobs/refill-daily.ts @@ -17,8 +17,14 @@ client.defineJob({ const lastDayOfMonth = new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate(); const today = date.getUTCDate(); const db = connectDatabase(); - - + const BUCKET_NAME = "unkey_mutations"; + + type Key = `${string}::${string}`; + + type BucketId = string; + + const bucketCache = new Map(); + // If refillDay is after last day of month, refillDay will be today. const keys = await db.query.keys.findMany({ where: (table, { isNotNull, isNull, and, gt, or, eq }) => { @@ -26,35 +32,53 @@ client.defineJob({ isNull(table.deletedAt), isNotNull(table.refillAmount), gt(table.refillAmount, table.remaining), - or( - isNull(table.refillDay), - eq(table.refillDay, today) - ) + or(isNull(table.refillDay), eq(table.refillDay, today)), ); - + if (today === lastDayOfMonth) { - return and( - baseConditions, - gt(table.refillDay, today) - ); + return and(baseConditions, gt(table.refillDay, today)); } return baseConditions; - } - }) + }, + }); io.logger.info(`found ${keys.length} keys with refill set for today`); for (const key of keys) { - const bucket = await io.runTask(`get bucket for ${key.workspaceId}`, async () => { - return await db.query.auditLogBucket.findFirst({ + const cacheKey: Key = `${key.workspaceId}::${BUCKET_NAME}`; + + let bucketId = ""; + + const cachedBucketId = bucketCache.get(cacheKey); + + if (cachedBucketId) { + bucketId = cachedBucketId; + } else { + const bucket = await db.query.auditLogBucket.findFirst({ where: (table, { eq, and }) => - and(eq(table.workspaceId, key.workspaceId), eq(table.name, "unkey_mutations")), + and(eq(table.workspaceId, key.workspaceId), eq(table.name, BUCKET_NAME)), + + columns: { + id: true, + }, }); - }); - if (!bucket) { - io.logger.error(`bucket for ${key.workspaceId} does not exist`); - continue; + + if (bucket) { + bucketId = bucket.id; + } else { + bucketId = newId("auditLogBucket"); + + await db.insert(schema.auditLogBucket).values({ + id: bucketId, + + workspaceId: key.workspaceId, + + name: BUCKET_NAME, + }); + } } + bucketCache.set(cacheKey, bucketId); + await io.runTask(`refill for ${key.id}`, async () => { await db.transaction(async (tx) => { await tx @@ -69,7 +93,7 @@ client.defineJob({ await tx.insert(schema.auditLog).values({ id: auditLogId, workspaceId: key.workspaceId, - bucketId: bucket.id, + bucketId: bucketId, time: Date.now(), event: "key.update", actorId: "trigger", @@ -81,7 +105,7 @@ client.defineJob({ type: "workspace", id: key.workspaceId, workspaceId: key.workspaceId, - bucketId: bucket.id, + bucketId: bucketId, auditLogId, displayName: `workspace ${key.workspaceId}`, }, @@ -89,7 +113,7 @@ client.defineJob({ type: "key", id: key.id, workspaceId: key.workspaceId, - bucketId: bucket.id, + bucketId: bucketId, auditLogId, displayName: `key ${key.id}`, }, diff --git a/apps/www/app/feed.xml/route.ts b/apps/www/app/feed.xml/route.ts index cdca05473f..56cd3e993f 100644 --- a/apps/www/app/feed.xml/route.ts +++ b/apps/www/app/feed.xml/route.ts @@ -1,11 +1,11 @@ -import RSS from "rss"; -import { type Post, allPosts } from "content-collections"; import { authors } from "@/content/blog/authors"; +import { type Post, allPosts } from "content-collections"; +import RSS from "rss"; const feed = new RSS({ title: "Unkey", description: "Open Source API Development platform", site_url: "https://unkey.com", - feed_url: `https://unkey.com/feed.xml`, + feed_url: "https://unkey.com/feed.xml", copyright: `${new Date().getFullYear()} Unkey`, language: "en", pubDate: new Date(), diff --git a/packages/api/src/openapi.d.ts b/packages/api/src/openapi.d.ts index b8908d1d73..ce8cfafe72 100644 --- a/packages/api/src/openapi.d.ts +++ b/packages/api/src/openapi.d.ts @@ -3,11 +3,14 @@ * Do not make direct changes to the file. */ - /** OneOf type helpers */ type Without = { [P in Exclude]?: never }; -type XOR = (T | U) extends object ? (Without & U) | (Without & T) : T | U; -type OneOf = T extends [infer Only] ? Only : T extends [infer A, infer B, ...infer Rest] ? OneOf<[XOR, ...Rest]> : never; +type XOR = T | U extends object ? (Without & U) | (Without & T) : T | U; +type OneOf = T extends [infer Only] + ? Only + : T extends [infer A, infer B, ...infer Rest] + ? OneOf<[XOR, ...Rest]> + : never; export interface paths { "/v1/liveness": { @@ -521,7 +524,16 @@ export interface components { * * @enum {string} */ - code: "VALID" | "NOT_FOUND" | "FORBIDDEN" | "USAGE_EXCEEDED" | "RATE_LIMITED" | "UNAUTHORIZED" | "DISABLED" | "INSUFFICIENT_PERMISSIONS" | "EXPIRED"; + code: + | "VALID" + | "NOT_FOUND" + | "FORBIDDEN" + | "USAGE_EXCEEDED" + | "RATE_LIMITED" + | "UNAUTHORIZED" + | "DISABLED" + | "INSUFFICIENT_PERMISSIONS" + | "EXPIRED"; /** @description Sets the key to be enabled or disabled. Disabled keys will not verify. */ enabled?: boolean; /** @@ -547,11 +559,18 @@ export interface components { }; }; /** @description A query for which permissions you require */ - PermissionQuery: OneOf<[string, { - and: components["schemas"]["PermissionQuery"][]; - }, { - or: components["schemas"]["PermissionQuery"][]; - }, null]>; + PermissionQuery: OneOf< + [ + string, + { + and: components["schemas"]["PermissionQuery"][]; + }, + { + or: components["schemas"]["PermissionQuery"][]; + }, + null, + ] + >; V1KeysVerifyKeyRequest: { /** * @description The id of the api where the key belongs to. This is optional for now but will be required soon. @@ -596,21 +615,21 @@ export interface components { * ] */ ratelimits?: { - /** - * @description The name of the ratelimit. - * @example tokens - */ - name: string; - /** - * @description Optionally override how expensive this operation is and how many tokens are deducted from the current limit. - * @default 1 - */ - cost?: number; - /** @description Optionally override the limit. */ - limit?: number; - /** @description Optionally override the ratelimit window duration. */ - duration?: number; - }[]; + /** + * @description The name of the ratelimit. + * @example tokens + */ + name: string; + /** + * @description Optionally override how expensive this operation is and how many tokens are deducted from the current limit. + * @default 1 + */ + cost?: number; + /** @description Optionally override the limit. */ + limit?: number; + /** @description Optionally override the ratelimit window duration. */ + duration?: number; + }[]; }; ErrDeleteProtected: { error: { @@ -636,8 +655,7 @@ export interface components { }; }; responses: never; - parameters: { - }; + parameters: {}; requestBodies: never; headers: never; pathItems: never; @@ -648,7 +666,6 @@ export type $defs = Record; export type external = Record; export interface operations { - "v1.liveness": { responses: { /** @description The configured services and their status */ @@ -1293,7 +1310,7 @@ export interface operations { * "refillInterval": 60 * } */ - ratelimit?: ({ + ratelimit?: { /** * @deprecated * @description Fast ratelimiting doesn't add latency, while consistent ratelimiting is more accurate. @@ -1325,7 +1342,7 @@ export interface operations { * This field will become required in a future version. */ duration?: number; - }) | null; + } | null; /** * @description The number of requests that can be made with this key before it becomes invalid. Set `null` to disable. * @example 1000 @@ -1338,7 +1355,7 @@ export interface operations { * "amount": 100 * } */ - refill?: ({ + refill?: { /** * @description Unkey will automatically refill verifications at the set interval. If null is used the refill functionality will be removed from the key. * @enum {string} @@ -1346,7 +1363,7 @@ export interface operations { interval: "daily" | "monthly"; /** @description The amount of verifications to refill for each occurrence is determined individually for each key. */ amount: number; - }) | null; + } | null; /** * @description Set if key is enabled or disabled. If disabled, the key cannot be used to verify. * @example true @@ -1369,16 +1386,16 @@ export interface operations { * ] */ roles?: { - /** @description The id of the role. Provide either `id` or `name`. If both are provided `id` is used. */ - id?: string; - /** @description Identify the role via its name. Provide either `id` or `name`. If both are provided `id` is used. */ - name?: string; - /** - * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. - * Autocreating roles requires your root key to have the `rbac.*.create_role` permission, otherwise the request will get rejected - */ - create?: boolean; - }[]; + /** @description The id of the role. Provide either `id` or `name`. If both are provided `id` is used. */ + id?: string; + /** @description Identify the role via its name. Provide either `id` or `name`. If both are provided `id` is used. */ + name?: string; + /** + * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. + * Autocreating roles requires your root key to have the `rbac.*.create_role` permission, otherwise the request will get rejected + */ + create?: boolean; + }[]; /** * @description The permissions you want to set for this key. This overwrites all existing permissions. * Setting permissions requires the `rbac.*.add_permission_to_key` permission. @@ -1396,16 +1413,16 @@ export interface operations { * ] */ permissions?: { - /** @description The id of the permission. Provide either `id` or `name`. If both are provided `id` is used. */ - id?: string; - /** @description Identify the permission via its name. Provide either `id` or `name`. If both are provided `id` is used. */ - name?: string; - /** - * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. - * Autocreating permissions requires your root key to have the `rbac.*.create_permission` permission, otherwise the request will get rejected - */ - create?: boolean; - }[]; + /** @description The id of the permission. Provide either `id` or `name`. If both are provided `id` is used. */ + id?: string; + /** @description Identify the permission via its name. Provide either `id` or `name`. If both are provided `id` is used. */ + name?: string; + /** + * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. + * Autocreating permissions requires your root key to have the `rbac.*.create_permission` permission, otherwise the request will get rejected + */ + create?: boolean; + }[]; }; }; }; @@ -1555,27 +1572,27 @@ export interface operations { content: { "application/json": { verifications: { - /** - * @description The timestamp of the usage data - * @example 1620000000000 - */ - time: number; - /** - * @description The number of successful requests - * @example 100 - */ - success: number; - /** - * @description The number of requests that were rate limited - * @example 10 - */ - rateLimited: number; - /** - * @description The number of requests that exceeded the usage limit - * @example 0 - */ - usageExceeded: number; - }[]; + /** + * @description The timestamp of the usage data + * @example 1620000000000 + */ + time: number; + /** + * @description The number of successful requests + * @example 100 + */ + success: number; + /** + * @description The number of requests that were rate limited + * @example 10 + */ + rateLimited: number; + /** + * @description The number of requests that exceeded the usage limit + * @example 0 + */ + usageExceeded: number; + }[]; }; }; }; @@ -1631,16 +1648,16 @@ export interface operations { keyId: string; /** @description The permissions you want to add to this key */ permissions: { - /** @description The id of the permission. Provide either `id` or `name`. If both are provided `id` is used. */ - id?: string; - /** @description Identify the permission via its name. Provide either `id` or `name`. If both are provided `id` is used. */ - name?: string; - /** - * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. - * Autocreating permissions requires your root key to have the `rbac.*.create_permission` permission, otherwise the request will get rejected - */ - create?: boolean; - }[]; + /** @description The id of the permission. Provide either `id` or `name`. If both are provided `id` is used. */ + id?: string; + /** @description Identify the permission via its name. Provide either `id` or `name`. If both are provided `id` is used. */ + name?: string; + /** + * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. + * Autocreating permissions requires your root key to have the `rbac.*.create_permission` permission, otherwise the request will get rejected + */ + create?: boolean; + }[]; }; }; }; @@ -1649,17 +1666,17 @@ export interface operations { 200: { content: { "application/json": { - /** - * @description The id of the permission. This is used internally - * @example perm_123 - */ - id: string; - /** - * @description The name of the permission - * @example dns.record.create - */ - name: string; - }[]; + /** + * @description The id of the permission. This is used internally + * @example perm_123 + */ + id: string; + /** + * @description The name of the permission + * @example dns.record.create + */ + name: string; + }[]; }; }; /** @description The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). */ @@ -1724,11 +1741,11 @@ export interface operations { * ] */ permissions: { - /** @description The id of the permission. Provide either `id` or `name`. If both are provided `id` is used. */ - id?: string; - /** @description Identify the permission via its name. Provide either `id` or `name`. If both are provided `id` is used. */ - name?: string; - }[]; + /** @description The id of the permission. Provide either `id` or `name`. If both are provided `id` is used. */ + id?: string; + /** @description Identify the permission via its name. Provide either `id` or `name`. If both are provided `id` is used. */ + name?: string; + }[]; }; }; }; @@ -1806,16 +1823,16 @@ export interface operations { * ] */ permissions: { - /** @description The id of the permission. Provide either `id` or `name`. If both are provided `id` is used. */ - id?: string; - /** @description Identify the permission via its name. Provide either `id` or `name`. If both are provided `id` is used. */ - name?: string; - /** - * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. - * Autocreating permissions requires your root key to have the `rbac.*.create_permission` permission, otherwise the request will get rejected - */ - create?: boolean; - }[]; + /** @description The id of the permission. Provide either `id` or `name`. If both are provided `id` is used. */ + id?: string; + /** @description Identify the permission via its name. Provide either `id` or `name`. If both are provided `id` is used. */ + name?: string; + /** + * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. + * Autocreating permissions requires your root key to have the `rbac.*.create_permission` permission, otherwise the request will get rejected + */ + create?: boolean; + }[]; }; }; }; @@ -1824,17 +1841,17 @@ export interface operations { 200: { content: { "application/json": { - /** - * @description The id of the permission. This is used internally - * @example perm_123 - */ - id: string; - /** - * @description The name of the permission - * @example dns.record.create - */ - name: string; - }[]; + /** + * @description The id of the permission. This is used internally + * @example perm_123 + */ + id: string; + /** + * @description The name of the permission + * @example dns.record.create + */ + name: string; + }[]; }; }; /** @description The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). */ @@ -1904,16 +1921,16 @@ export interface operations { * ] */ roles: { - /** @description The id of the role. Provide either `id` or `name`. If both are provided `id` is used. */ - id?: string; - /** @description Identify the role via its name. Provide either `id` or `name`. If both are provided `id` is used. */ - name?: string; - /** - * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. - * Autocreating roles requires your root key to have the `rbac.*.create_role` permission, otherwise the request will get rejected - */ - create?: boolean; - }[]; + /** @description The id of the role. Provide either `id` or `name`. If both are provided `id` is used. */ + id?: string; + /** @description Identify the role via its name. Provide either `id` or `name`. If both are provided `id` is used. */ + name?: string; + /** + * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. + * Autocreating roles requires your root key to have the `rbac.*.create_role` permission, otherwise the request will get rejected + */ + create?: boolean; + }[]; }; }; }; @@ -1922,17 +1939,17 @@ export interface operations { 200: { content: { "application/json": { - /** - * @description The id of the role. This is used internally - * @example role_123 - */ - id: string; - /** - * @description The name of the role - * @example dns.record.create - */ - name: string; - }[]; + /** + * @description The id of the role. This is used internally + * @example role_123 + */ + id: string; + /** + * @description The name of the role + * @example dns.record.create + */ + name: string; + }[]; }; }; /** @description The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). */ @@ -1997,11 +2014,11 @@ export interface operations { * ] */ roles: { - /** @description The id of the role. Provide either `id` or `name`. If both are provided `id` is used. */ - id?: string; - /** @description Identify the role via its name. Provide either `id` or `name`. If both are provided `id` is used. */ - name?: string; - }[]; + /** @description The id of the role. Provide either `id` or `name`. If both are provided `id` is used. */ + id?: string; + /** @description Identify the role via its name. Provide either `id` or `name`. If both are provided `id` is used. */ + name?: string; + }[]; }; }; }; @@ -2079,16 +2096,16 @@ export interface operations { * ] */ roles: { - /** @description The id of the role. Provide either `id` or `name`. If both are provided `id` is used. */ - id?: string; - /** @description Identify the role via its name. Provide either `id` or `name`. If both are provided `id` is used. */ - name?: string; - /** - * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. - * Autocreating roles requires your root key to have the `rbac.*.create_role` permission, otherwise the request will get rejected - */ - create?: boolean; - }[]; + /** @description The id of the role. Provide either `id` or `name`. If both are provided `id` is used. */ + id?: string; + /** @description Identify the role via its name. Provide either `id` or `name`. If both are provided `id` is used. */ + name?: string; + /** + * @description Set to true to automatically create the permissions they do not exist yet. Only works when specifying `name`. + * Autocreating roles requires your root key to have the `rbac.*.create_role` permission, otherwise the request will get rejected + */ + create?: boolean; + }[]; }; }; }; @@ -2097,17 +2114,17 @@ export interface operations { 200: { content: { "application/json": { - /** - * @description The id of the role. This is used internally - * @example role_123 - */ - id: string; - /** - * @description The name of the role - * @example dns.record.create - */ - name: string; - }[]; + /** + * @description The id of the role. This is used internally + * @example role_123 + */ + id: string; + /** + * @description The name of the role + * @example dns.record.create + */ + name: string; + }[]; }; }; /** @description The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). */ @@ -2556,26 +2573,26 @@ export interface operations { * ] */ resources?: { - /** - * @description The type of resource - * @example organization - */ - type: string; - /** - * @description The unique identifier for the resource - * @example org_123 - */ - id: string; - /** - * @description A human readable name for this resource - * @example unkey - */ - name?: string; - /** @description Attach any metadata to this resources */ - meta?: { - [key: string]: unknown; - }; - }[]; + /** + * @description The type of resource + * @example organization + */ + type: string; + /** + * @description The unique identifier for the resource + * @example org_123 + */ + id: string; + /** + * @description A human readable name for this resource + * @example unkey + */ + name?: string; + /** @description Attach any metadata to this resources */ + meta?: { + [key: string]: unknown; + }; + }[]; }; }; }; @@ -2653,33 +2670,245 @@ export interface operations { "v1.migrations.createKeys": { requestBody: { content: { - "application/json": ({ - /** - * @description Choose an `API` where this key should be created. - * @example api_123 - */ - apiId: string; + "application/json": { + /** + * @description Choose an `API` where this key should be created. + * @example api_123 + */ + apiId: string; + /** + * @description To make it easier for your users to understand which product an api key belongs to, you can add prefix them. + * + * For example Stripe famously prefixes their customer ids with cus_ or their api keys with sk_live_. + * + * The underscore is automatically added if you are defining a prefix, for example: "prefix": "abc" will result in a key like abc_xxxxxxxxx + */ + prefix?: string; + /** + * @description The name for your Key. This is not customer facing. + * @example my key + */ + name?: string; + /** @description The raw key in plaintext. If provided, unkey encrypts this value and stores it securely. Provide either `hash` or `plaintext` */ + plaintext?: string; + /** @description Provide either `hash` or `plaintext` */ + hash?: { + /** @description The hashed and encoded key */ + value: string; /** - * @description To make it easier for your users to understand which product an api key belongs to, you can add prefix them. - * - * For example Stripe famously prefixes their customer ids with cus_ or their api keys with sk_live_. - * - * The underscore is automatically added if you are defining a prefix, for example: "prefix": "abc" will result in a key like abc_xxxxxxxxx + * @description The algorithm for hashing and encoding, currently only sha256 and base64 are supported + * @enum {string} */ - prefix?: string; + variant: "sha256_base64"; + }; + /** + * @description The first 4 characters of the key. If a prefix is used, it should be the prefix plus 4 characters. + * @example unkey_32kq + */ + start?: string; + /** + * @description Your user’s Id. This will provide a link between Unkey and your customer record. + * When validating a key, we will return this back to you, so you can clearly identify your user from their api key. + * @example team_123 + */ + ownerId?: string; + /** + * @description This is a place for dynamic meta data, anything that feels useful for you should go here + * @example { + * "billingTier": "PRO", + * "trialEnds": "2023-06-16T17:16:37.161Z" + * } + */ + meta?: { + [key: string]: unknown; + }; + /** + * @description A list of roles that this key should have. If the role does not exist, an error is thrown + * @example [ + * "admin", + * "finance" + * ] + */ + roles?: string[]; + /** + * @description A list of permissions that this key should have. If the permission does not exist, an error is thrown + * @example [ + * "domains.create_record", + * "say_hello" + * ] + */ + permissions?: string[]; + /** + * @description You can auto expire keys by providing a unix timestamp in milliseconds. Once Keys expire they will automatically be disabled and are no longer valid unless you enable them again. + * @example 1623869797161 + */ + expires?: number; + /** + * @description You can limit the number of requests a key can make. Once a key reaches 0 remaining requests, it will automatically be disabled and is no longer valid unless you update it. + * @example 1000 + */ + remaining?: number; + /** + * @description Unkey enables you to refill verifications for each key at regular intervals. + * @example { + * "interval": "daily", + * "amount": 100 + * } + */ + refill?: { /** - * @description The name for your Key. This is not customer facing. - * @example my key + * @description Unkey will automatically refill verifications at the set interval. + * @enum {string} */ - name?: string; - /** @description The raw key in plaintext. If provided, unkey encrypts this value and stores it securely. Provide either `hash` or `plaintext` */ - plaintext?: string; - /** @description Provide either `hash` or `plaintext` */ - hash?: { - /** @description The hashed and encoded key */ - value: string; - /** - * @description The algorithm for hashing and encoding, currently only sha256 and base64 are supported + interval: "daily" | "monthly"; + /** @description The number of verifications to refill for each occurrence is determined individually for each key. */ + amount: number; + }; + /** + * @description Unkey comes with per-key ratelimiting out of the box. + * @example { + * "type": "fast", + * "limit": 10, + * "refillRate": 1, + * "refillInterval": 60 + * } + */ + ratelimit?: { + /** + * @description Async will return a response immediately, lowering latency at the cost of accuracy. + * @default false + */ + async?: boolean; + /** + * @deprecated + * @description Fast ratelimiting doesn't add latency, while consistent ratelimiting is more accurate. + * @default fast + * @enum {string} + */ + type?: "fast" | "consistent"; + /** @description The total amount of burstable requests. */ + limit: number; + /** + * @deprecated + * @description How many tokens to refill during each refillInterval. + */ + refillRate: number; + /** + * @deprecated + * @description Determines the speed at which tokens are refilled, in milliseconds. + */ + refillInterval: number; + }; + /** + * @description Sets if key is enabled or disabled. Disabled keys are not valid. + * @default true + * @example false + */ + enabled?: boolean; + /** + * @description Environments allow you to divide your keyspace. + * + * Some applications like Stripe, Clerk, WorkOS and others have a concept of "live" and "test" keys to + * give the developer a way to develop their own application without the risk of modifying real world + * resources. + * + * When you set an environment, we will return it back to you when validating the key, so you can + * handle it correctly. + */ + environment?: string; + }[]; + }; + }; + responses: { + /** @description The key ids of all created keys */ + 200: { + content: { + "application/json": { + /** + * @description The ids of the keys. This is not a secret and can be stored as a reference if you wish. You need the keyId to update or delete a key later. + * @example [ + * "key_123", + * "key_456" + * ] + */ + keyIds: string[]; + }; + }; + }; + /** @description The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). */ + 400: { + content: { + "application/json": components["schemas"]["ErrBadRequest"]; + }; + }; + /** @description Although the HTTP standard specifies "unauthorized", semantically this response means "unauthenticated". That is, the client must authenticate itself to get the requested response. */ + 401: { + content: { + "application/json": components["schemas"]["ErrUnauthorized"]; + }; + }; + /** @description The client does not have access rights to the content; that is, it is unauthorized, so the server is refusing to give the requested resource. Unlike 401 Unauthorized, the client's identity is known to the server. */ + 403: { + content: { + "application/json": components["schemas"]["ErrForbidden"]; + }; + }; + /** @description The server cannot find the requested resource. In the browser, this means the URL is not recognized. In an API, this can also mean that the endpoint is valid but the resource itself does not exist. Servers may also send this response instead of 403 Forbidden to hide the existence of a resource from an unauthorized client. This response code is probably the most well known due to its frequent occurrence on the web. */ + 404: { + content: { + "application/json": components["schemas"]["ErrNotFound"]; + }; + }; + /** @description This response is sent when a request conflicts with the current state of the server. */ + 409: { + content: { + "application/json": components["schemas"]["ErrConflict"]; + }; + }; + /** @description The user has sent too many requests in a given amount of time ("rate limiting") */ + 429: { + content: { + "application/json": components["schemas"]["ErrTooManyRequests"]; + }; + }; + /** @description The server has encountered a situation it does not know how to handle. */ + 500: { + content: { + "application/json": components["schemas"]["ErrInternalServerError"]; + }; + }; + }; + }; + "v1.migrations.enqueueKeys": { + requestBody: { + content: { + "application/json": { + /** @description Contact support@unkey.dev to receive your migration id. */ + migrationId: string; + /** @description The id of the api, you want to migrate keys to */ + apiId: string; + keys: { + /** + * @description To make it easier for your users to understand which product an api key belongs to, you can add prefix them. + * + * For example Stripe famously prefixes their customer ids with cus_ or their api keys with sk_live_. + * + * The underscore is automatically added if you are defining a prefix, for example: "prefix": "abc" will result in a key like abc_xxxxxxxxx + */ + prefix?: string; + /** + * @description The name for your Key. This is not customer facing. + * @example my key + */ + name?: string; + /** @description The raw key in plaintext. If provided, unkey encrypts this value and stores it securely. Provide either `hash` or `plaintext` */ + plaintext?: string; + /** @description Provide either `hash` or `plaintext` */ + hash?: { + /** @description The hashed and encoded key */ + value: string; + /** + * @description The algorithm for hashing and encoding, currently only sha256 and base64 are supported * @enum {string} */ variant: "sha256_base64"; @@ -2748,39 +2977,43 @@ export interface operations { amount: number; }; /** - * @description Unkey comes with per-key ratelimiting out of the box. + * @description Unkey comes with per-key fixed-window ratelimiting out of the box. * @example { * "type": "fast", * "limit": 10, - * "refillRate": 1, - * "refillInterval": 60 + * "duration": 60000 * } */ ratelimit?: { /** * @description Async will return a response immediately, lowering latency at the cost of accuracy. - * @default false + * @default true */ async?: boolean; /** * @deprecated - * @description Fast ratelimiting doesn't add latency, while consistent ratelimiting is more accurate. + * @description Deprecated, use `async`. Fast ratelimiting doesn't add latency, while consistent ratelimiting is more accurate. * @default fast * @enum {string} */ type?: "fast" | "consistent"; - /** @description The total amount of burstable requests. */ + /** @description The total amount of requests in a given interval. */ limit: number; + /** + * @description The window duration in milliseconds + * @example 60000 + */ + duration: number; /** * @deprecated * @description How many tokens to refill during each refillInterval. */ - refillRate: number; + refillRate?: number; /** * @deprecated - * @description Determines the speed at which tokens are refilled, in milliseconds. + * @description The refill timeframe, in milliseconds. */ - refillInterval: number; + refillInterval?: number; }; /** * @description Sets if key is enabled or disabled. Disabled keys are not valid. @@ -2799,223 +3032,7 @@ export interface operations { * handle it correctly. */ environment?: string; - })[]; - }; - }; - responses: { - /** @description The key ids of all created keys */ - 200: { - content: { - "application/json": { - /** - * @description The ids of the keys. This is not a secret and can be stored as a reference if you wish. You need the keyId to update or delete a key later. - * @example [ - * "key_123", - * "key_456" - * ] - */ - keyIds: string[]; - }; - }; - }; - /** @description The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). */ - 400: { - content: { - "application/json": components["schemas"]["ErrBadRequest"]; - }; - }; - /** @description Although the HTTP standard specifies "unauthorized", semantically this response means "unauthenticated". That is, the client must authenticate itself to get the requested response. */ - 401: { - content: { - "application/json": components["schemas"]["ErrUnauthorized"]; - }; - }; - /** @description The client does not have access rights to the content; that is, it is unauthorized, so the server is refusing to give the requested resource. Unlike 401 Unauthorized, the client's identity is known to the server. */ - 403: { - content: { - "application/json": components["schemas"]["ErrForbidden"]; - }; - }; - /** @description The server cannot find the requested resource. In the browser, this means the URL is not recognized. In an API, this can also mean that the endpoint is valid but the resource itself does not exist. Servers may also send this response instead of 403 Forbidden to hide the existence of a resource from an unauthorized client. This response code is probably the most well known due to its frequent occurrence on the web. */ - 404: { - content: { - "application/json": components["schemas"]["ErrNotFound"]; - }; - }; - /** @description This response is sent when a request conflicts with the current state of the server. */ - 409: { - content: { - "application/json": components["schemas"]["ErrConflict"]; - }; - }; - /** @description The user has sent too many requests in a given amount of time ("rate limiting") */ - 429: { - content: { - "application/json": components["schemas"]["ErrTooManyRequests"]; - }; - }; - /** @description The server has encountered a situation it does not know how to handle. */ - 500: { - content: { - "application/json": components["schemas"]["ErrInternalServerError"]; - }; - }; - }; - }; - "v1.migrations.enqueueKeys": { - requestBody: { - content: { - "application/json": { - /** @description Contact support@unkey.dev to receive your migration id. */ - migrationId: string; - /** @description The id of the api, you want to migrate keys to */ - apiId: string; - keys: ({ - /** - * @description To make it easier for your users to understand which product an api key belongs to, you can add prefix them. - * - * For example Stripe famously prefixes their customer ids with cus_ or their api keys with sk_live_. - * - * The underscore is automatically added if you are defining a prefix, for example: "prefix": "abc" will result in a key like abc_xxxxxxxxx - */ - prefix?: string; - /** - * @description The name for your Key. This is not customer facing. - * @example my key - */ - name?: string; - /** @description The raw key in plaintext. If provided, unkey encrypts this value and stores it securely. Provide either `hash` or `plaintext` */ - plaintext?: string; - /** @description Provide either `hash` or `plaintext` */ - hash?: { - /** @description The hashed and encoded key */ - value: string; - /** - * @description The algorithm for hashing and encoding, currently only sha256 and base64 are supported - * @enum {string} - */ - variant: "sha256_base64"; - }; - /** - * @description The first 4 characters of the key. If a prefix is used, it should be the prefix plus 4 characters. - * @example unkey_32kq - */ - start?: string; - /** - * @description Your user’s Id. This will provide a link between Unkey and your customer record. - * When validating a key, we will return this back to you, so you can clearly identify your user from their api key. - * @example team_123 - */ - ownerId?: string; - /** - * @description This is a place for dynamic meta data, anything that feels useful for you should go here - * @example { - * "billingTier": "PRO", - * "trialEnds": "2023-06-16T17:16:37.161Z" - * } - */ - meta?: { - [key: string]: unknown; - }; - /** - * @description A list of roles that this key should have. If the role does not exist, an error is thrown - * @example [ - * "admin", - * "finance" - * ] - */ - roles?: string[]; - /** - * @description A list of permissions that this key should have. If the permission does not exist, an error is thrown - * @example [ - * "domains.create_record", - * "say_hello" - * ] - */ - permissions?: string[]; - /** - * @description You can auto expire keys by providing a unix timestamp in milliseconds. Once Keys expire they will automatically be disabled and are no longer valid unless you enable them again. - * @example 1623869797161 - */ - expires?: number; - /** - * @description You can limit the number of requests a key can make. Once a key reaches 0 remaining requests, it will automatically be disabled and is no longer valid unless you update it. - * @example 1000 - */ - remaining?: number; - /** - * @description Unkey enables you to refill verifications for each key at regular intervals. - * @example { - * "interval": "daily", - * "amount": 100 - * } - */ - refill?: { - /** - * @description Unkey will automatically refill verifications at the set interval. - * @enum {string} - */ - interval: "daily" | "monthly"; - /** @description The number of verifications to refill for each occurrence is determined individually for each key. */ - amount: number; - }; - /** - * @description Unkey comes with per-key fixed-window ratelimiting out of the box. - * @example { - * "type": "fast", - * "limit": 10, - * "duration": 60000 - * } - */ - ratelimit?: { - /** - * @description Async will return a response immediately, lowering latency at the cost of accuracy. - * @default true - */ - async?: boolean; - /** - * @deprecated - * @description Deprecated, use `async`. Fast ratelimiting doesn't add latency, while consistent ratelimiting is more accurate. - * @default fast - * @enum {string} - */ - type?: "fast" | "consistent"; - /** @description The total amount of requests in a given interval. */ - limit: number; - /** - * @description The window duration in milliseconds - * @example 60000 - */ - duration: number; - /** - * @deprecated - * @description How many tokens to refill during each refillInterval. - */ - refillRate?: number; - /** - * @deprecated - * @description The refill timeframe, in milliseconds. - */ - refillInterval?: number; - }; - /** - * @description Sets if key is enabled or disabled. Disabled keys are not valid. - * @default true - * @example false - */ - enabled?: boolean; - /** - * @description Environments allow you to divide your keyspace. - * - * Some applications like Stripe, Clerk, WorkOS and others have a concept of "live" and "test" keys to - * give the developer a way to develop their own application without the risk of modifying real world - * resources. - * - * When you set an environment, we will return it back to you when validating the key, so you can - * handle it correctly. - */ - environment?: string; - })[]; + }[]; }; }; }; @@ -3286,22 +3303,22 @@ export interface operations { 200: { content: { "application/json": { - /** - * @description The id of the permission - * @example perm_123 - */ - id: string; - /** - * @description The name of the permission. - * @example domain.record.manager - */ - name: string; - /** - * @description The description of what this permission does. This is just for your team, your users will not see this. - * @example Can manage dns records - */ - description?: string; - }[]; + /** + * @description The id of the permission + * @example perm_123 + */ + id: string; + /** + * @description The name of the permission. + * @example domain.record.manager + */ + name: string; + /** + * @description The description of what this permission does. This is just for your team, your users will not see this. + * @example Can manage dns records + */ + description?: string; + }[]; }; }; /** @description The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). */ @@ -3564,22 +3581,22 @@ export interface operations { 200: { content: { "application/json": { - /** - * @description The id of the role - * @example role_1234 - */ - id: string; - /** - * @description The name of the role. - * @example domain.record.manager - */ - name: string; - /** - * @description The description of what this role does. This is just for your team, your users will not see this. - * @example Can manage dns records - */ - description?: string; - }[]; + /** + * @description The id of the role + * @example role_1234 + */ + id: string; + /** + * @description The name of the role. + * @example domain.record.manager + */ + name: string; + /** + * @description The description of what this role does. This is just for your team, your users will not see this. + * @example Can manage dns records + */ + description?: string; + }[]; }; }; /** @description The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing). */ @@ -3653,22 +3670,22 @@ export interface operations { * When verifying keys, you can specify which limits you want to use and all keys attached to this identity, will share the limits. */ ratelimits?: { - /** - * @description The name of this limit. You will need to use this again when verifying a key. - * @example tokens - */ - name: string; - /** - * @description How many requests may pass within a given window before requests are rejected. - * @example 10 - */ - limit: number; - /** - * @description The duration for each ratelimit window in milliseconds. - * @example 1000 - */ - duration: number; - }[]; + /** + * @description The name of this limit. You will need to use this again when verifying a key. + * @example tokens + */ + name: string; + /** + * @description How many requests may pass within a given window before requests are rejected. + * @example 10 + */ + limit: number; + /** + * @description The duration for each ratelimit window in milliseconds. + * @example 1000 + */ + duration: number; + }[]; }; }; }; @@ -3751,22 +3768,22 @@ export interface operations { }; /** @description When verifying keys, you can specify which limits you want to use and all keys attached to this identity, will share the limits. */ ratelimits: { - /** - * @description The name of this limit. You will need to use this again when verifying a key. - * @example tokens - */ - name: string; - /** - * @description How many requests may pass within a given window before requests are rejected. - * @example 10 - */ - limit: number; - /** - * @description The duration for each ratelimit window in milliseconds. - * @example 1000 - */ - duration: number; - }[]; + /** + * @description The name of this limit. You will need to use this again when verifying a key. + * @example tokens + */ + name: string; + /** + * @description How many requests may pass within a given window before requests are rejected. + * @example 10 + */ + limit: number; + /** + * @description The duration for each ratelimit window in milliseconds. + * @example 1000 + */ + duration: number; + }[]; }; }; }; @@ -3829,29 +3846,29 @@ export interface operations { "application/json": { /** @description A list of identities. */ identities: { - /** @description The id of this identity. Used to interact with unkey's API */ - id: string; - /** @description The id in your system */ - externalId: string; - /** @description When verifying keys, you can specify which limits you want to use and all keys attached to this identity, will share the limits. */ - ratelimits: { - /** - * @description The name of this limit. You will need to use this again when verifying a key. - * @example tokens - */ - name: string; - /** - * @description How many requests may pass within a given window before requests are rejected. - * @example 10 - */ - limit: number; - /** - * @description The duration for each ratelimit window in milliseconds. - * @example 1000 - */ - duration: number; - }[]; + /** @description The id of this identity. Used to interact with unkey's API */ + id: string; + /** @description The id in your system */ + externalId: string; + /** @description When verifying keys, you can specify which limits you want to use and all keys attached to this identity, will share the limits. */ + ratelimits: { + /** + * @description The name of this limit. You will need to use this again when verifying a key. + * @example tokens + */ + name: string; + /** + * @description How many requests may pass within a given window before requests are rejected. + * @example 10 + */ + limit: number; + /** + * @description The duration for each ratelimit window in milliseconds. + * @example 1000 + */ + duration: number; }[]; + }[]; /** * @description The cursor to use for the next page of results, if no cursor is returned, there are no more results * @example eyJrZXkiOiJrZXlfMTIzNCJ9 @@ -3944,22 +3961,22 @@ export interface operations { * When verifying keys, you can specify which limits you want to use and all keys attached to this identity, will share the limits. */ ratelimits?: { - /** - * @description The name of this limit. You will need to use this again when verifying a key. - * @example tokens - */ - name: string; - /** - * @description How many requests may pass within a given window before requests are rejected. - * @example 10 - */ - limit: number; - /** - * @description The duration for each ratelimit window in milliseconds. - * @example 1000 - */ - duration: number; - }[]; + /** + * @description The name of this limit. You will need to use this again when verifying a key. + * @example tokens + */ + name: string; + /** + * @description How many requests may pass within a given window before requests are rejected. + * @example 10 + */ + limit: number; + /** + * @description The duration for each ratelimit window in milliseconds. + * @example 1000 + */ + duration: number; + }[]; }; }; }; @@ -3988,22 +4005,22 @@ export interface operations { [key: string]: unknown; }; ratelimits: { - /** - * @description The name of this limit. - * @example tokens - */ - name: string; - /** - * @description How many requests may pass within a given window before requests are rejected. - * @example 10 - */ - limit: number; - /** - * @description The duration for each ratelimit window in milliseconds. - * @example 1000 - */ - duration: number; - }[]; + /** + * @description The name of this limit. + * @example tokens + */ + name: string; + /** + * @description How many requests may pass within a given window before requests are rejected. + * @example 10 + */ + limit: number; + /** + * @description The duration for each ratelimit window in milliseconds. + * @example 1000 + */ + duration: number; + }[]; }; }; }; @@ -4365,7 +4382,15 @@ export interface operations { * @example NOT_FOUND * @enum {string} */ - code?: "NOT_FOUND" | "FORBIDDEN" | "USAGE_EXCEEDED" | "RATE_LIMITED" | "UNAUTHORIZED" | "DISABLED" | "INSUFFICIENT_PERMISSIONS" | "EXPIRED"; + code?: + | "NOT_FOUND" + | "FORBIDDEN" + | "USAGE_EXCEEDED" + | "RATE_LIMITED" + | "UNAUTHORIZED" + | "DISABLED" + | "INSUFFICIENT_PERMISSIONS" + | "EXPIRED"; }; }; }; From 315b16d9d013d95028507f4c865763f23bbcbd81 Mon Sep 17 00:00:00 2001 From: Mike Silva Date: Thu, 10 Oct 2024 09:22:15 -0700 Subject: [PATCH 24/29] remove whitespace --- apps/workflows/jobs/refill-daily.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/apps/workflows/jobs/refill-daily.ts b/apps/workflows/jobs/refill-daily.ts index 2882b602e0..748f3bb279 100644 --- a/apps/workflows/jobs/refill-daily.ts +++ b/apps/workflows/jobs/refill-daily.ts @@ -20,9 +20,7 @@ client.defineJob({ const BUCKET_NAME = "unkey_mutations"; type Key = `${string}::${string}`; - type BucketId = string; - const bucketCache = new Map(); // If refillDay is after last day of month, refillDay will be today. @@ -45,18 +43,14 @@ client.defineJob({ io.logger.info(`found ${keys.length} keys with refill set for today`); for (const key of keys) { const cacheKey: Key = `${key.workspaceId}::${BUCKET_NAME}`; - let bucketId = ""; - const cachedBucketId = bucketCache.get(cacheKey); - if (cachedBucketId) { bucketId = cachedBucketId; } else { const bucket = await db.query.auditLogBucket.findFirst({ where: (table, { eq, and }) => and(eq(table.workspaceId, key.workspaceId), eq(table.name, BUCKET_NAME)), - columns: { id: true, }, @@ -66,19 +60,15 @@ client.defineJob({ bucketId = bucket.id; } else { bucketId = newId("auditLogBucket"); - await db.insert(schema.auditLogBucket).values({ id: bucketId, - workspaceId: key.workspaceId, - name: BUCKET_NAME, }); } } bucketCache.set(cacheKey, bucketId); - await io.runTask(`refill for ${key.id}`, async () => { await db.transaction(async (tx) => { await tx From 9870f06b02be5be41be2ca2911bf172223ea55d9 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Thu, 10 Oct 2024 16:58:55 +0000 Subject: [PATCH 25/29] [autofix.ci] apply automated fixes --- .../app/(app)/apis/[apiId]/keys/[keyAuthId]/new/client.tsx | 1 - apps/workflows/jobs/refill-daily.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/new/client.tsx b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/new/client.tsx index c8fd698234..f940d81724 100644 --- a/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/new/client.tsx +++ b/apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/new/client.tsx @@ -18,7 +18,6 @@ import { FormMessage, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; import { Select, SelectContent, diff --git a/apps/workflows/jobs/refill-daily.ts b/apps/workflows/jobs/refill-daily.ts index 748f3bb279..4800b88115 100644 --- a/apps/workflows/jobs/refill-daily.ts +++ b/apps/workflows/jobs/refill-daily.ts @@ -1,4 +1,4 @@ -import { connectDatabase, eq, lte, schema } from "@/lib/db"; +import { connectDatabase, eq, schema } from "@/lib/db"; import { client } from "@/trigger"; import { cronTrigger } from "@trigger.dev/sdk"; import { newId } from "@unkey/id"; From 061b99596004812947aa46277796dcc374006a72 Mon Sep 17 00:00:00 2001 From: Michael Silva Date: Mon, 14 Oct 2024 21:25:48 -0400 Subject: [PATCH 26/29] revert to working --- apps/workflows/jobs/refill-daily.ts | 31 +---------------------------- 1 file changed, 1 insertion(+), 30 deletions(-) diff --git a/apps/workflows/jobs/refill-daily.ts b/apps/workflows/jobs/refill-daily.ts index c56c714c2c..37a0ad44d7 100644 --- a/apps/workflows/jobs/refill-daily.ts +++ b/apps/workflows/jobs/refill-daily.ts @@ -45,13 +45,6 @@ client.defineJob({ const cacheKey: Key = `${key.workspaceId}::${BUCKET_NAME}`; let bucketId = ""; const cachedBucketId = bucketCache.get(cacheKey); - if (cachedBucketId) { - bucketId = cachedBucketId; - } else { - const bucket = await db.query.auditLogBucket.findFirst({ - const cacheKey: Key = `${key.workspaceId}::${BUCKET_NAME}`; - let bucketId = ""; - const cachedBucketId = bucketCache.get(cacheKey); if (cachedBucketId) { bucketId = cachedBucketId; } else { @@ -75,25 +68,6 @@ client.defineJob({ } } - bucketCache.set(cacheKey, bucketId); - and(eq(table.workspaceId, key.workspaceId), eq(table.name, BUCKET_NAME)), - columns: { - id: true, - }, - }); - - if (bucket) { - bucketId = bucket.id; - } else { - bucketId = newId("auditLogBucket"); - await db.insert(schema.auditLogBucket).values({ - id: bucketId, - workspaceId: key.workspaceId, - name: BUCKET_NAME, - }); - } - } - bucketCache.set(cacheKey, bucketId); await io.runTask(`refill for ${key.id}`, async () => { await db.transaction(async (tx) => { @@ -110,7 +84,6 @@ client.defineJob({ id: auditLogId, workspaceId: key.workspaceId, bucketId: bucketId, - bucketId: bucketId, time: Date.now(), event: "key.update", actorId: "trigger", @@ -123,7 +96,6 @@ client.defineJob({ id: key.workspaceId, workspaceId: key.workspaceId, bucketId: bucketId, - bucketId: bucketId, auditLogId, displayName: `workspace ${key.workspaceId}`, }, @@ -132,7 +104,6 @@ client.defineJob({ id: key.id, workspaceId: key.workspaceId, bucketId: bucketId, - bucketId: bucketId, auditLogId, displayName: `key ${key.id}`, }, @@ -144,4 +115,4 @@ client.defineJob({ refillKeyIds: keys.map((k) => k.id), }; }, -}); +}); \ No newline at end of file From 7205bee21b529dbcb0e75c826c07181d839678ba Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 15 Oct 2024 11:33:18 +0000 Subject: [PATCH 27/29] [autofix.ci] apply automated fixes --- apps/workflows/jobs/refill-daily.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/workflows/jobs/refill-daily.ts b/apps/workflows/jobs/refill-daily.ts index 37a0ad44d7..4800b88115 100644 --- a/apps/workflows/jobs/refill-daily.ts +++ b/apps/workflows/jobs/refill-daily.ts @@ -115,4 +115,4 @@ client.defineJob({ refillKeyIds: keys.map((k) => k.id), }; }, -}); \ No newline at end of file +}); From e895a13d1b582b6d981b02e587179814bc8557d6 Mon Sep 17 00:00:00 2001 From: Michael Silva Date: Tue, 15 Oct 2024 10:49:41 -0400 Subject: [PATCH 28/29] Fix type --- apps/api/src/routes/v1_keys_updateKey.error.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/api/src/routes/v1_keys_updateKey.error.test.ts b/apps/api/src/routes/v1_keys_updateKey.error.test.ts index 3aa1590a33..396b0564cd 100644 --- a/apps/api/src/routes/v1_keys_updateKey.error.test.ts +++ b/apps/api/src/routes/v1_keys_updateKey.error.test.ts @@ -74,7 +74,7 @@ test("reject invalid refill config", async (t) => { error: { code: "BAD_REQUEST", docs: "https://unkey.dev/docs/api-reference/errors/code/BAD_REQUEST", - message: "Connot set 'refillDay' if 'interval' is 'daily'", + message: "Cannot set 'refillDay' if 'interval' is 'daily'", }, }); }); From d93ce6da69dbb75e55c20a31cd118d0d4d47adef Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 15 Oct 2024 15:11:49 +0000 Subject: [PATCH 29/29] [autofix.ci] apply automated fixes --- apps/api/src/routes/v1_keys_verifyKey.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/api/src/routes/v1_keys_verifyKey.ts b/apps/api/src/routes/v1_keys_verifyKey.ts index b8473deaa1..e80ce1201c 100644 --- a/apps/api/src/routes/v1_keys_verifyKey.ts +++ b/apps/api/src/routes/v1_keys_verifyKey.ts @@ -344,10 +344,10 @@ export const registerV1KeysVerifyKey = (app: App) => code: val.valid ? ("VALID" as const) : val.code, identity: val.identity ? { - id: val.identity.id, - externalId: val.identity.externalId, - meta: val.identity.meta ?? {}, - } + id: val.identity.id, + externalId: val.identity.externalId, + meta: val.identity.meta ?? {}, + } : undefined, }; c.executionCtx.waitUntil(