2 Commits
0.1.5 ... 0.1.7

Author SHA1 Message Date
Morten Olsen
e3398e1968 fix: use view recreate 2025-11-04 13:17:44 +01:00
Morten Olsen
5cb31324ee feat: support views as data catalogs 2025-11-04 11:51:51 +01:00
8 changed files with 364 additions and 1 deletions

View File

@@ -2,6 +2,7 @@
"type": "module",
"main": "dist/exports.js",
"scripts": {
"dev": "node --no-warnings --watch src/dev.ts",
"build": "tsc --build",
"test:unit": "vitest --run --passWithNoTests",
"test": "pnpm run \"/^test:/\""
@@ -18,6 +19,7 @@
"@morten-olsen/reservoir-tests": "workspace:*",
"@types/node": "24.10.0",
"@vitest/coverage-v8": "4.0.6",
"dotenv": "^17.2.3",
"typescript": "5.9.3",
"vitest": "4.0.6"
},
@@ -27,6 +29,7 @@
"#root/*": "./src/*"
},
"dependencies": {
"@fastify/sensible": "^6.0.3",
"@fastify/swagger": "^9.5.2",
"@scalar/fastify-api-reference": "^1.38.1",
"better-sqlite3": "^12.4.1",
@@ -38,6 +41,7 @@
"pg": "^8.16.3",
"pino": "^10.1.0",
"pino-pretty": "^13.1.2",
"yaml": "^2.8.1",
"zod": "^4.1.12"
}
}

View File

