diff --git a/api/index.ts b/api/index.ts index 7a5b1d321c3..5edaed94086 100644 --- a/api/index.ts +++ b/api/index.ts @@ -4,11 +4,13 @@ import middie from '@fastify/middie'; import fastifySession from '@fastify/session'; import fastifyCookie from '@fastify/cookie'; import MongoStore from 'connect-mongo'; +import type { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'; import jwtAuthz from './plugins/fastify-jwt-authz'; import sessionAuth from './plugins/session-auth'; import { testRoutes } from './routes/test'; import { auth0Routes } from './routes/auth0'; +import { testValidatedRoutes } from './routes/validation-test'; import { testMiddleware } from './middleware'; import { @@ -24,7 +26,9 @@ import prismaPlugin from './db/prisma'; const fastify = Fastify({ logger: { level: NODE_ENV === 'development' ? 'debug' : 'fatal' } -}); +}).withTypeProvider(); + +export type FastifyInstanceWithTypeProvider = typeof fastify; fastify.get('/', async (_request, _reply) => { return { hello: 'world' }; @@ -61,6 +65,7 @@ const start = async () => { void fastify.register(testRoutes); void fastify.register(auth0Routes, { prefix: '/auth0' }); + void fastify.register(testValidatedRoutes); try { const port = Number(PORT); diff --git a/api/package.json b/api/package.json index 344a6f4af43..69daf3a2549 100644 --- a/api/package.json +++ b/api/package.json @@ -9,6 +9,7 @@ "@fastify/session": "^10.1.1", "@prisma/client": "4.10.1", "connect-mongo": "4.6.0", + "@sinclair/typebox": "0.25.24", "fastify": "4.14.1", "fastify-auth0-verify": "^1.0.0", "fastify-plugin": "^4.3.0", @@ -46,6 +47,7 @@ }, "version": "0.0.1", "devDependencies": { - "prisma": "4.10.1" + "prisma": "4.10.1", + "@fastify/type-provider-typebox": "2.4.0" } } diff --git a/api/routes/validation-test.ts b/api/routes/validation-test.ts new file mode 100644 index 00000000000..1726f9500c5 --- /dev/null +++ b/api/routes/validation-test.ts @@ -0,0 +1,61 @@ +import { Type } from '@sinclair/typebox'; + +import type { FastifyInstanceWithTypeProvider } from '..'; +import { responseSchema, subSchema } from '../schemas/example'; + +export const testValidatedRoutes = ( + fastify: FastifyInstanceWithTypeProvider, + _options: never, + done: (err?: Error) => void +): void => { + fastify.get( + '/route-with-validation', + { + schema: { + querystring: Type.Object({ + foo: Type.Number(), + bar: Type.String() + }) + } + }, + request => { + const { foo, bar } = request.query; + + // Eslint can use the types given by TypeBoxTypeProvider: + // eslint-disable-next-line @typescript-eslint/restrict-plus-operands + const fooBar = foo + bar; + return { foo, bar, fooBar }; + } + ); + + fastify.post( + '/route-with-validation-shared-schema', + { + schema: { + body: Type.Object({ + foo: Type.Number(), + bar: Type.String(), + sub: subSchema + }), + response: { + 200: responseSchema + } + } + }, + request => { + const { foo, bar } = request.body; + // The TypeProvider constrains this by requiring value: string and + // otherValue: boolean. Anything else is a type error. 'ignored' is + // neither type checked nor returned, so it's safe. 'optional' is + // self-explanatory. + return { + value: bar, + otherValue: !foo, + ignored: 'not in reply', + ...(foo == 2 && { optional: 'optional' }) + }; + } + ); + + done(); +}; diff --git a/api/schemas/example.ts b/api/schemas/example.ts new file mode 100644 index 00000000000..a2c3401cfb1 --- /dev/null +++ b/api/schemas/example.ts @@ -0,0 +1,31 @@ +import { Static, Type } from '@sinclair/typebox'; + +// The schema that TypeBox generates is compatible with ajv, e.g. the +// Type.Object call below puts the following object into subSchema. +/* +{ + type: 'object', + properties: { + bat: { type: 'number' }, + baz: { type: 'string' } + }, + required: ['bat', 'baz'] +} + */ + +export const subSchema = Type.Object({ + bat: Type.Integer(), + baz: Type.String() +}); + +export const responseSchema = Type.Object({ + value: Type.String(), + otherValue: Type.Boolean(), + optional: Type.Optional(Type.String()) +}); + +// The schema types would be the only code the client needs to import +// { value: string; otherValue: boolean; optional?: string | undefined;} +export type ResponseSchema = Static; +// { bat: number; baz: string; } +export type SubSchema = Static; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c7398a3b741..9c03c2ff77f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -118,7 +118,9 @@ importers: '@fastify/cookie': ^8.3.0 '@fastify/middie': '8.1' '@fastify/session': ^10.1.1 + '@fastify/type-provider-typebox': 2.4.0 '@prisma/client': 4.10.1 + '@sinclair/typebox': 0.25.24 connect-mongo: 4.6.0 fastify: 4.14.1 fastify-auth0-verify: ^1.0.0 @@ -130,12 +132,14 @@ importers: '@fastify/middie': 8.1.0 '@fastify/session': 10.1.1 '@prisma/client': 4.10.1_prisma@4.10.1 + '@sinclair/typebox': 0.25.24 connect-mongo: 4.6.0_lw7oj3533zfvrhxigrep3g2fam fastify: 4.14.1 fastify-auth0-verify: 1.0.0 fastify-plugin: 4.5.0 nodemon: 2.0.21 devDependencies: + '@fastify/type-provider-typebox': 2.4.0_naatsb2dmwxq3j32xoqzjtyzqm prisma: 4.10.1 api-server: @@ -4675,7 +4679,6 @@ packages: ajv: 8.12.0 ajv-formats: 2.1.1_ajv@8.12.0 fast-uri: 2.2.0 - dev: false /@fastify/cookie/8.3.0: resolution: {integrity: sha512-P9hY9GO11L20TnZ33XN3i0bt+3x0zaT7S0ohAzWO950E9PB2xnNhLYzPFJIGFi5AVN0yr5+/iZhWxeYvR6KCzg==} @@ -4686,17 +4689,14 @@ packages: /@fastify/deepmerge/1.3.0: resolution: {integrity: sha512-J8TOSBq3SoZbDhM9+R/u77hP93gz/rajSA+K2kGyijPpORPWUXHUpTaleoj+92As0S9uPRP7Oi8IqMf0u+ro6A==} - dev: false /@fastify/error/3.2.0: resolution: {integrity: sha512-KAfcLa+CnknwVi5fWogrLXgidLic+GXnLjijXdpl8pvkvbXU5BGa37iZO9FGvsh9ZL4y+oFi5cbHBm5UOG+dmQ==} - dev: false /@fastify/fast-json-stringify-compiler/4.2.0: resolution: {integrity: sha512-ypZynRvXA3dibfPykQN3RB5wBdEUgSGgny8Qc6k163wYPLD4mEGEDkACp+00YmqkGvIm8D/xYoHajwyEdWD/eg==} dependencies: fast-json-stringify: 5.6.2 - dev: false /@fastify/jwt/6.6.0: resolution: {integrity: sha512-D5lYtj8M2Geh/njiNmC9DDw+1jVq9muc36xBrl8rp4n46f4a3vFNzJrCI4dcWIZqUmuki8hwM/zRSvU+Y5wy1g==} @@ -4723,6 +4723,16 @@ packages: safe-stable-stringify: 2.4.2 dev: false + /@fastify/type-provider-typebox/2.4.0_naatsb2dmwxq3j32xoqzjtyzqm: + resolution: {integrity: sha512-dP8KnpfyBD1FT1UxNWCRqSpKG69xYAK53q6MqUjuwYL7xvogXBbH/tpAMc1kZkKmgAHPT0migy9DaYtnkTbIIQ==} + peerDependencies: + '@sinclair/typebox': ^0.25.9 + fastify: ^4.0.0 + dependencies: + '@sinclair/typebox': 0.25.24 + fastify: 4.14.1 + dev: true + /@fortawesome/fontawesome-common-types/6.3.0: resolution: {integrity: sha512-4BC1NMoacEBzSXRwKjZ/X/gmnbp/HU5Qqat7E8xqorUtBFZS+bwfGH5/wqOC2K6GV0rgEobp3OjGRMa5fK9pFg==} engines: {node: '>=6'} @@ -6193,7 +6203,6 @@ packages: /@sinclair/typebox/0.25.24: resolution: {integrity: sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==} - dev: true /@sindresorhus/is/0.14.0: resolution: {integrity: sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==} @@ -9101,7 +9110,6 @@ packages: /abstract-logging/2.0.1: resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==} - dev: false /accept-language/3.0.18: resolution: {integrity: sha512-sUofgqBPzgfcF20sPoBYGQ1IhQLt2LSkxTnlQSuLF3n5gPEqd5AimbvOvHEi0T1kLMiGVqPWzI5a9OteBRth3A==} @@ -9262,7 +9270,6 @@ packages: optional: true dependencies: ajv: 8.12.0 - dev: false /ajv-keywords/3.5.2_ajv@6.12.6: resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} @@ -9482,7 +9489,6 @@ packages: /archy/1.0.0: resolution: {integrity: sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==} - dev: false /are-we-there-yet/2.0.0: resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==} @@ -9769,7 +9775,6 @@ packages: /atomic-sleep/1.0.0: resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} engines: {node: '>=8.0.0'} - dev: false /automation-events/5.0.2: resolution: {integrity: sha512-8mfgjeI22tlKeaGY8y0bDt93IIUJhQKR/ISTsnhhFkpAzCirPdQ/Rmfp3xakCTmIihLgDuIWcWygHWozYKVGyQ==} @@ -9819,7 +9824,6 @@ packages: fastq: 1.15.0 transitivePeerDependencies: - supports-color - dev: false /aws-sdk/2.1326.0: resolution: {integrity: sha512-LSGiO4RSooupHnkvYbPOuOYqwAxmcnYinwIxBz4P1YI8ulhZZ/pypOj/HKqC629UyhY1ndSMtlM1l56U74UclA==} @@ -11545,7 +11549,6 @@ packages: dependencies: base64-js: 1.5.1 ieee754: 1.2.1 - dev: false /builtin-modules/3.3.0: resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} @@ -15494,14 +15497,12 @@ packages: /fast-content-type-parse/1.0.0: resolution: {integrity: sha512-Xbc4XcysUXcsP5aHUU7Nq3OwvHq97C+WnbkeIefpeYLX+ryzFJlU6OStFJhs6Ol0LkUGpcK+wL0JwfM+FCU5IA==} - dev: false /fast-copy/2.1.7: resolution: {integrity: sha512-ozrGwyuCTAy7YgFCua8rmqmytECYk/JYAMXcswOcm0qvGoE3tPb7ivBeIHTOK2DiapBhDZgacIhzhQIKU5TCfA==} /fast-decode-uri-component/1.0.1: resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==} - dev: false /fast-deep-equal/3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -15550,7 +15551,6 @@ packages: fast-deep-equal: 3.1.3 fast-uri: 2.2.0 rfdc: 1.3.0 - dev: false /fast-jwt/2.2.0: resolution: {integrity: sha512-0KSVmXROvYRCHzmmNzFMEDkd1mfbKopW1TfM2fELv5hZb/blUhfc7bxY7dJiagvR116Vhg6itk9LPUGFRQjRSg==} @@ -15568,19 +15568,16 @@ packages: resolution: {integrity: sha512-qR2r+e3HvhEFmpdHMv//U8FnFlnYjaC6QKDuaXALDkw2kvHO8WDjxH+f/rHGR4Me4pnk8p9JAkRNTjYHAKRn2Q==} dependencies: fast-decode-uri-component: 1.0.1 - dev: false /fast-redact/3.1.2: resolution: {integrity: sha512-+0em+Iya9fKGfEQGcd62Yv6onjBmmhV1uh86XVfOU8VwAe6kaFdQCWI9s0/Nnugx5Vd9tdbZ7e6gE2tR9dzXdw==} engines: {node: '>=6'} - dev: false /fast-safe-stringify/2.1.1: resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} /fast-uri/2.2.0: resolution: {integrity: sha512-cIusKBIt/R/oI6z/1nyfe2FvGKVTohVRfvkOhvx0nCEW+xf5NoCXjAHcWp93uOUBchzYcsvPlrapAdX1uW+YGg==} - dev: false /fast-url-parser/1.1.3: resolution: {integrity: sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==} @@ -15645,7 +15642,6 @@ packages: tiny-lru: 10.0.1 transitivePeerDependencies: - supports-color - dev: false /fastparallel/2.4.1: resolution: {integrity: sha512-qUmhxPgNHmvRjZKBFUNI0oZuuH9OlSIOXmJ98lhKPxMZZ7zS/Fi0wRHOihDSz0R1YiIOjxzOY4bq65YTcdBi2Q==} @@ -15866,7 +15862,6 @@ packages: fast-deep-equal: 3.1.3 fast-querystring: 1.1.1 safe-regex2: 2.0.0 - dev: false /find-up/1.1.2: resolution: {integrity: sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA==} @@ -20213,7 +20208,6 @@ packages: cookie: 0.5.0 process-warning: 2.1.0 set-cookie-parser: 2.5.1 - dev: false /lilconfig/2.0.6: resolution: {integrity: sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==} @@ -22695,7 +22689,6 @@ packages: /on-exit-leak-free/2.1.0: resolution: {integrity: sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w==} - dev: false /on-finished/2.3.0: resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} @@ -23479,11 +23472,9 @@ packages: dependencies: readable-stream: 4.3.0 split2: 4.1.0 - dev: false /pino-std-serializers/6.1.0: resolution: {integrity: sha512-KO0m2f1HkrPe9S0ldjx7za9BJjeHqBku5Ch8JyxETxT8dEFGz1PwgrHaOQupVYitpzbFSYm7nnljxD8dik2c+g==} - dev: false /pino/8.11.0: resolution: {integrity: sha512-Z2eKSvlrl2rH8p5eveNUnTdd4AjJk8tAsLkHYZQKGHP4WTh2Gi1cOSOs3eWPqaj+niS3gj4UkoreoaWgF3ZWYg==} @@ -23500,7 +23491,6 @@ packages: safe-stable-stringify: 2.4.2 sonic-boom: 3.2.1 thread-stream: 2.3.0 - dev: false /pirates/4.0.5: resolution: {integrity: sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==} @@ -24624,7 +24614,6 @@ packages: /process-warning/2.1.0: resolution: {integrity: sha512-9C20RLxrZU/rFnxWncDkuF6O999NdIf3E1ws4B0ZeY3sRVPzWBMsYDE2lxjxhiXxg464cQTgKUGm8/i6y2YGXg==} - dev: false /process/0.11.10: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} @@ -24967,7 +24956,6 @@ packages: /quick-format-unescaped/4.0.4: resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} - dev: false /quick-lru/5.1.1: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} @@ -25791,7 +25779,6 @@ packages: buffer: 6.0.3 events: 3.3.0 process: 0.11.10 - dev: false /readable-web-to-node-stream/3.0.2: resolution: {integrity: sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==} @@ -25820,7 +25807,6 @@ packages: /real-require/0.2.0: resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} engines: {node: '>= 12.13.0'} - dev: false /rechoir/0.6.2: resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} @@ -26462,7 +26448,6 @@ packages: /ret/0.2.2: resolution: {integrity: sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==} engines: {node: '>=4'} - dev: false /retext-english/3.0.4: resolution: {integrity: sha512-yr1PgaBDde+25aJXrnt3p1jvT8FVLVat2Bx8XeAWX13KXo8OT+3nWGU3HWxM4YFJvmfqvJYJZG2d7xxaO774gw==} @@ -26640,12 +26625,10 @@ packages: resolution: {integrity: sha512-PaUSFsUaNNuKwkBijoAPHAK6/eM6VirvyPWlZ7BAQy4D+hCvh4B6lIG+nPdhbFfIbP+gTGBcrdsOaUs0F+ZBOQ==} dependencies: ret: 0.2.2 - dev: false /safe-stable-stringify/2.4.2: resolution: {integrity: sha512-gMxvPJYhP0O9n2pvcfYfIuYgbledAOJFcqRThtPRmjscaipiwcwPPKLytpVzMkG2HAN87Qmo2d4PtGiri1dSLA==} engines: {node: '>=10'} - dev: false /safer-buffer/2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -26801,7 +26784,6 @@ packages: /secure-json-parse/2.7.0: resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} - dev: false /select-hose/2.0.0: resolution: {integrity: sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==} @@ -26956,7 +26938,6 @@ packages: /set-cookie-parser/2.5.1: resolution: {integrity: sha512-1jeBGaKNGdEq4FgIrORu/N570dwoPYio8lSoYLWmX7sQ//0JY08Xh9o5pBcgmHQ/MbsYp/aZnOe1s1lIsbLprQ==} - dev: false /set-value/2.0.1: resolution: {integrity: sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==} @@ -27312,7 +27293,6 @@ packages: resolution: {integrity: sha512-iITeTHxy3B9FGu8aVdiDXUVAcHMF9Ss0cCsAOo2HfCrmVGT3/DT5oYaeu0M/YKZDlKTvChEyPq0zI9Hf33EX6A==} dependencies: atomic-sleep: 1.0.0 - dev: false /source-list-map/2.0.1: resolution: {integrity: sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==} @@ -27470,7 +27450,6 @@ packages: /split2/4.1.0: resolution: {integrity: sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==} engines: {node: '>= 10.x'} - dev: false /sprintf-js/1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} @@ -28500,7 +28479,6 @@ packages: resolution: {integrity: sha512-kaDqm1DET9pp3NXwR8382WHbnpXnRkN9xGN9dQt3B2+dmXiW8X1SOwmFOxAErEQ47ObhZ96J6yhZNXuyCOL7KA==} dependencies: real-require: 0.2.0 - dev: false /throat/6.0.2: resolution: {integrity: sha512-WKexMoJj3vEuK0yFEapj8y64V0A6xcuPuK9Gt1d0R+dzCSJc0lHqQytAbSB4cDAK0dWh4T0E2ETkoLE2WZ41OQ==} @@ -28554,7 +28532,6 @@ packages: /tiny-lru/10.0.1: resolution: {integrity: sha512-Vst+6kEsWvb17Zpz14sRJV/f8bUWKhqm6Dc+v08iShmIJ/WxqWytHzCTd6m88pS33rE2zpX34TRmOpAJPloNCA==} engines: {node: '>=6'} - dev: false /tinydate/1.3.0: resolution: {integrity: sha512-7cR8rLy2QhYHpsBDBVYnnWXm8uRTr38RoZakFSW7Bs7PzfMPNZthuMLkwqZv7MTu8lhQ91cOFYS5a7iFj2oR3w==}