mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-15 20:17:22 +00:00
feat/custom-json-schema (#172)
* init * update * finished new repo query, removed old implementation * remove debug folder
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -29,4 +29,5 @@ packages/media/.env
|
|||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
.git_old
|
.git_old
|
||||||
docker/tmp
|
docker/tmp
|
||||||
|
.debug
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, test } from "bun:test";
|
import { describe, expect, test } from "bun:test";
|
||||||
import { type ObjectQuery, convert, validate } from "../../../src/core/object/query/object-query";
|
import { type ObjectQuery, convert, validate } from "core/object/query/object-query";
|
||||||
|
|
||||||
describe("object-query", () => {
|
describe("object-query", () => {
|
||||||
const q: ObjectQuery = { name: "Michael" };
|
const q: ObjectQuery = { name: "Michael" };
|
||||||
|
|||||||
@@ -1,25 +1,18 @@
|
|||||||
import { describe, expect, test } from "bun:test";
|
import { describe, test, expect } from "bun:test";
|
||||||
import { Value, _jsonp } from "../../src/core/utils";
|
import { getDummyConnection } from "../helper";
|
||||||
import { type RepoQuery, WhereBuilder, type WhereQuery, querySchema } from "../../src/data";
|
import { type WhereQuery, WhereBuilder } from "data";
|
||||||
import type { RepoQueryIn } from "../../src/data/server/data-query-impl";
|
|
||||||
import { getDummyConnection } from "./helper";
|
|
||||||
|
|
||||||
const decode = (input: RepoQueryIn, expected: RepoQuery) => {
|
function qb() {
|
||||||
const result = Value.Decode(querySchema, input);
|
const c = getDummyConnection();
|
||||||
expect(result).toEqual(expected);
|
const kysely = c.dummyConnection.kysely;
|
||||||
};
|
return kysely.selectFrom("t").selectAll();
|
||||||
|
}
|
||||||
describe("data-query-impl", () => {
|
function compile(q: WhereQuery) {
|
||||||
function qb() {
|
const { sql, parameters } = WhereBuilder.addClause(qb(), q).compile();
|
||||||
const c = getDummyConnection();
|
return { sql, parameters };
|
||||||
const kysely = c.dummyConnection.kysely;
|
}
|
||||||
return kysely.selectFrom("t").selectAll();
|
|
||||||
}
|
|
||||||
function compile(q: WhereQuery) {
|
|
||||||
const { sql, parameters } = WhereBuilder.addClause(qb(), q).compile();
|
|
||||||
return { sql, parameters };
|
|
||||||
}
|
|
||||||
|
|
||||||
|
describe("WhereBuilder", () => {
|
||||||
test("single validation", () => {
|
test("single validation", () => {
|
||||||
const tests: [WhereQuery, string, any[]][] = [
|
const tests: [WhereQuery, string, any[]][] = [
|
||||||
[{ name: "Michael", age: 40 }, '("name" = ? and "age" = ?)', ["Michael", 40]],
|
[{ name: "Michael", age: 40 }, '("name" = ? and "age" = ?)', ["Michael", 40]],
|
||||||
@@ -94,64 +87,4 @@ describe("data-query-impl", () => {
|
|||||||
expect(keys).toEqual(expectedKeys);
|
expect(keys).toEqual(expectedKeys);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test("with", () => {
|
|
||||||
decode({ with: ["posts"] }, { with: { posts: {} } });
|
|
||||||
decode({ with: { posts: {} } }, { with: { posts: {} } });
|
|
||||||
decode({ with: { posts: { limit: 1 } } }, { with: { posts: { limit: 1 } } });
|
|
||||||
decode(
|
|
||||||
{
|
|
||||||
with: {
|
|
||||||
posts: {
|
|
||||||
with: {
|
|
||||||
images: {
|
|
||||||
select: ["id"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
with: {
|
|
||||||
posts: {
|
|
||||||
with: {
|
|
||||||
images: {
|
|
||||||
select: ["id"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// over http
|
|
||||||
{
|
|
||||||
const output = { with: { images: {} } };
|
|
||||||
decode({ with: "images" }, output);
|
|
||||||
decode({ with: '["images"]' }, output);
|
|
||||||
decode({ with: ["images"] }, output);
|
|
||||||
decode({ with: { images: {} } }, output);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const output = { with: { images: {}, comments: {} } };
|
|
||||||
decode({ with: "images,comments" }, output);
|
|
||||||
decode({ with: ["images", "comments"] }, output);
|
|
||||||
decode({ with: '["images", "comments"]' }, output);
|
|
||||||
decode({ with: { images: {}, comments: {} } }, output);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("data-query-impl: Typebox", () => {
|
|
||||||
test("sort", async () => {
|
|
||||||
const _dflt = { sort: { by: "id", dir: "asc" } };
|
|
||||||
|
|
||||||
decode({ sort: "" }, _dflt);
|
|
||||||
decode({ sort: "name" }, { sort: { by: "name", dir: "asc" } });
|
|
||||||
decode({ sort: "-name" }, { sort: { by: "name", dir: "desc" } });
|
|
||||||
decode({ sort: "-posts.name" }, { sort: { by: "posts.name", dir: "desc" } });
|
|
||||||
decode({ sort: "-1name" }, _dflt);
|
|
||||||
decode({ sort: { by: "name", dir: "desc" } }, { sort: { by: "name", dir: "desc" } });
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
@@ -43,8 +43,9 @@ beforeAll(disableConsoleLog);
|
|||||||
afterAll(enableConsoleLog);
|
afterAll(enableConsoleLog);
|
||||||
|
|
||||||
describe("MediaController", () => {
|
describe("MediaController", () => {
|
||||||
test.only("accepts direct", async () => {
|
test("accepts direct", async () => {
|
||||||
const app = await makeApp();
|
const app = await makeApp();
|
||||||
|
console.log("app", app);
|
||||||
|
|
||||||
const file = Bun.file(path);
|
const file = Bun.file(path);
|
||||||
const name = makeName("png");
|
const name = makeName("png");
|
||||||
|
|||||||
@@ -52,6 +52,7 @@
|
|||||||
"@libsql/client": "^0.15.2",
|
"@libsql/client": "^0.15.2",
|
||||||
"@mantine/core": "^7.17.1",
|
"@mantine/core": "^7.17.1",
|
||||||
"@mantine/hooks": "^7.17.1",
|
"@mantine/hooks": "^7.17.1",
|
||||||
|
"@sinclair/typebox": "0.34.30",
|
||||||
"@tanstack/react-form": "^1.0.5",
|
"@tanstack/react-form": "^1.0.5",
|
||||||
"@uiw/react-codemirror": "^4.23.10",
|
"@uiw/react-codemirror": "^4.23.10",
|
||||||
"@xyflow/react": "^12.4.4",
|
"@xyflow/react": "^12.4.4",
|
||||||
@@ -63,13 +64,13 @@
|
|||||||
"json-schema-form-react": "^0.0.2",
|
"json-schema-form-react": "^0.0.2",
|
||||||
"json-schema-library": "10.0.0-rc7",
|
"json-schema-library": "10.0.0-rc7",
|
||||||
"json-schema-to-ts": "^3.1.1",
|
"json-schema-to-ts": "^3.1.1",
|
||||||
|
"jsonv-ts": "^0.0.11",
|
||||||
"kysely": "^0.27.6",
|
"kysely": "^0.27.6",
|
||||||
|
"lodash-es": "^4.17.21",
|
||||||
"oauth4webapi": "^2.11.1",
|
"oauth4webapi": "^2.11.1",
|
||||||
"object-path-immutable": "^4.1.2",
|
"object-path-immutable": "^4.1.2",
|
||||||
"radix-ui": "^1.1.3",
|
"radix-ui": "^1.1.3",
|
||||||
"swr": "^2.3.3",
|
"swr": "^2.3.3"
|
||||||
"lodash-es": "^4.17.21",
|
|
||||||
"@sinclair/typebox": "0.34.30"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.758.0",
|
"@aws-sdk/client-s3": "^3.758.0",
|
||||||
@@ -101,11 +102,11 @@
|
|||||||
"kysely-d1": "^0.3.0",
|
"kysely-d1": "^0.3.0",
|
||||||
"open": "^10.1.0",
|
"open": "^10.1.0",
|
||||||
"openapi-types": "^12.1.3",
|
"openapi-types": "^12.1.3",
|
||||||
|
"picocolors": "^1.1.1",
|
||||||
"postcss": "^8.5.3",
|
"postcss": "^8.5.3",
|
||||||
"postcss-preset-mantine": "^1.17.0",
|
"postcss-preset-mantine": "^1.17.0",
|
||||||
"postcss-simple-vars": "^7.0.1",
|
"postcss-simple-vars": "^7.0.1",
|
||||||
"posthog-js-lite": "^3.4.2",
|
"posthog-js-lite": "^3.4.2",
|
||||||
"picocolors": "^1.1.1",
|
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-hook-form": "^7.54.2",
|
"react-hook-form": "^7.54.2",
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ export class App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get fetch(): Hono["fetch"] {
|
get fetch(): Hono["fetch"] {
|
||||||
return this.server.fetch;
|
return this.server.fetch as any;
|
||||||
}
|
}
|
||||||
|
|
||||||
get module() {
|
get module() {
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ type ExpressionMap<Exps extends Expressions> = {
|
|||||||
? E
|
? E
|
||||||
: never;
|
: never;
|
||||||
};
|
};
|
||||||
|
type ExpressionKeys<Exps extends Expressions> = Exps[number]["key"];
|
||||||
|
|
||||||
type ExpressionCondition<Exps extends Expressions> = {
|
type ExpressionCondition<Exps extends Expressions> = {
|
||||||
[K in keyof ExpressionMap<Exps>]: { [P in K]: ExpressionMap<Exps>[K] };
|
[K in keyof ExpressionMap<Exps>]: { [P in K]: ExpressionMap<Exps>[K] };
|
||||||
}[keyof ExpressionMap<Exps>];
|
}[keyof ExpressionMap<Exps>];
|
||||||
@@ -195,5 +197,7 @@ export function makeValidator<Exps extends Expressions>(expressions: Exps) {
|
|||||||
const fns = _build(query, expressions, options);
|
const fns = _build(query, expressions, options);
|
||||||
return _validate(fns);
|
return _validate(fns);
|
||||||
},
|
},
|
||||||
|
expressions,
|
||||||
|
expressionKeys: expressions.map((e) => e.key) as ExpressionKeys<Exps>,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
43
app/src/core/object/schema/index.ts
Normal file
43
app/src/core/object/schema/index.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { mergeObject } from "core/utils";
|
||||||
|
|
||||||
|
export { jsc, type Options, type Hook } from "./validator";
|
||||||
|
import * as s from "jsonv-ts";
|
||||||
|
|
||||||
|
export { s };
|
||||||
|
|
||||||
|
export class InvalidSchemaError extends Error {
|
||||||
|
constructor(
|
||||||
|
public schema: s.TAnySchema,
|
||||||
|
public value: unknown,
|
||||||
|
public errors: s.ErrorDetail[] = [],
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
`Invalid schema given for ${JSON.stringify(value, null, 2)}\n\n` +
|
||||||
|
`Error: ${JSON.stringify(errors[0], null, 2)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ParseOptions = {
|
||||||
|
withDefaults?: boolean;
|
||||||
|
coerse?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function parse<S extends s.TAnySchema>(
|
||||||
|
_schema: S,
|
||||||
|
v: unknown,
|
||||||
|
opts: ParseOptions = {},
|
||||||
|
): s.StaticCoerced<S> {
|
||||||
|
const schema = _schema as unknown as s.TSchema;
|
||||||
|
const value = opts.coerse !== false ? schema.coerce(v) : v;
|
||||||
|
const result = schema.validate(value, {
|
||||||
|
shortCircuit: true,
|
||||||
|
ignoreUnsupported: true,
|
||||||
|
});
|
||||||
|
if (!result.valid) throw new InvalidSchemaError(schema, v, result.errors);
|
||||||
|
if (opts.withDefaults) {
|
||||||
|
return mergeObject(schema.template({ withOptional: true }), value) as any;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value as any;
|
||||||
|
}
|
||||||
63
app/src/core/object/schema/validator.ts
Normal file
63
app/src/core/object/schema/validator.ts
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import type { Context, Env, Input, MiddlewareHandler, ValidationTargets } from "hono";
|
||||||
|
import { validator as honoValidator } from "hono/validator";
|
||||||
|
import type { Static, StaticCoerced, TAnySchema } from "jsonv-ts";
|
||||||
|
|
||||||
|
export type Options = {
|
||||||
|
coerce?: boolean;
|
||||||
|
includeSchema?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ValidationResult = {
|
||||||
|
valid: boolean;
|
||||||
|
errors: {
|
||||||
|
keywordLocation: string;
|
||||||
|
instanceLocation: string;
|
||||||
|
error: string;
|
||||||
|
data?: unknown;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Hook<T, E extends Env, P extends string> = (
|
||||||
|
result: { result: ValidationResult; data: T },
|
||||||
|
c: Context<E, P>,
|
||||||
|
) => Response | Promise<Response> | void;
|
||||||
|
|
||||||
|
export const validator = <
|
||||||
|
// @todo: somehow hono prevents the usage of TSchema
|
||||||
|
Schema extends TAnySchema,
|
||||||
|
Target extends keyof ValidationTargets,
|
||||||
|
E extends Env,
|
||||||
|
P extends string,
|
||||||
|
Opts extends Options = Options,
|
||||||
|
Out = Opts extends { coerce: false } ? Static<Schema> : StaticCoerced<Schema>,
|
||||||
|
I extends Input = {
|
||||||
|
in: { [K in Target]: Static<Schema> };
|
||||||
|
out: { [K in Target]: Out };
|
||||||
|
},
|
||||||
|
>(
|
||||||
|
target: Target,
|
||||||
|
schema: Schema,
|
||||||
|
options?: Opts,
|
||||||
|
hook?: Hook<Out, E, P>,
|
||||||
|
): MiddlewareHandler<E, P, I> => {
|
||||||
|
// @ts-expect-error not typed well
|
||||||
|
return honoValidator(target, async (_value, c) => {
|
||||||
|
const value = options?.coerce !== false ? schema.coerce(_value) : _value;
|
||||||
|
// @ts-ignore
|
||||||
|
const result = schema.validate(value);
|
||||||
|
if (!result.valid) {
|
||||||
|
return c.json({ ...result, schema }, 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hook) {
|
||||||
|
const hookResult = hook({ result, data: value as Out }, c);
|
||||||
|
if (hookResult) {
|
||||||
|
return hookResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value as Out;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const jsc = validator;
|
||||||
1
app/src/core/server/lib/index.ts
Normal file
1
app/src/core/server/lib/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { tbValidator } from "./tbValidator";
|
||||||
29
app/src/core/server/lib/jscValidator.ts
Normal file
29
app/src/core/server/lib/jscValidator.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import type { Env, Input, MiddlewareHandler, ValidationTargets } from "hono";
|
||||||
|
import { validator } from "hono/validator";
|
||||||
|
import type { Static, TSchema } from "simple-jsonschema-ts";
|
||||||
|
|
||||||
|
export const honoValidator = <
|
||||||
|
Target extends keyof ValidationTargets,
|
||||||
|
E extends Env,
|
||||||
|
P extends string,
|
||||||
|
const Schema extends TSchema = TSchema,
|
||||||
|
Out = Static<Schema>,
|
||||||
|
I extends Input = {
|
||||||
|
in: { [K in Target]: Static<Schema> };
|
||||||
|
out: { [K in Target]: Static<Schema> };
|
||||||
|
},
|
||||||
|
>(
|
||||||
|
target: Target,
|
||||||
|
schema: Schema,
|
||||||
|
): MiddlewareHandler<E, P, I> => {
|
||||||
|
// @ts-expect-error not typed well
|
||||||
|
return validator(target, async (value, c) => {
|
||||||
|
const coersed = schema.coerce(value);
|
||||||
|
const result = schema.validate(coersed);
|
||||||
|
if (!result.valid) {
|
||||||
|
return c.json({ ...result, schema }, 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
return coersed as Out;
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -406,3 +406,16 @@ export function objectToJsLiteral(value: object, indent: number = 0, _level: num
|
|||||||
|
|
||||||
throw new TypeError(`Unsupported data type: ${t}`);
|
throw new TypeError(`Unsupported data type: ${t}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// lodash-es compatible `pick` with perfect type inference
|
||||||
|
export function pick<T extends object, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
|
||||||
|
return keys.reduce(
|
||||||
|
(acc, key) => {
|
||||||
|
if (key in obj) {
|
||||||
|
acc[key] = obj[key];
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{} as Pick<T, K>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,11 +8,12 @@ import {
|
|||||||
type MutatorResponse,
|
type MutatorResponse,
|
||||||
type RepoQuery,
|
type RepoQuery,
|
||||||
type RepositoryResponse,
|
type RepositoryResponse,
|
||||||
querySchema,
|
repoQuery,
|
||||||
} from "data";
|
} from "data";
|
||||||
import type { Handler } from "hono/types";
|
import type { Handler } from "hono/types";
|
||||||
import type { ModuleBuildContext } from "modules";
|
import type { ModuleBuildContext } from "modules";
|
||||||
import { Controller } from "modules/Controller";
|
import { Controller } from "modules/Controller";
|
||||||
|
import { jsc, s } from "core/object/schema";
|
||||||
import * as SystemPermissions from "modules/permissions";
|
import * as SystemPermissions from "modules/permissions";
|
||||||
import type { AppDataConfig } from "../data-schema";
|
import type { AppDataConfig } from "../data-schema";
|
||||||
const { Type } = tbbox;
|
const { Type } = tbbox;
|
||||||
@@ -205,7 +206,7 @@ export class DataController extends Controller {
|
|||||||
hono.post(
|
hono.post(
|
||||||
"/:entity/fn/count",
|
"/:entity/fn/count",
|
||||||
permission(DataPermissions.entityRead),
|
permission(DataPermissions.entityRead),
|
||||||
tb("param", Type.Object({ entity: Type.String() })),
|
jsc("param", s.object({ entity: s.string() })),
|
||||||
async (c) => {
|
async (c) => {
|
||||||
const { entity } = c.req.valid("param");
|
const { entity } = c.req.valid("param");
|
||||||
if (!this.entityExists(entity)) {
|
if (!this.entityExists(entity)) {
|
||||||
@@ -222,7 +223,7 @@ export class DataController extends Controller {
|
|||||||
hono.post(
|
hono.post(
|
||||||
"/:entity/fn/exists",
|
"/:entity/fn/exists",
|
||||||
permission(DataPermissions.entityRead),
|
permission(DataPermissions.entityRead),
|
||||||
tb("param", Type.Object({ entity: Type.String() })),
|
jsc("param", s.object({ entity: s.string() })),
|
||||||
async (c) => {
|
async (c) => {
|
||||||
const { entity } = c.req.valid("param");
|
const { entity } = c.req.valid("param");
|
||||||
if (!this.entityExists(entity)) {
|
if (!this.entityExists(entity)) {
|
||||||
@@ -242,10 +243,10 @@ export class DataController extends Controller {
|
|||||||
hono.get(
|
hono.get(
|
||||||
"/:entity",
|
"/:entity",
|
||||||
permission(DataPermissions.entityRead),
|
permission(DataPermissions.entityRead),
|
||||||
tb("param", Type.Object({ entity: Type.String() })),
|
jsc("param", s.object({ entity: s.string() })),
|
||||||
tb("query", querySchema),
|
jsc("query", repoQuery),
|
||||||
async (c) => {
|
async (c) => {
|
||||||
const { entity } = c.req.param();
|
const { entity } = c.req.valid("param");
|
||||||
if (!this.entityExists(entity)) {
|
if (!this.entityExists(entity)) {
|
||||||
return this.notFound(c);
|
return this.notFound(c);
|
||||||
}
|
}
|
||||||
@@ -260,16 +261,16 @@ export class DataController extends Controller {
|
|||||||
hono.get(
|
hono.get(
|
||||||
"/:entity/:id",
|
"/:entity/:id",
|
||||||
permission(DataPermissions.entityRead),
|
permission(DataPermissions.entityRead),
|
||||||
tb(
|
jsc(
|
||||||
"param",
|
"param",
|
||||||
Type.Object({
|
s.object({
|
||||||
entity: Type.String(),
|
entity: s.string(),
|
||||||
id: tbNumber,
|
id: s.string(),
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
tb("query", querySchema),
|
jsc("query", repoQuery),
|
||||||
async (c) => {
|
async (c) => {
|
||||||
const { entity, id } = c.req.param();
|
const { entity, id } = c.req.valid("param");
|
||||||
if (!this.entityExists(entity)) {
|
if (!this.entityExists(entity)) {
|
||||||
return this.notFound(c);
|
return this.notFound(c);
|
||||||
}
|
}
|
||||||
@@ -284,17 +285,17 @@ export class DataController extends Controller {
|
|||||||
hono.get(
|
hono.get(
|
||||||
"/:entity/:id/:reference",
|
"/:entity/:id/:reference",
|
||||||
permission(DataPermissions.entityRead),
|
permission(DataPermissions.entityRead),
|
||||||
tb(
|
jsc(
|
||||||
"param",
|
"param",
|
||||||
Type.Object({
|
s.object({
|
||||||
entity: Type.String(),
|
entity: s.string(),
|
||||||
id: tbNumber,
|
id: s.string(),
|
||||||
reference: Type.String(),
|
reference: s.string(),
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
tb("query", querySchema),
|
jsc("query", repoQuery),
|
||||||
async (c) => {
|
async (c) => {
|
||||||
const { entity, id, reference } = c.req.param();
|
const { entity, id, reference } = c.req.valid("param");
|
||||||
if (!this.entityExists(entity)) {
|
if (!this.entityExists(entity)) {
|
||||||
return this.notFound(c);
|
return this.notFound(c);
|
||||||
}
|
}
|
||||||
@@ -312,10 +313,10 @@ export class DataController extends Controller {
|
|||||||
hono.post(
|
hono.post(
|
||||||
"/:entity/query",
|
"/:entity/query",
|
||||||
permission(DataPermissions.entityRead),
|
permission(DataPermissions.entityRead),
|
||||||
tb("param", Type.Object({ entity: Type.String() })),
|
jsc("param", s.object({ entity: s.string() })),
|
||||||
tb("json", querySchema),
|
jsc("json", repoQuery),
|
||||||
async (c) => {
|
async (c) => {
|
||||||
const { entity } = c.req.param();
|
const { entity } = c.req.valid("param");
|
||||||
if (!this.entityExists(entity)) {
|
if (!this.entityExists(entity)) {
|
||||||
return this.notFound(c);
|
return this.notFound(c);
|
||||||
}
|
}
|
||||||
@@ -333,10 +334,11 @@ export class DataController extends Controller {
|
|||||||
hono.post(
|
hono.post(
|
||||||
"/:entity",
|
"/:entity",
|
||||||
permission(DataPermissions.entityCreate),
|
permission(DataPermissions.entityCreate),
|
||||||
tb("param", Type.Object({ entity: Type.String() })),
|
jsc("param", s.object({ entity: s.string() })),
|
||||||
tb("json", Type.Union([Type.Object({}), Type.Array(Type.Object({}))])),
|
jsc("json", s.anyOf([s.object({}), s.array(s.object({}))])),
|
||||||
|
//tb("json", Type.Union([Type.Object({}), Type.Array(Type.Object({}))])),
|
||||||
async (c) => {
|
async (c) => {
|
||||||
const { entity } = c.req.param();
|
const { entity } = c.req.valid("param");
|
||||||
if (!this.entityExists(entity)) {
|
if (!this.entityExists(entity)) {
|
||||||
return this.notFound(c);
|
return this.notFound(c);
|
||||||
}
|
}
|
||||||
@@ -356,12 +358,12 @@ export class DataController extends Controller {
|
|||||||
hono.patch(
|
hono.patch(
|
||||||
"/:entity",
|
"/:entity",
|
||||||
permission(DataPermissions.entityUpdate),
|
permission(DataPermissions.entityUpdate),
|
||||||
tb("param", Type.Object({ entity: Type.String() })),
|
jsc("param", s.object({ entity: s.string() })),
|
||||||
tb(
|
jsc(
|
||||||
"json",
|
"json",
|
||||||
Type.Object({
|
s.object({
|
||||||
update: Type.Object({}),
|
update: s.object({}),
|
||||||
where: querySchema.properties.where,
|
where: repoQuery.properties.where,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
async (c) => {
|
async (c) => {
|
||||||
@@ -383,9 +385,9 @@ export class DataController extends Controller {
|
|||||||
hono.patch(
|
hono.patch(
|
||||||
"/:entity/:id",
|
"/:entity/:id",
|
||||||
permission(DataPermissions.entityUpdate),
|
permission(DataPermissions.entityUpdate),
|
||||||
tb("param", Type.Object({ entity: Type.String(), id: tbNumber })),
|
jsc("param", s.object({ entity: s.string(), id: s.number() })),
|
||||||
async (c) => {
|
async (c) => {
|
||||||
const { entity, id } = c.req.param();
|
const { entity, id } = c.req.valid("param");
|
||||||
if (!this.entityExists(entity)) {
|
if (!this.entityExists(entity)) {
|
||||||
return this.notFound(c);
|
return this.notFound(c);
|
||||||
}
|
}
|
||||||
@@ -400,9 +402,9 @@ export class DataController extends Controller {
|
|||||||
hono.delete(
|
hono.delete(
|
||||||
"/:entity/:id",
|
"/:entity/:id",
|
||||||
permission(DataPermissions.entityDelete),
|
permission(DataPermissions.entityDelete),
|
||||||
tb("param", Type.Object({ entity: Type.String(), id: tbNumber })),
|
jsc("param", s.object({ entity: s.string(), id: s.number() })),
|
||||||
async (c) => {
|
async (c) => {
|
||||||
const { entity, id } = c.req.param();
|
const { entity, id } = c.req.valid("param");
|
||||||
if (!this.entityExists(entity)) {
|
if (!this.entityExists(entity)) {
|
||||||
return this.notFound(c);
|
return this.notFound(c);
|
||||||
}
|
}
|
||||||
@@ -416,10 +418,10 @@ export class DataController extends Controller {
|
|||||||
hono.delete(
|
hono.delete(
|
||||||
"/:entity",
|
"/:entity",
|
||||||
permission(DataPermissions.entityDelete),
|
permission(DataPermissions.entityDelete),
|
||||||
tb("param", Type.Object({ entity: Type.String() })),
|
jsc("param", s.object({ entity: s.string() })),
|
||||||
tb("json", querySchema.properties.where),
|
jsc("json", repoQuery.properties.where),
|
||||||
async (c) => {
|
async (c) => {
|
||||||
const { entity } = c.req.param();
|
const { entity } = c.req.valid("param");
|
||||||
if (!this.entityExists(entity)) {
|
if (!this.entityExists(entity)) {
|
||||||
return this.notFound(c);
|
return this.notFound(c);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import type { Entity, EntityData, EntityManager } from "../entities";
|
|||||||
import { InvalidSearchParamsException } from "../errors";
|
import { InvalidSearchParamsException } from "../errors";
|
||||||
import { MutatorEvents } from "../events";
|
import { MutatorEvents } from "../events";
|
||||||
import { RelationMutator } from "../relations";
|
import { RelationMutator } from "../relations";
|
||||||
import type { RepoQuery } from "../server/data-query-impl";
|
import type { RepoQuery } from "../server/query";
|
||||||
|
|
||||||
type MutatorQB =
|
type MutatorQB =
|
||||||
| InsertQueryBuilder<any, any, any>
|
| InsertQueryBuilder<any, any, any>
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export class JoinBuilder {
|
|||||||
|
|
||||||
// @todo: returns multiple on manytomany (edit: so?)
|
// @todo: returns multiple on manytomany (edit: so?)
|
||||||
static getJoinedEntityNames(em: EntityManager<any>, entity: Entity, joins: string[]): string[] {
|
static getJoinedEntityNames(em: EntityManager<any>, entity: Entity, joins: string[]): string[] {
|
||||||
|
console.log("join", joins);
|
||||||
return joins.flatMap((join) => {
|
return joins.flatMap((join) => {
|
||||||
const relation = em.relationOf(entity.name, join);
|
const relation = em.relationOf(entity.name, join);
|
||||||
if (!relation) {
|
if (!relation) {
|
||||||
|
|||||||
@@ -2,10 +2,9 @@ import type { DB as DefaultDB, PrimaryFieldType } from "core";
|
|||||||
import { $console } from "core";
|
import { $console } from "core";
|
||||||
import { type EmitsEvents, EventManager } from "core/events";
|
import { type EmitsEvents, EventManager } from "core/events";
|
||||||
import { type SelectQueryBuilder, sql } from "kysely";
|
import { type SelectQueryBuilder, sql } from "kysely";
|
||||||
import { cloneDeep } from "lodash-es";
|
|
||||||
import { InvalidSearchParamsException } from "../../errors";
|
import { InvalidSearchParamsException } from "../../errors";
|
||||||
import { MutatorEvents, RepositoryEvents } from "../../events";
|
import { MutatorEvents, RepositoryEvents } from "../../events";
|
||||||
import { type RepoQuery, defaultQuerySchema } from "../../server/data-query-impl";
|
import { type RepoQuery, getRepoQueryTemplate } from "data/server/query";
|
||||||
import {
|
import {
|
||||||
type Entity,
|
type Entity,
|
||||||
type EntityData,
|
type EntityData,
|
||||||
@@ -84,14 +83,14 @@ export class Repository<TBD extends object = DefaultDB, TB extends keyof TBD = a
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getValidOptions(options?: Partial<RepoQuery>): RepoQuery {
|
getValidOptions(options?: RepoQuery): RepoQuery {
|
||||||
const entity = this.entity;
|
const entity = this.entity;
|
||||||
// @todo: if not cloned deep, it will keep references and error if multiple requests come in
|
// @todo: if not cloned deep, it will keep references and error if multiple requests come in
|
||||||
const validated = {
|
const validated = {
|
||||||
...cloneDeep(defaultQuerySchema),
|
...structuredClone(getRepoQueryTemplate()),
|
||||||
sort: entity.getDefaultSort(),
|
sort: entity.getDefaultSort(),
|
||||||
select: entity.getSelect(),
|
select: entity.getSelect(),
|
||||||
};
|
} satisfies Required<RepoQuery>;
|
||||||
|
|
||||||
if (!options) return validated;
|
if (!options) return validated;
|
||||||
|
|
||||||
@@ -99,12 +98,15 @@ export class Repository<TBD extends object = DefaultDB, TB extends keyof TBD = a
|
|||||||
if (!validated.select.includes(options.sort.by)) {
|
if (!validated.select.includes(options.sort.by)) {
|
||||||
throw new InvalidSearchParamsException(`Invalid sort field "${options.sort.by}"`);
|
throw new InvalidSearchParamsException(`Invalid sort field "${options.sort.by}"`);
|
||||||
}
|
}
|
||||||
if (!["asc", "desc"].includes(options.sort.dir)) {
|
if (!["asc", "desc"].includes(options.sort.dir!)) {
|
||||||
throw new InvalidSearchParamsException(`Invalid sort direction "${options.sort.dir}"`);
|
throw new InvalidSearchParamsException(`Invalid sort direction "${options.sort.dir}"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.checkIndex(entity.name, options.sort.by, "sort");
|
this.checkIndex(entity.name, options.sort.by, "sort");
|
||||||
validated.sort = options.sort;
|
validated.sort = {
|
||||||
|
dir: "asc",
|
||||||
|
...options.sort,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.select && options.select.length > 0) {
|
if (options.select && options.select.length > 0) {
|
||||||
@@ -505,7 +507,7 @@ export class Repository<TBD extends object = DefaultDB, TB extends keyof TBD = a
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async exists(where: Required<RepoQuery["where"]>): Promise<RepositoryExistsResponse> {
|
async exists(where: Required<RepoQuery>["where"]): Promise<RepositoryExistsResponse> {
|
||||||
const entity = this.entity;
|
const entity = this.entity;
|
||||||
const options = this.getValidOptions({ where });
|
const options = this.getValidOptions({ where });
|
||||||
|
|
||||||
@@ -513,7 +515,7 @@ export class Repository<TBD extends object = DefaultDB, TB extends keyof TBD = a
|
|||||||
let qb = this.conn.selectFrom(entity.name).select(selector);
|
let qb = this.conn.selectFrom(entity.name).select(selector);
|
||||||
|
|
||||||
// add mandatory where
|
// add mandatory where
|
||||||
qb = WhereBuilder.addClause(qb, options.where).limit(1);
|
qb = WhereBuilder.addClause(qb, options.where!).limit(1);
|
||||||
|
|
||||||
const { result, ...compiled } = await this.executeQb(qb);
|
const { result, ...compiled } = await this.executeQb(qb);
|
||||||
|
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ const expressions = [
|
|||||||
export type WhereQuery = FilterQuery<typeof expressions>;
|
export type WhereQuery = FilterQuery<typeof expressions>;
|
||||||
|
|
||||||
const validator = makeValidator(expressions);
|
const validator = makeValidator(expressions);
|
||||||
|
export const expressionKeys = validator.expressionKeys;
|
||||||
|
|
||||||
export class WhereBuilder {
|
export class WhereBuilder {
|
||||||
static addClause<QB extends WhereQb>(qb: QB, query: WhereQuery) {
|
static addClause<QB extends WhereQb>(qb: QB, query: WhereQuery) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { $console, type PrimaryFieldType } from "core";
|
import { $console, type PrimaryFieldType } from "core";
|
||||||
import { Event, InvalidEventReturn } from "core/events";
|
import { Event, InvalidEventReturn } from "core/events";
|
||||||
import type { Entity, EntityData } from "../entities";
|
import type { Entity, EntityData } from "../entities";
|
||||||
import type { RepoQuery } from "../server/data-query-impl";
|
import type { RepoQuery } from "data/server/query";
|
||||||
|
|
||||||
export class MutatorInsertBefore extends Event<{ entity: Entity; data: EntityData }, EntityData> {
|
export class MutatorInsertBefore extends Event<{ entity: Entity; data: EntityData }, EntityData> {
|
||||||
static override slug = "mutator-insert-before";
|
static override slug = "mutator-insert-before";
|
||||||
|
|||||||
@@ -10,10 +10,11 @@ export * from "./connection";
|
|||||||
export {
|
export {
|
||||||
type RepoQuery,
|
type RepoQuery,
|
||||||
type RepoQueryIn,
|
type RepoQueryIn,
|
||||||
defaultQuerySchema,
|
getRepoQueryTemplate,
|
||||||
querySchema,
|
repoQuery,
|
||||||
whereSchema,
|
} from "./server/query";
|
||||||
} from "./server/data-query-impl";
|
|
||||||
|
export type { WhereQuery } from "./entities/query/WhereBuilder";
|
||||||
|
|
||||||
export { KyselyPluginRunner } from "./plugins/KyselyPluginRunner";
|
export { KyselyPluginRunner } from "./plugins/KyselyPluginRunner";
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
type MutationInstructionResponse,
|
type MutationInstructionResponse,
|
||||||
RelationHelper,
|
RelationHelper,
|
||||||
} from "../relations";
|
} from "../relations";
|
||||||
import type { RepoQuery } from "../server/data-query-impl";
|
import type { RepoQuery } from "../server/query";
|
||||||
import type { RelationType } from "./relation-types";
|
import type { RelationType } from "./relation-types";
|
||||||
import * as tbbox from "@sinclair/typebox";
|
import * as tbbox from "@sinclair/typebox";
|
||||||
const { Type } = tbbox;
|
const { Type } = tbbox;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { Static } from "core/utils";
|
|||||||
import type { ExpressionBuilder } from "kysely";
|
import type { ExpressionBuilder } from "kysely";
|
||||||
import { Entity, type EntityManager } from "../entities";
|
import { Entity, type EntityManager } from "../entities";
|
||||||
import { type Field, PrimaryField } from "../fields";
|
import { type Field, PrimaryField } from "../fields";
|
||||||
import type { RepoQuery } from "../server/data-query-impl";
|
import type { RepoQuery } from "../server/query";
|
||||||
import { EntityRelation, type KyselyQueryBuilder } from "./EntityRelation";
|
import { EntityRelation, type KyselyQueryBuilder } from "./EntityRelation";
|
||||||
import { EntityRelationAnchor } from "./EntityRelationAnchor";
|
import { EntityRelationAnchor } from "./EntityRelationAnchor";
|
||||||
import { RelationField } from "./RelationField";
|
import { RelationField } from "./RelationField";
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { snakeToPascalWithSpaces } from "core/utils";
|
|||||||
import type { Static } from "core/utils";
|
import type { Static } from "core/utils";
|
||||||
import type { ExpressionBuilder } from "kysely";
|
import type { ExpressionBuilder } from "kysely";
|
||||||
import type { Entity, EntityManager } from "../entities";
|
import type { Entity, EntityManager } from "../entities";
|
||||||
import type { RepoQuery } from "../server/data-query-impl";
|
import type { RepoQuery } from "../server/query";
|
||||||
import { EntityRelation, type KyselyQueryBuilder } from "./EntityRelation";
|
import { EntityRelation, type KyselyQueryBuilder } from "./EntityRelation";
|
||||||
import { EntityRelationAnchor } from "./EntityRelationAnchor";
|
import { EntityRelationAnchor } from "./EntityRelationAnchor";
|
||||||
import { RelationField, type RelationFieldBaseConfig } from "./RelationField";
|
import { RelationField, type RelationFieldBaseConfig } from "./RelationField";
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { Static } from "core/utils";
|
|||||||
import type { ExpressionBuilder } from "kysely";
|
import type { ExpressionBuilder } from "kysely";
|
||||||
import type { Entity, EntityManager } from "../entities";
|
import type { Entity, EntityManager } from "../entities";
|
||||||
import { NumberField, TextField } from "../fields";
|
import { NumberField, TextField } from "../fields";
|
||||||
import type { RepoQuery } from "../server/data-query-impl";
|
import type { RepoQuery } from "../server/query";
|
||||||
import { EntityRelation, type KyselyJsonFrom, type KyselyQueryBuilder } from "./EntityRelation";
|
import { EntityRelation, type KyselyJsonFrom, type KyselyQueryBuilder } from "./EntityRelation";
|
||||||
import { EntityRelationAnchor } from "./EntityRelationAnchor";
|
import { EntityRelationAnchor } from "./EntityRelationAnchor";
|
||||||
import { type RelationType, RelationTypes } from "./relation-types";
|
import { type RelationType, RelationTypes } from "./relation-types";
|
||||||
|
|||||||
@@ -1,148 +0,0 @@
|
|||||||
import type { TThis } from "@sinclair/typebox";
|
|
||||||
import { type SchemaOptions, type StaticDecode, StringEnum, Value, isObject } from "core/utils";
|
|
||||||
import { WhereBuilder, type WhereQuery } from "../entities";
|
|
||||||
import * as tbbox from "@sinclair/typebox";
|
|
||||||
const { Type } = tbbox;
|
|
||||||
|
|
||||||
const NumberOrString = (options: SchemaOptions = {}) =>
|
|
||||||
Type.Transform(Type.Union([Type.Number(), Type.String()], options))
|
|
||||||
.Decode((value) => Number.parseInt(String(value)))
|
|
||||||
.Encode(String);
|
|
||||||
|
|
||||||
const limit = NumberOrString({ default: 10 });
|
|
||||||
const offset = NumberOrString({ default: 0 });
|
|
||||||
|
|
||||||
const sort_default = { by: "id", dir: "asc" };
|
|
||||||
const sort = Type.Transform(
|
|
||||||
Type.Union(
|
|
||||||
[Type.String(), Type.Object({ by: Type.String(), dir: StringEnum(["asc", "desc"]) })],
|
|
||||||
{
|
|
||||||
default: sort_default,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.Decode((value): { by: string; dir: "asc" | "desc" } => {
|
|
||||||
if (typeof value === "string") {
|
|
||||||
if (/^-?[a-zA-Z_][a-zA-Z0-9_.]*$/.test(value)) {
|
|
||||||
const dir = value[0] === "-" ? "desc" : "asc";
|
|
||||||
return { by: dir === "desc" ? value.slice(1) : value, dir } as any;
|
|
||||||
} else if (/^{.*}$/.test(value)) {
|
|
||||||
return JSON.parse(value) as any;
|
|
||||||
}
|
|
||||||
|
|
||||||
return sort_default as any;
|
|
||||||
}
|
|
||||||
return value as any;
|
|
||||||
})
|
|
||||||
.Encode((value) => value);
|
|
||||||
|
|
||||||
const stringArray = Type.Transform(
|
|
||||||
Type.Union([Type.String(), Type.Array(Type.String())], { default: [] }),
|
|
||||||
)
|
|
||||||
.Decode((value) => {
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
return value;
|
|
||||||
} else if (value.includes(",")) {
|
|
||||||
return value.split(",");
|
|
||||||
}
|
|
||||||
return [value];
|
|
||||||
})
|
|
||||||
.Encode((value) => (Array.isArray(value) ? value : [value]));
|
|
||||||
|
|
||||||
export const whereSchema = Type.Transform(
|
|
||||||
Type.Union([Type.String(), Type.Object({})], { default: {} }),
|
|
||||||
)
|
|
||||||
.Decode((value) => {
|
|
||||||
const q = typeof value === "string" ? JSON.parse(value) : value;
|
|
||||||
return WhereBuilder.convert(q);
|
|
||||||
})
|
|
||||||
.Encode(JSON.stringify);
|
|
||||||
|
|
||||||
export type RepoWithSchema = Record<
|
|
||||||
string,
|
|
||||||
Omit<RepoQueryIn, "with"> & {
|
|
||||||
with?: unknown;
|
|
||||||
}
|
|
||||||
>;
|
|
||||||
|
|
||||||
export const withSchema = <TSelf extends TThis>(Self: TSelf) =>
|
|
||||||
Type.Transform(
|
|
||||||
Type.Union([Type.String(), Type.Array(Type.String()), Type.Record(Type.String(), Self)]),
|
|
||||||
)
|
|
||||||
.Decode((value) => {
|
|
||||||
// images
|
|
||||||
// images,comments
|
|
||||||
// ["images","comments"]
|
|
||||||
// { "images": {} }
|
|
||||||
|
|
||||||
if (!Array.isArray(value) && isObject(value)) {
|
|
||||||
return value as RepoWithSchema;
|
|
||||||
}
|
|
||||||
|
|
||||||
let _value: any = null;
|
|
||||||
if (typeof value === "string") {
|
|
||||||
// if stringified object
|
|
||||||
if (value.match(/^\{/)) {
|
|
||||||
return JSON.parse(value) as RepoWithSchema;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if stringified array
|
|
||||||
if (value.match(/^\[/)) {
|
|
||||||
_value = JSON.parse(value) as string[];
|
|
||||||
|
|
||||||
// if comma-separated string
|
|
||||||
} else if (value.includes(",")) {
|
|
||||||
_value = value.split(",");
|
|
||||||
|
|
||||||
// if single string
|
|
||||||
} else {
|
|
||||||
_value = [value];
|
|
||||||
}
|
|
||||||
} else if (Array.isArray(value)) {
|
|
||||||
_value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_value || !Array.isArray(_value) || !_value.every((v) => typeof v === "string")) {
|
|
||||||
throw new Error("Invalid 'with' schema");
|
|
||||||
}
|
|
||||||
|
|
||||||
return _value.reduce((acc, v) => {
|
|
||||||
acc[v] = {};
|
|
||||||
return acc;
|
|
||||||
}, {} as RepoWithSchema);
|
|
||||||
})
|
|
||||||
.Encode((value) => value);
|
|
||||||
|
|
||||||
export const querySchema = Type.Recursive(
|
|
||||||
(Self) =>
|
|
||||||
Type.Partial(
|
|
||||||
Type.Object(
|
|
||||||
{
|
|
||||||
limit: limit,
|
|
||||||
offset: offset,
|
|
||||||
sort: sort,
|
|
||||||
select: stringArray,
|
|
||||||
with: withSchema(Self),
|
|
||||||
join: stringArray,
|
|
||||||
where: whereSchema,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// @todo: determine if unknown is allowed, it's ignore anyway
|
|
||||||
additionalProperties: false,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
{ $id: "query-schema" },
|
|
||||||
);
|
|
||||||
|
|
||||||
export type RepoQueryIn = {
|
|
||||||
limit?: number;
|
|
||||||
offset?: number;
|
|
||||||
sort?: string | { by: string; dir: "asc" | "desc" };
|
|
||||||
select?: string[];
|
|
||||||
with?: string | string[] | Record<string, RepoQueryIn>;
|
|
||||||
join?: string[];
|
|
||||||
where?: WhereQuery;
|
|
||||||
};
|
|
||||||
export type RepoQuery = Required<StaticDecode<typeof querySchema>>;
|
|
||||||
export const defaultQuerySchema = Value.Default(querySchema, {}) as RepoQuery;
|
|
||||||
184
app/src/data/server/query.spec.ts
Normal file
184
app/src/data/server/query.spec.ts
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
import { test, describe, expect } from "bun:test";
|
||||||
|
import * as q from "./query";
|
||||||
|
import { s as schema, parse as $parse, type ParseOptions } from "core/object/schema";
|
||||||
|
|
||||||
|
const parse = (v: unknown, o: ParseOptions = {}) => $parse(q.repoQuery, v, o);
|
||||||
|
|
||||||
|
// compatibility
|
||||||
|
const decode = (input: any, output: any) => {
|
||||||
|
expect(parse(input)).toEqual(output);
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("server/query", () => {
|
||||||
|
test("limit & offset", () => {
|
||||||
|
expect(() => parse({ limit: false })).toThrow();
|
||||||
|
expect(parse({ limit: "11" })).toEqual({ limit: 11 });
|
||||||
|
expect(parse({ limit: 20 })).toEqual({ limit: 20 });
|
||||||
|
expect(parse({ offset: "1" })).toEqual({ offset: 1 });
|
||||||
|
});
|
||||||
|
|
||||||
|
test("select", () => {
|
||||||
|
expect(parse({ select: "id" })).toEqual({ select: ["id"] });
|
||||||
|
expect(parse({ select: "id,title" })).toEqual({ select: ["id", "title"] });
|
||||||
|
expect(parse({ select: "id,title,desc" })).toEqual({ select: ["id", "title", "desc"] });
|
||||||
|
expect(parse({ select: ["id", "title"] })).toEqual({ select: ["id", "title"] });
|
||||||
|
|
||||||
|
expect(() => parse({ select: "not allowed" })).toThrow();
|
||||||
|
expect(() => parse({ select: "id," })).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("join", () => {
|
||||||
|
expect(parse({ join: "id" })).toEqual({ join: ["id"] });
|
||||||
|
expect(parse({ join: "id,title" })).toEqual({ join: ["id", "title"] });
|
||||||
|
expect(parse({ join: ["id", "title"] })).toEqual({ join: ["id", "title"] });
|
||||||
|
});
|
||||||
|
|
||||||
|
test("sort", () => {
|
||||||
|
expect(parse({ sort: "id" }).sort).toEqual({
|
||||||
|
by: "id",
|
||||||
|
dir: "asc",
|
||||||
|
});
|
||||||
|
expect(parse({ sort: "-id" }).sort).toEqual({
|
||||||
|
by: "id",
|
||||||
|
dir: "desc",
|
||||||
|
});
|
||||||
|
expect(parse({ sort: { by: "title" } }).sort).toEqual({
|
||||||
|
by: "title",
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
parse(
|
||||||
|
{ sort: { by: "id" } },
|
||||||
|
{
|
||||||
|
withDefaults: true,
|
||||||
|
},
|
||||||
|
).sort,
|
||||||
|
).toEqual({
|
||||||
|
by: "id",
|
||||||
|
dir: "asc",
|
||||||
|
});
|
||||||
|
expect(parse({ sort: { by: "count", dir: "desc" } }).sort).toEqual({
|
||||||
|
by: "count",
|
||||||
|
dir: "desc",
|
||||||
|
});
|
||||||
|
// invalid gives default
|
||||||
|
expect(parse({ sort: "not allowed" }).sort).toEqual({
|
||||||
|
by: "id",
|
||||||
|
dir: "asc",
|
||||||
|
});
|
||||||
|
|
||||||
|
// json
|
||||||
|
expect(parse({ sort: JSON.stringify({ by: "count", dir: "desc" }) }).sort).toEqual({
|
||||||
|
by: "count",
|
||||||
|
dir: "desc",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("sort2", () => {
|
||||||
|
const _dflt = { sort: { by: "id", dir: "asc" } } as const;
|
||||||
|
|
||||||
|
decode({ sort: "" }, _dflt);
|
||||||
|
decode({ sort: "name" }, { sort: { by: "name", dir: "asc" } });
|
||||||
|
decode({ sort: "-name" }, { sort: { by: "name", dir: "desc" } });
|
||||||
|
decode({ sort: "-posts.name" }, { sort: { by: "posts.name", dir: "desc" } });
|
||||||
|
decode({ sort: "-1name" }, _dflt);
|
||||||
|
decode({ sort: { by: "name", dir: "desc" } }, { sort: { by: "name", dir: "desc" } });
|
||||||
|
});
|
||||||
|
|
||||||
|
test("where", () => {
|
||||||
|
expect(parse({ where: { id: 1 } }).where).toEqual({
|
||||||
|
id: { $eq: 1 },
|
||||||
|
});
|
||||||
|
expect(parse({ where: JSON.stringify({ id: 1 }) }).where).toEqual({
|
||||||
|
id: { $eq: 1 },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(parse({ where: { count: { $gt: 1 } } }).where).toEqual({
|
||||||
|
count: { $gt: 1 },
|
||||||
|
});
|
||||||
|
expect(parse({ where: JSON.stringify({ count: { $gt: 1 } }) }).where).toEqual({
|
||||||
|
count: { $gt: 1 },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("template", () => {
|
||||||
|
expect(
|
||||||
|
q.repoQuery.template({
|
||||||
|
withOptional: true,
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
limit: 10,
|
||||||
|
offset: 0,
|
||||||
|
sort: { by: "id", dir: "asc" },
|
||||||
|
where: {},
|
||||||
|
select: [],
|
||||||
|
join: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("with", () => {
|
||||||
|
let example = {
|
||||||
|
limit: 10,
|
||||||
|
with: {
|
||||||
|
posts: { limit: "10", with: ["comments"] },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
expect(parse(example)).toEqual({
|
||||||
|
limit: 10,
|
||||||
|
with: {
|
||||||
|
posts: {
|
||||||
|
limit: 10,
|
||||||
|
with: {
|
||||||
|
comments: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
decode({ with: ["posts"] }, { with: { posts: {} } });
|
||||||
|
decode({ with: { posts: {} } }, { with: { posts: {} } });
|
||||||
|
decode({ with: { posts: { limit: 1 } } }, { with: { posts: { limit: 1 } } });
|
||||||
|
decode(
|
||||||
|
{
|
||||||
|
with: {
|
||||||
|
posts: {
|
||||||
|
with: {
|
||||||
|
images: {
|
||||||
|
limit: "10",
|
||||||
|
select: "id",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
with: {
|
||||||
|
posts: {
|
||||||
|
with: {
|
||||||
|
images: {
|
||||||
|
limit: 10,
|
||||||
|
select: ["id"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// over http
|
||||||
|
{
|
||||||
|
const output = { with: { images: {} } };
|
||||||
|
decode({ with: "images" }, output);
|
||||||
|
decode({ with: '["images"]' }, output);
|
||||||
|
decode({ with: ["images"] }, output);
|
||||||
|
decode({ with: { images: {} } }, output);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const output = { with: { images: {}, comments: {} } };
|
||||||
|
decode({ with: "images,comments" }, output);
|
||||||
|
decode({ with: ["images", "comments"] }, output);
|
||||||
|
decode({ with: '["images", "comments"]' }, output);
|
||||||
|
decode({ with: { images: {}, comments: {} } }, output);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
158
app/src/data/server/query.ts
Normal file
158
app/src/data/server/query.ts
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
import { s } from "core/object/schema";
|
||||||
|
import { WhereBuilder, type WhereQuery } from "data";
|
||||||
|
import { $console } from "core";
|
||||||
|
import { isObject } from "core/utils";
|
||||||
|
import type { CoercionOptions, TAnyOf } from "jsonv-ts";
|
||||||
|
|
||||||
|
// -------
|
||||||
|
// helpers
|
||||||
|
const stringIdentifier = s.string({
|
||||||
|
// allow "id", "id,title" – but not "id," or "not allowed"
|
||||||
|
pattern: "^(?:[a-zA-Z_$][\\w$]*)(?:,[a-zA-Z_$][\\w$]*)*$",
|
||||||
|
});
|
||||||
|
const numberOrString = <N extends s.UnionSchema>(c: N = {} as N) =>
|
||||||
|
s.anyOf([s.number(), s.string()], {
|
||||||
|
...c,
|
||||||
|
coerse: function (this: s.TSchema, v): number {
|
||||||
|
if (typeof v === "string") {
|
||||||
|
const n = Number.parseInt(v);
|
||||||
|
if (Number.isNaN(n)) return this.default ?? 10;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
return v as number;
|
||||||
|
},
|
||||||
|
}) as unknown as s.TSchemaInOut<number | string, number>;
|
||||||
|
const stringArray = s.anyOf(
|
||||||
|
[
|
||||||
|
stringIdentifier,
|
||||||
|
s.array(stringIdentifier, {
|
||||||
|
uniqueItems: true,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
{
|
||||||
|
default: [],
|
||||||
|
coerce: (v): string[] => {
|
||||||
|
if (Array.isArray(v)) {
|
||||||
|
return v;
|
||||||
|
} else if (typeof v === "string") {
|
||||||
|
if (v.includes(",")) {
|
||||||
|
return v.split(",");
|
||||||
|
}
|
||||||
|
return [v];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// -------
|
||||||
|
// sorting
|
||||||
|
const sortDefault = { by: "id", dir: "asc" };
|
||||||
|
const sortSchema = s.object({
|
||||||
|
by: s.string(),
|
||||||
|
dir: s.string({ enum: ["asc", "desc"] }).optional(),
|
||||||
|
});
|
||||||
|
type SortSchema = s.Static<typeof sortSchema>;
|
||||||
|
const sort = s.anyOf([s.string(), sortSchema], {
|
||||||
|
default: sortDefault,
|
||||||
|
coerce: (v): SortSchema => {
|
||||||
|
if (typeof v === "string") {
|
||||||
|
if (/^-?[a-zA-Z_][a-zA-Z0-9_.]*$/.test(v)) {
|
||||||
|
const dir = v[0] === "-" ? "desc" : "asc";
|
||||||
|
return { by: dir === "desc" ? v.slice(1) : v, dir } as any;
|
||||||
|
} else if (/^{.*}$/.test(v)) {
|
||||||
|
return JSON.parse(v) as any;
|
||||||
|
}
|
||||||
|
|
||||||
|
$console.warn(`Invalid sort given: '${JSON.stringify(v)}'`);
|
||||||
|
return sortDefault as any;
|
||||||
|
}
|
||||||
|
return v as any;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// ------
|
||||||
|
// filter
|
||||||
|
const where = s.anyOf([s.string(), s.object({})], {
|
||||||
|
default: {},
|
||||||
|
coerce: (value: unknown) => {
|
||||||
|
const q = typeof value === "string" ? JSON.parse(value) : value;
|
||||||
|
return WhereBuilder.convert(q);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
//type WhereSchemaIn = s.Static<typeof where>;
|
||||||
|
//type WhereSchema = s.StaticCoerced<typeof where>;
|
||||||
|
|
||||||
|
// ------
|
||||||
|
// with
|
||||||
|
// @todo: waiting for recursion support
|
||||||
|
export type RepoWithSchema = Record<
|
||||||
|
string,
|
||||||
|
Omit<RepoQueryIn, "with"> & {
|
||||||
|
with?: unknown;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
|
||||||
|
const withSchema = <In, Out = In>(self: s.TSchema): s.TSchemaInOut<In, Out> =>
|
||||||
|
s.anyOf([stringIdentifier, s.array(stringIdentifier), self], {
|
||||||
|
coerce: function (this: TAnyOf<any>, _value: unknown, opts: CoercionOptions = {}) {
|
||||||
|
let value: any = _value;
|
||||||
|
|
||||||
|
if (typeof value === "string") {
|
||||||
|
// if stringified object
|
||||||
|
if (value.match(/^\{/) || value.match(/^\[/)) {
|
||||||
|
value = JSON.parse(value);
|
||||||
|
} else if (value.includes(",")) {
|
||||||
|
value = value.split(",");
|
||||||
|
} else {
|
||||||
|
value = [value];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert arrays to objects
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
value = value.reduce((acc, v) => {
|
||||||
|
acc[v] = {};
|
||||||
|
return acc;
|
||||||
|
}, {} as any);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle object case
|
||||||
|
if (isObject(value)) {
|
||||||
|
for (const k in value) {
|
||||||
|
value[k] = self.coerce(value[k], opts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value as unknown as any;
|
||||||
|
},
|
||||||
|
}) as any;
|
||||||
|
|
||||||
|
// ==========
|
||||||
|
// REPO QUERY
|
||||||
|
export const repoQuery = s.recursive((self) =>
|
||||||
|
s.partialObject({
|
||||||
|
limit: numberOrString({ default: 10 }),
|
||||||
|
offset: numberOrString({ default: 0 }),
|
||||||
|
sort,
|
||||||
|
where,
|
||||||
|
select: stringArray,
|
||||||
|
join: stringArray,
|
||||||
|
with: withSchema<RepoWithSchema>(self),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
export const getRepoQueryTemplate = () =>
|
||||||
|
repoQuery.template({
|
||||||
|
withOptional: true,
|
||||||
|
}) as Required<RepoQuery>;
|
||||||
|
|
||||||
|
export type RepoQueryIn = {
|
||||||
|
limit?: number;
|
||||||
|
offset?: number;
|
||||||
|
sort?: string | { by: string; dir: "asc" | "desc" };
|
||||||
|
select?: string[];
|
||||||
|
with?: string | string[] | Record<string, RepoQueryIn>;
|
||||||
|
join?: string[];
|
||||||
|
where?: WhereQuery;
|
||||||
|
};
|
||||||
|
export type RepoQuery = s.StaticCoerced<typeof repoQuery>;
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import type { App } from "App";
|
import type { App } from "App";
|
||||||
import { type Context, Hono } from "hono";
|
import { type Context, type Env, Hono } from "hono";
|
||||||
import * as middlewares from "modules/middlewares";
|
import * as middlewares from "modules/middlewares";
|
||||||
import type { SafeUser } from "auth";
|
import type { SafeUser } from "auth";
|
||||||
|
|
||||||
export type ServerEnv = {
|
export type ServerEnv = Env & {
|
||||||
Variables: {
|
Variables: {
|
||||||
app: App;
|
app: App;
|
||||||
// to prevent resolving auth multiple times
|
// to prevent resolving auth multiple times
|
||||||
|
|||||||
@@ -1,46 +1,43 @@
|
|||||||
import {
|
import { decodeSearch, encodeSearch, parseDecode } from "core/utils";
|
||||||
type Static,
|
|
||||||
type StaticDecode,
|
|
||||||
type TSchema,
|
|
||||||
decodeSearch,
|
|
||||||
encodeSearch,
|
|
||||||
parseDecode,
|
|
||||||
} from "core/utils";
|
|
||||||
import { isEqual, transform } from "lodash-es";
|
import { isEqual, transform } from "lodash-es";
|
||||||
import { useLocation, useSearch as useWouterSearch } from "wouter";
|
import { useLocation, useSearch as useWouterSearch } from "wouter";
|
||||||
|
import { type s, parse } from "core/object/schema";
|
||||||
|
|
||||||
// @todo: migrate to Typebox
|
// @todo: migrate to Typebox
|
||||||
export function useSearch<Schema extends TSchema = TSchema>(
|
export function useSearch<Schema extends s.TAnySchema = s.TAnySchema>(
|
||||||
schema: Schema,
|
schema: Schema,
|
||||||
defaultValue?: Partial<StaticDecode<Schema>>,
|
defaultValue?: Partial<s.StaticCoerced<Schema>>,
|
||||||
) {
|
) {
|
||||||
const searchString = useWouterSearch();
|
const searchString = useWouterSearch();
|
||||||
const [location, navigate] = useLocation();
|
const [location, navigate] = useLocation();
|
||||||
let value: StaticDecode<Schema> = defaultValue ? parseDecode(schema, defaultValue as any) : {};
|
let value = (defaultValue ? parse(schema, defaultValue as any) : {}) as s.StaticCoerced<Schema>;
|
||||||
|
|
||||||
if (searchString.length > 0) {
|
if (searchString.length > 0) {
|
||||||
value = parseDecode(schema, decodeSearch(searchString));
|
value = parse(schema, decodeSearch(searchString));
|
||||||
//console.log("search:decode", value);
|
//console.log("search:decode", value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// @todo: add option to set multiple keys at once
|
// @todo: add option to set multiple keys at once
|
||||||
function set<Key extends keyof Static<Schema>>(key: Key, value: Static<Schema>[Key]): void {
|
function set<Key extends keyof s.StaticCoerced<Schema>>(
|
||||||
|
key: Key,
|
||||||
|
value: s.StaticCoerced<Schema>[Key],
|
||||||
|
): void {
|
||||||
//console.log("set", key, value);
|
//console.log("set", key, value);
|
||||||
const update = parseDecode(schema, { ...decodeSearch(searchString), [key]: value });
|
const update = parse(schema, { ...decodeSearch(searchString), [key]: value });
|
||||||
const search = transform(
|
const search = transform(
|
||||||
update as any,
|
update as any,
|
||||||
(result, value, key) => {
|
(result, value, key) => {
|
||||||
if (defaultValue && isEqual(value, defaultValue[key])) return;
|
if (defaultValue && isEqual(value, defaultValue[key])) return;
|
||||||
result[key] = value;
|
result[key] = value;
|
||||||
},
|
},
|
||||||
{} as Static<Schema>,
|
{} as s.StaticCoerced<Schema>,
|
||||||
);
|
);
|
||||||
const encoded = encodeSearch(search, { encode: false });
|
const encoded = encodeSearch(search, { encode: false });
|
||||||
navigate(location + (encoded.length > 0 ? "?" + encoded : ""));
|
navigate(location + (encoded.length > 0 ? "?" + encoded : ""));
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
value: value as Required<StaticDecode<Schema>>,
|
value: value as Required<s.StaticCoerced<Schema>>,
|
||||||
set,
|
set,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,7 @@ import { Breadcrumbs2 } from "ui/layouts/AppShell/Breadcrumbs2";
|
|||||||
import { routes } from "ui/lib/routes";
|
import { routes } from "ui/lib/routes";
|
||||||
import { EntityForm } from "ui/modules/data/components/EntityForm";
|
import { EntityForm } from "ui/modules/data/components/EntityForm";
|
||||||
import { useEntityForm } from "ui/modules/data/hooks/useEntityForm";
|
import { useEntityForm } from "ui/modules/data/hooks/useEntityForm";
|
||||||
import * as tbbox from "@sinclair/typebox";
|
import { s } from "core/object/schema";
|
||||||
const { Type } = tbbox;
|
|
||||||
|
|
||||||
export function DataEntityCreate({ params }) {
|
export function DataEntityCreate({ params }) {
|
||||||
const { $data } = useBkndData();
|
const { $data } = useBkndData();
|
||||||
@@ -29,7 +28,7 @@ export function DataEntityCreate({ params }) {
|
|||||||
const $q = useEntityMutate(entity.name);
|
const $q = useEntityMutate(entity.name);
|
||||||
|
|
||||||
// @todo: use entity schema for prefilling
|
// @todo: use entity schema for prefilling
|
||||||
const search = useSearch(Type.Object({}), {});
|
const search = useSearch(s.object({}), {});
|
||||||
|
|
||||||
function goBack() {
|
function goBack() {
|
||||||
window.history.go(-1);
|
window.history.go(-1);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { type Entity, querySchema } from "data";
|
import { type Entity, repoQuery } from "data";
|
||||||
import { Fragment } from "react";
|
import { Fragment } from "react";
|
||||||
import { TbDots } from "react-icons/tb";
|
import { TbDots } from "react-icons/tb";
|
||||||
import { useApiQuery } from "ui/client";
|
import { useApiQuery } from "ui/client";
|
||||||
@@ -14,20 +14,14 @@ import * as AppShell from "ui/layouts/AppShell/AppShell";
|
|||||||
import { routes, useNavigate } from "ui/lib/routes";
|
import { routes, useNavigate } from "ui/lib/routes";
|
||||||
import { useCreateUserModal } from "ui/modules/auth/hooks/use-create-user-modal";
|
import { useCreateUserModal } from "ui/modules/auth/hooks/use-create-user-modal";
|
||||||
import { EntityTable2 } from "ui/modules/data/components/EntityTable2";
|
import { EntityTable2 } from "ui/modules/data/components/EntityTable2";
|
||||||
import * as tbbox from "@sinclair/typebox";
|
import { s } from "core/object/schema";
|
||||||
const { Type } = tbbox;
|
import { pick } from "core/utils/objects";
|
||||||
|
|
||||||
// @todo: migrate to Typebox
|
const searchSchema = s.partialObject({
|
||||||
const searchSchema = Type.Composite(
|
...pick(repoQuery.properties, ["select", "where", "sort"]),
|
||||||
[
|
page: s.number({ default: 1 }).optional(),
|
||||||
Type.Pick(querySchema, ["select", "where", "sort"]),
|
perPage: s.number({ default: 10 }).optional(),
|
||||||
Type.Object({
|
});
|
||||||
page: Type.Optional(Type.Number({ default: 1 })),
|
|
||||||
perPage: Type.Optional(Type.Number({ default: 10 })),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
{ additionalProperties: false },
|
|
||||||
);
|
|
||||||
|
|
||||||
const PER_PAGE_OPTIONS = [5, 10, 25];
|
const PER_PAGE_OPTIONS = [5, 10, 25];
|
||||||
|
|
||||||
@@ -74,8 +68,6 @@ export function DataEntityList({ params }) {
|
|||||||
const sort = search.value.sort!;
|
const sort = search.value.sort!;
|
||||||
const newSort = { by: name, dir: sort.by === name && sort.dir === "asc" ? "desc" : "asc" };
|
const newSort = { by: name, dir: sort.by === name && sort.dir === "asc" ? "desc" : "asc" };
|
||||||
|
|
||||||
// // @ts-expect-error - somehow all search keys are optional
|
|
||||||
console.log("new sort", newSort);
|
|
||||||
search.set("sort", newSort as any);
|
search.set("sort", newSort as any);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
102
bun.lock
102
bun.lock
@@ -27,18 +27,17 @@
|
|||||||
},
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"name": "bknd",
|
"name": "bknd",
|
||||||
"version": "0.11.0",
|
"version": "0.12.0",
|
||||||
"bin": "./dist/cli/index.js",
|
"bin": "./dist/cli/index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cfworker/json-schema": "^4.1.1",
|
"@cfworker/json-schema": "^4.1.1",
|
||||||
"@codemirror/lang-html": "^6.4.9",
|
"@codemirror/lang-html": "^6.4.9",
|
||||||
"@codemirror/lang-json": "^6.0.1",
|
"@codemirror/lang-json": "^6.0.1",
|
||||||
"@codemirror/lang-liquid": "^6.2.2",
|
|
||||||
"@hello-pangea/dnd": "^18.0.1",
|
"@hello-pangea/dnd": "^18.0.1",
|
||||||
"@libsql/client": "^0.15.2",
|
"@libsql/client": "^0.15.2",
|
||||||
"@mantine/core": "^7.17.1",
|
"@mantine/core": "^7.17.1",
|
||||||
"@mantine/hooks": "^7.17.1",
|
"@mantine/hooks": "^7.17.1",
|
||||||
"@sinclair/typebox": "^0.34.30",
|
"@sinclair/typebox": "0.34.30",
|
||||||
"@tanstack/react-form": "^1.0.5",
|
"@tanstack/react-form": "^1.0.5",
|
||||||
"@uiw/react-codemirror": "^4.23.10",
|
"@uiw/react-codemirror": "^4.23.10",
|
||||||
"@xyflow/react": "^12.4.4",
|
"@xyflow/react": "^12.4.4",
|
||||||
@@ -48,17 +47,15 @@
|
|||||||
"fast-xml-parser": "^5.0.8",
|
"fast-xml-parser": "^5.0.8",
|
||||||
"hono": "^4.7.4",
|
"hono": "^4.7.4",
|
||||||
"json-schema-form-react": "^0.0.2",
|
"json-schema-form-react": "^0.0.2",
|
||||||
"json-schema-library": "^10.0.0-rc7",
|
"json-schema-library": "10.0.0-rc7",
|
||||||
"json-schema-to-ts": "^3.1.1",
|
"json-schema-to-ts": "^3.1.1",
|
||||||
|
"jsonv-ts": "^0.0.11",
|
||||||
"kysely": "^0.27.6",
|
"kysely": "^0.27.6",
|
||||||
"liquidjs": "^10.21.0",
|
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"oauth4webapi": "^2.11.1",
|
"oauth4webapi": "^2.11.1",
|
||||||
"object-path-immutable": "^4.1.2",
|
"object-path-immutable": "^4.1.2",
|
||||||
"picocolors": "^1.1.1",
|
|
||||||
"radix-ui": "^1.1.3",
|
"radix-ui": "^1.1.3",
|
||||||
"swr": "^2.3.3",
|
"swr": "^2.3.3",
|
||||||
"wrangler": "^4.4.1",
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.758.0",
|
"@aws-sdk/client-s3": "^3.758.0",
|
||||||
@@ -90,6 +87,7 @@
|
|||||||
"kysely-d1": "^0.3.0",
|
"kysely-d1": "^0.3.0",
|
||||||
"open": "^10.1.0",
|
"open": "^10.1.0",
|
||||||
"openapi-types": "^12.1.3",
|
"openapi-types": "^12.1.3",
|
||||||
|
"picocolors": "^1.1.1",
|
||||||
"postcss": "^8.5.3",
|
"postcss": "^8.5.3",
|
||||||
"postcss-preset-mantine": "^1.17.0",
|
"postcss-preset-mantine": "^1.17.0",
|
||||||
"postcss-simple-vars": "^7.0.1",
|
"postcss-simple-vars": "^7.0.1",
|
||||||
@@ -514,8 +512,6 @@
|
|||||||
|
|
||||||
"@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.3.4", "", { "dependencies": { "mime": "^3.0.0" } }, "sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q=="],
|
"@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.3.4", "", { "dependencies": { "mime": "^3.0.0" } }, "sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q=="],
|
||||||
|
|
||||||
"@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.3.0", "", { "peerDependencies": { "unenv": "2.0.0-rc.15", "workerd": "^1.20250311.0" }, "optionalPeers": ["workerd"] }, "sha512-AaKYnbFpHaVDZGh3Hjy3oLYd12+LZw9aupAOudYJ+tjekahxcIqlSAr0zK9kPOdtgn10tzaqH7QJFUWcLE+k7g=="],
|
|
||||||
|
|
||||||
"@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20250224.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-sBbaAF2vgQ9+T50ik1ihekdepStBp0w4fvNghBfXIw1iWqfNWnypcjDMmi/7JhXJt2uBxBrSlXCvE5H7Gz+kbw=="],
|
"@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20250224.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-sBbaAF2vgQ9+T50ik1ihekdepStBp0w4fvNghBfXIw1iWqfNWnypcjDMmi/7JhXJt2uBxBrSlXCvE5H7Gz+kbw=="],
|
||||||
|
|
||||||
"@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20250224.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-naetGefgjAaDbEacpwaVruJXNwxmRRL7v3ppStgEiqAlPmTpQ/Edjn2SQ284QwOw3MvaVPHrWcaTBupUpkqCyg=="],
|
"@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20250224.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-naetGefgjAaDbEacpwaVruJXNwxmRRL7v3ppStgEiqAlPmTpQ/Edjn2SQ284QwOw3MvaVPHrWcaTBupUpkqCyg=="],
|
||||||
@@ -542,8 +538,6 @@
|
|||||||
|
|
||||||
"@codemirror/lang-json": ["@codemirror/lang-json@6.0.1", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@lezer/json": "^1.0.0" } }, "sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ=="],
|
"@codemirror/lang-json": ["@codemirror/lang-json@6.0.1", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@lezer/json": "^1.0.0" } }, "sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ=="],
|
||||||
|
|
||||||
"@codemirror/lang-liquid": ["@codemirror/lang-liquid@6.2.2", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/lang-html": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", "@lezer/common": "^1.0.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.3.1" } }, "sha512-7Dm841fk37+JQW6j2rI1/uGkJyESrjzyhiIkaLjbbR0U6aFFQvMrJn35WxQreRMADMhzkyVkZM4467OR7GR8nQ=="],
|
|
||||||
|
|
||||||
"@codemirror/language": ["@codemirror/language@6.10.8", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.23.0", "@lezer/common": "^1.1.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0", "style-mod": "^4.0.0" } }, "sha512-wcP8XPPhDH2vTqf181U8MbZnW+tDyPYy0UzVOa+oHORjyT+mhhom9vBd7dApJwoDz9Nb/a8kHjJIsuA/t8vNFw=="],
|
"@codemirror/language": ["@codemirror/language@6.10.8", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.23.0", "@lezer/common": "^1.1.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0", "style-mod": "^4.0.0" } }, "sha512-wcP8XPPhDH2vTqf181U8MbZnW+tDyPYy0UzVOa+oHORjyT+mhhom9vBd7dApJwoDz9Nb/a8kHjJIsuA/t8vNFw=="],
|
||||||
|
|
||||||
"@codemirror/lint": ["@codemirror/lint@6.8.4", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.35.0", "crelt": "^1.0.5" } }, "sha512-u4q7PnZlJUojeRe8FJa/njJcMctISGgPQ4PnWsd9268R4ZTtU+tfFYmwkBvgcrK2+QQ8tYFVALVb5fVJykKc5A=="],
|
"@codemirror/lint": ["@codemirror/lint@6.8.4", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.35.0", "crelt": "^1.0.5" } }, "sha512-u4q7PnZlJUojeRe8FJa/njJcMctISGgPQ4PnWsd9268R4ZTtU+tfFYmwkBvgcrK2+QQ8tYFVALVb5fVJykKc5A=="],
|
||||||
@@ -2038,8 +2032,6 @@
|
|||||||
|
|
||||||
"express-rate-limit": ["express-rate-limit@5.5.1", "", {}, "sha512-MTjE2eIbHv5DyfuFz4zLYWxpqVhEhkTiwFGuB74Q9CSou2WHO52nlE5y3Zlg6SIsiYUIPj6ifFxnkPz6O3sIUg=="],
|
"express-rate-limit": ["express-rate-limit@5.5.1", "", {}, "sha512-MTjE2eIbHv5DyfuFz4zLYWxpqVhEhkTiwFGuB74Q9CSou2WHO52nlE5y3Zlg6SIsiYUIPj6ifFxnkPz6O3sIUg=="],
|
||||||
|
|
||||||
"exsolve": ["exsolve@1.0.4", "", {}, "sha512-xsZH6PXaER4XoV+NiT7JHp1bJodJVT+cxeSH1G0f0tlT0lJqYuHUP3bUx2HtfTDvOagMINYp8rsqusxud3RXhw=="],
|
|
||||||
|
|
||||||
"extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
|
"extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
|
||||||
|
|
||||||
"extend-shallow": ["extend-shallow@3.0.2", "", { "dependencies": { "assign-symbols": "^1.0.0", "is-extendable": "^1.0.1" } }, "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q=="],
|
"extend-shallow": ["extend-shallow@3.0.2", "", { "dependencies": { "assign-symbols": "^1.0.0", "is-extendable": "^1.0.1" } }, "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q=="],
|
||||||
@@ -2526,6 +2518,8 @@
|
|||||||
|
|
||||||
"jsonpointer": ["jsonpointer@5.0.1", "", {}, "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ=="],
|
"jsonpointer": ["jsonpointer@5.0.1", "", {}, "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ=="],
|
||||||
|
|
||||||
|
"jsonv-ts": ["jsonv-ts@0.0.11", "", { "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-W5WC6iwQvOuB0gRaAW9jAQKqT56pXjTA7XCjjAXZIM92/VBVNczTmV7iPtClqV1Zpgy4CtzaUsOJj4kWNeB5YQ=="],
|
||||||
|
|
||||||
"jsonwebtoken": ["jsonwebtoken@9.0.2", "", { "dependencies": { "jws": "^3.2.2", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ=="],
|
"jsonwebtoken": ["jsonwebtoken@9.0.2", "", { "dependencies": { "jws": "^3.2.2", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ=="],
|
||||||
|
|
||||||
"jsprim": ["jsprim@2.0.2", "", { "dependencies": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", "json-schema": "0.4.0", "verror": "1.10.0" } }, "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ=="],
|
"jsprim": ["jsprim@2.0.2", "", { "dependencies": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", "json-schema": "0.4.0", "verror": "1.10.0" } }, "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ=="],
|
||||||
@@ -2586,8 +2580,6 @@
|
|||||||
|
|
||||||
"lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
|
"lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
|
||||||
|
|
||||||
"liquidjs": ["liquidjs@10.21.0", "", { "dependencies": { "commander": "^10.0.0" }, "bin": { "liquidjs": "bin/liquid.js", "liquid": "bin/liquid.js" } }, "sha512-DouqxNU2jfoZzb1LinVjOc/f6ssitGIxiDJT+kEKyYqPSSSd+WmGOAhtWbVm1/n75svu4aQ+FyQ3ctd3wh1bbw=="],
|
|
||||||
|
|
||||||
"load-tsconfig": ["load-tsconfig@0.2.5", "", {}, "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg=="],
|
"load-tsconfig": ["load-tsconfig@0.2.5", "", {}, "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg=="],
|
||||||
|
|
||||||
"locate-app": ["locate-app@2.5.0", "", { "dependencies": { "@promptbook/utils": "0.69.5", "type-fest": "4.26.0", "userhome": "1.0.1" } }, "sha512-xIqbzPMBYArJRmPGUZD9CzV9wOqmVtQnaAn3wrj3s6WYW0bQvPI7x+sPYUGmDTYMHefVK//zc6HEYZ1qnxIK+Q=="],
|
"locate-app": ["locate-app@2.5.0", "", { "dependencies": { "@promptbook/utils": "0.69.5", "type-fest": "4.26.0", "userhome": "1.0.1" } }, "sha512-xIqbzPMBYArJRmPGUZD9CzV9wOqmVtQnaAn3wrj3s6WYW0bQvPI7x+sPYUGmDTYMHefVK//zc6HEYZ1qnxIK+Q=="],
|
||||||
@@ -3858,8 +3850,6 @@
|
|||||||
|
|
||||||
"@bundled-es-modules/tough-cookie/tough-cookie": ["tough-cookie@4.1.4", "", { "dependencies": { "psl": "^1.1.33", "punycode": "^2.1.1", "universalify": "^0.2.0", "url-parse": "^1.5.3" } }, "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag=="],
|
"@bundled-es-modules/tough-cookie/tough-cookie": ["tough-cookie@4.1.4", "", { "dependencies": { "psl": "^1.1.33", "punycode": "^2.1.1", "universalify": "^0.2.0", "url-parse": "^1.5.3" } }, "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag=="],
|
||||||
|
|
||||||
"@cloudflare/unenv-preset/unenv": ["unenv@2.0.0-rc.15", "", { "dependencies": { "defu": "^6.1.4", "exsolve": "^1.0.4", "ohash": "^2.0.11", "pathe": "^2.0.3", "ufo": "^1.5.4" } }, "sha512-J/rEIZU8w6FOfLNz/hNKsnY+fFHWnu9MH4yRbSZF3xbbGHovcetXPs7sD+9p8L6CeNC//I9bhRYAOsBt2u7/OA=="],
|
|
||||||
|
|
||||||
"@emnapi/runtime/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
"@emnapi/runtime/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||||
|
|
||||||
"@inquirer/core/cli-width": ["cli-width@4.1.0", "", {}, "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ=="],
|
"@inquirer/core/cli-width": ["cli-width@4.1.0", "", {}, "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ=="],
|
||||||
@@ -4164,8 +4154,6 @@
|
|||||||
|
|
||||||
"bknd/vitest": ["vitest@3.0.9", "", { "dependencies": { "@vitest/expect": "3.0.9", "@vitest/mocker": "3.0.9", "@vitest/pretty-format": "^3.0.9", "@vitest/runner": "3.0.9", "@vitest/snapshot": "3.0.9", "@vitest/spy": "3.0.9", "@vitest/utils": "3.0.9", "chai": "^5.2.0", "debug": "^4.4.0", "expect-type": "^1.1.0", "magic-string": "^0.30.17", "pathe": "^2.0.3", "std-env": "^3.8.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinypool": "^1.0.2", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0", "vite-node": "3.0.9", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "@vitest/browser": "3.0.9", "@vitest/ui": "3.0.9", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/debug", "@types/node", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-BbcFDqNyBlfSpATmTtXOAOj71RNKDDvjBM/uPfnxxVGrG+FSH2RQIwgeEngTaTkuU/h0ScFvf+tRcKfYXzBybQ=="],
|
"bknd/vitest": ["vitest@3.0.9", "", { "dependencies": { "@vitest/expect": "3.0.9", "@vitest/mocker": "3.0.9", "@vitest/pretty-format": "^3.0.9", "@vitest/runner": "3.0.9", "@vitest/snapshot": "3.0.9", "@vitest/spy": "3.0.9", "@vitest/utils": "3.0.9", "chai": "^5.2.0", "debug": "^4.4.0", "expect-type": "^1.1.0", "magic-string": "^0.30.17", "pathe": "^2.0.3", "std-env": "^3.8.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinypool": "^1.0.2", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0", "vite-node": "3.0.9", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "@vitest/browser": "3.0.9", "@vitest/ui": "3.0.9", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/debug", "@types/node", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-BbcFDqNyBlfSpATmTtXOAOj71RNKDDvjBM/uPfnxxVGrG+FSH2RQIwgeEngTaTkuU/h0ScFvf+tRcKfYXzBybQ=="],
|
||||||
|
|
||||||
"bknd/wrangler": ["wrangler@4.4.1", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.0", "@cloudflare/unenv-preset": "2.3.0", "blake3-wasm": "2.1.5", "esbuild": "0.24.2", "miniflare": "4.20250321.0", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.15", "workerd": "1.20250321.0" }, "optionalDependencies": { "fsevents": "~2.3.2", "sharp": "^0.33.5" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20250321.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-EFwr7hiVeAmPOuOGQ7HFfeaLKLxEXQMJ86kyn6RFB8pGjMEUtvZMsVa9cPubKkKgNi3WcDEFeFLalclGyq+tGA=="],
|
|
||||||
|
|
||||||
"bknd-cli/@libsql/client": ["@libsql/client@0.14.0", "", { "dependencies": { "@libsql/core": "^0.14.0", "@libsql/hrana-client": "^0.7.0", "js-base64": "^3.7.5", "libsql": "^0.4.4", "promise-limit": "^2.7.0" } }, "sha512-/9HEKfn6fwXB5aTEEoMeFh4CtG0ZzbncBb1e++OCdVpgKZ/xyMsIVYXm0w7Pv4RUel803vE6LwniB3PqD72R0Q=="],
|
"bknd-cli/@libsql/client": ["@libsql/client@0.14.0", "", { "dependencies": { "@libsql/core": "^0.14.0", "@libsql/hrana-client": "^0.7.0", "js-base64": "^3.7.5", "libsql": "^0.4.4", "promise-limit": "^2.7.0" } }, "sha512-/9HEKfn6fwXB5aTEEoMeFh4CtG0ZzbncBb1e++OCdVpgKZ/xyMsIVYXm0w7Pv4RUel803vE6LwniB3PqD72R0Q=="],
|
||||||
|
|
||||||
"body-parser/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
|
"body-parser/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
|
||||||
@@ -4428,8 +4416,6 @@
|
|||||||
|
|
||||||
"libsql/detect-libc": ["detect-libc@2.0.2", "", {}, "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw=="],
|
"libsql/detect-libc": ["detect-libc@2.0.2", "", {}, "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw=="],
|
||||||
|
|
||||||
"liquidjs/commander": ["commander@10.0.1", "", {}, "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug=="],
|
|
||||||
|
|
||||||
"locate-app/type-fest": ["type-fest@4.26.0", "", {}, "sha512-OduNjVJsFbifKb57UqZ2EMP1i4u64Xwow3NYXUtBbD4vIwJdQd4+xl8YDou1dlm4DVrtwT/7Ky8z8WyCULVfxw=="],
|
"locate-app/type-fest": ["type-fest@4.26.0", "", {}, "sha512-OduNjVJsFbifKb57UqZ2EMP1i4u64Xwow3NYXUtBbD4vIwJdQd4+xl8YDou1dlm4DVrtwT/7Ky8z8WyCULVfxw=="],
|
||||||
|
|
||||||
"log-update/ansi-escapes": ["ansi-escapes@3.2.0", "", {}, "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ=="],
|
"log-update/ansi-escapes": ["ansi-escapes@3.2.0", "", {}, "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ=="],
|
||||||
@@ -4708,8 +4694,6 @@
|
|||||||
|
|
||||||
"@bundled-es-modules/tough-cookie/tough-cookie/universalify": ["universalify@0.2.0", "", {}, "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg=="],
|
"@bundled-es-modules/tough-cookie/tough-cookie/universalify": ["universalify@0.2.0", "", {}, "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg=="],
|
||||||
|
|
||||||
"@cloudflare/unenv-preset/unenv/ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="],
|
|
||||||
|
|
||||||
"@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
|
"@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
|
||||||
|
|
||||||
"@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="],
|
"@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="],
|
||||||
@@ -4790,16 +4774,6 @@
|
|||||||
|
|
||||||
"bknd-cli/@libsql/client/libsql": ["libsql@0.4.7", "", { "dependencies": { "@neon-rs/load": "^0.0.4", "detect-libc": "2.0.2" }, "optionalDependencies": { "@libsql/darwin-arm64": "0.4.7", "@libsql/darwin-x64": "0.4.7", "@libsql/linux-arm64-gnu": "0.4.7", "@libsql/linux-arm64-musl": "0.4.7", "@libsql/linux-x64-gnu": "0.4.7", "@libsql/linux-x64-musl": "0.4.7", "@libsql/win32-x64-msvc": "0.4.7" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ] }, "sha512-T9eIRCs6b0J1SHKYIvD8+KCJMcWZ900iZyxdnSCdqxN12Z1ijzT+jY5nrk72Jw4B0HGzms2NgpryArlJqvc3Lw=="],
|
"bknd-cli/@libsql/client/libsql": ["libsql@0.4.7", "", { "dependencies": { "@neon-rs/load": "^0.0.4", "detect-libc": "2.0.2" }, "optionalDependencies": { "@libsql/darwin-arm64": "0.4.7", "@libsql/darwin-x64": "0.4.7", "@libsql/linux-arm64-gnu": "0.4.7", "@libsql/linux-arm64-musl": "0.4.7", "@libsql/linux-x64-gnu": "0.4.7", "@libsql/linux-x64-musl": "0.4.7", "@libsql/win32-x64-msvc": "0.4.7" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ] }, "sha512-T9eIRCs6b0J1SHKYIvD8+KCJMcWZ900iZyxdnSCdqxN12Z1ijzT+jY5nrk72Jw4B0HGzms2NgpryArlJqvc3Lw=="],
|
||||||
|
|
||||||
"bknd/wrangler/@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.4.0", "", { "dependencies": { "mime": "^3.0.0" } }, "sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA=="],
|
|
||||||
|
|
||||||
"bknd/wrangler/esbuild": ["esbuild@0.24.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.24.2", "@esbuild/android-arm": "0.24.2", "@esbuild/android-arm64": "0.24.2", "@esbuild/android-x64": "0.24.2", "@esbuild/darwin-arm64": "0.24.2", "@esbuild/darwin-x64": "0.24.2", "@esbuild/freebsd-arm64": "0.24.2", "@esbuild/freebsd-x64": "0.24.2", "@esbuild/linux-arm": "0.24.2", "@esbuild/linux-arm64": "0.24.2", "@esbuild/linux-ia32": "0.24.2", "@esbuild/linux-loong64": "0.24.2", "@esbuild/linux-mips64el": "0.24.2", "@esbuild/linux-ppc64": "0.24.2", "@esbuild/linux-riscv64": "0.24.2", "@esbuild/linux-s390x": "0.24.2", "@esbuild/linux-x64": "0.24.2", "@esbuild/netbsd-arm64": "0.24.2", "@esbuild/netbsd-x64": "0.24.2", "@esbuild/openbsd-arm64": "0.24.2", "@esbuild/openbsd-x64": "0.24.2", "@esbuild/sunos-x64": "0.24.2", "@esbuild/win32-arm64": "0.24.2", "@esbuild/win32-ia32": "0.24.2", "@esbuild/win32-x64": "0.24.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA=="],
|
|
||||||
|
|
||||||
"bknd/wrangler/miniflare": ["miniflare@4.20250321.0", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", "stoppable": "1.1.0", "undici": "^5.28.5", "workerd": "1.20250321.0", "ws": "8.18.0", "youch": "3.2.3", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-os+NJA7Eqi00BJHdVhzIa+3PMotnCtZg3hiUIRYcsZF5W7He8SK2EkV8csAb+npZq3jZ4SNpDebO01swM5dcWw=="],
|
|
||||||
|
|
||||||
"bknd/wrangler/unenv": ["unenv@2.0.0-rc.15", "", { "dependencies": { "defu": "^6.1.4", "exsolve": "^1.0.4", "ohash": "^2.0.11", "pathe": "^2.0.3", "ufo": "^1.5.4" } }, "sha512-J/rEIZU8w6FOfLNz/hNKsnY+fFHWnu9MH4yRbSZF3xbbGHovcetXPs7sD+9p8L6CeNC//I9bhRYAOsBt2u7/OA=="],
|
|
||||||
|
|
||||||
"bknd/wrangler/workerd": ["workerd@1.20250321.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20250321.0", "@cloudflare/workerd-darwin-arm64": "1.20250321.0", "@cloudflare/workerd-linux-64": "1.20250321.0", "@cloudflare/workerd-linux-arm64": "1.20250321.0", "@cloudflare/workerd-windows-64": "1.20250321.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-vyuz9pdJ+7o1lC79vQ2UVRLXPARa2Lq94PbTfqEcYQeSxeR9X+YqhNq2yysv8Zs5vpokmexLCtMniPp9u+2LVQ=="],
|
|
||||||
|
|
||||||
"body-parser/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
|
"body-parser/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
|
||||||
|
|
||||||
"class-utils/define-property/is-descriptor": ["is-descriptor@0.1.7", "", { "dependencies": { "is-accessor-descriptor": "^1.0.1", "is-data-descriptor": "^1.0.1" } }, "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg=="],
|
"class-utils/define-property/is-descriptor": ["is-descriptor@0.1.7", "", { "dependencies": { "is-accessor-descriptor": "^1.0.1", "is-data-descriptor": "^1.0.1" } }, "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg=="],
|
||||||
@@ -5304,68 +5278,6 @@
|
|||||||
|
|
||||||
"bknd-cli/@libsql/client/libsql/detect-libc": ["detect-libc@2.0.2", "", {}, "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw=="],
|
"bknd-cli/@libsql/client/libsql/detect-libc": ["detect-libc@2.0.2", "", {}, "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw=="],
|
||||||
|
|
||||||
"bknd/wrangler/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.24.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA=="],
|
|
||||||
|
|
||||||
"bknd/wrangler/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.24.2", "", { "os": "android", "cpu": "arm" }, "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q=="],
|
|
||||||
|
|
||||||
"bknd/wrangler/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.24.2", "", { "os": "android", "cpu": "arm64" }, "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg=="],
|
|
||||||
|
|
||||||
"bknd/wrangler/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.24.2", "", { "os": "android", "cpu": "x64" }, "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw=="],
|
|
||||||
|
|
||||||
"bknd/wrangler/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.24.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA=="],
|
|
||||||
|
|
||||||
"bknd/wrangler/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.24.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA=="],
|
|
||||||
|
|
||||||
"bknd/wrangler/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.24.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg=="],
|
|
||||||
|
|
||||||
"bknd/wrangler/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.24.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q=="],
|
|
||||||
|
|
||||||
"bknd/wrangler/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.24.2", "", { "os": "linux", "cpu": "arm" }, "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA=="],
|
|
||||||
|
|
||||||
"bknd/wrangler/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.24.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg=="],
|
|
||||||
|
|
||||||
"bknd/wrangler/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.24.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw=="],
|
|
||||||
|
|
||||||
"bknd/wrangler/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ=="],
|
|
||||||
|
|
||||||
"bknd/wrangler/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw=="],
|
|
||||||
|
|
||||||
"bknd/wrangler/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.24.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw=="],
|
|
||||||
|
|
||||||
"bknd/wrangler/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q=="],
|
|
||||||
|
|
||||||
"bknd/wrangler/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.24.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw=="],
|
|
||||||
|
|
||||||
"bknd/wrangler/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.24.2", "", { "os": "linux", "cpu": "x64" }, "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q=="],
|
|
||||||
|
|
||||||
"bknd/wrangler/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.24.2", "", { "os": "none", "cpu": "arm64" }, "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw=="],
|
|
||||||
|
|
||||||
"bknd/wrangler/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.24.2", "", { "os": "none", "cpu": "x64" }, "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw=="],
|
|
||||||
|
|
||||||
"bknd/wrangler/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.24.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A=="],
|
|
||||||
|
|
||||||
"bknd/wrangler/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.24.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA=="],
|
|
||||||
|
|
||||||
"bknd/wrangler/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.24.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig=="],
|
|
||||||
|
|
||||||
"bknd/wrangler/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.24.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ=="],
|
|
||||||
|
|
||||||
"bknd/wrangler/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.24.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA=="],
|
|
||||||
|
|
||||||
"bknd/wrangler/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.24.2", "", { "os": "win32", "cpu": "x64" }, "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg=="],
|
|
||||||
|
|
||||||
"bknd/wrangler/unenv/ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="],
|
|
||||||
|
|
||||||
"bknd/wrangler/workerd/@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20250321.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-y273GfLaNCxkL8hTfo0c8FZKkOPdq+CPZAKJXPWB+YpS1JCOULu6lNTptpD7ZtF14dTYPkn5Weug31TTlviJmw=="],
|
|
||||||
|
|
||||||
"bknd/wrangler/workerd/@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20250321.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-qvf7/gkkQq7fAsoMlntJSimN/WfwQqxi2oL0aWZMGodTvs/yRHO2I4oE0eOihVdK1BXyBHJXNxEvNDBjF0+Yuw=="],
|
|
||||||
|
|
||||||
"bknd/wrangler/workerd/@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20250321.0", "", { "os": "linux", "cpu": "x64" }, "sha512-AEp3xjWFrNPO/h0StCOgOb0bWCcNThnkESpy91Wf4mfUF2p7tOCdp37Nk/1QIRqSxnfv4Hgxyi7gcWud9cJuMw=="],
|
|
||||||
|
|
||||||
"bknd/wrangler/workerd/@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20250321.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-wRWyMIoPIS1UBXCisW0FYTgGsfZD4AVS0hXA5nuLc0c21CvzZpmmTjqEWMcwPFenwy/MNL61NautVOC4qJqQ3Q=="],
|
|
||||||
|
|
||||||
"bknd/wrangler/workerd/@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20250321.0", "", { "os": "win32", "cpu": "x64" }, "sha512-8vYP3QYO0zo2faUDfWl88jjfUvz7Si9GS3mUYaTh/TR9LcAUtsO7muLxPamqEyoxNFtbQgy08R4rTid94KRi3w=="],
|
|
||||||
|
|
||||||
"eslint/chalk/ansi-styles/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="],
|
"eslint/chalk/ansi-styles/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="],
|
||||||
|
|
||||||
"eslint/chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="],
|
"eslint/chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="],
|
||||||
|
|||||||
Reference in New Issue
Block a user