@@ -11,7 +11,7 @@ const documentsPlugin: FastifyPluginAsyncZod = async (app) => {
method: 'POST',
url: '',
schema: {
operationId: 'v1.documents.post',
operationId: 'v1.documents.upsert',
tags: ['documents'],
summary: 'Upsert documents',
body: upsertDocumentRequestSchema,

View File

@@ -1,9 +1,12 @@
import fastify from 'fastify';
import fastifySensible from '@fastify/sensible';
import fastifySwagger from '@fastify/swagger';
import fastifyScalar from '@scalar/fastify-api-reference';
import YAML from 'yaml';
import { jsonSchemaTransform, serializerCompiler, validatorCompiler } from 'fastify-type-provider-zod';
import { documentsPlugin } from './api.documents.ts';
import { viewsPlugin } from './api.views.ts';
import { Services } from '#root/utils/utils.services.ts';
import { DatabaseService } from '#root/database/database.ts';
@@ -22,6 +25,16 @@ const createApi = async (services: Services = new Services()) => {
app.setValidatorCompiler(validatorCompiler);
app.setSerializerCompiler(serializerCompiler);
app.decorate('services', services);
app.addContentTypeParser('application/yaml', { parseAs: 'buffer' }, (_, body, done) => {
try {
const parsed = YAML.parse(body.toString('utf8')); // Parse the buffer as YAML
done(null, parsed); // Call done with null for error and the parsed object
} catch {
done(app.httpErrors.badRequest('Invalid YAML format'));
}
});
await app.register(fastifySensible);
await app.register(fastifySwagger, {
openapi: {
@@ -42,6 +55,10 @@ const createApi = async (services: Services = new Services()) => {
prefix: '/api/v1/documents',
});
await app.register(viewsPlugin, {
prefix: '/api/v1/views',
});
app.addHook('onReady', async () => {
app.swagger();
});

View File

@@ -0,0 +1,86 @@
import type { FastifyPluginAsyncZod } from 'fastify-type-provider-zod';
import { z } from 'zod';
import { viewInfoSchema, viewUpsertRequestSchema } from '#root/services/views/views.schemas.ts';
import { ViewsService } from '#root/services/views/views.ts';
const viewsPlugin: FastifyPluginAsyncZod = async (app) => {
app.route({
method: 'GET',
url: '',
schema: {
operationId: 'v1.views.list',
tags: ['views'],
summary: 'List views',
response: {
200: z.object({
items: z.array(viewInfoSchema),
}),
},
},
handler: async (req, reply) => {
const viewsService = app.services.get(ViewsService);
const items = await viewsService.list();
return reply.send({ items });
},
});
app.route({
method: 'PUT',
url: '/:name',
schema: {
operationId: 'v1.views.list',
tags: ['views'],
summary: 'Upsert view',
params: z.object({
name: z.string(),
}),
body: viewUpsertRequestSchema.omit({
name: true,
}),
response: {
200: z.object({
name: z.string(),
}),
},
},
handler: async (req, reply) => {
const input = {
...req.body,
name: req.params.name,
};
const viewsService = app.services.get(ViewsService);
await viewsService.upsert(input);
reply.send({
name: req.params.name,
});
},
});
app.route({
method: 'DELETE',
url: '/:name',
schema: {
operationId: 'v1.views.delete',
tags: ['views'],
summary: 'Delete view',
params: z.object({
name: z.string(),
}),
response: {
200: z.object({
name: z.string(),
}),
},
},
handler: async (req, reply) => {
const viewsService = app.services.get(ViewsService);
await viewsService.remove(req.params.name);
reply.send({
name: req.params.name,
});
},
});
};
export { viewsPlugin };

View File

@@ -0,0 +1,8 @@
import 'dotenv/config';
import { createApi } from './api/api.ts';
const app = await createApi();
await app.listen({
port: 9111,
host: process.env.SERVER_HOST,
});

View File

@@ -0,0 +1,35 @@
import { z } from 'zod';
const columnInfoSchema = z.object({
name: z.string(),
dataType: z.string(),
isNullable: z.boolean(),
});
type ColumnInfo = z.infer<typeof columnInfoSchema>;
const viewInfoSchema = z.object({
name: z.string(),
columns: z.array(columnInfoSchema),
});
type ViewInfo = z.infer<typeof viewInfoSchema>;
const viewUpsertRequestSchema = z.object({
name: z.string(),
description: z.string().nullable(),
columns: z
.record(
z.string(),
z.object({
description: z.string().nullish(),
}),
)
.optional(),
query: z.string(),
});
type ViewUpsertRequest = z.infer<typeof viewUpsertRequestSchema>;
export type { ColumnInfo, ViewInfo, ViewUpsertRequest };
export { columnInfoSchema, viewInfoSchema, viewUpsertRequestSchema };

View File

@@ -0,0 +1,103 @@
import type { ViewInfo, ViewUpsertRequest } from './views.schemas.ts';
import { DatabaseService } from '#root/database/database.ts';
import type { Services } from '#root/utils/utils.services.ts';
class ViewsService {
#services: Services;
constructor(services: Services) {
this.#services = services;
}
#listFromPostgres = async () => {
const dbService = this.#services.get(DatabaseService);
const db = await dbService.getInstance();
const pgViewsResult = await db.raw<{ rows: { schema_name: string; view_name: string }[] }>(`
SELECT
schemaname AS schema_name,
viewname AS view_name
FROM
pg_views
WHERE
schemaname NOT IN ('pg_catalog', 'information_schema', 'information_schema')
AND viewname NOT LIKE 'pg_%'
AND viewname NOT LIKE 'sql_%';
`);
const views: ViewInfo[] = await Promise.all(
pgViewsResult.rows.map(async (row) => {
const pgColumnsResult = await db.raw<{
rows: { column_name: string; data_type: string; is_nullable: 'YES' | 'NO' }[];
}>(
`
SELECT
column_name,
data_type,
is_nullable
FROM
information_schema.columns
WHERE
table_schema = ?
AND table_name = ?
ORDER BY
ordinal_position;
`,
[row.schema_name, row.view_name],
);
const result: ViewInfo = {
name: row.view_name,
columns: pgColumnsResult.rows.map((column) => ({
name: column.column_name,
dataType: column.data_type,
isNullable: column.is_nullable === 'YES',
})),
};
return result;
}),
);
return views;
};
public list = async () => {
const dbService = this.#services.get(DatabaseService);
const db = await dbService.getInstance();
switch (db.client.config.client) {
case 'pg':
return this.#listFromPostgres();
default:
throw new Error(`Client ${db.client} is not supported`);
}
};
public upsert = async (options: ViewUpsertRequest) => {
const { name, columns = {}, query, description } = options;
const dbService = this.#services.get(DatabaseService);
const db = await dbService.getInstance();
const subquery = db.raw(query);
await db.transaction(async (trx) => {
await trx.schema.dropViewIfExists(name);
await trx.schema.createViewOrReplace(name, (view) => {
// view.columns(columns);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
view.as(subquery as any);
});
if (description) {
const sql = trx.raw(`COMMENT ON VIEW ?? IS ?;`, [name, description]);
await trx.raw(sql.toQuery());
}
for (const [columnName, info] of Object.entries(columns)) {
const sql = trx.raw(`COMMENT ON COLUMN ??.?? IS ?;`, [name, columnName, info.description || null]);
await trx.raw(sql.toQuery());
}
});
};
public remove = async (name: string) => {
const dbService = this.#services.get(DatabaseService);
const db = await dbService.getInstance();
await db.schema.dropViewIfExists(name);
};
}
export { ViewsService };

110
pnpm-lock.yaml generated
View File

@@ -52,6 +52,9 @@ importers:
packages/server:
dependencies:
'@fastify/sensible':
specifier: ^6.0.3
version: 6.0.3
'@fastify/swagger':
specifier: ^9.5.2
version: 9.5.2
@@ -85,6 +88,9 @@ importers:
pino-pretty:
specifier: ^13.1.2
version: 13.1.2
yaml:
specifier: ^2.8.1
version: 2.8.1
zod:
specifier: ^4.1.12
version: 4.1.12
@@ -101,6 +107,9 @@ importers:
'@vitest/coverage-v8':
specifier: 4.0.6
version: 4.0.6(vitest@4.0.6(@types/node@24.10.0)(yaml@2.8.1))
dotenv:
specifier: ^17.2.3
version: 17.2.3
typescript:
specifier: 5.9.3
version: 5.9.3
@@ -369,6 +378,9 @@ packages:
'@fastify/proxy-addr@5.1.0':
resolution: {integrity: sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw==}
'@fastify/sensible@6.0.3':
resolution: {integrity: sha512-Iyn8698hp/e5+v8SNBBruTa7UfrMEP52R16dc9jMpqSyEcPsvWFQo+R6WwHCUnJiLIsuci2ZoEZ7ilrSSCPIVg==}
'@fastify/swagger@9.5.2':
resolution: {integrity: sha512-8e8w/LItg/cF6IR/hYKtnt+E0QImees5o3YWJsTLxaIk+tzNUEc6Z2Ursi4oOHWwUlKjUCnV6yh5z5ZdxvlsWA==}
@@ -402,6 +414,10 @@ packages:
'@jridgewell/trace-mapping@0.3.31':
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
'@lukeed/ms@2.0.2':
resolution: {integrity: sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==}
engines: {node: '>=8'}
'@nodelib/fs.scandir@2.1.5':
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
@@ -1141,6 +1157,10 @@ packages:
resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
engines: {node: '>= 0.4'}
depd@2.0.0:
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
engines: {node: '>= 0.8'}
dequal@2.0.3:
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
engines: {node: '>=6'}
@@ -1153,6 +1173,10 @@ packages:
resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
engines: {node: '>=0.10.0'}
dotenv@17.2.3:
resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==}
engines: {node: '>=12'}
dunder-proto@1.0.1:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'}
@@ -1421,6 +1445,10 @@ packages:
resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
engines: {node: '>= 0.4'}
forwarded@0.2.0:
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
engines: {node: '>= 0.6'}
fs-constants@1.0.0:
resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
@@ -1537,6 +1565,10 @@ packages:
html-escaper@2.0.2:
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
http-errors@2.0.0:
resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
engines: {node: '>= 0.8'}
human-signals@2.1.0:
resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
engines: {node: '>=10.17.0'}
@@ -1863,6 +1895,10 @@ packages:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'}
media-typer@0.3.0:
resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
engines: {node: '>= 0.6'}
mem@8.1.1:
resolution: {integrity: sha512-qFCFUDs7U3b8mBDPyz5EToEKoAkgCzqquIgi9nkkR9bixxOVOre+09lbuH7+9Kn2NFpm56M3GUWVbU2hQgdACA==}
engines: {node: '>=10'}
@@ -1878,6 +1914,14 @@ packages:
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
engines: {node: '>=8.6'}
mime-db@1.52.0:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'}
mime-types@2.1.35:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
mimic-fn@2.1.0:
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
engines: {node: '>=6'}
@@ -2334,6 +2378,9 @@ packages:
resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==}
engines: {node: '>= 0.4'}
setprototypeof@1.2.0:
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
shebang-command@2.0.0:
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
engines: {node: '>=8'}
@@ -2402,6 +2449,10 @@ packages:
stacktracey@2.1.8:
resolution: {integrity: sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==}
statuses@2.0.1:
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
engines: {node: '>= 0.8'}
std-env@3.10.0:
resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
@@ -2522,6 +2573,10 @@ packages:
resolution: {integrity: sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==}
engines: {node: '>=12'}
toidentifier@1.0.1:
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
engines: {node: '>=0.6'}
ts-api-utils@2.1.0:
resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==}
engines: {node: '>=18.12'}
@@ -2587,6 +2642,10 @@ packages:
resolution: {integrity: sha512-GeJop7+u7BYlQ6yQCAY1nBQiRSHR+6OdCEtd8Bwp9a3NK3+fWAVjOaPKJDteB9f6cIJ0wt4IfnScjLG450EpXA==}
engines: {node: '>=20'}
type-is@1.6.18:
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
engines: {node: '>= 0.6'}
typed-array-buffer@1.0.3:
resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
engines: {node: '>= 0.4'}
@@ -2632,6 +2691,10 @@ packages:
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
vary@1.1.2:
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
engines: {node: '>= 0.8'}
vite@7.1.12:
resolution: {integrity: sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -2957,6 +3020,16 @@ snapshots:
'@fastify/forwarded': 3.0.1
ipaddr.js: 2.2.0
'@fastify/sensible@6.0.3':
dependencies:
'@lukeed/ms': 2.0.2
dequal: 2.0.3
fastify-plugin: 5.1.0
forwarded: 0.2.0
http-errors: 2.0.0
type-is: 1.6.18
vary: 1.1.2
'@fastify/swagger@9.5.2':
dependencies:
fastify-plugin: 5.1.0
@@ -2989,6 +3062,8 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5
'@lukeed/ms@2.0.2': {}
'@nodelib/fs.scandir@2.1.5':
dependencies:
'@nodelib/fs.stat': 2.0.5
@@ -3854,6 +3929,8 @@ snapshots:
has-property-descriptors: 1.0.2
object-keys: 1.1.1
depd@2.0.0: {}
dequal@2.0.3: {}
detect-libc@2.1.2: {}
@@ -3862,6 +3939,8 @@ snapshots:
dependencies:
esutils: 2.0.3
dotenv@17.2.3: {}
dunder-proto@1.0.1:
dependencies:
call-bind-apply-helpers: 1.0.2
@@ -4252,6 +4331,8 @@ snapshots:
dependencies:
is-callable: 1.2.7
forwarded@0.2.0: {}
fs-constants@1.0.0: {}
fsevents@2.3.3:
@@ -4360,6 +4441,14 @@ snapshots:
html-escaper@2.0.2: {}
http-errors@2.0.0:
dependencies:
depd: 2.0.0
inherits: 2.0.4
setprototypeof: 1.2.0
statuses: 2.0.1
toidentifier: 1.0.1
human-signals@2.1.0: {}
ieee754@1.2.1: {}
@@ -4660,6 +4749,8 @@ snapshots:
math-intrinsics@1.1.0: {}
media-typer@0.3.0: {}
mem@8.1.1:
dependencies:
map-age-cleaner: 0.1.3
@@ -4674,6 +4765,12 @@ snapshots:
braces: 3.0.3
picomatch: 2.3.1
mime-db@1.52.0: {}
mime-types@2.1.35:
dependencies:
mime-db: 1.52.0
mimic-fn@2.1.0: {}
mimic-fn@3.1.0: {}
@@ -5167,6 +5264,8 @@ snapshots:
es-errors: 1.3.0
es-object-atoms: 1.1.1
setprototypeof@1.2.0: {}
shebang-command@2.0.0:
dependencies:
shebang-regex: 3.0.0
@@ -5240,6 +5339,8 @@ snapshots:
as-table: 1.0.55
get-source: 2.0.12
statuses@2.0.1: {}
std-env@3.10.0: {}
stop-iteration-iterator@1.1.0:
@@ -5359,6 +5460,8 @@ snapshots:
toad-cache@3.7.0: {}
toidentifier@1.0.1: {}
ts-api-utils@2.1.0(typescript@5.9.3):
dependencies:
typescript: 5.9.3
@@ -5415,6 +5518,11 @@ snapshots:
dependencies:
tagged-tag: 1.0.0
type-is@1.6.18:
dependencies:
media-typer: 0.3.0
mime-types: 2.1.35
typed-array-buffer@1.0.3:
dependencies:
call-bound: 1.0.4
@@ -5480,6 +5588,8 @@ snapshots:
util-deprecate@1.0.2: {}
vary@1.1.2: {}
vite@7.1.12(@types/node@24.10.0)(yaml@2.8.1):
dependencies:
esbuild: 0.25.12