@mongosh/shell-bson 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,447 @@
1
+ import type { BSON, Long, Binary, ObjectId } from './bson-export';
2
+ import {
3
+ CommonErrors,
4
+ MongoshInternalError,
5
+ MongoshInvalidInputError,
6
+ } from '@mongosh/errors';
7
+ import {
8
+ assertArgsDefinedType,
9
+ functionCtorWithoutProps,
10
+ assignAll,
11
+ pickWithExactKeyMatch,
12
+ } from './helpers';
13
+ import { randomBytes } from 'crypto';
14
+
15
+ type LongWithoutAccidentallyExposedMethods = Omit<
16
+ typeof Long,
17
+ 'fromExtendedJSON'
18
+ >;
19
+ type BinaryType = Binary;
20
+ export interface ShellBsonBase<BSONLib extends BSON = BSON> {
21
+ DBRef: BSONLib['DBRef'] &
22
+ ((
23
+ namespace: string,
24
+ oid: any,
25
+ db?: string,
26
+ fields?: Document
27
+ ) => BSONLib['DBRef']['prototype']);
28
+ bsonsize: (object: any) => number;
29
+ MaxKey: (() => BSONLib['MaxKey']['prototype']) & {
30
+ toBSON: () => BSONLib['MaxKey']['prototype'];
31
+ };
32
+ MinKey: (() => BSONLib['MinKey']['prototype']) & {
33
+ toBSON: () => BSONLib['MinKey']['prototype'];
34
+ };
35
+ ObjectId: BSONLib['ObjectId'] &
36
+ ((
37
+ id?: string | number | ObjectId | Buffer
38
+ ) => BSONLib['ObjectId']['prototype']);
39
+ Timestamp: BSONLib['Timestamp'] &
40
+ ((
41
+ t?: number | Long | { t: number; i: number },
42
+ i?: number
43
+ ) => BSONLib['Timestamp']['prototype']);
44
+ Code: BSONLib['Code'] &
45
+ ((c?: string | Function, s?: any) => BSONLib['Code']['prototype']);
46
+ NumberDecimal: (s?: string) => BSONLib['Decimal128']['prototype'];
47
+ NumberInt: (v?: string) => BSONLib['Int32']['prototype'];
48
+ NumberLong: (s?: string | number) => BSONLib['Long']['prototype'];
49
+ ISODate: (input?: string) => Date;
50
+ BinData: (
51
+ subtype: number,
52
+ b64string: string
53
+ ) => BSONLib['Binary']['prototype'];
54
+ HexData: (subtype: number, hexstr: string) => BSONLib['Binary']['prototype'];
55
+ UUID: (hexstr?: string) => BSONLib['Binary']['prototype'];
56
+ MD5: (hexstr: string) => BSONLib['Binary']['prototype'];
57
+ Decimal128: BSONLib['Decimal128'];
58
+ BSONSymbol: BSONLib['BSONSymbol'];
59
+ Int32: BSONLib['Int32'];
60
+ Long: LongWithoutAccidentallyExposedMethods;
61
+ Binary: BSONLib['Binary'];
62
+ Double: BSONLib['Double'];
63
+ EJSON: BSONLib['EJSON'];
64
+ BSONRegExp: BSONLib['BSONRegExp'];
65
+ }
66
+
67
+ type WithHelp<T, Help> = {
68
+ [prop in keyof T]: T[prop] & { help?: () => Help } & {
69
+ prototype?: { help?: Help & (() => Help); _bsontype?: unknown };
70
+ };
71
+ };
72
+
73
+ export type ShellBson<BSONLib extends BSON = BSON, Help = unknown> = WithHelp<
74
+ ShellBsonBase<WithHelp<BSONLib, Help>>,
75
+ Help
76
+ >;
77
+
78
+ export interface ShellBsonOptions<BSONLib extends BSON = BSON, Help = unknown> {
79
+ bsonLibrary: BSONLib;
80
+ printWarning: (msg: string) => void;
81
+ assignMetadata?: (
82
+ target: any,
83
+ props: {
84
+ minVersion?: string;
85
+ maxVersion?: string;
86
+ deprecated?: boolean;
87
+ help?: Help;
88
+ }
89
+ ) => void;
90
+ constructHelp?: (className: string) => Help;
91
+ }
92
+
93
+ /**
94
+ * This method modifies the BSON class passed in as argument. This is required so that
95
+ * we can have help, serverVersions, and other metadata on the bson classes constructed by the user.
96
+ */
97
+ export function constructShellBson<
98
+ BSONLib extends BSON = BSON,
99
+ Help = unknown
100
+ >({
101
+ bsonLibrary: bson,
102
+ printWarning,
103
+ assignMetadata,
104
+ constructHelp,
105
+ }: ShellBsonOptions<BSONLib, Help>): ShellBson<BSONLib, Help> {
106
+ const bsonNames: (keyof ShellBsonBase & keyof BSON)[] = [
107
+ 'Binary',
108
+ 'Code',
109
+ 'DBRef',
110
+ 'Decimal128',
111
+ 'Double',
112
+ 'Int32',
113
+ 'Long',
114
+ 'MaxKey',
115
+ 'MinKey',
116
+ 'ObjectId',
117
+ 'Timestamp',
118
+ 'BSONSymbol',
119
+ 'BSONRegExp',
120
+ ]; // Statically set this so we can error if any are missing
121
+
122
+ const helps: Partial<Record<keyof ShellBsonBase, Help>> = {};
123
+ for (const className of bsonNames) {
124
+ if (!(className in bson)) {
125
+ throw new MongoshInternalError(
126
+ `${className} does not exist in provided BSON package.`
127
+ );
128
+ }
129
+ const help = constructHelp?.(className);
130
+ helps[className] = help;
131
+ if (!('prototype' in bson[className])) continue;
132
+ assignMetadata?.(bson[className].prototype, {
133
+ help: help,
134
+ // Symbol is deprecated
135
+ ...(className === 'BSONSymbol'
136
+ ? { deprecated: true, maxVersion: '1.6.0' }
137
+ : {}),
138
+ });
139
+ }
140
+
141
+ const bsonPkg: ShellBson<BSONLib, Help> = {
142
+ DBRef: assignAll(function DBRef(
143
+ namespace: string,
144
+ oid: any,
145
+ db?: string,
146
+ fields?: Document
147
+ ): typeof bson.DBRef.prototype {
148
+ assertArgsDefinedType(
149
+ [namespace, oid, db],
150
+ ['string', true, [undefined, 'string'], [undefined, 'object']],
151
+ 'DBRef'
152
+ );
153
+ return new bson.DBRef(namespace, oid, db, fields);
154
+ },
155
+ pickWithExactKeyMatch(bson.DBRef, ['prototype'])),
156
+ // DBPointer not available in the bson 1.x library, but depreciated since 1.6
157
+ bsonsize: function bsonsize(object: any): number {
158
+ assertArgsDefinedType([object], ['object'], 'bsonsize');
159
+ return bson.calculateObjectSize(object);
160
+ },
161
+ // See https://jira.mongodb.org/browse/MONGOSH-1024 for context on the toBSON additions
162
+ MaxKey: assignAll(
163
+ function MaxKey(): typeof bson.MaxKey.prototype {
164
+ return new bson.MaxKey();
165
+ },
166
+ pickWithExactKeyMatch(bson.MaxKey, ['prototype']),
167
+ { toBSON: () => new bson.MaxKey() }
168
+ ),
169
+ MinKey: assignAll(
170
+ function MinKey(): typeof bson.MinKey.prototype {
171
+ return new bson.MinKey();
172
+ },
173
+ pickWithExactKeyMatch(bson.MinKey, ['prototype']),
174
+ { toBSON: () => new bson.MinKey() }
175
+ ),
176
+ ObjectId: assignAll(function ObjectId(
177
+ id?: string | number | typeof bson.ObjectId.prototype | Buffer
178
+ ): typeof bson.ObjectId.prototype {
179
+ assertArgsDefinedType(
180
+ [id],
181
+ [[undefined, 'string', 'number', 'object']],
182
+ 'ObjectId'
183
+ );
184
+ return new bson.ObjectId(id);
185
+ },
186
+ pickWithExactKeyMatch(bson.ObjectId, ['prototype', 'cacheHexString', 'generate', 'createFromTime', 'createFromHexString', 'createFromBase64', 'isValid'])),
187
+ Timestamp: assignAll(function Timestamp(
188
+ t?: number | typeof bson.Long.prototype | { t: number; i: number },
189
+ i?: number
190
+ ): typeof bson.Timestamp.prototype {
191
+ assertArgsDefinedType(
192
+ [t, i],
193
+ [
194
+ ['number', 'object', undefined],
195
+ [undefined, 'number'],
196
+ ],
197
+ 'Timestamp'
198
+ );
199
+ // Order of Timestamp() arguments is reversed in mongo/mongosh and the driver:
200
+ // https://jira.mongodb.org/browse/MONGOSH-930
201
+ // TODO(maybe at some point...): Drop support for the two-argument variant of Timestamp().
202
+ if (typeof t === 'object' && t !== null && 't' in t && 'i' in t) {
203
+ return new bson.Timestamp(t);
204
+ } else if (i !== undefined || typeof t === 'number') {
205
+ return new bson.Timestamp({ t: t as number, i: i ?? (0 as number) });
206
+ }
207
+ return new bson.Timestamp(t as typeof bson.Long.prototype);
208
+ },
209
+ pickWithExactKeyMatch(bson.Timestamp, ['prototype', 'fromInt', 'fromNumber', 'fromBits', 'fromString', 'MAX_VALUE'])),
210
+ Code: assignAll(function Code(
211
+ c: string | Function = '',
212
+ s?: any
213
+ ): typeof bson.Code.prototype {
214
+ assertArgsDefinedType(
215
+ [c, s],
216
+ [
217
+ [undefined, 'string', 'function'],
218
+ [undefined, 'object'],
219
+ ],
220
+ 'Code'
221
+ );
222
+ return new bson.Code(c, s);
223
+ },
224
+ pickWithExactKeyMatch(bson.Code, ['prototype'])),
225
+ NumberDecimal: assignAll(
226
+ function NumberDecimal(s = '0'): typeof bson.Decimal128.prototype {
227
+ assertArgsDefinedType(
228
+ [s],
229
+ [['string', 'number', 'bson:Long', 'bson:Int32', 'bson:Decimal128']],
230
+ 'NumberDecimal'
231
+ );
232
+ if (typeof s === 'number') {
233
+ printWarning(
234
+ 'NumberDecimal: specifying a number as argument is deprecated and may lead to loss of precision, pass a string instead'
235
+ );
236
+ }
237
+ return bson.Decimal128.fromString(`${s}`);
238
+ },
239
+ { prototype: bson.Decimal128.prototype }
240
+ ),
241
+ NumberInt: assignAll(
242
+ function NumberInt(v = '0'): typeof bson.Int32.prototype {
243
+ v ??= '0';
244
+ assertArgsDefinedType(
245
+ [v],
246
+ [['string', 'number', 'bson:Long', 'bson:Int32']],
247
+ 'NumberInt'
248
+ );
249
+ return new bson.Int32(parseInt(`${v}`, 10));
250
+ },
251
+ { prototype: bson.Int32.prototype }
252
+ ),
253
+ NumberLong: assignAll(
254
+ function NumberLong(
255
+ s: string | number = '0'
256
+ ): typeof bson.Long.prototype {
257
+ s ??= '0';
258
+ assertArgsDefinedType(
259
+ [s],
260
+ [['string', 'number', 'bson:Long', 'bson:Int32']],
261
+ 'NumberLong'
262
+ );
263
+ if (typeof s === 'number') {
264
+ printWarning(
265
+ 'NumberLong: specifying a number as argument is deprecated and may lead to loss of precision, pass a string instead'
266
+ );
267
+ return bson.Long.fromNumber(s);
268
+ }
269
+ return bson.Long.fromString(`${s}`);
270
+ },
271
+ { prototype: bson.Long.prototype }
272
+ ),
273
+ ISODate: function ISODate(
274
+ input?: string | number | Date | undefined
275
+ ): Date {
276
+ if (input === undefined) return new Date();
277
+ if (typeof input !== 'string') return new Date(input);
278
+ const isoDateRegex =
279
+ /^(?<Y>\d{4})-?(?<M>\d{2})-?(?<D>\d{2})([T ](?<h>\d{2})(:?(?<m>\d{2})(:?((?<s>\d{2})(\.(?<ms>\d+))?))?)?(?<tz>Z|([+-])(\d{2}):?(\d{2})?)?)?$/;
280
+ const match = isoDateRegex.exec(input);
281
+ if (match !== null && match.groups !== undefined) {
282
+ // Normalize the representation because ISO-8601 accepts e.g.
283
+ // '20201002T102950Z' without : and -, but `new Date()` does not.
284
+ const { Y, M, D, h, m, s, ms, tz } = match.groups;
285
+ const normalized = `${Y}-${M}-${D}T${h || '00'}:${m || '00'}:${
286
+ s || '00'
287
+ }.${ms || '000'}${tz || 'Z'}`;
288
+ const date = new Date(normalized);
289
+ // Make sur we're in the range 0000-01-01T00:00:00.000Z - 9999-12-31T23:59:59.999Z
290
+ if (
291
+ date.getTime() >= -62167219200000 &&
292
+ date.getTime() <= 253402300799999
293
+ ) {
294
+ return date;
295
+ }
296
+ }
297
+ throw new MongoshInvalidInputError(
298
+ `${JSON.stringify(input)} is not a valid ISODate`,
299
+ CommonErrors.InvalidArgument
300
+ );
301
+ },
302
+ BinData: assignAll(
303
+ function BinData(subtype: number, b64string: string): BinaryType {
304
+ // this from 'help misc' in old shell
305
+ assertArgsDefinedType(
306
+ [subtype, b64string],
307
+ ['number', 'string'],
308
+ 'BinData'
309
+ );
310
+ const buffer = Buffer.from(b64string, 'base64');
311
+ return new bson.Binary(buffer, subtype);
312
+ },
313
+ { prototype: bson.Binary.prototype }
314
+ ),
315
+ HexData: assignAll(
316
+ function HexData(subtype: number, hexstr: string): BinaryType {
317
+ assertArgsDefinedType(
318
+ [subtype, hexstr],
319
+ ['number', 'string'],
320
+ 'HexData'
321
+ );
322
+ const buffer = Buffer.from(hexstr, 'hex');
323
+ return new bson.Binary(buffer, subtype);
324
+ },
325
+ { prototype: bson.Binary.prototype }
326
+ ),
327
+ UUID: assignAll(
328
+ function UUID(hexstr?: string): BinaryType {
329
+ if (hexstr === undefined) {
330
+ // Generate a version 4, variant 1 UUID, like the old shell did.
331
+ const uuid = randomBytes(16);
332
+ uuid[6] = (uuid[6] & 0x0f) | 0x40;
333
+ uuid[8] = (uuid[8] & 0x3f) | 0x80;
334
+ hexstr = uuid.toString('hex');
335
+ }
336
+ assertArgsDefinedType([hexstr], ['string'], 'UUID');
337
+ // Strip any dashes, as they occur in the standard UUID formatting
338
+ // (e.g. 01234567-89ab-cdef-0123-456789abcdef).
339
+ const buffer = Buffer.from((hexstr as string).replace(/-/g, ''), 'hex');
340
+ return new bson.Binary(buffer, bson.Binary.SUBTYPE_UUID);
341
+ },
342
+ { prototype: bson.Binary.prototype }
343
+ ),
344
+ MD5: assignAll(
345
+ function MD5(hexstr: string): BinaryType {
346
+ assertArgsDefinedType([hexstr], ['string'], 'MD5');
347
+ const buffer = Buffer.from(hexstr, 'hex');
348
+ return new bson.Binary(buffer, bson.Binary.SUBTYPE_MD5);
349
+ },
350
+ { prototype: bson.Binary.prototype }
351
+ ),
352
+ // Add the driver types to bsonPkg so we can deprecate the shell ones later
353
+ Decimal128: assignAll(
354
+ functionCtorWithoutProps(bson.Decimal128),
355
+ pickWithExactKeyMatch(bson.Decimal128, [
356
+ 'prototype',
357
+ 'fromString',
358
+ 'fromStringWithRounding',
359
+ ])
360
+ ),
361
+ BSONSymbol: assignAll(
362
+ functionCtorWithoutProps(bson.BSONSymbol),
363
+ pickWithExactKeyMatch(bson.BSONSymbol, ['prototype'])
364
+ ),
365
+ Int32: assignAll(
366
+ functionCtorWithoutProps(bson.Int32),
367
+ pickWithExactKeyMatch(bson.Int32, ['prototype', 'fromString'])
368
+ ),
369
+ Long: assignAll(
370
+ functionCtorWithoutProps(bson.Long),
371
+ pickWithExactKeyMatch(
372
+ bson.Long as LongWithoutAccidentallyExposedMethods,
373
+ [
374
+ 'prototype',
375
+ 'fromValue',
376
+ 'isLong',
377
+ 'fromBytesBE',
378
+ 'fromBytesLE',
379
+ 'fromBytes',
380
+ 'fromString',
381
+ 'fromStringStrict',
382
+ 'fromBigInt',
383
+ 'fromNumber',
384
+ 'fromInt',
385
+ 'fromBits',
386
+ 'MIN_VALUE',
387
+ 'MAX_VALUE',
388
+ 'NEG_ONE',
389
+ 'UONE',
390
+ 'ONE',
391
+ 'UZERO',
392
+ 'ZERO',
393
+ 'MAX_UNSIGNED_VALUE',
394
+ 'TWO_PWR_24',
395
+ ]
396
+ )
397
+ ),
398
+ Binary: assignAll(
399
+ functionCtorWithoutProps(bson.Binary),
400
+ pickWithExactKeyMatch(bson.Binary, [
401
+ 'prototype',
402
+ 'createFromBase64',
403
+ 'createFromHexString',
404
+ 'fromInt8Array',
405
+ 'fromFloat32Array',
406
+ 'fromPackedBits',
407
+ 'fromBits',
408
+ 'BUFFER_SIZE',
409
+ 'SUBTYPE_DEFAULT',
410
+ 'SUBTYPE_FUNCTION',
411
+ 'SUBTYPE_BYTE_ARRAY',
412
+ 'SUBTYPE_UUID_OLD',
413
+ 'SUBTYPE_UUID',
414
+ 'SUBTYPE_MD5',
415
+ 'SUBTYPE_ENCRYPTED',
416
+ 'SUBTYPE_COLUMN',
417
+ 'SUBTYPE_SENSITIVE',
418
+ 'SUBTYPE_VECTOR',
419
+ 'SUBTYPE_USER_DEFINED',
420
+ 'VECTOR_TYPE',
421
+ ])
422
+ ),
423
+ Double: assignAll(
424
+ functionCtorWithoutProps(bson.Double),
425
+ pickWithExactKeyMatch(bson.Double, ['prototype', 'fromString'])
426
+ ),
427
+ BSONRegExp: assignAll(
428
+ functionCtorWithoutProps(bson.BSONRegExp),
429
+ pickWithExactKeyMatch(bson.BSONRegExp, ['prototype', 'parseOptions'])
430
+ ),
431
+ // Clone EJSON here so that it's not a frozen object in the shell
432
+ EJSON: pickWithExactKeyMatch(bson.EJSON, [
433
+ 'parse',
434
+ 'serialize',
435
+ 'stringify',
436
+ 'deserialize',
437
+ ]),
438
+ };
439
+
440
+ for (const className of Object.keys(bsonPkg) as (keyof ShellBson)[]) {
441
+ const help = helps[className] ?? constructHelp?.(className);
442
+ if (!help) continue;
443
+ bsonPkg[className].help = (): Help => help;
444
+ Object.setPrototypeOf(bsonPkg[className].help, help);
445
+ }
446
+ return bsonPkg;
447
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "include": ["**/*"],
4
+ "exclude": ["node_modules", "dist", "lib"]
5
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "@mongodb-js/tsconfig-mongosh/tsconfig.common.json",
3
+ "compilerOptions": {
4
+ "outDir": "./lib",
5
+ "target": "es2020"
6
+ },
7
+ "include": ["src/**/*"],
8
+ "exclude": ["./src/**/*.spec.*"]
9
+ }