{"id":16057784,"url":"https://github.com/tylim88/firesword","last_synced_at":"2025-03-17T16:32:02.669Z","repository":{"id":57989447,"uuid":"529528686","full_name":"tylim88/FireSword","owner":"tylim88","description":"🔥 Filter Unknown Keys Or Keys With Incorrect Data Types Recursively Before Saving into Firestore and RTDB, Support All Field Values And Special Data Types.","archived":false,"fork":false,"pushed_at":"2024-08-21T18:12:50.000Z","size":2208,"stargazers_count":7,"open_issues_count":2,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-02-28T01:48:37.913Z","etag":null,"topics":["filter","firebase","firebase-realtime-database","firestore","javascript","schema","typescript"],"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/tylim88.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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":"2022-08-27T08:36:45.000Z","updated_at":"2024-08-21T18:12:54.000Z","dependencies_parsed_at":"2024-11-18T06:15:20.826Z","dependency_job_id":null,"html_url":"https://github.com/tylim88/FireSword","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tylim88%2FFireSword","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tylim88%2FFireSword/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tylim88%2FFireSword/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tylim88%2FFireSword/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tylim88","download_url":"https://codeload.github.com/tylim88/FireSword/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243871883,"owners_count":20361378,"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":["filter","firebase","firebase-realtime-database","firestore","javascript","schema","typescript"],"created_at":"2024-10-09T03:04:45.163Z","updated_at":"2025-03-17T16:32:02.279Z","avatar_url":"https://github.com/tylim88.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"## Update\n\nDo not use this library.\n\nI will repurpose this library to support Firelord code first approach.\n\n\u003c!-- markdownlint-disable MD010 --\u003e\n\u003c!-- markdownlint-disable MD033 --\u003e\n\u003c!-- markdownlint-disable MD041 --\u003e\n\n\u003cdiv align=\"center\"\u003e\n\t\t\u003cimg src=\"https://raw.githubusercontent.com/tylim88/FireSword/6eb62cd853d18e46fb667d991446f38b07a76960/logo.svg\" width=\"200px\"/\u003e\n\t\t\u003ch1\u003eFireSword 烈火剑\u003c/h1\u003e\n\u003c/div\u003e\n\n\u003cdiv align=\"center\"\u003e\n\t\t\u003ca href=\"https://www.npmjs.com/package/firesword\" target=\"_blank\"\u003e\n\t\t\t\t\u003cimg\n\t\t\t\t\tsrc=\"https://img.shields.io/npm/v/firesword\"\n\t\t\t\t\talt=\"Created by tylim88\"\n\t\t\t\t/\u003e\n\t\t\t\u003c/a\u003e\n\t\t\t\u0026nbsp;\n\t\t\t\u003ca\n\t\t\t\thref=\"https://github.com/tylim88/firesword/blob/main/LICENSE\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\u003e\n\t\t\t\t\u003cimg\n\t\t\t\t\tsrc=\"https://img.shields.io/github/license/tylim88/firesword\"\n\t\t\t\t\talt=\"License\"\n\t\t\t\t/\u003e\n\t\t\t\u003c/a\u003e\n\t\t\t\u0026nbsp;\n\t\t\t\u003ca\n\t\t\t\thref=\"https://www.npmjs.com/package/firesword?activeTab=dependencies\"\n\t\t\t\ttarget=\"_blank\"\n\t\t\t\u003e\n\t\t\t\t\u003cimg\n\t\t\t\t\tsrc=\"https://img.shields.io/badge/dynamic/json?url=https://api.npmutil.com/package/firesword\u0026label=dependencies\u0026query=$.dependencies.count\u0026color=brightgreen\"\n\t\t\t\t\talt=\"dependency count\"\n\t\t\t\t/\u003e\n\t\t\t\u003c/a\u003e\n\t\t\t\u0026nbsp;\n\t\t\t\u003cimg\n\t\t\t\tsrc=\"https://img.shields.io/badge/gzipped-6KB-brightgreen\"\n\t\t\t\talt=\"package size\"\n\t\t\t/\u003e\n\t\t\t\u0026nbsp;\n\t\t\t\u003ca href=\"https://github.com/tylim88/firesword/actions\" target=\"_blank\"\u003e\n\t\t\t\t\u003cimg\n\t\t\t\t\tsrc=\"https://github.com/tylim88/firesword/workflows/Main/badge.svg\"\n\t\t\t\t\talt=\"github action\"\n\t\t\t\t/\u003e\n\t\t\t\u003c/a\u003e\n\t\t\t\u0026nbsp;\n\t\t\t\u003ca href=\"https://codecov.io/gh/tylim88/firesword\" target=\"_blank\"\u003e\n\t\t\t\t\u003cimg\n\t\t\t\t\tsrc=\"https://codecov.io/gh/tylim88/firesword/branch/main/graph/badge.svg\"\n\t\t\t\t\talt=\"code coverage\"\n\t\t\t\t/\u003e\n\t\t\t\u003c/a\u003e\n\t\t\t\u0026nbsp;\n\t\t\t\u003ca href=\"https://github.com/tylim88/firesword/issues\" target=\"_blank\"\u003e\n\t\t\t\t\u003cimg\n\t\t\t\t\talt=\"GitHub issues\"\n\t\t\t\t\tsrc=\"https://img.shields.io/github/issues-raw/tylim88/firesword\"\n\t\t\t\t\u003e\u003c/img\u003e\n\t\t\t\u003c/a\u003e\n\t\t\t\u0026nbsp;\n\t\t\t\u003ca href=\"https://snyk.io/test/github/tylim88/firesword\" target=\"_blank\"\u003e\n\t\t\t\t\u003cimg\n\t\t\t\t\tsrc=\"https://snyk.io/test/github/tylim88/firesword/badge.svg\"\n\t\t\t\t\talt=\"code coverage\"\n\t\t\t\t/\u003e\n\t\t\t\u003c/a\u003e\n\u003c/div\u003e\n\u003cbr/\u003e\n\u003cdiv align=\"center\"\u003e\n\t\tFilter Unknown Keys Or Keys With Incorrect Data Types Recursively Before Saving into Firestore and RTDB, Support All Field Values And Special Data Types.\n\u003c/div\u003e\n\u003cbr/\u003e\n\u003cbr/\u003e\n\n## Purpose\n\nSome time our API data requirement is less strict, we **do not** want to reject the whole data just because:\n\n1. some information is incorrect(correct key but incorrect value)\n2. extra information(unknown keys)\n\nAt the same time we don't want to save them into database, we just want to save whatever that is correct.\n\nThis is where filtering come in handy.\n\n## Installation\n\n```bash\nnpm i firesword [zod strip](https://github.com/colinhacks/zod#strip) to filter, it will destroys the special data types. Always only use FireSword to filter.\n```\n\n## What It Does?\n\n1. Remove all incorrect enumerable keys(members where key or value type is incorrect), which mean it works for array too.\n2. Filters recursively, nothing can escape, it is a black hole.\n3. Does not throw on missing members, the job is to filter, not validating. In case you need to throw(validate), see point 4.\n4. To validate, simply call `yourSchema.parse(data)` or `yourSchema.safeParse(data)` depend on your use case. Keep in mind all members is required by default, you can set all members or certain members to partial, please read the Zod [documentation](https://github.com/colinhacks/zod) for more parsing options.\n5. Both Firestore and RTDB filters support native Zod types: `z.literal`, `z.string`, `z.number`, `z.null`, `z.boolean`, `z.array`, `z.union`, `z.object`.\n6. This library is structure in a way that it is possible to support other database(open an issue and I will expose the API, so you can create your own filter, or you can directly contribute to this repo).\n\nImportant: do not use zod st\n\n## Limitations For Both RTDB and Firestore Filters\n\n1. Do not union object type with any other type: `z.union([z.object({}), z.someOtherType])`\n2. Do not union array type with any other type: `z.union([z.array(...), z.someOtherType])`\n3. Top level data type must be an object type.\n\n## Firestore Quick Start\n\n1. `zTimestamp`, `zDocumentReference` and `zGeoPoint`, `zArrayUnionAndRemove`, `zDelete`, `zIncrement` and `zServerTimestamp` are custom Firestore Zod types.\n2. Support native Zod Type: `z.date`.\n3. The filtered data is deep clone of original data, will not clone Firestore `Timestamp`, `DocumentReference`, `GeoPoint` and all field values(`ServerTimestamp, ArrayRemove, ArrayUnion, Increment, Delete`).\n\n### Web\n\n```ts\nimport {\n\tfilter,\n\tzTimestamp,\n\tzDocumentReference,\n\tzGeoPoint,\n\tzArrayUnionAndRemove,\n\tzDelete,\n\tzIncrement,\n} from 'firesword/firestore-web'\nimport { z } from 'zod'\nimport {\n\tTimestamp,\n\tgetFirestore,\n\tdoc,\n\tarrayRemove,\n\tdeleteField,\n\tincrement,\n} from 'firebase/firestore'\nimport { initializeApp } from 'firebase/app'\n\ninitializeApp({ projectId: 'any' })\n\n// schema type\n// {\n// \ta: string\n// \tb: 1 | 2 | 3\n// \tc: {\n// \t\td: Timestamp\n// \t\te: DocumentReference\n// \t\tf: GeoPoint\n// \t}\n// \td: number[]\n// \te: { i: boolean; j: 'a' | 'b' | 'c' }[]\n//  f: (number|boolean)[]\n//  g: string[]\n//  h: number\n//  i: number\n//  j: Date\n// }\nconst schema = z.object({\n\ta: z.string(),\n\tb: z.union([z.literal(1), z.literal(2), z.literal(3)]),\n\tc: z.object({ d: zTimestamp(), e: zDocumentReference(), f: zGeoPoint() }),\n\td: z.array(z.number()),\n\te: z.array(\n\t\tz.object({\n\t\t\ti: z.boolean(),\n\t\t\tj: z.union([z.literal('a'), z.literal('b'), z.literal('c')]),\n\t\t})\n\t),\n\tf: z.array(z.union([z.boolean(), z.number()])),\n\tg: z.union([z.array(z.string()), zArrayUnionAndRemove(z.string())]),\n\th: z.union([zDelete(), z.number()]),\n\ti: z.union([zIncrement(), z.number()]),\n\tj: z.date(),\n})\n\nexport const filteredData = filter({\n\tschema,\n\tdata: {\n\t\t// 'a' is missing\n\t\tz: 'unknown member',\n\t\tb: 1,\n\t\tc: {\n\t\t\td: new Timestamp(0, 0),\n\t\t\te: doc(getFirestore(), 'a/b'),\n\t\t\t// f is missing\n\t\t\tz: 'unknown member',\n\t\t},\n\t\td: [100, 200, 300],\n\t\te: [\n\t\t\t{\n\t\t\t\ti: true,\n\t\t\t\t// j is missing\n\t\t\t},\n\t\t\t{\n\t\t\t\t// i is missing\n\t\t\t\tj: 'a',\n\t\t\t\tz: 'unknown member',\n\t\t\t},\n\t\t],\n\t\tf: arrayRemove('abc'),\n\t\tg: deleteField(),\n\t\th: increment(1),\n\t\ti: new Date(0),\n\t},\n})\n\nconsole.log(filteredData)\n// {\n// \tb: 1,\n// \tc: {\n// \t\td: new Timestamp(0, 0),\n// \t\te: doc(getFirestore(), 'a/b'),\n// \t},\n// \td: [100, 200, 300],\n// \te: [{ i: true }, { j: 'a' }],\n//  f: arrayRemove('abc'),\n//  g: deleteField(),\n//  h: increment(1),\n//  i: new Date(0),\n// }\n```\n\n### Admin\n\nThis is how you import the same thing in admin, the rest are similar to web.\n\n```ts\nimport {\n\tfilter,\n\tzTimestamp,\n\tzDocumentReference,\n\tzGeoPoint,\n\tzArrayUnionAndRemove,\n\tzDelete,\n\tzIncrement,\n} from 'firesword/firestore-admin'\n```\n\n## RTDB Quick Start\n\n1. `zServerTimestamp` and `zIncrement` are custom RTDB Zod types.\n2. Use `zServerTimestamp` for `serverTimestamp` and `zIncrement` for `increment`.\n3. RTDB's `zServerTimestamp` and `zIncrement` are not the same as Firestore's `zServerTimestamp` and `zIncrement`.\n4. RTDB [doesn't always return array type](https://firebase.blog/posts/2014/04/best-practices-arrays-in-firebase). \u003c-- this does not affect how you should use this library but something you should be aware of.\n5. One api for both admin and web.\n\n```ts\nimport { filter, zServerTimestamp, zIncrement } from 'firesword/database'\nimport { z } from 'zod'\nimport { serverTimestamp, increment } from 'firebase/database'\n\n// schema type\n// {\n// \ta: string\n// \tb: number\n// \tg: serverTimestamp[]\n// \th: { i: boolean; j: 'a' | 'b' | 'c' }[]\n// }\nconst schema = z.object({\n\ta: z.string(),\n\tb: z.union([z.number(), zIncrement()]),\n\tg: z.array(zServerTimestamp()),\n\th: z.array(\n\t\tz.object({\n\t\t\ti: z.boolean(),\n\t\t\tj: z.union([z.literal('a'), z.literal('b'), z.literal('c')]),\n\t\t})\n\t),\n})\n\nexport const filteredData = filter({\n\tschema,\n\tdata: {\n\t\t// missing 'a'\n\t\tz: 'unknown member',\n\t\tb: increment(1),\n\t\tg: [serverTimestamp(), serverTimestamp(), serverTimestamp()],\n\t\th: [\n\t\t\t{\n\t\t\t\ti: true,\n\t\t\t\t// missing j\n\t\t\t},\n\t\t\t{\n\t\t\t\t// missing i\n\t\t\t\tj: 'a',\n\t\t\t\tz: 'unknown member',\n\t\t\t},\n\t\t],\n\t},\n})\n\n// console.log(filteredData)\n// {\n// \tb: increment(1),\n// \tg: [ServerTimestamp, ServerTimestamp, ServerTimestamp],\n// \th: [{ i: true }, { j: 'a' }],\n// }\n```\n\n## Dealing With Incorrect Data Type\n\nThis section use Firestore filter as example but the same logic is applied to RTDB filter.\n\n```ts\nimport { filter, zArrayUnionAndRemove } from 'firesword/firestore-web'\nimport { number, z } from 'zod'\nimport { arrayUnion } from 'firebase/firestore'\n// {\n// \ta: string\n// \tb: 1 | 2 | 3\n// \tg: { x: number, y: null }\n// \th: boolean[]\n//  i: zArrayUnionAndRemove(string)\n// }\nconst schema = z.object({\n\ta: z.string(),\n\tb: z.union([z.literal(1), z.literal(2), z.literal(3)]),\n\tg: z.object({ x: z.number(), y: z.null() }),\n\th: z.array(z.boolean()),\n\ti: zArrayUnionAndRemove(z.string()),\n\tj: z.array(\n\t\tz.object({ x: z.number(), y: z.object({ a: z.null(), b: z.number() }) })\n\t),\n})\n\nexport const filteredData = filter({\n\tschema,\n\tdata: {\n\t\ta: true, // expect string\n\t\tb: {}, // expect 1 | 2 | 3\n\t\tg: 1, // expect { x:number, y:null }\n\t\th: null, // expect boolean[]\n\t\ti: arrayUnion(1), // expect arrayUnion(string)\n\t\tj: [{ x: 'abc', y: { a: null, b: 'abc' } }], // expect number for 'x' and 'b'\n\t},\n})\n// console.log(filteredData) // { j:[{y: { a:null }}] }\n\nexport const filteredData2 = filter({\n\tschema,\n\tdata: {\n\t\tg: { a: {}, b: true, c: 'abc' }, // expect { x:number, y:null }\n\t\th: [1, true, 3], // expect boolean[], only the 2nd element is correct\n\t},\n})\n// console.log(filteredData2) // { g: {}, h: [null, true, null] }\n```\n\n## Special Types Type Casting\n\nYou need to type cast Firestore `zTimestamp`, `zDocumentReference` and `zGeoPoint`.\n\n```ts\nimport {\n\tfilter,\n\tzTimestamp,\n\tzDocumentReference,\n\tzGeoPoint,\n} from 'firesword/firestore-web'\nimport { z } from 'zod'\nimport {\n\tTimestamp,\n\tdoc,\n\tGeoPoint,\n\tDocumentReference,\n\tgetFirestore,\n} from 'firebase/firestore'\nimport { initializeApp } from 'firebase/app'\n\ninitializeApp({ projectId: 'any' })\n\n// {\n// \td: Timestamp\n// \te: DocumentReference\n// \tf: GeoPoint\n// }\nconst schema = z.object({\n\td: zTimestamp(),\n\te: zDocumentReference(),\n\tf: zGeoPoint(),\n})\n\nexport const filteredData = filter({\n\tschema,\n\tdata: {\n\t\td: new Timestamp(0, 0),\n\t\te: doc(getFirestore(), 'a/b'),\n\t\tf: new GeoPoint(0, 0),\n\t},\n}) as unknown as {\n\td: Timestamp\n\te: DocumentReference\n\tf: GeoPoint\n}\n```\n\n## In Case of Compiler Ignoring Package.json Exports\n\nIf you see error like `Cannot find module 'firesword/firestore'` or `Cannot find module 'firesword/database'`, it means your compiler ignore `package.json` `exports` field.\n\nSolution for Jest: [jest-node-exports-resolver](https://www.npmjs.com/package/jest-node-exports-resolver).\n\nI am not aware of solution for other cases(eg webpack), please open issue if you are having similar issue.\n\n## Trivial\n\n1. The name FireSword is a reference to [Piandao of Avatar](https://avatar.fandom.com/wiki/Piandao).\n2. This library is the successor of [FireLaw](https://github.com/tylim88/FireLaw).\n\n## Related Projects\n\n1. [FirelordJS](https://github.com/tylim88/FirelordJS) - Typescript wrapper for Firestore Web\n2. [Firelord](https://github.com/tylim88/firelord) - Typescript wrapper for Firestore admin\n3. [FireCall](https://github.com/tylim88/FireCall) - Helper Function to write easier and safer Firebase onCall function.\n4. [FireSageJS](https://github.com/tylim88/FireSageJS) - Typescript wrapper for Realtime Database Web\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftylim88%2Ffiresword","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftylim88%2Ffiresword","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftylim88%2Ffiresword/lists"}