{"_id":"@amrachraf6690/resourcesjs","_rev":"3-99768760ebbbbd25299ea7b4df87227b","name":"@amrachraf6690/resourcesjs","dist-tags":{"latest":"0.1.2"},"versions":{"0.1.0":{"name":"@amrachraf6690/resourcesjs","version":"0.1.0","keywords":["api","resource","serializer","typescript","laravel","openapi","zod"],"author":{"name":"Amr Achraf"},"license":"MIT","_id":"@amrachraf6690/resourcesjs@0.1.0","maintainers":[{"name":"amrachraf6690","email":"amrachraf6690@gmail.com"}],"dist":{"shasum":"fe47d7677f69fab04c0a7f8a21f7291a4c0bf948","tarball":"https://registry.npmjs.org/@amrachraf6690/resourcesjs/-/resourcesjs-0.1.0.tgz","fileCount":9,"integrity":"sha512-92yAQFI+lHkw6FS9xZZk4w1f0wyuNtFUl9SPibzRsrrwhE+6oSIUrCAOJLRAEydprIcf0ZT9K77akN25evrzpQ==","signatures":[{"sig":"MEUCIAqSPfc41exntjpEklsrWnUBhe4K/LqlzMFM4asxBYo6AiEA11lMU9+QKq8dUYdeo34JMsB+LJXNvgwoS3ZcW7u9EVk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":63084},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18.18.0"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}}},"scripts":{"test":"vitest run","build":"tsup","check":"npm run typecheck && npm run test:coverage && npm run test:smoke && npm pack --dry-run","typecheck":"tsc --noEmit","test:smoke":"npm run build && node tests/smoke/esm.mjs && node tests/smoke/cjs.cjs","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"amrachraf6690","email":"amrachraf6690@gmail.com"},"_npmVersion":"10.9.0","description":"Laravel-inspired API resources for Node.js and TypeScript","directories":{},"sideEffects":false,"_nodeVersion":"22.12.0","dependencies":{"zod":"^4.4.3"},"_hasShrinkwrap":false,"devDependencies":{"hono":"^4.12.25","rxjs":"^7.8.2","tsup":"^8.5.1","vite":"^6.4.3","elysia":"^1.4.28","vitest":"^3.2.4","express":"^5.2.1","fastify":"^4.29.1","typescript":"^6.0.3","@types/node":"^24.10.1","@adonisjs/core":"6.2.3","@elysiajs/node":"^1.4.5","@nestjs/common":"^10.4.22","@types/express":"^5.0.6","reflect-metadata":"^0.2.2","@vitest/coverage-v8":"^3.2.4"},"_npmOperationalInternal":{"tmp":"tmp/resourcesjs_0.1.0_1781640428315_0.3557820454243361","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@amrachraf6690/resourcesjs","version":"0.1.1","keywords":["api","resource","serializer","typescript","laravel","openapi","zod"],"author":{"name":"Amr Achraf"},"license":"MIT","_id":"@amrachraf6690/resourcesjs@0.1.1","maintainers":[{"name":"amrachraf6690","email":"amrachraf6690@gmail.com"}],"homepage":"https://github.com/amrachraf6699/resourcesjs","bugs":{"url":"https://github.com/amrachraf6699/resourcesjs/issues"},"dist":{"shasum":"71b3db0c2bc9159ae42fdad3240d5b5a6801e20f","tarball":"https://registry.npmjs.org/@amrachraf6690/resourcesjs/-/resourcesjs-0.1.1.tgz","fileCount":9,"integrity":"sha512-yaJE90LMHCyON9YRrNerylxN2tTJyWdQGFyTzv6RDPIBY3jIyOtowZr38cTnztNH/cphY5owz9h0KlrawjJc6g==","signatures":[{"sig":"MEYCIQCz016kRd8gxGO9edGaXGQ+sipozIALppFZCdIjiF4aPQIhAOgCCOrGtGzFHW9ULA+azzjeX+vSBgTByUhQZtLnjPzW","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":63336},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18.18.0"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}}},"gitHead":"42ec9368256d0b47a25872d7d5140fef11dca4c4","scripts":{"test":"vitest run","build":"tsup","check":"npm run typecheck && npm run test:coverage && npm run test:smoke && npm pack --dry-run","typecheck":"tsc --noEmit","test:smoke":"npm run build && node tests/smoke/esm.mjs && node tests/smoke/cjs.cjs","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"amrachraf6690","email":"amrachraf6690@gmail.com"},"repository":{"url":"git+https://github.com/amrachraf6699/resourcesjs.git","type":"git"},"_npmVersion":"10.9.0","description":"Laravel-inspired API resources for Node.js and TypeScript","directories":{},"sideEffects":false,"_nodeVersion":"22.12.0","dependencies":{"zod":"^4.4.3"},"_hasShrinkwrap":false,"devDependencies":{"hono":"^4.12.25","rxjs":"^7.8.2","tsup":"^8.5.1","vite":"^6.4.3","elysia":"^1.4.28","vitest":"^3.2.4","express":"^5.2.1","fastify":"^4.29.1","typescript":"^6.0.3","@types/node":"^24.10.1","@adonisjs/core":"6.2.3","@elysiajs/node":"^1.4.5","@nestjs/common":"^10.4.22","@types/express":"^5.0.6","reflect-metadata":"^0.2.2","@vitest/coverage-v8":"^3.2.4"},"_npmOperationalInternal":{"tmp":"tmp/resourcesjs_0.1.1_1781641288209_0.7900546894730422","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@amrachraf6690/resourcesjs","version":"0.1.2","description":"Laravel-inspired API resources for Node.js and TypeScript","keywords":["api","resource","serializer","typescript","laravel","openapi","zod"],"license":"MIT","author":{"name":"Amr Achraf"},"homepage":"https://resourcesjs.amrachraf.cloud","repository":{"type":"git","url":"git+https://github.com/amrachraf6699/resourcesjs.git"},"bugs":{"url":"https://github.com/amrachraf6699/resourcesjs/issues"},"type":"module","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}}},"sideEffects":false,"engines":{"node":">=18.18.0"},"scripts":{"build":"tsup","typecheck":"tsc --noEmit","test":"vitest run","test:coverage":"vitest run --coverage","test:smoke":"npm run build && node tests/smoke/esm.mjs && node tests/smoke/cjs.cjs","check":"npm run typecheck && npm run test:coverage && npm run test:smoke && npm pack --dry-run"},"dependencies":{"zod":"^4.4.3"},"devDependencies":{"@adonisjs/core":"6.2.3","@elysiajs/node":"^1.4.5","@nestjs/common":"^10.4.22","@types/node":"^24.10.1","@types/express":"^5.0.6","@vitest/coverage-v8":"^3.2.4","elysia":"^1.4.28","express":"^5.2.1","fastify":"^4.29.1","hono":"^4.12.25","reflect-metadata":"^0.2.2","rxjs":"^7.8.2","tsup":"^8.5.1","typescript":"^6.0.3","vite":"^6.4.3","vitest":"^3.2.4"},"_id":"@amrachraf6690/resourcesjs@0.1.2","gitHead":"4b5da4c8968e5efb153448dedac93d7ce27ec8fb","_nodeVersion":"22.12.0","_npmVersion":"10.9.0","dist":{"integrity":"sha512-+32IZMUAeWIe4xCkHSKBw5pDsbYtObg/TQXAEc2VnvoVgsXS76IFhdz3bSLpddUYBIOqwoXQfi/U79a3P9LwDA==","shasum":"5ca9979711cd5a85982e179abe06d56f20040ec6","tarball":"https://registry.npmjs.org/@amrachraf6690/resourcesjs/-/resourcesjs-0.1.2.tgz","fileCount":9,"unpackedSize":63327,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIDUOYig+MBSf97T32LpZJ1aIKQsuG5lNdWwHVlFwZbarAiEAwMENLphd4ENVlGk7Vt/9sKxs9mxywfwrBRnglQj/IDA="}]},"_npmUser":{"name":"amrachraf6690","email":"amrachraf6690@gmail.com"},"directories":{},"maintainers":[{"name":"amrachraf6690","email":"amrachraf6690@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/resourcesjs_0.1.2_1781644573876_0.1747444214912972"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-16T20:07:08.161Z","modified":"2026-06-16T21:16:14.151Z","0.1.0":"2026-06-16T20:07:08.444Z","0.1.1":"2026-06-16T20:21:28.360Z","0.1.2":"2026-06-16T21:16:14.026Z"},"bugs":{"url":"https://github.com/amrachraf6699/resourcesjs/issues"},"author":{"name":"Amr Achraf"},"license":"MIT","homepage":"https://resourcesjs.amrachraf.cloud","keywords":["api","resource","serializer","typescript","laravel","openapi","zod"],"repository":{"type":"git","url":"git+https://github.com/amrachraf6699/resourcesjs.git"},"description":"Laravel-inspired API resources for Node.js and TypeScript","maintainers":[{"name":"amrachraf6690","email":"amrachraf6690@gmail.com"}],"readme":"# ResourcesJS\n\nLaravel-inspired API Resources for Node.js and TypeScript.\n\nResourcesJS keeps response serialization out of controllers by turning it into\nreusable, strongly typed Resource classes. It works with any framework that can\nreturn or send a plain JavaScript object.\n\n## Install\n\n```bash\nnpm install @amrachraf6690/resourcesjs zod\n```\n\nResourcesJS supports Node.js 18.18 and newer, ESM, and CommonJS.\n\n## Create A Resource\n\n```ts\nimport { Resource, type InferResource } from \"@amrachraf6690/resourcesjs\";\n\ninterface User {\n  id: number;\n  name: string;\n  email: string;\n}\n\nclass UserResource extends Resource<User> {\n  toArray() {\n    return {\n      id: this.resource.id,\n      name: this.resource.name,\n      email: this.resource.email,\n    };\n  }\n}\n\nconst result = UserResource.make(user);\ntype UserResponse = InferResource<typeof UserResource>;\n```\n\n`result` is a serializable object:\n\n```json\n{\n  \"data\": {\n    \"id\": 1,\n    \"name\": \"John\",\n    \"email\": \"john@example.com\"\n  }\n}\n```\n\nPassing `null` to `make()` returns `{ \"data\": null }`.\n\n## Collections And Pagination\n\n```ts\nconst collection = UserResource.collection(users);\n\nconst page = UserResource.paginated({\n  data: users,\n  page: 1,\n  perPage: 10,\n  total: 100,\n});\n```\n\nPaginated output:\n\n```json\n{\n  \"data\": [],\n  \"meta\": {\n    \"page\": 1,\n    \"perPage\": 10,\n    \"total\": 100\n  }\n}\n```\n\n## Nested Resources\n\nResource envelopes are automatically unwrapped when nested. Ordinary objects\nthat happen to contain a `data` property remain unchanged.\n\n```ts\nclass UserResource extends Resource<User> {\n  toArray() {\n    return {\n      id: this.resource.id,\n      profile: ProfileResource.make(this.resource.profile),\n      posts: PostResource.collection(this.resource.posts),\n    };\n  }\n}\n```\n\n## Conditional Fields\n\n`when()` and `unless()` remove missing values recursively. Their values can be\nfunctions for lazy evaluation. `mergeWhen()` conditionally spreads an object.\n\n```ts\nclass UserResource extends Resource<User> {\n  toArray() {\n    return {\n      id: this.resource.id,\n      email: this.when(isAdmin, () => this.resource.email),\n      nickname: this.unless(isGuest, this.resource.nickname),\n      ...this.mergeWhen(isAdmin, {\n        permissions: this.resource.permissions,\n      }),\n    };\n  }\n}\n```\n\nConditional properties become optional in `InferResource`.\n\n## Metadata\n\nOverride `meta()` to add metadata to a single top-level resource:\n\n```ts\nclass UserResource extends Resource<User> {\n  toArray() {\n    return { id: this.resource.id };\n  }\n\n  meta() {\n    return { version: \"1.0\" };\n  }\n}\n```\n\nNested and collection item metadata is intentionally discarded.\n\n## Zod Validation\n\nDeclare a static Zod schema to validate and transform resolved Resource data.\nThe parsed Zod output becomes the final response and drives `InferResource`.\n\n```ts\nimport { z } from \"zod\";\nimport {\n  Resource,\n  ResourceValidationError,\n} from \"@amrachraf6690/resourcesjs\";\n\nclass UserResource extends Resource<User> {\n  static schema = z.object({\n    id: z.number(),\n    name: z.string(),\n  });\n\n  toArray() {\n    return {\n      id: this.resource.id,\n      name: this.resource.name,\n    };\n  }\n}\n```\n\nInvalid output throws `ResourceValidationError`. The error exposes\n`resourceName`, `itemIndex` for collection failures, and the original Zod error\nas `cause`.\n\n## OpenAPI Schema\n\n```ts\nimport { generateOpenAPI } from \"@amrachraf6690/resourcesjs\";\n\nconst schema = generateOpenAPI(UserResource);\n```\n\n`generateOpenAPI()` returns an OpenAPI 3.0-compatible inner schema object from\nthe Resource's static Zod schema. It documents the schema input because Zod\ntransforms can change runtime output and cannot always be represented in\nOpenAPI. A Resource without a schema throws a clear error.\n\n## Standardized Success Response\n\n```ts\nimport { response } from \"@amrachraf6690/resourcesjs\";\n\nresponse.ok(UserResource.make(user));\n```\n\nOutput:\n\n```json\n{\n  \"success\": true,\n  \"data\": {\n    \"id\": 1\n  }\n}\n```\n\nResource metadata is preserved. Ordinary values are wrapped under `data`.\n\n## Framework Usage\n\nResourcesJS has no framework runtime dependency. Return or send the Resource\nresult using the framework's normal response API:\n\n```ts\n// Express\napp.get(\"/user\", (_request, response) => {\n  response.json(UserResource.make(user));\n});\n\n// Fastify\napp.get(\"/user\", () => UserResource.make(user));\n\n// Hono\napp.get(\"/user\", (context) => context.json(UserResource.make(user)));\n\n// NestJS\n@Get()\nshow() {\n  return UserResource.make(user);\n}\n\n// Elysia\napp.get(\"/user\", () => UserResource.make(user));\n\n// AdonisJS controller\nshow() {\n  return UserResource.make(user);\n}\n```\n\nThe repository verifies compatibility with Express 5, Fastify 4, Hono 4,\nNestJS 10, Elysia 1.4, and AdonisJS 6.2.x.\n\nElysia's current Node adapter expects a global Web Crypto implementation. On\nNode 18, initialize `globalThis.crypto` from `node:crypto`. Node 20 and newer\nprovide it globally.\n\n## API\n\n- `Resource<T>`: base class for synchronous Resources.\n- `Resource.make(value | null)`: serialize one value.\n- `Resource.collection(values)`: serialize a collection.\n- `Resource.paginated(input)`: serialize a paginated collection.\n- `when()`, `unless()`, `mergeWhen()`: conditional serialization helpers.\n- `InferResource<typeof ResourceClass>`: infer resolved or schema output.\n- `generateOpenAPI(ResourceClass)`: generate an inner OpenAPI schema object.\n- `response.ok(value)`: create a standardized successful response.\n\nResourcesJS recursively resolves arrays and plain objects without mutating the\nsource data. It preserves non-plain serializable values such as `Date` and\nthrows when it detects a cyclic plain object or array.\n\n## Development\n\n```bash\nnpm install\nnpm run check\n```\n\n## License\n\nMIT\n","readmeFilename":"README.md"}