{"id":19675314,"url":"https://github.com/snowflyt/safunc","last_synced_at":"2026-06-10T06:40:07.027Z","repository":{"id":236330967,"uuid":"792352710","full_name":"Snowflyt/safunc","owner":"Snowflyt","description":"Create runtime-validated functions for both synchronous and asynchronous ones with ease, supporting optional parameters and overloaded signatures with smart type inference in TypeScript.","archived":false,"fork":false,"pushed_at":"2024-04-27T10:55:16.000Z","size":1312,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-04-27T16:28:42.199Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Snowflyt.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-04-26T13:49:35.000Z","updated_at":"2024-04-27T14:35:34.000Z","dependencies_parsed_at":"2024-04-26T16:28:31.347Z","dependency_job_id":"7326c97f-051f-40da-a235-e8f5410b25b6","html_url":"https://github.com/Snowflyt/safunc","commit_stats":null,"previous_names":["snowflyt/safunc"],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Snowflyt%2Fsafunc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Snowflyt%2Fsafunc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Snowflyt%2Fsafunc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Snowflyt%2Fsafunc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Snowflyt","download_url":"https://codeload.github.com/Snowflyt/safunc/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240985240,"owners_count":19889033,"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":[],"created_at":"2024-11-11T17:23:07.565Z","updated_at":"2026-06-10T06:40:06.969Z","avatar_url":"https://github.com/Snowflyt.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003eSafunc\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\nCreate \u003cstrong\u003e\u003ci\u003eruntime-validated\u003c/i\u003e functions\u003c/strong\u003e for both \u003cstrong\u003esynchronous\u003c/strong\u003e and \u003cstrong\u003easynchronous\u003c/strong\u003e ones with ease, featuring \u003cstrong\u003esmart type inference\u003c/strong\u003e in TypeScript.\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://www.npmjs.com/package/safunc\"\u003e\n    \u003cimg src=\"https://img.shields.io/npm/v/safunc.svg\" alt=\"npm version\" height=\"18\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://bundlephobia.com/package/safunc\"\u003e\n    \u003cimg src=\"https://img.shields.io/bundlephobia/minzip/safunc.svg\" alt=\"minzipped size\" height=\"18\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/gvergnaud/safunc\"\u003e\n    \u003cimg src=\"https://img.shields.io/npm/l/safunc.svg\" alt=\"MIT license\" height=\"18\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n![screenshot](./screenshot.svg)\n\nHave a try on [StackBlitz](https://stackblitz.com/edit/safunc-minimal-example?file=main.ts\u0026view=editor)!\n\n## About\n\nSafunc is a small utility library that allows you to create both **synchronous** and **asynchronous** functions with **runtime validation** of arguments and (optionally) return values, supporting **optional parameters** and **overloaded signatures** with **smart type inference** in TypeScript. It is powered by [Arktype](https://github.com/arktypeio/arktype), an amazing runtime type-checking library using almost 1:1 syntax with TypeScript.\n\n![demo](./demo.gif)\n\n## Installation\n\n```bash\nnpm install safunc\n```\n\n... or any other package manager you prefer!\n\n## Usage\n\nThe following shows a minimal example of how to use Safunc to create a type-safe `add` function with runtime validation that only accepts two numbers and returns a number.\n\n```typescript\nimport { def, sig } from \"safunc\";\n\nconst add = def(sig(\"number\", \"number\", \"=\u003e\", \"number\"), (n, m) =\u003e n + m);\n//    ^?: Safunc\u003c(n: number, m: number) =\u003e number\u003e\nadd(1, 2); // =\u003e 3\nadd(1); // !TypeError: Expected 2 arguments, but got 1\nadd(\"foo\", 2); // !TypeError: The 1st argument of 'function(number, number): number' must be a number (was string)\n\n// ... or omit return type if you don't want to validate the return value\nconst add = def(sig(\"number\", \"number\"), (n, m) =\u003e n + m);\n//    ^?: Safunc\u003c(n: number, m: number) =\u003e number\u003e\nadd(1, \"foo\"); // !TypeError: The 2nd argument of 'function(number, number)' must be a number (was string)\n```\n\nYou can also define functions with optional parameters and overloaded signatures, which will be detailed later.\n\n```typescript\nimport { def, sig, optional } from \"safunc\";\n\n// With optional parameters\nconst repeat = def(\n  //  ^?: Safunc\u003c(s: string, args_1?: { n?: number }) =\u003e string\u003e\n  sig(\"string\", optional({ \"n?\": \"integer\u003e0\" }), \"=\u003e\", \"string\"),\n  (s, { n = 2 } = {}) =\u003e s.repeat(n),\n);\n\n// With overloaded signatures and optional parameters\nconst range = def(\n  sig(\"integer\", \"=\u003e\", \"integer[]\"),\n  sig(\"integer\", \"integer\", \"?integer\u003e0\", \"=\u003e\", \"integer[]\"),\n  function range(startOrStop, stop?, step = 1) {\n    // ...\n  },\n);\n```\n\nAs you can see here, you can use `def(...signatures, fn)` to create a function with runtime validation. Each signature is composed of several valid [Arktype](https://github.com/arktypeio/arktype) type definitions, split by `=\u003e` to separate parameters and return type. The return type along with `=\u003e` can be omitted if you don't want to validate the return value.\n\nThe types of parameters `n` and `m` in the above `add` example are automatically inferred as `number` from its signature, eliminating the need to specify parameter types within the function body. The same is true for `repeatString` and `range` in the examples above.\n\nThe `sig` function supports _0-4 parameters_ and 1 optional return type, which should be enough for most cases. If you find it not enough, you’d better consider redesigning your function.\n\nYou can use a function expression with name instead of anonymous functions for better error messages:\n\n```typescript\nconst addIntegers = def(sig(\"integer\", \"integer\", \"=\u003e\", \"integer\"), function add(n, m) {\n  return n + m + 0.5; // \u003c- This will throw a TypeError\n});\naddIntegers(1, 2); // !TypeError: The return value of 'function add(integer, integer): integer' must be an integer (was 3.5)\n//                                                              ^^^\n//                                       Name of the function is used in the error message\n```\n\n### Asynchronous Functions\n\nWhen working with asynchronous functions, such as those commonly found in REST API calls, it is likely you want to validate the arguments and return types if the API is unreliable or the data is critical. Safunc facilitates this with the `defAsync` function, which is used in place of `def`:\n\n```typescript\nimport { arrayOf, type } from \"arktype\";\nimport { defAsync, sig } from \"safunc\";\n\ntype Todo = typeof todo.infer;\nconst todo = type({\n  userId: \"integer\u003e0\",\n  id: \"integer\u003e0\",\n  title: \"string\",\n  completed: \"boolean\",\n});\n\nconst getTodos = defAsync(sig(\"=\u003e\", arrayOf(todo)), async () =\u003e {\n  //  ^?: Safunc\u003c() =\u003e Promise\u003cTodo[]\u003e\u003e\n  const res = await fetch(\"https://jsonplaceholder.typicode.com/todos\");\n  return res.json() as Promise\u003cTodo[]\u003e;\n});\nawait getTodos(); // =\u003e [{ userId: 1, id: 1, title: \"delectus aut autem\", completed: false }, ...]\n\ntype TodoWrong = typeof todoWrong.infer;\nconst todoWrong = type({\n  userId: \"integer\u003e0\",\n  id: \"string\u003e0\", // \u003c- This will throw a TypeError\n  title: \"string\",\n  completed: \"boolean\",\n});\n\nconst getTodosWrong = defAsync(sig(\"=\u003e\", arrayOf(todoWrong)), async () =\u003e {\n  //  ^?: Safunc\u003c() =\u003e Promise\u003cTodoWrong[]\u003e\u003e\n  const res = await fetch(\"https://jsonplaceholder.typicode.com/todos\");\n  return res.json() as Promise\u003cTodoWrong[]\u003e;\n});\nawait getTodosWrong(); // !TypeError: Property '0/id' of the return value of 'function(): Promise\u003cArray\u003c{ userId: integer\u003e0; id: string\u003e0; title: string; completed: boolean }\u003e\u003e' must be a string (was number)\n\nconst getTodo = defAsync(\n  //  ^?: Safunc\u003c(id: number) =\u003e Promise\u003cTodo\u003e\u003e\n  sig(\"integer\u003e0\", \"=\u003e\", todo),\n  async (id) =\u003e\n    await fetch(`https://jsonplaceholder.typicode.com/todos/${id}`).then(\n      (res) =\u003e res.json() as Promise\u003cTodo\u003e,\n    ),\n);\ngetTodo(0.5); // !TypeError: The 1st argument of 'function(integer\u003e0): Promise\u003c{ userId: integer\u003e0; id: integer\u003e0; title: string; completed: boolean }\u003e' must be an integer (was 0.5)\nawait getTodo(1); // =\u003e { userId: 1, id: 1, title: \"delectus aut autem\", completed: false }\n```\n\n`defAsync` supports all features of `def`, including optional parameters and overloaded signatures, which will be discussed later. The only difference is that `defAsync` requires functions to return a `Promise`, and validation of the return value is handled asynchronously (while the arguments are still validated synchronously).\n\n### Optional Parameters\n\nSafunc accommodates optional parameters using the `optional` helper function in its signatures.\n\n```typescript\nimport { def, sig, optional } from \"safunc\";\n\nconst range = def(\n  //  ^?: Safunc\u003c(startOrStop: number, stop?: number, step?: number) =\u003e number[]\u003e\n  sig(\"integer\", optional(\"integer\"), optional(\"integer\u003e0\"), \"=\u003e\", \"integer[]\"),\n  function range(startOrStop, stop, step = 1) {\n    const start = stop === undefined ? 0 : startOrStop;\n    stop ??= startOrStop;\n    return Array.from({ length: Math.ceil((stop - start) / step) }, (_, i) =\u003e start + i * step);\n  },\n);\n\nrange(3); // =\u003e [0, 1, 2]\nrange(1, 5); // =\u003e [1, 2, 3, 4]\nrange(1, \"foo\"); // !TypeError: The 2nd argument of 'function range(integer, ?integer, ?integer\u003e0): Array\u003cinteger\u003e' must be a number (was string)\nrange(1, 5, -1); // !TypeError: The 3rd argument of 'function range(integer, ?integer, ?integer\u003e0): Array\u003cinteger\u003e' must be more than 0 (was -1)\n```\n\nThe syntax gets a little clumsy, so Safunc offers a shorthand syntax for optional parameters by adding a `?` prefix to the type definition.\n\n```typescript\nconst range = def(\n  //  ^?: Safunc\u003c(startOrStop: number, stop?: number, step?: number) =\u003e number[]\u003e\n  sig(\"integer\", \"?integer\", \"?integer\u003e0\", \"=\u003e\", \"integer[]\"),\n  function range(startOrStop, stop, step = 1) {\n    const start = stop === undefined ? 0 : startOrStop;\n    stop ??= startOrStop;\n    return Array.from({ length: Math.ceil((stop - start) / step) }, (_, i) =\u003e start + i * step);\n  },\n);\n```\n\nThis shorthand syntax works well for straightforward string type definitions. For more complex type specifications, the `optional` helper function remains a necessity.\n\n```typescript\nconst repeat = def(\n  //  ^?: Safunc\u003c(s: string, args_1?: { n?: number }) =\u003e string\u003e\n  sig(\"string\", optional({ \"n?\": \"integer\u003e0\" }), \"=\u003e\", \"string\"),\n  (s, { n = 2 } = {}) =\u003e s.repeat(n),\n);\n\nrepeat(\"foo\", { n: 0.5 }); // !TypeError: Property 'n' of the 2nd argument of 'function(string, ?{ n?: number }): string' must be an integer (was 0.5)\n```\n\n### Overloaded Signatures\n\nSafunc supports defining functions with overloaded signatures, which is useful when you want to provide multiple ways to call a function with different sets of parameters.\n\n```typescript\nconst repeat = def(\n  //  ^?: Safunc\u003c((s: string) =\u003e string) \u0026 ((n: number, s: string) =\u003e string)\u003e\n  sig(\"string\", \"=\u003e\", \"string\"),\n  sig(\"integer\u003e0\", \"string\", \"=\u003e\", \"string\"),\n  function repeat(...args) {\n    const [n, s] = args.length === 1 ? [2, args[0]] : args;\n    return s.repeat(n);\n  },\n);\n\nrepeat(); // !TypeError: Expected 1-2 arguments, but got 0\nrepeat(\"foo\"); // =\u003e \"foofoo\"\nrepeat(3, \"bar\"); // =\u003e \"barbarbar\"\nrepeat(5); // !TypeError: The 1st argument of 'function repeat(string): string' (overload 1 of 2) must be a string (was number)\n\nconst concat = def(\n  //  ^?: Safunc\u003c((s1: string, s2: string) =\u003e string) \u0026 ((n: number, m: number) =\u003e number)\u003e\n  sig(\"string\", \"string\", \"=\u003e\", \"string\"),\n  sig(\"number\", \"number\", \"=\u003e\", \"number\"),\n  (a, b) =\u003e (a as any) + (b as any),\n);\nconcat(\"foo\", \"bar\"); // =\u003e \"foobar\"\nconcat(1, 2); // =\u003e 3\nconact(\"foo\", 42); // 🢇 Error message when length of arguments match multiple signatures\n// !TypeError: No overload matches this call.\n//   Overload 1 of 2, '(string, string): string', gave the following error.\n//     The 2nd argument must be a string (was number)\n//   Overload 2 of 2, '(number, number): number', gave the following error.\n//     The 1st argument must be a number (was string)\n```\n\nWhile using a single signature preserves the parameter names in the type information (as you can see in earlier examples), providing multiple signatures can obscure them. This _does not_ affect the runtime behavior of functions, but it can make the type information less readable. Despite these challenges, Safunc strives to maintain clear type information by deducing parameter names through [some type-level magic](./src/tools/name-params.ts), resulting in types like `((s: string) =\u003e string) \u0026 ((n: number, s: string) =\u003e string)` for `repeat` and similarly for `concat`.\n\nHowever, the inferred parameter names may not always meet your expectations:\n\n```typescript\nconst range = def(\n  //  ^?: Safunc\u003c((n: number) =\u003e number[]) \u0026 ((n1: number, n2: number, n3?: number) =\u003e number[])\u003e\n  sig(\"integer\", \"=\u003e\", \"integer[]\"),\n  sig(\"integer\", \"integer\", \"?integer\u003c0|integer\u003e0\", \"=\u003e\", \"integer[]\"),\n  function range(startOrStop, stop?, step = 1) {\n    //                        ^^^^^\n    // The `?` suffix of `stop?` is required to make the function compatible with overloaded signatures\n    const start = stop === undefined ? 0 : startOrStop;\n    stop ??= startOrStop;\n    const res: number[] = [];\n    if (step \u003e 0) for (let i = start; i \u003c stop; i += step) res.push(i);\n    else for (let i = start; i \u003e stop; i += step) res.push(i);\n    return res;\n  },\n);\n\nrange(); // !TypeError: Expected 1-3 arguments, but got 0\nrange(3); // =\u003e [0, 1, 2]\nrange(1, \"2\"); // !TypeError: The 2nd argument of 'function range(integer, integer, ?integer\u003c0|integer\u003e0): Array\u003cinteger\u003e' (overload 2 of 2) must be a number (was string)\n```\n\nTo ensure the parameters are properly named, use `as Sig\u003c...\u003e` to explicitly define the function’s type:\n\n```typescript\nimport { def, sig, type Sig } from \"safunc\";\n\nconst range = def(\n  //  ^?: Safunc\u003c((stop: number) =\u003e number[]) \u0026 ((start: number, stop: number, step?: number) =\u003e number[])\u003e\n  sig(\"integer\", \"=\u003e\", \"integer[]\") as Sig\u003c(stop: number) =\u003e number[]\u003e,\n  sig(\"integer\", \"integer\", \"?integer\u003c0|integer\u003e0\", \"=\u003e\", \"integer[]\") as Sig\u003c\n    (start: number, stop: number, step?: number) =\u003e number[]\n  \u003e,\n  function range(startOrStop, stop?, step = 1) {\n    /* ... */\n  },\n);\n```\n\nSafunc allows for up to _8 overloaded signatures_ per function.\n\n### Work with Arktype `morph`s\n\n[Arktype](https://github.com/arktypeio/arktype) features a powerful tool called morph, which validates a value and parse it into another value. This is akin to `z.preprocess()` from [Zod](https://github.com/colinhacks/zod), if you are familiar with it.\n\n```typescript\nimport { morph } from \"arktype\";\n\nconst stringifiablePrimitive = morph(\"string | number | bigint | boolean | null | undefined\", (x) =\u003e\n  //  ^?: Type\u003c(In: string | number | bigint | boolean | null | undefined) =\u003e Out\u003cstring\u003e\u003e\n  String(x),\n);\nstringifiablePrimitive(42); // =\u003e { data: \"42\" }\nstringifiablePrimitive(Symbol(\"foo\")); // =\u003e { problems: [Problem { message: \"Must be a string, a number, a bigint, boolean, null or undefined (was (symbol foo))\" }] }\n\nconst dateString = morph(\"string\", (x, problems) =\u003e\n  //  ^?: Type\u003c(In: string) =\u003e Out\u003cDate\u003e\u003e\n  isNaN(Date.parse(x)) ? problems.mustBe(\"a valid date\") : new Date(x),\n);\ndateString(\"2024-04-06\"); // =\u003e { data: Date(\"2024-04-06\") }\ndateString(\"foo\"); // =\u003e { problems: [Problem { message: \"Must be a valid date (was 'foo')\" }] }\ndateString(42); // =\u003e { problems: [Problem { message: \"Must be a string (was number)\" }] }\n```\n\nSafunc seamlessly integrates with this feature, accurately inferring types when using `morph`s:\n\n```typescript\nconst dateString = morph(\"string\", (x, problems) =\u003e\n  isNaN(Date.parse(x)) ? problems.mustBe(\"a valid date\") : new Date(x),\n);\nconst isoDateString = morph(\n  \"Date\",\n  (x) =\u003e\n    x.getFullYear() +\n    \"-\" +\n    String(x.getMonth() + 1).padStart(2, \"0\") +\n    \"-\" +\n    String(x.getDate()).padStart(2, \"0\"),\n);\n\nconst addYears = def(\n  //  ^?: Safunc\u003c(date: string, years: number) =\u003e string\u003e\n  sig(dateString, \"integer\", \"=\u003e\", isoDateString),\n  function addYears(date, years) {\n    //     ^?: addYears(date: Date, years: number): Date - Use `Date` inside the function body\n    date.setFullYear(date.getFullYear() + years);\n    return date;\n  },\n);\nexpect(addYears(\"2024-04-26\", 1)).toBe(\"2025-04-26\");\n```\n\nIn the example above, the implementation of `addYears` operates with types `(date: Date, years: number) =\u003e Date`, while its signature specifies `(date: string, years: number) =\u003e string`. Conversion between `string` and `Date` is managed by `dateString` and `isoDateString` respectively. Safunc ensures the correct inference of function types both inside and outside the function body.\n\n### Helper methods\n\nThe `Safunc` instance contains some helper methods to help you work with the function:\n\n```typescript\nconst sig1 = sig(\"integer\", \"=\u003e\", \"integer[]\") as Sig\u003c(stop: number) =\u003e number[]\u003e;\nconst sig2 = sig(\"integer\", \"integer\", \"?integer\u003e0\", \"=\u003e\", \"integer[]\") as Sig\u003c\n  (start: number, stop: number, step?: number) =\u003e number[]\n\u003e;\nconst range = def(sig1, sig2, (startOrStop, stop?, step = 1) =\u003e {\n  //  ^?: Safunc\u003c((stop: number) =\u003e number[]) \u0026 ((start: number, stop: number, step?: number) =\u003e number[])\u003e\n  /* ... */\n});\n\n// Use `Safunc#unwrap` to remove helper methods and get the original function\n// Note that _validation is still applied_ when calling the unwrapped function\nconst unwrappedRange = range.unwrap();\n//    ^?: ((stop: number) =\u003e number[]) \u0026 ((start: number, stop: number, step?: number) =\u003e number[])\n\n// Use `onValidationError` to provide alternative error handling instead of just throwing a `TypeError`\nconst range2 = range.onValidationError(console.error);\nrange2(1, 3.5); // =\u003e [1, 2, 3] - The function is returned as the errors are handled by a custom handler\n// This time, the error message is printed to the console and no error is thrown\n// If you still want to throw an error instead of returning the function, you can rethrow it in the custom handler\n\n// Use `Safunc#matchArguments` to get the matched `Sig` for the given arguments\nrange.matchArguments(3); // =\u003e sig1\nrange.matchArguments(1, 5); // =\u003e sig2\nrange.matchArguments(\"foo\"); // =\u003e null\n\n// Use `Safunc#assertArguments` to assert the given arguments are valid\nrange.assertArguments(1, 5); // OK\nrange.assertArguments(\"foo\"); // !TypeError: The 1st argument of 'function(integer): Array\u003cinteger\u003e' (overload 1 of 2) must be a number (was string)\n\n// Use `Safunc#allowArguments` to verify if the given arguments are valid\nrange.allowArguments(1, 5); // =\u003e true\nrange.allowArguments(\"foo\"); // =\u003e false\n```\n\nSome properties of the `Safunc` instance are not recommended for use due to their lack of type safety, though they may still be useful in certain situations:\n\n```typescript\n// `$sigs` contains all the signatures of the function\nrange.$sigs; // =\u003e [sig1, sig2]\n//    ^?: readonly Sig\u003cany\u003e[] - Type safety is not guaranteed\n\n// `$fn` contains the original function without validation\nrange.$fn;\n//    ^?: (...args: never[]) =\u003e unknown - Type safety is not guaranteed\n```\n\n### Arktype Utilities\n\nSafunc provides some handy utilities to easily define some common schemas with Arktype:\n\n```typescript\nimport { record, unions } from \"safunc\";\n\nconst recordSchema = record(\"string\", \"number\");\n//    ^?: Type\u003cRecord\u003cstring, number\u003e\u003e\nrecordSchema({ foo: 42 }); // =\u003e { data: { foo: 42 } }\nrecordSchema({ foo: \"bar\" }); // =\u003e { problems: [Problem { message: 'Must be an object with values of type \\'number\\' (was {\"foo\":\"bar\"})' }] }\n\nconst unionsSchema = unions(\"string\", \"number\", \"boolean\");\n//    ^?: Type\u003cstring | number | boolean\u003e\nunionsSchema(\"foo\"); // =\u003e { data: \"foo\" }\nunionsSchema(42); // =\u003e { data: 42 }\nunionsSchema(true); // =\u003e { data: true }\nunionsSchema({}); // =\u003e { problems: [Problem { message: \"Must be a string, a number or boolean (was {})\" }] }\n```\n\n### Using Safunc in Plain JavaScript Files with JSDoc\n\nUsing Safunc isn't limited to TypeScript environments. TypeScript supports type annotations in JavaScript through [JSDoc](https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html), allowing you to maintain type safety in plain JavaScript files. Since Safunc generally doesn't require explicit type annotations, it works seamlessly in most scenarios.\n\nWhen dealing with overloaded signatures in JavaScript, you can use JSDoc syntax similar to `as Sig\u003c...\u003e` in TypeScript to achieve clearer type information:\n\n```javascript\nimport { def, sig } from \"safunc\";\n\nconst range = def(\n  //  ^?: Safunc\u003c((stop: number) =\u003e number[]) \u0026 ((start: number, stop: number, step?: number) =\u003e number[])\u003e\n  /** @type {import(\"safunc\").Sig\u003c(stop: number) =\u003e number[]\u003e} */\n  (sig(\"integer\", \"=\u003e\", \"integer[]\")),\n  /** @type {import(\"safunc\").Sig\u003c(start: number, stop: number, step?: number) =\u003e number[]\u003e} */\n  (sig(\"integer\", \"integer\", \"?integer\u003e0\", \"=\u003e\", \"integer[]\")),\n  function range(startOrStop, stop, step = 1) {\n    /* ... */\n  },\n);\n```\n\nTo reduce verbosity, you can define a type alias for `Sig\u003c...\u003e`:\n\n```javascript\n/**\n * @template {(...args: never[]) =\u003e unknown} F\n * @typedef {import(\"safunc\").Sig\u003cF\u003e} Sig\n */\n\nconst range = def(\n  //  ^?: Safunc\u003c((stop: number) =\u003e number[]) \u0026 ((start: number, stop: number, step?: number) =\u003e number[])\u003e\n  /** @type {Sig\u003c(stop: number) =\u003e number[]\u003e} */\n  (sig(\"integer\", \"=\u003e\", \"integer[]\")),\n  /** @type {Sig\u003c(start: number, stop: number, step?: number) =\u003e number[]\u003e} */\n  (sig(\"integer\", \"integer\", \"?integer\u003e0\", \"=\u003e\", \"integer[]\")),\n  function range(startOrStop, stop, step = 1) {\n    /* ... */\n  },\n);\n```\n\nNote that you must enclose `sig(...)` in parentheses to enable TypeScript to recognize it as a type assertion.\n\nSafunc can also be utilized through CDNs and [import maps](https://developer.mozilla.org/docs/Web/HTML/Element/script/type/importmap) in modern browsers:\n\n```html\n\u003c!doctype html\u003e\n\u003chtml lang=\"en\"\u003e\n  \u003chead\u003e\n    \u003cmeta charset=\"UTF-8\" /\u003e\n    \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" /\u003e\n    \u003ctitle\u003eSafunc in Browser\u003c/title\u003e\n    \u003cscript type=\"importmap\"\u003e\n      {\n        \"imports\": {\n          \"safunc\": \"https://cdn.jsdelivr.net/npm/safunc@latest/+esm\"\n        }\n      }\n    \u003c/script\u003e\n  \u003c/head\u003e\n  \u003cbody\u003e\n    \u003cscript type=\"module\"\u003e\n      import { def, sig } from \"safunc\";\n\n      const add = def(sig(\"number\", \"number\", \"=\u003e\", \"number\"), (n, m) =\u003e n + m);\n      console.log(add(1, 2)); // 3\n      add(1, \"2\"); // !TypeError\n    \u003c/script\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\nThis setup is particularly useful in non-standard frontend environments, such as within backend projects that use a templating engine.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsnowflyt%2Fsafunc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsnowflyt%2Fsafunc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsnowflyt%2Fsafunc/lists"}