mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 04:27:21 +00:00
feat: adding initial uuid support
This commit is contained in:
@@ -110,4 +110,18 @@ describe("some tests", async () => {
|
|||||||
new EntityManager([entity, entity2], connection);
|
new EntityManager([entity, entity2], connection);
|
||||||
}).toThrow();
|
}).toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("primary uuid", async () => {
|
||||||
|
const entity = new Entity("users", [
|
||||||
|
new PrimaryField("id", { format: "uuid" }),
|
||||||
|
new TextField("username"),
|
||||||
|
]);
|
||||||
|
const em = new EntityManager([entity], getDummyConnection().dummyConnection);
|
||||||
|
await em.schema().sync({ force: true });
|
||||||
|
|
||||||
|
const mutator = em.mutator(entity);
|
||||||
|
const data = await mutator.insertOne({ username: "test" });
|
||||||
|
expect(data.data.id).toBeDefined();
|
||||||
|
expect(data.data.id).toBeString();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -70,7 +70,8 @@
|
|||||||
"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",
|
||||||
|
"uuid": "^11.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.758.0",
|
"@aws-sdk/client-s3": "^3.758.0",
|
||||||
@@ -99,7 +100,7 @@
|
|||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
"jotai": "^2.12.2",
|
"jotai": "^2.12.2",
|
||||||
"jsdom": "^26.0.0",
|
"jsdom": "^26.0.0",
|
||||||
"jsonv-ts": "^0.0.14-alpha.6",
|
"jsonv-ts": "^0.1.0",
|
||||||
"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",
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
*/
|
*/
|
||||||
import type { Generated } from "kysely";
|
import type { Generated } from "kysely";
|
||||||
|
|
||||||
export type PrimaryFieldType<IdType extends number = number> = IdType | Generated<IdType>;
|
export type PrimaryFieldType<IdType = number | string> = IdType | Generated<IdType>;
|
||||||
|
|
||||||
export interface AppEntity<IdType extends number = number> {
|
export interface AppEntity<IdType = number | string> {
|
||||||
id: PrimaryFieldType<IdType>;
|
id: PrimaryFieldType<IdType>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
|
import { v4, v7 } from "uuid";
|
||||||
|
|
||||||
// generates v4
|
// generates v4
|
||||||
export function uuid(): string {
|
export function uuid(): string {
|
||||||
return crypto.randomUUID();
|
return v4();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function uuidv7(): string {
|
||||||
|
return v7();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -233,6 +233,8 @@ export class DataController extends Controller {
|
|||||||
const hono = this.create();
|
const hono = this.create();
|
||||||
|
|
||||||
const entitiesEnum = this.getEntitiesEnum(this.em);
|
const entitiesEnum = this.getEntitiesEnum(this.em);
|
||||||
|
// @todo: make dynamic based on entity
|
||||||
|
const idType = s.anyOf([s.number(), s.string()], { coerce: (v) => v as any });
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function endpoints
|
* Function endpoints
|
||||||
@@ -333,7 +335,7 @@ export class DataController extends Controller {
|
|||||||
"param",
|
"param",
|
||||||
s.object({
|
s.object({
|
||||||
entity: entitiesEnum,
|
entity: entitiesEnum,
|
||||||
id: s.string(),
|
id: idType,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
jsc("query", repoQuery, { skipOpenAPI: true }),
|
jsc("query", repoQuery, { skipOpenAPI: true }),
|
||||||
@@ -342,8 +344,9 @@ export class DataController extends Controller {
|
|||||||
if (!this.entityExists(entity)) {
|
if (!this.entityExists(entity)) {
|
||||||
return this.notFound(c);
|
return this.notFound(c);
|
||||||
}
|
}
|
||||||
|
console.log("id", id);
|
||||||
const options = c.req.valid("query") as RepoQuery;
|
const options = c.req.valid("query") as RepoQuery;
|
||||||
const result = await this.em.repository(entity).findId(Number(id), options);
|
const result = await this.em.repository(entity).findId(id, options);
|
||||||
|
|
||||||
return c.json(this.repoResult(result), { status: result.data ? 200 : 404 });
|
return c.json(this.repoResult(result), { status: result.data ? 200 : 404 });
|
||||||
},
|
},
|
||||||
@@ -362,7 +365,7 @@ export class DataController extends Controller {
|
|||||||
"param",
|
"param",
|
||||||
s.object({
|
s.object({
|
||||||
entity: entitiesEnum,
|
entity: entitiesEnum,
|
||||||
id: s.string(),
|
id: idType,
|
||||||
reference: s.string(),
|
reference: s.string(),
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
@@ -376,7 +379,7 @@ export class DataController extends Controller {
|
|||||||
const options = c.req.valid("query") as RepoQuery;
|
const options = c.req.valid("query") as RepoQuery;
|
||||||
const result = await this.em
|
const result = await this.em
|
||||||
.repository(entity)
|
.repository(entity)
|
||||||
.findManyByReference(Number(id), reference, options);
|
.findManyByReference(id, reference, options);
|
||||||
|
|
||||||
return c.json(this.repoResult(result), { status: result.data ? 200 : 404 });
|
return c.json(this.repoResult(result), { status: result.data ? 200 : 404 });
|
||||||
},
|
},
|
||||||
@@ -485,7 +488,7 @@ export class DataController extends Controller {
|
|||||||
tags: ["data"],
|
tags: ["data"],
|
||||||
}),
|
}),
|
||||||
permission(DataPermissions.entityUpdate),
|
permission(DataPermissions.entityUpdate),
|
||||||
jsc("param", s.object({ entity: entitiesEnum, id: s.number() })),
|
jsc("param", s.object({ entity: entitiesEnum, id: idType })),
|
||||||
jsc("json", s.object({})),
|
jsc("json", s.object({})),
|
||||||
async (c) => {
|
async (c) => {
|
||||||
const { entity, id } = c.req.valid("param");
|
const { entity, id } = c.req.valid("param");
|
||||||
@@ -493,7 +496,7 @@ export class DataController extends Controller {
|
|||||||
return this.notFound(c);
|
return this.notFound(c);
|
||||||
}
|
}
|
||||||
const body = (await c.req.json()) as EntityData;
|
const body = (await c.req.json()) as EntityData;
|
||||||
const result = await this.em.mutator(entity).updateOne(Number(id), body);
|
const result = await this.em.mutator(entity).updateOne(id, body);
|
||||||
|
|
||||||
return c.json(this.mutatorResult(result));
|
return c.json(this.mutatorResult(result));
|
||||||
},
|
},
|
||||||
@@ -507,13 +510,13 @@ export class DataController extends Controller {
|
|||||||
tags: ["data"],
|
tags: ["data"],
|
||||||
}),
|
}),
|
||||||
permission(DataPermissions.entityDelete),
|
permission(DataPermissions.entityDelete),
|
||||||
jsc("param", s.object({ entity: entitiesEnum, id: s.number() })),
|
jsc("param", s.object({ entity: entitiesEnum, id: idType })),
|
||||||
async (c) => {
|
async (c) => {
|
||||||
const { entity, id } = c.req.valid("param");
|
const { entity, id } = c.req.valid("param");
|
||||||
if (!this.entityExists(entity)) {
|
if (!this.entityExists(entity)) {
|
||||||
return this.notFound(c);
|
return this.notFound(c);
|
||||||
}
|
}
|
||||||
const result = await this.em.mutator(entity).deleteOne(Number(id));
|
const result = await this.em.mutator(entity).deleteOne(id);
|
||||||
|
|
||||||
return c.json(this.mutatorResult(result));
|
return c.json(this.mutatorResult(result));
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -31,8 +31,12 @@ export class SqliteConnection extends Connection {
|
|||||||
type,
|
type,
|
||||||
(col: ColumnDefinitionBuilder) => {
|
(col: ColumnDefinitionBuilder) => {
|
||||||
if (spec.primary) {
|
if (spec.primary) {
|
||||||
|
if (spec.type === "integer") {
|
||||||
return col.primaryKey().notNull().autoIncrement();
|
return col.primaryKey().notNull().autoIncrement();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return col.primaryKey().notNull();
|
||||||
|
}
|
||||||
if (spec.references) {
|
if (spec.references) {
|
||||||
let relCol = col.references(spec.references);
|
let relCol = col.references(spec.references);
|
||||||
if (spec.onDelete) relCol = relCol.onDelete(spec.onDelete);
|
if (spec.onDelete) relCol = relCol.onDelete(spec.onDelete);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { type Static, StringRecord, objectTransform } from "core/utils";
|
import { type Static, StringEnum, StringRecord, objectTransform } from "core/utils";
|
||||||
import * as tb from "@sinclair/typebox";
|
import * as tb from "@sinclair/typebox";
|
||||||
import {
|
import {
|
||||||
FieldClassMap,
|
FieldClassMap,
|
||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
entityTypes,
|
entityTypes,
|
||||||
} from "data";
|
} from "data";
|
||||||
import { MediaField, mediaFieldConfigSchema } from "../media/MediaField";
|
import { MediaField, mediaFieldConfigSchema } from "../media/MediaField";
|
||||||
|
import { primaryFieldTypes } from "./fields";
|
||||||
|
|
||||||
export const FIELDS = {
|
export const FIELDS = {
|
||||||
...FieldClassMap,
|
...FieldClassMap,
|
||||||
@@ -72,6 +73,9 @@ export const indicesSchema = tb.Type.Object(
|
|||||||
export const dataConfigSchema = tb.Type.Object(
|
export const dataConfigSchema = tb.Type.Object(
|
||||||
{
|
{
|
||||||
basepath: tb.Type.Optional(tb.Type.String({ default: "/api/data" })),
|
basepath: tb.Type.Optional(tb.Type.String({ default: "/api/data" })),
|
||||||
|
default_primary_format: tb.Type.Optional(
|
||||||
|
StringEnum(primaryFieldTypes, { default: "integer" }),
|
||||||
|
),
|
||||||
entities: tb.Type.Optional(StringRecord(entitiesSchema, { default: {} })),
|
entities: tb.Type.Optional(StringRecord(entitiesSchema, { default: {} })),
|
||||||
relations: tb.Type.Optional(StringRecord(tb.Type.Union(relationsSchema), { default: {} })),
|
relations: tb.Type.Optional(StringRecord(tb.Type.Union(relationsSchema), { default: {} })),
|
||||||
indices: tb.Type.Optional(StringRecord(indicesSchema, { default: {} })),
|
indices: tb.Type.Optional(StringRecord(indicesSchema, { default: {} })),
|
||||||
|
|||||||
@@ -6,7 +6,13 @@ import {
|
|||||||
snakeToPascalWithSpaces,
|
snakeToPascalWithSpaces,
|
||||||
transformObject,
|
transformObject,
|
||||||
} from "core/utils";
|
} from "core/utils";
|
||||||
import { type Field, PrimaryField, type TActionContext, type TRenderContext } from "../fields";
|
import {
|
||||||
|
type Field,
|
||||||
|
PrimaryField,
|
||||||
|
primaryFieldTypes,
|
||||||
|
type TActionContext,
|
||||||
|
type TRenderContext,
|
||||||
|
} from "../fields";
|
||||||
import * as tbbox from "@sinclair/typebox";
|
import * as tbbox from "@sinclair/typebox";
|
||||||
const { Type } = tbbox;
|
const { Type } = tbbox;
|
||||||
|
|
||||||
@@ -18,6 +24,7 @@ export const entityConfigSchema = Type.Object(
|
|||||||
description: Type.Optional(Type.String()),
|
description: Type.Optional(Type.String()),
|
||||||
sort_field: Type.Optional(Type.String({ default: config.data.default_primary_field })),
|
sort_field: Type.Optional(Type.String({ default: config.data.default_primary_field })),
|
||||||
sort_dir: Type.Optional(StringEnum(["asc", "desc"], { default: "asc" })),
|
sort_dir: Type.Optional(StringEnum(["asc", "desc"], { default: "asc" })),
|
||||||
|
primary_format: Type.Optional(StringEnum(primaryFieldTypes)),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
additionalProperties: false,
|
additionalProperties: false,
|
||||||
@@ -68,7 +75,14 @@ export class Entity<
|
|||||||
if (primary_count > 1) {
|
if (primary_count > 1) {
|
||||||
throw new Error(`Entity "${name}" has more than one primary field`);
|
throw new Error(`Entity "${name}" has more than one primary field`);
|
||||||
}
|
}
|
||||||
this.fields = primary_count === 1 ? [] : [new PrimaryField()];
|
this.fields =
|
||||||
|
primary_count === 1
|
||||||
|
? []
|
||||||
|
: [
|
||||||
|
new PrimaryField(undefined, {
|
||||||
|
format: this.config.primary_format,
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
if (fields) {
|
if (fields) {
|
||||||
fields.forEach((field) => this.addField(field));
|
fields.forEach((field) => this.addField(field));
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ export class Mutator<
|
|||||||
|
|
||||||
// if listener returned, take what's returned
|
// if listener returned, take what's returned
|
||||||
const _data = result.returned ? result.params.data : data;
|
const _data = result.returned ? result.params.data : data;
|
||||||
const validatedData = {
|
let validatedData = {
|
||||||
...entity.getDefaultObject(),
|
...entity.getDefaultObject(),
|
||||||
...(await this.getValidatedData(_data, "create")),
|
...(await this.getValidatedData(_data, "create")),
|
||||||
};
|
};
|
||||||
@@ -159,6 +159,16 @@ export class Mutator<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// primary
|
||||||
|
const primary = entity.getPrimaryField();
|
||||||
|
const primary_value = primary.getNewValue();
|
||||||
|
if (primary_value) {
|
||||||
|
validatedData = {
|
||||||
|
[primary.name]: primary_value,
|
||||||
|
...validatedData,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const query = this.conn
|
const query = this.conn
|
||||||
.insertInto(entity.name)
|
.insertInto(entity.name)
|
||||||
.values(validatedData)
|
.values(validatedData)
|
||||||
@@ -175,7 +185,7 @@ export class Mutator<
|
|||||||
|
|
||||||
async updateOne(id: PrimaryFieldType, data: Partial<Input>): Promise<MutatorResponse<Output>> {
|
async updateOne(id: PrimaryFieldType, data: Partial<Input>): Promise<MutatorResponse<Output>> {
|
||||||
const entity = this.entity;
|
const entity = this.entity;
|
||||||
if (!Number.isInteger(id)) {
|
if (!id) {
|
||||||
throw new Error("ID must be provided for update");
|
throw new Error("ID must be provided for update");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,7 +222,7 @@ export class Mutator<
|
|||||||
|
|
||||||
async deleteOne(id: PrimaryFieldType): Promise<MutatorResponse<Output>> {
|
async deleteOne(id: PrimaryFieldType): Promise<MutatorResponse<Output>> {
|
||||||
const entity = this.entity;
|
const entity = this.entity;
|
||||||
if (!Number.isInteger(id)) {
|
if (!id) {
|
||||||
throw new Error("ID must be provided for deletion");
|
throw new Error("ID must be provided for deletion");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
import { config } from "core";
|
import { config } from "core";
|
||||||
import type { Static } from "core/utils";
|
import { StringEnum, uuidv7, type Static } from "core/utils";
|
||||||
import { Field, baseFieldConfigSchema } from "./Field";
|
import { Field, baseFieldConfigSchema } from "./Field";
|
||||||
import * as tbbox from "@sinclair/typebox";
|
import * as tbbox from "@sinclair/typebox";
|
||||||
import type { TFieldTSType } from "data/entities/EntityTypescript";
|
import type { TFieldTSType } from "data/entities/EntityTypescript";
|
||||||
const { Type } = tbbox;
|
const { Type } = tbbox;
|
||||||
|
|
||||||
|
export const primaryFieldTypes = ["integer", "uuid"] as const;
|
||||||
|
export type TPrimaryFieldFormat = (typeof primaryFieldTypes)[number];
|
||||||
|
|
||||||
export const primaryFieldConfigSchema = Type.Composite([
|
export const primaryFieldConfigSchema = Type.Composite([
|
||||||
Type.Omit(baseFieldConfigSchema, ["required"]),
|
Type.Omit(baseFieldConfigSchema, ["required"]),
|
||||||
Type.Object({
|
Type.Object({
|
||||||
|
format: Type.Optional(StringEnum(primaryFieldTypes, { default: "integer" })),
|
||||||
required: Type.Optional(Type.Literal(false)),
|
required: Type.Optional(Type.Literal(false)),
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
@@ -21,8 +25,8 @@ export class PrimaryField<Required extends true | false = false> extends Field<
|
|||||||
> {
|
> {
|
||||||
override readonly type = "primary";
|
override readonly type = "primary";
|
||||||
|
|
||||||
constructor(name: string = config.data.default_primary_field) {
|
constructor(name: string = config.data.default_primary_field, cfg?: PrimaryFieldConfig) {
|
||||||
super(name, { fillable: false, required: false });
|
super(name, { fillable: false, required: false, ...cfg });
|
||||||
}
|
}
|
||||||
|
|
||||||
override isRequired(): boolean {
|
override isRequired(): boolean {
|
||||||
@@ -30,18 +34,34 @@ export class PrimaryField<Required extends true | false = false> extends Field<
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected getSchema() {
|
protected getSchema() {
|
||||||
return baseFieldConfigSchema;
|
return primaryFieldConfigSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
get format() {
|
||||||
|
return this.config.format ?? "integer";
|
||||||
|
}
|
||||||
|
|
||||||
|
get fieldType() {
|
||||||
|
return this.format === "integer" ? "integer" : "text";
|
||||||
}
|
}
|
||||||
|
|
||||||
override schema() {
|
override schema() {
|
||||||
return Object.freeze({
|
return Object.freeze({
|
||||||
type: "integer",
|
type: this.fieldType,
|
||||||
name: this.name,
|
name: this.name,
|
||||||
primary: true,
|
primary: true,
|
||||||
nullable: false,
|
nullable: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getNewValue(): any {
|
||||||
|
if (this.format === "uuid") {
|
||||||
|
return uuidv7();
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
override async transformPersist(value: any): Promise<number> {
|
override async transformPersist(value: any): Promise<number> {
|
||||||
throw new Error("PrimaryField: This function should not be called");
|
throw new Error("PrimaryField: This function should not be called");
|
||||||
}
|
}
|
||||||
@@ -51,11 +71,12 @@ export class PrimaryField<Required extends true | false = false> extends Field<
|
|||||||
}
|
}
|
||||||
|
|
||||||
override toType(): TFieldTSType {
|
override toType(): TFieldTSType {
|
||||||
|
const type = this.format === "integer" ? "number" : "string";
|
||||||
return {
|
return {
|
||||||
...super.toType(),
|
...super.toType(),
|
||||||
required: true,
|
required: true,
|
||||||
import: [{ package: "kysely", name: "Generated" }],
|
import: [{ package: "kysely", name: "Generated" }],
|
||||||
type: "Generated<number>",
|
type: `Generated<${type}>`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
import type { RepoQuery } from "../server/query";
|
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";
|
||||||
|
import type { PrimaryFieldType } from "core";
|
||||||
const { Type } = tbbox;
|
const { Type } = tbbox;
|
||||||
|
|
||||||
const directions = ["source", "target"] as const;
|
const directions = ["source", "target"] as const;
|
||||||
@@ -72,7 +73,7 @@ export abstract class EntityRelation<
|
|||||||
reference: string,
|
reference: string,
|
||||||
): KyselyQueryBuilder;
|
): KyselyQueryBuilder;
|
||||||
|
|
||||||
getReferenceQuery(entity: Entity, id: number, reference: string): Partial<RepoQuery> {
|
getReferenceQuery(entity: Entity, id: PrimaryFieldType, reference: string): Partial<RepoQuery> {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { type Static, StringEnum } from "core/utils";
|
import { type Static, StringEnum } from "core/utils";
|
||||||
import type { EntityManager } from "../entities";
|
import type { EntityManager } from "../entities";
|
||||||
import { Field, baseFieldConfigSchema } from "../fields";
|
import { Field, baseFieldConfigSchema, primaryFieldTypes } from "../fields";
|
||||||
import type { EntityRelation } from "./EntityRelation";
|
import type { EntityRelation } from "./EntityRelation";
|
||||||
import type { EntityRelationAnchor } from "./EntityRelationAnchor";
|
import type { EntityRelationAnchor } from "./EntityRelationAnchor";
|
||||||
import * as tbbox from "@sinclair/typebox";
|
import * as tbbox from "@sinclair/typebox";
|
||||||
@@ -15,6 +15,7 @@ export const relationFieldConfigSchema = Type.Composite([
|
|||||||
reference: Type.String(),
|
reference: Type.String(),
|
||||||
target: Type.String(), // @todo: potentially has to be an instance!
|
target: Type.String(), // @todo: potentially has to be an instance!
|
||||||
target_field: Type.Optional(Type.String({ default: "id" })),
|
target_field: Type.Optional(Type.String({ default: "id" })),
|
||||||
|
target_field_type: Type.Optional(StringEnum(["integer", "text"], { default: "integer" })),
|
||||||
on_delete: Type.Optional(StringEnum(CASCADES, { default: "set null" })),
|
on_delete: Type.Optional(StringEnum(CASCADES, { default: "set null" })),
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
@@ -45,6 +46,7 @@ export class RelationField extends Field<RelationFieldConfig> {
|
|||||||
reference: target.reference,
|
reference: target.reference,
|
||||||
target: target.entity.name,
|
target: target.entity.name,
|
||||||
target_field: target.entity.getPrimaryField().name,
|
target_field: target.entity.getPrimaryField().name,
|
||||||
|
target_field_type: target.entity.getPrimaryField().fieldType,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +65,7 @@ export class RelationField extends Field<RelationFieldConfig> {
|
|||||||
override schema() {
|
override schema() {
|
||||||
return Object.freeze({
|
return Object.freeze({
|
||||||
...super.schema()!,
|
...super.schema()!,
|
||||||
type: "integer",
|
type: this.config.target_field_type ?? "integer",
|
||||||
references: `${this.config.target}.${this.config.target_field}`,
|
references: `${this.config.target}.${this.config.target_field}`,
|
||||||
onDelete: this.config.on_delete ?? "set null",
|
onDelete: this.config.on_delete ?? "set null",
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import type { CompiledQuery, TableMetadata } from "kysely";
|
|||||||
import type { IndexMetadata, SchemaResponse } from "../connection/Connection";
|
import type { IndexMetadata, SchemaResponse } from "../connection/Connection";
|
||||||
import type { Entity, EntityManager } from "../entities";
|
import type { Entity, EntityManager } from "../entities";
|
||||||
import { PrimaryField } from "../fields";
|
import { PrimaryField } from "../fields";
|
||||||
|
import { $console } from "core";
|
||||||
|
|
||||||
type IntrospectedTable = TableMetadata & {
|
type IntrospectedTable = TableMetadata & {
|
||||||
indices: IndexMetadata[];
|
indices: IndexMetadata[];
|
||||||
@@ -332,6 +333,7 @@ export class SchemaManager {
|
|||||||
|
|
||||||
if (config.force) {
|
if (config.force) {
|
||||||
try {
|
try {
|
||||||
|
$console.info("[SchemaManager]", sql, parameters);
|
||||||
await qb.execute();
|
await qb.execute();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`Failed to execute query: ${sql}: ${(e as any).message}`);
|
throw new Error(`Failed to execute query: ${sql}: ${(e as any).message}`);
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import type { Api } from "bknd/client";
|
import type { Api } from "bknd/client";
|
||||||
|
import type { PrimaryFieldType } from "core";
|
||||||
import type { RepoQueryIn } from "data";
|
import type { RepoQueryIn } from "data";
|
||||||
import type { MediaFieldSchema } from "media/AppMedia";
|
import type { MediaFieldSchema } from "media/AppMedia";
|
||||||
import type { TAppMediaConfig } from "media/media-schema";
|
import type { TAppMediaConfig } from "media/media-schema";
|
||||||
import { useId, useEffect, useRef, useState } from "react";
|
import { useId, useEffect, useRef, useState } from "react";
|
||||||
import { useApi, useApiInfiniteQuery, useApiQuery, useInvalidate } from "ui/client";
|
import { useApi, useApiInfiniteQuery, useApiQuery, useInvalidate } from "bknd/client";
|
||||||
import { useEvent } from "ui/hooks/use-event";
|
import { useEvent } from "ui/hooks/use-event";
|
||||||
import { Dropzone, type DropzoneProps } from "./Dropzone";
|
import { Dropzone, type DropzoneProps } from "./Dropzone";
|
||||||
import { mediaItemsToFileStates } from "./helper";
|
import { mediaItemsToFileStates } from "./helper";
|
||||||
@@ -14,7 +15,7 @@ export type DropzoneContainerProps = {
|
|||||||
infinite?: boolean;
|
infinite?: boolean;
|
||||||
entity?: {
|
entity?: {
|
||||||
name: string;
|
name: string;
|
||||||
id: number;
|
id: PrimaryFieldType;
|
||||||
field: string;
|
field: string;
|
||||||
};
|
};
|
||||||
media?: Pick<TAppMediaConfig, "entity_name" | "storage">;
|
media?: Pick<TAppMediaConfig, "entity_name" | "storage">;
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import { EntityRelationalFormField } from "./fields/EntityRelationalFormField";
|
|||||||
import ErrorBoundary from "ui/components/display/ErrorBoundary";
|
import ErrorBoundary from "ui/components/display/ErrorBoundary";
|
||||||
import { Alert } from "ui/components/display/Alert";
|
import { Alert } from "ui/components/display/Alert";
|
||||||
import { bkndModals } from "ui/modals";
|
import { bkndModals } from "ui/modals";
|
||||||
|
import type { PrimaryFieldType } from "core";
|
||||||
|
|
||||||
// simplify react form types 🤦
|
// simplify react form types 🤦
|
||||||
export type FormApi = ReactFormExtendedApi<any, any, any, any, any, any, any, any, any, any>;
|
export type FormApi = ReactFormExtendedApi<any, any, any, any, any, any, any, any, any, any>;
|
||||||
@@ -30,7 +31,7 @@ export type TFieldApi = FieldApi<any, any, any, any, any, any, any, any, any, an
|
|||||||
|
|
||||||
type EntityFormProps = {
|
type EntityFormProps = {
|
||||||
entity: Entity;
|
entity: Entity;
|
||||||
entityId?: number;
|
entityId?: PrimaryFieldType;
|
||||||
data?: EntityData;
|
data?: EntityData;
|
||||||
handleSubmit: (e: React.FormEvent<HTMLFormElement>) => void;
|
handleSubmit: (e: React.FormEvent<HTMLFormElement>) => void;
|
||||||
fieldsDisabled: boolean;
|
fieldsDisabled: boolean;
|
||||||
@@ -225,7 +226,7 @@ function EntityMediaFormField({
|
|||||||
formApi: FormApi;
|
formApi: FormApi;
|
||||||
field: MediaField;
|
field: MediaField;
|
||||||
entity: Entity;
|
entity: Entity;
|
||||||
entityId?: number;
|
entityId?: PrimaryFieldType;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}) {
|
}) {
|
||||||
if (!entityId) return;
|
if (!entityId) return;
|
||||||
|
|||||||
@@ -11,12 +11,14 @@ import {
|
|||||||
type EntityFieldsFormRef,
|
type EntityFieldsFormRef,
|
||||||
} from "ui/routes/data/forms/entity.fields.form";
|
} from "ui/routes/data/forms/entity.fields.form";
|
||||||
import { ModalBody, ModalFooter, type TCreateModalSchema, useStepContext } from "./CreateModal";
|
import { ModalBody, ModalFooter, type TCreateModalSchema, useStepContext } from "./CreateModal";
|
||||||
|
import { useBkndData } from "ui/client/schema/data/use-bknd-data";
|
||||||
|
|
||||||
const schema = entitiesSchema;
|
const schema = entitiesSchema;
|
||||||
type Schema = Static<typeof schema>;
|
type Schema = Static<typeof schema>;
|
||||||
|
|
||||||
export function StepEntityFields() {
|
export function StepEntityFields() {
|
||||||
const { nextStep, stepBack, state, setState } = useStepContext<TCreateModalSchema>();
|
const { nextStep, stepBack, state, setState } = useStepContext<TCreateModalSchema>();
|
||||||
|
const { config } = useBkndData();
|
||||||
const entity = state.entities?.create?.[0]!;
|
const entity = state.entities?.create?.[0]!;
|
||||||
const defaultFields = { id: { type: "primary", name: "id" } } as const;
|
const defaultFields = { id: { type: "primary", name: "id" } } as const;
|
||||||
const ref = useRef<EntityFieldsFormRef>(null);
|
const ref = useRef<EntityFieldsFormRef>(null);
|
||||||
@@ -82,6 +84,8 @@ export function StepEntityFields() {
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
fields={initial.fields as any}
|
fields={initial.fields as any}
|
||||||
onChange={updateListener}
|
onChange={updateListener}
|
||||||
|
defaultPrimaryFormat={config?.default_primary_format}
|
||||||
|
isNew={true}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,12 +10,13 @@ import {
|
|||||||
entitySchema,
|
entitySchema,
|
||||||
useStepContext,
|
useStepContext,
|
||||||
} from "./CreateModal";
|
} from "./CreateModal";
|
||||||
|
import { MantineSelect } from "ui/components/form/hook-form-mantine/MantineSelect";
|
||||||
|
|
||||||
export function StepEntity() {
|
export function StepEntity() {
|
||||||
const focusTrapRef = useFocusTrap();
|
const focusTrapRef = useFocusTrap();
|
||||||
|
|
||||||
const { nextStep, stepBack, state, setState } = useStepContext<TCreateModalSchema>();
|
const { nextStep, stepBack, state, setState } = useStepContext<TCreateModalSchema>();
|
||||||
const { register, handleSubmit, formState, watch } = useForm({
|
const { register, handleSubmit, formState, watch, control } = useForm({
|
||||||
mode: "onTouched",
|
mode: "onTouched",
|
||||||
resolver: typeboxResolver(entitySchema),
|
resolver: typeboxResolver(entitySchema),
|
||||||
defaultValues: state.entities?.create?.[0] ?? {},
|
defaultValues: state.entities?.create?.[0] ?? {},
|
||||||
@@ -56,7 +57,6 @@ export function StepEntity() {
|
|||||||
label="What's the name of the entity?"
|
label="What's the name of the entity?"
|
||||||
description="Use plural form, and all lowercase. It will be used as the database table."
|
description="Use plural form, and all lowercase. It will be used as the database table."
|
||||||
/>
|
/>
|
||||||
{/*<input type="submit" value="submit" />*/}
|
|
||||||
<TextInput
|
<TextInput
|
||||||
{...register("config.name")}
|
{...register("config.name")}
|
||||||
error={formState.errors.config?.name?.message}
|
error={formState.errors.config?.name?.message}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { PrimaryFieldType } from "core";
|
||||||
import { ucFirst } from "core/utils";
|
import { ucFirst } from "core/utils";
|
||||||
import type { Entity, EntityData, EntityRelation } from "data";
|
import type { Entity, EntityData, EntityRelation } from "data";
|
||||||
import { Fragment, useState } from "react";
|
import { Fragment, useState } from "react";
|
||||||
@@ -24,7 +25,7 @@ export function DataEntityUpdate({ params }) {
|
|||||||
return <Message.NotFound description={`Entity "${params.entity}" doesn't exist.`} />;
|
return <Message.NotFound description={`Entity "${params.entity}" doesn't exist.`} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const entityId = Number.parseInt(params.id as string);
|
const entityId = params.id as PrimaryFieldType;
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [navigate] = useNavigate();
|
const [navigate] = useNavigate();
|
||||||
useBrowserTitle(["Data", entity.label, `#${entityId}`]);
|
useBrowserTitle(["Data", entity.label, `#${entityId}`]);
|
||||||
@@ -202,7 +203,7 @@ function EntityDetailRelations({
|
|||||||
entity,
|
entity,
|
||||||
relations,
|
relations,
|
||||||
}: {
|
}: {
|
||||||
id: number;
|
id: PrimaryFieldType;
|
||||||
entity: Entity;
|
entity: Entity;
|
||||||
relations: EntityRelation[];
|
relations: EntityRelation[];
|
||||||
}) {
|
}) {
|
||||||
@@ -250,7 +251,7 @@ function EntityDetailInner({
|
|||||||
entity,
|
entity,
|
||||||
relation,
|
relation,
|
||||||
}: {
|
}: {
|
||||||
id: number;
|
id: PrimaryFieldType;
|
||||||
entity: Entity;
|
entity: Entity;
|
||||||
relation: EntityRelation;
|
relation: EntityRelation;
|
||||||
}) {
|
}) {
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ export function DataSchemaEntity({ params }) {
|
|||||||
const Fields = ({ entity }: { entity: Entity }) => {
|
const Fields = ({ entity }: { entity: Entity }) => {
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
const [updates, setUpdates] = useState(0);
|
const [updates, setUpdates] = useState(0);
|
||||||
const { actions, $data } = useBkndData();
|
const { actions, $data, config } = useBkndData();
|
||||||
const [res, setRes] = useState<any>();
|
const [res, setRes] = useState<any>();
|
||||||
const ref = useRef<EntityFieldsFormRef>(null);
|
const ref = useRef<EntityFieldsFormRef>(null);
|
||||||
async function handleUpdate() {
|
async function handleUpdate() {
|
||||||
@@ -201,6 +201,8 @@ const Fields = ({ entity }: { entity: Entity }) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
}))}
|
}))}
|
||||||
|
defaultPrimaryFormat={config?.default_primary_format}
|
||||||
|
isNew={false}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{isDebug() && (
|
{isDebug() && (
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ import { type TFieldSpec, fieldSpecs } from "ui/modules/data/components/fields-s
|
|||||||
import { dataFieldsUiSchema } from "../../settings/routes/data.settings";
|
import { dataFieldsUiSchema } from "../../settings/routes/data.settings";
|
||||||
import * as tbbox from "@sinclair/typebox";
|
import * as tbbox from "@sinclair/typebox";
|
||||||
import { useRoutePathState } from "ui/hooks/use-route-path-state";
|
import { useRoutePathState } from "ui/hooks/use-route-path-state";
|
||||||
|
import { MantineSelect } from "ui/components/form/hook-form-mantine/MantineSelect";
|
||||||
|
import type { TPrimaryFieldFormat } from "data/fields/PrimaryField";
|
||||||
const { Type } = tbbox;
|
const { Type } = tbbox;
|
||||||
|
|
||||||
const fieldsSchemaObject = originalFieldsSchemaObject;
|
const fieldsSchemaObject = originalFieldsSchemaObject;
|
||||||
@@ -65,6 +67,8 @@ export type EntityFieldsFormProps = {
|
|||||||
sortable?: boolean;
|
sortable?: boolean;
|
||||||
additionalFieldTypes?: (TFieldSpec & { onClick: () => void })[];
|
additionalFieldTypes?: (TFieldSpec & { onClick: () => void })[];
|
||||||
routePattern?: string;
|
routePattern?: string;
|
||||||
|
defaultPrimaryFormat?: TPrimaryFieldFormat;
|
||||||
|
isNew?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type EntityFieldsFormRef = {
|
export type EntityFieldsFormRef = {
|
||||||
@@ -77,7 +81,7 @@ export type EntityFieldsFormRef = {
|
|||||||
|
|
||||||
export const EntityFieldsForm = forwardRef<EntityFieldsFormRef, EntityFieldsFormProps>(
|
export const EntityFieldsForm = forwardRef<EntityFieldsFormRef, EntityFieldsFormProps>(
|
||||||
function EntityFieldsForm(
|
function EntityFieldsForm(
|
||||||
{ fields: _fields, sortable, additionalFieldTypes, routePattern, ...props },
|
{ fields: _fields, sortable, additionalFieldTypes, routePattern, isNew, ...props },
|
||||||
ref,
|
ref,
|
||||||
) {
|
) {
|
||||||
const entityFields = Object.entries(_fields).map(([name, field]) => ({
|
const entityFields = Object.entries(_fields).map(([name, field]) => ({
|
||||||
@@ -172,6 +176,10 @@ export const EntityFieldsForm = forwardRef<EntityFieldsFormRef, EntityFieldsForm
|
|||||||
remove={remove}
|
remove={remove}
|
||||||
dnd={dnd}
|
dnd={dnd}
|
||||||
routePattern={routePattern}
|
routePattern={routePattern}
|
||||||
|
primary={{
|
||||||
|
defaultFormat: props.defaultPrimaryFormat,
|
||||||
|
editable: isNew,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -186,6 +194,10 @@ export const EntityFieldsForm = forwardRef<EntityFieldsFormRef, EntityFieldsForm
|
|||||||
errors={errors}
|
errors={errors}
|
||||||
remove={remove}
|
remove={remove}
|
||||||
routePattern={routePattern}
|
routePattern={routePattern}
|
||||||
|
primary={{
|
||||||
|
defaultFormat: props.defaultPrimaryFormat,
|
||||||
|
editable: isNew,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -281,6 +293,7 @@ function EntityField({
|
|||||||
errors,
|
errors,
|
||||||
dnd,
|
dnd,
|
||||||
routePattern,
|
routePattern,
|
||||||
|
primary,
|
||||||
}: {
|
}: {
|
||||||
field: FieldArrayWithId<TFieldsFormSchema, "fields", "id">;
|
field: FieldArrayWithId<TFieldsFormSchema, "fields", "id">;
|
||||||
index: number;
|
index: number;
|
||||||
@@ -292,6 +305,10 @@ function EntityField({
|
|||||||
errors: any;
|
errors: any;
|
||||||
dnd?: SortableItemProps;
|
dnd?: SortableItemProps;
|
||||||
routePattern?: string;
|
routePattern?: string;
|
||||||
|
primary?: {
|
||||||
|
defaultFormat?: TPrimaryFieldFormat;
|
||||||
|
editable?: boolean;
|
||||||
|
};
|
||||||
}) {
|
}) {
|
||||||
const prefix = `fields.${index}.field` as const;
|
const prefix = `fields.${index}.field` as const;
|
||||||
const type = field.field.type;
|
const type = field.field.type;
|
||||||
@@ -363,15 +380,29 @@ function EntityField({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex-col gap-1 hidden md:flex">
|
<div className="flex-col gap-1 hidden md:flex">
|
||||||
<span className="text-xs text-primary/50 leading-none">Required</span>
|
|
||||||
{is_primary ? (
|
{is_primary ? (
|
||||||
<Switch size="sm" defaultChecked disabled />
|
<>
|
||||||
|
<MantineSelect
|
||||||
|
data={["integer", "uuid"]}
|
||||||
|
defaultValue={primary?.defaultFormat}
|
||||||
|
disabled={!primary?.editable}
|
||||||
|
placeholder="Select format"
|
||||||
|
name={`${prefix}.config.format`}
|
||||||
|
allowDeselect={false}
|
||||||
|
control={control}
|
||||||
|
size="xs"
|
||||||
|
className="w-20"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
<>
|
||||||
|
<span className="text-xs text-primary/50 leading-none">Required</span>
|
||||||
<MantineSwitch
|
<MantineSwitch
|
||||||
size="sm"
|
size="sm"
|
||||||
name={`${prefix}.config.required`}
|
name={`${prefix}.config.required`}
|
||||||
control={control}
|
control={control}
|
||||||
/>
|
/>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -30,7 +30,14 @@
|
|||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"outDir": "./dist/types",
|
"outDir": "./dist/types",
|
||||||
"paths": {
|
"paths": {
|
||||||
"*": ["./src/*"]
|
"*": ["./src/*"],
|
||||||
|
"bknd": ["./src/index.ts"],
|
||||||
|
"bknd/core": ["./src/core/index.ts"],
|
||||||
|
"bknd/adapter": ["./src/adapter/index.ts"],
|
||||||
|
"bknd/client": ["./src/ui/client/index.ts"],
|
||||||
|
"bknd/data": ["./src/data/index.ts"],
|
||||||
|
"bknd/media": ["./src/media/index.ts"],
|
||||||
|
"bknd/auth": ["./src/auth/index.ts"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
|
|||||||
11
bun.lock
11
bun.lock
@@ -27,7 +27,7 @@
|
|||||||
},
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"name": "bknd",
|
"name": "bknd",
|
||||||
"version": "0.12.0",
|
"version": "0.13.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",
|
||||||
@@ -56,6 +56,7 @@
|
|||||||
"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",
|
||||||
|
"uuid": "^11.1.0",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.758.0",
|
"@aws-sdk/client-s3": "^3.758.0",
|
||||||
@@ -84,7 +85,7 @@
|
|||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
"jotai": "^2.12.2",
|
"jotai": "^2.12.2",
|
||||||
"jsdom": "^26.0.0",
|
"jsdom": "^26.0.0",
|
||||||
"jsonv-ts": "^0.0.14-alpha.6",
|
"jsonv-ts": "^0.1.0",
|
||||||
"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",
|
||||||
@@ -2521,7 +2522,7 @@
|
|||||||
|
|
||||||
"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.14-alpha.6", "", { "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-pwMpjEbNtyq8Xi6QBXuQ8dOZm7WQAEwvCPu3vVf9b3aU2KRHW+cfTPqO53U01YYdjWSSRkqaTKcLSiYdfwBYRA=="],
|
"jsonv-ts": ["jsonv-ts@0.1.0", "", { "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-wJ+79o49MNie2Xk9w1hPN8ozjqemVWXOfWUTdioLui/SeGDC7C+QKXTDxsmUaIay86lorkjb3CCGo6JDKbyTZQ=="],
|
||||||
|
|
||||||
"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=="],
|
||||||
|
|
||||||
@@ -3603,7 +3604,7 @@
|
|||||||
|
|
||||||
"utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="],
|
"utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="],
|
||||||
|
|
||||||
"uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],
|
"uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="],
|
||||||
|
|
||||||
"v8-compile-cache": ["v8-compile-cache@2.4.0", "", {}, "sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw=="],
|
"v8-compile-cache": ["v8-compile-cache@2.4.0", "", {}, "sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw=="],
|
||||||
|
|
||||||
@@ -3853,6 +3854,8 @@
|
|||||||
|
|
||||||
"@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=="],
|
||||||
|
|
||||||
|
"@cypress/request/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],
|
||||||
|
|
||||||
"@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=="],
|
||||||
|
|||||||
Reference in New Issue
Block a user