{"_id":"@adysre/rules-next","name":"@adysre/rules-next","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@adysre/rules-next","version":"0.1.0","type":"module","description":"Rules over HTTP: route handlers and server actions on the Web standard, so they run in Next.js and anywhere else.","license":"MIT","repository":{"type":"git","url":"git+https://github.com/abhisheksahu093/Adysre.git","directory":"packages/rules-next"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./package.json":"./package.json"},"main":"./dist/index.js","types":"./dist/index.d.ts","devDependencies":{"@types/node":"^20.16.10","tsx":"^4.19.1","typescript":"^5.6.3","@adysre/config":"0.1.0"},"dependencies":{"@adysre/rules-core":"0.1.0","@adysre/rules-types":"0.1.0","@adysre/rules-storage":"0.1.0"},"keywords":["rules-engine","business-rules","rule-builder","json-logic","decision-engine","typescript"],"engines":{"node":">=20.0.0"},"sideEffects":false,"publishConfig":{"access":"public"},"scripts":{"build":"tsc -p tsconfig.build.json && node ../config/scripts/fix-declarations.mjs","lint":"eslint src","typecheck":"tsc --noEmit","test":"tsx --test 'src/**/*.test.ts'","clean":"rm -rf dist .turbo tsconfig.tsbuildinfo tsconfig.build.tsbuildinfo"},"_id":"@adysre/rules-next@0.1.0","bugs":{"url":"https://github.com/abhisheksahu093/Adysre/issues"},"homepage":"https://github.com/abhisheksahu093/Adysre#readme","_integrity":"sha512-ofoEjO0R7YEzmNqump7lFBT4/FOcSH2Ve/ERT4y9gnIMCGa8sDlAzjYe4eYTuNcEdol0XnHwGWKme45oyJ+C4w==","_resolved":"/private/var/folders/xx/vrjl61fs7dlb3dhyq5_lgzkm0000gn/T/13ddf278a695d3c5cbfa6c4579014a40/adysre-rules-next-0.1.0.tgz","_from":"file:adysre-rules-next-0.1.0.tgz","_nodeVersion":"22.23.1","_npmVersion":"10.9.8","dist":{"integrity":"sha512-ofoEjO0R7YEzmNqump7lFBT4/FOcSH2Ve/ERT4y9gnIMCGa8sDlAzjYe4eYTuNcEdol0XnHwGWKme45oyJ+C4w==","shasum":"04c6601545b0dab7d8dedc487887f9619860adba","tarball":"https://registry.npmjs.org/@adysre/rules-next/-/rules-next-0.1.0.tgz","fileCount":22,"unpackedSize":52073,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDeAGq+S1E2mQX8zqWJ4zpvUveO3AyKzuttHv7xe+BJSwIhAJCAoAc0HtNtqQ7X+u+uwGRVxRb2cQ91TJ5Lp9T3vBMN"}]},"_npmUser":{"name":"adysre","email":"abhisheksahu093@gmail.com"},"directories":{},"maintainers":[{"name":"adysre","email":"abhisheksahu093@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/rules-next_0.1.0_1785148403232_0.8507619630952261"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-27T10:33:23.052Z","0.1.0":"2026-07-27T10:33:23.395Z","modified":"2026-07-27T10:33:23.640Z"},"maintainers":[{"name":"adysre","email":"abhisheksahu093@gmail.com"}],"description":"Rules over HTTP: route handlers and server actions on the Web standard, so they run in Next.js and anywhere else.","homepage":"https://github.com/abhisheksahu093/Adysre#readme","keywords":["rules-engine","business-rules","rule-builder","json-logic","decision-engine","typescript"],"repository":{"type":"git","url":"git+https://github.com/abhisheksahu093/Adysre.git","directory":"packages/rules-next"},"bugs":{"url":"https://github.com/abhisheksahu093/Adysre/issues"},"license":"MIT","readme":"# @adysre/rules-next\n\nRules over HTTP.\n\n```ts\n// lib/rules/api.ts\nexport const api = createRuleApi({\n  storage,\n  registry,\n  authorize: async (request, action) => {\n    const session = await verify(request);\n    if (session === null) return { allowed: false, status: 401 };\n    return { allowed: can(session, action), actor: session.userId };\n  },\n});\n\n// app/api/v1/rules/route.ts\nexport const GET = nextRoute(api.list);\nexport const POST = nextRoute(api.save);\n\n// app/api/v1/rules/[id]/route.ts\nexport const GET = nextRoute(api.get);\nexport const DELETE = nextRoute(api.remove);\n```\n\n## It imports nothing from Next\n\nHandlers are `(Request) => Response`, which is all the App Router asks for and\nall any other Web-standard runtime asks for either. The same handlers run under\nHono, Deno, Bun or a Cloudflare Worker, and a change to Next's own helpers\ncannot break them.\n\n`nextRoute` is the only thing here that knows Next exists, and it still does not\nimport it. The App Router passes dynamic segments as a plain object in Next 14\nand as a **promise** in Next 15; a package that hard-coded either would break on\nan upgrade it has no stake in, so handlers take resolved parameters and\n`nextRoute` awaits whichever shape arrived.\n\nThat is also why the tests are integration tests. A test builds a real\n`Request`, reads a real `Response`, and needs no server, no Next and no mocks.\n\n## `authorize` is required\n\nNot optional with a permissive default. Deny by default is the rule, and the way\nan unauthenticated rules API reaches production is a factory that worked without\nbeing told about auth.\n\nA callback that **throws** is a refusal, not a pass. A database blip or an\nexpired key inside the host's check must not fall open: the only failure worse\nthan refusing a legitimate request is admitting an illegitimate one.\n\nThe check is asked about the **specific rule**, not just the route — `{ type:\n'write', id }` — so a host can decide per rule, per tenant, per owner, without\nthis package knowing what any of those are.\n\n## A body is parsed, never cast\n\nEvery document arrives through `parseRule`, the same door an import uses. So a\ndocument written by an older engine is migrated on the way in, one written by a\n**newer** engine is refused rather than half-understood, and anything that is\nnot a rule is a 400 with the reason.\n\nA rule naming an operator this deployment does not have is refused too. Storing\nit would move the failure from a clear message at save time to an `errored`\nverdict at three in the morning.\n\n## The envelope\n\nADYSRE's, from [`API_STANDARDS.md`](../../documents/API_STANDARDS.md) — a rules\nendpoint answering differently from every other endpoint in the platform would\nmake the client that consumes both carry two readers.\n\n```jsonc\n{ \"success\": true,  \"message\": \"OK\", \"data\": {}, \"meta\": { \"page\": 1, \"pageSize\": 25 } }\n{ \"success\": false, \"code\": \"VALIDATION_ERROR\", \"message\": \"…\" }\n```\n\nFiltering is `?filter[kind]=validation`, `?filter[tags]=a,b` (repeatable or\ncomma separated — both are what a caller reasonably writes). Paging is `page`\nand `pageSize`, capped, so one request cannot ask a store for everything.\n\nA malformed `page` is **ignored** rather than rejected: a stale bookmark with a\ntypo should show page 1, not an error screen. A `filter[kind]` naming something\nthat is not a kind **is** rejected, because that is a closed set and answering\nwith the whole list reads as \"the filter does nothing\".\n\n`total` appears only when the adapter can count without paging. The other way to\nproduce it is to load every row, which is not something a handler should do\nquietly.\n\n## Server actions\n\n`ruleAction` runs the same handler behind an action, so a route and an action\ncannot drift into disagreeing about who may save a rule. The `'use server'`\ndirective belongs to the file that declares the action, so the host writes it:\n\n```ts\n// app/rules/actions.ts\n'use server';\nexport const saveRule = ruleAction(api.save);\n```\n\nA failure comes back as a **value**, not an exception. An action's caller is a\ncomponent, and a component needs something it can render.\n\nSee [`documents/RULES_ENGINE.md`](../../documents/RULES_ENGINE.md).\n","readmeFilename":"README.md","_rev":"1-9bf814d0e29307db9264b0c167cd7924"}