@nxgt/janus-graphql 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +248 -0
  3. package/dist/context.d.ts +63 -0
  4. package/dist/context.d.ts.map +1 -0
  5. package/dist/directives/apply.d.ts +46 -0
  6. package/dist/directives/apply.d.ts.map +1 -0
  7. package/dist/directives/guard.d.ts +21 -0
  8. package/dist/directives/guard.d.ts.map +1 -0
  9. package/dist/directives/permission/enforce.d.ts +24 -0
  10. package/dist/directives/permission/enforce.d.ts.map +1 -0
  11. package/dist/directives/permission/model.d.ts +35 -0
  12. package/dist/directives/permission/model.d.ts.map +1 -0
  13. package/dist/directives/permission/path.d.ts +31 -0
  14. package/dist/directives/permission/path.d.ts.map +1 -0
  15. package/dist/directives/permission/validate.d.ts +42 -0
  16. package/dist/directives/permission/validate.d.ts.map +1 -0
  17. package/dist/directives/read.d.ts +35 -0
  18. package/dist/directives/read.d.ts.map +1 -0
  19. package/dist/directives/validate.d.ts +33 -0
  20. package/dist/directives/validate.d.ts.map +1 -0
  21. package/dist/directives/words.d.ts +7 -0
  22. package/dist/directives/words.d.ts.map +1 -0
  23. package/dist/errors.d.ts +44 -0
  24. package/dist/errors.d.ts.map +1 -0
  25. package/dist/helpers.d.ts +56 -0
  26. package/dist/helpers.d.ts.map +1 -0
  27. package/dist/index.d.ts +27 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +645 -0
  30. package/dist/index.js.map +24 -0
  31. package/dist/memo.d.ts +16 -0
  32. package/dist/memo.d.ts.map +1 -0
  33. package/dist/plugin.d.ts +32 -0
  34. package/dist/plugin.d.ts.map +1 -0
  35. package/dist/sdl.d.ts +12 -0
  36. package/dist/sdl.d.ts.map +1 -0
  37. package/dist/types.d.ts +64 -0
  38. package/dist/types.d.ts.map +1 -0
  39. package/dist/wiring.d.ts +56 -0
  40. package/dist/wiring.d.ts.map +1 -0
  41. package/docs/README.md +18 -0
  42. package/docs/guide/context.md +156 -0
  43. package/docs/guide/directives.md +312 -0
  44. package/docs/guide/errors.md +138 -0
  45. package/docs/guide/federation.md +70 -0
  46. package/docs/guide/telemetry.md +45 -0
  47. package/docs/roadmap.md +59 -0
  48. package/docs/troubleshooting.md +471 -0
  49. package/graphql/janus.graphqls +36 -0
  50. package/package.json +71 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Steve Tsala
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,248 @@
1
+ # @nxgt/janus-graphql
2
+
3
+ [`@nxgt/janus`](https://www.npmjs.com/package/@nxgt/janus) in a GraphQL
4
+ server: the signed-in user on the context, authenticated only when a field
5
+ asks, fields and types guarded by `@authenticated` and `@permission`, and
6
+ every error answered with the status it deserves — an outage as 503, never
7
+ as 401, 403 or 404.
8
+
9
+ An [envelop](https://the-guild.dev/graphql/envelop) plugin, so it runs in
10
+ [GraphQL Yoga](https://the-guild.dev/graphql/yoga-server) and any server built
11
+ on envelop.
12
+
13
+ ```ts
14
+ import { janusMaskError, janusTypeDefs, type JanusContext, useJanus } from '@nxgt/janus-graphql';
15
+ import { createSchema, createYoga, type YogaInitialContext } from 'graphql-yoga';
16
+ import { access } from './access'; // what permissions() answered
17
+ import { auth } from './auth'; // what janus() answered
18
+
19
+ type Context = YogaInitialContext & JanusContext<typeof auth, typeof access>;
20
+
21
+ const typeDefs = /* GraphQL */ `
22
+ type Query {
23
+ me: User @authenticated
24
+ ward(id: ID!): Ward @permission(name: "enter", type: "ward")
25
+ }
26
+ type User { id: ID!, email: String }
27
+ type Ward { id: ID!, name: String, roster: [String!] @authenticated(type: ["staff"]) }
28
+ `;
29
+
30
+ export const yoga = createYoga({
31
+ schema: createSchema({
32
+ typeDefs: [janusTypeDefs, typeDefs],
33
+ resolvers: {
34
+ Query: {
35
+ me: async (_: unknown, __: unknown, ctx: Context) => ctx.janus.user(), // never null here
36
+ ward: (_: unknown, { id }: { id: string }) => wards.find(id), // only for a user who may enter it
37
+ },
38
+ },
39
+ }),
40
+ plugins: [useJanus({ auth, access })],
41
+ maskedErrors: { maskError: janusMaskError() }, // STORE_FAILED → 503, not "Unexpected error."
42
+ });
43
+ ```
44
+
45
+ > **0.x.** A minor version may still change the surface; the changelog says how.
46
+
47
+ ## Install
48
+
49
+ ```sh
50
+ bun add @nxgt/janus-graphql @nxgt/janus graphql @graphql-tools/utils @envelop/core
51
+ bun add -d typescript # 6
52
+ ```
53
+
54
+ Every peer is required:
55
+
56
+ | Peer | Range | Why |
57
+ | --- | --- | --- |
58
+ | `@nxgt/janus` | the minor released beside it — `peerDependencies` states the range | A **peer**, never a dependency: this package defines no error class, so the `JanusError` a resolver throws is the one you import |
59
+ | `graphql` | `^16.9.0 \|\| ^17.0.0` | The schema and `GraphQLError` are yours; a second copy of `graphql` fails every schema |
60
+ | `@graphql-tools/utils` | `>=10.0.0 <13` | `mapSchema` and `getDirective`, which apply the directives |
61
+ | `@envelop/core` | `^5.0.0` | Types only — the `Plugin` `useJanus()` answers. Yoga already brings it |
62
+ | `typescript` | `^6.0.3` | As for `@nxgt/janus` |
63
+
64
+ The floors are tested, not claimed: the package's specs and its typecheck run
65
+ on `graphql` 16.9.0, `@graphql-tools/utils` 10.0.0 and `@envelop/core` 5.0.0
66
+ as well, together, with a single copy of `graphql`, in the Floors job, on
67
+ every CI run.
68
+
69
+ Like `@nxgt/janus`, it expects `"moduleResolution": "bundler"`: the
70
+ declarations import without extensions, so `nodenext` is not supported.
71
+
72
+ The SDL ships twice: `janusTypeDefs`, a string for `createSchema`, and
73
+ `graphql/janus.graphqls` in the package, for a code generator or an editor
74
+ that reads files — `node_modules/@nxgt/janus-graphql/graphql/janus.graphqls`.
75
+
76
+ ## API
77
+
78
+ | Export | What it is |
79
+ | --- | --- |
80
+ | `useJanus({ auth, access?, type?, loaders?, conditions? })` | The envelop plugin. Adds `ctx.janus` to every request's context, and applies the directives to every schema the server is given, once. `type` treats a user of any other type as anonymous. `loaders` and `conditions` are what `@permission` needs beside `access` |
81
+ | `ctx.janus.user()`, `ctx.janus.session()` | Who the request belongs to, and the session it presented — `null` for an anonymous request. `auth.authenticate(request)` runs the first time either is asked, once per request, and never when neither is |
82
+ | `ctx.janus.access` | The `permissions()` instance given to `useJanus()`. Absent from the type — and from the context — without one |
83
+ | `janusTypeDefs` | The SDL: `@authenticated`, `@permission` and the `JanusPermissionDenial` enum — prefixed, so it never collides with a type of your schema. The same text as `graphql/janus.graphqls` |
84
+ | `@authenticated(type: [String!])` | On a field, a type or an interface. A signed-in user, of one of the `type`s when it names some. Anonymous: `UNAUTHENTICATED`. Another type: `FORBIDDEN`. Every one that applies — the field's, its type's, its interfaces' — must hold |
85
+ | `@permission(name, type, id, onDeny)` | On a field, a type or an interface. A user holding permission `name` on the object of `type` whose id `id` reads — `args.<path>` or `parent.<path>`; `args.id` on a field, `parent.id` on a type. A list requires it on every id. Repeated, every one must hold, in order. Denied: `NOT_FOUND`, or `FORBIDDEN` with `onDeny: FORBIDDEN` |
86
+ | `loaders: { [type]: (id, ctx) => object \| null }` | The object `@permission` checks for an id alone — from `args`, or a parent field other than `id` — required for a type with a `fromField`, whose fields `access.can` reads. `null` answers `NOT_FOUND` |
87
+ | `conditions: { [type]: (object, ctx) => ctx }` | The `ctx` `@permission` passes to `access.can` for a permission that reaches a `when()` |
88
+ | `applyJanusDirectives(schema, { auth, type?, access?, loaders?, conditions? })` | The schema transform alone — what `useJanus()` runs — to check a schema in a test or a build script. Throws a `TypeError` naming the field for a directive no request could pass. Its guards read `ctx.janus`, which only `useJanus()` builds |
89
+ | `requireUser(ctx, { type? })` | The signed-in user, narrowed to `type` — one or a non-empty list — or a denial: `UNAUTHENTICATED`, `FORBIDDEN` |
90
+ | `can(ctx, permission, object, options?)` | `access.can` for the request's user, typed as `access.can` is. Anonymous answers `false`. Shares the request's checks with `@permission`: one question, one check per request |
91
+ | `janusMaskError(fallback?)` | Yoga's `maskedErrors.maskError`: a `JanusError` a resolver let through answered with its code and status; anything else to `fallback` |
92
+ | `janusGraphQLError(error)` | A `JanusError` as the `GraphQLError` the client reads: its code, its status, and only what the client can act on |
93
+ | `denial(code, message?)` | A denial of your own: `UNAUTHENTICATED` 401, `FORBIDDEN` 403, `NOT_FOUND` 404 |
94
+ | `JanusContext<typeof auth, typeof access?, Type?>` | What `useJanus()` adds to the context, for your resolvers' `ctx` |
95
+ | `JanusOptions`, `JanusOnContext`, `JanusDirectivesOptions`, `Loaders`, `Conditions`, `LoadedObject`, `RequireUserOptions`, `Auth`, `UserOfAuth`, `DenialCode`, `MaskError` | The types of the arguments and answers above |
96
+
97
+ ## Directives
98
+
99
+ ```graphql
100
+ type Query {
101
+ me: User @authenticated # any signed-in user
102
+ record(id: ID!): Record @permission(name: "view", type: "record") # args.id
103
+ records(ids: [ID!]!): [Record!]!
104
+ @permission(name: "view", type: "record", id: "args.ids") # every id
105
+ }
106
+
107
+ type Record @permission(name: "view", type: "record") { # parent.id: the record itself
108
+ title: String
109
+ billing: Billing @permission(name: "manage", type: "record", onDeny: FORBIDDEN)
110
+ }
111
+ ```
112
+
113
+ ```ts
114
+ useJanus({
115
+ auth,
116
+ access,
117
+ loaders: { record: (id, ctx) => records.find(id) }, // record has a fromField: an id alone is not enough
118
+ conditions: { record: (record, ctx: Context) => ({ onShift: ctx.shift.open }) }, // the ctx of a when() record's permissions reach
119
+ });
120
+ ```
121
+
122
+ Each runs before the resolver, which a refused request never reaches.
123
+ `@permission` implies a signed-in user, and asks `access.can` with the parent
124
+ itself when the id is `parent.id`, with what `loaders[type]` answers for an
125
+ id of a type with a `fromField`, and with `{ type, id }` otherwise. Every
126
+ directive that applies must hold — the field's, its type's, its
127
+ interfaces' — asked outermost first, so a type's `NOT_FOUND` answers before
128
+ a field's `FORBIDDEN` could tell the object exists. The same question asked
129
+ twice in a request, by two fields or by a directive and `can()`, is one
130
+ check. What no request could pass — an object type or a permission the
131
+ model does not declare, a malformed `id:`, an argument the field does not
132
+ take, a missing loader or condition — is a `TypeError` at start-up naming
133
+ the field. Detail: [the directives guide](docs/guide/directives.md).
134
+
135
+ ## Errors
136
+
137
+ This package defines **no error class**. A denial is a `GraphQLError`, and
138
+ every one carries a `code` and the HTTP status Yoga answers with:
139
+
140
+ ```json
141
+ { "errors": [{ "message": "Not signed in", "path": ["me"], "extensions": { "code": "UNAUTHENTICATED" } }] }
142
+ ```
143
+
144
+ | Code | Status | When |
145
+ | --- | --- | --- |
146
+ | `UNAUTHENTICATED` | 401 | An anonymous request reached a guarded field, or `requireUser()` |
147
+ | `FORBIDDEN` | 403 | A user of a type the directive or `requireUser()` does not name; a `@permission(onDeny: FORBIDDEN)` denied |
148
+ | `NOT_FOUND` | 404 | A `@permission` denied — its default — or a loader answered `null`; `denial('NOT_FOUND')` |
149
+ | `SERVICE_UNAVAILABLE` | 503 | A store could not answer — `STORE_FAILED`. **Never a denial** |
150
+ | any other `JanusErrorCode` | its [`statusOf`](https://github.com/softistx/nxgt-janus/blob/develop/packages/janus/docs/guide/errors.md) | A `JanusError` a resolver let through: `CREDENTIALS_INVALID` 401, `LOGIN_TAKEN` 409, … |
151
+
152
+ The status sits in `extensions.http.status`, which Yoga reads to answer the
153
+ HTTP response and strips from the body. `USER_INVALID` adds `issues`,
154
+ `PASSWORD_TOO_SHORT` `minLength` and `CODE_INVALID` `attemptsLeft`; nothing
155
+ else — never `reason`, `login`, a hash prefix or a cause. The message is a
156
+ fixed one per status, never the core's: read `code`. Detail, and the status of a
157
+ response with several errors: [the errors guide](docs/guide/errors.md).
158
+
159
+ ## Traps
160
+
161
+ - **Without `janusMaskError()`, a `JanusError` from a resolver is Yoga's
162
+ masked 500.** A guarded field and `requireUser()` answer an outage 503 on
163
+ their own; `ctx.janus.user()` read directly, or `auth.signIn` in a mutation,
164
+ does not. Wire `maskedErrors: { maskError: janusMaskError() }`.
165
+ - **`ctx.janus.user()` is `null` where no directive guards the field.** Read
166
+ it only where anonymous is a valid answer, or call `requireUser(ctx)`.
167
+ - **A type's `@authenticated` guards its fields, not the field that returns
168
+ it.** `type Ward @authenticated` leaves `Query.ward` open: its resolver runs
169
+ for an anonymous request, and the error lands on `ward.name`. Put the
170
+ directive on the field too — or call `requireUser()`, as above — when the
171
+ lookup itself must not run.
172
+ - **One refused field sets the status of the whole response.** Yoga answers
173
+ the highest `extensions.http.status` among the errors, so `{ open me }`
174
+ from an anonymous request is a 401 whose `data` still holds `open`. A client
175
+ reads `data` and `errors`, not only the status.
176
+ - **A renewed session is not sent back as a cookie.** `authenticate` renews
177
+ a sliding session in passing, and nothing here sets `Set-Cookie` on the
178
+ GraphQL response: a browser keeps the cookie with its old expiry. Sign in
179
+ and renew through your HTTP routes, or send a bearer token.
180
+ - **`useJanus({ type })` narrows the whole server.** A user of any other type
181
+ is anonymous everywhere, and a directive naming another type is refused
182
+ when the schema is built.
183
+ - **On a field, `@permission` reads `args.id` — even on a field of the
184
+ object.** `type Record { notes: String @permission(name: "view", type:
185
+ "record") }` is refused at start-up: `notes` takes no `id`. Write it on the
186
+ type, whose default is `parent.id`, or say `id: "parent.id"`.
187
+ - **A `FORBIDDEN` tells the user the object exists.** Keep `NOT_FOUND`, the
188
+ default, on anything reached by an id the client chose; use
189
+ `onDeny: FORBIDDEN` on a field of an object the user may already see.
190
+ - **The parent must carry the fields its `fromField`s read.** A type-level
191
+ `@permission` checks the parent itself, so a resolver answering
192
+ `{ id, title }` for a record whose `doctors` read `doctorId` denies every
193
+ doctor. Answer the field, or read the id from `args` with a loader.
194
+ - **A loader's own error is a 500.** `@permission` answers a `JanusError`
195
+ with its status, but a driver's error thrown by a loader is masked like any
196
+ other: throw `StoreFailure` for an outage, so it is a 503.
197
+ - **A request remembers its checks.** The same object, permission and user
198
+ asked twice in one request is one check — so a mutation that grants or
199
+ revokes, then asks again in the same request, reads the answer from
200
+ before the change. Call `access.can` directly there. In a subscription,
201
+ `@permission` asks afresh on every event; `can(ctx, …)` in its resolver
202
+ reads the memo — call `ctx.janus.access.can` there.
203
+ - **`@permission` is not a filter.** A list of ids requires the permission
204
+ on every one, and one denial denies the field; to answer only the items a
205
+ user may see, ask `access.list()` for their ids.
206
+ - **A context built without a request cannot authenticate.** `ctx.janus.user()`
207
+ rejects with a `TypeError` — a transport that is not HTTP, such as a
208
+ WebSocket, needs its own wiring, which is on the [roadmap](docs/roadmap.md).
209
+ - **A denial of a list field's item fails the whole list** where the item is
210
+ non-null (`[Doctor!]!`), as GraphQL's null propagation always does. Guard
211
+ the list field, or make the item nullable.
212
+
213
+ ## Documentation
214
+
215
+ - [Guides](docs/README.md) — the directives, the context and the lazy user, errors as statuses, telemetry, and federation
216
+ - [Troubleshooting](docs/troubleshooting.md) — by the message or status you see
217
+ - [Roadmap](docs/roadmap.md) — what is next, and what is not planned
218
+
219
+ ## Type safety, counted
220
+
221
+ Twenty-four plausible mistakes are refused by the compiler, each with a
222
+ `@ts-expect-error` case in `test/types/`:
223
+
224
+ - five in `context.ts`: reading the user or the session where either may be
225
+ `null` (twice), a field of another user type once `JanusContext` is narrowed,
226
+ `ctx.janus.access` where `useJanus()` was given none, and a context narrowed
227
+ to a user type the instance does not know;
228
+ - nine in `helpers.ts`: `requireUser()` — a field of another user type once
229
+ narrowed, a user type the instance does not know, an empty list of user
230
+ types — and `can()` — a
231
+ permission the object's type does not declare, an object type the model
232
+ does not, an object without a field a `fromField` reads, a condition reached
233
+ with no `ctx`, a `ctx` of the wrong shape, and a context with no `access`;
234
+ - five in `plugin.ts`: `useJanus()` given a user type the instance does not
235
+ know, something that is not what `janus()` answered, the `permissions()`
236
+ instance as `auth`, and `applyJanusDirectives()` without `auth` or narrowed
237
+ to a user type the instance does not know;
238
+ - five in `wiring.ts`: `useJanus()`'s `loaders` for an object type the model
239
+ does not declare, a loader answering an object without a field a
240
+ `fromField` reads, `conditions` for a type no `when()` is reached on, a
241
+ condition answering a `ctx` of the wrong shape, and `loaders` without
242
+ `access`.
243
+
244
+ `plugin.ts` also holds this README's quick start, which must keep compiling.
245
+
246
+ ## Licence
247
+
248
+ MIT
@@ -0,0 +1,63 @@
1
+ /**
2
+ * `ctx.janus`, built once per request by `useJanus()`: a lazy
3
+ * authentication, and the per-request check `can()` and the directives ask
4
+ * through.
5
+ */
6
+ import type { Authenticated, RequestLike } from '@nxgt/janus';
7
+ import type { JanusOnContext } from './types';
8
+ /** One check, as `access.can` answers it — loose: the typing is the caller's. */
9
+ export type Check = (subject: {
10
+ readonly type: string;
11
+ readonly id: string;
12
+ } | null, permission: string, object: {
13
+ readonly type: string;
14
+ readonly id: string;
15
+ }, options?: {
16
+ readonly ctx?: unknown;
17
+ }) => Promise<boolean>;
18
+ /** A user as this module reads one: the part every user type has. */
19
+ type AnyUser = {
20
+ readonly type: string;
21
+ readonly id: string;
22
+ };
23
+ type LooseAuth = {
24
+ authenticate(request: RequestLike, options?: {
25
+ readonly type?: string;
26
+ }): Promise<Authenticated<AnyUser> | null>;
27
+ };
28
+ type Built = JanusOnContext<AnyUser, unknown>;
29
+ /** What `createJanusContext` reads from `useJanus()`'s options. */
30
+ export interface ContextOptions {
31
+ readonly auth: LooseAuth;
32
+ readonly access?: {
33
+ readonly can: unknown;
34
+ } | undefined;
35
+ readonly type?: string | undefined;
36
+ }
37
+ /**
38
+ * `ctx.janus` for one request. `authenticate` runs the first time `user()`
39
+ * or `session()` is asked, once, and its answer — or its failure — is the
40
+ * answer of both from then on.
41
+ *
42
+ * A context with no request — a transport that is not HTTP — answers a
43
+ * `TypeError` from `user()`, not `null`: anonymous is an answer, and nothing
44
+ * here asked.
45
+ */
46
+ export declare function createJanusContext(request: RequestLike | undefined, options: ContextOptions): Built;
47
+ /**
48
+ * The check `ctx.janus` answers through: the one its context was built
49
+ * with — `access.can`, memoized for the request (`memo.ts`) — or
50
+ * `access.can` itself for a context built by hand. `null` when it has no
51
+ * `access` at all.
52
+ *
53
+ * `fresh` skips the memo: a subscription is one request whose checks run
54
+ * again on every event, and must see a `revoke()` made since it started.
55
+ */
56
+ export declare function checkOf(janus: object, fresh?: boolean): Check | null;
57
+ /**
58
+ * `ctx.janus`, or a wiring `TypeError` naming `caller` when nothing put it
59
+ * there: `useJanus()` is missing from the plugins.
60
+ */
61
+ export declare function janusOf(ctx: unknown, caller: string): Built;
62
+ export {};
63
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,iFAAiF;AACjF,MAAM,MAAM,KAAK,GAAG,CACnB,OAAO,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,EAC9D,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE,EACtD,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CAAE,KAChC,OAAO,CAAC,OAAO,CAAC,CAAC;AAEtB,qEAAqE;AACrE,KAAK,OAAO,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9D,KAAK,SAAS,GAAG;IAChB,YAAY,CACX,OAAO,EAAE,WAAW,EACpB,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAClC,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;CAC1C,CAAC;AAEF,KAAK,KAAK,GAAG,cAAc,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;AAU9C,mEAAmE;AACnE,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE;QAAE,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAA;KAAE,GAAG,SAAS,CAAC;IACxD,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACnC;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CACjC,OAAO,EAAE,WAAW,GAAG,SAAS,EAChC,OAAO,EAAE,cAAc,GACrB,KAAK,CAmCP;AAED;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,UAAQ,GAAG,KAAK,GAAG,IAAI,CAMlE;AAED;;;GAGG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,KAAK,CAQ3D"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The schema transform: every object field a directive guards gets its
3
+ * resolver wrapped, once, when the schema is built.
4
+ */
5
+ import { type GraphQLSchema } from 'graphql';
6
+ type Entries = {
7
+ readonly [type: string]: ((...args: never[]) => unknown) | undefined;
8
+ };
9
+ /** What `applyJanusDirectives()` takes. */
10
+ export interface JanusDirectivesOptions<T extends string = string> {
11
+ /** What `janus()` answered: its `types` are the names `type:` may take. */
12
+ readonly auth: {
13
+ readonly types: readonly T[];
14
+ };
15
+ /** `useJanus({ type })`'s: the one user type the schema's users can have. */
16
+ readonly type?: NoInfer<T>;
17
+ /** What `permissions()` answered: its model is what `@permission` may name. Required once a schema uses it. */
18
+ readonly access?: {
19
+ readonly model: unknown;
20
+ } | undefined;
21
+ /** `useJanus({ loaders })`'s. */
22
+ readonly loaders?: Entries | undefined;
23
+ /** `useJanus({ conditions })`'s. */
24
+ readonly conditions?: Entries | undefined;
25
+ }
26
+ /**
27
+ * The schema with every field guarded by `@authenticated` or `@permission`
28
+ * — on the field, on its type, or on an interface the type implements —
29
+ * wrapped so its resolver runs only for a signed-in user, of one of the
30
+ * types `type:` names when it names some, holding every permission asked.
31
+ * Every directive that applies must hold.
32
+ *
33
+ * `useJanus()` calls it on every schema it is given. Call it yourself to
34
+ * check a schema without a server: the guards it installs read `ctx.janus`,
35
+ * which only `useJanus()` builds.
36
+ *
37
+ * **Refuses when the schema is built**, with a `TypeError` naming the
38
+ * field: a user type `auth` does not know, a `type: []`, restrictions no
39
+ * user type meets together; for `@permission`, an object type or a
40
+ * permission the model does not declare, a malformed `id:`, an argument the
41
+ * field does not take, and a loader or a condition's `ctx` it needs and was
42
+ * not given.
43
+ */
44
+ export declare function applyJanusDirectives<const T extends string>(schema: GraphQLSchema, options: JanusDirectivesOptions<T>): GraphQLSchema;
45
+ export {};
46
+ //# sourceMappingURL=apply.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply.d.ts","sourceRoot":"","sources":["../../src/directives/apply.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAwB,KAAK,aAAa,EAAE,MAAM,SAAS,CAAC;AAMnE,KAAK,OAAO,GAAG;IACd,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,CAAC,GAAG,SAAS,CAAC;CACrE,CAAC;AAEF,2CAA2C;AAC3C,MAAM,WAAW,sBAAsB,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM;IAChE,2EAA2E;IAC3E,QAAQ,CAAC,IAAI,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,CAAA;KAAE,CAAC;IAChD,6EAA6E;IAC7E,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IAC3B,+GAA+G;IAC/G,QAAQ,CAAC,MAAM,CAAC,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;KAAE,GAAG,SAAS,CAAC;IAC1D,iCAAiC;IACjC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACvC,oCAAoC;IACpC,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAC1C;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,CAAC,CAAC,SAAS,MAAM,EAC1D,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,sBAAsB,CAAC,CAAC,CAAC,GAChC,aAAa,CAuBf"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * What runs before a guarded field's resolver, at request time: the user,
3
+ * then the requirement — its user types, then each `@permission` in order. A
4
+ * denial is a `GraphQLError`; a failure to answer is `SERVICE_UNAVAILABLE`,
5
+ * never a denial.
6
+ */
7
+ import type { GraphQLFieldResolver } from 'graphql';
8
+ import type { Requirement } from './validate';
9
+ /** What the field was asked with. */
10
+ export interface Resolving {
11
+ readonly parent: unknown;
12
+ readonly args: unknown;
13
+ readonly ctx: unknown;
14
+ /** A subscription's: its checks skip the request's memo, and see a revoke. */
15
+ readonly fresh: boolean;
16
+ }
17
+ /** Refuses the request unless it meets `requirement`. */
18
+ export declare function enforce(requirement: Requirement, { parent, args, ctx, fresh }: Resolving): Promise<void>;
19
+ /** `resolve`, run only once `requirement` is met. */
20
+ export declare function guarded<S, C, A>(resolve: GraphQLFieldResolver<S, C, A>, requirement: Requirement): GraphQLFieldResolver<S, C, A>;
21
+ //# sourceMappingURL=guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../../src/directives/guard.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAIpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,qCAAqC;AACrC,MAAM,WAAW,SAAS;IACzB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACxB;AAED,yDAAyD;AACzD,wBAAsB,OAAO,CAC5B,WAAW,EAAE,WAAW,EACxB,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,SAAS,GACrC,OAAO,CAAC,IAAI,CAAC,CAoBf;AAED,qDAAqD;AACrD,wBAAgB,OAAO,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAC9B,OAAO,EAAE,oBAAoB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EACtC,WAAW,EAAE,WAAW,GACtB,oBAAoB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAU/B"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * One `@permission`, asked at request time: the objects its `id:` names, each
3
+ * checked with the request's check — memoized per request — and a denial
4
+ * unless every one holds. A failure to answer is never a denial.
5
+ */
6
+ import type { Check } from '../../context';
7
+ import type { PermissionCheck } from './validate';
8
+ type Subject = {
9
+ readonly type: string;
10
+ readonly id: string;
11
+ };
12
+ /** What the guard has in hand when it asks. */
13
+ export interface Asking {
14
+ readonly check: Check;
15
+ readonly user: Subject;
16
+ readonly parent: unknown;
17
+ readonly args: unknown;
18
+ /** The GraphQL context, which `loaders` and `conditions` receive. */
19
+ readonly ctx: unknown;
20
+ }
21
+ /** Refuses the request unless `user` holds `permission` on every object its `id:` names. */
22
+ export declare function enforcePermission(permission: PermissionCheck, asking: Asking): Promise<void>;
23
+ export {};
24
+ //# sourceMappingURL=enforce.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enforce.d.ts","sourceRoot":"","sources":["../../../src/directives/permission/enforce.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAG3C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAGlD,KAAK,OAAO,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9D,+CAA+C;AAC/C,MAAM,WAAW,MAAM;IACtB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,qEAAqE;IACrE,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;CACtB;AAED,4FAA4F;AAC5F,wBAAsB,iBAAiB,CACtC,UAAU,EAAE,eAAe,EAC3B,MAAM,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC,CAiBf"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * What `@permission` reads of the permission model when the schema is built:
3
+ * the object types, the names each declares, whether one reads a field of
4
+ * its object (`fromField`), and whether a permission reaches a condition
5
+ * (`when`). Read from `access.model`, as `defineModel()` answered it.
6
+ */
7
+ /** `access.model`, as this module reads it. */
8
+ export interface ModelLike {
9
+ readonly types: readonly string[];
10
+ readonly definition: {
11
+ readonly types: {
12
+ readonly [type: string]: {
13
+ readonly related?: {
14
+ readonly [name: string]: unknown;
15
+ };
16
+ readonly permits?: {
17
+ readonly [name: string]: readonly unknown[];
18
+ };
19
+ };
20
+ };
21
+ };
22
+ }
23
+ /** `access.model`, or `null` when `access` is not what `permissions()` answered. */
24
+ export declare function modelOf(access: unknown): ModelLike | null;
25
+ /** The relations and permissions `type` declares: what `can()` may be asked. */
26
+ export declare function namesOf(model: ModelLike, type: string): readonly string[];
27
+ /** The fields `type`'s `fromField`s read; empty when it has none. */
28
+ export declare function fieldsOf(model: ModelLike, type: string): readonly string[];
29
+ /**
30
+ * Whether `can(…, name, { type })` reaches a `when` — through the
31
+ * permission's rules, the permissions they name, and arrows into other types
32
+ * — and so needs a `ctx`. What `CtxOf` computes in the compiler.
33
+ */
34
+ export declare function reachesCondition(model: ModelLike, type: string, name: string, seen?: Set<string>): boolean;
35
+ //# sourceMappingURL=model.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../../src/directives/permission/model.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,+CAA+C;AAC/C,MAAM,WAAW,SAAS;IACzB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,UAAU,EAAE;QACpB,QAAQ,CAAC,KAAK,EAAE;YACf,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG;gBACxB,QAAQ,CAAC,OAAO,CAAC,EAAE;oBAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;iBAAE,CAAC;gBACxD,QAAQ,CAAC,OAAO,CAAC,EAAE;oBAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,OAAO,EAAE,CAAA;iBAAE,CAAC;aACnE,CAAC;SACF,CAAC;KACF,CAAC;CACF;AAED,oFAAoF;AACpF,wBAAgB,OAAO,CAAC,MAAM,EAAE,OAAO,GAAG,SAAS,GAAG,IAAI,CAOzD;AAED,gFAAgF;AAChF,wBAAgB,OAAO,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAMzE;AAYD,qEAAqE;AACrE,wBAAgB,QAAQ,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAK1E;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC/B,KAAK,EAAE,SAAS,EAChB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,GAAG,CAAC,MAAM,CAAa,GAC3B,OAAO,CAiBT"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * `@permission`'s `id:` — `args.<path>` or `parent.<path>` — parsed when the
3
+ * schema is built, and read from a request's arguments or parent value when a
4
+ * field is resolved.
5
+ */
6
+ /** A parsed `id:`. */
7
+ export interface IdPath {
8
+ /** As written: `args.id`, `parent.wardId`, `args.input.ids`. */
9
+ readonly text: string;
10
+ readonly root: 'args' | 'parent';
11
+ /** The names after the root, at least one. */
12
+ readonly segments: readonly string[];
13
+ }
14
+ /** `text` parsed, or `null` when it is not `args.<name>…` or `parent.<name>…`. */
15
+ export declare function parsePath(text: string): IdPath | null;
16
+ /** One object a check is about: its id, and the value that holds it when that value is the object. */
17
+ export interface Target {
18
+ readonly id: string;
19
+ /** The value the id was read from, when `id` is its `id` field under `parent`. */
20
+ readonly holder: object | null;
21
+ }
22
+ /**
23
+ * The objects `path` names in this request: one, or one per element where a
24
+ * step reads a list. `null` when a step reads nothing, or an id is not a
25
+ * string or an integer: the request resolved no object id. An empty list
26
+ * under `parent` is `null` too — the object's data names nothing to check,
27
+ * and its fields are not open to everyone for it; under `args` it is the
28
+ * client asking for nothing, and there is nothing to refuse.
29
+ */
30
+ export declare function targetsOf(path: IdPath, parent: unknown, args: unknown): readonly Target[] | null;
31
+ //# sourceMappingURL=path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../../src/directives/permission/path.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,sBAAsB;AACtB,MAAM,WAAW,MAAM;IACtB,gEAAgE;IAChE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAC;IACjC,8CAA8C;IAC9C,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAID,kFAAkF;AAClF,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOrD;AAED,sGAAsG;AACtG,MAAM,WAAW,MAAM;IACtB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,kFAAkF;IAClF,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CACxB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,OAAO,EACf,IAAI,EAAE,OAAO,GACX,SAAS,MAAM,EAAE,GAAG,IAAI,CAiB1B"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * One `@permission`, checked against the model and the field when the schema
3
+ * is built, and turned into what the guard asks at request time. Everything
4
+ * no request could pass is a `TypeError` naming the field.
5
+ */
6
+ import type { PermissionUse } from '../read';
7
+ import { type IdPath } from './path';
8
+ /** `useJanus({ loaders })`'s entry: the object of a type, by id, or `null`. */
9
+ export type Loader = (id: string, ctx: unknown) => unknown;
10
+ /** `useJanus({ conditions })`'s entry: the `ctx` a type's conditions test. */
11
+ export type Condition = (object: unknown, ctx: unknown) => unknown;
12
+ /** What `@permission` reads from `useJanus()`'s options. */
13
+ export interface PermissionWiring {
14
+ readonly access?: unknown;
15
+ readonly loaders?: {
16
+ readonly [type: string]: unknown;
17
+ } | undefined;
18
+ readonly conditions?: {
19
+ readonly [type: string]: unknown;
20
+ } | undefined;
21
+ }
22
+ /** One `@permission`, as the guard asks it. */
23
+ export interface PermissionCheck {
24
+ /** `@permission on Record (read by Record.title)`, for the messages. */
25
+ readonly label: string;
26
+ readonly name: string;
27
+ readonly type: string;
28
+ readonly onDeny: 'NOT_FOUND' | 'FORBIDDEN';
29
+ readonly path: IdPath;
30
+ /** Loads an object named by id alone; `null` passes `{ type, id }`. */
31
+ readonly load: Loader | null;
32
+ /** Answers the check's `ctx`; `null` when no condition is reachable. */
33
+ readonly condition: Condition | null;
34
+ }
35
+ /** The field a directive is read for: `Type.field`, and the arguments it takes. */
36
+ export interface FieldShape {
37
+ readonly name: string;
38
+ readonly args: readonly string[];
39
+ }
40
+ /** `use`, checked, or a `TypeError` naming the field. */
41
+ export declare function permissionCheckOf(use: PermissionUse, field: FieldShape, wiring: PermissionWiring): PermissionCheck;
42
+ //# sourceMappingURL=validate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../../src/directives/permission/validate.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAS7C,OAAO,EAAE,KAAK,MAAM,EAAa,MAAM,QAAQ,CAAC;AAEhD,+EAA+E;AAC/E,MAAM,MAAM,MAAM,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC;AAC3D,8EAA8E;AAC9E,MAAM,MAAM,SAAS,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC;AAEnE,4DAA4D;AAC5D,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,OAAO,CAAC,EAAE;QAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,GAAG,SAAS,CAAC;IACpE,QAAQ,CAAC,UAAU,CAAC,EAAE;QAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,GAAG,SAAS,CAAC;CACvE;AAED,+CAA+C;AAC/C,MAAM,WAAW,eAAe;IAC/B,wEAAwE;IACxE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,WAAW,GAAG,WAAW,CAAC;IAC3C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,uEAAuE;IACvE,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,wEAAwE;IACxE,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,CAAC;CACrC;AAED,mFAAmF;AACnF,MAAM,WAAW,UAAU;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;CACjC;AAED,yDAAyD;AACzD,wBAAgB,iBAAiB,CAChC,GAAG,EAAE,aAAa,EAClB,KAAK,EAAE,UAAU,EACjB,MAAM,EAAE,gBAAgB,GACtB,eAAe,CAoCjB"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Reads the directives that apply to one object field: its own, its object
3
+ * type's, and those of every interface the type implements — on the
4
+ * interface itself and on the interface's field of the same name. Reads, and
5
+ * decides nothing: `validate.ts` decides what they add up to.
6
+ */
7
+ import type { GraphQLFieldConfig, GraphQLSchema } from 'graphql';
8
+ /** One `@authenticated`, and where it was written. */
9
+ export interface AuthenticatedUse {
10
+ /** `Type.field`, `Type`, `Interface.field` or `Interface`. */
11
+ readonly where: string;
12
+ /** Its `type:` argument, or `null` when it names none: any signed-in user. */
13
+ readonly types: readonly string[] | null;
14
+ }
15
+ /** One `@permission`, as written, and where. */
16
+ export interface PermissionUse {
17
+ /** `Type.field`, `Type`, `Interface.field` or `Interface`. */
18
+ readonly where: string;
19
+ /** Written on a type or an interface, rather than on a field. */
20
+ readonly onType: boolean;
21
+ readonly name: string;
22
+ readonly type: string;
23
+ /** Its `id:` argument, or `null` when absent. */
24
+ readonly id: string | null;
25
+ readonly onDeny: 'NOT_FOUND' | 'FORBIDDEN';
26
+ }
27
+ /** Everything written on one field's locations. */
28
+ export interface FieldDirectives {
29
+ readonly authenticated: readonly AuthenticatedUse[];
30
+ /** In the order they are checked: the type's and interfaces' first, then the field's. */
31
+ readonly permission: readonly PermissionUse[];
32
+ }
33
+ /** Every `@authenticated` and `@permission` that applies to one object field. */
34
+ export declare function readField(schema: GraphQLSchema, field: GraphQLFieldConfig<unknown, unknown>, fieldName: string, typeName: string): FieldDirectives;
35
+ //# sourceMappingURL=read.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read.d.ts","sourceRoot":"","sources":["../../src/directives/read.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EACX,kBAAkB,EAGlB,aAAa,EACb,MAAM,SAAS,CAAC;AAEjB,sDAAsD;AACtD,MAAM,WAAW,gBAAgB;IAChC,8DAA8D;IAC9D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAAC;CACzC;AAED,gDAAgD;AAChD,MAAM,WAAW,aAAa;IAC7B,8DAA8D;IAC9D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,iEAAiE;IACjE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,iDAAiD;IACjD,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,WAAW,GAAG,WAAW,CAAC;CAC3C;AAED,mDAAmD;AACnD,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,aAAa,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACpD,yFAAyF;IACzF,QAAQ,CAAC,UAAU,EAAE,SAAS,aAAa,EAAE,CAAC;CAC9C;AA0CD,iFAAiF;AACjF,wBAAgB,SAAS,CACxB,MAAM,EAAE,aAAa,EACrB,KAAK,EAAE,kBAAkB,CAAC,OAAO,EAAE,OAAO,CAAC,EAC3C,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GACd,eAAe,CAyBjB"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Decides, when the schema is built, what the directives on one field add up
3
+ * to — and refuses with a `TypeError` what no request could ever pass: a user
4
+ * type `auth` does not know, a `type: []`, restrictions that exclude each
5
+ * other, or a `@permission` the model, the field or the wiring cannot answer
6
+ * (`permission/validate.ts`).
7
+ */
8
+ import { type FieldShape, type PermissionCheck, type PermissionWiring } from './permission/validate';
9
+ import type { FieldDirectives } from './read';
10
+ /** What a field requires before its resolver runs. */
11
+ export interface Requirement {
12
+ /** `Type.field`, for the messages. */
13
+ readonly where: string;
14
+ /** The directive a missing `ctx.janus` is reported against. */
15
+ readonly label: string;
16
+ /**
17
+ * The user types allowed, every `type:` intersected; `null` when none
18
+ * narrows it: any signed-in user.
19
+ */
20
+ readonly types: ReadonlySet<string> | null;
21
+ /** The permissions asked after that, in order: every one must hold. */
22
+ readonly permissions: readonly PermissionCheck[];
23
+ }
24
+ /** The user types a schema may name: `auth.types`, or the one `useJanus({ type })` keeps. */
25
+ export interface Known {
26
+ readonly types: readonly string[];
27
+ }
28
+ /**
29
+ * The requirement of one field, or `null` when nothing guards it. Throws a
30
+ * `TypeError` naming the field for a directive no request could pass.
31
+ */
32
+ export declare function requirementOf(field: FieldShape, directives: FieldDirectives, known: Known, wiring: PermissionWiring): Requirement | null;
33
+ //# sourceMappingURL=validate.d.ts.map