diff --git a/.u8.json b/.u8.json index 3c32329..045240a 100644 --- a/.u8.json +++ b/.u8.json @@ -56,6 +56,16 @@ "packageVersion": "1.0.0", "packageName": "runtime" } + }, + { + "timestamp": "2025-12-10T09:46:52.130Z", + "template": "pkg", + "values": { + "monoRepo": true, + "packagePrefix": "@morten-olsen/stash-", + "packageVersion": "1.0.0", + "packageName": "client" + } } ] } \ No newline at end of file diff --git a/eslint.config.mjs b/eslint.config.mjs index a0cf5db..2e0a134 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -46,6 +46,6 @@ export default tseslint.config( }, ...compat.extends('plugin:prettier/recommended'), { - ignores: ['**/node_modules/', '**/dist/', '**/.turbo/', '**/generated/'], + ignores: ['**/node_modules/', '**/dist/', '**/.turbo/', '**/__generated__/'], }, ); diff --git a/packages/client/.gitignore b/packages/client/.gitignore new file mode 100644 index 0000000..8511d52 --- /dev/null +++ b/packages/client/.gitignore @@ -0,0 +1,4 @@ +/node_modules/ +/dist/ +/coverage/ +/.env diff --git a/packages/client/package.json b/packages/client/package.json new file mode 100644 index 0000000..b3b1aa3 --- /dev/null +++ b/packages/client/package.json @@ -0,0 +1,34 @@ +{ + "type": "module", + "main": "dist/exports.js", + "scripts": { + "build": "tsc --build", + "test:unit": "vitest --run --passWithNoTests", + "test": "pnpm run \"/^test:/\"", + "generate:client": "openapi-typescript http://localhost:3400/docs/openapi.json -o src/__generated__/schema.ts" + }, + "packageManager": "pnpm@10.6.0", + "files": [ + "dist" + ], + "exports": { + ".": "./dist/exports.js" + }, + "devDependencies": { + "@morten-olsen/stash-configs": "workspace:*", + "@morten-olsen/stash-tests": "workspace:*", + "@types/node": "24.10.2", + "@vitest/coverage-v8": "4.0.15", + "openapi-typescript": "^7.10.1", + "typescript": "5.9.3", + "vitest": "4.0.15" + }, + "name": "@morten-olsen/stash-client", + "version": "1.0.0", + "imports": { + "#root/*": "./src/*" + }, + "dependencies": { + "openapi-fetch": "^0.15.0" + } +} diff --git a/packages/client/src/__generated__/schema.ts b/packages/client/src/__generated__/schema.ts new file mode 100644 index 0000000..98755ec --- /dev/null +++ b/packages/client/src/__generated__/schema.ts @@ -0,0 +1,515 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +export interface paths { + "/system/ready": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get system ready state */ + get: operations["GET/system/ready"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/documents": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Upsert document */ + post: operations["POST/documents"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/document-filters": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Find documents */ + post: operations["POST/documents-filters"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/document-chunk-filters": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Find document chunks */ + post: operations["POST/documents-chunk-filters"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; +} +export type webhooks = Record; +export interface components { + schemas: never; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +} +export type $defs = Record; +export interface operations { + "GET/system/ready": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Default Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @enum {string} */ + status: "ok"; + }; + }; + }; + }; + }; + "POST/documents": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": { + id?: string | null; + owner?: string | null; + contentType?: string | null; + content?: string | null; + source?: string | null; + sourceId?: string | null; + type?: string; + typeVersion?: number | null; + searchText?: string | null; + metadata?: unknown; + }; + }; + }; + responses: { + /** @description Default Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + /** @enum {string} */ + action: "inserted" | "updated" | "skipped"; + id: string; + document: { + id: string; + owner: string | null; + /** Format: date-time */ + createdAt: string; + /** Format: date-time */ + updatedAt: string; + /** Format: date-time */ + deletedAt: string | null; + contentType: string | null; + content: string | null; + source: string | null; + sourceId: string | null; + type: string; + typeVersion: number | null; + searchText: string | null; + metadata: unknown; + }; + }; + }; + }; + }; + }; + "POST/documents-filters": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** @default 0 */ + offset?: number; + /** @default 20 */ + limit?: number; + condition: (({ + /** @enum {string} */ + type: "text"; + tableName?: string; + field: string[]; + conditions: { + equal?: string | null; + notEqual?: string; + like?: string; + notLike?: string; + in?: string[]; + notIn?: string[]; + }; + } | { + /** @enum {string} */ + type: "number"; + tableName?: string; + field: string[]; + conditions: { + equals?: number | null; + notEquals?: number | null; + greaterThan?: number; + greaterThanOrEqual?: number; + lessThan?: number; + lessThanOrEqual?: number; + in?: number[]; + notIn?: number[]; + }; + }) | { + /** @enum {string} */ + type: "operator"; + /** @enum {string} */ + operator: "and" | "or"; + conditions: (({ + /** @enum {string} */ + type: "text"; + tableName?: string; + field: string[]; + conditions: { + equal?: string | null; + notEqual?: string; + like?: string; + notLike?: string; + in?: string[]; + notIn?: string[]; + }; + } | { + /** @enum {string} */ + type: "number"; + tableName?: string; + field: string[]; + conditions: { + equals?: number | null; + notEquals?: number | null; + greaterThan?: number; + greaterThanOrEqual?: number; + lessThan?: number; + lessThanOrEqual?: number; + in?: number[]; + notIn?: number[]; + }; + }) | { + /** @enum {string} */ + type: "operator"; + /** @enum {string} */ + operator: "and" | "or"; + conditions: (({ + /** @enum {string} */ + type: "text"; + tableName?: string; + field: string[]; + conditions: { + equal?: string | null; + notEqual?: string; + like?: string; + notLike?: string; + in?: string[]; + notIn?: string[]; + }; + } | { + /** @enum {string} */ + type: "number"; + tableName?: string; + field: string[]; + conditions: { + equals?: number | null; + notEquals?: number | null; + greaterThan?: number; + greaterThanOrEqual?: number; + lessThan?: number; + lessThanOrEqual?: number; + in?: number[]; + notIn?: number[]; + }; + }) | { + /** @enum {string} */ + type: "operator"; + /** @enum {string} */ + operator: "and" | "or"; + conditions: ({ + /** @enum {string} */ + type: "text"; + tableName?: string; + field: string[]; + conditions: { + equal?: string | null; + notEqual?: string; + like?: string; + notLike?: string; + in?: string[]; + notIn?: string[]; + }; + } | { + /** @enum {string} */ + type: "number"; + tableName?: string; + field: string[]; + conditions: { + equals?: number | null; + notEquals?: number | null; + greaterThan?: number; + greaterThanOrEqual?: number; + lessThan?: number; + lessThanOrEqual?: number; + in?: number[]; + notIn?: number[]; + }; + })[]; + })[]; + })[]; + }) | string; + }; + }; + }; + responses: { + /** @description Default Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: { + id: string; + owner: string | null; + /** Format: date-time */ + createdAt: string; + /** Format: date-time */ + updatedAt: string; + /** Format: date-time */ + deletedAt: string | null; + contentType: string | null; + content: string | null; + source: string | null; + sourceId: string | null; + type: string; + typeVersion: number | null; + searchText: string | null; + metadata: unknown; + }[]; + }; + }; + }; + }; + }; + "POST/documents-chunk-filters": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: { + content: { + "application/json": { + /** @default 20 */ + limit?: number; + /** @default 0 */ + offset?: number; + semanticText?: string; + conditions?: (({ + /** @enum {string} */ + type: "text"; + tableName?: string; + field: string[]; + conditions: { + equal?: string | null; + notEqual?: string; + like?: string; + notLike?: string; + in?: string[]; + notIn?: string[]; + }; + } | { + /** @enum {string} */ + type: "number"; + tableName?: string; + field: string[]; + conditions: { + equals?: number | null; + notEquals?: number | null; + greaterThan?: number; + greaterThanOrEqual?: number; + lessThan?: number; + lessThanOrEqual?: number; + in?: number[]; + notIn?: number[]; + }; + }) | { + /** @enum {string} */ + type: "operator"; + /** @enum {string} */ + operator: "and" | "or"; + conditions: (({ + /** @enum {string} */ + type: "text"; + tableName?: string; + field: string[]; + conditions: { + equal?: string | null; + notEqual?: string; + like?: string; + notLike?: string; + in?: string[]; + notIn?: string[]; + }; + } | { + /** @enum {string} */ + type: "number"; + tableName?: string; + field: string[]; + conditions: { + equals?: number | null; + notEquals?: number | null; + greaterThan?: number; + greaterThanOrEqual?: number; + lessThan?: number; + lessThanOrEqual?: number; + in?: number[]; + notIn?: number[]; + }; + }) | { + /** @enum {string} */ + type: "operator"; + /** @enum {string} */ + operator: "and" | "or"; + conditions: (({ + /** @enum {string} */ + type: "text"; + tableName?: string; + field: string[]; + conditions: { + equal?: string | null; + notEqual?: string; + like?: string; + notLike?: string; + in?: string[]; + notIn?: string[]; + }; + } | { + /** @enum {string} */ + type: "number"; + tableName?: string; + field: string[]; + conditions: { + equals?: number | null; + notEquals?: number | null; + greaterThan?: number; + greaterThanOrEqual?: number; + lessThan?: number; + lessThanOrEqual?: number; + in?: number[]; + notIn?: number[]; + }; + }) | { + /** @enum {string} */ + type: "operator"; + /** @enum {string} */ + operator: "and" | "or"; + conditions: ({ + /** @enum {string} */ + type: "text"; + tableName?: string; + field: string[]; + conditions: { + equal?: string | null; + notEqual?: string; + like?: string; + notLike?: string; + in?: string[]; + notIn?: string[]; + }; + } | { + /** @enum {string} */ + type: "number"; + tableName?: string; + field: string[]; + conditions: { + equals?: number | null; + notEquals?: number | null; + greaterThan?: number; + greaterThanOrEqual?: number; + lessThan?: number; + lessThanOrEqual?: number; + in?: number[]; + notIn?: number[]; + }; + })[]; + })[]; + })[]; + }) | string; + }; + }; + }; + responses: { + /** @description Default Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + items: { + id: string; + owner: string; + content: string; + metadata: unknown; + distance?: number; + }[]; + }; + }; + }; + }; + }; +} diff --git a/packages/client/src/exports.ts b/packages/client/src/exports.ts new file mode 100644 index 0000000..8dc3888 --- /dev/null +++ b/packages/client/src/exports.ts @@ -0,0 +1,19 @@ +import createApiClient from 'openapi-fetch'; + +import type { paths } from './__generated__/schema.js'; + +type CreateStashClientOptions = { + baseUrl: string; +}; + +type StashClient = ReturnType>; + +const createStashClient = (options: CreateStashClientOptions): StashClient => { + const client = createApiClient({ + baseUrl: options.baseUrl, + }); + return client; +}; + +export type { StashClient }; +export { createStashClient }; diff --git a/packages/client/tsconfig.json b/packages/client/tsconfig.json new file mode 100644 index 0000000..09b3b05 --- /dev/null +++ b/packages/client/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "outDir": "./dist" + }, + "include": [ + "src/**/*.ts" + ], + "extends": "@morten-olsen/stash-configs/tsconfig.json" +} diff --git a/packages/client/vitest.config.ts b/packages/client/vitest.config.ts new file mode 100644 index 0000000..8998b5b --- /dev/null +++ b/packages/client/vitest.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from 'vitest/config'; +import { getAliases } from '@morten-olsen/stash-tests/vitest'; + +// eslint-disable-next-line import/no-default-export +export default defineConfig(async () => { + const aliases = await getAliases(); + return { + resolve: { + alias: aliases, + }, + }; +}); diff --git a/packages/query-dsl/src/query-parser.schemas.ts b/packages/query-dsl/src/query-parser.schemas.ts index ce52f48..3d590d8 100644 --- a/packages/query-dsl/src/query-parser.schemas.ts +++ b/packages/query-dsl/src/query-parser.schemas.ts @@ -1,85 +1,65 @@ -import { z } from 'zod'; +import { z, ZodArray } from 'zod'; -const queryConditionTextSchema = z.object({ - type: z.literal('text'), - tableName: z.string().optional(), - field: z.array(z.string()), - conditions: z.object({ - equal: z.string().nullish(), - notEqual: z.string().optional(), - like: z.string().optional(), - notLike: z.string().optional(), - in: z.array(z.string()).optional(), - notIn: z.array(z.string()).optional(), - }), -}); +const queryConditionTextSchema = z + .object({ + type: z.literal('text'), + tableName: z.string().optional(), + field: z.array(z.string()), + conditions: z.object({ + equal: z.string().nullish(), + notEqual: z.string().optional(), + like: z.string().optional(), + notLike: z.string().optional(), + in: z.array(z.string()).optional(), + notIn: z.array(z.string()).optional(), + }), + }) + .meta({ id: 'QueryConditionText' }); type QueryConditionText = z.infer; -const queryConditionNumberSchema = z.object({ - type: z.literal('number'), - tableName: z.string().optional(), - field: z.array(z.string()), - conditions: z.object({ - equals: z.number().nullish(), - notEquals: z.number().nullish(), - greaterThan: z.number().optional(), - greaterThanOrEqual: z.number().optional(), - lessThan: z.number().optional(), - lessThanOrEqual: z.number().optional(), - in: z.array(z.number()).optional(), - notIn: z.array(z.number()).optional(), - }), -}); +const queryConditionNumberSchema = z + .object({ + type: z.literal('number'), + tableName: z.string().optional(), + field: z.array(z.string()), + conditions: z.object({ + equals: z.number().nullish(), + notEquals: z.number().nullish(), + greaterThan: z.number().optional(), + greaterThanOrEqual: z.number().optional(), + lessThan: z.number().optional(), + lessThanOrEqual: z.number().optional(), + in: z.array(z.number()).optional(), + notIn: z.array(z.number()).optional(), + }), + }) + .meta({ id: 'QueryConditionNumber' }); type QueryConditionNumber = z.infer; -const queryConditionSchema = z.discriminatedUnion('type', [queryConditionTextSchema, queryConditionNumberSchema]); +const queryConditionSchema = z + .discriminatedUnion('type', [queryConditionTextSchema, queryConditionNumberSchema]) + .meta({ id: 'QueryCondition' }); type QueryCondition = z.infer; -type QueryFilter = QueryCondition | QueryOperator; - -type QueryOperator = { - type: 'operator'; - operator: 'and' | 'or'; - conditions: QueryFilter[]; -}; - -// Create a depth-limited recursive schema for OpenAPI compatibility -// This supports up to 3 levels of nesting, which should be sufficient for most use cases -// OpenAPI cannot handle z.lazy(), so we manually define the nesting -// If you need deeper nesting, you can add more levels (Level3, Level4, etc.) -const queryFilterSchemaLevel0: z.ZodType = z.union([ - queryConditionSchema, - z.object({ +const queryOperatorSchema = z + .object({ type: z.literal('operator'), operator: z.enum(['and', 'or']), - conditions: z.array(queryConditionSchema), - }), -]); + get conditions(): ZodArray { + // eslint-disable-next-line + return z.array(queryFilterSchema) as any; + }, + }) + .meta({ id: 'QueryOperator' }); -const queryFilterSchemaLevel1: z.ZodType = z.union([ - queryConditionSchema, - z.object({ - type: z.literal('operator'), - operator: z.enum(['and', 'or']), - conditions: z.array(queryFilterSchemaLevel0), - }), -]); +type QueryOperator = z.infer; -const queryFilterSchemaLevel2: z.ZodType = z.union([ - queryConditionSchema, - z.object({ - type: z.literal('operator'), - operator: z.enum(['and', 'or']), - conditions: z.array(queryFilterSchemaLevel1), - }), -]); +const queryFilterSchema = z.union([queryOperatorSchema, queryConditionSchema]).meta({ id: 'QueryFilter' }); -// Export the depth-limited schema (supports 3 levels of nesting) -// This works with OpenAPI schema generation -const queryFilterSchema = queryFilterSchemaLevel2; +type QueryFilter = z.infer; export type { QueryConditionText, QueryConditionNumber, QueryOperator, QueryCondition, QueryFilter }; export { queryConditionSchema, queryFilterSchema }; diff --git a/packages/runtime/src/services/document-chunks/document-chunks.schemas.ts b/packages/runtime/src/services/document-chunks/document-chunks.schemas.ts index 1a4da7d..cf4e356 100644 --- a/packages/runtime/src/services/document-chunks/document-chunks.schemas.ts +++ b/packages/runtime/src/services/document-chunks/document-chunks.schemas.ts @@ -1,31 +1,37 @@ import { z } from 'zod'; import { queryFilterSchema } from '@morten-olsen/stash-query-dsl'; -import { createListResultSchema } from '#root/utils/utils.schema.js'; +import { createListResultSchema, queryDSLSchema } from '#root/utils/utils.schema.js'; -const documentChunkSchema = z.object({ - id: z.string(), - owner: z.string(), - content: z.string(), - metadata: z.unknown(), -}); +const documentChunkSchema = z + .object({ + id: z.guid(), + owner: z.string(), + content: z.string(), + metadata: z.unknown(), + }) + .meta({ id: 'DocumentChunk' }); type DocumentChunk = z.infer; -const documentChunkFilterSchema = z.object({ - limit: z.number().default(20), - offset: z.number().default(0), - semanticText: z.string().optional(), - conditions: z.union([queryFilterSchema, z.string()]).optional(), -}); +const documentChunkFilterSchema = z + .object({ + limit: z.number().default(20), + offset: z.number().default(0), + semanticText: z.string().optional(), + conditions: z.union([queryDSLSchema, queryFilterSchema]).optional(), + }) + .meta({ id: 'DocumentChunkFilter' }); type DocumentChunkFilter = z.infer; const documentChunksFindResultSchema = createListResultSchema( - documentChunkSchema.extend({ - distance: z.number().optional(), - }), -); + documentChunkSchema + .extend({ + distance: z.number().optional(), + }) + .meta({ id: 'DocumentChunkWithDistance' }), +).meta({ id: 'DocumentChunkFindResult' }); type DocumentChunksFindResult = z.infer; diff --git a/packages/runtime/src/services/documents/documents.schemas.ts b/packages/runtime/src/services/documents/documents.schemas.ts index 75222e5..902d4fb 100644 --- a/packages/runtime/src/services/documents/documents.schemas.ts +++ b/packages/runtime/src/services/documents/documents.schemas.ts @@ -1,29 +1,31 @@ import { z } from 'zod'; import { queryFilterSchema } from '@morten-olsen/stash-query-dsl'; -import { createListResultSchema } from '#root/utils/utils.schema.js'; +import { createListResultSchema, queryDSLSchema } from '#root/utils/utils.schema.js'; -const documentSchema = z.object({ - id: z.string(), - owner: z.string().nullable(), - createdAt: z.iso.datetime(), - updatedAt: z.iso.datetime(), - deletedAt: z.iso.datetime().nullable(), - contentType: z.string().nullable(), - content: z.string().nullable(), - source: z.string().nullable(), - sourceId: z.string().nullable(), - type: z.string(), - typeVersion: z.int().nullable(), - searchText: z.string().nullable(), - metadata: z.unknown(), -}); +const documentSchema = z + .object({ + id: z.guid(), + owner: z.string().nullable(), + createdAt: z.iso.datetime(), + updatedAt: z.iso.datetime(), + deletedAt: z.iso.datetime().nullable(), + contentType: z.string().nullable(), + content: z.string().nullable(), + source: z.string().nullable(), + sourceId: z.string().nullable(), + type: z.string(), + typeVersion: z.int().nullable(), + searchText: z.string().nullable(), + metadata: z.unknown(), + }) + .meta({ id: 'Document' }); type Document = z.infer; const documentUpsertSchema = z .object({ - id: z.string().nullish(), + id: z.guid().optional(), owner: z.string().nullish(), contentType: z.string().nullish(), content: z.string().nullish(), @@ -35,6 +37,7 @@ const documentUpsertSchema = z metadata: z.unknown().nullish(), }) .meta({ + id: 'DocumentUpsert', example: { content: 'the cat is yellow', contentType: 'text/plain', @@ -61,7 +64,7 @@ type DocumentUpsertResult = z.infer; const documentFilterSchema = z.object({ offset: z.number().default(0), limit: z.number().default(20), - condition: z.union([queryFilterSchema, z.string()]), + condition: z.union([queryDSLSchema, queryFilterSchema]), }); type DocumentFilter = z.infer; diff --git a/packages/runtime/src/utils/utils.schema.ts b/packages/runtime/src/utils/utils.schema.ts index 83dbaeb..0b29aa9 100644 --- a/packages/runtime/src/utils/utils.schema.ts +++ b/packages/runtime/src/utils/utils.schema.ts @@ -5,4 +5,12 @@ const createListResultSchema = (schema: T) => items: z.array(schema), }); -export { createListResultSchema }; +const queryDSLSchema = z + .string() + .describe('Query DSL based filter') + .meta({ + id: 'QueryDSLString', + examples: ["metadata.foo = 'bar'"], + }); + +export { createListResultSchema, queryDSLSchema }; diff --git a/packages/server/src/api.ts b/packages/server/src/api.ts index 5db8e75..54e184d 100644 --- a/packages/server/src/api.ts +++ b/packages/server/src/api.ts @@ -5,10 +5,12 @@ import { hasZodFastifySchemaValidationErrors, isResponseSerializationError, jsonSchemaTransform, + jsonSchemaTransformObject, serializerCompiler, validatorCompiler, type ZodTypeProvider, } from 'fastify-type-provider-zod'; +import scalar from '@scalar/fastify-api-reference'; import { StashRuntime } from '@morten-olsen/stash-runtime'; import { systemEndpoints } from './endpoints/system/system.js'; @@ -41,10 +43,20 @@ const createApi = async (runtime: StashRuntime = new StashRuntime()) => { }, }, transform: jsonSchemaTransform, + transformObject: jsonSchemaTransformObject, }); - await app.register(import('@scalar/fastify-api-reference'), { + await app.register(scalar, { routePrefix: '/docs', + configuration: { + pageTitle: 'Foo', + title: 'Hello World!', + telemetry: false, + hideClientButton: true, + theme: 'laserwave', + persistAuth: true, + orderRequiredPropertiesFirst: false, + }, }); app.setErrorHandler((err, req, reply) => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 885bb65..c3a6dbb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -48,6 +48,34 @@ importers: specifier: 4.0.15 version: 4.0.15(@types/node@24.10.2)(tsx@4.21.0)(yaml@2.8.2) + packages/client: + dependencies: + openapi-fetch: + specifier: ^0.15.0 + version: 0.15.0 + devDependencies: + '@morten-olsen/stash-configs': + specifier: workspace:* + version: link:../configs + '@morten-olsen/stash-tests': + specifier: workspace:* + version: link:../tests + '@types/node': + specifier: 24.10.2 + version: 24.10.2 + '@vitest/coverage-v8': + specifier: 4.0.15 + version: 4.0.15(vitest@4.0.15(@types/node@24.10.2)(tsx@4.21.0)(yaml@2.8.2)) + openapi-typescript: + specifier: ^7.10.1 + version: 7.10.1(typescript@5.9.3) + typescript: + specifier: 5.9.3 + version: 5.9.3 + vitest: + specifier: 4.0.15 + version: 4.0.15(@types/node@24.10.2)(tsx@4.21.0)(yaml@2.8.2) + packages/configs: {} packages/query-dsl: @@ -1022,6 +1050,16 @@ packages: '@protobufjs/utf8@1.1.0': resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + '@redocly/ajv@8.17.1': + resolution: {integrity: sha512-EDtsGZS964mf9zAUXAl9Ew16eYbeyAFWhsPr0fX6oaJxgd8rApYlPBf0joyhnUHz88WxrigyFtTaqqzXNzPgqw==} + + '@redocly/config@0.22.2': + resolution: {integrity: sha512-roRDai8/zr2S9YfmzUfNhKjOF0NdcOIqF7bhf4MVC5UxpjIysDjyudvlAiVbpPHp3eDRWbdzUgtkK1a7YiDNyQ==} + + '@redocly/openapi-core@1.34.6': + resolution: {integrity: sha512-2+O+riuIUgVSuLl3Lyh5AplWZyVMNuG2F98/o6NrutKJfW4/GTZdPpZlIphS0HGgcOHgmWcCSHj+dWFlZaGSHw==} + engines: {node: '>=18.17.0', npm: '>=9.5.0'} + '@rollup/rollup-android-arm-eabi@4.53.3': resolution: {integrity: sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==} cpu: [arm] @@ -1312,6 +1350,10 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + ajv-draft-04@1.0.0: resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} peerDependencies: @@ -1337,6 +1379,10 @@ packages: ansi-align@3.0.1: resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + ansi-diff@1.2.0: resolution: {integrity: sha512-BIXwHKpjzghBjcwEV10Y4b17tjHfK4nhEqK3LqyQ3JgcMcjmi3DIevozNgrOpfvBMmrq9dfvrPJSu5/5vNUBQg==} @@ -1498,6 +1544,9 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + change-case@5.4.4: + resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} + char-regex@1.0.2: resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} engines: {node: '>=10'} @@ -1531,6 +1580,9 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + colorette@1.4.0: + resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==} + colorette@2.0.19: resolution: {integrity: sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==} @@ -2047,6 +2099,10 @@ packages: html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} @@ -2070,6 +2126,10 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + index-to-position@1.2.0: + resolution: {integrity: sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==} + engines: {node: '>=18'} + individual@3.0.0: resolution: {integrity: sha512-rUY5vtT748NMRbEMrTNiFfy29BgGZwGXUi2NFUVMWQrogSLzlJvQV9eeMWi+g1aVaQ53tpyLAQtd5x/JH0Nh1g==} @@ -2244,6 +2304,10 @@ packages: resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} engines: {node: '>=8'} + js-levenshtein@1.1.6: + resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==} + engines: {node: '>=0.10.0'} + js-tiktoken@1.0.21: resolution: {integrity: sha512-biOj/6M5qdgx5TKjDnFT1ymSpM5tbd3ylwDtrQvFQSu0Z7bBYko2dF+W/aUkXUPuk6IVpRxk/3Q2sHOzGlS36g==} @@ -2439,6 +2503,10 @@ packages: minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + minimatch@9.0.5: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} @@ -2558,9 +2626,21 @@ packages: onnxruntime-web@1.22.0-dev.20250409-89f8206ba4: resolution: {integrity: sha512-0uS76OPgH0hWCPrFKlL8kYVV7ckM7t/36HfbgoFw6Nd0CZVVbQC4PkrR8mBX8LtNUFZO25IQBqV2Hx2ho3FlbQ==} + openapi-fetch@0.15.0: + resolution: {integrity: sha512-OjQUdi61WO4HYhr9+byCPMj0+bgste/LtSBEcV6FzDdONTs7x0fWn8/ndoYwzqCsKWIxEZwo4FN/TG1c1rI8IQ==} + openapi-types@12.1.3: resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==} + openapi-typescript-helpers@0.0.15: + resolution: {integrity: sha512-opyTPaunsklCBpTK8JGef6mfPhLSnyy5a0IN9vKtx3+4aExf+KxEqYwIy3hqkedXIB97u357uLMJsOnm3GVjsw==} + + openapi-typescript@7.10.1: + resolution: {integrity: sha512-rBcU8bjKGGZQT4K2ekSTY2Q5veOQbVG/lTKZ49DeCyT9z62hM2Vj/LLHjDHC9W7LJG8YMHcdXpRZDqC1ojB/lw==} + hasBin: true + peerDependencies: + typescript: ^5.x + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -2609,6 +2689,10 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} + parse-json@8.3.0: + resolution: {integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==} + engines: {node: '>=18'} + parse-ms@2.1.0: resolution: {integrity: sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==} engines: {node: '>=6'} @@ -2703,6 +2787,10 @@ packages: platform@1.3.6: resolution: {integrity: sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==} + pluralize@8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + possible-typed-array-names@1.1.0: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} @@ -3071,6 +3159,10 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + supports-color@10.2.2: + resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} + engines: {node: '>=18'} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -3205,6 +3297,10 @@ packages: resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} engines: {node: '>=8'} + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + type-fest@5.0.0: resolution: {integrity: sha512-GeJop7+u7BYlQ6yQCAY1nBQiRSHR+6OdCEtd8Bwp9a3NK3+fWAVjOaPKJDteB9f6cIJ0wt4IfnScjLG450EpXA==} engines: {node: '>=20'} @@ -3409,6 +3505,9 @@ packages: resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} engines: {node: '>=18'} + yaml-ast-parser@0.0.43: + resolution: {integrity: sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==} + yaml@2.8.0: resolution: {integrity: sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==} engines: {node: '>= 14.6'} @@ -3419,6 +3518,10 @@ packages: engines: {node: '>= 14.6'} hasBin: true + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -3649,7 +3752,7 @@ snapshots: '@eslint/config-array@0.21.1': dependencies: '@eslint/object-schema': 2.1.7 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -3665,7 +3768,7 @@ snapshots: '@eslint/eslintrc@3.3.3': dependencies: ajv: 6.12.6 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) espree: 10.4.0 globals: 14.0.0 ignore: 5.3.2 @@ -4159,6 +4262,29 @@ snapshots: '@protobufjs/utf8@1.1.0': {} + '@redocly/ajv@8.17.1': + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + '@redocly/config@0.22.2': {} + + '@redocly/openapi-core@1.34.6(supports-color@10.2.2)': + dependencies: + '@redocly/ajv': 8.17.1 + '@redocly/config': 0.22.2 + colorette: 1.4.0 + https-proxy-agent: 7.0.6(supports-color@10.2.2) + js-levenshtein: 1.1.6 + js-yaml: 4.1.1 + minimatch: 5.1.6 + pluralize: 8.0.0 + yaml-ast-parser: 0.0.43 + transitivePeerDependencies: + - supports-color + '@rollup/rollup-android-arm-eabi@4.53.3': optional: true @@ -4321,7 +4447,7 @@ snapshots: '@typescript-eslint/types': 8.49.0 '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.49.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) eslint: 9.39.1 typescript: 5.9.3 transitivePeerDependencies: @@ -4331,7 +4457,7 @@ snapshots: dependencies: '@typescript-eslint/tsconfig-utils': 8.49.0(typescript@5.9.3) '@typescript-eslint/types': 8.49.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -4350,7 +4476,7 @@ snapshots: '@typescript-eslint/types': 8.49.0 '@typescript-eslint/typescript-estree': 8.49.0(typescript@5.9.3) '@typescript-eslint/utils': 8.49.0(eslint@9.39.1)(typescript@5.9.3) - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) eslint: 9.39.1 ts-api-utils: 2.1.0(typescript@5.9.3) typescript: 5.9.3 @@ -4365,7 +4491,7 @@ snapshots: '@typescript-eslint/tsconfig-utils': 8.49.0(typescript@5.9.3) '@typescript-eslint/types': 8.49.0 '@typescript-eslint/visitor-keys': 8.49.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) minimatch: 9.0.5 semver: 7.7.3 tinyglobby: 0.2.15 @@ -4458,6 +4584,8 @@ snapshots: acorn@8.15.0: {} + agent-base@7.1.4: {} + ajv-draft-04@1.0.0(ajv@8.17.1): optionalDependencies: ajv: 8.17.1 @@ -4484,6 +4612,8 @@ snapshots: dependencies: string-width: 4.2.3 + ansi-colors@4.1.3: {} + ansi-diff@1.2.0: dependencies: ansi-split: 1.0.1 @@ -4681,6 +4811,8 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + change-case@5.4.4: {} + char-regex@1.0.2: {} chevrotain@11.0.3: @@ -4711,6 +4843,8 @@ snapshots: color-name@1.1.4: {} + colorette@1.4.0: {} + colorette@2.0.19: {} commander@10.0.1: {} @@ -4764,9 +4898,11 @@ snapshots: dependencies: ms: 2.1.2 - debug@4.4.3: + debug@4.4.3(supports-color@10.2.2): dependencies: ms: 2.1.3 + optionalDependencies: + supports-color: 10.2.2 decamelize@1.2.0: {} @@ -5094,7 +5230,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) escape-string-regexp: 4.0.0 eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 @@ -5384,6 +5520,13 @@ snapshots: html-escaper@2.0.2: {} + https-proxy-agent@7.0.6(supports-color@10.2.2): + dependencies: + agent-base: 7.1.4 + debug: 4.4.3(supports-color@10.2.2) + transitivePeerDependencies: + - supports-color + human-signals@2.1.0: {} ieee754@1.2.1: {} @@ -5399,6 +5542,8 @@ snapshots: imurmurhash@0.1.4: {} + index-to-position@1.2.0: {} + individual@3.0.0: {} inherits@2.0.4: {} @@ -5559,7 +5704,7 @@ snapshots: istanbul-lib-source-maps@5.0.6: dependencies: '@jridgewell/trace-mapping': 0.3.31 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) istanbul-lib-coverage: 3.2.2 transitivePeerDependencies: - supports-color @@ -5569,6 +5714,8 @@ snapshots: html-escaper: 2.0.2 istanbul-lib-report: 3.0.1 + js-levenshtein@1.1.6: {} + js-tiktoken@1.0.21: dependencies: base64-js: 1.5.1 @@ -5591,7 +5738,7 @@ snapshots: json-schema-resolver@3.0.0: dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) fast-uri: 3.1.0 rfdc: 1.4.1 transitivePeerDependencies: @@ -5737,6 +5884,10 @@ snapshots: dependencies: brace-expansion: 1.1.12 + minimatch@5.1.6: + dependencies: + brace-expansion: 2.0.2 + minimatch@9.0.5: dependencies: brace-expansion: 2.0.2 @@ -5854,8 +6005,24 @@ snapshots: platform: 1.3.6 protobufjs: 7.5.4 + openapi-fetch@0.15.0: + dependencies: + openapi-typescript-helpers: 0.0.15 + openapi-types@12.1.3: {} + openapi-typescript-helpers@0.0.15: {} + + openapi-typescript@7.10.1(typescript@5.9.3): + dependencies: + '@redocly/openapi-core': 1.34.6(supports-color@10.2.2) + ansi-colors: 4.1.3 + change-case: 5.4.4 + parse-json: 8.3.0 + supports-color: 10.2.2 + typescript: 5.9.3 + yargs-parser: 21.1.1 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -5909,6 +6076,12 @@ snapshots: json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 + parse-json@8.3.0: + dependencies: + '@babel/code-frame': 7.27.1 + index-to-position: 1.2.0 + type-fest: 4.41.0 + parse-ms@2.1.0: {} path-absolute@1.0.1: {} @@ -5994,6 +6167,8 @@ snapshots: platform@1.3.6: {} + pluralize@8.0.0: {} + possible-typed-array-names@1.1.0: {} postcss@8.5.6: @@ -6435,6 +6610,8 @@ snapshots: strip-json-comments@3.1.1: {} + supports-color@10.2.2: {} + supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -6560,6 +6737,8 @@ snapshots: type-fest@0.6.0: {} + type-fest@4.41.0: {} + type-fest@5.0.0: dependencies: tagged-tag: 1.0.0 @@ -6770,10 +6949,14 @@ snapshots: yallist@5.0.0: {} + yaml-ast-parser@0.0.43: {} + yaml@2.8.0: {} yaml@2.8.2: {} + yargs-parser@21.1.1: {} + yocto-queue@0.1.0: {} zod-to-json-schema@3.25.0(zod@4.1.13): diff --git a/tsconfig.json b/tsconfig.json index d1870f7..0b096ac 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,6 +9,9 @@ }, { "path": "./packages/server/tsconfig.json" + }, + { + "path": "./packages/client/tsconfig.json" } ] }