Skip to content

Commit 48f3b4f

Browse files
committed
chore(snippet-manager): migrate from joi to zod MONGOSH-2010
This aligns us with the validators we've standardized on in Compass, improves TypeScript integration, and reduces executable size by 564 kB and startup time by 1.5% (locally on an M3).
1 parent 6d7e2e8 commit 48f3b4f

File tree

3 files changed

+45
-93
lines changed

3 files changed

+45
-93
lines changed

package-lock.json

Lines changed: 11 additions & 39 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/snippet-manager/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
"bson": "^6.10.1",
4343
"cross-spawn": "^7.0.5",
4444
"escape-string-regexp": "^4.0.0",
45-
"joi": "^17.4.0",
45+
"zod": "^3.24.1",
4646
"tar": "^6.1.15"
4747
},
4848
"devDependencies": {

packages/snippet-manager/src/snippet-manager.ts

Lines changed: 33 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ import type { SnippetShellUserConfig, MongoshBus } from '@mongosh/types';
99
import escapeRegexp from 'escape-string-regexp';
1010
import path from 'path';
1111
import { promisify, isDeepStrictEqual } from 'util';
12-
import { Console } from 'console';
12+
import { Console, error } from 'console';
1313
import { promises as fs } from 'fs';
1414
import stream, { PassThrough } from 'stream';
1515
import { once } from 'events';
1616
import tar from 'tar';
1717
import zlib from 'zlib';
1818
import bson from 'bson';
19-
import joi from 'joi';
19+
import { z } from 'zod';
2020
import type {
2121
AgentWithInitialize,
2222
DevtoolsProxyOptions,
@@ -34,64 +34,45 @@ export interface SnippetOptions {
3434
proxyOptions?: DevtoolsProxyOptions | AgentWithInitialize;
3535
}
3636

37-
export interface ErrorMatcher {
38-
matches: RegExp[];
39-
message: string;
40-
}
41-
42-
export interface SnippetDescription {
43-
name: string;
44-
snippetName: string;
45-
installSpec?: string;
46-
version: string;
47-
description: string;
48-
license: string;
49-
readme: string;
50-
errorMatchers?: ErrorMatcher[];
51-
}
52-
53-
export interface SnippetIndexFile {
54-
indexFileVersion: 1;
55-
index: SnippetDescription[];
56-
metadata: { homepage: string };
57-
sourceURL: string;
58-
}
59-
6037
interface NpmMetaDataResponse {
6138
dist?: {
6239
tarball?: string;
6340
};
6441
}
6542

66-
const indexFileSchema = joi.object({
67-
indexFileVersion: joi.number().integer().max(1).required(),
43+
const regExpTag = Object.prototype.toString.call(/foo/);
44+
const errorMatcherSchema = z.object({
45+
message: z.string(),
46+
matches: z.array(
47+
z.custom<RegExp>((val) => Object.prototype.toString.call(val) === regExpTag)
48+
),
49+
});
50+
const indexDescriptionSchema = z.object({
51+
name: z.string(),
52+
snippetName: z.string(),
53+
installSpec: z.string().optional(),
54+
version: z.string(),
55+
description: z.string(),
56+
license: z.string(),
57+
readme: z.string(),
58+
errorMatchers: z.array(errorMatcherSchema),
59+
});
60+
const indexFileSchema = z.object({
61+
indexFileVersion: z.number().int().max(1),
6862

69-
metadata: joi.object({
70-
homepage: joi.string(),
63+
metadata: z.object({
64+
homepage: z.string(),
7165
}),
7266

73-
index: joi
74-
.array()
75-
.required()
76-
.items(
77-
joi.object({
78-
name: joi.string().required(),
79-
snippetName: joi.string().required(),
80-
installSpec: joi.string(),
81-
version: joi.string().required(),
82-
description: joi.string().required().allow(''),
83-
license: joi.string().required(),
84-
readme: joi.string().required().allow(''),
85-
errorMatchers: joi.array().items(
86-
joi.object({
87-
message: joi.string().required(),
88-
matches: joi.array().required().items(joi.object().regex()),
89-
})
90-
),
91-
})
92-
),
67+
index: z.array(indexDescriptionSchema),
9368
});
9469

70+
export type ErrorMatcher = z.infer<typeof errorMatcherSchema>;
71+
export type SnippetIndexFile = z.infer<typeof indexFileSchema> & {
72+
sourceURL: string;
73+
};
74+
export type SnippetDescription = z.infer<typeof indexDescriptionSchema>;
75+
9576
async function unpackBSON<T = any>(data: Buffer): Promise<T> {
9677
return bson.deserialize(await brotliDecompress(data)) as T;
9778
}
@@ -361,9 +342,8 @@ export class SnippetManager implements ShellPlugin {
361342
`The specified index file ${url} could not be parsed: ${err.message}`
362343
);
363344
}
364-
const { error } = indexFileSchema.validate(data, {
365-
allowUnknown: true,
366-
});
345+
const { error, data: parsedData } =
346+
indexFileSchema.safeParse(data);
367347
if (error) {
368348
this.messageBus.emit('mongosh-snippets:fetch-index-error', {
369349
action: 'validate-fetched',
@@ -374,7 +354,7 @@ export class SnippetManager implements ShellPlugin {
374354
`The specified index file ${url} is not a valid index file: ${error.message}`
375355
);
376356
}
377-
return { ...data, sourceURL: url };
357+
return { ...parsedData, sourceURL: url };
378358
})
379359
);
380360
// If possible, write the result to disk for caching.

0 commit comments

Comments
 (0)