├── .nvmrc ├── .prettierignore ├── packages ├── protoscript │ ├── .npmignore │ ├── src │ │ ├── compiler.cmd │ │ ├── compiler.ts │ │ ├── index.ts │ │ ├── runtime │ │ │ ├── well-known-types │ │ │ │ ├── index.ts │ │ │ │ ├── empty.pb.ts │ │ │ │ ├── source_context.pb.ts │ │ │ │ ├── duration.pb.ts │ │ │ │ ├── any.pb.ts │ │ │ │ ├── timestamp.pb.ts │ │ │ │ └── field_mask.pb.ts │ │ │ ├── goog │ │ │ │ ├── asserts.ts │ │ │ │ ├── crypt.test.ts │ │ │ │ └── crypt.ts │ │ │ ├── index.ts │ │ │ ├── utils.test.ts │ │ │ ├── arith.ts │ │ │ ├── arith.test.ts │ │ │ ├── json.ts │ │ │ ├── constants.ts │ │ │ └── encoder.ts │ │ ├── cli │ │ │ ├── index.ts │ │ │ ├── utils.ts │ │ │ └── core.ts │ │ ├── codegen │ │ │ ├── autogenerate │ │ │ │ └── test.ts │ │ │ └── compile.ts │ │ └── plugin.ts │ ├── tsconfig.json │ └── package.json └── well-known-types │ ├── well_known_types.pb.js │ ├── tsconfig.json │ ├── package.json │ └── well_known_types.proto ├── e2e ├── conformance │ ├── proto.config.mjs │ ├── bin │ │ ├── conformance_test_runner │ │ └── generate_conformance_test_runner.sh │ ├── tsconfig.json │ ├── package.json │ ├── README.md │ ├── test.ts │ ├── runner.ts │ └── proto │ │ ├── conformance │ │ └── conformance.proto │ │ └── google │ │ └── protobuf │ │ └── test_messages_proto3.proto ├── serialization │ ├── proto.config.mjs │ ├── service.proto │ ├── service.pb.ts │ ├── tsconfig.json │ ├── package.json │ ├── message.proto │ └── json.proto └── treeshaking │ ├── proto.config.mjs │ ├── TreeshakingTest.ts │ ├── TreeshakingTestJSON.ts │ ├── tsconfig.json │ ├── package.json │ ├── treeshaking.proto │ └── test.ts ├── pnpm-workspace.yaml ├── .husky └── pre-commit ├── codecov.yml ├── babel.config.cjs ├── examples ├── typescript │ ├── src │ │ ├── size.proto │ │ ├── user.proto │ │ ├── haberdasher.proto │ │ ├── index.ts │ │ ├── user.ts │ │ ├── size.pb.ts │ │ ├── haberdasher.pb.ts │ │ └── user.pb.ts │ ├── tsconfig.json │ └── package.json ├── closure-compiler │ ├── tsconfig.json │ ├── src │ │ ├── index.js │ │ ├── haberdasher.proto │ │ ├── haberdasher.pb.js │ │ └── haberdasher.pb.ts │ └── package.json ├── javascript │ ├── package.json │ └── src │ │ ├── index.js │ │ ├── haberdasher.proto │ │ └── haberdasher.pb.js └── protoc │ ├── package.json │ └── src │ ├── index.js │ ├── haberdasher.proto │ ├── haberdasher.pb.js │ └── haberdasher_pb.js ├── .gitignore ├── jest.config.cjs ├── .github └── workflows │ ├── publish.yml │ └── ci.yml ├── tsconfig.json ├── CONTRIBUTING.md ├── DEVELOPMENT.md ├── LICENSE ├── eslint.config.js ├── package.json └── CHANGELOG.md /.nvmrc: -------------------------------------------------------------------------------- 1 | 22.14.0 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | coverage 3 | pnpm-lock.yaml 4 | -------------------------------------------------------------------------------- /packages/protoscript/.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | tsconfig.json 3 | -------------------------------------------------------------------------------- /packages/protoscript/src/compiler.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | node "%~dp0\compiler.js" %* -------------------------------------------------------------------------------- /e2e/conformance/proto.config.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | language: "typescript", 3 | }; 4 | -------------------------------------------------------------------------------- /e2e/serialization/proto.config.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | language: "typescript", 3 | }; 4 | -------------------------------------------------------------------------------- /e2e/treeshaking/proto.config.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | language: "typescript", 3 | }; 4 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - "examples/*" 3 | - "e2e/*" 4 | - "packages/*" 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | pnpm run lint:fix 5 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | target: auto 6 | threshold: 10% 7 | -------------------------------------------------------------------------------- /e2e/conformance/bin/conformance_test_runner: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tatethurston/ProtoScript/HEAD/e2e/conformance/bin/conformance_test_runner -------------------------------------------------------------------------------- /e2e/treeshaking/TreeshakingTest.ts: -------------------------------------------------------------------------------- 1 | import { TreeshakingTest } from "./treeshaking.pb.js"; 2 | 3 | export default TreeshakingTest.initialize(); 4 | -------------------------------------------------------------------------------- /packages/protoscript/src/compiler.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import { compile, compiler } from "./codegen/compile.js"; 3 | await compiler(compile); 4 | -------------------------------------------------------------------------------- /packages/well-known-types/well_known_types.pb.js: -------------------------------------------------------------------------------- 1 | // THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | // Source: well_known_types.proto 3 | -------------------------------------------------------------------------------- /e2e/treeshaking/TreeshakingTestJSON.ts: -------------------------------------------------------------------------------- 1 | import { TreeshakingTestJSON } from "./treeshaking.pb.js"; 2 | 3 | export default TreeshakingTestJSON.initialize(); 4 | -------------------------------------------------------------------------------- /babel.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ["@babel/preset-env", { targets: { node: "current" } }], 4 | "@babel/preset-typescript", 5 | ], 6 | }; 7 | -------------------------------------------------------------------------------- /examples/typescript/src/size.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | // Size of a Hat, in inches. 4 | message Size { 5 | // must be > 0 6 | int32 inches = 1; 7 | } 8 | 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/dist/* 2 | coverage 3 | node_modules 4 | todo.txt 5 | packages/well-known-types/**/*.pb.ts 6 | packages/protoscript/LICENSE 7 | packages/protoscript/README.md 8 | -------------------------------------------------------------------------------- /packages/well-known-types/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "outDir": "dist" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/closure-compiler/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "dist" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /e2e/conformance/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "outDir": "dist" 6 | }, 7 | "exclude": ["dist"] 8 | } 9 | -------------------------------------------------------------------------------- /e2e/treeshaking/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "outDir": "dist" 6 | }, 7 | "exclude": ["dist"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/protoscript/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./runtime/index.js"; 2 | export * from "./runtime/well-known-types/index.js"; 3 | export type { UserConfig as Config } from "./cli/core.js"; 4 | -------------------------------------------------------------------------------- /e2e/serialization/service.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | import "google/protobuf/empty.proto"; 3 | 4 | service Service { 5 | rpc Bar (google.protobuf.Empty) returns (google.protobuf.Empty); 6 | } 7 | -------------------------------------------------------------------------------- /packages/protoscript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src", 5 | "outDir": "dist" 6 | }, 7 | "exclude": ["dist"] 8 | } 9 | -------------------------------------------------------------------------------- /e2e/serialization/service.pb.ts: -------------------------------------------------------------------------------- 1 | // THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | // Source: service.proto 3 | /* eslint-disable */ 4 | 5 | import * as protoscript from "protoscript"; 6 | -------------------------------------------------------------------------------- /e2e/serialization/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "outDir": "dist", 6 | "noUnusedLocals": false 7 | }, 8 | "exclude": ["dist"] 9 | } 10 | -------------------------------------------------------------------------------- /e2e/serialization/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test-serialization", 3 | "type": "module", 4 | "scripts": { 5 | "typecheck": "tsc --noEmit" 6 | }, 7 | "dependencies": { 8 | "protoscript": "workspace:*" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/typescript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "rootDir": "src", 7 | "outDir": "dist" 8 | }, 9 | "include": ["src"] 10 | } 11 | -------------------------------------------------------------------------------- /examples/typescript/src/user.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | message User { 4 | string first_name = 1; 5 | string last_name = 2; 6 | bool active = 3; 7 | User manager = 4; 8 | repeated string locations = 5; 9 | map projects = 6; 10 | } 11 | -------------------------------------------------------------------------------- /examples/javascript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-example", 3 | "main": "index.js", 4 | "license": "MIT", 5 | "type": "module", 6 | "scripts": { 7 | "test": "node src/index.js" 8 | }, 9 | "dependencies": { 10 | "protoscript": "workspace:*" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /e2e/treeshaking/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test-serialization", 3 | "type": "module", 4 | "dependencies": { 5 | "protoscript": "workspace:*" 6 | }, 7 | "scripts": { 8 | "typecheck": "tsc --noEmit" 9 | }, 10 | "devDependencies": { 11 | "esbuild": "^0.19.5" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/well-known-types/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "well-known-types", 3 | "main": "index.js", 4 | "license": "MIT", 5 | "type": "module", 6 | "scripts": { 7 | "typecheck": "tsc --noEmit" 8 | }, 9 | "dependencies": { 10 | "protoscript": "workspace:*" 11 | }, 12 | "devDependencies": { 13 | "typescript": "^5.2.2" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /jest.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | clearMocks: true, 3 | coverageDirectory: "coverage", 4 | modulePathIgnorePatterns: ["dist"], 5 | // TS ESM imports are referenced with .js extensions, but jest will fail to find 6 | // the uncompiled file because it ends with .ts and is looking for .js. 7 | moduleNameMapper: { 8 | "(.+)\\.jsx?": "$1", 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /examples/protoc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-example", 3 | "main": "index.js", 4 | "license": "MIT", 5 | "scripts": { 6 | "build": "protoc --js_out=import_style=commonjs,binary:. src/haberdasher.proto", 7 | "test": "node src/index.js" 8 | }, 9 | "dependencies": { 10 | "google-protobuf": "^3.21.2", 11 | "protoscript": "workspace:*" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/protoc/src/index.js: -------------------------------------------------------------------------------- 1 | const { Hat } = require("./haberdasher_pb"); 2 | 3 | console.info("Protobuf"); 4 | const msg = new Hat([3, "red", "top hat"]).serializeBinary(); 5 | console.log("serialized:", msg); 6 | const hat = Hat.deserializeBinary(msg); 7 | console.log("deserialized:", hat.toObject()); 8 | 9 | console.info("JSON"); 10 | console.log("not implemented by protoc"); 11 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish NPM Package 2 | on: 3 | release: 4 | types: [published] 5 | workflow_dispatch: 6 | jobs: 7 | publish: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: tatethurston/github-actions/publish@main 11 | with: 12 | package_directory: packages/protoscript 13 | npm_token: ${{ secrets.NPM_TOKEN }} 14 | -------------------------------------------------------------------------------- /examples/typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ts-example", 3 | "main": "index.js", 4 | "license": "MIT", 5 | "type": "module", 6 | "scripts": { 7 | "test": "tsc && node dist/index.js", 8 | "typecheck": "tsc --noEmit" 9 | }, 10 | "dependencies": { 11 | "protoscript": "workspace:*" 12 | }, 13 | "devDependencies": { 14 | "typescript": "^5.2.2" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /e2e/conformance/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "conformance", 3 | "type": "module", 4 | "scripts": { 5 | "build": "esbuild runner.ts --bundle --platform=node --target=es2020 --outfile=dist/runner.cjs", 6 | "conformance:build": "./bin/generate_conformance_test_runner.sh", 7 | "typecheck": "tsc --noEmit" 8 | }, 9 | "dependencies": { 10 | "protoscript": "workspace:*" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/protoscript/src/runtime/well-known-types/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./any.pb.js"; 2 | export * from "./api.pb.js"; 3 | export * from "./duration.pb.js"; 4 | export * from "./empty.pb.js"; 5 | export * from "./field_mask.pb.js"; 6 | export * from "./source_context.pb.js"; 7 | export * from "./struct.pb.js"; 8 | export * from "./timestamp.pb.js"; 9 | export * from "./type.pb.js"; 10 | export * from "./wrappers.pb.js"; 11 | -------------------------------------------------------------------------------- /packages/protoscript/src/cli/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import { join } from "path"; 3 | import { fileURLToPath } from "url"; 4 | import { main } from "./core.js"; 5 | import { isWindows } from "./utils.js"; 6 | 7 | const compiler = join( 8 | fileURLToPath(import.meta.url), 9 | "..", 10 | "..", 11 | `compiler.${isWindows ? "cmd" : "js"}`, 12 | ); 13 | 14 | void main({ 15 | compiler: { path: compiler }, 16 | }); 17 | -------------------------------------------------------------------------------- /examples/typescript/src/haberdasher.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | import "src/size.proto"; 3 | 4 | // A Hat is a piece of headwear made by a Haberdasher. 5 | message Hat { 6 | Size size = 1; 7 | // anything but "invisible" 8 | string color = 2; 9 | // i.e. "bowler" 10 | string name = 3; 11 | } 12 | 13 | // Haberdasher service makes hats for clients. 14 | service Haberdasher { 15 | // MakeHat produces a hat of mysterious, randomly-selected color! 16 | rpc MakeHat(Size) returns (Hat); 17 | } 18 | -------------------------------------------------------------------------------- /packages/well-known-types/well_known_types.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | import "google/protobuf/any.proto"; 4 | import "google/protobuf/api.proto"; 5 | import "google/protobuf/duration.proto"; 6 | import "google/protobuf/empty.proto"; 7 | import "google/protobuf/field_mask.proto"; 8 | import "google/protobuf/source_context.proto"; 9 | import "google/protobuf/struct.proto"; 10 | import "google/protobuf/timestamp.proto"; 11 | import "google/protobuf/type.proto"; 12 | import "google/protobuf/wrappers.proto"; 13 | -------------------------------------------------------------------------------- /examples/javascript/src/index.js: -------------------------------------------------------------------------------- 1 | import { Hat, HatJSON } from "./haberdasher.pb.js"; 2 | 3 | console.info("Protobuf"); 4 | const msg = Hat.encode({ inches: 3, color: "red", name: "top hat" }); 5 | console.log("serialized:", msg); 6 | const hat = Hat.decode(msg); 7 | console.log("deserialized:", hat); 8 | 9 | console.info("JSON"); 10 | const json = HatJSON.encode({ inches: 3, color: "red", name: "top hat" }); 11 | console.log("serialized:", json); 12 | const hatJSON = HatJSON.decode(json); 13 | console.log("deserialized:", hatJSON); 14 | -------------------------------------------------------------------------------- /e2e/treeshaking/treeshaking.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | import "google/protobuf/timestamp.proto"; 4 | 5 | message TreeshakingTest { 6 | string string_field = 1; 7 | repeated string repeated_string_field = 2; 8 | bool bool_field = 3; 9 | repeated NestedMessage repeated_message_field = 4; 10 | optional NestedMessage optional_message_field = 5; 11 | google.protobuf.Timestamp timestamp_field = 6; 12 | map map_field = 7; 13 | } 14 | 15 | message NestedMessage { 16 | optional string string_field = 1; 17 | } 18 | -------------------------------------------------------------------------------- /examples/closure-compiler/src/index.js: -------------------------------------------------------------------------------- 1 | import { Hat, HatJSON } from "./haberdasher.pb.js"; 2 | 3 | console.info("Protobuf"); 4 | const msg = Hat.encode({ inches: 3, color: "red", name: "top hat" }); 5 | console.log("serialized:", msg); 6 | const hat = Hat.decode(msg); 7 | console.log("deserialized:", hat); 8 | 9 | console.info("JSON"); 10 | const json = HatJSON.encode({ inches: 3, color: "red", name: "top hat" }); 11 | console.log("serialized:", json); 12 | const hatJSON = HatJSON.decode(json); 13 | console.log("deserialized:", hatJSON); 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "noUnusedLocals": true, 5 | "noUnusedParameters": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "isolatedModules": true, 9 | "module": "NodeNext", 10 | "noEmitOnError": false, 11 | "outDir": "dist", 12 | "resolveJsonModule": true, 13 | "rootDir": "src", 14 | "skipLibCheck": true, 15 | "sourceMap": false, 16 | "strict": true, 17 | "declaration": true, 18 | "target": "ES2020" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/protoscript/src/runtime/goog/asserts.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/google/closure-library/blob/master/closure/goog/asserts/asserts.js#L174 2 | // istanbul ignore next: trivial 3 | export const assert = (condition: boolean) => { 4 | if (!condition) { 5 | throw new Error("Assertion failed"); 6 | } 7 | return condition; 8 | }; 9 | 10 | // https://github.com/google/closure-library/blob/master/closure/goog/asserts/asserts.js#L235 11 | // istanbul ignore next: trivial 12 | export const fail = (message: string) => { 13 | throw new Error(message); 14 | }; 15 | -------------------------------------------------------------------------------- /packages/protoscript/src/runtime/index.ts: -------------------------------------------------------------------------------- 1 | export { BinaryReader } from "./reader.js"; 2 | export { BinaryWriter } from "./writer.js"; 3 | export * from "./json.js"; 4 | export type ByteSource = ArrayBuffer | Uint8Array | number[] | string; 5 | export type PartialDeep = { 6 | /* eslint-disable @typescript-eslint/no-explicit-any */ 7 | [P in keyof T]?: NonNullable extends any[] | Uint8Array 8 | ? T[P] 9 | : NonNullable extends object 10 | ? PartialDeep 11 | : T[P]; 12 | /* eslint-enable @typescript-eslint/no-explicit-any */ 13 | }; 14 | -------------------------------------------------------------------------------- /examples/typescript/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Hat, HatJSON } from "./haberdasher.pb.js"; 2 | 3 | console.info("Protobuf"); 4 | const msg = Hat.encode({ size: { inches: 3 }, color: "red", name: "top hat" }); 5 | console.log("serialized:", msg); 6 | const hat = Hat.decode(msg); 7 | console.log("deserialized:", hat); 8 | 9 | console.info("JSON"); 10 | const json = HatJSON.encode({ 11 | size: { inches: 3 }, 12 | color: "red", 13 | name: "top hat", 14 | }); 15 | console.log("serialized:", json); 16 | const hatJSON = HatJSON.decode(json); 17 | console.log("deserialized:", hatJSON); 18 | -------------------------------------------------------------------------------- /examples/protoc/src/haberdasher.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | // Haberdasher service makes hats for clients. 4 | service Haberdasher { 5 | // MakeHat produces a hat of mysterious, randomly-selected color! 6 | rpc MakeHat(Size) returns (Hat); 7 | } 8 | 9 | // Size of a Hat, in inches. 10 | message Size { 11 | // must be > 0 12 | int32 inches = 1; 13 | } 14 | 15 | // A Hat is a piece of headwear made by a Haberdasher. 16 | message Hat { 17 | int32 inches = 1; 18 | // anything but "invisible" 19 | string color = 2; 20 | // i.e. "bowler" 21 | string name = 3; 22 | } 23 | -------------------------------------------------------------------------------- /examples/javascript/src/haberdasher.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | // Haberdasher service makes hats for clients. 4 | service Haberdasher { 5 | // MakeHat produces a hat of mysterious, randomly-selected color! 6 | rpc MakeHat(Size) returns (Hat); 7 | } 8 | 9 | // Size of a Hat, in inches. 10 | message Size { 11 | // must be > 0 12 | int32 inches = 1; 13 | } 14 | 15 | // A Hat is a piece of headwear made by a Haberdasher. 16 | message Hat { 17 | int32 inches = 1; 18 | // anything but "invisible" 19 | string color = 2; 20 | // i.e. "bowler" 21 | string name = 3; 22 | } 23 | -------------------------------------------------------------------------------- /examples/closure-compiler/src/haberdasher.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | // Haberdasher service makes hats for clients. 4 | service Haberdasher { 5 | // MakeHat produces a hat of mysterious, randomly-selected color! 6 | rpc MakeHat(Size) returns (Hat); 7 | } 8 | 9 | // Size of a Hat, in inches. 10 | message Size { 11 | // must be > 0 12 | int32 inches = 1; 13 | } 14 | 15 | // A Hat is a piece of headwear made by a Haberdasher. 16 | message Hat { 17 | int32 inches = 1; 18 | // anything but "invisible" 19 | string color = 2; 20 | // i.e. "bowler" 21 | string name = 3; 22 | } 23 | -------------------------------------------------------------------------------- /e2e/conformance/README.md: -------------------------------------------------------------------------------- 1 | Tests for `test_messages_proto2.proto` are intentionally skipped because ProtoScript targets Protocol Buffers v3. 2 | 3 | ProtoScript does not implement support for `group`s, and will throw when encountering `group` in protocol buffer messages. Groups are deprecated by protocol buffers and [should not be used](https://protobuf.dev/programming-guides/proto2/#groups). 4 | 5 | The conformance test runner is built with `pnpm conformance:build`. `test.ts` is a jest file that executes the conformance test using the `runner.ts` runner. 6 | 7 | ### Dependencies 8 | 9 | `brew install bazel` 10 | -------------------------------------------------------------------------------- /examples/closure-compiler/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-example", 3 | "type": "module", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "build:esm": "google-closure-compiler 'src/*.js' 'node_modules/protoscript/index.js' 'node_modules/protoscript/runtime/**/*.js' 'node_modules/protoscript/runtime/*.js' --js_output_file dist/index.js --module_resolution=NODE --compilation_level ADVANCED", 8 | "test": "node dist/index.js" 9 | }, 10 | "dependencies": { 11 | "protoscript": "workspace:*" 12 | }, 13 | "devDependencies": { 14 | "google-closure-compiler": "^20230802.0.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /e2e/conformance/test.ts: -------------------------------------------------------------------------------- 1 | import { spawnSync } from "child_process"; 2 | 3 | function run(cmd: string) { 4 | return spawnSync(cmd, { shell: true, encoding: "utf8" }); 5 | } 6 | 7 | describe("Conformance", () => { 8 | beforeAll(() => { 9 | process.chdir(__dirname); 10 | }); 11 | 12 | it("proto3 tests", () => { 13 | const result = run( 14 | `./bin/conformance_test_runner \ 15 | --enforce_recommended \ 16 | --failure_list ./expected_proto2_failing_tests.txt \ 17 | --output_dir . \ 18 | ./dist/runner.cjs`, 19 | ); 20 | 21 | expect(result.output).not.toEqual(undefined); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/protoscript/src/runtime/goog/crypt.test.ts: -------------------------------------------------------------------------------- 1 | import { stringToUint8Array, byteArrayToString } from "./crypt.js"; 2 | 3 | const str = "hello 👋"; 4 | const bytes = [104, 101, 108, 108, 111, 32, 240, 159, 145, 139]; 5 | 6 | describe("byteArrayToString", () => { 7 | it("number[]", () => { 8 | expect(byteArrayToString(bytes)).toEqual(str); 9 | }); 10 | 11 | it("reversible", () => { 12 | expect(byteArrayToString(stringToUint8Array(str))).toEqual(str); 13 | }); 14 | }); 15 | 16 | describe("stringToUint8Array", () => { 17 | it("string", () => { 18 | expect(stringToUint8Array(str)).toEqual(new Uint8Array(bytes)); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /packages/protoscript/src/runtime/utils.test.ts: -------------------------------------------------------------------------------- 1 | import { byteSourceToUint8Array } from "./utils.js"; 2 | 3 | describe("byteSourceToUint8Array", () => { 4 | const bytes = [104, 101, 108, 108, 111, 32, 240, 159, 145, 139]; 5 | const uint8array = new Uint8Array(bytes); 6 | 7 | it("ArrayBuffer", () => { 8 | expect(byteSourceToUint8Array(uint8array.buffer)).toEqual(uint8array); 9 | }); 10 | 11 | it("Uint8Array", () => { 12 | expect(byteSourceToUint8Array(uint8array)).toEqual(uint8array); 13 | }); 14 | 15 | it("number[]", () => { 16 | expect(byteSourceToUint8Array(bytes)).toEqual(uint8array); 17 | }); 18 | 19 | it("string", () => { 20 | expect(byteSourceToUint8Array("hello 👋")).toEqual(uint8array); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /packages/protoscript/src/codegen/autogenerate/test.ts: -------------------------------------------------------------------------------- 1 | import { printHeading } from "./index.js"; 2 | 3 | describe("printHeading", () => { 4 | it("short names", () => { 5 | expect(printHeading("Types")).toMatchInlineSnapshot(` 6 | " //========================================// 7 | // Types // 8 | //========================================// 9 | 10 | " 11 | `); 12 | }); 13 | 14 | it("long names", () => { 15 | expect(printHeading("VeryLongNameThatCausesAnErrorService")) 16 | .toMatchInlineSnapshot(` 17 | " //========================================// 18 | // VeryLongNameThatCausesAnErrorService // 19 | //========================================// 20 | 21 | " 22 | `); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /e2e/conformance/bin/generate_conformance_test_runner.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | ### 3 | # Run from e2e/conformance: 4 | # ./bin/generate_conformance_test_runner 5 | # 6 | TMP="tmp" 7 | GOOGLE_PROTOBUF_VERSION=23.3 8 | 9 | mkdir -p "$TMP" 10 | 11 | curl -L https://github.com/protocolbuffers/protobuf/releases/download/v"$GOOGLE_PROTOBUF_VERSION"/protobuf-"$GOOGLE_PROTOBUF_VERSION".tar.gz >|"$TMP"/protobuf-"$GOOGLE_PROTOBUF_VERSION".tar.gz 12 | tar -xzf "$TMP"/protobuf-"$GOOGLE_PROTOBUF_VERSION".tar.gz -C "$TMP"/ 13 | 14 | pushd "$TMP"/protobuf-"$GOOGLE_PROTOBUF_VERSION" 15 | bazel build test_messages_proto3_cc_proto conformance:conformance_proto conformance:conformance_test conformance:conformance_test_runner 16 | cp bazel-bin/conformance/conformance_test_runner ../../bin/conformance_test_runner 17 | popd 18 | 19 | rm -r "$TMP" 20 | -------------------------------------------------------------------------------- /packages/protoscript/src/runtime/goog/crypt.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/google/closure-library/blob/master/closure/goog/crypt/crypt.js#L46 2 | 3 | // https://developers.google.com/protocol-buffers/docs/encoding#strings 4 | // strings: valid UTF-8 string (often simply ASCII); max 2GB of bytes 5 | // bytes: any sequence of 8-bit bytes; max 2GB 6 | 7 | // Replacement for goog.crypt.base64.byteArrayToString 8 | const decoder = new TextDecoder("utf8"); 9 | export function byteArrayToString(bytes: Uint8Array | number[]): string { 10 | const buffer = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes); 11 | return decoder.decode(buffer); 12 | } 13 | 14 | // Replacement for goog.crypt.base64.decodeStringToUint8Array 15 | const encoder = new TextEncoder(); 16 | export function stringToUint8Array(s: string): Uint8Array { 17 | return encoder.encode(s); 18 | } 19 | -------------------------------------------------------------------------------- /packages/protoscript/src/runtime/arith.ts: -------------------------------------------------------------------------------- 1 | // This is to preserve the signatures in writer.ts 2 | // TODO convert writer to use bigint directly 3 | 4 | export class UInt64 { 5 | bigint: bigint; 6 | lo: number; 7 | hi: number; 8 | constructor(bigint: bigint, lo: number, hi: number) { 9 | this.bigint = bigint; 10 | this.lo = lo; 11 | this.hi = hi; 12 | } 13 | toString() { 14 | return this.bigint.toString(); 15 | } 16 | static fromString(s: string) { 17 | const bigint = BigInt.asUintN(64, BigInt(s)); 18 | const lo = Number(BigInt.asUintN(32, bigint)); 19 | const hi = Number(bigint >> BigInt(32)); 20 | return new UInt64(bigint, lo, hi); 21 | } 22 | } 23 | 24 | export class Int64 extends UInt64 { 25 | static fromString(s: string) { 26 | const bigint = BigInt.asIntN(64, BigInt(s)); 27 | const { lo, hi } = UInt64.fromString(s); 28 | return new Int64(bigint, lo, hi); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /e2e/serialization/message.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | message Foo { 4 | message FooBar { 5 | string field_one = 1; 6 | map field_two = 2; 7 | repeated int32 field_three = 3; 8 | } 9 | optional int32 field_one = 1; 10 | map field_two = 2; 11 | repeated Bar field_three = 3; 12 | FooBar field_four = 4; 13 | repeated int64 field_five = 5; 14 | Baz field_six = 6; 15 | repeated Baz field_seven = 7 [json_name="luckySeven"]; 16 | int64 field_eight = 8; 17 | bytes field_nine = 9; 18 | repeated bytes field_ten = 10; 19 | optional Bar field_eleven = 11; 20 | optional Bar field_twelve = 12; 21 | optional Bar field_thirteen = 13; 22 | Foo field_fourteen = 14; 23 | repeated Foo field_fifteen = 15; 24 | } 25 | 26 | message Bar { 27 | string field_one = 1; 28 | map field_two = 2; 29 | repeated int32 field_three = 3; 30 | } 31 | 32 | enum Baz { 33 | FOO = 0; 34 | BAR = 1; 35 | } 36 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 👫 2 | 3 | Thanks for helping make this project better! 4 | 5 | ## Report an Issue 🐛 6 | 7 | If you find a bug or want to discuss a new feature, please [create a new issue](https://github.com/tatethurston/protoscript/issues). If you'd prefer to keep things private, feel free to [email me](mailto:tatethurston@gmail.com?subject=protoscript). 8 | 9 | ## Contributing Code with Pull Requests 🎁 10 | 11 | Please create a [pull request](https://github.com/tatethurston/protoscript/pulls). Expect a few iterations and some discussion before your pull request is merged. If you want to take things in a new direction, feel free to fork and iterate without hindrance! 12 | 13 | ## Code of Conduct 🧐 14 | 15 | My expectations for myself and others is to strive to build a diverse, inclusive, safe community. 16 | 17 | For more guidance, check out [thoughtbot's code of conduct](https://thoughtbot.com/open-source-code-of-conduct). 18 | 19 | ## Licensing 📃 20 | 21 | See the project's [MIT License](https://github.com/tatethurston/protoscript/blob/main/LICENSE). 22 | -------------------------------------------------------------------------------- /DEVELOPMENT.md: -------------------------------------------------------------------------------- 1 | # Local Development 2 | 3 | ### First time setup 4 | 5 | 1. Install the following dependencies: 6 | 7 | ```sh 8 | brew install protobuf 9 | brew install bazel 10 | ``` 11 | 12 | 2. From the project directory root: 13 | 14 | ```sh 15 | nvm use 16 | pnpm install 17 | pnpm package:build 18 | # second time to link protoscript bin commands now available after package:build in packages 19 | pnpm install 20 | pnpm examples:regen 21 | pnpm build:wellknowntypes 22 | pnpm test 23 | ``` 24 | 25 | The source code for the package is in `src/`. 26 | 27 | There are examples that use the locally built package in `examples/`. 28 | 29 | There are e2e tests in `packages/e2e/`. 30 | 31 | ### Testing 32 | 33 | Tests are run with jest. 34 | 35 | From the project directory root: 36 | 37 | `pnpm test` 38 | 39 | ### Linting 40 | 41 | As part of installation, husky pre-commit hooks are installed to run linters against the repo. 42 | 43 | ### Publishing 44 | 45 | There are CI and publishing GitHub workflows in `./github/workflows`. These are named `ci.yml` and `publish.yml`. 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Tate Thurston 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /e2e/treeshaking/test.ts: -------------------------------------------------------------------------------- 1 | import * as esbuild from "esbuild"; 2 | 3 | describe("Treeshaking", () => { 4 | describe("json", () => { 5 | it("shakes out binary serdes", () => { 6 | const result = esbuild.buildSync({ 7 | entryPoints: ["./TreeshakingTestJSON.ts"], 8 | absWorkingDir: __dirname, 9 | bundle: true, 10 | write: false, 11 | }); 12 | 13 | const contents = result.outputFiles[0].contents; 14 | const code = Buffer.from(contents).toString("utf8"); 15 | expect(code).not.toMatch(/(BinaryReader|BinaryWriter)/); 16 | expect(code).toMatch(/JSON/); 17 | }); 18 | }); 19 | 20 | describe("protobuf", () => { 21 | it("shakes out json serdes", () => { 22 | const result = esbuild.buildSync({ 23 | entryPoints: ["./TreeshakingTest.ts"], 24 | absWorkingDir: __dirname, 25 | bundle: true, 26 | write: false, 27 | }); 28 | 29 | const contents = result.outputFiles[0].contents; 30 | const code = Buffer.from(contents).toString("utf8"); 31 | expect(code).not.toMatch(/JSON/); 32 | expect(code).toMatch(/(BinaryReader|BinaryWriter)/); 33 | }); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /examples/typescript/src/user.ts: -------------------------------------------------------------------------------- 1 | import { User, UserJSON } from "./user.pb.js"; 2 | 3 | // protocol buffers 4 | const bytes = User.encode({ 5 | firstName: "Homer", 6 | lastName: "Simpson", 7 | active: true, 8 | locations: ["Springfield"], 9 | projects: { SPP: "Springfield Power Plant" }, 10 | manager: { 11 | firstName: "Montgomery", 12 | lastName: "Burns", 13 | }, 14 | }); 15 | console.log(bytes); 16 | 17 | const userFromBytes = User.decode(bytes); 18 | console.log(userFromBytes); 19 | 20 | // json 21 | const json = UserJSON.encode({ 22 | firstName: "Homer", 23 | lastName: "Simpson", 24 | active: true, 25 | locations: ["Springfield"], 26 | projects: { SPP: "Springfield Power Plant" }, 27 | manager: { 28 | firstName: "Montgomery", 29 | lastName: "Burns", 30 | }, 31 | }); 32 | console.log(json); 33 | 34 | const userFromJSON = UserJSON.decode(json); 35 | console.log(userFromJSON); 36 | 37 | // ProtoScript generates and consumes plain JavaScript objects (POJOs) over classes. If you want to generate a full message 38 | // with default fields, you can use the #initialize method on the generated message class: 39 | const user = User.initialize(); 40 | console.log(user); 41 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from "@eslint/js"; 2 | import eslintConfigPrettier from "eslint-config-prettier"; 3 | import typescriptPlugin from "@typescript-eslint/eslint-plugin"; 4 | import tsParser from "@typescript-eslint/parser"; 5 | 6 | export default [ 7 | { 8 | ignores: ["**/dist", "examples", "packages/e2e", "coverage"], 9 | }, 10 | js.configs.recommended, 11 | { 12 | plugins: { 13 | "@typescript-eslint": typescriptPlugin, 14 | }, 15 | languageOptions: { 16 | parser: tsParser, 17 | }, 18 | rules: { 19 | ...typescriptPlugin.configs.recommended.rules, 20 | }, 21 | }, 22 | { 23 | files: ["*.ts"], 24 | parser: "@typescript-eslint/parser", 25 | parserOptions: { 26 | // eslint-disable-next-line no-undef 27 | tsconfigRootDir: process.cwd(), 28 | project: [ 29 | "./tsconfig.json", 30 | "./packages/*/tsconfig.json", 31 | "./examples/*/tsconfig.json", 32 | ], 33 | }, 34 | rules: { 35 | ...typescriptPlugin.configs["recommended-requiring-type-checking"].rules, 36 | "@typescript-eslint/prefer-nullish-coalescing": "error", 37 | "@typescript-eslint/no-unnecessary-condition": "error", 38 | "@typescript-eslint/prefer-optional-chain": "error", 39 | }, 40 | }, 41 | eslintConfigPrettier, 42 | ]; 43 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | jobs: 8 | build-package: 9 | name: Build Package 10 | runs-on: ubuntu-latest 11 | outputs: 12 | package: ${{ steps.publish-local-package.outputs.package }} 13 | steps: 14 | - uses: tatethurston/github-actions/publish-local-package@main 15 | id: publish-local-package 16 | with: 17 | path: packages/protoscript 18 | ci: 19 | name: Lint and Test 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: arduino/setup-protoc@v2 23 | with: 24 | version: "29.2" 25 | - uses: tatethurston/github-actions/test@main 26 | with: 27 | codecov_token: ${{ secrets.CODECOV_TOKEN }} 28 | - uses: tatethurston/github-actions/check-generated-files@main 29 | with: 30 | cmd: pnpm regen 31 | ci-windows: 32 | name: Windows CI 33 | runs-on: windows-latest 34 | needs: build-package 35 | steps: 36 | - uses: tatethurston/github-actions/install-local-package@main 37 | with: 38 | name: ${{ needs.build-package.outputs.package }} 39 | - uses: arduino/setup-protoc@v2 40 | with: 41 | version: "29.2" 42 | - name: Bin Check 43 | run: | 44 | touch dummy.proto 45 | npx --no protoscript 46 | -------------------------------------------------------------------------------- /packages/protoscript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "protoscript", 3 | "version": "0.0.23", 4 | "description": "A Protobuf runtime and code generation tool for JavaScript and TypeScript", 5 | "license": "MIT", 6 | "author": "Tate ", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/tatethurston/protoscript" 10 | }, 11 | "type": "module", 12 | "main": "./dist/cjs/index.cjs", 13 | "module": "./dist/index.js", 14 | "bin": { 15 | "protoscript": "./dist/cli/index.js" 16 | }, 17 | "scripts": { 18 | "build:commonjs": "esbuild src/index.ts --bundle --platform=node --target=es2020 --outfile=dist/cjs/index.cjs --external:protoscript", 19 | "build:module": "tsc", 20 | "clean": "rm -rf dist", 21 | "package:build": "pnpm run clean && pnpm run build:commonjs && pnpm run build:module && chmod +x dist/compiler.js dist/cli/index.js && cp src/compiler.cmd dist/compiler.cmd && pnpm run package:prune", 22 | "package:prune": "find dist -name *test* -delete", 23 | "typecheck": "tsc --noEmit" 24 | }, 25 | "sideEffects": false, 26 | "types": "./dist/index.d.ts", 27 | "dependencies": { 28 | "google-protobuf": "^3.21.2", 29 | "prettier": "^3.0.3" 30 | }, 31 | "keywords": [ 32 | "protobuf", 33 | "protocol buffers", 34 | "typescript" 35 | ], 36 | "exports": { 37 | "./package.json": "./package.json", 38 | ".": { 39 | "import": "./dist/index.js", 40 | "module": "./dist/index.js", 41 | "require": "./dist/cjs/index.cjs", 42 | "default": "./dist/index.js" 43 | }, 44 | "./plugin": "./dist/plugin.js" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/protoscript/src/plugin.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * This is an experimental implementation of plugins. It is used by 3 | * https://github.com/tatethurston/TwirpScript. 4 | * 5 | * This interface is likely to churn. If you are interested in creating a plugin, 6 | * please open an issue on https://github.com/tatethurston/ProtoScript. 7 | */ 8 | import type { Config } from "./codegen/autogenerate/index.js"; 9 | import type { ParsedAst } from "./codegen/utils.js"; 10 | 11 | /** 12 | * @private this is experimental and likely to change 13 | */ 14 | export interface PluginOpts { 15 | config: Config; 16 | ast: ParsedAst; 17 | } 18 | 19 | /** 20 | * @private this is experimental and likely to change 21 | */ 22 | export interface PluginOut { 23 | imports: string; 24 | services: string; 25 | } 26 | 27 | /** 28 | * @private this is experimental and likely to change 29 | */ 30 | export type Plugin = (opts: PluginOpts) => Partial | undefined; 31 | 32 | export { 33 | /** 34 | * @private this is experimental and likely to change 35 | */ 36 | printHeading, 37 | /** 38 | * @private this is experimental and likely to change 39 | */ 40 | printComments, 41 | /** 42 | * @private this is experimental and likely to change 43 | */ 44 | printIfTypescript, 45 | } from "./codegen/autogenerate/index.js"; 46 | 47 | export { 48 | /** 49 | * @private this is experimental and likely to change 50 | */ 51 | compile, 52 | /** 53 | * @private this is experimental and likely to change 54 | */ 55 | compiler, 56 | } from "./codegen/compile.js"; 57 | 58 | export { 59 | /** 60 | * @private this is experimental and likely to change 61 | */ 62 | type UserConfig as Config, 63 | /** 64 | * @private this is experimental and likely to change 65 | */ 66 | main, 67 | } from "./cli/core.js"; 68 | -------------------------------------------------------------------------------- /packages/protoscript/src/cli/utils.ts: -------------------------------------------------------------------------------- 1 | import { readdirSync, readFileSync, statSync } from "fs"; 2 | import { join } from "path"; 3 | import type { UserConfig } from "./core.js"; 4 | import { createHash } from "crypto"; 5 | import { execSync } from "child_process"; 6 | 7 | export const isWindows = process.platform === "win32"; 8 | 9 | export function commandIsInPath(cmd: string): boolean { 10 | try { 11 | const check = isWindows ? "where" : "which"; 12 | execSync(`${check} ${cmd}`); 13 | return true; 14 | } catch { 15 | return false; 16 | } 17 | } 18 | 19 | export function checksum(file: string): string { 20 | const hash = createHash("md5"); 21 | return hash.update(readFileSync(file, "utf8"), "utf8").digest("hex"); 22 | } 23 | 24 | export function pluralize(str: string, count: number): string { 25 | return count === 1 ? str : str + "s"; 26 | } 27 | 28 | export function deserializeConfig(config: string): UserConfig { 29 | const params = new URLSearchParams(config.replace(/,/g, "&")); 30 | return { 31 | language: 32 | params.get("language") === "typescript" ? "typescript" : "javascript", 33 | json: { 34 | emitFieldsWithDefaultValues: params 35 | .getAll("json") 36 | .includes("emitFieldsWithDefaultValues"), 37 | useProtoFieldName: params.getAll("json").includes("useProtoFieldName"), 38 | }, 39 | typescript: { 40 | emitDeclarationOnly: params 41 | .getAll("typescript") 42 | .includes("emitDeclarationOnly"), 43 | }, 44 | }; 45 | } 46 | 47 | export function findFiles(entry: string, ext: string): string[] { 48 | return readdirSync(entry) 49 | .flatMap((file) => { 50 | const filepath = join(entry, file); 51 | if ( 52 | statSync(filepath).isDirectory() && 53 | !filepath.includes("node_modules") 54 | ) { 55 | return findFiles(filepath, ext); 56 | } 57 | return filepath; 58 | }) 59 | .filter((file) => file.endsWith(ext)); 60 | } 61 | -------------------------------------------------------------------------------- /packages/protoscript/src/runtime/arith.test.ts: -------------------------------------------------------------------------------- 1 | import { UInt64, Int64 } from "./arith.js"; 2 | 3 | describe("UInt64", () => { 4 | it.each([ 5 | [0x5e84c935, 0xcae33d0e, "14619595947299359029"], 6 | [0x62b3b8b8, 0x93480544, "10612738313170434232"], 7 | [0x319bfb13, 0xc01c4172, "13843011313344445203"], 8 | [0x5b8a65fb, 0xa5885b31, "11927883880638080507"], 9 | [0x6bdb80f1, 0xb0d1b16b, "12741159895737008369"], 10 | [0x4b82b442, 0x2e0d8c97, "3318463081876730946"], 11 | [0x780d5208, 0x7d76752c, "9040542135845999112"], 12 | [0x2e46800f, 0x0993778d, "690026616168284175"], 13 | [0xf00a7e32, 0xcd8e3931, "14811839111111540274"], 14 | [0x1baeccd6, 0x923048c4, "10533999535534820566"], 15 | [0x03669d29, 0xbff3ab72, "13831587386756603177"], 16 | [0x2526073e, 0x01affc81, "121593346566522686"], 17 | [0xc24244e0, 0xd7f40d0e, "15561076969511732448"], 18 | [0xc56a341e, 0xa68b66a7, "12000798502816461854"], 19 | [0x8738d64d, 0xbfe78604, "13828168534871037517"], 20 | [0x5baff03b, 0xd7572aea, "15516918227177304123"], 21 | [0x4a843d8a, 0x864e132b, "9677693725920476554"], 22 | [0x25b4e94d, 0x22b54dc6, "2500990681505655117"], 23 | [0x6bbe664b, 0x55a5cc0e, "6171563226690381387"], 24 | [0xee916c81, 0xb00aabb3, "12685140089732426881"], 25 | ])("fromString(%s) hi=%s lo=%s", (lo, hi, bigint) => { 26 | expect(UInt64.fromString(bigint)).toEqual( 27 | expect.objectContaining({ 28 | lo, 29 | hi, 30 | bigint: BigInt(bigint), 31 | }), 32 | ); 33 | }); 34 | }); 35 | 36 | describe("Int64", () => { 37 | it.each([ 38 | [4294967295, 4294967295, "-1"], 39 | [4294967286, 4294967295, "-10"], 40 | [10, 0, "10"], 41 | [3567587328, 232, "1000000000000"], 42 | ])("fromString(%s) hi=%s lo=%s", (lo, hi, bigint) => { 43 | expect(Int64.fromString(bigint)).toEqual( 44 | expect.objectContaining({ 45 | lo, 46 | hi, 47 | bigint: BigInt(bigint), 48 | }), 49 | ); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "protoscript-dev", 3 | "version": "0.0.1", 4 | "description": "A Protobuf runtime and code generation tool for JavaScript and TypeScript", 5 | "license": "MIT", 6 | "author": "Tate ", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/tatethurston/protoscript" 10 | }, 11 | "type": "module", 12 | "exports": { 13 | ".": { 14 | "import": "./src/index.ts" 15 | } 16 | }, 17 | "scripts": { 18 | "build": "(pnpm package:copy:files && cd packages/protoscript && pnpm package:build)", 19 | "build:wellknowntypes": "(cd ./packages/well-known-types && GENERATE_KNOWN_TYPES=1 pnpm protoscript && cp google/protobuf/* ../protoscript/src/runtime/well-known-types/)", 20 | "e2e:build": "pnpm --filter './e2e/*' run build", 21 | "e2e:protoscript": "pnpm --filter './e2e/*' exec protoscript", 22 | "e2e:setup": "pnpm e2e:build && pnpm e2e:protoscript", 23 | "examples:regen": "pnpm --filter './examples/*' exec protoscript", 24 | "lint": "pnpm typecheck && prettier --check . && prettier-package-json --list-different '{,e2e/*,examples/*,packages/*,}package.json' && eslint .", 25 | "lint:fix": "prettier --write . && prettier-package-json --write '{,e2e/*,examples/*,packages/*}package.json' && eslint --fix .", 26 | "package:copy:files": "cp ./LICENSE ./README.md packages/protoscript", 27 | "prepare": "husky install", 28 | "regen": "pnpm examples:regen && pnpm e2e:protoscript && pnpm build:wellknowntypes", 29 | "test": "jest", 30 | "test:ci": "pnpm install --frozen-lockfile && pnpm run e2e:setup && pnpm run test --coverage", 31 | "typecheck": "pnpm --recursive run typecheck" 32 | }, 33 | "dependencies": { 34 | "google-protobuf": "^3.21.4" 35 | }, 36 | "devDependencies": { 37 | "@babel/preset-env": "^7.26.9", 38 | "@babel/preset-typescript": "^7.26.0", 39 | "@eslint/js": "^9.21.0", 40 | "@types/google-protobuf": "^3.15.12", 41 | "@types/jest": "^29.5.14", 42 | "@types/node": "^22.13.5", 43 | "@typescript-eslint/eslint-plugin": "^8.24.1", 44 | "@typescript-eslint/parser": "^8.24.1", 45 | "babel-loader": "^9.2.1", 46 | "codecov": "^3.8.3", 47 | "esbuild": "^0.25.0", 48 | "eslint": "^9.21.0", 49 | "eslint-config-prettier": "^10.0.1", 50 | "husky": "^9.1.7", 51 | "jest": "^29.7.0", 52 | "prettier": "^3.5.2", 53 | "prettier-package-json": "^2.8.0", 54 | "typescript": "^5.7.3" 55 | }, 56 | "packageManager": "pnpm@10.4.1+sha512.c753b6c3ad7afa13af388fa6d808035a008e30ea9993f58c6663e2bc5ff21679aa834db094987129aa4d488b86df57f7b634981b2f827cdcacc698cc0cfb88af" 57 | } 58 | -------------------------------------------------------------------------------- /packages/protoscript/src/codegen/compile.ts: -------------------------------------------------------------------------------- 1 | import PluginPb from "google-protobuf/google/protobuf/compiler/plugin_pb.js"; 2 | import type { CodeGeneratorResponse as CodeGeneratorResponseType } from "google-protobuf/google/protobuf/compiler/plugin_pb.js"; 3 | import { generate } from "./autogenerate/index.js"; 4 | import { 5 | getProtobufTSFileName, 6 | buildIdentifierTable, 7 | getProtobufJSFileName, 8 | KNOWN_TYPES, 9 | } from "./utils.js"; 10 | import { format } from "prettier"; 11 | import { deserializeConfig } from "../cli/utils.js"; 12 | const { CodeGeneratorRequest, CodeGeneratorResponse } = PluginPb; 13 | import { type Plugin } from "../plugin.js"; 14 | 15 | export async function compile( 16 | input: Uint8Array, 17 | plugins: Plugin[] = [], 18 | ): Promise { 19 | const request = CodeGeneratorRequest.deserializeBinary(input); 20 | const options = deserializeConfig(request.getParameter() ?? ""); 21 | const isTypescript = options.language === "typescript"; 22 | const response = new CodeGeneratorResponse(); 23 | response.setSupportedFeatures( 24 | CodeGeneratorResponse.Feature.FEATURE_PROTO3_OPTIONAL, 25 | ); 26 | 27 | const identifierTable = buildIdentifierTable(request); 28 | 29 | async function writeFile(name: string, content: string) { 30 | const file = new CodeGeneratorResponse.File(); 31 | file.setName(name); 32 | file.setContent( 33 | await format(content, { parser: isTypescript ? "typescript" : "babel" }), 34 | ); 35 | response.addFile(file); 36 | } 37 | 38 | for (const fileDescriptorProto of request.getProtoFileList()) { 39 | const name = fileDescriptorProto.getName(); 40 | if (!name) { 41 | continue; 42 | } 43 | if (!process.env.GENERATE_KNOWN_TYPES && KNOWN_TYPES.includes(name)) { 44 | continue; 45 | } 46 | 47 | const protobufTs = generate( 48 | fileDescriptorProto, 49 | identifierTable, 50 | options, 51 | plugins, 52 | ); 53 | await writeFile( 54 | isTypescript ? getProtobufTSFileName(name) : getProtobufJSFileName(name), 55 | protobufTs, 56 | ); 57 | } 58 | 59 | return response; 60 | } 61 | 62 | function readStream(stream: NodeJS.ReadStream): Promise { 63 | return new Promise((resolve) => { 64 | const chunks: Buffer[] = []; 65 | stream.on("readable", () => { 66 | let chunk: Buffer; 67 | // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment 68 | while ((chunk = process.stdin.read()) !== null) { 69 | chunks.push(chunk); 70 | } 71 | }); 72 | stream.on("end", () => { 73 | resolve(Buffer.concat(chunks)); 74 | }); 75 | }); 76 | } 77 | 78 | export async function compiler(protocompile: typeof compile): Promise { 79 | const input = await readStream(process.stdin); 80 | const response = await protocompile(input); 81 | process.stdout.write(response.serializeBinary()); 82 | } 83 | -------------------------------------------------------------------------------- /packages/protoscript/src/runtime/json.ts: -------------------------------------------------------------------------------- 1 | interface Duration { 2 | seconds: bigint; 3 | nanos: number; 4 | } 5 | 6 | type Timestamp = Duration; 7 | 8 | export function serializeBytes(bytes: Uint8Array): string { 9 | let binary = ""; 10 | for (let i = 0; i < bytes.length; i++) { 11 | binary += String.fromCharCode(bytes[i]); 12 | } 13 | return btoa(binary); 14 | } 15 | 16 | export function parseBytes(x: string): Uint8Array { 17 | // Convert URL-safe Base64 to standard Base64 18 | let str = x.replace(/-/g, "+").replace(/_/g, "/"); 19 | 20 | // Add padding if it's missing 21 | while (str.length % 4) { 22 | str += "="; 23 | } 24 | 25 | // decode Base64 26 | const binary = atob(str); 27 | 28 | // create Uint8Array 29 | const bytes = new Uint8Array(binary.length); 30 | for (let i = 0; i < binary.length; i++) { 31 | bytes[i] = binary.charCodeAt(i); 32 | } 33 | return bytes; 34 | } 35 | 36 | export function parseNumber(x: string | number): number { 37 | if (typeof x === "string") { 38 | return parseInt(x, 10); 39 | } 40 | return x; 41 | } 42 | 43 | export function parseDouble(x: string | number): number { 44 | if (typeof x === "string") { 45 | return parseFloat(x); 46 | } 47 | return x; 48 | } 49 | 50 | // Given '.3' => 300_000_000 51 | function parseNanos(x: string | undefined): number { 52 | return parseInt((x ?? "").padEnd(9, "0"), 10); 53 | } 54 | 55 | export function serializeTimestamp({ 56 | seconds = 0n, 57 | nanos = 0, 58 | }: Partial): string { 59 | const ms = Number(seconds) * 1_000 + nanos / 1_000_000; 60 | return new Date(ms).toISOString(); 61 | } 62 | 63 | export function parseTimestamp(x: string): Timestamp { 64 | const seconds = BigInt(Math.floor(new Date(x).getTime() / 1000)); 65 | // Given an RFC 3339 formated date such as 2023-10-18T04:02:27.123Z 66 | // spliting on '.' will yeild 123Z 67 | // parseInt will ignore the letter (or any other offset) 68 | const nanos = parseNanos(x.match(/\.(\d+)/)?.[1]); 69 | return { seconds, nanos }; 70 | } 71 | 72 | export function serializeDuration({ 73 | seconds = 0n, 74 | nanos = 0, 75 | }: Partial): string { 76 | const second = seconds.toString(); 77 | if (nanos === 0) { 78 | return `${second}s`; 79 | } 80 | 81 | let nano = Math.abs(nanos).toString().padStart(9, "0"); 82 | // Remove trailing zeros to have either 0, 3, 6 or 9 fractional digits 83 | if (nano.endsWith("000000")) { 84 | nano = nano.slice(0, -6); 85 | } else if (nano.endsWith("000")) { 86 | nano = nano.slice(0, -3); 87 | } 88 | 89 | // add negative sign bit for 0 seconds, negative nanos 90 | if (seconds === 0n && nanos < 0) { 91 | return `-${second}.${nano}s`; 92 | } 93 | return `${second}.${nano}s`; 94 | } 95 | 96 | export function parseDuration(x: string): Duration { 97 | // Given "1.000340012s" or "1s" 98 | const [seconds, nanos] = x.replace("s", "").split("."); 99 | return { seconds: BigInt(seconds), nanos: parseNanos(nanos) }; 100 | } 101 | -------------------------------------------------------------------------------- /packages/protoscript/src/runtime/well-known-types/empty.pb.ts: -------------------------------------------------------------------------------- 1 | // THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | // Source: google/protobuf/empty.proto 3 | /* eslint-disable */ 4 | 5 | import type { ByteSource, PartialDeep } from "protoscript"; 6 | import * as protoscript from "protoscript"; 7 | 8 | //========================================// 9 | // Types // 10 | //========================================// 11 | 12 | /** 13 | * A generic empty message that you can re-use to avoid defining duplicated 14 | * empty messages in your APIs. A typical example is to use it as the request 15 | * or the response type of an API method. For instance: 16 | * 17 | * service Foo { 18 | * rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty); 19 | * } 20 | * 21 | */ 22 | export interface Empty {} 23 | 24 | //========================================// 25 | // Protobuf Encode / Decode // 26 | //========================================// 27 | 28 | export const Empty = { 29 | /** 30 | * Serializes Empty to protobuf. 31 | */ 32 | encode: function (_msg?: PartialDeep): Uint8Array { 33 | return new Uint8Array(); 34 | }, 35 | 36 | /** 37 | * Deserializes Empty from protobuf. 38 | */ 39 | decode: function (_bytes?: ByteSource): Empty { 40 | return {}; 41 | }, 42 | 43 | /** 44 | * Initializes Empty with all fields set to their default value. 45 | */ 46 | initialize: function (msg?: Partial): Empty { 47 | return { 48 | ...msg, 49 | }; 50 | }, 51 | 52 | /** 53 | * @private 54 | */ 55 | _writeMessage: function ( 56 | _msg: PartialDeep, 57 | writer: protoscript.BinaryWriter, 58 | ): protoscript.BinaryWriter { 59 | return writer; 60 | }, 61 | 62 | /** 63 | * @private 64 | */ 65 | _readMessage: function ( 66 | _msg: Empty, 67 | _reader: protoscript.BinaryReader, 68 | ): Empty { 69 | return _msg; 70 | }, 71 | }; 72 | 73 | //========================================// 74 | // JSON Encode / Decode // 75 | //========================================// 76 | 77 | export const EmptyJSON = { 78 | /** 79 | * Serializes Empty to JSON. 80 | */ 81 | encode: function (_msg?: PartialDeep): string { 82 | return "{}"; 83 | }, 84 | 85 | /** 86 | * Deserializes Empty from JSON. 87 | */ 88 | decode: function (_json?: string): Empty { 89 | return {}; 90 | }, 91 | 92 | /** 93 | * Initializes Empty with all fields set to their default value. 94 | */ 95 | initialize: function (msg?: Partial): Empty { 96 | return { 97 | ...msg, 98 | }; 99 | }, 100 | 101 | /** 102 | * @private 103 | */ 104 | _writeMessage: function (_msg: PartialDeep): Record { 105 | return {}; 106 | }, 107 | 108 | /** 109 | * @private 110 | */ 111 | _readMessage: function (msg: Empty, _json: any): Empty { 112 | return msg; 113 | }, 114 | }; 115 | -------------------------------------------------------------------------------- /packages/protoscript/src/runtime/constants.ts: -------------------------------------------------------------------------------- 1 | export const FieldType = { 2 | INVALID: -1, 3 | DOUBLE: 1, 4 | FLOAT: 2, 5 | INT64: 3, 6 | UINT64: 4, 7 | INT32: 5, 8 | FIXED64: 6, 9 | FIXED32: 7, 10 | BOOL: 8, 11 | STRING: 9, 12 | GROUP: 10, 13 | MESSAGE: 11, 14 | BYTES: 12, 15 | UINT32: 13, 16 | ENUM: 14, 17 | SFIXED32: 15, 18 | SFIXED64: 16, 19 | SINT32: 17, 20 | SINT64: 18, 21 | 22 | // Extended types for Javascript 23 | FHASH64: 30, // 64-bit hash string, fixed-length encoding. 24 | VHASH64: 31, // 64-bit hash string, varint encoding. 25 | }; 26 | 27 | export type FieldType = (typeof FieldType)[keyof typeof FieldType]; 28 | 29 | /** 30 | * Wire-format type codes, taken from proto2/public/wire_format_lite.h. 31 | */ 32 | export const WireType = { 33 | INVALID: -1, 34 | VARINT: 0, 35 | FIXED64: 1, 36 | DELIMITED: 2, 37 | START_GROUP: 3, 38 | END_GROUP: 4, 39 | FIXED32: 5, 40 | }; 41 | 42 | export type WireType = (typeof WireType)[keyof typeof WireType]; 43 | 44 | /** 45 | * Translates field type to wire type. 46 | */ 47 | export const FieldTypeToWireType = function (fieldType: FieldType): WireType { 48 | switch (fieldType) { 49 | case FieldType.INT32: 50 | case FieldType.INT64: 51 | case FieldType.UINT32: 52 | case FieldType.UINT64: 53 | case FieldType.SINT32: 54 | case FieldType.SINT64: 55 | case FieldType.BOOL: 56 | case FieldType.ENUM: 57 | case FieldType.VHASH64: 58 | return WireType.VARINT; 59 | case FieldType.DOUBLE: 60 | case FieldType.FIXED64: 61 | case FieldType.SFIXED64: 62 | case FieldType.FHASH64: 63 | return WireType.FIXED64; 64 | case FieldType.STRING: 65 | case FieldType.MESSAGE: 66 | case FieldType.BYTES: 67 | return WireType.DELIMITED; 68 | case FieldType.FLOAT: 69 | case FieldType.FIXED32: 70 | case FieldType.SFIXED32: 71 | return WireType.FIXED32; 72 | case FieldType.INVALID: 73 | case FieldType.GROUP: 74 | default: 75 | return WireType.INVALID; 76 | } 77 | }; 78 | 79 | /** 80 | * Flag to indicate a missing field. 81 | */ 82 | export const INVALID_FIELD_NUMBER = -1; 83 | 84 | /** 85 | * The smallest normal float64 value. 86 | */ 87 | export const FLOAT32_MIN = 1.1754943508222875e-38; 88 | 89 | /** 90 | * The largest finite float32 value. 91 | */ 92 | export const FLOAT32_MAX = 3.4028234663852886e38; 93 | 94 | /** 95 | * The smallest normal float64 value. 96 | */ 97 | export const FLOAT64_MIN = 2.2250738585072014e-308; 98 | 99 | /** 100 | * The largest finite float64 value. 101 | */ 102 | export const FLOAT64_MAX = 1.7976931348623157e308; 103 | 104 | /** 105 | * Convenience constant equal to 2^20. 106 | */ 107 | export const TWO_TO_20 = 1048576; 108 | 109 | /** 110 | * Convenience constant equal to 2^23. 111 | */ 112 | export const TWO_TO_23 = 8388608; 113 | 114 | /** 115 | * Convenience constant equal to 2^31. 116 | */ 117 | export const TWO_TO_31 = 2147483648; 118 | 119 | /** 120 | * Convenience constant equal to 2^32. 121 | */ 122 | export const TWO_TO_32 = 4294967296; 123 | 124 | /** 125 | * Convenience constant equal to 2^52. 126 | */ 127 | export const TWO_TO_52 = 4503599627370496; 128 | 129 | /** 130 | * Convenience constant equal to 2^63. 131 | */ 132 | export const TWO_TO_63 = 9223372036854775808; 133 | 134 | /** 135 | * Convenience constant equal to 2^64. 136 | */ 137 | export const TWO_TO_64 = 18446744073709551616; 138 | -------------------------------------------------------------------------------- /examples/typescript/src/size.pb.ts: -------------------------------------------------------------------------------- 1 | // THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | // Source: src/size.proto 3 | /* eslint-disable */ 4 | 5 | import type { ByteSource, PartialDeep } from "protoscript"; 6 | import * as protoscript from "protoscript"; 7 | 8 | //========================================// 9 | // Types // 10 | //========================================// 11 | 12 | /** 13 | * Size of a Hat, in inches. 14 | */ 15 | export interface Size { 16 | /** 17 | * must be > 0 18 | */ 19 | inches: number; 20 | } 21 | 22 | //========================================// 23 | // Protobuf Encode / Decode // 24 | //========================================// 25 | 26 | export const Size = { 27 | /** 28 | * Serializes Size to protobuf. 29 | */ 30 | encode: function (msg: PartialDeep): Uint8Array { 31 | return Size._writeMessage( 32 | msg, 33 | new protoscript.BinaryWriter(), 34 | ).getResultBuffer(); 35 | }, 36 | 37 | /** 38 | * Deserializes Size from protobuf. 39 | */ 40 | decode: function (bytes: ByteSource): Size { 41 | return Size._readMessage( 42 | Size.initialize(), 43 | new protoscript.BinaryReader(bytes), 44 | ); 45 | }, 46 | 47 | /** 48 | * Initializes Size with all fields set to their default value. 49 | */ 50 | initialize: function (msg?: Partial): Size { 51 | return { 52 | inches: 0, 53 | ...msg, 54 | }; 55 | }, 56 | 57 | /** 58 | * @private 59 | */ 60 | _writeMessage: function ( 61 | msg: PartialDeep, 62 | writer: protoscript.BinaryWriter, 63 | ): protoscript.BinaryWriter { 64 | if (msg.inches) { 65 | writer.writeInt32(1, msg.inches); 66 | } 67 | return writer; 68 | }, 69 | 70 | /** 71 | * @private 72 | */ 73 | _readMessage: function (msg: Size, reader: protoscript.BinaryReader): Size { 74 | while (reader.nextField()) { 75 | const field = reader.getFieldNumber(); 76 | switch (field) { 77 | case 1: { 78 | msg.inches = reader.readInt32(); 79 | break; 80 | } 81 | default: { 82 | reader.skipField(); 83 | break; 84 | } 85 | } 86 | } 87 | return msg; 88 | }, 89 | }; 90 | 91 | //========================================// 92 | // JSON Encode / Decode // 93 | //========================================// 94 | 95 | export const SizeJSON = { 96 | /** 97 | * Serializes Size to JSON. 98 | */ 99 | encode: function (msg: PartialDeep): string { 100 | return JSON.stringify(SizeJSON._writeMessage(msg)); 101 | }, 102 | 103 | /** 104 | * Deserializes Size from JSON. 105 | */ 106 | decode: function (json: string): Size { 107 | return SizeJSON._readMessage(SizeJSON.initialize(), JSON.parse(json)); 108 | }, 109 | 110 | /** 111 | * Initializes Size with all fields set to their default value. 112 | */ 113 | initialize: function (msg?: Partial): Size { 114 | return { 115 | inches: 0, 116 | ...msg, 117 | }; 118 | }, 119 | 120 | /** 121 | * @private 122 | */ 123 | _writeMessage: function (msg: PartialDeep): Record { 124 | const json: Record = {}; 125 | if (msg.inches) { 126 | json["inches"] = msg.inches; 127 | } 128 | return json; 129 | }, 130 | 131 | /** 132 | * @private 133 | */ 134 | _readMessage: function (msg: Size, json: any): Size { 135 | const _inches_ = json["inches"]; 136 | if (_inches_) { 137 | msg.inches = protoscript.parseNumber(_inches_); 138 | } 139 | return msg; 140 | }, 141 | }; 142 | -------------------------------------------------------------------------------- /e2e/serialization/json.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | import "google/protobuf/timestamp.proto"; 4 | import "google/protobuf/duration.proto"; 5 | // import "google/protobuf/any.proto"; 6 | import "google/protobuf/struct.proto"; 7 | // import "google/protobuf/wrappers.proto"; 8 | // import "google/protobuf/field_mask.proto"; 9 | 10 | message Message { 11 | string foo_bar = 1; 12 | int64 g = 2; 13 | } 14 | 15 | enum Enum { 16 | FOO_BAR = 0; 17 | BAZ = 1; 18 | } 19 | 20 | message SampleMessage { 21 | Message sample_message = 1; 22 | 23 | Enum sample_enum = 2; 24 | 25 | map sample_map = 3; 26 | 27 | repeated string sample_repeated = 4; 28 | 29 | bool sample_bool = 5; 30 | 31 | string sample_string = 6; 32 | 33 | bytes sample_bytes = 7; 34 | 35 | int32 sample_int32 = 8; 36 | fixed32 sample_fixed32 = 9; 37 | uint32 sample_uint32 = 10; 38 | 39 | int64 sample_int64 = 11; 40 | fixed64 sample_fixed64 = 12; 41 | uint64 sample_uint64 = 13; 42 | 43 | float sample_float = 14; 44 | double sample_double = 15; 45 | 46 | // TODO: Any 47 | // google.protobuf.Any sample_any = 16; 48 | 49 | google.protobuf.Timestamp sample_timestamp = 17; 50 | 51 | google.protobuf.Duration sample_duration = 18; 52 | 53 | google.protobuf.Struct sample_struct = 19; 54 | 55 | // TODO: Wrapper Types 56 | // google.protobuf.BoolValue sample_bool_wrapper = 20; 57 | // google.protobuf.BytesValue sample_bytes_wrapper = 21; 58 | // google.protobuf.Int32Value sample_int32_wrapper = 22; 59 | // google.protobuf.Int64Value sample_int64_wrapper = 23; 60 | // google.protobuf.UInt32Value sample_uint32_wrapper = 24; 61 | // google.protobuf.UInt64Value sample_uint64_wrapper = 25; 62 | // google.protobuf.StringValue sample_string_wrapper = 26; 63 | // google.protobuf.DoubleValue sample_double_wrapper = 27; 64 | 65 | // TODO: FieldMask 66 | // google.protobuf.FieldMask sample_fieldmask = 28; 67 | 68 | // TODO: Remaining Well Known Types 69 | // ListValue array [foo, bar, ...] 70 | // Value value Any JSON value. Check google.protobuf.Value for details. 71 | // NullValue null JSON null 72 | // Empty object {} An empty JSON object 73 | } 74 | 75 | message OptionalMessage { 76 | optional string foo_bar = 1; 77 | optional int64 g = 2; 78 | } 79 | 80 | message OptionalSampleMessage { 81 | optional OptionalMessage sample_message = 1; 82 | 83 | optional Enum sample_enum = 2; 84 | 85 | map sample_map = 3; 86 | 87 | repeated string sample_repeated = 4; 88 | 89 | optional bool sample_bool = 5; 90 | 91 | optional string sample_string = 6; 92 | 93 | optional bytes sample_bytes = 7; 94 | 95 | optional int32 sample_int32 = 8; 96 | optional fixed32 sample_fixed32 = 9; 97 | optional uint32 sample_uint32 = 10; 98 | 99 | optional int64 sample_int64 = 11; 100 | optional fixed64 sample_fixed64 = 12; 101 | optional uint64 sample_uint64 = 13; 102 | 103 | optional float sample_float = 14; 104 | optional double sample_double = 15; 105 | 106 | // TODO: Any 107 | // optional google.protobuf.Any sample_any = 16; 108 | 109 | optional google.protobuf.Timestamp sample_timestamp = 17; 110 | 111 | optional google.protobuf.Duration sample_duration = 18; 112 | 113 | optional google.protobuf.Struct sample_struct = 19; 114 | 115 | // TODO: Wrapper Types 116 | // optional google.protobuf.BoolValue sample_bool_wrapper = 20; 117 | // optional google.protobuf.BytesValue sample_bytes_wrapper = 21; 118 | // optional google.protobuf.Int32Value sample_int32_wrapper = 22; 119 | // optional google.protobuf.Int64Value sample_int64_wrapper = 23; 120 | // optional google.protobuf.UInt32Value sample_uint32_wrapper = 24; 121 | // optional google.protobuf.UInt64Value sample_uint64_wrapper = 25; 122 | // optional google.protobuf.StringValue sample_string_wrapper = 26; 123 | // optional google.protobuf.DoubleValue sample_double_wrapper = 27; 124 | 125 | // TODO: FieldMask 126 | // optional google.protobuf.FieldMask sample_fieldmask = 28; 127 | 128 | // TODO: Remaining Well Known Types 129 | // optional ListValue array [foo, bar, ...] 130 | // optional Value value Any JSON value. Check google.protobuf.Value for details. 131 | // optional NullValue null JSON null 132 | // optional Empty object {} An empty JSON object 133 | } 134 | -------------------------------------------------------------------------------- /packages/protoscript/src/runtime/well-known-types/source_context.pb.ts: -------------------------------------------------------------------------------- 1 | // THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | // Source: google/protobuf/source_context.proto 3 | /* eslint-disable */ 4 | 5 | import type { ByteSource, PartialDeep } from "protoscript"; 6 | import * as protoscript from "protoscript"; 7 | 8 | //========================================// 9 | // Types // 10 | //========================================// 11 | 12 | /** 13 | * `SourceContext` represents information about the source of a 14 | * protobuf element, like the file in which it is defined. 15 | */ 16 | export interface SourceContext { 17 | /** 18 | * The path-qualified name of the .proto file that contained the associated 19 | * protobuf element. For example: `"google/protobuf/source_context.proto"`. 20 | */ 21 | fileName: string; 22 | } 23 | 24 | //========================================// 25 | // Protobuf Encode / Decode // 26 | //========================================// 27 | 28 | export const SourceContext = { 29 | /** 30 | * Serializes SourceContext to protobuf. 31 | */ 32 | encode: function (msg: PartialDeep): Uint8Array { 33 | return SourceContext._writeMessage( 34 | msg, 35 | new protoscript.BinaryWriter(), 36 | ).getResultBuffer(); 37 | }, 38 | 39 | /** 40 | * Deserializes SourceContext from protobuf. 41 | */ 42 | decode: function (bytes: ByteSource): SourceContext { 43 | return SourceContext._readMessage( 44 | SourceContext.initialize(), 45 | new protoscript.BinaryReader(bytes), 46 | ); 47 | }, 48 | 49 | /** 50 | * Initializes SourceContext with all fields set to their default value. 51 | */ 52 | initialize: function (msg?: Partial): SourceContext { 53 | return { 54 | fileName: "", 55 | ...msg, 56 | }; 57 | }, 58 | 59 | /** 60 | * @private 61 | */ 62 | _writeMessage: function ( 63 | msg: PartialDeep, 64 | writer: protoscript.BinaryWriter, 65 | ): protoscript.BinaryWriter { 66 | if (msg.fileName) { 67 | writer.writeString(1, msg.fileName); 68 | } 69 | return writer; 70 | }, 71 | 72 | /** 73 | * @private 74 | */ 75 | _readMessage: function ( 76 | msg: SourceContext, 77 | reader: protoscript.BinaryReader, 78 | ): SourceContext { 79 | while (reader.nextField()) { 80 | const field = reader.getFieldNumber(); 81 | switch (field) { 82 | case 1: { 83 | msg.fileName = reader.readString(); 84 | break; 85 | } 86 | default: { 87 | reader.skipField(); 88 | break; 89 | } 90 | } 91 | } 92 | return msg; 93 | }, 94 | }; 95 | 96 | //========================================// 97 | // JSON Encode / Decode // 98 | //========================================// 99 | 100 | export const SourceContextJSON = { 101 | /** 102 | * Serializes SourceContext to JSON. 103 | */ 104 | encode: function (msg: PartialDeep): string { 105 | return JSON.stringify(SourceContextJSON._writeMessage(msg)); 106 | }, 107 | 108 | /** 109 | * Deserializes SourceContext from JSON. 110 | */ 111 | decode: function (json: string): SourceContext { 112 | return SourceContextJSON._readMessage( 113 | SourceContextJSON.initialize(), 114 | JSON.parse(json), 115 | ); 116 | }, 117 | 118 | /** 119 | * Initializes SourceContext with all fields set to their default value. 120 | */ 121 | initialize: function (msg?: Partial): SourceContext { 122 | return { 123 | fileName: "", 124 | ...msg, 125 | }; 126 | }, 127 | 128 | /** 129 | * @private 130 | */ 131 | _writeMessage: function ( 132 | msg: PartialDeep, 133 | ): Record { 134 | const json: Record = {}; 135 | if (msg.fileName) { 136 | json["fileName"] = msg.fileName; 137 | } 138 | return json; 139 | }, 140 | 141 | /** 142 | * @private 143 | */ 144 | _readMessage: function (msg: SourceContext, json: any): SourceContext { 145 | const _fileName_ = json["fileName"] ?? json["file_name"]; 146 | if (_fileName_) { 147 | msg.fileName = _fileName_; 148 | } 149 | return msg; 150 | }, 151 | }; 152 | -------------------------------------------------------------------------------- /examples/typescript/src/haberdasher.pb.ts: -------------------------------------------------------------------------------- 1 | // THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | // Source: src/haberdasher.proto 3 | /* eslint-disable */ 4 | 5 | import type { ByteSource, PartialDeep } from "protoscript"; 6 | import * as protoscript from "protoscript"; 7 | 8 | import * as srcSize from "./size.pb"; 9 | 10 | //========================================// 11 | // Types // 12 | //========================================// 13 | 14 | /** 15 | * A Hat is a piece of headwear made by a Haberdasher. 16 | */ 17 | export interface Hat { 18 | size: srcSize.Size; 19 | /** 20 | * anything but "invisible" 21 | */ 22 | color: string; 23 | /** 24 | * i.e. "bowler" 25 | */ 26 | name: string; 27 | } 28 | 29 | //========================================// 30 | // Protobuf Encode / Decode // 31 | //========================================// 32 | 33 | export const Hat = { 34 | /** 35 | * Serializes Hat to protobuf. 36 | */ 37 | encode: function (msg: PartialDeep): Uint8Array { 38 | return Hat._writeMessage( 39 | msg, 40 | new protoscript.BinaryWriter(), 41 | ).getResultBuffer(); 42 | }, 43 | 44 | /** 45 | * Deserializes Hat from protobuf. 46 | */ 47 | decode: function (bytes: ByteSource): Hat { 48 | return Hat._readMessage( 49 | Hat.initialize(), 50 | new protoscript.BinaryReader(bytes), 51 | ); 52 | }, 53 | 54 | /** 55 | * Initializes Hat with all fields set to their default value. 56 | */ 57 | initialize: function (msg?: Partial): Hat { 58 | return { 59 | size: srcSize.Size.initialize(), 60 | color: "", 61 | name: "", 62 | ...msg, 63 | }; 64 | }, 65 | 66 | /** 67 | * @private 68 | */ 69 | _writeMessage: function ( 70 | msg: PartialDeep, 71 | writer: protoscript.BinaryWriter, 72 | ): protoscript.BinaryWriter { 73 | if (msg.size) { 74 | writer.writeMessage(1, msg.size, srcSize.Size._writeMessage); 75 | } 76 | if (msg.color) { 77 | writer.writeString(2, msg.color); 78 | } 79 | if (msg.name) { 80 | writer.writeString(3, msg.name); 81 | } 82 | return writer; 83 | }, 84 | 85 | /** 86 | * @private 87 | */ 88 | _readMessage: function (msg: Hat, reader: protoscript.BinaryReader): Hat { 89 | while (reader.nextField()) { 90 | const field = reader.getFieldNumber(); 91 | switch (field) { 92 | case 1: { 93 | reader.readMessage(msg.size, srcSize.Size._readMessage); 94 | break; 95 | } 96 | case 2: { 97 | msg.color = reader.readString(); 98 | break; 99 | } 100 | case 3: { 101 | msg.name = reader.readString(); 102 | break; 103 | } 104 | default: { 105 | reader.skipField(); 106 | break; 107 | } 108 | } 109 | } 110 | return msg; 111 | }, 112 | }; 113 | 114 | //========================================// 115 | // JSON Encode / Decode // 116 | //========================================// 117 | 118 | export const HatJSON = { 119 | /** 120 | * Serializes Hat to JSON. 121 | */ 122 | encode: function (msg: PartialDeep): string { 123 | return JSON.stringify(HatJSON._writeMessage(msg)); 124 | }, 125 | 126 | /** 127 | * Deserializes Hat from JSON. 128 | */ 129 | decode: function (json: string): Hat { 130 | return HatJSON._readMessage(HatJSON.initialize(), JSON.parse(json)); 131 | }, 132 | 133 | /** 134 | * Initializes Hat with all fields set to their default value. 135 | */ 136 | initialize: function (msg?: Partial): Hat { 137 | return { 138 | size: srcSize.SizeJSON.initialize(), 139 | color: "", 140 | name: "", 141 | ...msg, 142 | }; 143 | }, 144 | 145 | /** 146 | * @private 147 | */ 148 | _writeMessage: function (msg: PartialDeep): Record { 149 | const json: Record = {}; 150 | if (msg.size) { 151 | const _size_ = srcSize.SizeJSON._writeMessage(msg.size); 152 | if (Object.keys(_size_).length > 0) { 153 | json["size"] = _size_; 154 | } 155 | } 156 | if (msg.color) { 157 | json["color"] = msg.color; 158 | } 159 | if (msg.name) { 160 | json["name"] = msg.name; 161 | } 162 | return json; 163 | }, 164 | 165 | /** 166 | * @private 167 | */ 168 | _readMessage: function (msg: Hat, json: any): Hat { 169 | const _size_ = json["size"]; 170 | if (_size_) { 171 | srcSize.SizeJSON._readMessage(msg.size, _size_); 172 | } 173 | const _color_ = json["color"]; 174 | if (_color_) { 175 | msg.color = _color_; 176 | } 177 | const _name_ = json["name"]; 178 | if (_name_) { 179 | msg.name = _name_; 180 | } 181 | return msg; 182 | }, 183 | }; 184 | -------------------------------------------------------------------------------- /examples/closure-compiler/src/haberdasher.pb.js: -------------------------------------------------------------------------------- 1 | // THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | // Source: src/haberdasher.proto 3 | /* eslint-disable */ 4 | 5 | import { BinaryReader, BinaryWriter } from "protoscript"; 6 | 7 | //========================================// 8 | // Protobuf Encode / Decode // 9 | //========================================// 10 | 11 | export const Size = { 12 | /** 13 | * Serializes Size to protobuf. 14 | */ 15 | encode: function (msg) { 16 | return Size._writeMessage(msg, new BinaryWriter()).getResultBuffer(); 17 | }, 18 | 19 | /** 20 | * Deserializes Size from protobuf. 21 | */ 22 | decode: function (bytes) { 23 | return Size._readMessage(Size.initialize(), new BinaryReader(bytes)); 24 | }, 25 | 26 | /** 27 | * Initializes Size with all fields set to their default value. 28 | */ 29 | initialize: function () { 30 | return { 31 | inches: 0, 32 | }; 33 | }, 34 | 35 | /** 36 | * @private 37 | */ 38 | _writeMessage: function (msg, writer) { 39 | if (msg.inches) { 40 | writer.writeInt32(1, msg.inches); 41 | } 42 | return writer; 43 | }, 44 | 45 | /** 46 | * @private 47 | */ 48 | _readMessage: function (msg, reader) { 49 | while (reader.nextField()) { 50 | const field = reader.getFieldNumber(); 51 | switch (field) { 52 | case 1: { 53 | msg.inches = reader.readInt32(); 54 | break; 55 | } 56 | default: { 57 | reader.skipField(); 58 | break; 59 | } 60 | } 61 | } 62 | return msg; 63 | }, 64 | }; 65 | 66 | export const Hat = { 67 | /** 68 | * Serializes Hat to protobuf. 69 | */ 70 | encode: function (msg) { 71 | return Hat._writeMessage(msg, new BinaryWriter()).getResultBuffer(); 72 | }, 73 | 74 | /** 75 | * Deserializes Hat from protobuf. 76 | */ 77 | decode: function (bytes) { 78 | return Hat._readMessage(Hat.initialize(), new BinaryReader(bytes)); 79 | }, 80 | 81 | /** 82 | * Initializes Hat with all fields set to their default value. 83 | */ 84 | initialize: function () { 85 | return { 86 | inches: 0, 87 | color: "", 88 | name: "", 89 | }; 90 | }, 91 | 92 | /** 93 | * @private 94 | */ 95 | _writeMessage: function (msg, writer) { 96 | if (msg.inches) { 97 | writer.writeInt32(1, msg.inches); 98 | } 99 | if (msg.color) { 100 | writer.writeString(2, msg.color); 101 | } 102 | if (msg.name) { 103 | writer.writeString(3, msg.name); 104 | } 105 | return writer; 106 | }, 107 | 108 | /** 109 | * @private 110 | */ 111 | _readMessage: function (msg, reader) { 112 | while (reader.nextField()) { 113 | const field = reader.getFieldNumber(); 114 | switch (field) { 115 | case 1: { 116 | msg.inches = reader.readInt32(); 117 | break; 118 | } 119 | case 2: { 120 | msg.color = reader.readString(); 121 | break; 122 | } 123 | case 3: { 124 | msg.name = reader.readString(); 125 | break; 126 | } 127 | default: { 128 | reader.skipField(); 129 | break; 130 | } 131 | } 132 | } 133 | return msg; 134 | }, 135 | }; 136 | 137 | //========================================// 138 | // JSON Encode / Decode // 139 | //========================================// 140 | 141 | export const SizeJSON = { 142 | /** 143 | * Serializes Size to JSON. 144 | */ 145 | encode: function (msg) { 146 | return JSON.stringify(SizeJSON._writeMessage(msg)); 147 | }, 148 | 149 | /** 150 | * Deserializes Size from JSON. 151 | */ 152 | decode: function (json) { 153 | return SizeJSON._readMessage(SizeJSON.initialize(), JSON.parse(json)); 154 | }, 155 | 156 | /** 157 | * Initializes Size with all fields set to their default value. 158 | */ 159 | initialize: function () { 160 | return { 161 | inches: 0, 162 | }; 163 | }, 164 | 165 | /** 166 | * @private 167 | */ 168 | _writeMessage: function (msg) { 169 | const json = {}; 170 | if (msg.inches) { 171 | json["inches"] = msg.inches; 172 | } 173 | return json; 174 | }, 175 | 176 | /** 177 | * @private 178 | */ 179 | _readMessage: function (msg, json) { 180 | const _inches_ = json["inches"]; 181 | if (_inches_) { 182 | msg.inches = _inches_; 183 | } 184 | return msg; 185 | }, 186 | }; 187 | 188 | export const HatJSON = { 189 | /** 190 | * Serializes Hat to JSON. 191 | */ 192 | encode: function (msg) { 193 | return JSON.stringify(HatJSON._writeMessage(msg)); 194 | }, 195 | 196 | /** 197 | * Deserializes Hat from JSON. 198 | */ 199 | decode: function (json) { 200 | return HatJSON._readMessage(HatJSON.initialize(), JSON.parse(json)); 201 | }, 202 | 203 | /** 204 | * Initializes Hat with all fields set to their default value. 205 | */ 206 | initialize: function () { 207 | return { 208 | inches: 0, 209 | color: "", 210 | name: "", 211 | }; 212 | }, 213 | 214 | /** 215 | * @private 216 | */ 217 | _writeMessage: function (msg) { 218 | const json = {}; 219 | if (msg.inches) { 220 | json["inches"] = msg.inches; 221 | } 222 | if (msg.color) { 223 | json["color"] = msg.color; 224 | } 225 | if (msg.name) { 226 | json["name"] = msg.name; 227 | } 228 | return json; 229 | }, 230 | 231 | /** 232 | * @private 233 | */ 234 | _readMessage: function (msg, json) { 235 | const _inches_ = json["inches"]; 236 | if (_inches_) { 237 | msg.inches = _inches_; 238 | } 239 | const _color_ = json["color"]; 240 | if (_color_) { 241 | msg.color = _color_; 242 | } 243 | const _name_ = json["name"]; 244 | if (_name_) { 245 | msg.name = _name_; 246 | } 247 | return msg; 248 | }, 249 | }; 250 | -------------------------------------------------------------------------------- /e2e/conformance/runner.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { 4 | ConformanceRequest, 5 | ConformanceResponse, 6 | FailureSet, 7 | WireFormat, 8 | } from "./proto/conformance/conformance.pb.js"; 9 | import { 10 | TestAllTypesProto3, 11 | TestAllTypesProto3JSON, 12 | } from "./proto/google/protobuf/test_messages_proto3.pb.js"; 13 | import { readSync, writeSync } from "node:fs"; 14 | 15 | function main() { 16 | let testCount = 0; 17 | try { 18 | while (testIo(test)) { 19 | testCount += 1; 20 | } 21 | } catch (e) { 22 | process.stderr.write( 23 | `conformance.ts: exiting after ${testCount} tests: ${String(e)}`, 24 | ); 25 | process.exit(1); 26 | } 27 | } 28 | 29 | function test(request: ConformanceRequest): ConformanceResponse { 30 | switch (request.messageType) { 31 | // The conformance runner will request a list of failures as the first request. 32 | // This will be known by message_type == "conformance.FailureSet", a conformance 33 | // test should return a serialized FailureSet in protobuf_payload. 34 | case "conformance.FailureSet": { 35 | return { 36 | protobufPayload: FailureSet.encode({}), 37 | }; 38 | } 39 | case "protobuf_test_messages.proto3.TestAllTypesProto3": { 40 | break; 41 | } 42 | case "protobuf_test_messages.proto2.TestAllTypesProto2": { 43 | return { 44 | runtimeError: 45 | "Protobuf2 Tests are skipped because TwirpScript targets Protobuf3", 46 | }; 47 | } 48 | default: { 49 | return { 50 | runtimeError: `unknown request message type ${request.messageType}`, 51 | }; 52 | } 53 | } 54 | 55 | let testMessage: TestAllTypesProto3; 56 | try { 57 | if (request.protobufPayload) { 58 | testMessage = TestAllTypesProto3.decode(request.protobufPayload); 59 | } else if (request.jsonPayload) { 60 | testMessage = TestAllTypesProto3JSON.decode(request.jsonPayload); 61 | } else { 62 | return { runtimeError: "request not supported" }; 63 | } 64 | } catch (err) { 65 | // > This string should be set to indicate parsing failed. The string can 66 | // > provide more information about the parse error if it is available. 67 | // > 68 | // > Setting this string does not necessarily mean the testee failed the 69 | // > test. Some of the test cases are intentionally invalid input. 70 | return { parseError: String(err) }; 71 | } 72 | 73 | try { 74 | switch (request.requestedOutputFormat) { 75 | case WireFormat.PROTOBUF: { 76 | return { protobufPayload: TestAllTypesProto3.encode(testMessage) }; 77 | } 78 | case WireFormat.JSON: { 79 | return { jsonPayload: TestAllTypesProto3JSON.encode(testMessage) }; 80 | } 81 | case WireFormat.JSPB: { 82 | return { skipped: "JSPB not supported." }; 83 | } 84 | case WireFormat.TEXT_FORMAT: { 85 | return { skipped: "Text format not supported." }; 86 | } 87 | default: { 88 | return { 89 | runtimeError: `unknown requested output format ${request.requestedOutputFormat}`, 90 | }; 91 | } 92 | } 93 | } catch (err) { 94 | // > If the input was successfully parsed but errors occurred when 95 | // > serializing it to the requested output format, set the error message in 96 | // > this field. 97 | return { serializeError: String(err) }; 98 | } 99 | } 100 | 101 | // Returns true if the test ran successfully, false on legitimate EOF. 102 | // If EOF is encountered in an unexpected place, raises IOError. 103 | function testIo( 104 | test: (request: ConformanceRequest) => ConformanceResponse, 105 | ): boolean { 106 | setBlockingStdout(); 107 | 108 | const requestLengthBuf = readBuffer(4); 109 | if (requestLengthBuf === "EOF") { 110 | return false; 111 | } 112 | 113 | const requestLength = requestLengthBuf.readInt32LE(0); 114 | const serializedRequest = readBuffer(requestLength); 115 | if (serializedRequest === "EOF") { 116 | throw "Failed to read request."; 117 | } 118 | 119 | const request = ConformanceRequest.decode(serializedRequest); 120 | const response = test(request); 121 | 122 | const serializedResponse = ConformanceResponse.encode(response); 123 | const responseLengthBuf = Buffer.alloc(4); 124 | responseLengthBuf.writeInt32LE(serializedResponse.length, 0); 125 | writeBuffer(responseLengthBuf); 126 | writeBuffer(Buffer.from(serializedResponse)); 127 | 128 | return true; 129 | } 130 | 131 | // Read a buffer of N bytes from stdin. 132 | function readBuffer(bytes: number): Buffer | "EOF" { 133 | const buf = Buffer.alloc(bytes); 134 | let read = 0; 135 | try { 136 | read = readSync(0, buf, 0, bytes, null); 137 | } catch (e) { 138 | throw `failed to read from stdin: ${String(e)}`; 139 | } 140 | if (read !== bytes) { 141 | if (read === 0) { 142 | return "EOF"; 143 | } 144 | throw "premature EOF on stdin."; 145 | } 146 | return buf; 147 | } 148 | 149 | // Write a buffer to stdout. 150 | function writeBuffer(buffer: Buffer): void { 151 | let totalWritten = 0; 152 | while (totalWritten < buffer.length) { 153 | totalWritten += writeSync( 154 | 1, 155 | buffer, 156 | totalWritten, 157 | buffer.length - totalWritten, 158 | ); 159 | } 160 | } 161 | 162 | // Fixes https://github.com/timostamm/protobuf-ts/issues/134 163 | // Node is buffering chunks to stdout, meaning that for big generated 164 | // files the CodeGeneratorResponse will not reach protoc completely. 165 | // To fix this, we set stdout to block using the internal private 166 | // method setBlocking(true) 167 | function setBlockingStdout(): void { 168 | // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-assignment 169 | const stdoutHandle = (process.stdout as any)._handle; 170 | if (stdoutHandle !== undefined) { 171 | // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access 172 | stdoutHandle.setBlocking(true); 173 | } 174 | } 175 | 176 | main(); 177 | -------------------------------------------------------------------------------- /examples/protoc/src/haberdasher.pb.js: -------------------------------------------------------------------------------- 1 | // THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | // Source: src/haberdasher.proto 3 | /* eslint-disable */ 4 | 5 | import * as protoscript from "protoscript"; 6 | 7 | //========================================// 8 | // Protobuf Encode / Decode // 9 | //========================================// 10 | 11 | export const Size = { 12 | /** 13 | * Serializes Size to protobuf. 14 | */ 15 | encode: function (msg) { 16 | return Size._writeMessage( 17 | msg, 18 | new protoscript.BinaryWriter(), 19 | ).getResultBuffer(); 20 | }, 21 | 22 | /** 23 | * Deserializes Size from protobuf. 24 | */ 25 | decode: function (bytes) { 26 | return Size._readMessage( 27 | Size.initialize(), 28 | new protoscript.BinaryReader(bytes), 29 | ); 30 | }, 31 | 32 | /** 33 | * Initializes Size with all fields set to their default value. 34 | */ 35 | initialize: function (msg) { 36 | return { 37 | inches: 0, 38 | ...msg, 39 | }; 40 | }, 41 | 42 | /** 43 | * @private 44 | */ 45 | _writeMessage: function (msg, writer) { 46 | if (msg.inches) { 47 | writer.writeInt32(1, msg.inches); 48 | } 49 | return writer; 50 | }, 51 | 52 | /** 53 | * @private 54 | */ 55 | _readMessage: function (msg, reader) { 56 | while (reader.nextField()) { 57 | const field = reader.getFieldNumber(); 58 | switch (field) { 59 | case 1: { 60 | msg.inches = reader.readInt32(); 61 | break; 62 | } 63 | default: { 64 | reader.skipField(); 65 | break; 66 | } 67 | } 68 | } 69 | return msg; 70 | }, 71 | }; 72 | 73 | export const Hat = { 74 | /** 75 | * Serializes Hat to protobuf. 76 | */ 77 | encode: function (msg) { 78 | return Hat._writeMessage( 79 | msg, 80 | new protoscript.BinaryWriter(), 81 | ).getResultBuffer(); 82 | }, 83 | 84 | /** 85 | * Deserializes Hat from protobuf. 86 | */ 87 | decode: function (bytes) { 88 | return Hat._readMessage( 89 | Hat.initialize(), 90 | new protoscript.BinaryReader(bytes), 91 | ); 92 | }, 93 | 94 | /** 95 | * Initializes Hat with all fields set to their default value. 96 | */ 97 | initialize: function (msg) { 98 | return { 99 | inches: 0, 100 | color: "", 101 | name: "", 102 | ...msg, 103 | }; 104 | }, 105 | 106 | /** 107 | * @private 108 | */ 109 | _writeMessage: function (msg, writer) { 110 | if (msg.inches) { 111 | writer.writeInt32(1, msg.inches); 112 | } 113 | if (msg.color) { 114 | writer.writeString(2, msg.color); 115 | } 116 | if (msg.name) { 117 | writer.writeString(3, msg.name); 118 | } 119 | return writer; 120 | }, 121 | 122 | /** 123 | * @private 124 | */ 125 | _readMessage: function (msg, reader) { 126 | while (reader.nextField()) { 127 | const field = reader.getFieldNumber(); 128 | switch (field) { 129 | case 1: { 130 | msg.inches = reader.readInt32(); 131 | break; 132 | } 133 | case 2: { 134 | msg.color = reader.readString(); 135 | break; 136 | } 137 | case 3: { 138 | msg.name = reader.readString(); 139 | break; 140 | } 141 | default: { 142 | reader.skipField(); 143 | break; 144 | } 145 | } 146 | } 147 | return msg; 148 | }, 149 | }; 150 | 151 | //========================================// 152 | // JSON Encode / Decode // 153 | //========================================// 154 | 155 | export const SizeJSON = { 156 | /** 157 | * Serializes Size to JSON. 158 | */ 159 | encode: function (msg) { 160 | return JSON.stringify(SizeJSON._writeMessage(msg)); 161 | }, 162 | 163 | /** 164 | * Deserializes Size from JSON. 165 | */ 166 | decode: function (json) { 167 | return SizeJSON._readMessage(SizeJSON.initialize(), JSON.parse(json)); 168 | }, 169 | 170 | /** 171 | * Initializes Size with all fields set to their default value. 172 | */ 173 | initialize: function (msg) { 174 | return { 175 | inches: 0, 176 | ...msg, 177 | }; 178 | }, 179 | 180 | /** 181 | * @private 182 | */ 183 | _writeMessage: function (msg) { 184 | const json = {}; 185 | if (msg.inches) { 186 | json["inches"] = msg.inches; 187 | } 188 | return json; 189 | }, 190 | 191 | /** 192 | * @private 193 | */ 194 | _readMessage: function (msg, json) { 195 | const _inches_ = json["inches"]; 196 | if (_inches_) { 197 | msg.inches = protoscript.parseNumber(_inches_); 198 | } 199 | return msg; 200 | }, 201 | }; 202 | 203 | export const HatJSON = { 204 | /** 205 | * Serializes Hat to JSON. 206 | */ 207 | encode: function (msg) { 208 | return JSON.stringify(HatJSON._writeMessage(msg)); 209 | }, 210 | 211 | /** 212 | * Deserializes Hat from JSON. 213 | */ 214 | decode: function (json) { 215 | return HatJSON._readMessage(HatJSON.initialize(), JSON.parse(json)); 216 | }, 217 | 218 | /** 219 | * Initializes Hat with all fields set to their default value. 220 | */ 221 | initialize: function (msg) { 222 | return { 223 | inches: 0, 224 | color: "", 225 | name: "", 226 | ...msg, 227 | }; 228 | }, 229 | 230 | /** 231 | * @private 232 | */ 233 | _writeMessage: function (msg) { 234 | const json = {}; 235 | if (msg.inches) { 236 | json["inches"] = msg.inches; 237 | } 238 | if (msg.color) { 239 | json["color"] = msg.color; 240 | } 241 | if (msg.name) { 242 | json["name"] = msg.name; 243 | } 244 | return json; 245 | }, 246 | 247 | /** 248 | * @private 249 | */ 250 | _readMessage: function (msg, json) { 251 | const _inches_ = json["inches"]; 252 | if (_inches_) { 253 | msg.inches = protoscript.parseNumber(_inches_); 254 | } 255 | const _color_ = json["color"]; 256 | if (_color_) { 257 | msg.color = _color_; 258 | } 259 | const _name_ = json["name"]; 260 | if (_name_) { 261 | msg.name = _name_; 262 | } 263 | return msg; 264 | }, 265 | }; 266 | -------------------------------------------------------------------------------- /examples/javascript/src/haberdasher.pb.js: -------------------------------------------------------------------------------- 1 | // THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | // Source: src/haberdasher.proto 3 | /* eslint-disable */ 4 | 5 | import * as protoscript from "protoscript"; 6 | 7 | //========================================// 8 | // Protobuf Encode / Decode // 9 | //========================================// 10 | 11 | export const Size = { 12 | /** 13 | * Serializes Size to protobuf. 14 | */ 15 | encode: function (msg) { 16 | return Size._writeMessage( 17 | msg, 18 | new protoscript.BinaryWriter(), 19 | ).getResultBuffer(); 20 | }, 21 | 22 | /** 23 | * Deserializes Size from protobuf. 24 | */ 25 | decode: function (bytes) { 26 | return Size._readMessage( 27 | Size.initialize(), 28 | new protoscript.BinaryReader(bytes), 29 | ); 30 | }, 31 | 32 | /** 33 | * Initializes Size with all fields set to their default value. 34 | */ 35 | initialize: function (msg) { 36 | return { 37 | inches: 0, 38 | ...msg, 39 | }; 40 | }, 41 | 42 | /** 43 | * @private 44 | */ 45 | _writeMessage: function (msg, writer) { 46 | if (msg.inches) { 47 | writer.writeInt32(1, msg.inches); 48 | } 49 | return writer; 50 | }, 51 | 52 | /** 53 | * @private 54 | */ 55 | _readMessage: function (msg, reader) { 56 | while (reader.nextField()) { 57 | const field = reader.getFieldNumber(); 58 | switch (field) { 59 | case 1: { 60 | msg.inches = reader.readInt32(); 61 | break; 62 | } 63 | default: { 64 | reader.skipField(); 65 | break; 66 | } 67 | } 68 | } 69 | return msg; 70 | }, 71 | }; 72 | 73 | export const Hat = { 74 | /** 75 | * Serializes Hat to protobuf. 76 | */ 77 | encode: function (msg) { 78 | return Hat._writeMessage( 79 | msg, 80 | new protoscript.BinaryWriter(), 81 | ).getResultBuffer(); 82 | }, 83 | 84 | /** 85 | * Deserializes Hat from protobuf. 86 | */ 87 | decode: function (bytes) { 88 | return Hat._readMessage( 89 | Hat.initialize(), 90 | new protoscript.BinaryReader(bytes), 91 | ); 92 | }, 93 | 94 | /** 95 | * Initializes Hat with all fields set to their default value. 96 | */ 97 | initialize: function (msg) { 98 | return { 99 | inches: 0, 100 | color: "", 101 | name: "", 102 | ...msg, 103 | }; 104 | }, 105 | 106 | /** 107 | * @private 108 | */ 109 | _writeMessage: function (msg, writer) { 110 | if (msg.inches) { 111 | writer.writeInt32(1, msg.inches); 112 | } 113 | if (msg.color) { 114 | writer.writeString(2, msg.color); 115 | } 116 | if (msg.name) { 117 | writer.writeString(3, msg.name); 118 | } 119 | return writer; 120 | }, 121 | 122 | /** 123 | * @private 124 | */ 125 | _readMessage: function (msg, reader) { 126 | while (reader.nextField()) { 127 | const field = reader.getFieldNumber(); 128 | switch (field) { 129 | case 1: { 130 | msg.inches = reader.readInt32(); 131 | break; 132 | } 133 | case 2: { 134 | msg.color = reader.readString(); 135 | break; 136 | } 137 | case 3: { 138 | msg.name = reader.readString(); 139 | break; 140 | } 141 | default: { 142 | reader.skipField(); 143 | break; 144 | } 145 | } 146 | } 147 | return msg; 148 | }, 149 | }; 150 | 151 | //========================================// 152 | // JSON Encode / Decode // 153 | //========================================// 154 | 155 | export const SizeJSON = { 156 | /** 157 | * Serializes Size to JSON. 158 | */ 159 | encode: function (msg) { 160 | return JSON.stringify(SizeJSON._writeMessage(msg)); 161 | }, 162 | 163 | /** 164 | * Deserializes Size from JSON. 165 | */ 166 | decode: function (json) { 167 | return SizeJSON._readMessage(SizeJSON.initialize(), JSON.parse(json)); 168 | }, 169 | 170 | /** 171 | * Initializes Size with all fields set to their default value. 172 | */ 173 | initialize: function (msg) { 174 | return { 175 | inches: 0, 176 | ...msg, 177 | }; 178 | }, 179 | 180 | /** 181 | * @private 182 | */ 183 | _writeMessage: function (msg) { 184 | const json = {}; 185 | if (msg.inches) { 186 | json["inches"] = msg.inches; 187 | } 188 | return json; 189 | }, 190 | 191 | /** 192 | * @private 193 | */ 194 | _readMessage: function (msg, json) { 195 | const _inches_ = json["inches"]; 196 | if (_inches_) { 197 | msg.inches = protoscript.parseNumber(_inches_); 198 | } 199 | return msg; 200 | }, 201 | }; 202 | 203 | export const HatJSON = { 204 | /** 205 | * Serializes Hat to JSON. 206 | */ 207 | encode: function (msg) { 208 | return JSON.stringify(HatJSON._writeMessage(msg)); 209 | }, 210 | 211 | /** 212 | * Deserializes Hat from JSON. 213 | */ 214 | decode: function (json) { 215 | return HatJSON._readMessage(HatJSON.initialize(), JSON.parse(json)); 216 | }, 217 | 218 | /** 219 | * Initializes Hat with all fields set to their default value. 220 | */ 221 | initialize: function (msg) { 222 | return { 223 | inches: 0, 224 | color: "", 225 | name: "", 226 | ...msg, 227 | }; 228 | }, 229 | 230 | /** 231 | * @private 232 | */ 233 | _writeMessage: function (msg) { 234 | const json = {}; 235 | if (msg.inches) { 236 | json["inches"] = msg.inches; 237 | } 238 | if (msg.color) { 239 | json["color"] = msg.color; 240 | } 241 | if (msg.name) { 242 | json["name"] = msg.name; 243 | } 244 | return json; 245 | }, 246 | 247 | /** 248 | * @private 249 | */ 250 | _readMessage: function (msg, json) { 251 | const _inches_ = json["inches"]; 252 | if (_inches_) { 253 | msg.inches = protoscript.parseNumber(_inches_); 254 | } 255 | const _color_ = json["color"]; 256 | if (_color_) { 257 | msg.color = _color_; 258 | } 259 | const _name_ = json["name"]; 260 | if (_name_) { 261 | msg.name = _name_; 262 | } 263 | return msg; 264 | }, 265 | }; 266 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v0.0.23 4 | 5 | - Remove hardcoded compiler path on Windows to support monorepos. Thanks @lordvlad! 6 | 7 | ## v0.0.22 8 | 9 | - Fix Timestamp and Duration JSON serialization. Previously, when either seconds or nanos were 0, the Timestamp / Duration was omitted from the serialized json. Thanks @martynchamberlin! 10 | 11 | ## v0.0.21 12 | 13 | - Fix compiler path on Windows. 14 | 15 | ## v0.0.20 16 | 17 | - Preserve protoscript import when well known types are imported. This corrects a regression in 0.0.19. 18 | 19 | ## v0.0.19 20 | 21 | - Fix JSON serializtion for Timestamp and Duration well known types. See [#39](https://github.com/tatethurston/ProtoScript/issues/39). 22 | - Accept all value permutations as described by the Proto3 JSON spec when parsing JSON messages. 23 | - #initialize now accepts partial messages. This enables you to create a full message with select fields set to a user-provided value: 24 | ```ts 25 | const ron = User.initialize({ firstName: "Ron" }); 26 | ``` 27 | 28 | ## v0.0.18 29 | 30 | - Fix JSON deserializtion of recursive (self referencing) messages. 31 | - Fix generated TypeScript types for repeated recursive (self referencing) messages. 32 | 33 | ## v0.0.17 34 | 35 | - Omit `Uint8Array` from `PartialDeep` type. This fixes a type error for TypeScript users that use `bytes`. 36 | 37 | ## v0.0.16 38 | 39 | - `encode` methods now accept partials for nested messages as well (`PartialDeep` instead of `Partial`). Previously, the types required that full messages were provided for any nested messages. 40 | - Buf users will need to update their `buf.gen.yaml` path: 41 | `buf.gen.yaml` 42 | 43 | ```diff 44 | version: v1 45 | plugins: 46 | - name: protoc-gen-protoscript 47 | - path: ./node_modules/protoscript/compiler.js 48 | + path: ./node_modules/protoscript/dist/compiler.js 49 | out: . 50 | opt: 51 | - language=typescript 52 | strategy: all 53 | ``` 54 | 55 | ## v0.0.15 56 | 57 | This release includes a number of bug fixes 58 | 59 | - Fix treeshaking for nested messages. Previously, there were cases where protobuf did not tree shake out of JSON only client usage. Thanks @noahseger! 60 | - Fix camelcasing for fieldnames with multiple sequential underscores. Thanks @noahseger! 61 | - Fix generated toInt helper when using aliased enums. Thanks @noahseger! 62 | - Fix recursive message initialization. Previously, recursive messages, messages with fields that referenced themselves, would cause an infinite loop when initializing because protoscript eagerly instantiates message objects. Now the compiler detects cycles and will instead generate up until the cycle, and mark the recursive field as optional. 63 | 64 | ## v0.0.14 65 | 66 | - Fix intermittent EAGAIN issue encountered when compiling protos 67 | 68 | - Use glob imports for generated messages instead of destructuring. This preserves tree shaking, but preserves module namespacing to disambiguate name collisions between protos. Previously, identically named messages in different modules could causes a name collision, eg: 69 | 70 | ```proto 71 | // foo.proto 72 | message Foo {} 73 | ``` 74 | 75 | ```proto 76 | // bar.proto 77 | import "foo.proto"; 78 | message Foo {} 79 | ``` 80 | 81 | Would result in errors in the generated code. Now, this is namespaced and works correctly. 82 | 83 | ## v0.0.13 84 | 85 | Update package [Protocol Buffers Well-Known Types](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf) to enable strict ESM. 86 | 87 | ## v0.0.12 88 | 89 | [Protocol Buffers Well-Known Types](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf) are now exported from `protoscript`. References to well-known types are now imported from `protoscript` rather than being generated. This is a non breaking change. If you have well-known types in your project, you can remove the `google/protobuf` directory that was generated in previous versions alongside your other `.pb.js/ts` files. 90 | 91 | The output location of `google/protobuf` was a common reason for using `dest` in `proto.config.mjs` so this change should facilitate zero configuration for a greater number of projects. 92 | 93 | ## v0.0.11 94 | 95 | - Revert `Include file extensions in generated file imports` introduced in `v0.0.7` for TypeScript users. Generated TypeScript imports will revert to the following: 96 | 97 | ```diff 98 | - import { Foo } from './foo.pb.js'; 99 | + import { Foo } from './foo.pb'; 100 | ``` 101 | 102 | When targeting ESM, the TypeScript compiler expects `.js` extensions and not `.ts` extensions for imports because the compiler does not manipulate import paths: https://www.typescriptlang.org/docs/handbook/esm-node.html. 103 | 104 | Including a full extension results in the following TypeScript error: 105 | 106 | ``` 107 | [tsserver 2691] [E] An import path cannot end with a '.ts' extension. 108 | ``` 109 | 110 | The TypeScript team's recommendation to use `.js` extensions for `.ts` file imports when targeting ESM causes a number of issues with the broader JavaScript ecosystem. Until this situation is rectified, ProtoScript will not emit ESM compliant extensions for TypeScript. This only impacts TypeScript users who wish to target ESM in Node.JS using the TypeScript compiler, as bundlers are not pedantic about file extensions. If you're impacted by this, please join the discussion in [#202](https://github.com/tatethurston/TwirpScript/issues/202.) 111 | 112 | ## v0.0.10 113 | 114 | - Change configuration file format. Now, the configuration file is JS instead of JSON. This provides better discoverability and type checking for TypeScript users. 115 | 116 | The following `.protoscript.json`: 117 | 118 | ```json 119 | { 120 | "root": "src", 121 | }; 122 | ``` 123 | 124 | Would be renamed to `proto.config.mjs` and changed to the following: 125 | 126 | ```js 127 | /** @type {import('protoscript').Config} */ 128 | export default { 129 | root: "src", 130 | }; 131 | ``` 132 | 133 | - Use relative file path for determining path to compiler instead of hard coding from project root. This should interop better with more exotic package tooling and repo setup. 134 | 135 | - Fix: Improved `map` detection. Previously field types suffixed with `Entry` were incorrectly flagged as maps. This has been fixed. 136 | 137 | ## v0.0.9 138 | 139 | - Remove `process.stdin.fd` usage to see if it resolves intermittent `Error: EAGAIN: resource temporarily unavailable, read`. See [#191](https://github.com/tatethurston/TwirpScript/issues/191) for more context. 140 | 141 | ## v0.0.8 142 | 143 | - Use ["property"] access for JSON objects. This ensures generated JSON serialization code is correct when using minification tools that perform property mangling. See #4 for more context. 144 | 145 | ## v0.0.7 146 | 147 | - Include file extensions in generated file imports. 148 | 149 | ## v0.0.6 150 | 151 | - Distribute strict ESM. A CommonJS is runtime is included for legacy node clients. Code generation uses ESM and requires Node.js v14 or later. 152 | -------------------------------------------------------------------------------- /packages/protoscript/src/runtime/well-known-types/duration.pb.ts: -------------------------------------------------------------------------------- 1 | // THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | // Source: google/protobuf/duration.proto 3 | /* eslint-disable */ 4 | 5 | import type { ByteSource, PartialDeep } from "protoscript"; 6 | import * as protoscript from "protoscript"; 7 | 8 | //========================================// 9 | // Types // 10 | //========================================// 11 | 12 | /** 13 | * A Duration represents a signed, fixed-length span of time represented 14 | * as a count of seconds and fractions of seconds at nanosecond 15 | * resolution. It is independent of any calendar and concepts like "day" 16 | * or "month". It is related to Timestamp in that the difference between 17 | * two Timestamp values is a Duration and it can be added or subtracted 18 | * from a Timestamp. Range is approximately +-10,000 years. 19 | * 20 | * # Examples 21 | * 22 | * Example 1: Compute Duration from two Timestamps in pseudo code. 23 | * 24 | * Timestamp start = ...; 25 | * Timestamp end = ...; 26 | * Duration duration = ...; 27 | * 28 | * duration.seconds = end.seconds - start.seconds; 29 | * duration.nanos = end.nanos - start.nanos; 30 | * 31 | * if (duration.seconds < 0 && duration.nanos > 0) { 32 | * duration.seconds += 1; 33 | * duration.nanos -= 1000000000; 34 | * } else if (duration.seconds > 0 && duration.nanos < 0) { 35 | * duration.seconds -= 1; 36 | * duration.nanos += 1000000000; 37 | * } 38 | * 39 | * Example 2: Compute Timestamp from Timestamp + Duration in pseudo code. 40 | * 41 | * Timestamp start = ...; 42 | * Duration duration = ...; 43 | * Timestamp end = ...; 44 | * 45 | * end.seconds = start.seconds + duration.seconds; 46 | * end.nanos = start.nanos + duration.nanos; 47 | * 48 | * if (end.nanos < 0) { 49 | * end.seconds -= 1; 50 | * end.nanos += 1000000000; 51 | * } else if (end.nanos >= 1000000000) { 52 | * end.seconds += 1; 53 | * end.nanos -= 1000000000; 54 | * } 55 | * 56 | * Example 3: Compute Duration from datetime.timedelta in Python. 57 | * 58 | * td = datetime.timedelta(days=3, minutes=10) 59 | * duration = Duration() 60 | * duration.FromTimedelta(td) 61 | * 62 | * # JSON Mapping 63 | * 64 | * In JSON format, the Duration type is encoded as a string rather than an 65 | * object, where the string ends in the suffix "s" (indicating seconds) and 66 | * is preceded by the number of seconds, with nanoseconds expressed as 67 | * fractional seconds. For example, 3 seconds with 0 nanoseconds should be 68 | * encoded in JSON format as "3s", while 3 seconds and 1 nanosecond should 69 | * be expressed in JSON format as "3.000000001s", and 3 seconds and 1 70 | * microsecond should be expressed in JSON format as "3.000001s". 71 | * 72 | */ 73 | export interface Duration { 74 | /** 75 | * Signed seconds of the span of time. Must be from -315,576,000,000 76 | * to +315,576,000,000 inclusive. Note: these bounds are computed from: 77 | * 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years 78 | */ 79 | seconds: bigint; 80 | /** 81 | * Signed fractions of a second at nanosecond resolution of the span 82 | * of time. Durations less than one second are represented with a 0 83 | * `seconds` field and a positive or negative `nanos` field. For durations 84 | * of one second or more, a non-zero value for the `nanos` field must be 85 | * of the same sign as the `seconds` field. Must be from -999,999,999 86 | * to +999,999,999 inclusive. 87 | */ 88 | nanos: number; 89 | } 90 | 91 | //========================================// 92 | // Protobuf Encode / Decode // 93 | //========================================// 94 | 95 | export const Duration = { 96 | /** 97 | * Serializes Duration to protobuf. 98 | */ 99 | encode: function (msg: PartialDeep): Uint8Array { 100 | return Duration._writeMessage( 101 | msg, 102 | new protoscript.BinaryWriter(), 103 | ).getResultBuffer(); 104 | }, 105 | 106 | /** 107 | * Deserializes Duration from protobuf. 108 | */ 109 | decode: function (bytes: ByteSource): Duration { 110 | return Duration._readMessage( 111 | Duration.initialize(), 112 | new protoscript.BinaryReader(bytes), 113 | ); 114 | }, 115 | 116 | /** 117 | * Initializes Duration with all fields set to their default value. 118 | */ 119 | initialize: function (msg?: Partial): Duration { 120 | return { 121 | seconds: 0n, 122 | nanos: 0, 123 | ...msg, 124 | }; 125 | }, 126 | 127 | /** 128 | * @private 129 | */ 130 | _writeMessage: function ( 131 | msg: PartialDeep, 132 | writer: protoscript.BinaryWriter, 133 | ): protoscript.BinaryWriter { 134 | if (msg.seconds) { 135 | writer.writeInt64String(1, msg.seconds.toString() as any); 136 | } 137 | if (msg.nanos) { 138 | writer.writeInt32(2, msg.nanos); 139 | } 140 | return writer; 141 | }, 142 | 143 | /** 144 | * @private 145 | */ 146 | _readMessage: function ( 147 | msg: Duration, 148 | reader: protoscript.BinaryReader, 149 | ): Duration { 150 | while (reader.nextField()) { 151 | const field = reader.getFieldNumber(); 152 | switch (field) { 153 | case 1: { 154 | msg.seconds = BigInt(reader.readInt64String()); 155 | break; 156 | } 157 | case 2: { 158 | msg.nanos = reader.readInt32(); 159 | break; 160 | } 161 | default: { 162 | reader.skipField(); 163 | break; 164 | } 165 | } 166 | } 167 | return msg; 168 | }, 169 | }; 170 | 171 | //========================================// 172 | // JSON Encode / Decode // 173 | //========================================// 174 | 175 | export const DurationJSON = { 176 | /** 177 | * Serializes Duration to JSON. 178 | */ 179 | encode: function (msg: PartialDeep): string { 180 | return JSON.stringify(DurationJSON._writeMessage(msg)); 181 | }, 182 | 183 | /** 184 | * Deserializes Duration from JSON. 185 | */ 186 | decode: function (json: string): Duration { 187 | return DurationJSON._readMessage( 188 | DurationJSON.initialize(), 189 | JSON.parse(json), 190 | ); 191 | }, 192 | 193 | /** 194 | * Initializes Duration with all fields set to their default value. 195 | */ 196 | initialize: function (msg?: Partial): Duration { 197 | return { 198 | seconds: 0n, 199 | nanos: 0, 200 | ...msg, 201 | }; 202 | }, 203 | 204 | /** 205 | * @private 206 | */ 207 | _writeMessage: function ( 208 | msg: PartialDeep, 209 | ): Record { 210 | const json: Record = {}; 211 | if (msg.seconds) { 212 | json["seconds"] = String(msg.seconds); 213 | } 214 | if (msg.nanos) { 215 | json["nanos"] = msg.nanos; 216 | } 217 | return json; 218 | }, 219 | 220 | /** 221 | * @private 222 | */ 223 | _readMessage: function (msg: Duration, json: any): Duration { 224 | const _seconds_ = json["seconds"]; 225 | if (_seconds_) { 226 | msg.seconds = BigInt(_seconds_); 227 | } 228 | const _nanos_ = json["nanos"]; 229 | if (_nanos_) { 230 | msg.nanos = protoscript.parseNumber(_nanos_); 231 | } 232 | return msg; 233 | }, 234 | }; 235 | -------------------------------------------------------------------------------- /examples/closure-compiler/src/haberdasher.pb.ts: -------------------------------------------------------------------------------- 1 | // THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | // Source: src/haberdasher.proto 3 | /* eslint-disable */ 4 | 5 | import type { ByteSource, PartialDeep } from "protoscript"; 6 | import * as protoscript from "protoscript"; 7 | 8 | //========================================// 9 | // Types // 10 | //========================================// 11 | 12 | /** 13 | * Size of a Hat, in inches. 14 | */ 15 | export interface Size { 16 | /** 17 | * must be > 0 18 | */ 19 | inches: number; 20 | } 21 | 22 | /** 23 | * A Hat is a piece of headwear made by a Haberdasher. 24 | */ 25 | export interface Hat { 26 | inches: number; 27 | /** 28 | * anything but "invisible" 29 | */ 30 | color: string; 31 | /** 32 | * i.e. "bowler" 33 | */ 34 | name: string; 35 | } 36 | 37 | //========================================// 38 | // Protobuf Encode / Decode // 39 | //========================================// 40 | 41 | export const Size = { 42 | /** 43 | * Serializes Size to protobuf. 44 | */ 45 | encode: function (msg: PartialDeep): Uint8Array { 46 | return Size._writeMessage( 47 | msg, 48 | new protoscript.BinaryWriter(), 49 | ).getResultBuffer(); 50 | }, 51 | 52 | /** 53 | * Deserializes Size from protobuf. 54 | */ 55 | decode: function (bytes: ByteSource): Size { 56 | return Size._readMessage( 57 | Size.initialize(), 58 | new protoscript.BinaryReader(bytes), 59 | ); 60 | }, 61 | 62 | /** 63 | * Initializes Size with all fields set to their default value. 64 | */ 65 | initialize: function (msg?: Partial): Size { 66 | return { 67 | inches: 0, 68 | ...msg, 69 | }; 70 | }, 71 | 72 | /** 73 | * @private 74 | */ 75 | _writeMessage: function ( 76 | msg: PartialDeep, 77 | writer: protoscript.BinaryWriter, 78 | ): protoscript.BinaryWriter { 79 | if (msg.inches) { 80 | writer.writeInt32(1, msg.inches); 81 | } 82 | return writer; 83 | }, 84 | 85 | /** 86 | * @private 87 | */ 88 | _readMessage: function (msg: Size, reader: protoscript.BinaryReader): Size { 89 | while (reader.nextField()) { 90 | const field = reader.getFieldNumber(); 91 | switch (field) { 92 | case 1: { 93 | msg.inches = reader.readInt32(); 94 | break; 95 | } 96 | default: { 97 | reader.skipField(); 98 | break; 99 | } 100 | } 101 | } 102 | return msg; 103 | }, 104 | }; 105 | 106 | export const Hat = { 107 | /** 108 | * Serializes Hat to protobuf. 109 | */ 110 | encode: function (msg: PartialDeep): Uint8Array { 111 | return Hat._writeMessage( 112 | msg, 113 | new protoscript.BinaryWriter(), 114 | ).getResultBuffer(); 115 | }, 116 | 117 | /** 118 | * Deserializes Hat from protobuf. 119 | */ 120 | decode: function (bytes: ByteSource): Hat { 121 | return Hat._readMessage( 122 | Hat.initialize(), 123 | new protoscript.BinaryReader(bytes), 124 | ); 125 | }, 126 | 127 | /** 128 | * Initializes Hat with all fields set to their default value. 129 | */ 130 | initialize: function (msg?: Partial): Hat { 131 | return { 132 | inches: 0, 133 | color: "", 134 | name: "", 135 | ...msg, 136 | }; 137 | }, 138 | 139 | /** 140 | * @private 141 | */ 142 | _writeMessage: function ( 143 | msg: PartialDeep, 144 | writer: protoscript.BinaryWriter, 145 | ): protoscript.BinaryWriter { 146 | if (msg.inches) { 147 | writer.writeInt32(1, msg.inches); 148 | } 149 | if (msg.color) { 150 | writer.writeString(2, msg.color); 151 | } 152 | if (msg.name) { 153 | writer.writeString(3, msg.name); 154 | } 155 | return writer; 156 | }, 157 | 158 | /** 159 | * @private 160 | */ 161 | _readMessage: function (msg: Hat, reader: protoscript.BinaryReader): Hat { 162 | while (reader.nextField()) { 163 | const field = reader.getFieldNumber(); 164 | switch (field) { 165 | case 1: { 166 | msg.inches = reader.readInt32(); 167 | break; 168 | } 169 | case 2: { 170 | msg.color = reader.readString(); 171 | break; 172 | } 173 | case 3: { 174 | msg.name = reader.readString(); 175 | break; 176 | } 177 | default: { 178 | reader.skipField(); 179 | break; 180 | } 181 | } 182 | } 183 | return msg; 184 | }, 185 | }; 186 | 187 | //========================================// 188 | // JSON Encode / Decode // 189 | //========================================// 190 | 191 | export const SizeJSON = { 192 | /** 193 | * Serializes Size to JSON. 194 | */ 195 | encode: function (msg: PartialDeep): string { 196 | return JSON.stringify(SizeJSON._writeMessage(msg)); 197 | }, 198 | 199 | /** 200 | * Deserializes Size from JSON. 201 | */ 202 | decode: function (json: string): Size { 203 | return SizeJSON._readMessage(SizeJSON.initialize(), JSON.parse(json)); 204 | }, 205 | 206 | /** 207 | * Initializes Size with all fields set to their default value. 208 | */ 209 | initialize: function (msg?: Partial): Size { 210 | return { 211 | inches: 0, 212 | ...msg, 213 | }; 214 | }, 215 | 216 | /** 217 | * @private 218 | */ 219 | _writeMessage: function (msg: PartialDeep): Record { 220 | const json: Record = {}; 221 | if (msg.inches) { 222 | json["inches"] = msg.inches; 223 | } 224 | return json; 225 | }, 226 | 227 | /** 228 | * @private 229 | */ 230 | _readMessage: function (msg: Size, json: any): Size { 231 | const _inches_ = json["inches"]; 232 | if (_inches_) { 233 | msg.inches = protoscript.parseNumber(_inches_); 234 | } 235 | return msg; 236 | }, 237 | }; 238 | 239 | export const HatJSON = { 240 | /** 241 | * Serializes Hat to JSON. 242 | */ 243 | encode: function (msg: PartialDeep): string { 244 | return JSON.stringify(HatJSON._writeMessage(msg)); 245 | }, 246 | 247 | /** 248 | * Deserializes Hat from JSON. 249 | */ 250 | decode: function (json: string): Hat { 251 | return HatJSON._readMessage(HatJSON.initialize(), JSON.parse(json)); 252 | }, 253 | 254 | /** 255 | * Initializes Hat with all fields set to their default value. 256 | */ 257 | initialize: function (msg?: Partial): Hat { 258 | return { 259 | inches: 0, 260 | color: "", 261 | name: "", 262 | ...msg, 263 | }; 264 | }, 265 | 266 | /** 267 | * @private 268 | */ 269 | _writeMessage: function (msg: PartialDeep): Record { 270 | const json: Record = {}; 271 | if (msg.inches) { 272 | json["inches"] = msg.inches; 273 | } 274 | if (msg.color) { 275 | json["color"] = msg.color; 276 | } 277 | if (msg.name) { 278 | json["name"] = msg.name; 279 | } 280 | return json; 281 | }, 282 | 283 | /** 284 | * @private 285 | */ 286 | _readMessage: function (msg: Hat, json: any): Hat { 287 | const _inches_ = json["inches"]; 288 | if (_inches_) { 289 | msg.inches = protoscript.parseNumber(_inches_); 290 | } 291 | const _color_ = json["color"]; 292 | if (_color_) { 293 | msg.color = _color_; 294 | } 295 | const _name_ = json["name"]; 296 | if (_name_) { 297 | msg.name = _name_; 298 | } 299 | return msg; 300 | }, 301 | }; 302 | -------------------------------------------------------------------------------- /e2e/conformance/proto/conformance/conformance.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | syntax = "proto3"; 32 | 33 | package conformance; 34 | 35 | option java_package = "com.google.protobuf.conformance"; 36 | option objc_class_prefix = "Conformance"; 37 | 38 | // This defines the conformance testing protocol. This protocol exists between 39 | // the conformance test suite itself and the code being tested. For each test, 40 | // the suite will send a ConformanceRequest message and expect a 41 | // ConformanceResponse message. 42 | // 43 | // You can either run the tests in two different ways: 44 | // 45 | // 1. in-process (using the interface in conformance_test.h). 46 | // 47 | // 2. as a sub-process communicating over a pipe. Information about how to 48 | // do this is in conformance_test_runner.cc. 49 | // 50 | // Pros/cons of the two approaches: 51 | // 52 | // - running as a sub-process is much simpler for languages other than C/C++. 53 | // 54 | // - running as a sub-process may be more tricky in unusual environments like 55 | // iOS apps, where fork/stdin/stdout are not available. 56 | 57 | enum WireFormat { 58 | UNSPECIFIED = 0; 59 | PROTOBUF = 1; 60 | JSON = 2; 61 | JSPB = 3; // Only used inside Google. Opensource testees just skip it. 62 | TEXT_FORMAT = 4; 63 | } 64 | 65 | enum TestCategory { 66 | UNSPECIFIED_TEST = 0; 67 | BINARY_TEST = 1; // Test binary wire format. 68 | JSON_TEST = 2; // Test json wire format. 69 | // Similar to JSON_TEST. However, during parsing json, testee should ignore 70 | // unknown fields. This feature is optional. Each implementation can decide 71 | // whether to support it. See 72 | // https://developers.google.com/protocol-buffers/docs/proto3#json_options 73 | // for more detail. 74 | JSON_IGNORE_UNKNOWN_PARSING_TEST = 3; 75 | // Test jspb wire format. Only used inside Google. Opensource testees just 76 | // skip it. 77 | JSPB_TEST = 4; 78 | // Test text format. For cpp, java and python, testees can already deal with 79 | // this type. Testees of other languages can simply skip it. 80 | TEXT_FORMAT_TEST = 5; 81 | } 82 | 83 | // The conformance runner will request a list of failures as the first request. 84 | // This will be known by message_type == "conformance.FailureSet", a conformance 85 | // test should return a serialized FailureSet in protobuf_payload. 86 | message FailureSet { 87 | repeated string failure = 1; 88 | } 89 | 90 | // Represents a single test case's input. The testee should: 91 | // 92 | // 1. parse this proto (which should always succeed) 93 | // 2. parse the protobuf or JSON payload in "payload" (which may fail) 94 | // 3. if the parse succeeded, serialize the message in the requested format. 95 | message ConformanceRequest { 96 | // The payload (whether protobuf of JSON) is always for a 97 | // protobuf_test_messages.proto3.TestAllTypes proto (as defined in 98 | // src/google/protobuf/proto3_test_messages.proto). 99 | oneof payload { 100 | bytes protobuf_payload = 1; 101 | string json_payload = 2; 102 | // Only used inside Google. Opensource testees just skip it. 103 | string jspb_payload = 7; 104 | string text_payload = 8; 105 | } 106 | 107 | // Which format should the testee serialize its message to? 108 | WireFormat requested_output_format = 3; 109 | 110 | // The full name for the test message to use; for the moment, either: 111 | // protobuf_test_messages.proto3.TestAllTypesProto3 or 112 | // protobuf_test_messages.google.protobuf.TestAllTypesProto2. 113 | string message_type = 4; 114 | 115 | // Each test is given a specific test category. Some category may need 116 | // specific support in testee programs. Refer to the definition of 117 | // TestCategory for more information. 118 | TestCategory test_category = 5; 119 | 120 | // Specify details for how to encode jspb. 121 | JspbEncodingConfig jspb_encoding_options = 6; 122 | 123 | // This can be used in json and text format. If true, testee should print 124 | // unknown fields instead of ignore. This feature is optional. 125 | bool print_unknown_fields = 9; 126 | } 127 | 128 | // Represents a single test case's output. 129 | message ConformanceResponse { 130 | oneof result { 131 | // This string should be set to indicate parsing failed. The string can 132 | // provide more information about the parse error if it is available. 133 | // 134 | // Setting this string does not necessarily mean the testee failed the 135 | // test. Some of the test cases are intentionally invalid input. 136 | string parse_error = 1; 137 | 138 | // If the input was successfully parsed but errors occurred when 139 | // serializing it to the requested output format, set the error message in 140 | // this field. 141 | string serialize_error = 6; 142 | 143 | // This should be set if the test program timed out. The string should 144 | // provide more information about what the child process was doing when it 145 | // was killed. 146 | string timeout_error = 9; 147 | 148 | // This should be set if some other error occurred. This will always 149 | // indicate that the test failed. The string can provide more information 150 | // about the failure. 151 | string runtime_error = 2; 152 | 153 | // If the input was successfully parsed and the requested output was 154 | // protobuf, serialize it to protobuf and set it in this field. 155 | bytes protobuf_payload = 3; 156 | 157 | // If the input was successfully parsed and the requested output was JSON, 158 | // serialize to JSON and set it in this field. 159 | string json_payload = 4; 160 | 161 | // For when the testee skipped the test, likely because a certain feature 162 | // wasn't supported, like JSON input/output. 163 | string skipped = 5; 164 | 165 | // If the input was successfully parsed and the requested output was JSPB, 166 | // serialize to JSPB and set it in this field. JSPB is only used inside 167 | // Google. Opensource testees can just skip it. 168 | string jspb_payload = 7; 169 | 170 | // If the input was successfully parsed and the requested output was 171 | // TEXT_FORMAT, serialize to TEXT_FORMAT and set it in this field. 172 | string text_payload = 8; 173 | } 174 | } 175 | 176 | // Encoding options for jspb format. 177 | message JspbEncodingConfig { 178 | // Encode the value field of Any as jspb array if true, otherwise binary. 179 | bool use_jspb_array_any_format = 1; 180 | } 181 | -------------------------------------------------------------------------------- /packages/protoscript/src/runtime/well-known-types/any.pb.ts: -------------------------------------------------------------------------------- 1 | // THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | // Source: google/protobuf/any.proto 3 | /* eslint-disable */ 4 | 5 | import type { ByteSource, PartialDeep } from "protoscript"; 6 | import * as protoscript from "protoscript"; 7 | 8 | //========================================// 9 | // Types // 10 | //========================================// 11 | 12 | /** 13 | * `Any` contains an arbitrary serialized protocol buffer message along with a 14 | * URL that describes the type of the serialized message. 15 | * 16 | * Protobuf library provides support to pack/unpack Any values in the form 17 | * of utility functions or additional generated methods of the Any type. 18 | * 19 | * Example 1: Pack and unpack a message in C++. 20 | * 21 | * Foo foo = ...; 22 | * Any any; 23 | * any.PackFrom(foo); 24 | * ... 25 | * if (any.UnpackTo(&foo)) { 26 | * ... 27 | * } 28 | * 29 | * Example 2: Pack and unpack a message in Java. 30 | * 31 | * Foo foo = ...; 32 | * Any any = Any.pack(foo); 33 | * ... 34 | * if (any.is(Foo.class)) { 35 | * foo = any.unpack(Foo.class); 36 | * } 37 | * // or ... 38 | * if (any.isSameTypeAs(Foo.getDefaultInstance())) { 39 | * foo = any.unpack(Foo.getDefaultInstance()); 40 | * } 41 | * 42 | * Example 3: Pack and unpack a message in Python. 43 | * 44 | * foo = Foo(...) 45 | * any = Any() 46 | * any.Pack(foo) 47 | * ... 48 | * if any.Is(Foo.DESCRIPTOR): 49 | * any.Unpack(foo) 50 | * ... 51 | * 52 | * Example 4: Pack and unpack a message in Go 53 | * 54 | * foo := &pb.Foo{...} 55 | * any, err := anypb.New(foo) 56 | * if err != nil { 57 | * ... 58 | * } 59 | * ... 60 | * foo := &pb.Foo{} 61 | * if err := any.UnmarshalTo(foo); err != nil { 62 | * ... 63 | * } 64 | * 65 | * The pack methods provided by protobuf library will by default use 66 | * 'type.googleapis.com/full.type.name' as the type URL and the unpack 67 | * methods only use the fully qualified type name after the last '/' 68 | * in the type URL, for example "foo.bar.com/x/y.z" will yield type 69 | * name "y.z". 70 | * 71 | * JSON 72 | * ==== 73 | * The JSON representation of an `Any` value uses the regular 74 | * representation of the deserialized, embedded message, with an 75 | * additional field `@type` which contains the type URL. Example: 76 | * 77 | * package google.profile; 78 | * message Person { 79 | * string first_name = 1; 80 | * string last_name = 2; 81 | * } 82 | * 83 | * { 84 | * "@type": "type.googleapis.com/google.profile.Person", 85 | * "firstName": , 86 | * "lastName": 87 | * } 88 | * 89 | * If the embedded message type is well-known and has a custom JSON 90 | * representation, that representation will be embedded adding a field 91 | * `value` which holds the custom JSON in addition to the `@type` 92 | * field. Example (for message [google.protobuf.Duration][]): 93 | * 94 | * { 95 | * "@type": "type.googleapis.com/google.protobuf.Duration", 96 | * "value": "1.212s" 97 | * } 98 | * 99 | */ 100 | export interface Any { 101 | /** 102 | * A URL/resource name that uniquely identifies the type of the serialized 103 | * protocol buffer message. This string must contain at least 104 | * one "/" character. The last segment of the URL's path must represent 105 | * the fully qualified name of the type (as in 106 | * `path/google.protobuf.Duration`). The name should be in a canonical form 107 | * (e.g., leading "." is not accepted). 108 | * 109 | * In practice, teams usually precompile into the binary all types that they 110 | * expect it to use in the context of Any. However, for URLs which use the 111 | * scheme `http`, `https`, or no scheme, one can optionally set up a type 112 | * server that maps type URLs to message definitions as follows: 113 | * 114 | * * If no scheme is provided, `https` is assumed. 115 | * * An HTTP GET on the URL must yield a [google.protobuf.Type][] 116 | * value in binary format, or produce an error. 117 | * * Applications are allowed to cache lookup results based on the 118 | * URL, or have them precompiled into a binary to avoid any 119 | * lookup. Therefore, binary compatibility needs to be preserved 120 | * on changes to types. (Use versioned type names to manage 121 | * breaking changes.) 122 | * 123 | * Note: this functionality is not currently available in the official 124 | * protobuf release, and it is not used for type URLs beginning with 125 | * type.googleapis.com. As of May 2023, there are no widely used type server 126 | * implementations and no plans to implement one. 127 | * 128 | * Schemes other than `http`, `https` (or the empty scheme) might be 129 | * used with implementation specific semantics. 130 | * 131 | */ 132 | typeUrl: string; 133 | /** 134 | * Must be a valid serialized protocol buffer of the above specified type. 135 | */ 136 | value: Uint8Array; 137 | } 138 | 139 | //========================================// 140 | // Protobuf Encode / Decode // 141 | //========================================// 142 | 143 | export const Any = { 144 | /** 145 | * Serializes Any to protobuf. 146 | */ 147 | encode: function (msg: PartialDeep): Uint8Array { 148 | return Any._writeMessage( 149 | msg, 150 | new protoscript.BinaryWriter(), 151 | ).getResultBuffer(); 152 | }, 153 | 154 | /** 155 | * Deserializes Any from protobuf. 156 | */ 157 | decode: function (bytes: ByteSource): Any { 158 | return Any._readMessage( 159 | Any.initialize(), 160 | new protoscript.BinaryReader(bytes), 161 | ); 162 | }, 163 | 164 | /** 165 | * Initializes Any with all fields set to their default value. 166 | */ 167 | initialize: function (msg?: Partial): Any { 168 | return { 169 | typeUrl: "", 170 | value: new Uint8Array(), 171 | ...msg, 172 | }; 173 | }, 174 | 175 | /** 176 | * @private 177 | */ 178 | _writeMessage: function ( 179 | msg: PartialDeep, 180 | writer: protoscript.BinaryWriter, 181 | ): protoscript.BinaryWriter { 182 | if (msg.typeUrl) { 183 | writer.writeString(1, msg.typeUrl); 184 | } 185 | if (msg.value?.length) { 186 | writer.writeBytes(2, msg.value); 187 | } 188 | return writer; 189 | }, 190 | 191 | /** 192 | * @private 193 | */ 194 | _readMessage: function (msg: Any, reader: protoscript.BinaryReader): Any { 195 | while (reader.nextField()) { 196 | const field = reader.getFieldNumber(); 197 | switch (field) { 198 | case 1: { 199 | msg.typeUrl = reader.readString(); 200 | break; 201 | } 202 | case 2: { 203 | msg.value = reader.readBytes(); 204 | break; 205 | } 206 | default: { 207 | reader.skipField(); 208 | break; 209 | } 210 | } 211 | } 212 | return msg; 213 | }, 214 | }; 215 | 216 | //========================================// 217 | // JSON Encode / Decode // 218 | //========================================// 219 | 220 | export const AnyJSON = { 221 | /** 222 | * Serializes Any to JSON. 223 | */ 224 | encode: function (msg: PartialDeep): string { 225 | return JSON.stringify(AnyJSON._writeMessage(msg)); 226 | }, 227 | 228 | /** 229 | * Deserializes Any from JSON. 230 | */ 231 | decode: function (json: string): Any { 232 | return AnyJSON._readMessage(AnyJSON.initialize(), JSON.parse(json)); 233 | }, 234 | 235 | /** 236 | * Initializes Any with all fields set to their default value. 237 | */ 238 | initialize: function (msg?: Partial): Any { 239 | return { 240 | typeUrl: "", 241 | value: new Uint8Array(), 242 | ...msg, 243 | }; 244 | }, 245 | 246 | /** 247 | * @private 248 | */ 249 | _writeMessage: function (msg: PartialDeep): Record { 250 | const json: Record = {}; 251 | if (msg.typeUrl) { 252 | json["typeUrl"] = msg.typeUrl; 253 | } 254 | if (msg.value?.length) { 255 | json["value"] = protoscript.serializeBytes(msg.value); 256 | } 257 | return json; 258 | }, 259 | 260 | /** 261 | * @private 262 | */ 263 | _readMessage: function (msg: Any, json: any): Any { 264 | const _typeUrl_ = json["typeUrl"] ?? json["type_url"]; 265 | if (_typeUrl_) { 266 | msg.typeUrl = _typeUrl_; 267 | } 268 | const _value_ = json["value"]; 269 | if (_value_) { 270 | msg.value = protoscript.parseBytes(_value_); 271 | } 272 | return msg; 273 | }, 274 | }; 275 | -------------------------------------------------------------------------------- /packages/protoscript/src/runtime/well-known-types/timestamp.pb.ts: -------------------------------------------------------------------------------- 1 | // THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | // Source: google/protobuf/timestamp.proto 3 | /* eslint-disable */ 4 | 5 | import type { ByteSource, PartialDeep } from "protoscript"; 6 | import * as protoscript from "protoscript"; 7 | 8 | //========================================// 9 | // Types // 10 | //========================================// 11 | 12 | /** 13 | * A Timestamp represents a point in time independent of any time zone or local 14 | * calendar, encoded as a count of seconds and fractions of seconds at 15 | * nanosecond resolution. The count is relative to an epoch at UTC midnight on 16 | * January 1, 1970, in the proleptic Gregorian calendar which extends the 17 | * Gregorian calendar backwards to year one. 18 | * 19 | * All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap 20 | * second table is needed for interpretation, using a [24-hour linear 21 | * smear](https://developers.google.com/time/smear). 22 | * 23 | * The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By 24 | * restricting to that range, we ensure that we can convert to and from [RFC 25 | * 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings. 26 | * 27 | * # Examples 28 | * 29 | * Example 1: Compute Timestamp from POSIX `time()`. 30 | * 31 | * Timestamp timestamp; 32 | * timestamp.set_seconds(time(NULL)); 33 | * timestamp.set_nanos(0); 34 | * 35 | * Example 2: Compute Timestamp from POSIX `gettimeofday()`. 36 | * 37 | * struct timeval tv; 38 | * gettimeofday(&tv, NULL); 39 | * 40 | * Timestamp timestamp; 41 | * timestamp.set_seconds(tv.tv_sec); 42 | * timestamp.set_nanos(tv.tv_usec * 1000); 43 | * 44 | * Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`. 45 | * 46 | * FILETIME ft; 47 | * GetSystemTimeAsFileTime(&ft); 48 | * UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime; 49 | * 50 | * // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z 51 | * // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z. 52 | * Timestamp timestamp; 53 | * timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL)); 54 | * timestamp.set_nanos((INT32) ((ticks % 10000000) * 100)); 55 | * 56 | * Example 4: Compute Timestamp from Java `System.currentTimeMillis()`. 57 | * 58 | * long millis = System.currentTimeMillis(); 59 | * 60 | * Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000) 61 | * .setNanos((int) ((millis % 1000) * 1000000)).build(); 62 | * 63 | * Example 5: Compute Timestamp from Java `Instant.now()`. 64 | * 65 | * Instant now = Instant.now(); 66 | * 67 | * Timestamp timestamp = 68 | * Timestamp.newBuilder().setSeconds(now.getEpochSecond()) 69 | * .setNanos(now.getNano()).build(); 70 | * 71 | * Example 6: Compute Timestamp from current time in Python. 72 | * 73 | * timestamp = Timestamp() 74 | * timestamp.GetCurrentTime() 75 | * 76 | * # JSON Mapping 77 | * 78 | * In JSON format, the Timestamp type is encoded as a string in the 79 | * [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the 80 | * format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" 81 | * where {year} is always expressed using four digits while {month}, {day}, 82 | * {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional 83 | * seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution), 84 | * are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone 85 | * is required. A proto3 JSON serializer should always use UTC (as indicated by 86 | * "Z") when printing the Timestamp type and a proto3 JSON parser should be 87 | * able to accept both UTC and other timezones (as indicated by an offset). 88 | * 89 | * For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past 90 | * 01:30 UTC on January 15, 2017. 91 | * 92 | * In JavaScript, one can convert a Date object to this format using the 93 | * standard 94 | * [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString) 95 | * method. In Python, a standard `datetime.datetime` object can be converted 96 | * to this format using 97 | * [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with 98 | * the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use 99 | * the Joda Time's [`ISODateTimeFormat.dateTime()`]( 100 | * http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime() 101 | * ) to obtain a formatter capable of generating timestamps in this format. 102 | * 103 | */ 104 | export interface Timestamp { 105 | /** 106 | * Represents seconds of UTC time since Unix epoch 107 | * 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to 108 | * 9999-12-31T23:59:59Z inclusive. 109 | */ 110 | seconds: bigint; 111 | /** 112 | * Non-negative fractions of a second at nanosecond resolution. Negative 113 | * second values with fractions must still have non-negative nanos values 114 | * that count forward in time. Must be from 0 to 999,999,999 115 | * inclusive. 116 | */ 117 | nanos: number; 118 | } 119 | 120 | //========================================// 121 | // Protobuf Encode / Decode // 122 | //========================================// 123 | 124 | export const Timestamp = { 125 | /** 126 | * Serializes Timestamp to protobuf. 127 | */ 128 | encode: function (msg: PartialDeep): Uint8Array { 129 | return Timestamp._writeMessage( 130 | msg, 131 | new protoscript.BinaryWriter(), 132 | ).getResultBuffer(); 133 | }, 134 | 135 | /** 136 | * Deserializes Timestamp from protobuf. 137 | */ 138 | decode: function (bytes: ByteSource): Timestamp { 139 | return Timestamp._readMessage( 140 | Timestamp.initialize(), 141 | new protoscript.BinaryReader(bytes), 142 | ); 143 | }, 144 | 145 | /** 146 | * Initializes Timestamp with all fields set to their default value. 147 | */ 148 | initialize: function (msg?: Partial): Timestamp { 149 | return { 150 | seconds: 0n, 151 | nanos: 0, 152 | ...msg, 153 | }; 154 | }, 155 | 156 | /** 157 | * @private 158 | */ 159 | _writeMessage: function ( 160 | msg: PartialDeep, 161 | writer: protoscript.BinaryWriter, 162 | ): protoscript.BinaryWriter { 163 | if (msg.seconds) { 164 | writer.writeInt64String(1, msg.seconds.toString() as any); 165 | } 166 | if (msg.nanos) { 167 | writer.writeInt32(2, msg.nanos); 168 | } 169 | return writer; 170 | }, 171 | 172 | /** 173 | * @private 174 | */ 175 | _readMessage: function ( 176 | msg: Timestamp, 177 | reader: protoscript.BinaryReader, 178 | ): Timestamp { 179 | while (reader.nextField()) { 180 | const field = reader.getFieldNumber(); 181 | switch (field) { 182 | case 1: { 183 | msg.seconds = BigInt(reader.readInt64String()); 184 | break; 185 | } 186 | case 2: { 187 | msg.nanos = reader.readInt32(); 188 | break; 189 | } 190 | default: { 191 | reader.skipField(); 192 | break; 193 | } 194 | } 195 | } 196 | return msg; 197 | }, 198 | }; 199 | 200 | //========================================// 201 | // JSON Encode / Decode // 202 | //========================================// 203 | 204 | export const TimestampJSON = { 205 | /** 206 | * Serializes Timestamp to JSON. 207 | */ 208 | encode: function (msg: PartialDeep): string { 209 | return JSON.stringify(TimestampJSON._writeMessage(msg)); 210 | }, 211 | 212 | /** 213 | * Deserializes Timestamp from JSON. 214 | */ 215 | decode: function (json: string): Timestamp { 216 | return TimestampJSON._readMessage( 217 | TimestampJSON.initialize(), 218 | JSON.parse(json), 219 | ); 220 | }, 221 | 222 | /** 223 | * Initializes Timestamp with all fields set to their default value. 224 | */ 225 | initialize: function (msg?: Partial): Timestamp { 226 | return { 227 | seconds: 0n, 228 | nanos: 0, 229 | ...msg, 230 | }; 231 | }, 232 | 233 | /** 234 | * @private 235 | */ 236 | _writeMessage: function ( 237 | msg: PartialDeep, 238 | ): Record { 239 | const json: Record = {}; 240 | if (msg.seconds) { 241 | json["seconds"] = String(msg.seconds); 242 | } 243 | if (msg.nanos) { 244 | json["nanos"] = msg.nanos; 245 | } 246 | return json; 247 | }, 248 | 249 | /** 250 | * @private 251 | */ 252 | _readMessage: function (msg: Timestamp, json: any): Timestamp { 253 | const _seconds_ = json["seconds"]; 254 | if (_seconds_) { 255 | msg.seconds = BigInt(_seconds_); 256 | } 257 | const _nanos_ = json["nanos"]; 258 | if (_nanos_) { 259 | msg.nanos = protoscript.parseNumber(_nanos_); 260 | } 261 | return msg; 262 | }, 263 | }; 264 | -------------------------------------------------------------------------------- /examples/typescript/src/user.pb.ts: -------------------------------------------------------------------------------- 1 | // THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | // Source: src/user.proto 3 | /* eslint-disable */ 4 | 5 | import type { ByteSource, PartialDeep } from "protoscript"; 6 | import * as protoscript from "protoscript"; 7 | 8 | //========================================// 9 | // Types // 10 | //========================================// 11 | 12 | export interface User { 13 | firstName: string; 14 | lastName: string; 15 | active: boolean; 16 | manager: User | null | undefined; 17 | locations: string[]; 18 | projects: Record; 19 | } 20 | 21 | export declare namespace User { 22 | interface Projects { 23 | key: string; 24 | value: string; 25 | } 26 | } 27 | 28 | //========================================// 29 | // Protobuf Encode / Decode // 30 | //========================================// 31 | 32 | export const User = { 33 | /** 34 | * Serializes User to protobuf. 35 | */ 36 | encode: function (msg: PartialDeep): Uint8Array { 37 | return User._writeMessage( 38 | msg, 39 | new protoscript.BinaryWriter(), 40 | ).getResultBuffer(); 41 | }, 42 | 43 | /** 44 | * Deserializes User from protobuf. 45 | */ 46 | decode: function (bytes: ByteSource): User { 47 | return User._readMessage( 48 | User.initialize(), 49 | new protoscript.BinaryReader(bytes), 50 | ); 51 | }, 52 | 53 | /** 54 | * Initializes User with all fields set to their default value. 55 | */ 56 | initialize: function (msg?: Partial): User { 57 | return { 58 | firstName: "", 59 | lastName: "", 60 | active: false, 61 | manager: undefined, 62 | locations: [], 63 | projects: {}, 64 | ...msg, 65 | }; 66 | }, 67 | 68 | /** 69 | * @private 70 | */ 71 | _writeMessage: function ( 72 | msg: PartialDeep, 73 | writer: protoscript.BinaryWriter, 74 | ): protoscript.BinaryWriter { 75 | if (msg.firstName) { 76 | writer.writeString(1, msg.firstName); 77 | } 78 | if (msg.lastName) { 79 | writer.writeString(2, msg.lastName); 80 | } 81 | if (msg.active) { 82 | writer.writeBool(3, msg.active); 83 | } 84 | if (msg.manager) { 85 | writer.writeMessage(4, msg.manager, User._writeMessage); 86 | } 87 | if (msg.locations?.length) { 88 | writer.writeRepeatedString(5, msg.locations); 89 | } 90 | if (msg.projects) { 91 | writer.writeRepeatedMessage( 92 | 6, 93 | Object.entries(msg.projects).map(([key, value]) => ({ 94 | key: key as any, 95 | value: value as any, 96 | })) as any, 97 | User.Projects._writeMessage, 98 | ); 99 | } 100 | return writer; 101 | }, 102 | 103 | /** 104 | * @private 105 | */ 106 | _readMessage: function (msg: User, reader: protoscript.BinaryReader): User { 107 | while (reader.nextField()) { 108 | const field = reader.getFieldNumber(); 109 | switch (field) { 110 | case 1: { 111 | msg.firstName = reader.readString(); 112 | break; 113 | } 114 | case 2: { 115 | msg.lastName = reader.readString(); 116 | break; 117 | } 118 | case 3: { 119 | msg.active = reader.readBool(); 120 | break; 121 | } 122 | case 4: { 123 | msg.manager = User.initialize(); 124 | reader.readMessage(msg.manager, User._readMessage); 125 | break; 126 | } 127 | case 5: { 128 | msg.locations.push(reader.readString()); 129 | break; 130 | } 131 | case 6: { 132 | const map = {} as User.Projects; 133 | reader.readMessage(map, User.Projects._readMessage); 134 | msg.projects[map.key.toString()] = map.value; 135 | break; 136 | } 137 | default: { 138 | reader.skipField(); 139 | break; 140 | } 141 | } 142 | } 143 | return msg; 144 | }, 145 | 146 | Projects: { 147 | /** 148 | * @private 149 | */ 150 | _writeMessage: function ( 151 | msg: PartialDeep, 152 | writer: protoscript.BinaryWriter, 153 | ): protoscript.BinaryWriter { 154 | if (msg.key) { 155 | writer.writeString(1, msg.key); 156 | } 157 | if (msg.value) { 158 | writer.writeString(2, msg.value); 159 | } 160 | return writer; 161 | }, 162 | 163 | /** 164 | * @private 165 | */ 166 | _readMessage: function ( 167 | msg: User.Projects, 168 | reader: protoscript.BinaryReader, 169 | ): User.Projects { 170 | while (reader.nextField()) { 171 | const field = reader.getFieldNumber(); 172 | switch (field) { 173 | case 1: { 174 | msg.key = reader.readString(); 175 | break; 176 | } 177 | case 2: { 178 | msg.value = reader.readString(); 179 | break; 180 | } 181 | default: { 182 | reader.skipField(); 183 | break; 184 | } 185 | } 186 | } 187 | return msg; 188 | }, 189 | }, 190 | }; 191 | 192 | //========================================// 193 | // JSON Encode / Decode // 194 | //========================================// 195 | 196 | export const UserJSON = { 197 | /** 198 | * Serializes User to JSON. 199 | */ 200 | encode: function (msg: PartialDeep): string { 201 | return JSON.stringify(UserJSON._writeMessage(msg)); 202 | }, 203 | 204 | /** 205 | * Deserializes User from JSON. 206 | */ 207 | decode: function (json: string): User { 208 | return UserJSON._readMessage(UserJSON.initialize(), JSON.parse(json)); 209 | }, 210 | 211 | /** 212 | * Initializes User with all fields set to their default value. 213 | */ 214 | initialize: function (msg?: Partial): User { 215 | return { 216 | firstName: "", 217 | lastName: "", 218 | active: false, 219 | manager: undefined, 220 | locations: [], 221 | projects: {}, 222 | ...msg, 223 | }; 224 | }, 225 | 226 | /** 227 | * @private 228 | */ 229 | _writeMessage: function (msg: PartialDeep): Record { 230 | const json: Record = {}; 231 | if (msg.firstName) { 232 | json["firstName"] = msg.firstName; 233 | } 234 | if (msg.lastName) { 235 | json["lastName"] = msg.lastName; 236 | } 237 | if (msg.active) { 238 | json["active"] = msg.active; 239 | } 240 | if (msg.manager) { 241 | const _manager_ = UserJSON._writeMessage(msg.manager); 242 | if (Object.keys(_manager_).length > 0) { 243 | json["manager"] = _manager_; 244 | } 245 | } 246 | if (msg.locations?.length) { 247 | json["locations"] = msg.locations; 248 | } 249 | if (msg.projects) { 250 | const _projects_ = Object.fromEntries( 251 | Object.entries(msg.projects) 252 | .map(([key, value]) => ({ key: key as any, value: value as any })) 253 | .map(UserJSON.Projects._writeMessage) 254 | .map(({ key, value }) => [key, value]), 255 | ); 256 | if (Object.keys(_projects_).length > 0) { 257 | json["projects"] = _projects_; 258 | } 259 | } 260 | return json; 261 | }, 262 | 263 | /** 264 | * @private 265 | */ 266 | _readMessage: function (msg: User, json: any): User { 267 | const _firstName_ = json["firstName"] ?? json["first_name"]; 268 | if (_firstName_) { 269 | msg.firstName = _firstName_; 270 | } 271 | const _lastName_ = json["lastName"] ?? json["last_name"]; 272 | if (_lastName_) { 273 | msg.lastName = _lastName_; 274 | } 275 | const _active_ = json["active"]; 276 | if (_active_) { 277 | msg.active = _active_; 278 | } 279 | const _manager_ = json["manager"]; 280 | if (_manager_) { 281 | msg.manager = UserJSON.initialize(); 282 | UserJSON._readMessage(msg.manager, _manager_); 283 | } 284 | const _locations_ = json["locations"]; 285 | if (_locations_) { 286 | msg.locations = _locations_; 287 | } 288 | const _projects_ = json["projects"]; 289 | if (_projects_) { 290 | msg.projects = Object.fromEntries( 291 | Object.entries(_projects_) 292 | .map(([key, value]) => ({ key: key as any, value: value as any })) 293 | .map(UserJSON.Projects._readMessage) 294 | .map(({ key, value }) => [key, value]), 295 | ); 296 | } 297 | return msg; 298 | }, 299 | 300 | Projects: { 301 | /** 302 | * @private 303 | */ 304 | _writeMessage: function ( 305 | msg: PartialDeep, 306 | ): Record { 307 | const json: Record = {}; 308 | if (msg.key) { 309 | json["key"] = msg.key; 310 | } 311 | if (msg.value) { 312 | json["value"] = msg.value; 313 | } 314 | return json; 315 | }, 316 | 317 | /** 318 | * @private 319 | */ 320 | _readMessage: function (msg: User.Projects, json: any): User.Projects { 321 | const _key_ = json["key"]; 322 | if (_key_) { 323 | msg.key = _key_; 324 | } 325 | const _value_ = json["value"]; 326 | if (_value_) { 327 | msg.value = _value_; 328 | } 329 | return msg; 330 | }, 331 | }, 332 | }; 333 | -------------------------------------------------------------------------------- /packages/protoscript/src/runtime/well-known-types/field_mask.pb.ts: -------------------------------------------------------------------------------- 1 | // THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | // Source: google/protobuf/field_mask.proto 3 | /* eslint-disable */ 4 | 5 | import type { ByteSource, PartialDeep } from "protoscript"; 6 | import * as protoscript from "protoscript"; 7 | 8 | //========================================// 9 | // Types // 10 | //========================================// 11 | 12 | /** 13 | * `FieldMask` represents a set of symbolic field paths, for example: 14 | * 15 | * paths: "f.a" 16 | * paths: "f.b.d" 17 | * 18 | * Here `f` represents a field in some root message, `a` and `b` 19 | * fields in the message found in `f`, and `d` a field found in the 20 | * message in `f.b`. 21 | * 22 | * Field masks are used to specify a subset of fields that should be 23 | * returned by a get operation or modified by an update operation. 24 | * Field masks also have a custom JSON encoding (see below). 25 | * 26 | * # Field Masks in Projections 27 | * 28 | * When used in the context of a projection, a response message or 29 | * sub-message is filtered by the API to only contain those fields as 30 | * specified in the mask. For example, if the mask in the previous 31 | * example is applied to a response message as follows: 32 | * 33 | * f { 34 | * a : 22 35 | * b { 36 | * d : 1 37 | * x : 2 38 | * } 39 | * y : 13 40 | * } 41 | * z: 8 42 | * 43 | * The result will not contain specific values for fields x,y and z 44 | * (their value will be set to the default, and omitted in proto text 45 | * output): 46 | * 47 | * 48 | * f { 49 | * a : 22 50 | * b { 51 | * d : 1 52 | * } 53 | * } 54 | * 55 | * A repeated field is not allowed except at the last position of a 56 | * paths string. 57 | * 58 | * If a FieldMask object is not present in a get operation, the 59 | * operation applies to all fields (as if a FieldMask of all fields 60 | * had been specified). 61 | * 62 | * Note that a field mask does not necessarily apply to the 63 | * top-level response message. In case of a REST get operation, the 64 | * field mask applies directly to the response, but in case of a REST 65 | * list operation, the mask instead applies to each individual message 66 | * in the returned resource list. In case of a REST custom method, 67 | * other definitions may be used. Where the mask applies will be 68 | * clearly documented together with its declaration in the API. In 69 | * any case, the effect on the returned resource/resources is required 70 | * behavior for APIs. 71 | * 72 | * # Field Masks in Update Operations 73 | * 74 | * A field mask in update operations specifies which fields of the 75 | * targeted resource are going to be updated. The API is required 76 | * to only change the values of the fields as specified in the mask 77 | * and leave the others untouched. If a resource is passed in to 78 | * describe the updated values, the API ignores the values of all 79 | * fields not covered by the mask. 80 | * 81 | * If a repeated field is specified for an update operation, new values will 82 | * be appended to the existing repeated field in the target resource. Note that 83 | * a repeated field is only allowed in the last position of a `paths` string. 84 | * 85 | * If a sub-message is specified in the last position of the field mask for an 86 | * update operation, then new value will be merged into the existing sub-message 87 | * in the target resource. 88 | * 89 | * For example, given the target message: 90 | * 91 | * f { 92 | * b { 93 | * d: 1 94 | * x: 2 95 | * } 96 | * c: [1] 97 | * } 98 | * 99 | * And an update message: 100 | * 101 | * f { 102 | * b { 103 | * d: 10 104 | * } 105 | * c: [2] 106 | * } 107 | * 108 | * then if the field mask is: 109 | * 110 | * paths: ["f.b", "f.c"] 111 | * 112 | * then the result will be: 113 | * 114 | * f { 115 | * b { 116 | * d: 10 117 | * x: 2 118 | * } 119 | * c: [1, 2] 120 | * } 121 | * 122 | * An implementation may provide options to override this default behavior for 123 | * repeated and message fields. 124 | * 125 | * In order to reset a field's value to the default, the field must 126 | * be in the mask and set to the default value in the provided resource. 127 | * Hence, in order to reset all fields of a resource, provide a default 128 | * instance of the resource and set all fields in the mask, or do 129 | * not provide a mask as described below. 130 | * 131 | * If a field mask is not present on update, the operation applies to 132 | * all fields (as if a field mask of all fields has been specified). 133 | * Note that in the presence of schema evolution, this may mean that 134 | * fields the client does not know and has therefore not filled into 135 | * the request will be reset to their default. If this is unwanted 136 | * behavior, a specific service may require a client to always specify 137 | * a field mask, producing an error if not. 138 | * 139 | * As with get operations, the location of the resource which 140 | * describes the updated values in the request message depends on the 141 | * operation kind. In any case, the effect of the field mask is 142 | * required to be honored by the API. 143 | * 144 | * ## Considerations for HTTP REST 145 | * 146 | * The HTTP kind of an update operation which uses a field mask must 147 | * be set to PATCH instead of PUT in order to satisfy HTTP semantics 148 | * (PUT must only be used for full updates). 149 | * 150 | * # JSON Encoding of Field Masks 151 | * 152 | * In JSON, a field mask is encoded as a single string where paths are 153 | * separated by a comma. Fields name in each path are converted 154 | * to/from lower-camel naming conventions. 155 | * 156 | * As an example, consider the following message declarations: 157 | * 158 | * message Profile { 159 | * User user = 1; 160 | * Photo photo = 2; 161 | * } 162 | * message User { 163 | * string display_name = 1; 164 | * string address = 2; 165 | * } 166 | * 167 | * In proto a field mask for `Profile` may look as such: 168 | * 169 | * mask { 170 | * paths: "user.display_name" 171 | * paths: "photo" 172 | * } 173 | * 174 | * In JSON, the same mask is represented as below: 175 | * 176 | * { 177 | * mask: "user.displayName,photo" 178 | * } 179 | * 180 | * # Field Masks and Oneof Fields 181 | * 182 | * Field masks treat fields in oneofs just as regular fields. Consider the 183 | * following message: 184 | * 185 | * message SampleMessage { 186 | * oneof test_oneof { 187 | * string name = 4; 188 | * SubMessage sub_message = 9; 189 | * } 190 | * } 191 | * 192 | * The field mask can be: 193 | * 194 | * mask { 195 | * paths: "name" 196 | * } 197 | * 198 | * Or: 199 | * 200 | * mask { 201 | * paths: "sub_message" 202 | * } 203 | * 204 | * Note that oneof type names ("test_oneof" in this case) cannot be used in 205 | * paths. 206 | * 207 | * ## Field Mask Verification 208 | * 209 | * The implementation of any API method which has a FieldMask type field in the 210 | * request should verify the included field paths, and return an 211 | * `INVALID_ARGUMENT` error if any path is unmappable. 212 | */ 213 | export interface FieldMask { 214 | /** 215 | * The set of field mask paths. 216 | */ 217 | paths: string[]; 218 | } 219 | 220 | //========================================// 221 | // Protobuf Encode / Decode // 222 | //========================================// 223 | 224 | export const FieldMask = { 225 | /** 226 | * Serializes FieldMask to protobuf. 227 | */ 228 | encode: function (msg: PartialDeep): Uint8Array { 229 | return FieldMask._writeMessage( 230 | msg, 231 | new protoscript.BinaryWriter(), 232 | ).getResultBuffer(); 233 | }, 234 | 235 | /** 236 | * Deserializes FieldMask from protobuf. 237 | */ 238 | decode: function (bytes: ByteSource): FieldMask { 239 | return FieldMask._readMessage( 240 | FieldMask.initialize(), 241 | new protoscript.BinaryReader(bytes), 242 | ); 243 | }, 244 | 245 | /** 246 | * Initializes FieldMask with all fields set to their default value. 247 | */ 248 | initialize: function (msg?: Partial): FieldMask { 249 | return { 250 | paths: [], 251 | ...msg, 252 | }; 253 | }, 254 | 255 | /** 256 | * @private 257 | */ 258 | _writeMessage: function ( 259 | msg: PartialDeep, 260 | writer: protoscript.BinaryWriter, 261 | ): protoscript.BinaryWriter { 262 | if (msg.paths?.length) { 263 | writer.writeRepeatedString(1, msg.paths); 264 | } 265 | return writer; 266 | }, 267 | 268 | /** 269 | * @private 270 | */ 271 | _readMessage: function ( 272 | msg: FieldMask, 273 | reader: protoscript.BinaryReader, 274 | ): FieldMask { 275 | while (reader.nextField()) { 276 | const field = reader.getFieldNumber(); 277 | switch (field) { 278 | case 1: { 279 | msg.paths.push(reader.readString()); 280 | break; 281 | } 282 | default: { 283 | reader.skipField(); 284 | break; 285 | } 286 | } 287 | } 288 | return msg; 289 | }, 290 | }; 291 | 292 | //========================================// 293 | // JSON Encode / Decode // 294 | //========================================// 295 | 296 | export const FieldMaskJSON = { 297 | /** 298 | * Serializes FieldMask to JSON. 299 | */ 300 | encode: function (msg: PartialDeep): string { 301 | return JSON.stringify(FieldMaskJSON._writeMessage(msg)); 302 | }, 303 | 304 | /** 305 | * Deserializes FieldMask from JSON. 306 | */ 307 | decode: function (json: string): FieldMask { 308 | return FieldMaskJSON._readMessage( 309 | FieldMaskJSON.initialize(), 310 | JSON.parse(json), 311 | ); 312 | }, 313 | 314 | /** 315 | * Initializes FieldMask with all fields set to their default value. 316 | */ 317 | initialize: function (msg?: Partial): FieldMask { 318 | return { 319 | paths: [], 320 | ...msg, 321 | }; 322 | }, 323 | 324 | /** 325 | * @private 326 | */ 327 | _writeMessage: function ( 328 | msg: PartialDeep, 329 | ): Record { 330 | const json: Record = {}; 331 | if (msg.paths?.length) { 332 | json["paths"] = msg.paths; 333 | } 334 | return json; 335 | }, 336 | 337 | /** 338 | * @private 339 | */ 340 | _readMessage: function (msg: FieldMask, json: any): FieldMask { 341 | const _paths_ = json["paths"]; 342 | if (_paths_) { 343 | msg.paths = _paths_; 344 | } 345 | return msg; 346 | }, 347 | }; 348 | -------------------------------------------------------------------------------- /packages/protoscript/src/cli/core.ts: -------------------------------------------------------------------------------- 1 | import { spawnSync } from "child_process"; 2 | import { existsSync, mkdirSync } from "fs"; 3 | import { join, relative, resolve } from "path"; 4 | import { checksum, commandIsInPath, findFiles, pluralize } from "./utils.js"; 5 | 6 | let logger: Pick; 7 | 8 | function initLogger(name: string) { 9 | const prefix = `[${name}] `; 10 | logger = { 11 | info: (str: string) => console.info(prefix, str), 12 | warn: (str: string) => console.warn(prefix, str), 13 | error: (str: string) => console.error(prefix, str), 14 | }; 15 | } 16 | 17 | function onCliError(error: string, statusCode: number): void { 18 | logger.error("Protobuf Compiler Error: \n"); 19 | console.error(error); 20 | if (statusCode !== 0) { 21 | console.error(); 22 | console.error("No .pb.ts files were created or updated."); 23 | } 24 | process.exit(statusCode); 25 | } 26 | 27 | export type UserConfig = Partial; 28 | 29 | type Config = { 30 | /** 31 | * The root directory. `.proto` files will be searched under this directory, and `proto` import paths will be resolved relative to this directory. ProtoScript will recursively search all subdirectories for `.proto` files. 32 | * 33 | * Defaults to the project root. 34 | * 35 | * Example: 36 | * 37 | * If we have the following project structure: 38 | * 39 | * /src 40 | * A.proto 41 | * B.proto 42 | * 43 | * Default: 44 | * 45 | * A.proto would `import` B.proto as follows: 46 | * 47 | * ```proto 48 | * import "src/B.proto"; 49 | * ``` 50 | * 51 | * Setting `root` to `src`: 52 | * 53 | * // proto.config.mjs 54 | * ```js 55 | * // @type {import('protoscript').Config} 56 | * export default { 57 | * root: "src" 58 | * } 59 | * ``` 60 | * 61 | * A.proto would `import` B.proto as follows: 62 | * 63 | * ```proto 64 | * import "B.proto"; 65 | * ``` 66 | * 67 | * TypeScript projects will generally want to set this value to match their `rootDir`. 68 | */ 69 | root: string; 70 | /** 71 | * An array of patterns that should be skipped when searching for `.proto` files. 72 | * 73 | * Example: 74 | * 75 | * If we have the following project structure: 76 | * /src 77 | * /foo 78 | * A.proto 79 | * /bar 80 | * B.proto 81 | * 82 | * Setting `exclude` to `["/bar/"]`: 83 | * 84 | * // proto.config.mjs 85 | * ```js 86 | * // @type {import('protoscript').Config} 87 | * export default { 88 | * exclude: ["/bar/"] 89 | * } 90 | * ``` 91 | * 92 | * Will only process A.proto (B.proto) will be excluded from ProtoScript's code generation. 93 | * 94 | */ 95 | exclude: string[]; 96 | /** The destination folder for generated files. 97 | * 98 | * Defaults to colocating generated files with the corresponding `proto` definition. 99 | * Example: 100 | * 101 | * If we have the following project structure: 102 | * 103 | * /src 104 | * A.proto 105 | * B.proto 106 | * 107 | * Default: 108 | * 109 | * ProtoScript will generate the following: 110 | * 111 | * /src 112 | * A.proto 113 | * A.pb.ts 114 | * B.proto 115 | * B.pb.ts 116 | * 117 | * Setting `dest` to `out`: 118 | * 119 | * // proto.config.mjs 120 | * ```js 121 | * // @type {import('protoscript').Config} 122 | * export default { 123 | * dest: "out", 124 | * } 125 | * 126 | * /src 127 | * A.proto 128 | * B.proto 129 | * /out 130 | * /src 131 | * A.pb.ts 132 | * B.pb.ts 133 | * 134 | * Note that the generated directory structure will mirror the `proto` paths exactly as is, only nested under the `dest` directory. If you want to change this, for instance, to omit `src` from the `out` directory above, you can set the `root`. 135 | * 136 | * Setting `root` to `src`: 137 | * 138 | * // proto.config.mjs 139 | * ```js 140 | * // @type {import('protoscript').Config} 141 | * export default { 142 | * root: "src", 143 | * dest: "out", 144 | * } 145 | * 146 | * /src 147 | * A.proto 148 | * B.proto 149 | * /out 150 | * A.pb.ts 151 | * B.pb.ts 152 | */ 153 | dest: string; 154 | /** 155 | * Whether to generate JavaScript or TypeScript. 156 | * 157 | * If omitted, ProtoScript will attempt to autodetect the language by looking for a `tsconfig.json` in the project root. If found, ProtoScript will generate TypeScript, otherwise JavaScript. 158 | */ 159 | language: "javascript" | "typescript"; 160 | /** 161 | * JSON serializer options. 162 | * 163 | * See https://developers.google.com/protocol-buffers/docs/proto3#json for more context. 164 | */ 165 | json: { 166 | /** 167 | * Fields with default values are omitted by default in proto3 JSON. Setting this to true will serialize fields with their default values. 168 | */ 169 | emitFieldsWithDefaultValues?: boolean; 170 | /** 171 | * Field names are converted to lowerCamelCase by default in proto3 JSON. Setting this to true will use the proto field name as the JSON key when serializing JSON. 172 | * 173 | * Either way, Proto3 JSON parsers are required to accept both the converted lowerCamelCase name and the proto field name. 174 | */ 175 | useProtoFieldName?: boolean; 176 | }; 177 | /** 178 | * TypeScript options. 179 | */ 180 | typescript: { 181 | /** 182 | * Only emit TypeScript type definitions. 183 | */ 184 | emitDeclarationOnly?: boolean; 185 | }; 186 | }; 187 | 188 | function getConfigFilePath(): string | undefined { 189 | const cwd = process.cwd(); 190 | for (const ext of [".js", ".mjs", ".cjs"]) { 191 | const path = join(cwd, "proto.config") + ext; 192 | if (existsSync(path)) { 193 | return path; 194 | } 195 | } 196 | } 197 | 198 | async function getConfig(): Promise { 199 | const projectRoot = process.cwd(); 200 | const defaultConfig: Config = { 201 | root: projectRoot, 202 | exclude: [], 203 | dest: ".", 204 | language: existsSync(join(projectRoot, "tsconfig.json")) 205 | ? "typescript" 206 | : "javascript", 207 | json: {}, 208 | typescript: {}, 209 | }; 210 | 211 | const configFilePath = getConfigFilePath(); 212 | let userConfig: UserConfig = {}; 213 | if (configFilePath) { 214 | logger.info(`Using configuration file at '${configFilePath}'.`); 215 | try { 216 | // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access 217 | userConfig = (await import(configFilePath)).default; 218 | } catch (e) { 219 | logger.error(`Failed to parse configuration file.`); 220 | console.log(e); 221 | process.exit(1); 222 | } 223 | 224 | const unknownKeys = Object.keys(userConfig).filter( 225 | (key) => !(key in defaultConfig), 226 | ); 227 | if (unknownKeys.length) { 228 | logger.warn( 229 | `Found unknown configuration options: ${unknownKeys 230 | .map((k) => `'${k}'`) 231 | .join(", ")}.`, 232 | ); 233 | } 234 | } 235 | 236 | return { 237 | ...defaultConfig, 238 | ...userConfig, 239 | }; 240 | } 241 | 242 | interface CliOptions { 243 | compiler: { 244 | path: string; 245 | }; 246 | logger?: { 247 | name: string; 248 | }; 249 | } 250 | 251 | export async function main(opts: CliOptions): Promise { 252 | initLogger(opts.logger?.name ?? "ProtoScript"); 253 | const config = await getConfig(); 254 | const excludes = config.exclude.map((pattern) => RegExp(pattern)); 255 | const protos = findFiles(config.root, ".proto") 256 | .map((filepath) => relative(config.root, filepath)) 257 | .filter((file) => !excludes.some((exclude) => exclude.exec(file))); 258 | 259 | if (!commandIsInPath("protoc")) { 260 | logger.error( 261 | `Could not find the protobuf compiler. Please make sure 'protoc' is installed and in your '$PATH'. 262 | 263 | MacOS: 264 | \`brew install protobuf\` 265 | 266 | Linux: 267 | \`apt install -y protobuf-compiler\` 268 | 269 | Windows: 270 | \`choco install protoc\` 271 | 272 | Or install from a precompiled binary: 273 | https://github.com/protocolbuffers/protobuf/releases 274 | `, 275 | ); 276 | process.exit(1); 277 | } 278 | 279 | if (protos.length === 0) { 280 | logger.info("No '.proto' files found."); 281 | process.exit(0); 282 | } 283 | 284 | try { 285 | const destination = config.dest === "." ? "." : resolve(config.dest); 286 | 287 | if (!existsSync(destination)) { 288 | logger.info(`Created destination folder '${destination}'.`); 289 | mkdirSync(destination, { recursive: true }); 290 | } 291 | 292 | process.chdir(config.root); 293 | 294 | const protoExt = config.language === "typescript" ? "pb.ts" : "pb.js"; 295 | const protosBeforeCompile = Object.fromEntries( 296 | findFiles(destination, protoExt).map((file) => [file, checksum(file)]), 297 | ); 298 | 299 | const protoc = spawnSync( 300 | `\ 301 | protoc \ 302 | --plugin=protoc-gen-protoscript=${opts.compiler.path} \ 303 | --protoscript_out=${destination} \ 304 | --protoscript_opt=language=${config.language} \ 305 | ${ 306 | config.json.emitFieldsWithDefaultValues 307 | ? "--protoscript_opt=json=emitFieldsWithDefaultValues" 308 | : "" 309 | } \ 310 | ${ 311 | config.json.useProtoFieldName 312 | ? "--protoscript_opt=json=useProtoFieldName" 313 | : "" 314 | } \ 315 | ${ 316 | config.typescript.emitDeclarationOnly 317 | ? "--protoscript_opt=typescript=emitDeclarationOnly" 318 | : "" 319 | } \ 320 | ${protos.join(" ")} 321 | `, 322 | { shell: true, encoding: "utf8" }, 323 | ); 324 | 325 | if (protoc.stderr) { 326 | onCliError(protoc.stderr, protoc.status ?? 1); 327 | } 328 | 329 | const protosAfterCompile = findFiles(destination, protoExt).map((file) => [ 330 | file, 331 | checksum(file), 332 | ]); 333 | 334 | const created = protosAfterCompile.filter( 335 | (file) => !protosBeforeCompile[file[0]], 336 | ); 337 | const updated = protosAfterCompile.filter( 338 | (file) => 339 | protosBeforeCompile[file[0]] && 340 | protosBeforeCompile[file[0]] !== file[1], 341 | ); 342 | const unchanged = protosAfterCompile.filter( 343 | (file) => protosBeforeCompile[file[0]] === file[1], 344 | ); 345 | 346 | logger.info("\n"); 347 | if (created.length > 0) { 348 | console.info( 349 | // eslint-disable-next-line @typescript-eslint/restrict-template-expressions 350 | `Created:\n${created.map((f) => ` - ${f[0]}`).join("\n")}\n`, 351 | ); 352 | } 353 | if (updated.length > 0) { 354 | console.info( 355 | // eslint-disable-next-line @typescript-eslint/restrict-template-expressions 356 | `Updated:\n${updated.map((f) => ` - ${f[0]}`).join("\n")}\n`, 357 | ); 358 | } 359 | console.info( 360 | `${created.length} ${pluralize("file", created.length)} created, ${ 361 | updated.length 362 | } ${pluralize("file", updated.length)} updated, ${ 363 | unchanged.length 364 | } ${pluralize("file", unchanged.length)} unchanged. ${ 365 | protos.length 366 | } ${pluralize("file", protos.length)} found.`, 367 | ); 368 | } catch (error) { 369 | onCliError(error as string, 1); 370 | } 371 | } 372 | -------------------------------------------------------------------------------- /e2e/conformance/proto/google/protobuf/test_messages_proto3.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffers - Google's data interchange format 2 | // Copyright 2008 Google Inc. All rights reserved. 3 | // https://developers.google.com/protocol-buffers/ 4 | // 5 | // Redistribution and use in source and binary forms, with or without 6 | // modification, are permitted provided that the following conditions are 7 | // met: 8 | // 9 | // * Redistributions of source code must retain the above copyright 10 | // notice, this list of conditions and the following disclaimer. 11 | // * Redistributions in binary form must reproduce the above 12 | // copyright notice, this list of conditions and the following disclaimer 13 | // in the documentation and/or other materials provided with the 14 | // distribution. 15 | // * Neither the name of Google Inc. nor the names of its 16 | // contributors may be used to endorse or promote products derived from 17 | // this software without specific prior written permission. 18 | // 19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | // 31 | // Test schema for proto3 messages. This test schema is used by: 32 | // 33 | // - benchmarks 34 | // - fuzz tests 35 | // - conformance tests 36 | // 37 | 38 | syntax = "proto3"; 39 | 40 | package protobuf_test_messages.proto3; 41 | 42 | option java_package = "com.google.protobuf_test_messages.proto3"; 43 | option objc_class_prefix = "Proto3"; 44 | 45 | // This is the default, but we specify it here explicitly. 46 | option optimize_for = SPEED; 47 | 48 | import "google/protobuf/any.proto"; 49 | import "google/protobuf/duration.proto"; 50 | import "google/protobuf/field_mask.proto"; 51 | import "google/protobuf/struct.proto"; 52 | import "google/protobuf/timestamp.proto"; 53 | import "google/protobuf/wrappers.proto"; 54 | 55 | option cc_enable_arenas = true; 56 | 57 | // This proto includes every type of field in both singular and repeated 58 | // forms. 59 | // 60 | // Also, crucially, all messages and enums in this file are eventually 61 | // submessages of this message. So for example, a fuzz test of TestAllTypes 62 | // could trigger bugs that occur in any message type in this file. We verify 63 | // this stays true in a unit test. 64 | message TestAllTypesProto3 { 65 | message NestedMessage { 66 | int32 a = 1; 67 | TestAllTypesProto3 corecursive = 2; 68 | } 69 | 70 | enum NestedEnum { 71 | FOO = 0; 72 | BAR = 1; 73 | BAZ = 2; 74 | NEG = -1; // Intentionally negative. 75 | } 76 | 77 | enum AliasedEnum { 78 | option allow_alias = true; 79 | 80 | ALIAS_FOO = 0; 81 | ALIAS_BAR = 1; 82 | ALIAS_BAZ = 2; 83 | MOO = 2; 84 | moo = 2; 85 | bAz = 2; 86 | } 87 | 88 | // Singular 89 | int32 optional_int32 = 1; 90 | int64 optional_int64 = 2; 91 | uint32 optional_uint32 = 3; 92 | uint64 optional_uint64 = 4; 93 | sint32 optional_sint32 = 5; 94 | sint64 optional_sint64 = 6; 95 | fixed32 optional_fixed32 = 7; 96 | fixed64 optional_fixed64 = 8; 97 | sfixed32 optional_sfixed32 = 9; 98 | sfixed64 optional_sfixed64 = 10; 99 | float optional_float = 11; 100 | double optional_double = 12; 101 | bool optional_bool = 13; 102 | string optional_string = 14; 103 | bytes optional_bytes = 15; 104 | 105 | NestedMessage optional_nested_message = 18; 106 | ForeignMessage optional_foreign_message = 19; 107 | 108 | NestedEnum optional_nested_enum = 21; 109 | ForeignEnum optional_foreign_enum = 22; 110 | AliasedEnum optional_aliased_enum = 23; 111 | 112 | string optional_string_piece = 24 [ctype = STRING_PIECE]; 113 | string optional_cord = 25 [ctype = CORD]; 114 | 115 | TestAllTypesProto3 recursive_message = 27; 116 | 117 | // Repeated 118 | repeated int32 repeated_int32 = 31; 119 | repeated int64 repeated_int64 = 32; 120 | repeated uint32 repeated_uint32 = 33; 121 | repeated uint64 repeated_uint64 = 34; 122 | repeated sint32 repeated_sint32 = 35; 123 | repeated sint64 repeated_sint64 = 36; 124 | repeated fixed32 repeated_fixed32 = 37; 125 | repeated fixed64 repeated_fixed64 = 38; 126 | repeated sfixed32 repeated_sfixed32 = 39; 127 | repeated sfixed64 repeated_sfixed64 = 40; 128 | repeated float repeated_float = 41; 129 | repeated double repeated_double = 42; 130 | repeated bool repeated_bool = 43; 131 | repeated string repeated_string = 44; 132 | repeated bytes repeated_bytes = 45; 133 | 134 | repeated NestedMessage repeated_nested_message = 48; 135 | repeated ForeignMessage repeated_foreign_message = 49; 136 | 137 | repeated NestedEnum repeated_nested_enum = 51; 138 | repeated ForeignEnum repeated_foreign_enum = 52; 139 | 140 | repeated string repeated_string_piece = 54 [ctype = STRING_PIECE]; 141 | repeated string repeated_cord = 55 [ctype = CORD]; 142 | 143 | // Packed 144 | repeated int32 packed_int32 = 75 [packed = true]; 145 | repeated int64 packed_int64 = 76 [packed = true]; 146 | repeated uint32 packed_uint32 = 77 [packed = true]; 147 | repeated uint64 packed_uint64 = 78 [packed = true]; 148 | repeated sint32 packed_sint32 = 79 [packed = true]; 149 | repeated sint64 packed_sint64 = 80 [packed = true]; 150 | repeated fixed32 packed_fixed32 = 81 [packed = true]; 151 | repeated fixed64 packed_fixed64 = 82 [packed = true]; 152 | repeated sfixed32 packed_sfixed32 = 83 [packed = true]; 153 | repeated sfixed64 packed_sfixed64 = 84 [packed = true]; 154 | repeated float packed_float = 85 [packed = true]; 155 | repeated double packed_double = 86 [packed = true]; 156 | repeated bool packed_bool = 87 [packed = true]; 157 | repeated NestedEnum packed_nested_enum = 88 [packed = true]; 158 | 159 | // Unpacked 160 | repeated int32 unpacked_int32 = 89 [packed = false]; 161 | repeated int64 unpacked_int64 = 90 [packed = false]; 162 | repeated uint32 unpacked_uint32 = 91 [packed = false]; 163 | repeated uint64 unpacked_uint64 = 92 [packed = false]; 164 | repeated sint32 unpacked_sint32 = 93 [packed = false]; 165 | repeated sint64 unpacked_sint64 = 94 [packed = false]; 166 | repeated fixed32 unpacked_fixed32 = 95 [packed = false]; 167 | repeated fixed64 unpacked_fixed64 = 96 [packed = false]; 168 | repeated sfixed32 unpacked_sfixed32 = 97 [packed = false]; 169 | repeated sfixed64 unpacked_sfixed64 = 98 [packed = false]; 170 | repeated float unpacked_float = 99 [packed = false]; 171 | repeated double unpacked_double = 100 [packed = false]; 172 | repeated bool unpacked_bool = 101 [packed = false]; 173 | repeated NestedEnum unpacked_nested_enum = 102 [packed = false]; 174 | 175 | // Map 176 | map map_int32_int32 = 56; 177 | map map_int64_int64 = 57; 178 | map map_uint32_uint32 = 58; 179 | map map_uint64_uint64 = 59; 180 | map map_sint32_sint32 = 60; 181 | map map_sint64_sint64 = 61; 182 | map map_fixed32_fixed32 = 62; 183 | map map_fixed64_fixed64 = 63; 184 | map map_sfixed32_sfixed32 = 64; 185 | map map_sfixed64_sfixed64 = 65; 186 | map map_int32_float = 66; 187 | map map_int32_double = 67; 188 | map map_bool_bool = 68; 189 | map map_string_string = 69; 190 | map map_string_bytes = 70; 191 | map map_string_nested_message = 71; 192 | map map_string_foreign_message = 72; 193 | map map_string_nested_enum = 73; 194 | map map_string_foreign_enum = 74; 195 | 196 | oneof oneof_field { 197 | uint32 oneof_uint32 = 111; 198 | NestedMessage oneof_nested_message = 112; 199 | string oneof_string = 113; 200 | bytes oneof_bytes = 114; 201 | bool oneof_bool = 115; 202 | uint64 oneof_uint64 = 116; 203 | float oneof_float = 117; 204 | double oneof_double = 118; 205 | NestedEnum oneof_enum = 119; 206 | google.protobuf.NullValue oneof_null_value = 120; 207 | } 208 | 209 | // Well-known types 210 | google.protobuf.BoolValue optional_bool_wrapper = 201; 211 | google.protobuf.Int32Value optional_int32_wrapper = 202; 212 | google.protobuf.Int64Value optional_int64_wrapper = 203; 213 | google.protobuf.UInt32Value optional_uint32_wrapper = 204; 214 | google.protobuf.UInt64Value optional_uint64_wrapper = 205; 215 | google.protobuf.FloatValue optional_float_wrapper = 206; 216 | google.protobuf.DoubleValue optional_double_wrapper = 207; 217 | google.protobuf.StringValue optional_string_wrapper = 208; 218 | google.protobuf.BytesValue optional_bytes_wrapper = 209; 219 | 220 | repeated google.protobuf.BoolValue repeated_bool_wrapper = 211; 221 | repeated google.protobuf.Int32Value repeated_int32_wrapper = 212; 222 | repeated google.protobuf.Int64Value repeated_int64_wrapper = 213; 223 | repeated google.protobuf.UInt32Value repeated_uint32_wrapper = 214; 224 | repeated google.protobuf.UInt64Value repeated_uint64_wrapper = 215; 225 | repeated google.protobuf.FloatValue repeated_float_wrapper = 216; 226 | repeated google.protobuf.DoubleValue repeated_double_wrapper = 217; 227 | repeated google.protobuf.StringValue repeated_string_wrapper = 218; 228 | repeated google.protobuf.BytesValue repeated_bytes_wrapper = 219; 229 | 230 | google.protobuf.Duration optional_duration = 301; 231 | google.protobuf.Timestamp optional_timestamp = 302; 232 | google.protobuf.FieldMask optional_field_mask = 303; 233 | google.protobuf.Struct optional_struct = 304; 234 | google.protobuf.Any optional_any = 305; 235 | google.protobuf.Value optional_value = 306; 236 | google.protobuf.NullValue optional_null_value = 307; 237 | 238 | repeated google.protobuf.Duration repeated_duration = 311; 239 | repeated google.protobuf.Timestamp repeated_timestamp = 312; 240 | repeated google.protobuf.FieldMask repeated_fieldmask = 313; 241 | repeated google.protobuf.Struct repeated_struct = 324; 242 | repeated google.protobuf.Any repeated_any = 315; 243 | repeated google.protobuf.Value repeated_value = 316; 244 | repeated google.protobuf.ListValue repeated_list_value = 317; 245 | 246 | // Test field-name-to-JSON-name convention. 247 | // (protobuf says names can be any valid C/C++ identifier.) 248 | int32 fieldname1 = 401; 249 | int32 field_name2 = 402; 250 | int32 _field_name3 = 403; 251 | int32 field__name4_ = 404; 252 | int32 field0name5 = 405; 253 | int32 field_0_name6 = 406; 254 | int32 fieldName7 = 407; 255 | int32 FieldName8 = 408; 256 | int32 field_Name9 = 409; 257 | int32 Field_Name10 = 410; 258 | int32 FIELD_NAME11 = 411; 259 | int32 FIELD_name12 = 412; 260 | int32 __field_name13 = 413; 261 | int32 __Field_name14 = 414; 262 | int32 field__name15 = 415; 263 | int32 field__Name16 = 416; 264 | int32 field_name17__ = 417; 265 | int32 Field_name18__ = 418; 266 | 267 | // Reserved for testing unknown fields 268 | reserved 501 to 510; 269 | } 270 | 271 | message ForeignMessage { 272 | int32 c = 1; 273 | } 274 | 275 | enum ForeignEnum { 276 | FOREIGN_FOO = 0; 277 | FOREIGN_BAR = 1; 278 | FOREIGN_BAZ = 2; 279 | } 280 | 281 | message NullHypothesisProto3 {} 282 | 283 | message EnumOnlyProto3 { 284 | enum Bool { 285 | kFalse = 0; 286 | kTrue = 1; 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /examples/protoc/src/haberdasher_pb.js: -------------------------------------------------------------------------------- 1 | // source: src/haberdasher.proto 2 | /** 3 | * @fileoverview 4 | * @enhanceable 5 | * @suppress {missingRequire} reports error on implicit type usages. 6 | * @suppress {messageConventions} JS Compiler reports an error if a variable or 7 | * field starts with 'MSG_' and isn't a translatable message. 8 | * @public 9 | */ 10 | // GENERATED CODE -- DO NOT EDIT! 11 | /* eslint-disable */ 12 | // @ts-nocheck 13 | 14 | var jspb = require("google-protobuf"); 15 | var goog = jspb; 16 | var global = function () { 17 | if (this) { 18 | return this; 19 | } 20 | if (typeof window !== "undefined") { 21 | return window; 22 | } 23 | if (typeof global !== "undefined") { 24 | return global; 25 | } 26 | if (typeof self !== "undefined") { 27 | return self; 28 | } 29 | return Function("return this")(); 30 | }.call(null); 31 | 32 | goog.exportSymbol("proto.Hat", null, global); 33 | goog.exportSymbol("proto.Size", null, global); 34 | /** 35 | * Generated by JsPbCodeGenerator. 36 | * @param {Array=} opt_data Optional initial data array, typically from a 37 | * server response, or constructed directly in Javascript. The array is used 38 | * in place and becomes part of the constructed object. It is not cloned. 39 | * If no data is provided, the constructed object will be empty, but still 40 | * valid. 41 | * @extends {jspb.Message} 42 | * @constructor 43 | */ 44 | proto.Size = function (opt_data) { 45 | jspb.Message.initialize(this, opt_data, 0, -1, null, null); 46 | }; 47 | goog.inherits(proto.Size, jspb.Message); 48 | if (goog.DEBUG && !COMPILED) { 49 | /** 50 | * @public 51 | * @override 52 | */ 53 | proto.Size.displayName = "proto.Size"; 54 | } 55 | /** 56 | * Generated by JsPbCodeGenerator. 57 | * @param {Array=} opt_data Optional initial data array, typically from a 58 | * server response, or constructed directly in Javascript. The array is used 59 | * in place and becomes part of the constructed object. It is not cloned. 60 | * If no data is provided, the constructed object will be empty, but still 61 | * valid. 62 | * @extends {jspb.Message} 63 | * @constructor 64 | */ 65 | proto.Hat = function (opt_data) { 66 | jspb.Message.initialize(this, opt_data, 0, -1, null, null); 67 | }; 68 | goog.inherits(proto.Hat, jspb.Message); 69 | if (goog.DEBUG && !COMPILED) { 70 | /** 71 | * @public 72 | * @override 73 | */ 74 | proto.Hat.displayName = "proto.Hat"; 75 | } 76 | 77 | if (jspb.Message.GENERATE_TO_OBJECT) { 78 | /** 79 | * Creates an object representation of this proto. 80 | * Field names that are reserved in JavaScript and will be renamed to pb_name. 81 | * Optional fields that are not set will be set to undefined. 82 | * To access a reserved field use, foo.pb_, eg, foo.pb_default. 83 | * For the list of reserved names please see: 84 | * net/proto2/compiler/js/internal/generator.cc#kKeyword. 85 | * @param {boolean=} opt_includeInstance Deprecated. whether to include the 86 | * JSPB instance for transitional soy proto support: 87 | * http://goto/soy-param-migration 88 | * @return {!Object} 89 | */ 90 | proto.Size.prototype.toObject = function (opt_includeInstance) { 91 | return proto.Size.toObject(opt_includeInstance, this); 92 | }; 93 | 94 | /** 95 | * Static version of the {@see toObject} method. 96 | * @param {boolean|undefined} includeInstance Deprecated. Whether to include 97 | * the JSPB instance for transitional soy proto support: 98 | * http://goto/soy-param-migration 99 | * @param {!proto.Size} msg The msg instance to transform. 100 | * @return {!Object} 101 | * @suppress {unusedLocalVariables} f is only used for nested messages 102 | */ 103 | proto.Size.toObject = function (includeInstance, msg) { 104 | var f, 105 | obj = { 106 | inches: jspb.Message.getFieldWithDefault(msg, 1, 0), 107 | }; 108 | 109 | if (includeInstance) { 110 | obj.$jspbMessageInstance = msg; 111 | } 112 | return obj; 113 | }; 114 | } 115 | 116 | /** 117 | * Deserializes binary data (in protobuf wire format). 118 | * @param {jspb.ByteSource} bytes The bytes to deserialize. 119 | * @return {!proto.Size} 120 | */ 121 | proto.Size.deserializeBinary = function (bytes) { 122 | var reader = new jspb.BinaryReader(bytes); 123 | var msg = new proto.Size(); 124 | return proto.Size.deserializeBinaryFromReader(msg, reader); 125 | }; 126 | 127 | /** 128 | * Deserializes binary data (in protobuf wire format) from the 129 | * given reader into the given message object. 130 | * @param {!proto.Size} msg The message object to deserialize into. 131 | * @param {!jspb.BinaryReader} reader The BinaryReader to use. 132 | * @return {!proto.Size} 133 | */ 134 | proto.Size.deserializeBinaryFromReader = function (msg, reader) { 135 | while (reader.nextField()) { 136 | if (reader.isEndGroup()) { 137 | break; 138 | } 139 | var field = reader.getFieldNumber(); 140 | switch (field) { 141 | case 1: 142 | var value = /** @type {number} */ (reader.readInt32()); 143 | msg.setInches(value); 144 | break; 145 | default: 146 | reader.skipField(); 147 | break; 148 | } 149 | } 150 | return msg; 151 | }; 152 | 153 | /** 154 | * Serializes the message to binary data (in protobuf wire format). 155 | * @return {!Uint8Array} 156 | */ 157 | proto.Size.prototype.serializeBinary = function () { 158 | var writer = new jspb.BinaryWriter(); 159 | proto.Size.serializeBinaryToWriter(this, writer); 160 | return writer.getResultBuffer(); 161 | }; 162 | 163 | /** 164 | * Serializes the given message to binary data (in protobuf wire 165 | * format), writing to the given BinaryWriter. 166 | * @param {!proto.Size} message 167 | * @param {!jspb.BinaryWriter} writer 168 | * @suppress {unusedLocalVariables} f is only used for nested messages 169 | */ 170 | proto.Size.serializeBinaryToWriter = function (message, writer) { 171 | var f = undefined; 172 | f = message.getInches(); 173 | if (f !== 0) { 174 | writer.writeInt32(1, f); 175 | } 176 | }; 177 | 178 | /** 179 | * optional int32 inches = 1; 180 | * @return {number} 181 | */ 182 | proto.Size.prototype.getInches = function () { 183 | return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 1, 0)); 184 | }; 185 | 186 | /** 187 | * @param {number} value 188 | * @return {!proto.Size} returns this 189 | */ 190 | proto.Size.prototype.setInches = function (value) { 191 | return jspb.Message.setProto3IntField(this, 1, value); 192 | }; 193 | 194 | if (jspb.Message.GENERATE_TO_OBJECT) { 195 | /** 196 | * Creates an object representation of this proto. 197 | * Field names that are reserved in JavaScript and will be renamed to pb_name. 198 | * Optional fields that are not set will be set to undefined. 199 | * To access a reserved field use, foo.pb_, eg, foo.pb_default. 200 | * For the list of reserved names please see: 201 | * net/proto2/compiler/js/internal/generator.cc#kKeyword. 202 | * @param {boolean=} opt_includeInstance Deprecated. whether to include the 203 | * JSPB instance for transitional soy proto support: 204 | * http://goto/soy-param-migration 205 | * @return {!Object} 206 | */ 207 | proto.Hat.prototype.toObject = function (opt_includeInstance) { 208 | return proto.Hat.toObject(opt_includeInstance, this); 209 | }; 210 | 211 | /** 212 | * Static version of the {@see toObject} method. 213 | * @param {boolean|undefined} includeInstance Deprecated. Whether to include 214 | * the JSPB instance for transitional soy proto support: 215 | * http://goto/soy-param-migration 216 | * @param {!proto.Hat} msg The msg instance to transform. 217 | * @return {!Object} 218 | * @suppress {unusedLocalVariables} f is only used for nested messages 219 | */ 220 | proto.Hat.toObject = function (includeInstance, msg) { 221 | var f, 222 | obj = { 223 | inches: jspb.Message.getFieldWithDefault(msg, 1, 0), 224 | color: jspb.Message.getFieldWithDefault(msg, 2, ""), 225 | name: jspb.Message.getFieldWithDefault(msg, 3, ""), 226 | }; 227 | 228 | if (includeInstance) { 229 | obj.$jspbMessageInstance = msg; 230 | } 231 | return obj; 232 | }; 233 | } 234 | 235 | /** 236 | * Deserializes binary data (in protobuf wire format). 237 | * @param {jspb.ByteSource} bytes The bytes to deserialize. 238 | * @return {!proto.Hat} 239 | */ 240 | proto.Hat.deserializeBinary = function (bytes) { 241 | var reader = new jspb.BinaryReader(bytes); 242 | var msg = new proto.Hat(); 243 | return proto.Hat.deserializeBinaryFromReader(msg, reader); 244 | }; 245 | 246 | /** 247 | * Deserializes binary data (in protobuf wire format) from the 248 | * given reader into the given message object. 249 | * @param {!proto.Hat} msg The message object to deserialize into. 250 | * @param {!jspb.BinaryReader} reader The BinaryReader to use. 251 | * @return {!proto.Hat} 252 | */ 253 | proto.Hat.deserializeBinaryFromReader = function (msg, reader) { 254 | while (reader.nextField()) { 255 | if (reader.isEndGroup()) { 256 | break; 257 | } 258 | var field = reader.getFieldNumber(); 259 | switch (field) { 260 | case 1: 261 | var value = /** @type {number} */ (reader.readInt32()); 262 | msg.setInches(value); 263 | break; 264 | case 2: 265 | var value = /** @type {string} */ (reader.readString()); 266 | msg.setColor(value); 267 | break; 268 | case 3: 269 | var value = /** @type {string} */ (reader.readString()); 270 | msg.setName(value); 271 | break; 272 | default: 273 | reader.skipField(); 274 | break; 275 | } 276 | } 277 | return msg; 278 | }; 279 | 280 | /** 281 | * Serializes the message to binary data (in protobuf wire format). 282 | * @return {!Uint8Array} 283 | */ 284 | proto.Hat.prototype.serializeBinary = function () { 285 | var writer = new jspb.BinaryWriter(); 286 | proto.Hat.serializeBinaryToWriter(this, writer); 287 | return writer.getResultBuffer(); 288 | }; 289 | 290 | /** 291 | * Serializes the given message to binary data (in protobuf wire 292 | * format), writing to the given BinaryWriter. 293 | * @param {!proto.Hat} message 294 | * @param {!jspb.BinaryWriter} writer 295 | * @suppress {unusedLocalVariables} f is only used for nested messages 296 | */ 297 | proto.Hat.serializeBinaryToWriter = function (message, writer) { 298 | var f = undefined; 299 | f = message.getInches(); 300 | if (f !== 0) { 301 | writer.writeInt32(1, f); 302 | } 303 | f = message.getColor(); 304 | if (f.length > 0) { 305 | writer.writeString(2, f); 306 | } 307 | f = message.getName(); 308 | if (f.length > 0) { 309 | writer.writeString(3, f); 310 | } 311 | }; 312 | 313 | /** 314 | * optional int32 inches = 1; 315 | * @return {number} 316 | */ 317 | proto.Hat.prototype.getInches = function () { 318 | return /** @type {number} */ (jspb.Message.getFieldWithDefault(this, 1, 0)); 319 | }; 320 | 321 | /** 322 | * @param {number} value 323 | * @return {!proto.Hat} returns this 324 | */ 325 | proto.Hat.prototype.setInches = function (value) { 326 | return jspb.Message.setProto3IntField(this, 1, value); 327 | }; 328 | 329 | /** 330 | * optional string color = 2; 331 | * @return {string} 332 | */ 333 | proto.Hat.prototype.getColor = function () { 334 | return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); 335 | }; 336 | 337 | /** 338 | * @param {string} value 339 | * @return {!proto.Hat} returns this 340 | */ 341 | proto.Hat.prototype.setColor = function (value) { 342 | return jspb.Message.setProto3StringField(this, 2, value); 343 | }; 344 | 345 | /** 346 | * optional string name = 3; 347 | * @return {string} 348 | */ 349 | proto.Hat.prototype.getName = function () { 350 | return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, "")); 351 | }; 352 | 353 | /** 354 | * @param {string} value 355 | * @return {!proto.Hat} returns this 356 | */ 357 | proto.Hat.prototype.setName = function (value) { 358 | return jspb.Message.setProto3StringField(this, 3, value); 359 | }; 360 | 361 | goog.object.extend(exports, proto); 362 | -------------------------------------------------------------------------------- /packages/protoscript/src/runtime/encoder.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "./goog/asserts.js"; 2 | import { 3 | decimalStringToHash64, 4 | split64High, 5 | split64Low, 6 | splitFloat32, 7 | splitFloat64, 8 | splitHash64, 9 | splitInt64, 10 | splitUint64, 11 | splitZigzag64, 12 | toZigzag64, 13 | } from "./utils.js"; 14 | import { 15 | TWO_TO_31, 16 | TWO_TO_63, 17 | TWO_TO_32, 18 | TWO_TO_64, 19 | FLOAT32_MAX, 20 | FLOAT64_MAX, 21 | } from "./constants.js"; 22 | import { stringToUint8Array } from "./goog/crypt.js"; 23 | 24 | /** 25 | * BinaryEncoder implements encoders for all the wire types specified in 26 | * https://developers.google.com/protocol-buffers/docs/encoding. 27 | */ 28 | export class BinaryEncoder { 29 | buffer_: number[]; 30 | constructor() { 31 | this.buffer_ = []; 32 | } 33 | 34 | length(): number { 35 | return this.buffer_.length; 36 | } 37 | 38 | end(): Array { 39 | const buffer = this.buffer_; 40 | this.buffer_ = []; 41 | return buffer; 42 | } 43 | 44 | /** 45 | * Encodes a 64-bit integer in 32:32 split representation into its wire-format 46 | * varint representation and stores it in the buffer. 47 | */ 48 | writeSplitVarint64(lowBits: number, highBits: number) { 49 | assert(lowBits == Math.floor(lowBits)); 50 | assert(highBits == Math.floor(highBits)); 51 | assert(lowBits >= 0 && lowBits < TWO_TO_32); 52 | assert(highBits >= 0 && highBits < TWO_TO_32); 53 | 54 | // Break the binary representation into chunks of 7 bits, set the 8th bit 55 | // in each chunk if it's not the final chunk, and append to the result. 56 | while (highBits > 0 || lowBits > 127) { 57 | this.buffer_.push((lowBits & 0x7f) | 0x80); 58 | lowBits = ((lowBits >>> 7) | (highBits << 25)) >>> 0; 59 | highBits = highBits >>> 7; 60 | } 61 | this.buffer_.push(lowBits); 62 | } 63 | 64 | /** 65 | * Encodes a 64-bit integer in 32:32 split representation into its wire-format 66 | * fixed representation and stores it in the buffer. 67 | */ 68 | writeSplitFixed64(lowBits: number, highBits: number) { 69 | assert(lowBits == Math.floor(lowBits)); 70 | assert(highBits == Math.floor(highBits)); 71 | assert(lowBits >= 0 && lowBits < TWO_TO_32); 72 | assert(highBits >= 0 && highBits < TWO_TO_32); 73 | this.writeUint32(lowBits); 74 | this.writeUint32(highBits); 75 | } 76 | 77 | /** 78 | * Encodes a 32-bit unsigned integer into its wire-format varint representation 79 | * and stores it in the buffer. 80 | */ 81 | writeUnsignedVarint32(value: number) { 82 | assert(value == Math.floor(value)); 83 | assert(value >= 0 && value < TWO_TO_32); 84 | 85 | while (value > 127) { 86 | this.buffer_.push((value & 0x7f) | 0x80); 87 | value = value >>> 7; 88 | } 89 | 90 | this.buffer_.push(value); 91 | } 92 | 93 | /** 94 | * Encodes a 32-bit signed integer into its wire-format varint representation 95 | * and stores it in the buffer. 96 | */ 97 | writeSignedVarint32(value: number) { 98 | assert(value == Math.floor(value)); 99 | assert(value >= -TWO_TO_31 && value < TWO_TO_31); 100 | 101 | // Use the unsigned version if the value is not negative. 102 | if (value >= 0) { 103 | this.writeUnsignedVarint32(value); 104 | return; 105 | } 106 | 107 | // Write nine bytes with a _signed_ right shift so we preserve the sign bit. 108 | for (let i = 0; i < 9; i++) { 109 | this.buffer_.push((value & 0x7f) | 0x80); 110 | value = value >> 7; 111 | } 112 | 113 | // The above loop writes out 63 bits, so the last byte is always the sign bit 114 | // which is always set for negative numbers. 115 | this.buffer_.push(1); 116 | } 117 | 118 | /** 119 | * Encodes a 64-bit unsigned integer into its wire-format varint representation 120 | * and stores it in the buffer. Integers that are not representable in 64 bits 121 | * will be truncated. 122 | */ 123 | writeUnsignedVarint64(value: number) { 124 | assert(value == Math.floor(value)); 125 | assert(value >= 0 && value < TWO_TO_64); 126 | splitInt64(value); 127 | this.writeSplitVarint64(split64Low, split64High); 128 | } 129 | 130 | /** 131 | * Encodes a 64-bit signed integer into its wire-format varint representation 132 | * and stores it in the buffer. Integers that are not representable in 64 bits 133 | * will be truncated. 134 | */ 135 | writeSignedVarint64(value: number) { 136 | assert(value == Math.floor(value)); 137 | assert(value >= -TWO_TO_63 && value < TWO_TO_63); 138 | splitInt64(value); 139 | this.writeSplitVarint64(split64Low, split64High); 140 | } 141 | 142 | /** 143 | * Encodes a JavaScript integer into its wire-format, zigzag-encoded varint 144 | * representation and stores it in the buffer. 145 | */ 146 | writeZigzagVarint32(value: number) { 147 | assert(value == Math.floor(value)); 148 | assert(value >= -TWO_TO_31 && value < TWO_TO_31); 149 | this.writeUnsignedVarint32(((value << 1) ^ (value >> 31)) >>> 0); 150 | } 151 | 152 | /** 153 | * Encodes a JavaScript integer into its wire-format, zigzag-encoded varint 154 | * representation and stores it in the buffer. Integers not representable in 64 155 | * bits will be truncated. 156 | */ 157 | writeZigzagVarint64(value: number) { 158 | assert(value == Math.floor(value)); 159 | assert(value >= -TWO_TO_63 && value < TWO_TO_63); 160 | splitZigzag64(value); 161 | this.writeSplitVarint64(split64Low, split64High); 162 | } 163 | 164 | /** 165 | * Encodes a JavaScript decimal string into its wire-format, zigzag-encoded 166 | * varint representation and stores it in the buffer. Integers not representable 167 | * in 64 bits will be truncated. 168 | */ 169 | writeZigzagVarint64String(value: string) { 170 | this.writeZigzagVarintHash64(decimalStringToHash64(value)); 171 | } 172 | 173 | /** 174 | * Writes a 64-bit hash string (8 characters @ 8 bits of data each) to the 175 | * buffer as a zigzag varint. 176 | */ 177 | writeZigzagVarintHash64(hash: string) { 178 | splitHash64(hash); 179 | toZigzag64(split64Low, split64High, (lo, hi) => { 180 | this.writeSplitVarint64(lo >>> 0, hi >>> 0); 181 | }); 182 | } 183 | 184 | /** 185 | * Writes an 8-bit unsigned integer to the buffer. Numbers outside the range 186 | * [0,2^8) will be truncated. 187 | */ 188 | writeUint8(value: number) { 189 | assert(value == Math.floor(value)); 190 | assert(value >= 0 && value < 256); 191 | this.buffer_.push((value >>> 0) & 0xff); 192 | } 193 | 194 | /** 195 | * Writes a 16-bit unsigned integer to the buffer. Numbers outside the 196 | * range [0,2^16) will be truncated. 197 | */ 198 | writeUint16(value: number) { 199 | assert(value == Math.floor(value)); 200 | assert(value >= 0 && value < 65536); 201 | this.buffer_.push((value >>> 0) & 0xff); 202 | this.buffer_.push((value >>> 8) & 0xff); 203 | } 204 | 205 | /** 206 | * Writes a 32-bit unsigned integer to the buffer. Numbers outside the 207 | * range [0,2^32) will be truncated. 208 | */ 209 | writeUint32(value: number) { 210 | assert(value == Math.floor(value)); 211 | assert(value >= 0 && value < TWO_TO_32); 212 | this.buffer_.push((value >>> 0) & 0xff); 213 | this.buffer_.push((value >>> 8) & 0xff); 214 | this.buffer_.push((value >>> 16) & 0xff); 215 | this.buffer_.push((value >>> 24) & 0xff); 216 | } 217 | 218 | /** 219 | * Writes a 64-bit unsigned integer to the buffer. Numbers outside the 220 | * range [0,2^64) will be truncated. 221 | */ 222 | writeUint64(value: number) { 223 | assert(value == Math.floor(value)); 224 | assert(value >= 0 && value < TWO_TO_64); 225 | splitUint64(value); 226 | this.writeUint32(split64Low); 227 | this.writeUint32(split64High); 228 | } 229 | 230 | /** 231 | * Writes an 8-bit integer to the buffer. Numbers outside the range 232 | * [-2^7,2^7) will be truncated. 233 | */ 234 | writeInt8(value: number) { 235 | assert(value == Math.floor(value)); 236 | assert(value >= -128 && value < 128); 237 | this.buffer_.push((value >>> 0) & 0xff); 238 | } 239 | 240 | /** 241 | * Writes a 16-bit integer to the buffer. Numbers outside the range 242 | * [-2^15,2^15) will be truncated. 243 | */ 244 | writeInt16(value: number) { 245 | assert(value == Math.floor(value)); 246 | assert(value >= -32768 && value < 32768); 247 | this.buffer_.push((value >>> 0) & 0xff); 248 | this.buffer_.push((value >>> 8) & 0xff); 249 | } 250 | 251 | /** 252 | * Writes a 32-bit integer to the buffer. Numbers outside the range 253 | * [-2^31,2^31) will be truncated. 254 | */ 255 | writeInt32(value: number) { 256 | assert(value == Math.floor(value)); 257 | assert(value >= -TWO_TO_31 && value < TWO_TO_31); 258 | this.buffer_.push((value >>> 0) & 0xff); 259 | this.buffer_.push((value >>> 8) & 0xff); 260 | this.buffer_.push((value >>> 16) & 0xff); 261 | this.buffer_.push((value >>> 24) & 0xff); 262 | } 263 | 264 | /** 265 | * Writes a 64-bit integer to the buffer. Numbers outside the range 266 | * [-2^63,2^63) will be truncated. 267 | */ 268 | writeInt64(value: number) { 269 | assert(value == Math.floor(value)); 270 | assert(value >= -TWO_TO_63 && value < TWO_TO_63); 271 | splitInt64(value); 272 | this.writeSplitFixed64(split64Low, split64High); 273 | } 274 | 275 | /** 276 | * Writes a 64-bit integer decimal strings to the buffer. Numbers outside the 277 | * range [-2^63,2^63) will be truncated. 278 | */ 279 | writeInt64String(value: string) { 280 | assert( 281 | (value as unknown as number) == Math.floor(value as unknown as number), 282 | ); 283 | assert(+value >= -TWO_TO_63 && +value < TWO_TO_63); 284 | splitHash64(decimalStringToHash64(value)); 285 | this.writeSplitFixed64(split64Low, split64High); 286 | } 287 | 288 | /** 289 | * Writes a single-precision floating point value to the buffer. Numbers 290 | * requiring more than 32 bits of precision will be truncated. 291 | */ 292 | writeFloat(value: number) { 293 | assert( 294 | value === Infinity || 295 | value === -Infinity || 296 | isNaN(value) || 297 | (value >= -FLOAT32_MAX && value <= FLOAT32_MAX), 298 | ); 299 | splitFloat32(value); 300 | this.writeUint32(split64Low); 301 | } 302 | 303 | /** 304 | * Writes a double-precision floating point value to the buffer. As this is 305 | * the native format used by JavaScript, no precision will be lost. 306 | */ 307 | writeDouble(value: number) { 308 | assert( 309 | value === Infinity || 310 | value === -Infinity || 311 | isNaN(value) || 312 | (value >= -FLOAT64_MAX && value <= FLOAT64_MAX), 313 | ); 314 | splitFloat64(value); 315 | this.writeUint32(split64Low); 316 | this.writeUint32(split64High); 317 | } 318 | 319 | /** 320 | * Writes a boolean value to the buffer as a varint. We allow numbers as input 321 | * because the JSPB code generator uses 0/1 instead of true/false to save space 322 | * in the string representation of the proto. 323 | */ 324 | writeBool(value: boolean | number) { 325 | assert(typeof value === "boolean" || typeof value === "number"); 326 | this.buffer_.push(value ? 1 : 0); 327 | } 328 | 329 | /** 330 | * Writes an enum value to the buffer as a varint. 331 | */ 332 | writeEnum(value: number) { 333 | assert(value == Math.floor(value)); 334 | assert(value >= -TWO_TO_31 && value < TWO_TO_31); 335 | this.writeSignedVarint32(value); 336 | } 337 | 338 | /** 339 | * Writes an arbitrary byte array to the buffer. 340 | */ 341 | writeBytes(bytes: Uint8Array) { 342 | this.buffer_.push(...bytes); 343 | } 344 | 345 | /** 346 | * Writes a 64-bit hash string (8 characters @ 8 bits of data each) to the 347 | * buffer as a varint. 348 | */ 349 | writeVarintHash64(hash: string) { 350 | splitHash64(hash); 351 | this.writeSplitVarint64(split64Low, split64High); 352 | } 353 | 354 | /** 355 | * Writes a 64-bit hash string (8 characters @ 8 bits of data each) to the 356 | * buffer as a fixed64. 357 | */ 358 | writeFixedHash64(hash: string) { 359 | splitHash64(hash); 360 | this.writeUint32(split64Low); 361 | this.writeUint32(split64High); 362 | } 363 | 364 | /** 365 | * Writes a UTF16 Javascript string to the buffer encoded as UTF8. 366 | */ 367 | writeString(value: string): number { 368 | const oldLength = this.buffer_.length; 369 | const buffer = stringToUint8Array(value); 370 | buffer.forEach((val) => this.buffer_.push(val)); 371 | const length = this.buffer_.length - oldLength; 372 | return length; 373 | } 374 | } 375 | --------------------------------------------------------------------------------