chore: improved schema
This commit is contained in:
4
packages/client/.gitignore
vendored
Normal file
4
packages/client/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/node_modules/
|
||||
/dist/
|
||||
/coverage/
|
||||
/.env
|
||||
34
packages/client/package.json
Normal file
34
packages/client/package.json
Normal file
@@ -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"
|
||||
}
|
||||
}
|
||||
515
packages/client/src/__generated__/schema.ts
generated
Normal file
515
packages/client/src/__generated__/schema.ts
generated
Normal file
@@ -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<string, never>;
|
||||
export interface components {
|
||||
schemas: never;
|
||||
responses: never;
|
||||
parameters: never;
|
||||
requestBodies: never;
|
||||
headers: never;
|
||||
pathItems: never;
|
||||
}
|
||||
export type $defs = Record<string, never>;
|
||||
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;
|
||||
}[];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
19
packages/client/src/exports.ts
Normal file
19
packages/client/src/exports.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import createApiClient from 'openapi-fetch';
|
||||
|
||||
import type { paths } from './__generated__/schema.js';
|
||||
|
||||
type CreateStashClientOptions = {
|
||||
baseUrl: string;
|
||||
};
|
||||
|
||||
type StashClient = ReturnType<typeof createApiClient<paths>>;
|
||||
|
||||
const createStashClient = (options: CreateStashClientOptions): StashClient => {
|
||||
const client = createApiClient<paths>({
|
||||
baseUrl: options.baseUrl,
|
||||
});
|
||||
return client;
|
||||
};
|
||||
|
||||
export type { StashClient };
|
||||
export { createStashClient };
|
||||
9
packages/client/tsconfig.json
Normal file
9
packages/client/tsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"extends": "@morten-olsen/stash-configs/tsconfig.json"
|
||||
}
|
||||
12
packages/client/vitest.config.ts
Normal file
12
packages/client/vitest.config.ts
Normal file
@@ -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,
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createToken, Lexer, EmbeddedActionsParser } from 'chevrotain';
|
||||
import type { ZodType } from 'zod';
|
||||
|
||||
import type { QueryFilter, QueryCondition } from './query-parser.schemas.js';
|
||||
import { type QueryFilter, type QueryCondition, queryFilterSchema } from './query-parser.schemas.js';
|
||||
|
||||
// ----------------- Lexer -----------------
|
||||
|
||||
@@ -426,7 +427,10 @@ class QueryParserParser extends EmbeddedActionsParser {
|
||||
return this.SUBRULE(this.#orExpression);
|
||||
});
|
||||
|
||||
public parse = (input: string): QueryFilter => {
|
||||
public parse = <T extends typeof queryFilterSchema>(
|
||||
input: string,
|
||||
schema: T = queryFilterSchema as unknown as T,
|
||||
): QueryFilter => {
|
||||
const lexResult = QueryLexer.tokenize(input);
|
||||
|
||||
if (lexResult.errors.length > 0) {
|
||||
@@ -450,7 +454,7 @@ class QueryParserParser extends EmbeddedActionsParser {
|
||||
throw new Error(`Parse error: ${error.message}`);
|
||||
}
|
||||
|
||||
return result;
|
||||
return schema.parse(result);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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<typeof queryConditionTextSchema>;
|
||||
|
||||
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<typeof queryConditionNumberSchema>;
|
||||
|
||||
const queryConditionSchema = z.discriminatedUnion('type', [queryConditionTextSchema, queryConditionNumberSchema]);
|
||||
const queryConditionSchema = z
|
||||
.discriminatedUnion('type', [queryConditionTextSchema, queryConditionNumberSchema])
|
||||
.meta({ id: 'QueryCondition' });
|
||||
|
||||
type QueryCondition = z.infer<typeof queryConditionSchema>;
|
||||
|
||||
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<QueryFilter> = 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<typeof queryOperatorSchema | typeof queryConditionSchema> {
|
||||
// eslint-disable-next-line
|
||||
return z.array(queryFilterSchema) as any;
|
||||
},
|
||||
})
|
||||
.meta({ id: 'QueryOperator' });
|
||||
|
||||
const queryFilterSchemaLevel1: z.ZodType<QueryFilter> = z.union([
|
||||
queryConditionSchema,
|
||||
z.object({
|
||||
type: z.literal('operator'),
|
||||
operator: z.enum(['and', 'or']),
|
||||
conditions: z.array(queryFilterSchemaLevel0),
|
||||
}),
|
||||
]);
|
||||
type QueryOperator = z.infer<typeof queryOperatorSchema>;
|
||||
|
||||
const queryFilterSchemaLevel2: z.ZodType<QueryFilter> = 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<typeof queryFilterSchema>;
|
||||
|
||||
export type { QueryConditionText, QueryConditionNumber, QueryOperator, QueryCondition, QueryFilter };
|
||||
export { queryConditionSchema, queryFilterSchema };
|
||||
|
||||
@@ -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<typeof documentChunkSchema>;
|
||||
|
||||
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<typeof documentChunkFilterSchema>;
|
||||
|
||||
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<typeof documentChunksFindResultSchema>;
|
||||
|
||||
|
||||
@@ -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<typeof documentSchema>;
|
||||
|
||||
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<typeof documentUpsertResultSchema>;
|
||||
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<typeof documentFilterSchema>;
|
||||
|
||||
@@ -5,4 +5,12 @@ const createListResultSchema = <T extends ZodType>(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 };
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
Reference in New Issue
Block a user