{"id":16313118,"url":"https://github.com/blainehansen/macro-ts","last_synced_at":"2025-04-09T13:03:40.546Z","repository":{"id":46888063,"uuid":"278537141","full_name":"blainehansen/macro-ts","owner":"blainehansen","description":"An ergonomic typescript compiler that enables typesafe syntactic macros.","archived":false,"fork":false,"pushed_at":"2021-10-18T00:23:12.000Z","size":549,"stargazers_count":221,"open_issues_count":3,"forks_count":5,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-04-02T10:34:04.815Z","etag":null,"topics":["macros","metaprogramming","typesafe-macros","typescript"],"latest_commit_sha":null,"homepage":"https://blainehansen.me/post/macro-ts/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/blainehansen.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-07-10T04:23:16.000Z","updated_at":"2025-01-02T12:27:52.000Z","dependencies_parsed_at":"2022-09-23T08:00:16.827Z","dependency_job_id":null,"html_url":"https://github.com/blainehansen/macro-ts","commit_stats":null,"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/blainehansen%2Fmacro-ts","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/blainehansen%2Fmacro-ts/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/blainehansen%2Fmacro-ts/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/blainehansen%2Fmacro-ts/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/blainehansen","download_url":"https://codeload.github.com/blainehansen/macro-ts/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248045230,"owners_count":21038553,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["macros","metaprogramming","typesafe-macros","typescript"],"created_at":"2024-10-10T21:50:10.760Z","updated_at":"2025-04-09T13:03:40.529Z","avatar_url":"https://github.com/blainehansen.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# `macro-ts`\n\nAn ergonomic typescript compiler that enables typesafe syntactic macros.\n\nWrite code like this:\n\n```ts\n// an import macro\nimport ProductDetails from sql!!('./productDetails.sql')\n\n// a block macro\ndb!!;{\n  port = 5432; host = 'db'\n  user = DB_ENV_USER\n  password = DB_ENV_PASS,\n}\n\n// a decorator macro\n@get!!('/product/{id}')\nasync function productDetails(id: number) {\n  const queryResult = await db.query(ProductDetails.compile(id))\n  // a function macro\n  const products = required!!(queryResult)\n  const count = products.length\n  return { products, count }\n}\n```\n\nand have it transformed into something like the following, *and then fully typechecked*:\n\n```ts\n// import macros can inspect the contents of real files\n// and produce \"virtual\" typescript files\nimport ProductDetails from './productDetails.sql.ts'\n\n// block macros can inspect a list of statements\n// and expand to any other list of statements\nimport driver from 'some-database-driver-library'\nconst dbUser = process.env.DB_ENV_USER\nif (!dbUser) throw new Error(`DB_ENV_USER isn't set`)\nconst dbPassword = process.env.DB_ENV_PASS\nif (!dbPassword) throw new Error(`DB_ENV_PASS isn't set`)\nconst db = new driver.Pool({\n  port: 5432, host: 'db',\n  user: dbUser, password: dbPassword,\n})\n\n// a decorator macro can inspect the statement it's attached to\n// choose to replace that statement\n// and provide additional statements to place around it\nimport * as v from 'some-validation-library'\nconst productDetailsParamsValidator = v.object({ id: v.number })\nasync function productDetails(params: { [key: string]: unknown }) {\n  const paramsResult = productDetailsParamsValidator.validate(params)\n  if (paramsResult === null)\n    throw new v.ValidationError(params)\n  const { id } = paramsResult\n\n  const queryResult = await db.query(ProductDetails.compile(id))\n  // function macros can inspect the args it's passed\n  // return any expression to replace the function call\n  // and provide additional statements to place around it\n  if (queryResult === null)\n    throw new Error()\n  const products = queryResult.value\n  const count = products.length\n  return { products, count }\n}\napp.get('/product/{id}', productDetails)\n```\n\nTypesafe macros can unlock huge productivity gains for any development team. Enjoy!\n\n\n## Quickstart\n\nYou can quickly run or check code without a project configuration file. This method is appropriate for rapid prototyping or experimentation, will use the `anywhere` compilation environment, and will optionally attempt to to load macros from a `.macros.ts` file in the current working directory.\n\n```bash\nnpm install --save-dev @blainehansen/macro-ts\n\nnpx macro-ts run someScript.ts\nnpx macro-ts check 'someDir/**/*.ts'\n```\n\nWhen you want to create a proper project, create a `.macro-ts.toml` file to hold configuration specific to `macro-ts`.\n\n```toml\n# points to the file\n# where your macros are defined\n# this is the default\nmacros = '.macros.ts'\n\n# entry globs and compilation environments\n# for as many different directories as you want\n[[packages]]\nlocation = 'app'\nentry = 'main.ts'\n# you can give a package\n# more than one compilation environment\nenvironment = ['modernbrowser', 'legacybrowser']\n\n[[packages]]\nlocation = 'bin'\nentry = '*.ts'\nenvironment = 'node'\n\n[[packages]]\nlocation = 'lib'\nentry = '**/*.ts'\nexclude = '**/*.test.ts'\nenvironment = 'anywhere'\n```\n\n\n## Compilation environments\n\nIn the javascript world, we almost always write code with one of these intended execution environments, which effects what ambient type libraries typescript should include:\n\n- `browser`: requires the dom libraries and globals.\n- `webworker`: requires the webworker libraries and globals.\n- `node`: requires the node libraries and globals.\n- `anywhere`: shouldn't assume the existence of *any* special libraries or globals.\n\nTypescript has ways of including different type libraries for node and the browser, but they're a little clunky and inexact. `macro-ts` introduces the concept of compilation environments that allow you to easily choose the ambient types that should be available, as well as the typescript `target`.\n\nThere are five environment shorthands, which expand to an object of this type:\n\n```ts\nimport ts = require('typescript')\ntype CompilationEnvironment = {\n  platform: 'browser' | 'webworker' | 'node' | 'anywhere',\n  target: ts.ScriptTarget,\n};\n```\n\n- `legacybrowser`: `{ platform: 'browser', target: ts.ScriptTarget.ES5 }`\n- `modernbrowser`: `{ platform: 'browser', target: ts.ScriptTarget.Latest }`\n- `webworker`: `{ platform: 'webworker', target: ts.ScriptTarget.Latest }`\n- `node`: `{ platform: 'node', target: ts.ScriptTarget.Latest }`\n- `anywhere`: `{ platform: 'anywhere', target: ts.ScriptTarget.Latest }`\n\n## Dev mode\n\nBy default these settings are used when typechecking: `noUnusedParameters: true`, `noUnusedLocals: true`, `preserveConstEnums: false`, and `removeComments: true`.\n\nThese settings are all more appropriate for a release quality build. Both the config file and the cli however support a \"dev\" mode that inverts all these settings to their more lenient form.\n\n\n## Config format\n\nThe `.macro-ts.toml` file will accept configs matching this type:\n\n```ts\nexport type MacroTsConfig = {\n  macros?: string,\n  packages: {\n    location: string,\n    entry: string | [string, ...string[]],\n    exclude?: string | [string, ...string[]],\n    environment: ConfigEnv | [ConfigEnv, ...ConfigEnv[]],\n    dev?: boolean,\n  }[]\n}\n\ntype ConfigEnv =\n  | CompilationEnvironment\n  | 'legacybrowser' | 'modernbrowser'\n  | 'webworker'\n  | 'node'\n  | 'anywhere'\n\nexport type CompilationEnvironment = {\n  platform: 'browser' | 'webworker' | 'node' | 'anywhere',\n  target: ScriptTarget,\n}\n\nexport type ScriptTarget = Exclude\u003cts.ScriptTarget, ts.ScriptTarget.JSON\u003e;\n```\n\n\n## Cli usage\n\nThe cli has these global options:\n\n- `--help` (alias `-h`). Displays help message.\n- `--version` (alias `-v`). Displays the version.\n- `--dev` (alias `-d`). Performs typechecking in \"dev\" mode.\n\nThe cli has these subcommands:\n\n### `run \u003centryFile\u003e.ts`\n\nRuns the given file.\n\nSince this inherently means the code will be run in node, node appropriate typechecking settings are used, regardless of any package specific settings that could apply to the code. Rely on the `check` and `build` commands to correctly typecheck for intended release environments.\n\n```bash\nmacro-ts run main.ts\nmacro-ts --dev run playground.ts\n```\n\n### `check [entryGlob]`\n\nOnly typechecks the code, without running it or emitting any javascript.\n\nThe `entryGlob` is optional if a `.macro-ts.toml` file is present, and if not provided will check all packages in that config file.\n\n```bash\nmacro-ts check\nmacro-ts --dev check\nmacro-ts check 'dir/working/on/**/*.ts'\nmacro-ts --dev check 'other/dir/*.ts'\n```\n\n### `build`\n\nBuilds all configured packages, emitting them into `target/.dist`.\n\nRequires a `.macro-ts.toml` file.\n\nSince each package could be emitted with different settings based on the intended execution environment, any common modules will be compiled multiple times in different forms in different `.dist` directories.\n\n```bash\nmacro-ts build\nmacro-ts --dev build\n```\n\n## Writing macros\n\nHere's a simple macros file:\n\n```ts\nimport {\n  FunctionMacro, BlockMacro,\n  DecoratorMacro, ImportMacro,\n} from '@blainehansen/macro-ts'\n\nexport const macros = {\n  f: FunctionMacro(/* ... */),\n  b: BlockMacro(/* ... */),\n  d: DecoratorMacro(/* ... */),\n  i: ImportMacro(/* ... */),\n}\n```\n\nThe `macro-ts` cli expects the macros file to export a dictionary named `macros`. `macro-ts` provides the `FunctionMacro`, `BlockMacro`, `DecoratorMacro`, and `ImportMacro` constructor functions to make writing macros easier.\n\nAll macros are given a `MacroContext` object that contains helper functions for returning values from macros. These helpers basically all deal with `SpanResult\u003cT\u003e`, a type that signifies either that the macro was successful and is returning a value (using `Ok`), or that it failed and is returning some errors to show to the user (using `TsNodeErr` or `Err`). `TsNodeErr` is especially useful, since it allows you to give any typescript `Node` that will be highlighted as the source of the error, along with text describing it.\n\n`tsNodeWarn` and `warn` are also provided to allow you to give warnings to the user that don't necessarily require failure.\n\n```ts\nimport ts = require('typescript')\nexport type MacroContext = {\n  Ok: \u003cT\u003e(value: T) =\u003e SpanResult\u003cT\u003e,\n  TsNodeErr: (node: ts.TextRange, title: string, ...paragraphs: string[]) =\u003e SpanResult\u003cany\u003e,\n  Err: (fileName: string, title: string, ...paragraphs: string[]) =\u003e SpanResult\u003cany\u003e,\n  tsNodeWarn: (node: ts.TextRange, title: string, ...paragraphs: string[]) =\u003e void,\n  warn: (fileName: string, title: string, ...paragraphs: string[]) =\u003e void,\n};\n```\n\n\n### `FunctionMacro`\n\nAny expression can use this syntax: `macroName!!(expressions...)` to expand that expression.\n\n```ts\n// this code:\nlet a: undefined | number = 1\nconst v = required!!(a)\n\n// could expand to:\nlet a: undefined | number = 1\nif (a === undefined) throw new Error()\nconst v = a\n```\n\nExample:\n\n```ts\nimport ts = require('typescript')\nimport { FunctionMacro } from '@blainehansen/macro-ts'\n\nexport const macros = {\n  required: FunctionMacro((ctx, args) =\u003e {\n    if (args.length !== 1) return ctx.TsNodeErr(\n      args, 'Incorrect arguments',\n      'The \"required\" macro accepts exactly one argument.',\n    )\n\n    const target = args[0]\n    return ctx.Ok({\n      prepend: [ts.createIf(\n        ts.createBinary(\n          target,\n          ts.createToken(ts.SyntaxKind.EqualsEqualsEqualsToken),\n          ts.createIdentifier('undefined'),\n        ),\n        ts.createThrow(\n          ts.createNew(ts.createIdentifier('Error'), undefined, []),\n        ),\n        undefined,\n      )],\n      expression: target,\n      append: [],\n    })\n  }),\n}\n```\n\nType signature:\n\n```ts\nimport ts = require('typescript')\nexport type FunctionMacroFn = (\n  ctx: MacroContext,\n  args: ts.NodeArray\u003cts.Expression\u003e,\n  typeArgs: ts.NodeArray\u003cts.TypeNode\u003e | undefined,\n) =\u003e FunctionMacroResult\nexport type FunctionMacroResult = SpanResult\u003c{\n  prepend?: ts.Statement[],\n  expression: ts.Expression,\n  append?: ts.Statement[],\n}\u003e\nexport function FunctionMacro(execute: FunctionMacroFn) {\n  /* ... */\n}\n```\n\n\n### `BlockMacro`\n\nWithin a block of statements, you can use the `macroName!!;{ statements... }` syntax to expand those statements. Some examples of things that are possible:\n\n```ts\n// this code:\nrepeat!!;{\n  times = 3\n  greetings += yo().dude()\n}\n\n// could expand to:\ngreetings += yo().dude()\ngreetings += yo().dude()\ngreetings += yo().dude()\n```\n\nExample:\n\n```ts\nimport ts = require('typescript')\nimport { BlockMacro } from '@blainehansen/macro-ts'\n\nexport const macros = {\n  repeat: BlockMacro((ctx, inputStatements) =\u003e {\n    const [times, statement] = inputStatements\n    if (\n      !times || !statement\n      || !ts.isExpressionStatement(times)\n      || !ts.isBinaryExpression(times.expression)\n      || !ts.isIdentifier(times.expression.left)\n      || times.expression.operatorToken.kind !== ts.SyntaxKind.EqualsToken\n      || !ts.isNumericLiteral(times.expression.right)\n    ) return ctx.TsNodeErr(\n      inputStatements, 'Invalid repeat',\n      `The \"repeat\" macro isn't being used correctly.`,\n    )\n\n    const repetitions = parseInt(times.expression.right.text)\n    const statements = [...Array(repetitions)].map(() =\u003e statement)\n    return ctx.Ok(statements)\n  }),\n}\n```\n\nType signature:\n\n```ts\nimport ts = require('typescript')\nexport type BlockMacroFn = (\n  ctx: MacroContext,\n  args: ts.NodeArray\u003cts.Statement\u003e,\n) =\u003e BlockMacroResult\nexport type BlockMacroResult = SpanResult\u003cts.Statement[]\u003e\nexport function BlockMacro(execute: BlockMacroFn) {\n  /* ... */\n}\n```\n\n### `DecoratorMacro`\n\nDecorator macros can be used on definitions, such as type aliases, interfaces, classes, functions, and variable declarations.\n\n```ts\n// this code:\n@creator!!()\ntype A = {\n  a: number, b: string,\n}\n\n// could expand to:\ntype A = {\n  a: number, b: string,\n}\nfunction A(a: number, b: string): A {\n  return { a, b }\n}\n```\n\nExample:\n\n```ts\nimport ts = require('typescript')\nimport { DecoratorMacro } from '@blainehansen/macro-ts'\n\nexport const macros = {\n  creator: DecoratorMacro((ctx, statement) =\u003e {\n    if (\n      !ts.isTypeAliasDeclaration(statement)\n      || !ts.isTypeLiteralNode(statement.type)\n    ) return ctx.TsNodeErr(\n      statement, 'Not a type literal',\n      `The \"creator\" macro isn't being used correctly.`,\n    )\n\n    const members: { name: ts.Identifier, type: ts.TypeNode }[] = []\n    for (const member of statement.type.members) {\n      if (\n        !ts.isPropertySignature(member)\n        || !member.type\n        || !ts.isIdentifier(member.name)\n      ) return ctx.TsNodeErr(\n        member, 'Invalid member',\n        `The \"creator\" macro requires all members to be simple.`,\n      )\n\n      members.push({ name: member.name, type: member.type })\n    }\n\n    const parameters = members.map(({ name, type }) =\u003e {\n      return ts.createParameter(\n        undefined, undefined, undefined, name,\n        undefined, type, undefined,\n      )\n    })\n    const properties = members.map(({ name }) =\u003e {\n      return ts.createShorthandPropertyAssignment(name, undefined)\n    })\n\n    const creator = ts.createFunctionDeclaration(\n      undefined, undefined, undefined,\n      statement.name,\n      statement.typeParameters, parameters,\n      ts.createTypeReferenceNode(statement.name, undefined),\n      ts.createBlock([\n        ts.createReturn(\n          ts.createObjectLiteral(properties, false),\n        ),\n      ], true),\n    )\n\n    return ctx.Ok({ replacement: statement, additional: [creator] })\n  }),\n}\n```\n\nType signature:\n\n```ts\nimport ts = require('typescript')\nexport type DecoratorMacroFn = (\n  ctx: MacroContext,\n  statement: ts.Statement,\n  args: ts.NodeArray\u003cts.Expression\u003e,\n  typeArgs: ts.NodeArray\u003cts.TypeNode\u003e | undefined,\n) =\u003e DecoratorMacroResult\nexport type DecoratorMacroResult = SpanResult\u003c{\n  prepend?: ts.Statement[],\n  replacement: ts.Statement | undefined,\n  append?: ts.Statement[],\n}\u003e\nexport function DecoratorMacro(execute: DecoratorMacroFn) {\n  /* ... */\n}\n```\n\n### `ImportMacro`\n\nAn import statement can use this form `import * as t from macroName!!('./some/path')` to create \"virtual\" typescript files at the path. Import macros are a lot like webpack/rollup loaders, except that the typescript file produced is typechecked and compiled just like any other.\n\nLet's imagine you had this yaml file describing some data:\n\n```yaml\n# obj.yaml\na: 'a'\nb: 1\n```\n\nYou could create a `yaml` macro to load it at compile time, perhaps expanding it to this \"virtual\" typescript:\n\n```ts\n// \"virtual\" obj.yaml.ts\nexport default {\n  a: 'a',\n  b: 1,\n}\n```\n\nAnd then you can consume it in a typesafe way:\n\n```ts\n// main.ts\nimport obj from yaml!!('./obj.yaml')\nobj.a.toLowerCase()\nobj.b.toFixed()\nobj.c // compiler error!\n```\n\nThe possibilities are endless.\n\nExample:\n\n```ts\nimport yaml = require('js-yaml')\nimport ts = require('typescript')\nimport { ImportMacro } from '@blainehansen/macro-ts'\n\nexport const macros = {\n  yaml: ImportMacro((ctx, targetSource, targetPath) =\u003e {\n    const obj = YAML.safeLoad(targetSource)\n    if (typeof obj !== 'object')\n      return ctx.Err(\n        targetPath, 'Invalid yaml',\n        `The \"yaml\" macro requires the yaml contents to be an object.`,\n      )\n\n    const properties = Object.entries(obj).map(([key, value]) =\u003e {\n      return ts.createPropertyAssignment(\n        ts.createIdentifier(key),\n        // this is a cool hack,\n        // typescript just passes \"identifiers\" along exactly!\n        ts.createIdentifier(JSON.stringify(value)),\n      )\n    })\n    const statement = ts.createExportAssignment(\n      undefined, undefined,\n      undefined, ts.createObjectLiteral(properties, false),\n    )\n\n    return ctx.Ok({ statements: [statement] })\n  }),\n}\n```\n\nType signature:\n\n\u003c!-- TODO document this properly. This system is generic over some type (`S`) for additional sources to be produced and processed along with the typescript, so it's possible to use import macros as the basis of a robust and typesafe bundling system. --\u003e\n\n```ts\nimport ts = require('typescript')\nexport type ImportMacroFn\u003cS = undefined\u003e = (\n  ctx: MacroContext,\n  targetSource: string,\n  targetPath: string,\n  file: FileContext,\n) =\u003e ImportMacroResult\u003cS\u003e\nexport type FileContext = {\n  workingDir: string,\n  currentDir: string, currentFile: string\n}\nexport type ImportMacroResult\u003cS = undefined\u003e = SpanResult\u003c{\n  statements: ts.Statement[],\n  sources?: Dict\u003cS\u003e,\n}\u003e\nexport type Dict\u003cT\u003e = { [key: string]: T }\n\nexport function ImportMacro\u003cS = undefined\u003e(execute: ImportMacroFn\u003cS\u003e) {\n  /* ... */\n}\n```\n\n\n## Hint: use `ts-creator`\n\nThe simple but wonderful [`ts-creator` package](https://www.npmjs.com/package/ts-creator) makes it much easier to figure out how to generate typescript of a certain shape, so you can worry about making interesting macros rather than the minutiae of the typescript AST.\n\n[I especially recommend the cli.](https://www.npmjs.com/package/ts-creator#cli-usage)\n\n\n## Project goals:\n\nOverall, we want the macros to have these properties:\n\n- Typesafe. Both the macro functions themselves and their outputs should be checked to prevent obvious errors. Also, we'd like to be able to compile a library with the ambient types narrowed to only those that will be available in the intended execution environment.\n- Powerful. The syntax and capabilities of macros should allow very expressive and useful transformations.\n- Explicit. It should be obvious that something is different about a macro invocation compared to its surroundings.\n\n### Project non-goals:\n\nAnything that requires first-class support from the actual typescript compiler basically won't be considered. If the typescript team decides to make life easier, I'll gladly accept it. But I'm not going to wait around for them.\n\n- Hygienic Macros. This would be very complicated to get right without first-class support from the compiler.\n- Having a less ugly syntax. The `macroName!!` syntax is very ugly, but it's a hack that has some important properties. Suggestions for other syntaxes that meet these same requirements are welcome!\n  - `!!` is accepted as valid syntax by the typescript parser.\n  - `!!` is visually obvious. A reader can tell that this usage is different than the surrounding code.\n  - `identifier!!` is *technically* valid (asserting the identifier is non-nullish *twice*) but never actually useful. This makes it distinct from normal uses of the non-nullish assertion, and doesn't conflict with any useful pattern.\n- Alignment with javascript/typescript. As far as I'm concerned, javascript is just a compile target (and a lousy one). I'm much more worried about expressive and safe code, and don't really care what microsoft corporation or the TC39 group would prefer.\n\n## Project philosophy\n\n[This blog post describes my motivations for this project.](https://blainehansen.me/post/macro-ts/)\n\n## Known Limitations\n\n### Source maps\n\nSince macros arbitrarily transform the original source, and then typecheck the *transformed* source, the `line:column` values in the typechecking errors won't necessarily correspond with the original source.\n\nIf you heavily rely on your editor to interact with typescript, you might have a bad time, since integrating your editor language service with `macro-ts` is unlikely to happen.\nHowever if you instead mostly use the terminal, this problem is just an inconvenience.\n\n**Pull requests are welcome!**\n\nI don't know much about sourcemaps, and nice sourcemaps are less important to me than expressive and safe code, so I haven't prioritized this work. But I won't turn down reasonable pull requests to solve this problem.\n\n\n## Roadmap\n\n- [ ] Improve performance through caching, both of file data and build outputs.\n- [ ] Generalize compilation functions to allow using `macro-ts` to be used as the foundation of arbitrary specialized typesafe compilers, like a web application bundler.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fblainehansen%2Fmacro-ts","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fblainehansen%2Fmacro-ts","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fblainehansen%2Fmacro-ts/lists"}