From 2baeb3e09a4c47514c5eee1978aeeb4c3272d25e Mon Sep 17 00:00:00 2001 From: Dave Arata Date: Fri, 7 Jan 2022 16:26:43 +0000 Subject: [PATCH] feat: support mapping ObjectId message as mongodb.ObjectId --- README.markdown | 7 +- .../objectid/objectid.bin | Bin 0 -> 228 bytes .../objectid/objectid.proto | 7 + .../objectid/objectid.ts | 86 +++++++ .../parameters.txt | 1 + .../use-objectid-true-external-import-test.ts | 51 ++++ .../use-objectid-true.bin | Bin 0 -> 1014 bytes .../use-objectid-true.proto | 13 + .../use-objectid-true.ts | 243 ++++++++++++++++++ jest.config.js | 1 + package.json | 1 + src/main.ts | 63 +++++ src/options.ts | 2 + src/types.ts | 10 + tests/options-test.ts | 1 + yarn.lock | 127 ++++++++- 16 files changed, 601 insertions(+), 12 deletions(-) create mode 100644 integration/use-objectid-true-external-import/objectid/objectid.bin create mode 100644 integration/use-objectid-true-external-import/objectid/objectid.proto create mode 100644 integration/use-objectid-true-external-import/objectid/objectid.ts create mode 100644 integration/use-objectid-true-external-import/parameters.txt create mode 100644 integration/use-objectid-true-external-import/use-objectid-true-external-import-test.ts create mode 100644 integration/use-objectid-true-external-import/use-objectid-true.bin create mode 100644 integration/use-objectid-true-external-import/use-objectid-true.proto create mode 100644 integration/use-objectid-true-external-import/use-objectid-true.ts diff --git a/README.markdown b/README.markdown index 096a49733..dd767d54c 100644 --- a/README.markdown +++ b/README.markdown @@ -161,6 +161,10 @@ creating a class and calling the right getters/setters. - `fromJSON`/`toJSON` use the [proto3 canonical JSON encoding format](https://developers.google.com/protocol-buffers/docs/proto3#json) (e.g. timestamps are ISO strings), unlike [`protobufjs`](https://github.com/protobufjs/protobuf.js/issues/1304). +- ObjectIds can be mapped as `mongodb.ObjectId` + + (Configurable with the `useObjectId` parameter.) + # Auto-Batching / N+1 Prevention (Note: this is currently only supported by the Twirp clients.) @@ -333,6 +337,8 @@ Generated code will be placed in the Gradle build directory. - With `--ts_proto_opt=useDate=false`, fields of type `google.protobuf.Timestamp` will not be mapped to type `Date` in the generated types. See [Timestamp](#timestamp) for more details. +- With `--ts_proto_opt=useObjectId=true`, fields of a type called ObjectId where the message is constructed to have on field called value that is a string will be mapped to type `mongodb.ObjectId` in the generated types. This will require your project to install the mongodb npm package. See [ObjectId](#objectid) for more details. + - With `--ts_proto_opt=outputSchema=true`, meta typings will be generated that can later be used in other code generators. - With `--ts_proto_opt=outputTypeRegistry=true`, the type registry will be generated that can be used to resolve message types by fully-qualified name. Also, each message will get extra `$type` field containing fully-qualified name. @@ -671,7 +677,6 @@ The representation of `google.protobuf.Timestamp` is configurable by the `useDat | --------------------------- | ---------------------- | ------------------------------------ | ---------------- | | `google.protobuf.Timestamp` | `Date` | `{ seconds: number, nanos: number }` | `string` | - # Number Types Numbers are by default assumed to be plain JavaScript `number`s. diff --git a/integration/use-objectid-true-external-import/objectid/objectid.bin b/integration/use-objectid-true-external-import/objectid/objectid.bin new file mode 100644 index 0000000000000000000000000000000000000000..af1eca891c4c2c98c5792425b5a17c65c7e3e36d GIT binary patch literal 228 zcmZ|KI|{-;5C-7cSJ?b6qN8H43aPAO;VHBcyn!(he87rC3lHbn%tp4fn{Vcyfx+BY zm*&`>>s2hu`@^?>VXYduOc(V22>eU2J9%Hm%_4{0_2)J>> 3) { + case 1: + message.value = reader.string(); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): ObjectId { + return { + value: isSet(object.value) ? String(object.value) : '', + }; + }, + + toJSON(message: ObjectId): unknown { + const obj: any = {}; + message.value !== undefined && (obj.value = message.value); + return obj; + }, + + fromPartial, I>>(object: I): ObjectId { + const message = createBaseObjectId(); + message.value = object.value ?? ''; + return message; + }, +}; + +type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined; + +export type DeepPartial = T extends Builtin + ? T + : T extends Array + ? Array> + : T extends ReadonlyArray + ? ReadonlyArray> + : T extends {} + ? { [K in keyof T]?: DeepPartial } + : Partial; + +type KeysOfUnion = T extends T ? keyof T : never; +export type Exact = P extends Builtin + ? P + : P & { [K in keyof P]: Exact } & Record>, never>; + +// If you get a compile-error about 'Constructor and ... have no overlap', +// add '--ts_proto_opt=esModuleInterop=true' as a flag when calling 'protoc'. +if (util.Long !== Long) { + util.Long = Long as any; + configure(); +} + +function isSet(value: any): boolean { + return value !== null && value !== undefined; +} diff --git a/integration/use-objectid-true-external-import/parameters.txt b/integration/use-objectid-true-external-import/parameters.txt new file mode 100644 index 000000000..ffd837642 --- /dev/null +++ b/integration/use-objectid-true-external-import/parameters.txt @@ -0,0 +1 @@ +useMongoObjectId=true diff --git a/integration/use-objectid-true-external-import/use-objectid-true-external-import-test.ts b/integration/use-objectid-true-external-import/use-objectid-true-external-import-test.ts new file mode 100644 index 000000000..6a8a88742 --- /dev/null +++ b/integration/use-objectid-true-external-import/use-objectid-true-external-import-test.ts @@ -0,0 +1,51 @@ +import { Todo } from './use-objectid-true'; + +import * as mongodb from 'mongodb'; + +const id1 = new mongodb.ObjectId(); +const id2 = new mongodb.ObjectId(); + +describe('useMongoObjectId=true External Import', () => { + it('generates types that compile and encode', () => { + const output = Todo.encode({ + id: '6883ed6e-bd0d-4817-ba58-c2a53c73edc2', + oid: id1, + repeatedOid: [id1, id2], + mapOfOids: { + id1, + id2, + }, + }).finish(); + + expect(Todo.decode(output)).toMatchInlineSnapshot(` + Object { + "id": "6883ed6e-bd0d-4817-ba58-c2a53c73edc2", + "mapOfOids": Object { + "id1": "${id1.toString()}", + "id2": "${id2.toString()}", + }, + "oid": "${id1.toString()}", + "optionalOid": undefined, + "repeatedOid": Array [ + "${id1.toString()}", + "${id2.toString()}", + ], + } + `); + + expect(Todo.toJSON(Todo.decode(output))).toMatchInlineSnapshot(` + Object { + "id": "6883ed6e-bd0d-4817-ba58-c2a53c73edc2", + "mapOfOids": Object { + "id1": "${id1.toString()}", + "id2": "${id2.toString()}", + }, + "oid": "${id1.toString()}", + "repeatedOid": Array [ + "${id1.toString()}", + "${id2.toString()}", + ], + } + `); + }); +}); diff --git a/integration/use-objectid-true-external-import/use-objectid-true.bin b/integration/use-objectid-true-external-import/use-objectid-true.bin new file mode 100644 index 0000000000000000000000000000000000000000..f7cb5b92f641f3b7a46007a663add6ded2ee2e6f GIT binary patch literal 1014 zcmaJ=+iuf95Z#*{d)B03DZ~mAq!CDcAZ}?vDm?IjganmPq^R$aiybL#=~{_{)P6zV z_#?iAZ{Qm`YkLfpXx{ehoO8T0a}53UDj(a^v;14R$j0SzohQZ8mUa*XvKPf_#IApF z=o4d)4mHa}b88d28x0|tR90Uyvkl&l^kSXIe8_jhX$_9g!8eLApF;pVGLkXlPQpmR z9s__-%qZpR08%k-qzZbV0q+De(i4v$0?z@@p@0w)y|7D2B6?^}{i>70>z_W{*QSx! zz~H|2M!yB96PsDn1+mDQR`6tYL0Y^J*qc6hlKeY`ghnuR6!Q+EWnScInP)R6kwdvb zqKP{}=?92xQ7-IddQnN$rmuZwQ%!6X-*9e@Kz~n*nVmcJD)y=`$JJw9Q_0t~n9Py8 z`gBcIODkZ}YqQA-Lh#qYplhL`8j83SjhRNu3w;CCSCJ@7>#C?bQgj_9t zN(g1av*(Qr!y1Z%rX-X@KnP7jO_l;p!ksbI&?MY{-IySN)BBelN8AE57PRmH literal 0 HcmV?d00001 diff --git a/integration/use-objectid-true-external-import/use-objectid-true.proto b/integration/use-objectid-true-external-import/use-objectid-true.proto new file mode 100644 index 000000000..8c1ba1419 --- /dev/null +++ b/integration/use-objectid-true-external-import/use-objectid-true.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +package foo; + +import "objectid/objectid.proto"; + +message Todo { + string id = 1; + foo.objectid.ObjectId oid = 2; + repeated foo.objectid.ObjectId repeated_oid = 3; + optional foo.objectid.ObjectId optional_oid = 4; + map map_of_oids = 5; +} diff --git a/integration/use-objectid-true-external-import/use-objectid-true.ts b/integration/use-objectid-true-external-import/use-objectid-true.ts new file mode 100644 index 000000000..d4edc3a4e --- /dev/null +++ b/integration/use-objectid-true-external-import/use-objectid-true.ts @@ -0,0 +1,243 @@ +/* eslint-disable */ +import { util, configure, Writer, Reader } from 'protobufjs/minimal'; +import * as Long from 'long'; +import * as mongodb from 'mongodb'; +import { ObjectId } from './objectid/objectid'; + +export const protobufPackage = 'foo'; + +export interface Todo { + id: string; + oid: mongodb.ObjectId | undefined; + repeatedOid: mongodb.ObjectId[]; + optionalOid?: mongodb.ObjectId | undefined; + mapOfOids: { [key: string]: mongodb.ObjectId }; +} + +export interface Todo_MapOfOidsEntry { + key: string; + value: mongodb.ObjectId | undefined; +} + +function createBaseTodo(): Todo { + return { id: '', oid: undefined, repeatedOid: [], optionalOid: undefined, mapOfOids: {} }; +} + +export const Todo = { + encode(message: Todo, writer: Writer = Writer.create()): Writer { + if (message.id !== '') { + writer.uint32(10).string(message.id); + } + if (message.oid !== undefined) { + ObjectId.encode(toProtoObjectId(message.oid), writer.uint32(18).fork()).ldelim(); + } + for (const v of message.repeatedOid) { + ObjectId.encode(toProtoObjectId(v!), writer.uint32(26).fork()).ldelim(); + } + if (message.optionalOid !== undefined) { + ObjectId.encode(toProtoObjectId(message.optionalOid), writer.uint32(34).fork()).ldelim(); + } + Object.entries(message.mapOfOids).forEach(([key, value]) => { + Todo_MapOfOidsEntry.encode({ key: key as any, value }, writer.uint32(42).fork()).ldelim(); + }); + return writer; + }, + + decode(input: Reader | Uint8Array, length?: number): Todo { + const reader = input instanceof Reader ? input : new Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseTodo(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.id = reader.string(); + break; + case 2: + message.oid = fromProtoObjectId(ObjectId.decode(reader, reader.uint32())); + break; + case 3: + message.repeatedOid.push(fromProtoObjectId(ObjectId.decode(reader, reader.uint32()))); + break; + case 4: + message.optionalOid = fromProtoObjectId(ObjectId.decode(reader, reader.uint32())); + break; + case 5: + const entry5 = Todo_MapOfOidsEntry.decode(reader, reader.uint32()); + if (entry5.value !== undefined) { + message.mapOfOids[entry5.key] = entry5.value; + } + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): Todo { + return { + id: isSet(object.id) ? String(object.id) : '', + oid: isSet(object.oid) ? fromJsonObjectId(object.oid) : undefined, + repeatedOid: Array.isArray(object?.repeatedOid) ? object.repeatedOid.map((e: any) => fromJsonObjectId(e)) : [], + optionalOid: isSet(object.optionalOid) ? fromJsonObjectId(object.optionalOid) : undefined, + mapOfOids: isObject(object.mapOfOids) + ? Object.entries(object.mapOfOids).reduce<{ [key: string]: mongodb.ObjectId }>((acc, [key, value]) => { + acc[key] = fromJsonObjectId(value); + return acc; + }, {}) + : {}, + }; + }, + + toJSON(message: Todo): unknown { + const obj: any = {}; + message.id !== undefined && (obj.id = message.id); + message.oid !== undefined && (obj.oid = message.oid.toString()); + if (message.repeatedOid) { + obj.repeatedOid = message.repeatedOid.map((e) => e.toString()); + } else { + obj.repeatedOid = []; + } + message.optionalOid !== undefined && (obj.optionalOid = message.optionalOid.toString()); + obj.mapOfOids = {}; + if (message.mapOfOids) { + Object.entries(message.mapOfOids).forEach(([k, v]) => { + obj.mapOfOids[k] = v.toString(); + }); + } + return obj; + }, + + fromPartial, I>>(object: I): Todo { + const message = createBaseTodo(); + message.id = object.id ?? ''; + message.oid = object.oid !== undefined && object.oid !== null ? (object.oid as mongodb.ObjectId) : undefined; + message.repeatedOid = object.repeatedOid?.map((e) => e as mongodb.ObjectId) || []; + message.optionalOid = + object.optionalOid !== undefined && object.optionalOid !== null + ? (object.optionalOid as mongodb.ObjectId) + : undefined; + message.mapOfOids = Object.entries(object.mapOfOids ?? {}).reduce<{ [key: string]: mongodb.ObjectId }>( + (acc, [key, value]) => { + if (value !== undefined) { + acc[key] = value as mongodb.ObjectId; + } + return acc; + }, + {} + ); + return message; + }, +}; + +function createBaseTodo_MapOfOidsEntry(): Todo_MapOfOidsEntry { + return { key: '', value: undefined }; +} + +export const Todo_MapOfOidsEntry = { + encode(message: Todo_MapOfOidsEntry, writer: Writer = Writer.create()): Writer { + if (message.key !== '') { + writer.uint32(10).string(message.key); + } + if (message.value !== undefined) { + ObjectId.encode(toProtoObjectId(message.value), writer.uint32(18).fork()).ldelim(); + } + return writer; + }, + + decode(input: Reader | Uint8Array, length?: number): Todo_MapOfOidsEntry { + const reader = input instanceof Reader ? input : new Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseTodo_MapOfOidsEntry(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.key = reader.string(); + break; + case 2: + message.value = fromProtoObjectId(ObjectId.decode(reader, reader.uint32())); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): Todo_MapOfOidsEntry { + return { + key: isSet(object.key) ? String(object.key) : '', + value: isSet(object.value) ? fromJsonObjectId(object.value) : undefined, + }; + }, + + toJSON(message: Todo_MapOfOidsEntry): unknown { + const obj: any = {}; + message.key !== undefined && (obj.key = message.key); + message.value !== undefined && (obj.value = message.value.toString()); + return obj; + }, + + fromPartial, I>>(object: I): Todo_MapOfOidsEntry { + const message = createBaseTodo_MapOfOidsEntry(); + message.key = object.key ?? ''; + message.value = + object.value !== undefined && object.value !== null ? (object.value as mongodb.ObjectId) : undefined; + return message; + }, +}; + +type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined; + +export type DeepPartial = T extends Builtin + ? T + : T extends Array + ? Array> + : T extends ReadonlyArray + ? ReadonlyArray> + : T extends {} + ? { [K in keyof T]?: DeepPartial } + : Partial; + +type KeysOfUnion = T extends T ? keyof T : never; +export type Exact = P extends Builtin + ? P + : P & { [K in keyof P]: Exact } & Record>, never>; + +function fromJsonObjectId(o: any): mongodb.ObjectId { + if (o instanceof mongodb.ObjectId) { + return o; + } else if (typeof o === 'string') { + return new mongodb.ObjectId(o); + } else { + return fromProtoObjectId(ObjectId.fromJSON(o)); + } +} + +function fromProtoObjectId(oid: ObjectId): mongodb.ObjectId { + return new mongodb.ObjectId(oid.value); +} + +function toProtoObjectId(oid: mongodb.ObjectId): ObjectId { + const value = oid.toString(); + return { value }; +} + +// If you get a compile-error about 'Constructor and ... have no overlap', +// add '--ts_proto_opt=esModuleInterop=true' as a flag when calling 'protoc'. +if (util.Long !== Long) { + util.Long = Long as any; + configure(); +} + +function isObject(value: any): boolean { + return typeof value === 'object' && value !== null; +} + +function isSet(value: any): boolean { + return value !== null && value !== undefined; +} diff --git a/jest.config.js b/jest.config.js index 99a8bbd99..f816d5b8c 100644 --- a/jest.config.js +++ b/jest.config.js @@ -5,4 +5,5 @@ module.exports = { moduleFileExtensions: ['js', 'ts'], testMatch: ['/integration/**/*-test.ts', '/tests/**/*-test.ts'], testPathIgnorePatterns: ['/integration/simple-esmodule-interop/*'], + testEnvironment: "node" }; diff --git a/package.json b/package.json index e329076dd..966e9a41d 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "@types/node": "^14.14.37", "chokidar": "^3.5.2", "jest": "^26.6.3", + "mongodb": "^4.3.0", "prettier": "^2.2.1", "reflect-metadata": "^0.1.13", "rxjs": "^7.4.0", diff --git a/src/main.ts b/src/main.ts index 614155343..0c6899961 100644 --- a/src/main.ts +++ b/src/main.ts @@ -18,6 +18,7 @@ import { isLongValueType, isMapType, isMessage, + isObjectId, isOptionalProperty, isPrimitive, isRepeated, @@ -281,6 +282,7 @@ export function generateFile(ctx: Context, fileDesc: FileDescriptorProto): [stri } export type Utils = ReturnType & + ReturnType & ReturnType & ReturnType & ReturnType & @@ -293,6 +295,7 @@ export function makeUtils(options: Options): Utils { return { ...bytes, ...makeDeepPartial(options, longs), + ...makeObjectIdMethods(options), ...makeTimestampMethods(options, longs), ...longs, ...makeComparisonUtils(), @@ -469,6 +472,46 @@ function makeDeepPartial(options: Options, longs: ReturnType) { const Timestamp = impProto(options, 'google/protobuf/timestamp', 'Timestamp'); @@ -775,6 +818,9 @@ function generateDecode(ctx: Context, fullName: string, messageDesc: DescriptorP } else if (isTimestamp(field) && (options.useDate === DateOption.DATE || options.useDate === DateOption.STRING)) { const type = basicTypeName(ctx, field, { keepValueType: true }); readSnippet = code`${utils.fromTimestamp}(${type}.decode(reader, reader.uint32()))`; + } else if (isObjectId(field) && options.useMongoObjectId) { + const type = basicTypeName(ctx, field, { keepValueType: true }); + readSnippet = code`${utils.fromProtoObjectId}(${type}.decode(reader, reader.uint32()))`; } else if (isMessage(field)) { const type = basicTypeName(ctx, field); readSnippet = code`${type}.decode(reader, reader.uint32())`; @@ -862,6 +908,11 @@ function generateEncode(ctx: Context, fullName: string, messageDesc: DescriptorP } else if (isScalar(field) || isEnum(field)) { const tag = ((field.number << 3) | basicWireType(field.type)) >>> 0; writeSnippet = (place) => code`writer.uint32(${tag}).${toReaderCall(field)}(${place})`; + } else if (isObjectId(field) && options.useMongoObjectId) { + const tag = ((field.number << 3) | 2) >>> 0; + const type = basicTypeName(ctx, field, { keepValueType: true }); + writeSnippet = (place) => + code`${type}.encode(${utils.toProtoObjectId}(${place}), writer.uint32(${tag}).fork()).ldelim()`; } else if (isTimestamp(field) && (options.useDate === DateOption.DATE || options.useDate === DateOption.STRING)) { const tag = ((field.number << 3) | 2) >>> 0; const type = basicTypeName(ctx, field, { keepValueType: true }); @@ -1049,6 +1100,8 @@ function generateFromJson(ctx: Context, fullName: string, messageDesc: Descripto const cstr = capitalize(basicTypeName(ctx, field, { keepValueType: true }).toCodeString()); return code`${cstr}(${from})`; } + } else if (isObjectId(field) && options.useMongoObjectId) { + return code`${utils.fromJsonObjectId}(${from})`; } else if (isTimestamp(field) && options.useDate === DateOption.STRING) { return code`String(${from})`; } else if ( @@ -1088,6 +1141,8 @@ function generateFromJson(ctx: Context, fullName: string, messageDesc: Descripto const cstr = capitalize(basicTypeName(ctx, valueType).toCodeString()); return code`${cstr}(${from})`; } + } else if (isObjectId(valueType) && options.useMongoObjectId) { + return code`${utils.fromJsonObjectId}(${from})`; } else if (isTimestamp(valueType) && options.useDate === DateOption.STRING) { return code`String(${from})`; } else if ( @@ -1207,6 +1262,8 @@ function generateToJson(ctx: Context, fullName: string, messageDesc: DescriptorP return isWithinOneOf(field) ? code`${from} !== undefined ? ${toJson}(${from}) : undefined` : code`${toJson}(${from})`; + } else if (isObjectId(field) && options.useMongoObjectId) { + return code`${from}.toString()`; } else if (isTimestamp(field) && options.useDate === DateOption.DATE) { return code`${from}.toISOString()`; } else if (isTimestamp(field) && options.useDate === DateOption.STRING) { @@ -1221,6 +1278,8 @@ function generateToJson(ctx: Context, fullName: string, messageDesc: DescriptorP return code`${toJson}(${from})`; } else if (isBytes(valueType)) { return code`${utils.base64FromBytes}(${from})`; + } else if (isObjectId(valueType) && options.useMongoObjectId) { + return code`${from}.toString()`; } else if (isTimestamp(valueType) && options.useDate === DateOption.DATE) { return code`${from}.toISOString()`; } else if (isTimestamp(valueType) && options.useDate === DateOption.STRING) { @@ -1320,6 +1379,8 @@ function generateFromPartial(ctx: Context, fullName: string, messageDesc: Descri const readSnippet = (from: string): Code => { if ((isLong(field) || isLongValueType(field)) && options.forceLong === LongOption.LONG) { return code`Long.fromValue(${from})`; + } else if (isObjectId(field) && options.useMongoObjectId) { + return code`${from} as mongodb.ObjectId`; } else if ( isPrimitive(field) || (isTimestamp(field) && (options.useDate === DateOption.DATE || options.useDate === DateOption.STRING)) || @@ -1342,6 +1403,8 @@ function generateFromPartial(ctx: Context, fullName: string, messageDesc: Descri } } else if (isAnyValueType(valueType)) { return code`${from}`; + } else if (isObjectId(valueType) && options.useMongoObjectId) { + return code`${from} as mongodb.ObjectId`; } else if ( isTimestamp(valueType) && (options.useDate === DateOption.DATE || options.useDate === DateOption.STRING) diff --git a/src/options.ts b/src/options.ts index 380718f13..cef284424 100644 --- a/src/options.ts +++ b/src/options.ts @@ -34,6 +34,7 @@ export type Options = { forceLong: LongOption; useOptionals: boolean | 'none' | 'messages' | 'all'; // boolean is deprecated useDate: DateOption; + useMongoObjectId: boolean; oneof: OneofOption; esModuleInterop: boolean; fileSuffix: string; @@ -68,6 +69,7 @@ export function defaultOptions(): Options { forceLong: LongOption.NUMBER, useOptionals: 'none', useDate: DateOption.DATE, + useMongoObjectId: false, oneof: OneofOption.PROPERTIES, esModuleInterop: false, fileSuffix: '', diff --git a/src/types.ts b/src/types.ts index c3ed344fd..1bacd5f9a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -404,6 +404,11 @@ export function isMapType(ctx: Context, messageDesc: DescriptorProto, field: Fie return detectMapType(ctx, messageDesc, field) !== undefined; } +export function isObjectId(field: FieldDescriptorProto): boolean { + // need to use endsWith instead of === because objectid could be imported from an external proto file + return field.typeName.endsWith('.ObjectId'); +} + export function isTimestamp(field: FieldDescriptorProto): boolean { return field.typeName === '.google.protobuf.Timestamp'; } @@ -542,6 +547,11 @@ export function messageToTypeName( return code`string`; } } + + // need to use endsWith instead of === because objectid could be imported from an external proto file + if (!typeOptions.keepValueType && options.useMongoObjectId && protoType.endsWith('.ObjectId')) { + return code`mongodb.ObjectId`; + } const [module, type] = toModuleAndType(typeMap, protoType); return code`${impProto(options, module, type)}`; } diff --git a/tests/options-test.ts b/tests/options-test.ts index 6f82a0818..cd0cffe2a 100644 --- a/tests/options-test.ts +++ b/tests/options-test.ts @@ -35,6 +35,7 @@ describe('options', () => { "unrecognizedEnum": true, "useDate": "timestamp", "useExactTypes": true, + "useMongoObjectId": false, "useOptionals": "none", } `); diff --git a/yarn.lock b/yarn.lock index f51709c21..533481b1e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1092,6 +1092,19 @@ resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.0.tgz" integrity sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw== +"@types/webidl-conversions@*": + version "6.1.1" + resolved "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-6.1.1.tgz" + integrity sha512-XAahCdThVuCFDQLT7R7Pk/vqeObFNL3YqRyFZg+AqAP/W1/w3xHaIxuW7WszQqTbIBOPRcItYJIou3i/mppu3Q== + +"@types/whatwg-url@^8.2.1": + version "8.2.1" + resolved "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.1.tgz" + integrity sha512-2YubE1sjj5ifxievI5Ge1sckb9k/Er66HyR2c+3+I6VDUUg1TLPdYYTEbQ+DjRkS4nTxMJhgWfSfMRD2sl2EYQ== + dependencies: + "@types/node" "*" + "@types/webidl-conversions" "*" + "@types/yargs-parser@*": version "15.0.0" resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz" @@ -1258,7 +1271,7 @@ anymatch@^3.0.3, anymatch@~3.1.2: aproba@^1.0.3: version "1.2.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" + resolved "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz" integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== aproba@^2.0.0: @@ -1456,7 +1469,7 @@ balanced-match@^1.0.0: resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= -base64-js@^1.3.0: +base64-js@^1.3.0, base64-js@^1.3.1: version "1.5.1" resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== @@ -1568,6 +1581,13 @@ bser@2.1.1: dependencies: node-int64 "^0.4.0" +bson@^4.6.1: + version "4.6.1" + resolved "https://registry.npmjs.org/bson/-/bson-4.6.1.tgz" + integrity sha512-I1LQ7Hz5zgwR4QquilLNZwbhPw0Apx7i7X9kGMBTsqPdml/03Q9NBtD9nt/19ahjlphktQImrnderxqpzeVDjw== + dependencies: + buffer "^5.6.0" + buffer-equal-constant-time@1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz" @@ -1578,6 +1598,14 @@ buffer-from@1.x, buffer-from@^1.0.0: resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz" integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== +buffer@^5.6.0: + version "5.7.1" + resolved "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + builtins@^1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz" @@ -1694,7 +1722,7 @@ char-regex@^1.0.2: chokidar@^3.5.2: version "3.5.2" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75" + resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz" integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ== dependencies: anymatch "~3.1.2" @@ -2160,6 +2188,11 @@ delegates@^1.0.0: resolved "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz" integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= +denque@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/denque/-/denque-2.0.1.tgz" + integrity sha512-tfiWc6BQLXNLpNiR5iGd0Ocu3P3VpxfzFiqubLgMfhfOw9WyvgJBd46CClNn9k3qfbjvT//0cf7AlYRX/OslMQ== + depd@^1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz" @@ -2971,6 +3004,11 @@ iconv-lite@^0.6.2: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + ignore-walk@^3.0.3: version "3.0.3" resolved "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz" @@ -3097,7 +3135,7 @@ is-arrayish@^0.2.1: is-binary-path@~2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + resolved "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz" integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== dependencies: binary-extensions "^2.0.0" @@ -3213,7 +3251,7 @@ is-glob@^4.0.1: is-glob@~4.0.1: version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== dependencies: is-extglob "^2.1.1" @@ -4224,6 +4262,11 @@ marked@^2.0.0: resolved "https://registry.npmjs.org/marked/-/marked-2.0.1.tgz" integrity sha512-5+/fKgMv2hARmMW7DOpykr2iLhl0NgjyELk5yn92iE7z8Se1IS9n3UsFm86hFXIkvMBmVxki8+ckcpjBeyo/hw== +memory-pager@^1.0.2: + version "1.5.0" + resolved "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz" + integrity sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg== + meow@^8.0.0: version "8.1.2" resolved "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz" @@ -4415,6 +4458,26 @@ modify-values@^1.0.0: resolved "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz" integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw== +mongodb-connection-string-url@^2.3.2: + version "2.4.1" + resolved "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.4.1.tgz" + integrity sha512-d5Kd2bVsKcSA7YI/yo57fSTtMwRQdFkvc5IZwod1RRxJtECeWPPSo7zqcUGJELifRA//Igs4spVtYAmvFCatug== + dependencies: + "@types/whatwg-url" "^8.2.1" + whatwg-url "^11.0.0" + +mongodb@^4.3.0: + version "4.3.0" + resolved "https://registry.npmjs.org/mongodb/-/mongodb-4.3.0.tgz" + integrity sha512-ovq9ZD9wEvab+LsaQOiwtne1Sy2egaHW8K/H5M18Tv+V5PgTRi+qdmxDGlbm94TSL3h56m6amstptu115Nzgow== + dependencies: + bson "^4.6.1" + denque "^2.0.1" + mongodb-connection-string-url "^2.3.2" + socks "^2.6.1" + optionalDependencies: + saslprep "^1.0.3" + ms@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" @@ -5013,7 +5076,7 @@ path-key@^3.0.0, path-key@^3.1.0: path-parse@^1.0.6: version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== path-to-regexp@3.2.0: @@ -5291,7 +5354,7 @@ readdir-scoped-modules@^1.1.0: readdirp@~3.6.0: version "3.6.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz" integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== dependencies: picomatch "^2.2.1" @@ -5506,6 +5569,13 @@ sane@^4.0.3: minimist "^1.1.1" walker "~1.0.5" +saslprep@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz" + integrity sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag== + dependencies: + sparse-bitfield "^3.0.3" + saxes@^5.0.1: version "5.0.1" resolved "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz" @@ -5556,7 +5626,7 @@ semver-diff@^3.1.1: semver-regex@^3.1.2: version "3.1.3" - resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-3.1.3.tgz#b2bcc6f97f63269f286994e297e229b6245d0dc3" + resolved "https://registry.npmjs.org/semver-regex/-/semver-regex-3.1.3.tgz" integrity sha512-Aqi54Mk9uYTjVexLnR67rTyBusmwd04cLkHy9hNvk3+G3nT2Oyg7E0l4XVbOaNwIvQ3hHeYxGcyEy+mKreyBFQ== "semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0: @@ -5696,6 +5766,14 @@ socks@^2.3.3: ip "^1.1.5" smart-buffer "^4.1.0" +socks@^2.6.1: + version "2.6.1" + resolved "https://registry.npmjs.org/socks/-/socks-2.6.1.tgz" + integrity sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA== + dependencies: + ip "^1.1.5" + smart-buffer "^4.1.0" + source-map-resolve@^0.5.0: version "0.5.3" resolved "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz" @@ -5735,6 +5813,13 @@ source-map@^0.7.3: resolved "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz" integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== +sparse-bitfield@^3.0.3: + version "3.0.3" + resolved "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz" + integrity sha1-/0rm5oZWBWuks+eSqzM004JzyhE= + dependencies: + memory-pager "^1.0.2" + spawn-error-forwarder@~1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/spawn-error-forwarder/-/spawn-error-forwarder-1.0.0.tgz" @@ -5859,7 +5944,7 @@ string-length@^4.0.1: string-width@^1.0.1: version "1.0.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + resolved "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz" integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= dependencies: code-point-at "^1.0.0" @@ -5904,7 +5989,7 @@ strip-ansi@^3.0.0, strip-ansi@^3.0.1: strip-ansi@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz" integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= dependencies: ansi-regex "^3.0.0" @@ -6062,7 +6147,7 @@ tiny-relative-date@^1.3.0: tmpl@1.0.x: version "1.0.5" - resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + resolved "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz" integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== to-fast-properties@^2.0.0: @@ -6126,6 +6211,13 @@ tr46@^2.0.2: dependencies: punycode "^2.1.1" +tr46@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz" + integrity sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA== + dependencies: + punycode "^2.1.1" + traverse@~0.6.6: version "0.6.6" resolved "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz" @@ -6434,6 +6526,11 @@ webidl-conversions@^6.1.0: resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz" integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== +webidl-conversions@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz" + integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== + whatwg-encoding@^1.0.5: version "1.0.5" resolved "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz" @@ -6446,6 +6543,14 @@ whatwg-mimetype@^2.3.0: resolved "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz" integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== +whatwg-url@^11.0.0: + version "11.0.0" + resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz" + integrity sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ== + dependencies: + tr46 "^3.0.0" + webidl-conversions "^7.0.0" + whatwg-url@^8.0.0, whatwg-url@^8.5.0: version "8.5.0" resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.5.0.tgz"