mirror of
https://github.com/lukevella/rallly.git
synced 2025-07-25 20:27:44 +02:00
✨ Add licensing api (#1723)
This commit is contained in:
parent
982fc39ac7
commit
679d6fb034
14 changed files with 607 additions and 40 deletions
|
@ -20,6 +20,8 @@
|
||||||
"@auth/prisma-adapter": "^2.7.4",
|
"@auth/prisma-adapter": "^2.7.4",
|
||||||
"@aws-sdk/client-s3": "^3.645.0",
|
"@aws-sdk/client-s3": "^3.645.0",
|
||||||
"@aws-sdk/s3-request-presigner": "^3.645.0",
|
"@aws-sdk/s3-request-presigner": "^3.645.0",
|
||||||
|
"@hono-rate-limiter/redis": "^0.1.4",
|
||||||
|
"@hono/zod-validator": "^0.5.0",
|
||||||
"@hookform/resolvers": "^3.3.1",
|
"@hookform/resolvers": "^3.3.1",
|
||||||
"@next/bundle-analyzer": "^15.3.1",
|
"@next/bundle-analyzer": "^15.3.1",
|
||||||
"@next/env": "^15.3.1",
|
"@next/env": "^15.3.1",
|
||||||
|
@ -50,7 +52,7 @@
|
||||||
"@upstash/qstash": "^2.7.17",
|
"@upstash/qstash": "^2.7.17",
|
||||||
"@upstash/ratelimit": "^1.2.1",
|
"@upstash/ratelimit": "^1.2.1",
|
||||||
"@vercel/functions": "^2.0.0",
|
"@vercel/functions": "^2.0.0",
|
||||||
"@vercel/kv": "^2.0.0",
|
"@vercel/kv": "^3.0.0",
|
||||||
"ai": "^4.1.50",
|
"ai": "^4.1.50",
|
||||||
"autoprefixer": "^10.4.13",
|
"autoprefixer": "^10.4.13",
|
||||||
"calendar-link": "^2.6.0",
|
"calendar-link": "^2.6.0",
|
||||||
|
@ -59,6 +61,8 @@
|
||||||
"cookie": "^0.7.0",
|
"cookie": "^0.7.0",
|
||||||
"crypto": "^1.0.1",
|
"crypto": "^1.0.1",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
|
"hono": "^4.7.10",
|
||||||
|
"hono-rate-limiter": "^0.2.1",
|
||||||
"i18next": "^24.2.2",
|
"i18next": "^24.2.2",
|
||||||
"i18next-http-backend": "^3.0.2",
|
"i18next-http-backend": "^3.0.2",
|
||||||
"i18next-icu": "^2.3.0",
|
"i18next-icu": "^2.3.0",
|
||||||
|
@ -92,7 +96,7 @@
|
||||||
"superjson": "^2.0.0",
|
"superjson": "^2.0.0",
|
||||||
"tailwindcss": "^3.4.17",
|
"tailwindcss": "^3.4.17",
|
||||||
"timezone-soft": "^1.5.1",
|
"timezone-soft": "^1.5.1",
|
||||||
"zod": "^3.23.8"
|
"zod": "^3.25.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.26.10",
|
"@babel/core": "^7.26.10",
|
||||||
|
|
153
apps/web/src/app/api/licensing/v1/[...route]/route.ts
Normal file
153
apps/web/src/app/api/licensing/v1/[...route]/route.ts
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
import { env } from "@/env";
|
||||||
|
import { generateLicenseKey } from "@/features/licensing/helpers/generate-license-key";
|
||||||
|
import {
|
||||||
|
type CreateLicenseResponse,
|
||||||
|
type ValidateLicenseKeyResponse,
|
||||||
|
createLicenseInputSchema,
|
||||||
|
validateLicenseKeyInputSchema,
|
||||||
|
} from "@/features/licensing/schema";
|
||||||
|
import { isSelfHosted } from "@/utils/constants";
|
||||||
|
import { RedisStore } from "@hono-rate-limiter/redis";
|
||||||
|
import { zValidator } from "@hono/zod-validator";
|
||||||
|
import { prisma } from "@rallly/database";
|
||||||
|
import { kv } from "@vercel/kv";
|
||||||
|
import { Hono } from "hono";
|
||||||
|
import { rateLimiter } from "hono-rate-limiter";
|
||||||
|
import { bearerAuth } from "hono/bearer-auth";
|
||||||
|
import { some } from "hono/combine";
|
||||||
|
import { handle } from "hono/vercel";
|
||||||
|
|
||||||
|
const isKvAvailable =
|
||||||
|
process.env.KV_REST_API_URL && process.env.KV_REST_API_TOKEN;
|
||||||
|
|
||||||
|
const app = new Hono().basePath("/api/licensing/v1");
|
||||||
|
|
||||||
|
app.use("*", async (c, next) => {
|
||||||
|
if (isSelfHosted) {
|
||||||
|
return c.json({ error: "Not available in self-hosted instances" }, 404);
|
||||||
|
}
|
||||||
|
return next();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (env.LICENSE_API_AUTH_TOKEN) {
|
||||||
|
app.post(
|
||||||
|
"/licenses",
|
||||||
|
zValidator("json", createLicenseInputSchema),
|
||||||
|
some(
|
||||||
|
bearerAuth({ token: env.LICENSE_API_AUTH_TOKEN }),
|
||||||
|
rateLimiter({
|
||||||
|
windowMs: 60 * 60 * 1000,
|
||||||
|
limit: 10,
|
||||||
|
store: isKvAvailable
|
||||||
|
? new RedisStore({
|
||||||
|
client: kv,
|
||||||
|
})
|
||||||
|
: undefined,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
async (c) => {
|
||||||
|
const {
|
||||||
|
type,
|
||||||
|
seats,
|
||||||
|
expiresAt,
|
||||||
|
licenseeEmail,
|
||||||
|
licenseeName,
|
||||||
|
version,
|
||||||
|
stripeCustomerId,
|
||||||
|
} = c.req.valid("json");
|
||||||
|
|
||||||
|
try {
|
||||||
|
const license = await prisma.license.create({
|
||||||
|
data: {
|
||||||
|
licenseKey: generateLicenseKey({ version }),
|
||||||
|
version,
|
||||||
|
type,
|
||||||
|
seats,
|
||||||
|
issuedAt: new Date(),
|
||||||
|
expiresAt,
|
||||||
|
licenseeEmail,
|
||||||
|
licenseeName,
|
||||||
|
stripeCustomerId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return c.json({
|
||||||
|
data: { key: license.licenseKey },
|
||||||
|
} satisfies CreateLicenseResponse);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to create license:", error);
|
||||||
|
return c.json({ error: "Failed to create license" }, 500);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
app.post(
|
||||||
|
"/licenses/actions/validate-key",
|
||||||
|
zValidator("json", validateLicenseKeyInputSchema),
|
||||||
|
rateLimiter({
|
||||||
|
keyGenerator: async (c) => {
|
||||||
|
const { key, fingerprint } = await c.req.json();
|
||||||
|
return `validate-key:${key}:${fingerprint}`;
|
||||||
|
},
|
||||||
|
windowMs: 60 * 60 * 1000,
|
||||||
|
limit: 10,
|
||||||
|
store: isKvAvailable
|
||||||
|
? new RedisStore({
|
||||||
|
client: kv,
|
||||||
|
})
|
||||||
|
: undefined,
|
||||||
|
}),
|
||||||
|
async (c) => {
|
||||||
|
const { key, fingerprint } = c.req.valid("json");
|
||||||
|
|
||||||
|
const license = await prisma.license.findUnique({
|
||||||
|
where: {
|
||||||
|
licenseKey: key,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
licenseKey: true,
|
||||||
|
status: true,
|
||||||
|
issuedAt: true,
|
||||||
|
expiresAt: true,
|
||||||
|
licenseeEmail: true,
|
||||||
|
licenseeName: true,
|
||||||
|
seats: true,
|
||||||
|
type: true,
|
||||||
|
version: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!license) {
|
||||||
|
return c.json({ error: "License not found" }, 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
await prisma.licenseValidation.create({
|
||||||
|
data: {
|
||||||
|
licenseId: license.id,
|
||||||
|
ipAddress: c.req.header("x-forwarded-for"),
|
||||||
|
fingerprint,
|
||||||
|
validatedAt: new Date(),
|
||||||
|
userAgent: c.req.header("user-agent"),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return c.json({
|
||||||
|
data: {
|
||||||
|
key: license.licenseKey,
|
||||||
|
valid: license.status === "ACTIVE",
|
||||||
|
status: license.status,
|
||||||
|
issuedAt: license.issuedAt,
|
||||||
|
expiresAt: license.expiresAt,
|
||||||
|
licenseeEmail: license.licenseeEmail,
|
||||||
|
licenseeName: license.licenseeName,
|
||||||
|
seats: license.seats,
|
||||||
|
type: license.type,
|
||||||
|
version: license.version,
|
||||||
|
},
|
||||||
|
} satisfies ValidateLicenseKeyResponse);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const GET = handle(app);
|
||||||
|
export const POST = handle(app);
|
|
@ -74,6 +74,11 @@ export const env = createEnv({
|
||||||
* @default "false"
|
* @default "false"
|
||||||
*/
|
*/
|
||||||
MODERATION_ENABLED: z.enum(["true", "false"]).default("false"),
|
MODERATION_ENABLED: z.enum(["true", "false"]).default("false"),
|
||||||
|
/**
|
||||||
|
* Licensing API Configuration
|
||||||
|
*/
|
||||||
|
LICENSE_API_URL: z.string().optional(),
|
||||||
|
LICENSE_API_AUTH_TOKEN: z.string().optional(),
|
||||||
},
|
},
|
||||||
/*
|
/*
|
||||||
* Environment variables available on the client (and server).
|
* Environment variables available on the client (and server).
|
||||||
|
@ -125,6 +130,8 @@ export const env = createEnv({
|
||||||
NOREPLY_EMAIL_NAME: process.env.NOREPLY_EMAIL_NAME,
|
NOREPLY_EMAIL_NAME: process.env.NOREPLY_EMAIL_NAME,
|
||||||
OPENAI_API_KEY: process.env.OPENAI_API_KEY,
|
OPENAI_API_KEY: process.env.OPENAI_API_KEY,
|
||||||
MODERATION_ENABLED: process.env.MODERATION_ENABLED,
|
MODERATION_ENABLED: process.env.MODERATION_ENABLED,
|
||||||
|
LICENSE_API_URL: process.env.LICENSE_API_URL,
|
||||||
|
LICENSE_API_AUTH_TOKEN: process.env.LICENSE_API_AUTH_TOKEN,
|
||||||
},
|
},
|
||||||
skipValidation: !!process.env.SKIP_ENV_VALIDATION,
|
skipValidation: !!process.env.SKIP_ENV_VALIDATION,
|
||||||
});
|
});
|
||||||
|
|
43
apps/web/src/features/licensing/actions/validate-license.ts
Normal file
43
apps/web/src/features/licensing/actions/validate-license.ts
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
"use server";
|
||||||
|
|
||||||
|
import { rateLimit } from "@/features/rate-limit";
|
||||||
|
import { prisma } from "@rallly/database";
|
||||||
|
import { licensingClient } from "../client";
|
||||||
|
|
||||||
|
export async function validateLicenseKey(key: string) {
|
||||||
|
const { success } = await rateLimit("validate_license_key", 10, "1 m");
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
throw new Error("Rate limit exceeded");
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data, error } = await licensingClient.validateLicenseKey({
|
||||||
|
key,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
throw new Error(`License validation failed: ${error}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
await prisma.instanceLicense.create({
|
||||||
|
data: {
|
||||||
|
licenseKey: data.key,
|
||||||
|
licenseeName: data.licenseeName,
|
||||||
|
licenseeEmail: data.licenseeEmail,
|
||||||
|
issuedAt: data.issuedAt,
|
||||||
|
expiresAt: data.expiresAt,
|
||||||
|
seats: data.seats,
|
||||||
|
type: data.type,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
valid: true,
|
||||||
|
};
|
||||||
|
}
|
7
apps/web/src/features/licensing/client.ts
Normal file
7
apps/web/src/features/licensing/client.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import { env } from "@/env";
|
||||||
|
import { LicensingClient } from "./lib/licensing-client";
|
||||||
|
|
||||||
|
export const licensingClient = new LicensingClient({
|
||||||
|
apiUrl: env.LICENSE_API_URL,
|
||||||
|
authToken: env.LICENSE_API_AUTH_TOKEN,
|
||||||
|
});
|
|
@ -0,0 +1,13 @@
|
||||||
|
/**
|
||||||
|
* Calculate a checksum for a string
|
||||||
|
* @param str The string to calculate the checksum for
|
||||||
|
* @returns The checksum
|
||||||
|
*/
|
||||||
|
export function calculateChecksum(str: string): string {
|
||||||
|
// Simple checksum: sum char codes, mod 100000, base36
|
||||||
|
let sum = 0;
|
||||||
|
for (let i = 0; i < str.length; i++) {
|
||||||
|
sum += str.charCodeAt(i);
|
||||||
|
}
|
||||||
|
return (sum % 100000).toString(36).toUpperCase().padStart(5, "0");
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { calculateChecksum } from "./calculate-checksum";
|
||||||
|
|
||||||
|
export function checkLicenseKey(key: string): boolean {
|
||||||
|
const parts = key.split("-");
|
||||||
|
if (parts.length !== 6) return false;
|
||||||
|
const checksum = parts[5];
|
||||||
|
const licenseBody = parts.slice(0, 5).join("-");
|
||||||
|
return calculateChecksum(licenseBody) === checksum;
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { customAlphabet } from "nanoid";
|
||||||
|
import { calculateChecksum } from "./calculate-checksum";
|
||||||
|
|
||||||
|
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||||
|
const generate = customAlphabet(alphabet, 4);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate user friendly licenses
|
||||||
|
* eg. RLYV4-ABCD-1234-ABCD-1234-XXXX
|
||||||
|
*/
|
||||||
|
export const generateLicenseKey = ({ version }: { version?: number }) => {
|
||||||
|
let license = `RLYV${version ?? "X"}-`;
|
||||||
|
for (let i = 0; i < 4; i++) {
|
||||||
|
license += generate();
|
||||||
|
if (i < 3) {
|
||||||
|
license += "-";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const checksum = calculateChecksum(license);
|
||||||
|
return `${license}-${checksum}`;
|
||||||
|
};
|
1
apps/web/src/features/licensing/index.ts
Normal file
1
apps/web/src/features/licensing/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export { LicensingClient } from "./lib/licensing-client";
|
59
apps/web/src/features/licensing/lib/licensing-client.ts
Normal file
59
apps/web/src/features/licensing/lib/licensing-client.ts
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
import {
|
||||||
|
type CreateLicenseInput,
|
||||||
|
type ValidateLicenseInputKeySchema,
|
||||||
|
createLicenseResponseSchema,
|
||||||
|
validateLicenseKeyResponseSchema,
|
||||||
|
} from "../schema";
|
||||||
|
|
||||||
|
export class LicensingClient {
|
||||||
|
apiUrl: string;
|
||||||
|
authToken?: string;
|
||||||
|
|
||||||
|
constructor({
|
||||||
|
apiUrl = "https://licensing.rallly.co",
|
||||||
|
authToken,
|
||||||
|
}: {
|
||||||
|
apiUrl?: string;
|
||||||
|
authToken?: string;
|
||||||
|
}) {
|
||||||
|
this.apiUrl = apiUrl;
|
||||||
|
this.authToken = authToken;
|
||||||
|
}
|
||||||
|
async createLicense(input: CreateLicenseInput) {
|
||||||
|
if (!this.authToken) {
|
||||||
|
throw new Error("Licensing API auth token is not configured.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await fetch(`${this.apiUrl}/api/v1/licenses`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `Bearer ${this.authToken}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify(input),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error("Failed to create license.");
|
||||||
|
}
|
||||||
|
return createLicenseResponseSchema.parse(await res.json());
|
||||||
|
}
|
||||||
|
async validateLicenseKey(input: ValidateLicenseInputKeySchema) {
|
||||||
|
const res = await fetch(
|
||||||
|
`${this.apiUrl}/api/v1/licenses/actions/validate-key`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(input),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error("Failed to validate license key.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return validateLicenseKeyResponseSchema.parse(await res.json());
|
||||||
|
}
|
||||||
|
}
|
88
apps/web/src/features/licensing/schema.ts
Normal file
88
apps/web/src/features/licensing/schema.ts
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
// =========================
|
||||||
|
// Enums & Basic Types
|
||||||
|
// =========================
|
||||||
|
|
||||||
|
export const licenseTypeSchema = z.enum(["PLUS", "ORGANIZATION", "ENTERPRISE"]);
|
||||||
|
export type LicenseType = z.infer<typeof licenseTypeSchema>;
|
||||||
|
|
||||||
|
export const licenseStatusSchema = z.enum(["ACTIVE", "REVOKED"]);
|
||||||
|
export type LicenseStatus = z.infer<typeof licenseStatusSchema>;
|
||||||
|
|
||||||
|
// =========================
|
||||||
|
// Generic API Response
|
||||||
|
// =========================
|
||||||
|
|
||||||
|
export const apiResponseSchema = <T extends z.ZodTypeAny>(dataSchema: T) =>
|
||||||
|
z.object({
|
||||||
|
data: dataSchema.optional(),
|
||||||
|
error: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type ApiResponse<T> = {
|
||||||
|
data?: T;
|
||||||
|
error?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// =========================
|
||||||
|
// Create License
|
||||||
|
// =========================
|
||||||
|
|
||||||
|
export const createLicenseInputSchema = z.object({
|
||||||
|
type: licenseTypeSchema,
|
||||||
|
seats: z.number().optional(),
|
||||||
|
expiresAt: z.date().optional(),
|
||||||
|
licenseeEmail: z.string().optional(),
|
||||||
|
licenseeName: z.string().optional(),
|
||||||
|
version: z.number().optional(),
|
||||||
|
stripeCustomerId: z.string().optional(),
|
||||||
|
});
|
||||||
|
export type CreateLicenseInput = z.infer<typeof createLicenseInputSchema>;
|
||||||
|
|
||||||
|
export const createLicenseResponseSchema = apiResponseSchema(
|
||||||
|
z.object({
|
||||||
|
key: z.string(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
export type CreateLicenseResponse = z.infer<typeof createLicenseResponseSchema>;
|
||||||
|
|
||||||
|
// =========================
|
||||||
|
// Validate License Key
|
||||||
|
// =========================
|
||||||
|
|
||||||
|
export const validateLicenseKeyInputSchema = z.object({
|
||||||
|
key: z.string(),
|
||||||
|
fingerprint: z.string().optional(),
|
||||||
|
});
|
||||||
|
export type ValidateLicenseInputKeySchema = z.infer<
|
||||||
|
typeof validateLicenseKeyInputSchema
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const validateLicenseKeyResponseSchema = apiResponseSchema(
|
||||||
|
z.object({
|
||||||
|
key: z.string(),
|
||||||
|
valid: z.boolean(),
|
||||||
|
status: licenseStatusSchema,
|
||||||
|
issuedAt: z.date(),
|
||||||
|
expiresAt: z.date().nullable(),
|
||||||
|
licenseeEmail: z.string().nullable(),
|
||||||
|
licenseeName: z.string().nullable(),
|
||||||
|
seats: z.number().nullable(),
|
||||||
|
type: licenseTypeSchema,
|
||||||
|
version: z.number().nullable(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
export type ValidateLicenseKeyResponse = z.infer<
|
||||||
|
typeof validateLicenseKeyResponseSchema
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const licenseCheckoutMetadataSchema = z.object({
|
||||||
|
licenseType: licenseTypeSchema,
|
||||||
|
seats: z.number(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type LicenseCheckoutMetada = z.infer<
|
||||||
|
typeof licenseCheckoutMetadataSchema
|
||||||
|
>;
|
|
@ -0,0 +1,59 @@
|
||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "LicenseType" AS ENUM ('PLUS', 'ORGANIZATION', 'ENTERPRISE');
|
||||||
|
|
||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "LicenseStatus" AS ENUM ('ACTIVE', 'REVOKED');
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "licenses" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"license_key" TEXT NOT NULL,
|
||||||
|
"version" INTEGER,
|
||||||
|
"type" "LicenseType" NOT NULL,
|
||||||
|
"seats" INTEGER,
|
||||||
|
"issued_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"expires_at" TIMESTAMP(3),
|
||||||
|
"licensee_email" TEXT,
|
||||||
|
"licensee_name" TEXT,
|
||||||
|
"status" "LicenseStatus" NOT NULL DEFAULT 'ACTIVE',
|
||||||
|
"stripe_customer_id" TEXT,
|
||||||
|
|
||||||
|
CONSTRAINT "licenses_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "license_validations" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"license_id" TEXT NOT NULL,
|
||||||
|
"ip_address" TEXT,
|
||||||
|
"fingerprint" TEXT,
|
||||||
|
"validated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"user_agent" TEXT,
|
||||||
|
|
||||||
|
CONSTRAINT "license_validations_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "instance_licenses" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"license_key" TEXT NOT NULL,
|
||||||
|
"version" INTEGER,
|
||||||
|
"type" "LicenseType" NOT NULL,
|
||||||
|
"seats" INTEGER,
|
||||||
|
"issued_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"expires_at" TIMESTAMP(3),
|
||||||
|
"licensee_email" TEXT,
|
||||||
|
"licensee_name" TEXT,
|
||||||
|
"status" "LicenseStatus" NOT NULL DEFAULT 'ACTIVE',
|
||||||
|
|
||||||
|
CONSTRAINT "instance_licenses_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "licenses_license_key_key" ON "licenses"("license_key");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "instance_licenses_license_key_key" ON "instance_licenses"("license_key");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "license_validations" ADD CONSTRAINT "license_validations_license_id_fkey" FOREIGN KEY ("license_id") REFERENCES "licenses"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
56
packages/database/prisma/models/licensing.prisma
Normal file
56
packages/database/prisma/models/licensing.prisma
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
enum LicenseType {
|
||||||
|
PLUS
|
||||||
|
ORGANIZATION
|
||||||
|
ENTERPRISE
|
||||||
|
}
|
||||||
|
|
||||||
|
enum LicenseStatus {
|
||||||
|
ACTIVE
|
||||||
|
REVOKED
|
||||||
|
}
|
||||||
|
|
||||||
|
model License {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
licenseKey String @unique @map("license_key")
|
||||||
|
version Int? @map("version")
|
||||||
|
type LicenseType
|
||||||
|
seats Int? @map("seats")
|
||||||
|
issuedAt DateTime @default(now()) @map("issued_at")
|
||||||
|
expiresAt DateTime? @map("expires_at")
|
||||||
|
licenseeEmail String? @map("licensee_email")
|
||||||
|
licenseeName String? @map("licensee_name")
|
||||||
|
status LicenseStatus @default(ACTIVE) @map("status")
|
||||||
|
stripeCustomerId String? @map("stripe_customer_id")
|
||||||
|
|
||||||
|
validations LicenseValidation[]
|
||||||
|
|
||||||
|
@@map("licenses")
|
||||||
|
}
|
||||||
|
|
||||||
|
model LicenseValidation {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
licenseId String @map("license_id")
|
||||||
|
license License @relation(fields: [licenseId], references: [id], onDelete: Cascade)
|
||||||
|
ipAddress String? @map("ip_address")
|
||||||
|
fingerprint String? @map("fingerprint")
|
||||||
|
validatedAt DateTime @default(now()) @map("validated_at")
|
||||||
|
userAgent String? @map("user_agent")
|
||||||
|
|
||||||
|
@@map("license_validations")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
model InstanceLicense {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
licenseKey String @unique @map("license_key")
|
||||||
|
version Int? @map("version")
|
||||||
|
type LicenseType
|
||||||
|
seats Int? @map("seats")
|
||||||
|
issuedAt DateTime @default(now()) @map("issued_at")
|
||||||
|
expiresAt DateTime? @map("expires_at")
|
||||||
|
licenseeEmail String? @map("licensee_email")
|
||||||
|
licenseeName String? @map("licensee_name")
|
||||||
|
status LicenseStatus @default(ACTIVE) @map("status")
|
||||||
|
|
||||||
|
@@map("instance_licenses")
|
||||||
|
}
|
123
pnpm-lock.yaml
generated
123
pnpm-lock.yaml
generated
|
@ -152,7 +152,7 @@ importers:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ai-sdk/openai':
|
'@ai-sdk/openai':
|
||||||
specifier: ^1.2.0
|
specifier: ^1.2.0
|
||||||
version: 1.3.20(zod@3.24.3)
|
version: 1.3.20(zod@3.25.20)
|
||||||
'@auth/prisma-adapter':
|
'@auth/prisma-adapter':
|
||||||
specifier: ^2.7.4
|
specifier: ^2.7.4
|
||||||
version: 2.9.0(@prisma/client@6.8.2(prisma@6.8.2(typescript@5.8.3))(typescript@5.8.3))(nodemailer@6.10.1)
|
version: 2.9.0(@prisma/client@6.8.2(prisma@6.8.2(typescript@5.8.3))(typescript@5.8.3))(nodemailer@6.10.1)
|
||||||
|
@ -162,6 +162,12 @@ importers:
|
||||||
'@aws-sdk/s3-request-presigner':
|
'@aws-sdk/s3-request-presigner':
|
||||||
specifier: ^3.645.0
|
specifier: ^3.645.0
|
||||||
version: 3.797.0
|
version: 3.797.0
|
||||||
|
'@hono-rate-limiter/redis':
|
||||||
|
specifier: ^0.1.4
|
||||||
|
version: 0.1.4(hono-rate-limiter@0.2.3(hono@4.7.10))
|
||||||
|
'@hono/zod-validator':
|
||||||
|
specifier: ^0.5.0
|
||||||
|
version: 0.5.0(hono@4.7.10)(zod@3.25.20)
|
||||||
'@hookform/resolvers':
|
'@hookform/resolvers':
|
||||||
specifier: ^3.3.1
|
specifier: ^3.3.1
|
||||||
version: 3.10.0(react-hook-form@7.56.1(react@19.1.0))
|
version: 3.10.0(react-hook-form@7.56.1(react@19.1.0))
|
||||||
|
@ -221,7 +227,7 @@ importers:
|
||||||
version: 8.1.0(typescript@5.8.3)
|
version: 8.1.0(typescript@5.8.3)
|
||||||
'@t3-oss/env-nextjs':
|
'@t3-oss/env-nextjs':
|
||||||
specifier: ^0.11.0
|
specifier: ^0.11.0
|
||||||
version: 0.11.1(typescript@5.8.3)(zod@3.24.3)
|
version: 0.11.1(typescript@5.8.3)(zod@3.25.20)
|
||||||
'@tanstack/react-query':
|
'@tanstack/react-query':
|
||||||
specifier: ^5.74.11
|
specifier: ^5.74.11
|
||||||
version: 5.74.11(react@19.1.0)
|
version: 5.74.11(react@19.1.0)
|
||||||
|
@ -253,11 +259,11 @@ importers:
|
||||||
specifier: ^2.0.0
|
specifier: ^2.0.0
|
||||||
version: 2.0.0(@aws-sdk/credential-provider-web-identity@3.797.0)
|
version: 2.0.0(@aws-sdk/credential-provider-web-identity@3.797.0)
|
||||||
'@vercel/kv':
|
'@vercel/kv':
|
||||||
specifier: ^2.0.0
|
specifier: ^3.0.0
|
||||||
version: 2.0.0
|
version: 3.0.0
|
||||||
ai:
|
ai:
|
||||||
specifier: ^4.1.50
|
specifier: ^4.1.50
|
||||||
version: 4.3.10(react@19.1.0)(zod@3.24.3)
|
version: 4.3.10(react@19.1.0)(zod@3.25.20)
|
||||||
autoprefixer:
|
autoprefixer:
|
||||||
specifier: ^10.4.13
|
specifier: ^10.4.13
|
||||||
version: 10.4.21(postcss@8.5.3)
|
version: 10.4.21(postcss@8.5.3)
|
||||||
|
@ -279,6 +285,12 @@ importers:
|
||||||
dayjs:
|
dayjs:
|
||||||
specifier: ^1.11.13
|
specifier: ^1.11.13
|
||||||
version: 1.11.13
|
version: 1.11.13
|
||||||
|
hono:
|
||||||
|
specifier: ^4.7.10
|
||||||
|
version: 4.7.10
|
||||||
|
hono-rate-limiter:
|
||||||
|
specifier: ^0.2.1
|
||||||
|
version: 0.2.3(hono@4.7.10)
|
||||||
i18next:
|
i18next:
|
||||||
specifier: ^24.2.2
|
specifier: ^24.2.2
|
||||||
version: 24.2.3(typescript@5.8.3)
|
version: 24.2.3(typescript@5.8.3)
|
||||||
|
@ -379,8 +391,8 @@ importers:
|
||||||
specifier: ^1.5.1
|
specifier: ^1.5.1
|
||||||
version: 1.5.2
|
version: 1.5.2
|
||||||
zod:
|
zod:
|
||||||
specifier: ^3.23.8
|
specifier: ^3.25.6
|
||||||
version: 3.24.3
|
version: 3.25.20
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@babel/core':
|
'@babel/core':
|
||||||
specifier: ^7.26.10
|
specifier: ^7.26.10
|
||||||
|
@ -2026,6 +2038,17 @@ packages:
|
||||||
resolution: {integrity: sha512-kjotm7XJrJ6v+7knhPaRgaT6q8F8K2jiafwYdNHLzmV0uGLuZY43FK6smNSHUPrhq5kX2slCUy+RGG/xGqmIKA==}
|
resolution: {integrity: sha512-kjotm7XJrJ6v+7knhPaRgaT6q8F8K2jiafwYdNHLzmV0uGLuZY43FK6smNSHUPrhq5kX2slCUy+RGG/xGqmIKA==}
|
||||||
engines: {node: '>=10.13.0'}
|
engines: {node: '>=10.13.0'}
|
||||||
|
|
||||||
|
'@hono-rate-limiter/redis@0.1.4':
|
||||||
|
resolution: {integrity: sha512-RSrVX5N2Oo/xXApskegu667cBVHyr8RXGWnbRDGjU2py8pN4BttEKSHA0iKi3BAwh1xSkENgDRng4tpFD9DbKg==}
|
||||||
|
peerDependencies:
|
||||||
|
hono-rate-limiter: ^0.2.1
|
||||||
|
|
||||||
|
'@hono/zod-validator@0.5.0':
|
||||||
|
resolution: {integrity: sha512-ds5bW6DCgAnNHP33E3ieSbaZFd5dkV52ZjyaXtGoR06APFrCtzAsKZxTHwOrJNBdXsi0e5wNwo5L4nVEVnJUdg==}
|
||||||
|
peerDependencies:
|
||||||
|
hono: '>=3.9.0'
|
||||||
|
zod: ^3.19.1
|
||||||
|
|
||||||
'@hookform/resolvers@3.10.0':
|
'@hookform/resolvers@3.10.0':
|
||||||
resolution: {integrity: sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==}
|
resolution: {integrity: sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
@ -4221,8 +4244,8 @@ packages:
|
||||||
'@aws-sdk/credential-provider-web-identity':
|
'@aws-sdk/credential-provider-web-identity':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@vercel/kv@2.0.0':
|
'@vercel/kv@3.0.0':
|
||||||
resolution: {integrity: sha512-zdVrhbzZBYo5d1Hfn4bKtqCeKf0FuzW8rSHauzQVMUgv1+1JOwof2mWcBuI+YMJy8s0G0oqAUfQ7HgUDzb8EbA==}
|
resolution: {integrity: sha512-pKT8fRnfyYk2MgvyB6fn6ipJPCdfZwiKDdw7vB+HL50rjboEBHDVBEcnwfkEpVSp2AjNtoaOUH7zG+bVC/rvSg==}
|
||||||
engines: {node: '>=14.6'}
|
engines: {node: '>=14.6'}
|
||||||
|
|
||||||
'@vitest/expect@2.1.9':
|
'@vitest/expect@2.1.9':
|
||||||
|
@ -5296,6 +5319,15 @@ packages:
|
||||||
hoist-non-react-statics@3.3.2:
|
hoist-non-react-statics@3.3.2:
|
||||||
resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
|
resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
|
||||||
|
|
||||||
|
hono-rate-limiter@0.2.3:
|
||||||
|
resolution: {integrity: sha512-/pQIJWMd8saoySMoM3rhHNiC3XtZuZDkxBvtnPmRKrpNBZIG38k0cs+nVmBW5pc8HAmkS+I/14rbHfRObekWmw==}
|
||||||
|
peerDependencies:
|
||||||
|
hono: ^4.1.1
|
||||||
|
|
||||||
|
hono@4.7.10:
|
||||||
|
resolution: {integrity: sha512-QkACju9MiN59CKSY5JsGZCYmPZkA6sIW6OFCUp7qDjZu6S6KHtJHhAc9Uy9mV9F8PJ1/HQ3ybZF2yjCa/73fvQ==}
|
||||||
|
engines: {node: '>=16.9.0'}
|
||||||
|
|
||||||
html-escaper@2.0.2:
|
html-escaper@2.0.2:
|
||||||
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
|
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
|
||||||
|
|
||||||
|
@ -7341,47 +7373,47 @@ packages:
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
zod: ^3.24.1
|
zod: ^3.24.1
|
||||||
|
|
||||||
zod@3.24.3:
|
zod@3.25.20:
|
||||||
resolution: {integrity: sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==}
|
resolution: {integrity: sha512-z03fqpTMDF1G02VLKUMt6vyACE7rNWkh3gpXVHgPTw28NPtDFRGvcpTtPwn2kMKtQ0idtYJUTxchytmnqYswcw==}
|
||||||
|
|
||||||
zwitch@2.0.4:
|
zwitch@2.0.4:
|
||||||
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
|
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
|
||||||
|
|
||||||
snapshots:
|
snapshots:
|
||||||
|
|
||||||
'@ai-sdk/openai@1.3.20(zod@3.24.3)':
|
'@ai-sdk/openai@1.3.20(zod@3.25.20)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ai-sdk/provider': 1.1.3
|
'@ai-sdk/provider': 1.1.3
|
||||||
'@ai-sdk/provider-utils': 2.2.7(zod@3.24.3)
|
'@ai-sdk/provider-utils': 2.2.7(zod@3.25.20)
|
||||||
zod: 3.24.3
|
zod: 3.25.20
|
||||||
|
|
||||||
'@ai-sdk/provider-utils@2.2.7(zod@3.24.3)':
|
'@ai-sdk/provider-utils@2.2.7(zod@3.25.20)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ai-sdk/provider': 1.1.3
|
'@ai-sdk/provider': 1.1.3
|
||||||
nanoid: 3.3.11
|
nanoid: 3.3.11
|
||||||
secure-json-parse: 2.7.0
|
secure-json-parse: 2.7.0
|
||||||
zod: 3.24.3
|
zod: 3.25.20
|
||||||
|
|
||||||
'@ai-sdk/provider@1.1.3':
|
'@ai-sdk/provider@1.1.3':
|
||||||
dependencies:
|
dependencies:
|
||||||
json-schema: 0.4.0
|
json-schema: 0.4.0
|
||||||
|
|
||||||
'@ai-sdk/react@1.2.9(react@19.1.0)(zod@3.24.3)':
|
'@ai-sdk/react@1.2.9(react@19.1.0)(zod@3.25.20)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ai-sdk/provider-utils': 2.2.7(zod@3.24.3)
|
'@ai-sdk/provider-utils': 2.2.7(zod@3.25.20)
|
||||||
'@ai-sdk/ui-utils': 1.2.8(zod@3.24.3)
|
'@ai-sdk/ui-utils': 1.2.8(zod@3.25.20)
|
||||||
react: 19.1.0
|
react: 19.1.0
|
||||||
swr: 2.3.3(react@19.1.0)
|
swr: 2.3.3(react@19.1.0)
|
||||||
throttleit: 2.1.0
|
throttleit: 2.1.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
zod: 3.24.3
|
zod: 3.25.20
|
||||||
|
|
||||||
'@ai-sdk/ui-utils@1.2.8(zod@3.24.3)':
|
'@ai-sdk/ui-utils@1.2.8(zod@3.25.20)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ai-sdk/provider': 1.1.3
|
'@ai-sdk/provider': 1.1.3
|
||||||
'@ai-sdk/provider-utils': 2.2.7(zod@3.24.3)
|
'@ai-sdk/provider-utils': 2.2.7(zod@3.25.20)
|
||||||
zod: 3.24.3
|
zod: 3.25.20
|
||||||
zod-to-json-schema: 3.24.5(zod@3.24.3)
|
zod-to-json-schema: 3.24.5(zod@3.25.20)
|
||||||
|
|
||||||
'@alloc/quick-lru@5.2.0': {}
|
'@alloc/quick-lru@5.2.0': {}
|
||||||
|
|
||||||
|
@ -8973,6 +9005,15 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-negated-glob: 1.0.0
|
is-negated-glob: 1.0.0
|
||||||
|
|
||||||
|
'@hono-rate-limiter/redis@0.1.4(hono-rate-limiter@0.2.3(hono@4.7.10))':
|
||||||
|
dependencies:
|
||||||
|
hono-rate-limiter: 0.2.3(hono@4.7.10)
|
||||||
|
|
||||||
|
'@hono/zod-validator@0.5.0(hono@4.7.10)(zod@3.25.20)':
|
||||||
|
dependencies:
|
||||||
|
hono: 4.7.10
|
||||||
|
zod: 3.25.20
|
||||||
|
|
||||||
'@hookform/resolvers@3.10.0(react-hook-form@7.56.1(react@19.1.0))':
|
'@hookform/resolvers@3.10.0(react-hook-form@7.56.1(react@19.1.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
react-hook-form: 7.56.1(react@19.1.0)
|
react-hook-form: 7.56.1(react@19.1.0)
|
||||||
|
@ -10977,16 +11018,16 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@t3-oss/env-core@0.11.1(typescript@5.8.3)(zod@3.24.3)':
|
'@t3-oss/env-core@0.11.1(typescript@5.8.3)(zod@3.25.20)':
|
||||||
dependencies:
|
dependencies:
|
||||||
zod: 3.24.3
|
zod: 3.25.20
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
typescript: 5.8.3
|
typescript: 5.8.3
|
||||||
|
|
||||||
'@t3-oss/env-nextjs@0.11.1(typescript@5.8.3)(zod@3.24.3)':
|
'@t3-oss/env-nextjs@0.11.1(typescript@5.8.3)(zod@3.25.20)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@t3-oss/env-core': 0.11.1(typescript@5.8.3)(zod@3.24.3)
|
'@t3-oss/env-core': 0.11.1(typescript@5.8.3)(zod@3.25.20)
|
||||||
zod: 3.24.3
|
zod: 3.25.20
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
typescript: 5.8.3
|
typescript: 5.8.3
|
||||||
|
|
||||||
|
@ -11270,7 +11311,7 @@ snapshots:
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@aws-sdk/credential-provider-web-identity': 3.797.0
|
'@aws-sdk/credential-provider-web-identity': 3.797.0
|
||||||
|
|
||||||
'@vercel/kv@2.0.0':
|
'@vercel/kv@3.0.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@upstash/redis': 1.34.8
|
'@upstash/redis': 1.34.8
|
||||||
|
|
||||||
|
@ -11447,15 +11488,15 @@ snapshots:
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
ai@4.3.10(react@19.1.0)(zod@3.24.3):
|
ai@4.3.10(react@19.1.0)(zod@3.25.20):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ai-sdk/provider': 1.1.3
|
'@ai-sdk/provider': 1.1.3
|
||||||
'@ai-sdk/provider-utils': 2.2.7(zod@3.24.3)
|
'@ai-sdk/provider-utils': 2.2.7(zod@3.25.20)
|
||||||
'@ai-sdk/react': 1.2.9(react@19.1.0)(zod@3.24.3)
|
'@ai-sdk/react': 1.2.9(react@19.1.0)(zod@3.25.20)
|
||||||
'@ai-sdk/ui-utils': 1.2.8(zod@3.24.3)
|
'@ai-sdk/ui-utils': 1.2.8(zod@3.25.20)
|
||||||
'@opentelemetry/api': 1.9.0
|
'@opentelemetry/api': 1.9.0
|
||||||
jsondiffpatch: 0.6.0
|
jsondiffpatch: 0.6.0
|
||||||
zod: 3.24.3
|
zod: 3.25.20
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
react: 19.1.0
|
react: 19.1.0
|
||||||
|
|
||||||
|
@ -12469,6 +12510,12 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
react-is: 16.13.1
|
react-is: 16.13.1
|
||||||
|
|
||||||
|
hono-rate-limiter@0.2.3(hono@4.7.10):
|
||||||
|
dependencies:
|
||||||
|
hono: 4.7.10
|
||||||
|
|
||||||
|
hono@4.7.10: {}
|
||||||
|
|
||||||
html-escaper@2.0.2: {}
|
html-escaper@2.0.2: {}
|
||||||
|
|
||||||
html-parse-stringify@3.0.1:
|
html-parse-stringify@3.0.1:
|
||||||
|
@ -14858,10 +14905,10 @@ snapshots:
|
||||||
toposort: 2.0.2
|
toposort: 2.0.2
|
||||||
type-fest: 2.19.0
|
type-fest: 2.19.0
|
||||||
|
|
||||||
zod-to-json-schema@3.24.5(zod@3.24.3):
|
zod-to-json-schema@3.24.5(zod@3.25.20):
|
||||||
dependencies:
|
dependencies:
|
||||||
zod: 3.24.3
|
zod: 3.25.20
|
||||||
|
|
||||||
zod@3.24.3: {}
|
zod@3.25.20: {}
|
||||||
|
|
||||||
zwitch@2.0.4: {}
|
zwitch@2.0.4: {}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue