basic party analysis
This commit is contained in:
parent
e2fee7850c
commit
dfef859ede
9 changed files with 960 additions and 47 deletions
35
api/biome.json
Normal file
35
api/biome.json
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"$schema": "https://biomejs.dev/schemas/2.4.11/schema.json",
|
||||
"vcs": {
|
||||
"enabled": false,
|
||||
"clientKind": "git",
|
||||
"useIgnoreFile": false
|
||||
},
|
||||
"files": {
|
||||
"ignoreUnknown": false,
|
||||
"includes": [
|
||||
"**/src/**/*",
|
||||
"**/.vscode/**/*",
|
||||
"**/index.html",
|
||||
"**/vite.config.ts",
|
||||
"!**/src/routeTree.gen.ts",
|
||||
"!**/src/styles.css"
|
||||
]
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"indentStyle": "tab"
|
||||
},
|
||||
"assist": { "actions": { "source": { "organizeImports": "on" } } },
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"recommended": true
|
||||
}
|
||||
},
|
||||
"javascript": {
|
||||
"formatter": {
|
||||
"quoteStyle": "double"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,10 +6,13 @@
|
|||
"main": "src/index.ts",
|
||||
"scripts": {
|
||||
"dev": "bun run --watch src/index.ts",
|
||||
"typecheck": "tsc --noEmit"
|
||||
"typecheck": "tsc --noEmit",
|
||||
"test": "DATABASE_URL=postgres://postgres:postgres@localhost/postgres vitest run"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest"
|
||||
"@types/bun": "latest",
|
||||
"tsx": "^4.19.2",
|
||||
"vitest": "^3.0.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5"
|
||||
|
|
@ -19,6 +22,7 @@
|
|||
"@spotify/web-api-ts-sdk": "^1.2.0",
|
||||
"@statsfm/statsfm.js": "github.com:statsfm/statsfm.js",
|
||||
"better-auth": "^1.6.5",
|
||||
"elysia": "catalog:"
|
||||
"elysia": "catalog:",
|
||||
"vite": "^8.0.10"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ export const partyMember = pgTable(
|
|||
partyId: uuid()
|
||||
.references(() => party.id)
|
||||
.notNull(),
|
||||
userId: uuid()
|
||||
userId: text()
|
||||
.references(() => user.id)
|
||||
.notNull(),
|
||||
joinedAt: timestamp().defaultNow().notNull(),
|
||||
|
|
|
|||
372
api/src/test/factories.ts
Normal file
372
api/src/test/factories.ts
Normal file
|
|
@ -0,0 +1,372 @@
|
|||
import { randomUUID } from "node:crypto";
|
||||
import { db } from "../db";
|
||||
import * as schema from "../db/schema";
|
||||
|
||||
export interface TestUser {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
export interface TestParty {
|
||||
partyId: string;
|
||||
hostId: string;
|
||||
}
|
||||
|
||||
export interface TestGenre {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface TestArtist {
|
||||
id: string;
|
||||
name: string;
|
||||
genreIds: string[];
|
||||
}
|
||||
|
||||
export interface TestAlbum {
|
||||
id: string;
|
||||
name: string;
|
||||
artistIds: string[];
|
||||
genreIds: string[];
|
||||
}
|
||||
|
||||
export interface TestTrack {
|
||||
id: string;
|
||||
name: string;
|
||||
albumId: string;
|
||||
artistIds: string[];
|
||||
}
|
||||
|
||||
export async function createGenre(name: string): Promise<TestGenre> {
|
||||
const id = randomUUID();
|
||||
await db.insert(schema.genre).values({ id, name });
|
||||
return { id, name };
|
||||
}
|
||||
|
||||
export async function createArtist(
|
||||
name: string,
|
||||
genreIds: string[] = [],
|
||||
): Promise<TestArtist> {
|
||||
const id = randomUUID();
|
||||
await db.insert(schema.artist).values({
|
||||
id,
|
||||
platform_id: `test-artist-${id}`,
|
||||
platform: "spotify",
|
||||
name,
|
||||
popularity: 50,
|
||||
});
|
||||
|
||||
if (genreIds.length > 0) {
|
||||
const relations = genreIds.map((genreId) => ({
|
||||
artistId: id,
|
||||
genreId,
|
||||
}));
|
||||
await db.insert(schema.artistGenre).values(relations);
|
||||
}
|
||||
|
||||
return { id, name, genreIds };
|
||||
}
|
||||
|
||||
export async function createAlbum(
|
||||
name: string,
|
||||
artistIds: string[] = [],
|
||||
genreIds: string[] = [],
|
||||
): Promise<TestAlbum> {
|
||||
const id = randomUUID();
|
||||
await db.insert(schema.album).values({
|
||||
id,
|
||||
type: "album",
|
||||
name,
|
||||
platform: "spotify",
|
||||
platform_id: `test-album-${id}`,
|
||||
popularity: 70,
|
||||
});
|
||||
|
||||
if (artistIds.length > 0) {
|
||||
const relations = artistIds.map((artistId) => ({
|
||||
albumId: id,
|
||||
artistId,
|
||||
}));
|
||||
await db.insert(schema.albumArtist).values(relations);
|
||||
}
|
||||
|
||||
if (genreIds.length > 0) {
|
||||
const relations = genreIds.map((genreId) => ({
|
||||
albumId: id,
|
||||
genreId,
|
||||
}));
|
||||
await db.insert(schema.albumGenre).values(relations);
|
||||
}
|
||||
|
||||
return { id, name, artistIds, genreIds };
|
||||
}
|
||||
|
||||
export async function createTrack(
|
||||
name: string,
|
||||
albumId: string,
|
||||
artistIds: string[] = [],
|
||||
): Promise<TestTrack> {
|
||||
const id = randomUUID();
|
||||
await db.insert(schema.track).values({
|
||||
id,
|
||||
albumId,
|
||||
name,
|
||||
platform: "spotify",
|
||||
platform_id: `test-track-${id}`,
|
||||
popularity: 80,
|
||||
duration: 180000,
|
||||
explicit: false,
|
||||
disc_number: 1,
|
||||
track_number: 1,
|
||||
});
|
||||
|
||||
if (artistIds.length > 0) {
|
||||
const relations = artistIds.map((artistId) => ({
|
||||
trackId: id,
|
||||
artistId,
|
||||
}));
|
||||
await db.insert(schema.trackArtist).values(relations);
|
||||
}
|
||||
|
||||
return { id, name, albumId, artistIds };
|
||||
}
|
||||
|
||||
export async function createUser(name: string): Promise<TestUser> {
|
||||
const id = randomUUID();
|
||||
const emailValue = `test-${id}@example.com`;
|
||||
await db.insert(schema.user).values({
|
||||
id,
|
||||
name,
|
||||
email: emailValue,
|
||||
emailVerified: true,
|
||||
});
|
||||
return { id, name, email: emailValue };
|
||||
}
|
||||
|
||||
export async function createParty(hostId: string): Promise<TestParty> {
|
||||
const partyId = randomUUID();
|
||||
await db.insert(schema.party).values({
|
||||
id: partyId,
|
||||
hostId,
|
||||
status: "created",
|
||||
});
|
||||
return { partyId, hostId };
|
||||
}
|
||||
|
||||
export async function joinParty(
|
||||
partyId: string,
|
||||
userId: string,
|
||||
): Promise<void> {
|
||||
await db.insert(schema.partyMember).values({
|
||||
partyId,
|
||||
userId,
|
||||
});
|
||||
}
|
||||
|
||||
export async function addTopTrack(
|
||||
userId: string,
|
||||
trackId: string,
|
||||
position: number,
|
||||
timeline: "short_term" | "medium_term" | "long_term" = "medium_term",
|
||||
): Promise<void> {
|
||||
await db.insert(schema.topTrack).values({
|
||||
userId,
|
||||
trackId,
|
||||
position,
|
||||
timeline,
|
||||
});
|
||||
}
|
||||
|
||||
export async function addTopArtist(
|
||||
userId: string,
|
||||
artistId: string,
|
||||
position: number,
|
||||
timeline: "short_term" | "medium_term" | "long_term" = "medium_term",
|
||||
): Promise<void> {
|
||||
await db.insert(schema.topArtist).values({
|
||||
userId,
|
||||
artistId,
|
||||
position,
|
||||
timeline,
|
||||
});
|
||||
}
|
||||
|
||||
export async function addSavedTrack(
|
||||
userId: string,
|
||||
trackId: string,
|
||||
): Promise<void> {
|
||||
await db.insert(schema.savedTrack).values({
|
||||
userId,
|
||||
trackId,
|
||||
});
|
||||
}
|
||||
|
||||
export async function addPlaybackHistory(
|
||||
userId: string,
|
||||
trackId: string,
|
||||
playedAt: Date = new Date(),
|
||||
): Promise<void> {
|
||||
await db.insert(schema.playbackHistory).values({
|
||||
userId,
|
||||
trackId,
|
||||
played_at: playedAt,
|
||||
});
|
||||
}
|
||||
|
||||
export async function addFollowedArtist(
|
||||
userId: string,
|
||||
artistId: string,
|
||||
): Promise<void> {
|
||||
await db.insert(schema.followedArtist).values({
|
||||
userId,
|
||||
artistId,
|
||||
});
|
||||
}
|
||||
|
||||
export async function addSavedAlbum(
|
||||
userId: string,
|
||||
albumId: string,
|
||||
): Promise<void> {
|
||||
await db.insert(schema.savedAlbum).values({
|
||||
userId,
|
||||
albumId,
|
||||
});
|
||||
}
|
||||
|
||||
export async function seedPartyWithTwoSimilarUsers(): Promise<{
|
||||
partyId: string;
|
||||
hostId: string;
|
||||
userIdA: string;
|
||||
userIdB: string;
|
||||
sharedTrackId: string;
|
||||
sharedArtistId: string;
|
||||
sharedGenreId: string;
|
||||
uniqueTrackIdA?: string;
|
||||
uniqueTrackIdB?: string;
|
||||
}> {
|
||||
// Create shared genre with unique name
|
||||
const sharedGenreName = `shared-rock-${randomUUID()}`;
|
||||
const genre = await createGenre(sharedGenreName);
|
||||
const genreId = genre.id;
|
||||
|
||||
// Create shared artist with genre
|
||||
const sharedArtist = await createArtist(`shared-artist-${randomUUID()}`, [
|
||||
genreId,
|
||||
]);
|
||||
const sharedArtistId = sharedArtist.id;
|
||||
|
||||
// Create shared album
|
||||
const sharedAlbum = await createAlbum(
|
||||
`shared-album-${randomUUID()}`,
|
||||
[sharedArtistId],
|
||||
[genreId],
|
||||
);
|
||||
|
||||
// Create shared track
|
||||
const sharedTrack = await createTrack(
|
||||
`shared-track-${randomUUID()}`,
|
||||
sharedAlbum.id,
|
||||
[sharedArtistId],
|
||||
);
|
||||
const sharedTrackId = sharedTrack.id;
|
||||
|
||||
// Create users
|
||||
const userA = await createUser("User A");
|
||||
const userB = await createUser("User B");
|
||||
|
||||
// Create party with user A as host
|
||||
const party = await createParty(userA.id);
|
||||
|
||||
// Both users join the party
|
||||
await joinParty(party.partyId, userA.id);
|
||||
await joinParty(party.partyId, userB.id);
|
||||
|
||||
// Give both users the shared track at position 1
|
||||
await addTopTrack(userA.id, sharedTrackId, 1);
|
||||
await addTopTrack(userB.id, sharedTrackId, 1);
|
||||
|
||||
// Give both users the shared artist at position 1
|
||||
await addTopArtist(userA.id, sharedArtistId, 1);
|
||||
await addTopArtist(userB.id, sharedArtistId, 1);
|
||||
|
||||
// Add some saved tracks for user A
|
||||
await addSavedTrack(userA.id, sharedTrackId);
|
||||
|
||||
// Add playback history (recent)
|
||||
await addPlaybackHistory(userA.id, sharedTrackId, new Date());
|
||||
await addPlaybackHistory(userB.id, sharedTrackId, new Date());
|
||||
|
||||
return {
|
||||
partyId: party.partyId,
|
||||
hostId: party.hostId,
|
||||
userIdA: userA.id,
|
||||
userIdB: userB.id,
|
||||
sharedTrackId,
|
||||
sharedArtistId,
|
||||
sharedGenreId: genreId,
|
||||
};
|
||||
}
|
||||
|
||||
export async function seedPartyWithThreeDiverseUsers(): Promise<{
|
||||
partyId: string;
|
||||
userIds: string[];
|
||||
genres: TestGenre[];
|
||||
artists: TestArtist[];
|
||||
tracks: TestTrack[];
|
||||
}> {
|
||||
// Create 3 distinct genres with unique names
|
||||
const genres = await Promise.all([
|
||||
createGenre(`jazz-${randomUUID()}`),
|
||||
createGenre(`electronic-${randomUUID()}`),
|
||||
createGenre(`classical-${randomUUID()}`),
|
||||
]);
|
||||
|
||||
const userIds: string[] = [];
|
||||
const artists: TestArtist[] = [];
|
||||
const tracks: TestTrack[] = [];
|
||||
|
||||
// Create one user per genre with no overlap
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const user = await createUser(`Diverse User ${i}`);
|
||||
userIds.push(user.id);
|
||||
|
||||
const genre = genres[i]!;
|
||||
const artist = await createArtist(`artist-${i}-${randomUUID()}`, [
|
||||
genre.id,
|
||||
]);
|
||||
artists.push(artist);
|
||||
|
||||
const album = await createAlbum(
|
||||
`album-${i}-${randomUUID()}`,
|
||||
[artist.id],
|
||||
[genre.id],
|
||||
);
|
||||
const track = await createTrack(`track-${i}-${randomUUID()}`, album.id, [
|
||||
artist.id,
|
||||
]);
|
||||
tracks.push(track);
|
||||
}
|
||||
|
||||
// Create party with first user as host
|
||||
const hostId = userIds[0]!;
|
||||
const party = await createParty(hostId);
|
||||
|
||||
for (const userId of userIds) {
|
||||
await joinParty(party.partyId, userId);
|
||||
}
|
||||
|
||||
// Give each user their unique track
|
||||
for (let i = 0; i < 3; i++) {
|
||||
await addTopTrack(userIds[i]!, tracks[i]?.id, 1);
|
||||
await addTopArtist(userIds[i]!, artists[i]?.id, 1);
|
||||
}
|
||||
|
||||
return {
|
||||
partyId: party.partyId,
|
||||
userIds,
|
||||
genres,
|
||||
artists,
|
||||
tracks,
|
||||
};
|
||||
}
|
||||
67
api/src/test/setup.ts
Normal file
67
api/src/test/setup.ts
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import { execSync } from "node:child_process";
|
||||
import { Pool } from "pg";
|
||||
import * as schema from "../db/schema";
|
||||
|
||||
const url = process.env.DATABASE_URL;
|
||||
|
||||
if (!url) {
|
||||
throw new Error("DATABASE_URL is required for tests");
|
||||
}
|
||||
|
||||
let pool: Pool | null = null;
|
||||
|
||||
const getPool = (): Pool => {
|
||||
if (!pool) {
|
||||
pool = new Pool({ connectionString: url! });
|
||||
}
|
||||
return pool;
|
||||
};
|
||||
|
||||
const getTableNames = (): string[] => {
|
||||
const tables: string[] = [];
|
||||
for (const [, value] of Object.entries(schema)) {
|
||||
if (
|
||||
typeof value === "object" &&
|
||||
value !== null &&
|
||||
"tableName" in value &&
|
||||
typeof (value as { tableName: unknown }).tableName === "string"
|
||||
) {
|
||||
tables.push((value as { tableName: string }).tableName);
|
||||
}
|
||||
}
|
||||
return tables;
|
||||
};
|
||||
|
||||
const _runMigrations = async () => {
|
||||
try {
|
||||
execSync("bun x drizzle-kit push", {
|
||||
stdio: "pipe",
|
||||
env: { ...process.env },
|
||||
cwd: `${process.cwd()}/../..`,
|
||||
});
|
||||
} catch {
|
||||
// migrations may have already been applied
|
||||
}
|
||||
};
|
||||
|
||||
const truncateTables = async () => {
|
||||
const pool = getPool();
|
||||
const tables = getTableNames();
|
||||
if (tables.length === 0) return;
|
||||
const deleteSQL = `DELETE FROM ${tables.map((t) => `"${t}"`).join(", ")} CASCADE;`;
|
||||
try {
|
||||
await pool.query(deleteSQL);
|
||||
} catch {
|
||||
// some tables may not exist yet, ignore
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
await truncateTables();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
if (pool) {
|
||||
await pool.end();
|
||||
}
|
||||
});
|
||||
323
api/src/workflows/__tests__/party-analysis.test.ts
Normal file
323
api/src/workflows/__tests__/party-analysis.test.ts
Normal file
|
|
@ -0,0 +1,323 @@
|
|||
import { DBOS } from "@dbos-inc/dbos-sdk";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
addFollowedArtist,
|
||||
addPlaybackHistory,
|
||||
addSavedTrack,
|
||||
addTopArtist,
|
||||
addTopTrack,
|
||||
createAlbum,
|
||||
createArtist,
|
||||
createParty,
|
||||
createTrack,
|
||||
createUser,
|
||||
joinParty,
|
||||
seedPartyWithThreeDiverseUsers,
|
||||
seedPartyWithTwoSimilarUsers,
|
||||
} from "../../test/factories";
|
||||
import { partyAnalysisWorkflow } from "../party-analysis";
|
||||
import "../../dbos";
|
||||
|
||||
await DBOS.launch();
|
||||
|
||||
describe("PartyAnalysisWorkflow", () => {
|
||||
describe("analyzeParty - basic behavior", () => {
|
||||
it("returns empty results for party with fewer than 2 members", async () => {
|
||||
const user = await createUser("Solo User");
|
||||
const party = await createParty(user.id);
|
||||
await joinParty(party.partyId, user.id);
|
||||
|
||||
const result = await partyAnalysisWorkflow.analyzeParty(party.partyId);
|
||||
|
||||
expect(result.storyClusters).toHaveLength(0);
|
||||
expect(result.pairwise).toHaveLength(0);
|
||||
expect(result.groupSummary.totalMembers).toBe(1);
|
||||
expect(result.groupSummary.mostDiverseMember).toBeNull();
|
||||
expect(result.groupSummary.mostAlignedPair).toBeNull();
|
||||
expect(result.memberProfiles).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("returns empty results for party with 0 members", async () => {
|
||||
const user = await createUser("Host Only");
|
||||
const party = await createParty(user.id);
|
||||
// Don't add any members
|
||||
|
||||
const result = await partyAnalysisWorkflow.analyzeParty(party.partyId);
|
||||
|
||||
expect(result.storyClusters).toHaveLength(0);
|
||||
expect(result.groupSummary.totalMembers).toBe(0);
|
||||
});
|
||||
|
||||
it("returns empty results for party with 1 member and no data", async () => {
|
||||
const user = await createUser("Empty Member");
|
||||
const party = await createParty(user.id);
|
||||
await joinParty(party.partyId, user.id);
|
||||
|
||||
const result = await partyAnalysisWorkflow.analyzeParty(party.partyId);
|
||||
|
||||
expect(result.storyClusters).toHaveLength(0);
|
||||
expect(result.groupSummary.totalMembers).toBe(1);
|
||||
expect(result.memberProfiles).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("analyzeParty - two similar users", () => {
|
||||
it("correctly identifies shared tracks, artists, and genres", async () => {
|
||||
const { partyId, userIdA, userIdB, sharedTrackId, sharedArtistId } =
|
||||
await seedPartyWithTwoSimilarUsers();
|
||||
|
||||
const result = await partyAnalysisWorkflow.analyzeParty(partyId);
|
||||
|
||||
expect(result.storyClusters).toHaveLength(1);
|
||||
const cluster = result.storyClusters[0]!;
|
||||
expect(cluster.memberCount).toBe(2);
|
||||
|
||||
// The shared track should appear in the cluster
|
||||
const sharedClusterTrack = cluster.tracks.find(
|
||||
(t) => t.id === sharedTrackId,
|
||||
);
|
||||
expect(sharedClusterTrack).toBeDefined();
|
||||
if (sharedClusterTrack) {
|
||||
expect(sharedClusterTrack.memberCount).toBe(2);
|
||||
}
|
||||
|
||||
// The shared artist should appear in the cluster
|
||||
const sharedClusterArtist = cluster.artists.find(
|
||||
(a) => a.id === sharedArtistId,
|
||||
);
|
||||
expect(sharedClusterArtist).toBeDefined();
|
||||
if (sharedClusterArtist) {
|
||||
expect(sharedClusterArtist.memberCount).toBe(2);
|
||||
}
|
||||
|
||||
// Should have exactly 1 pairwise comparison
|
||||
expect(result.pairwise).toHaveLength(1);
|
||||
const comparison = result.pairwise[0]!;
|
||||
expect(comparison.userIdA).toBe(userIdA);
|
||||
expect(comparison.userIdB).toBe(userIdB);
|
||||
expect(comparison.sharedTracks).toBeGreaterThan(0);
|
||||
expect(comparison.sharedArtists).toBeGreaterThan(0);
|
||||
expect(comparison.similarity).toBeGreaterThan(0);
|
||||
expect(comparison.similarity).toBeLessThan(1);
|
||||
|
||||
// Should have member profiles
|
||||
expect(result.memberProfiles).toHaveLength(2);
|
||||
const profileA = result.memberProfiles.find((p) => p.userId === userIdA);
|
||||
const profileB = result.memberProfiles.find((p) => p.userId === userIdB);
|
||||
expect(profileA).toBeDefined();
|
||||
expect(profileB).toBeDefined();
|
||||
if (profileA) {
|
||||
expect(profileA.trackCount).toBeGreaterThan(0);
|
||||
}
|
||||
if (profileB) {
|
||||
expect(profileB.trackCount).toBeGreaterThan(0);
|
||||
}
|
||||
});
|
||||
|
||||
it("correctly identifies group summary", async () => {
|
||||
const { partyId, userIdA } = await seedPartyWithTwoSimilarUsers();
|
||||
|
||||
const result = await partyAnalysisWorkflow.analyzeParty(partyId);
|
||||
|
||||
expect(result.groupSummary.totalMembers).toBe(2);
|
||||
expect(result.groupSummary.mostAlignedPair).toBeDefined();
|
||||
if (result.groupSummary.mostAlignedPair) {
|
||||
expect(result.groupSummary.mostAlignedPair.userIdA).toBe(userIdA);
|
||||
}
|
||||
expect(result.groupSummary.mostSharedGenres).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("analyzeParty - three diverse users", () => {
|
||||
it("does not find shared tracks across all members", async () => {
|
||||
const { partyId } = await seedPartyWithThreeDiverseUsers();
|
||||
|
||||
const result = await partyAnalysisWorkflow.analyzeParty(partyId);
|
||||
|
||||
expect(result.storyClusters).toHaveLength(3);
|
||||
expect(result.pairwise).toHaveLength(3); // C(3,2) = 3 pairs
|
||||
|
||||
// No track should be shared by all 3 members
|
||||
const allMembersCluster = result.storyClusters.find(
|
||||
(c) => c.memberCount === 3,
|
||||
);
|
||||
expect(allMembersCluster).toBeUndefined();
|
||||
});
|
||||
|
||||
it("identifies pairwise comparisons for all member pairs", async () => {
|
||||
const { partyId } = await seedPartyWithThreeDiverseUsers();
|
||||
|
||||
const result = await partyAnalysisWorkflow.analyzeParty(partyId);
|
||||
|
||||
expect(result.pairwise).toHaveLength(3);
|
||||
result.pairwise.forEach((comparison) => {
|
||||
expect(comparison.sharedTracks).toBe(0);
|
||||
expect(comparison.similarity).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
it("correctly identifies genre diversity for each member", async () => {
|
||||
const { partyId } = await seedPartyWithThreeDiverseUsers();
|
||||
|
||||
const result = await partyAnalysisWorkflow.analyzeParty(partyId);
|
||||
|
||||
expect(result.memberProfiles).toHaveLength(3);
|
||||
expect(result.groupSummary.mostDiverseMember).toBeDefined();
|
||||
|
||||
if (result.groupSummary.mostDiverseMember) {
|
||||
expect(
|
||||
result.groupSummary.mostDiverseMember.genreEntropy,
|
||||
).toBeGreaterThan(0);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("analyzeParty - scoring sources", () => {
|
||||
it("includes playback history in scoring", async () => {
|
||||
const { partyId, userIdA, userIdB } =
|
||||
await seedPartyWithTwoSimilarUsers();
|
||||
|
||||
// Add old playback history (more than a week ago)
|
||||
const oldDate = new Date();
|
||||
oldDate.setDate(oldDate.getDate() - 8);
|
||||
const { id: trackC } = await createTrack(
|
||||
"Old Track C",
|
||||
(await createAlbum("Old Album C")).id,
|
||||
);
|
||||
await addTopTrack(userIdA, trackC, 3);
|
||||
await addTopTrack(userIdB, trackC, 4);
|
||||
await addPlaybackHistory(userIdA, trackC, oldDate);
|
||||
await addPlaybackHistory(userIdB, trackC, oldDate);
|
||||
|
||||
const result = await partyAnalysisWorkflow.analyzeParty(partyId);
|
||||
|
||||
const comparison = result.pairwise[0]!;
|
||||
expect(comparison.sharedTracks).toBeGreaterThan(1); // sharedTrack + trackC
|
||||
|
||||
// Both members should have track count reflecting playback data
|
||||
const profileA = result.memberProfiles.find((p) => p.userId === userIdA);
|
||||
if (profileA) {
|
||||
expect(profileA.trackCount).toBeGreaterThan(1);
|
||||
}
|
||||
});
|
||||
|
||||
it("includes saved tracks in scoring", async () => {
|
||||
const { partyId, userIdA } = await seedPartyWithTwoSimilarUsers();
|
||||
|
||||
const extraAlbum = await createAlbum("Extra Saved Album");
|
||||
const extraArtist = await createArtist("Extra Saved Artist");
|
||||
const extraTrack = await createTrack("Extra Saved Track", extraAlbum.id, [
|
||||
extraArtist.id,
|
||||
]);
|
||||
await addSavedTrack(userIdA, extraTrack.id);
|
||||
|
||||
const result = await partyAnalysisWorkflow.analyzeParty(partyId);
|
||||
|
||||
const profileA = result.memberProfiles.find((p) => p.userId === userIdA);
|
||||
expect(profileA).toBeDefined();
|
||||
if (profileA) {
|
||||
expect(profileA.trackCount).toBeGreaterThan(1);
|
||||
}
|
||||
});
|
||||
|
||||
it("includes followed artists in scoring", async () => {
|
||||
const { partyId, userIdA } = await seedPartyWithTwoSimilarUsers();
|
||||
|
||||
const followedArtist = await createArtist("Followed Artist");
|
||||
await addTopArtist(userIdA, followedArtist.id, 5);
|
||||
await addFollowedArtist(userIdA, followedArtist.id);
|
||||
|
||||
const result = await partyAnalysisWorkflow.analyzeParty(partyId);
|
||||
|
||||
const profileA = result.memberProfiles.find((p) => p.userId === userIdA);
|
||||
expect(profileA).toBeDefined();
|
||||
if (profileA) {
|
||||
expect(profileA.artistCount).toBeGreaterThan(1);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("analyzeParty - story clusters", () => {
|
||||
it("sorts clusters with all-member cluster first", async () => {
|
||||
const { partyId, sharedTrackId } = await seedPartyWithTwoSimilarUsers();
|
||||
|
||||
const result = await partyAnalysisWorkflow.analyzeParty(partyId);
|
||||
|
||||
// The cluster with both members should be first
|
||||
expect(result.storyClusters[0]?.memberCount).toBe(2);
|
||||
|
||||
// The shared track should be in the first (all-members) cluster
|
||||
const firstCluster = result.storyClusters[0]!;
|
||||
const trackInFirstCluster = firstCluster.tracks.find(
|
||||
(t) => t.id === sharedTrackId,
|
||||
);
|
||||
expect(trackInFirstCluster).toBeDefined();
|
||||
});
|
||||
|
||||
it("sorts tracks within cluster by total score descending", async () => {
|
||||
const { partyId, userIdA, userIdB, sharedTrackId } =
|
||||
await seedPartyWithTwoSimilarUsers();
|
||||
|
||||
// Add another shared track at a lower position (lower score)
|
||||
const extraAlbum = await createAlbum("Extra Album A");
|
||||
const extraArtist = await createArtist("Extra Artist A");
|
||||
const extraTrack = await createTrack("Extra Track A", extraAlbum.id, [
|
||||
extraArtist.id,
|
||||
]);
|
||||
await addTopTrack(userIdA, extraTrack.id, 50);
|
||||
await addTopTrack(userIdB, extraTrack.id, 50);
|
||||
|
||||
const result = await partyAnalysisWorkflow.analyzeParty(partyId);
|
||||
|
||||
const cluster = result.storyClusters[0]!;
|
||||
expect(cluster.tracks.length).toBeGreaterThan(1);
|
||||
|
||||
// Shared track (position 1) should have higher score than extra track (position 50)
|
||||
const sharedClusterTrack = cluster.tracks.find(
|
||||
(t) => t.id === sharedTrackId,
|
||||
);
|
||||
if (sharedClusterTrack) {
|
||||
expect(sharedClusterTrack.memberScores.length).toBe(2);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("analyzeParty - similarity calculation", () => {
|
||||
it("calculates Jaccard-like similarity using min/max scoring", async () => {
|
||||
const { partyId, userIdA, userIdB } =
|
||||
await seedPartyWithTwoSimilarUsers();
|
||||
|
||||
const result = await partyAnalysisWorkflow.analyzeParty(partyId);
|
||||
|
||||
const comparison = result.pairwise[0]!;
|
||||
expect(comparison.sharedTracks).toBeGreaterThanOrEqual(1);
|
||||
expect(comparison.sharedArtists).toBeGreaterThanOrEqual(1);
|
||||
expect(comparison.sharedGenres).toBeGreaterThanOrEqual(1);
|
||||
expect(comparison.similarity).toBeGreaterThanOrEqual(0);
|
||||
expect(comparison.similarity).toBeLessThanOrEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("analyzeParty - save analysis", () => {
|
||||
it("saves the analysis result to the party record", async () => {
|
||||
const { partyId } = await seedPartyWithTwoSimilarUsers();
|
||||
|
||||
await partyAnalysisWorkflow.analyzeParty(partyId);
|
||||
|
||||
const { db } = await import("../../db");
|
||||
const { party } = await import("../../db/schema");
|
||||
|
||||
const savedParty = await db.query.party.findFirst({
|
||||
where: { id: partyId },
|
||||
});
|
||||
|
||||
expect(savedParty).toBeDefined();
|
||||
expect(savedParty?.analysisData).toBeDefined();
|
||||
const analysisData = savedParty?.analysisData as
|
||||
| Record<string, unknown>
|
||||
| undefined;
|
||||
expect(analysisData?.storyClusters).toHaveLength(1);
|
||||
expect(analysisData?.pairwise).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -25,6 +25,11 @@
|
|||
// Some stricter flags (disabled by default)
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noPropertyAccessFromIndexSignature": false
|
||||
}
|
||||
"noPropertyAccessFromIndexSignature": false,
|
||||
|
||||
// Test support
|
||||
"types": ["vitest/globals"]
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
|
|
|||
14
api/vitest.config.ts
Normal file
14
api/vitest.config.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { defineConfig } from "vitest/config";
|
||||
|
||||
process.env.SPOTIFY_CLIENT_ID = process.env.SPOTIFY_CLIENT_ID || "test-client-id";
|
||||
process.env.SPOTIFY_CLIENT_SECRET = process.env.SPOTIFY_CLIENT_SECRET || "test-client-secret";
|
||||
|
||||
export default defineConfig(({ mode }) => ({
|
||||
test: {
|
||||
globals: true,
|
||||
environment: "node",
|
||||
include: ["src/**/*.test.ts"],
|
||||
testTimeout: 30000,
|
||||
setupFiles: ["./src/test/setup.ts"],
|
||||
},
|
||||
}));
|
||||
137
bun.lock
137
bun.lock
|
|
@ -20,9 +20,12 @@
|
|||
"@statsfm/statsfm.js": "github.com:statsfm/statsfm.js",
|
||||
"better-auth": "^1.6.5",
|
||||
"elysia": "catalog:",
|
||||
"vite": "^8.0.10",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
"tsx": "^4.19.2",
|
||||
"vitest": "^3.0.5",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5",
|
||||
|
|
@ -219,9 +222,9 @@
|
|||
|
||||
"@elysiajs/eden": ["@elysiajs/eden@1.4.9", "", { "peerDependencies": { "elysia": ">=1.4.19" } }, "sha512-3CKVD4ycVjB8nCNssfmhnUuq3SzSHkUES3v5PNCFr9LxIrx39/HVRAZ8z2sLxrFqzUs48dCBZaxoZzJ5UUVHDA=="],
|
||||
|
||||
"@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="],
|
||||
"@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="],
|
||||
|
||||
"@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="],
|
||||
"@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="],
|
||||
|
||||
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="],
|
||||
|
||||
|
|
@ -351,7 +354,7 @@
|
|||
|
||||
"@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="],
|
||||
|
||||
"@oxc-project/types": ["@oxc-project/types@0.124.0", "", {}, "sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg=="],
|
||||
"@oxc-project/types": ["@oxc-project/types@0.127.0", "", {}, "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ=="],
|
||||
|
||||
"@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="],
|
||||
|
||||
|
|
@ -387,35 +390,35 @@
|
|||
|
||||
"@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="],
|
||||
|
||||
"@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.15", "", { "os": "android", "cpu": "arm64" }, "sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA=="],
|
||||
"@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.17", "", { "os": "android", "cpu": "arm64" }, "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ=="],
|
||||
|
||||
"@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-rc.15", "", { "os": "darwin", "cpu": "arm64" }, "sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg=="],
|
||||
"@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-rc.17", "", { "os": "darwin", "cpu": "arm64" }, "sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw=="],
|
||||
|
||||
"@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-rc.15", "", { "os": "darwin", "cpu": "x64" }, "sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw=="],
|
||||
"@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-rc.17", "", { "os": "darwin", "cpu": "x64" }, "sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw=="],
|
||||
|
||||
"@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-rc.15", "", { "os": "freebsd", "cpu": "x64" }, "sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw=="],
|
||||
"@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-rc.17", "", { "os": "freebsd", "cpu": "x64" }, "sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw=="],
|
||||
|
||||
"@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.15", "", { "os": "linux", "cpu": "arm" }, "sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA=="],
|
||||
"@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17", "", { "os": "linux", "cpu": "arm" }, "sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ=="],
|
||||
|
||||
"@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-rc.15", "", { "os": "linux", "cpu": "arm64" }, "sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w=="],
|
||||
"@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17", "", { "os": "linux", "cpu": "arm64" }, "sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q=="],
|
||||
|
||||
"@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-rc.15", "", { "os": "linux", "cpu": "arm64" }, "sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ=="],
|
||||
"@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-rc.17", "", { "os": "linux", "cpu": "arm64" }, "sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg=="],
|
||||
|
||||
"@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.15", "", { "os": "linux", "cpu": "ppc64" }, "sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ=="],
|
||||
"@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17", "", { "os": "linux", "cpu": "ppc64" }, "sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA=="],
|
||||
|
||||
"@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.0.0-rc.15", "", { "os": "linux", "cpu": "s390x" }, "sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ=="],
|
||||
"@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17", "", { "os": "linux", "cpu": "s390x" }, "sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA=="],
|
||||
|
||||
"@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-rc.15", "", { "os": "linux", "cpu": "x64" }, "sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA=="],
|
||||
"@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-rc.17", "", { "os": "linux", "cpu": "x64" }, "sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA=="],
|
||||
|
||||
"@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-rc.15", "", { "os": "linux", "cpu": "x64" }, "sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw=="],
|
||||
"@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-rc.17", "", { "os": "linux", "cpu": "x64" }, "sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw=="],
|
||||
|
||||
"@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-rc.15", "", { "os": "none", "cpu": "arm64" }, "sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg=="],
|
||||
"@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-rc.17", "", { "os": "none", "cpu": "arm64" }, "sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA=="],
|
||||
|
||||
"@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-rc.15", "", { "dependencies": { "@emnapi/core": "1.9.2", "@emnapi/runtime": "1.9.2", "@napi-rs/wasm-runtime": "^1.1.3" }, "cpu": "none" }, "sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q=="],
|
||||
"@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-rc.17", "", { "dependencies": { "@emnapi/core": "1.10.0", "@emnapi/runtime": "1.10.0", "@napi-rs/wasm-runtime": "^1.1.4" }, "cpu": "none" }, "sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA=="],
|
||||
|
||||
"@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-rc.15", "", { "os": "win32", "cpu": "arm64" }, "sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA=="],
|
||||
"@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17", "", { "os": "win32", "cpu": "arm64" }, "sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA=="],
|
||||
|
||||
"@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-rc.15", "", { "os": "win32", "cpu": "x64" }, "sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g=="],
|
||||
"@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-rc.17", "", { "os": "win32", "cpu": "x64" }, "sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg=="],
|
||||
|
||||
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.7", "", {}, "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA=="],
|
||||
|
||||
|
|
@ -609,7 +612,7 @@
|
|||
|
||||
"@types/aria-query": ["@types/aria-query@5.0.4", "", {}, "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="],
|
||||
|
||||
"@types/bun": ["@types/bun@1.3.12", "", { "dependencies": { "bun-types": "1.3.12" } }, "sha512-DBv81elK+/VSwXHDlnH3Qduw+KxkTIWi7TXkAeh24zpi5l0B2kUg9Ga3tb4nJaPcOFswflgi/yAvMVBPrxMB+A=="],
|
||||
"@types/bun": ["@types/bun@1.3.13", "", { "dependencies": { "bun-types": "1.3.13" } }, "sha512-9fqXWk5YIHGGnUau9TEi+qdlTYDAnOj+xLCmSTwXfAIqXr2x4tytJb43E9uCvt09zJURKXwAtkoH4nLQfzeTXw=="],
|
||||
|
||||
"@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="],
|
||||
|
||||
|
|
@ -1355,7 +1358,7 @@
|
|||
|
||||
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
||||
|
||||
"rolldown": ["rolldown@1.0.0-rc.15", "", { "dependencies": { "@oxc-project/types": "=0.124.0", "@rolldown/pluginutils": "1.0.0-rc.15" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.15", "@rolldown/binding-darwin-arm64": "1.0.0-rc.15", "@rolldown/binding-darwin-x64": "1.0.0-rc.15", "@rolldown/binding-freebsd-x64": "1.0.0-rc.15", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.15", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.15", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.15", "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.15", "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.15", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.15", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.15", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.15", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.15", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.15", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.15" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g=="],
|
||||
"rolldown": ["rolldown@1.0.0-rc.17", "", { "dependencies": { "@oxc-project/types": "=0.127.0", "@rolldown/pluginutils": "1.0.0-rc.17" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.17", "@rolldown/binding-darwin-arm64": "1.0.0-rc.17", "@rolldown/binding-darwin-x64": "1.0.0-rc.17", "@rolldown/binding-freebsd-x64": "1.0.0-rc.17", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.17", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.17", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.17", "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.17", "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.17", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.17", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.17", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.17", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.17", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.17", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.17" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA=="],
|
||||
|
||||
"rollup": ["rollup@4.60.2", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.2", "@rollup/rollup-android-arm64": "4.60.2", "@rollup/rollup-darwin-arm64": "4.60.2", "@rollup/rollup-darwin-x64": "4.60.2", "@rollup/rollup-freebsd-arm64": "4.60.2", "@rollup/rollup-freebsd-x64": "4.60.2", "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", "@rollup/rollup-linux-arm-musleabihf": "4.60.2", "@rollup/rollup-linux-arm64-gnu": "4.60.2", "@rollup/rollup-linux-arm64-musl": "4.60.2", "@rollup/rollup-linux-loong64-gnu": "4.60.2", "@rollup/rollup-linux-loong64-musl": "4.60.2", "@rollup/rollup-linux-ppc64-gnu": "4.60.2", "@rollup/rollup-linux-ppc64-musl": "4.60.2", "@rollup/rollup-linux-riscv64-gnu": "4.60.2", "@rollup/rollup-linux-riscv64-musl": "4.60.2", "@rollup/rollup-linux-s390x-gnu": "4.60.2", "@rollup/rollup-linux-x64-gnu": "4.60.2", "@rollup/rollup-linux-x64-musl": "4.60.2", "@rollup/rollup-openbsd-x64": "4.60.2", "@rollup/rollup-openharmony-arm64": "4.60.2", "@rollup/rollup-win32-arm64-msvc": "4.60.2", "@rollup/rollup-win32-ia32-msvc": "4.60.2", "@rollup/rollup-win32-x64-gnu": "4.60.2", "@rollup/rollup-win32-x64-msvc": "4.60.2", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ=="],
|
||||
|
||||
|
|
@ -1543,7 +1546,7 @@
|
|||
|
||||
"vaul": ["vaul@1.1.2", "", { "dependencies": { "@radix-ui/react-dialog": "^1.1.1" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA=="],
|
||||
|
||||
"vite": ["vite@8.0.8", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.8", "rolldown": "1.0.0-rc.15", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw=="],
|
||||
"vite": ["vite@8.0.10", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.10", "rolldown": "1.0.0-rc.17", "tinyglobby": "^0.2.16" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw=="],
|
||||
|
||||
"vite-node": ["vite-node@3.2.4", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.1", "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg=="],
|
||||
|
||||
|
|
@ -1643,10 +1646,14 @@
|
|||
|
||||
"@tanstack/start-plugin-core/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||
|
||||
"@types/bun/bun-types": ["bun-types@1.3.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-QXKeHLlOLqQX9LgYaHJfzdBaV21T63HhFJnvuRCcjZiaUDpbs5ED1MgxbMra71CsryN/1dAoXuJJJwIv/2drVA=="],
|
||||
|
||||
"@types/pg/@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="],
|
||||
|
||||
"@types/set-cookie-parser/@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="],
|
||||
|
||||
"@vitest/mocker/vite": ["vite@8.0.8", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.8", "rolldown": "1.0.0-rc.15", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw=="],
|
||||
|
||||
"anymatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
|
||||
|
||||
"bun-types/@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="],
|
||||
|
|
@ -1699,7 +1706,7 @@
|
|||
|
||||
"restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="],
|
||||
|
||||
"rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.15", "", {}, "sha512-UromN0peaE53IaBRe9W7CjrZgXl90fqGpK+mIZbA3qSTeYqg3pqpROBdIPvOG3F5ereDHNwoHBI2e50n1BDr1g=="],
|
||||
"rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.17", "", {}, "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg=="],
|
||||
|
||||
"router/path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="],
|
||||
|
||||
|
|
@ -1721,6 +1728,8 @@
|
|||
|
||||
"vitest/vite": ["vite@7.3.2", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg=="],
|
||||
|
||||
"web/vite": ["vite@8.0.8", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.8", "rolldown": "1.0.0-rc.15", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw=="],
|
||||
|
||||
"whatwg-encoding/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
|
||||
|
||||
"wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
|
|
@ -1739,10 +1748,14 @@
|
|||
|
||||
"@dotenvx/dotenvx/execa/strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="],
|
||||
|
||||
"@types/bun/bun-types/@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="],
|
||||
|
||||
"@types/pg/@types/node/undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
|
||||
|
||||
"@types/set-cookie-parser/@types/node/undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
|
||||
|
||||
"@vitest/mocker/vite/rolldown": ["rolldown@1.0.0-rc.15", "", { "dependencies": { "@oxc-project/types": "=0.124.0", "@rolldown/pluginutils": "1.0.0-rc.15" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.15", "@rolldown/binding-darwin-arm64": "1.0.0-rc.15", "@rolldown/binding-darwin-x64": "1.0.0-rc.15", "@rolldown/binding-freebsd-x64": "1.0.0-rc.15", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.15", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.15", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.15", "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.15", "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.15", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.15", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.15", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.15", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.15", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.15", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.15" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g=="],
|
||||
|
||||
"bun-types/@types/node/undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
|
||||
|
||||
"cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||
|
|
@ -1813,6 +1826,44 @@
|
|||
|
||||
"vitest/vite/esbuild": ["esbuild@0.27.7", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.7", "@esbuild/android-arm": "0.27.7", "@esbuild/android-arm64": "0.27.7", "@esbuild/android-x64": "0.27.7", "@esbuild/darwin-arm64": "0.27.7", "@esbuild/darwin-x64": "0.27.7", "@esbuild/freebsd-arm64": "0.27.7", "@esbuild/freebsd-x64": "0.27.7", "@esbuild/linux-arm": "0.27.7", "@esbuild/linux-arm64": "0.27.7", "@esbuild/linux-ia32": "0.27.7", "@esbuild/linux-loong64": "0.27.7", "@esbuild/linux-mips64el": "0.27.7", "@esbuild/linux-ppc64": "0.27.7", "@esbuild/linux-riscv64": "0.27.7", "@esbuild/linux-s390x": "0.27.7", "@esbuild/linux-x64": "0.27.7", "@esbuild/netbsd-arm64": "0.27.7", "@esbuild/netbsd-x64": "0.27.7", "@esbuild/openbsd-arm64": "0.27.7", "@esbuild/openbsd-x64": "0.27.7", "@esbuild/openharmony-arm64": "0.27.7", "@esbuild/sunos-x64": "0.27.7", "@esbuild/win32-arm64": "0.27.7", "@esbuild/win32-ia32": "0.27.7", "@esbuild/win32-x64": "0.27.7" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w=="],
|
||||
|
||||
"web/vite/rolldown": ["rolldown@1.0.0-rc.15", "", { "dependencies": { "@oxc-project/types": "=0.124.0", "@rolldown/pluginutils": "1.0.0-rc.15" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.15", "@rolldown/binding-darwin-arm64": "1.0.0-rc.15", "@rolldown/binding-darwin-x64": "1.0.0-rc.15", "@rolldown/binding-freebsd-x64": "1.0.0-rc.15", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.15", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.15", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.15", "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.15", "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.15", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.15", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.15", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.15", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.15", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.15", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.15" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g=="],
|
||||
|
||||
"@types/bun/bun-types/@types/node/undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
|
||||
|
||||
"@vitest/mocker/vite/rolldown/@oxc-project/types": ["@oxc-project/types@0.124.0", "", {}, "sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg=="],
|
||||
|
||||
"@vitest/mocker/vite/rolldown/@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.15", "", { "os": "android", "cpu": "arm64" }, "sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA=="],
|
||||
|
||||
"@vitest/mocker/vite/rolldown/@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-rc.15", "", { "os": "darwin", "cpu": "arm64" }, "sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg=="],
|
||||
|
||||
"@vitest/mocker/vite/rolldown/@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-rc.15", "", { "os": "darwin", "cpu": "x64" }, "sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw=="],
|
||||
|
||||
"@vitest/mocker/vite/rolldown/@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-rc.15", "", { "os": "freebsd", "cpu": "x64" }, "sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw=="],
|
||||
|
||||
"@vitest/mocker/vite/rolldown/@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.15", "", { "os": "linux", "cpu": "arm" }, "sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA=="],
|
||||
|
||||
"@vitest/mocker/vite/rolldown/@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-rc.15", "", { "os": "linux", "cpu": "arm64" }, "sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w=="],
|
||||
|
||||
"@vitest/mocker/vite/rolldown/@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-rc.15", "", { "os": "linux", "cpu": "arm64" }, "sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ=="],
|
||||
|
||||
"@vitest/mocker/vite/rolldown/@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.15", "", { "os": "linux", "cpu": "ppc64" }, "sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ=="],
|
||||
|
||||
"@vitest/mocker/vite/rolldown/@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.0.0-rc.15", "", { "os": "linux", "cpu": "s390x" }, "sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ=="],
|
||||
|
||||
"@vitest/mocker/vite/rolldown/@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-rc.15", "", { "os": "linux", "cpu": "x64" }, "sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA=="],
|
||||
|
||||
"@vitest/mocker/vite/rolldown/@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-rc.15", "", { "os": "linux", "cpu": "x64" }, "sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw=="],
|
||||
|
||||
"@vitest/mocker/vite/rolldown/@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-rc.15", "", { "os": "none", "cpu": "arm64" }, "sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg=="],
|
||||
|
||||
"@vitest/mocker/vite/rolldown/@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-rc.15", "", { "dependencies": { "@emnapi/core": "1.9.2", "@emnapi/runtime": "1.9.2", "@napi-rs/wasm-runtime": "^1.1.3" }, "cpu": "none" }, "sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q=="],
|
||||
|
||||
"@vitest/mocker/vite/rolldown/@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-rc.15", "", { "os": "win32", "cpu": "arm64" }, "sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA=="],
|
||||
|
||||
"@vitest/mocker/vite/rolldown/@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-rc.15", "", { "os": "win32", "cpu": "x64" }, "sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g=="],
|
||||
|
||||
"@vitest/mocker/vite/rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.15", "", {}, "sha512-UromN0peaE53IaBRe9W7CjrZgXl90fqGpK+mIZbA3qSTeYqg3pqpROBdIPvOG3F5ereDHNwoHBI2e50n1BDr1g=="],
|
||||
|
||||
"isomorphic-unfetch/node-fetch/whatwg-url/tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
|
||||
|
||||
"isomorphic-unfetch/node-fetch/whatwg-url/webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
|
||||
|
|
@ -1925,6 +1976,48 @@
|
|||
|
||||
"vitest/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.7", "", { "os": "win32", "cpu": "x64" }, "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg=="],
|
||||
|
||||
"web/vite/rolldown/@oxc-project/types": ["@oxc-project/types@0.124.0", "", {}, "sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg=="],
|
||||
|
||||
"web/vite/rolldown/@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.15", "", { "os": "android", "cpu": "arm64" }, "sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA=="],
|
||||
|
||||
"web/vite/rolldown/@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-rc.15", "", { "os": "darwin", "cpu": "arm64" }, "sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg=="],
|
||||
|
||||
"web/vite/rolldown/@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-rc.15", "", { "os": "darwin", "cpu": "x64" }, "sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw=="],
|
||||
|
||||
"web/vite/rolldown/@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-rc.15", "", { "os": "freebsd", "cpu": "x64" }, "sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw=="],
|
||||
|
||||
"web/vite/rolldown/@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.15", "", { "os": "linux", "cpu": "arm" }, "sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA=="],
|
||||
|
||||
"web/vite/rolldown/@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-rc.15", "", { "os": "linux", "cpu": "arm64" }, "sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w=="],
|
||||
|
||||
"web/vite/rolldown/@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-rc.15", "", { "os": "linux", "cpu": "arm64" }, "sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ=="],
|
||||
|
||||
"web/vite/rolldown/@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.15", "", { "os": "linux", "cpu": "ppc64" }, "sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ=="],
|
||||
|
||||
"web/vite/rolldown/@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.0.0-rc.15", "", { "os": "linux", "cpu": "s390x" }, "sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ=="],
|
||||
|
||||
"web/vite/rolldown/@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-rc.15", "", { "os": "linux", "cpu": "x64" }, "sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA=="],
|
||||
|
||||
"web/vite/rolldown/@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-rc.15", "", { "os": "linux", "cpu": "x64" }, "sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw=="],
|
||||
|
||||
"web/vite/rolldown/@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-rc.15", "", { "os": "none", "cpu": "arm64" }, "sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg=="],
|
||||
|
||||
"web/vite/rolldown/@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-rc.15", "", { "dependencies": { "@emnapi/core": "1.9.2", "@emnapi/runtime": "1.9.2", "@napi-rs/wasm-runtime": "^1.1.3" }, "cpu": "none" }, "sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q=="],
|
||||
|
||||
"web/vite/rolldown/@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-rc.15", "", { "os": "win32", "cpu": "arm64" }, "sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA=="],
|
||||
|
||||
"web/vite/rolldown/@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-rc.15", "", { "os": "win32", "cpu": "x64" }, "sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g=="],
|
||||
|
||||
"web/vite/rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.15", "", {}, "sha512-UromN0peaE53IaBRe9W7CjrZgXl90fqGpK+mIZbA3qSTeYqg3pqpROBdIPvOG3F5ereDHNwoHBI2e50n1BDr1g=="],
|
||||
|
||||
"@vitest/mocker/vite/rolldown/@rolldown/binding-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="],
|
||||
|
||||
"@vitest/mocker/vite/rolldown/@rolldown/binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="],
|
||||
|
||||
"msw/yargs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
|
||||
"web/vite/rolldown/@rolldown/binding-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="],
|
||||
|
||||
"web/vite/rolldown/@rolldown/binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="],
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue