@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
@@ -0,0 +1,24 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/directives/apply.ts", "../src/memo.ts", "../src/context.ts", "../src/errors.ts", "../src/directives/permission/path.ts", "../src/directives/permission/enforce.ts", "../src/directives/guard.ts", "../src/directives/read.ts", "../src/directives/words.ts", "../src/directives/permission/model.ts", "../src/directives/permission/validate.ts", "../src/directives/validate.ts", "../src/helpers.ts", "../src/plugin.ts", "../src/sdl.ts"],
4
+ "sourcesContent": [
5
+ "/**\n * The schema transform: every object field a directive guards gets its\n * resolver wrapped, once, when the schema is built.\n */\n\nimport { MapperKind, mapSchema } from '@graphql-tools/utils';\nimport { defaultFieldResolver, type GraphQLSchema } from 'graphql';\nimport { guarded } from './guard';\nimport { readField } from './read';\nimport { type Known, requirementOf } from './validate';\nimport { list, PREFIX } from './words';\n\ntype Entries = {\n\treadonly [type: string]: ((...args: never[]) => unknown) | undefined;\n};\n\n/** What `applyJanusDirectives()` takes. */\nexport interface JanusDirectivesOptions<T extends string = string> {\n\t/** What `janus()` answered: its `types` are the names `type:` may take. */\n\treadonly auth: { readonly types: readonly T[] };\n\t/** `useJanus({ type })`'s: the one user type the schema's users can have. */\n\treadonly type?: NoInfer<T>;\n\t/** What `permissions()` answered: its model is what `@permission` may name. Required once a schema uses it. */\n\treadonly access?: { readonly model: unknown } | undefined;\n\t/** `useJanus({ loaders })`'s. */\n\treadonly loaders?: Entries | undefined;\n\t/** `useJanus({ conditions })`'s. */\n\treadonly conditions?: Entries | undefined;\n}\n\n/**\n * The schema with every field guarded by `@authenticated` or `@permission`\n * — on the field, on its type, or on an interface the type implements —\n * wrapped so its resolver runs only for a signed-in user, of one of the\n * types `type:` names when it names some, holding every permission asked.\n * Every directive that applies must hold.\n *\n * `useJanus()` calls it on every schema it is given. Call it yourself to\n * check a schema without a server: the guards it installs read `ctx.janus`,\n * which only `useJanus()` builds.\n *\n * **Refuses when the schema is built**, with a `TypeError` naming the\n * field: a user type `auth` does not know, a `type: []`, restrictions no\n * user type meets together; for `@permission`, an object type or a\n * permission the model does not declare, a malformed `id:`, an argument the\n * field does not take, and a loader or a condition's `ctx` it needs and was\n * not given.\n */\nexport function applyJanusDirectives<const T extends string>(\n\tschema: GraphQLSchema,\n\toptions: JanusDirectivesOptions<T>,\n): GraphQLSchema {\n\tconst known = knownOf(options);\n\treturn mapSchema(schema, {\n\t\t[MapperKind.OBJECT_FIELD]: (field, fieldName, typeName) => {\n\t\t\tconst requirement = requirementOf(\n\t\t\t\t{\n\t\t\t\t\tname: `${typeName}.${fieldName}`,\n\t\t\t\t\targs: Object.keys(field.args ?? {}),\n\t\t\t\t},\n\t\t\t\treadField(schema, field, fieldName, typeName),\n\t\t\t\tknown,\n\t\t\t\toptions,\n\t\t\t);\n\t\t\tif (requirement === null) return field;\n\t\t\treturn {\n\t\t\t\t...field,\n\t\t\t\tresolve: guarded(field.resolve ?? defaultFieldResolver, requirement),\n\t\t\t\t...(field.subscribe === undefined\n\t\t\t\t\t? {}\n\t\t\t\t\t: { subscribe: guarded(field.subscribe, requirement) }),\n\t\t\t};\n\t\t},\n\t});\n}\n\n/** The user types a directive may name — `type` alone when it is given, and one of `auth.types`. */\nfunction knownOf({ auth, type }: JanusDirectivesOptions): Known {\n\tif (type === undefined) return { types: auth.types };\n\tif (!auth.types.includes(type)) {\n\t\tthrow new TypeError(\n\t\t\t`${PREFIX}: type '${type}' is not a user type of auth — it knows ${list(auth.types)}`,\n\t\t);\n\t}\n\treturn { types: [type] };\n}\n",
6
+ "/**\n * The per-request memo of permission checks: the same question asked twice\n * in one request — by two fields, by a directive and a resolver's `can()`,\n * or by two fields at once — is one call to `access.can`.\n */\n\nimport type { Check } from './context';\n\ntype Ref = {\n\treadonly type: string;\n\treadonly id: string;\n\treadonly relation?: unknown;\n};\n\n/**\n * What no id in a key may hold: the notation reads `@` and `#` as its own,\n * so a key built from such an id could be another question's. `can()`\n * refuses or denies such an id anyway; it is asked, not remembered.\n */\nconst NOTATION = /[@#]/;\n\n/**\n * The key of one question, in the notation — `record:r1#view@staff:u1` —\n * or `null` when it is not remembered: an anonymous subject, which `can()`\n * answers `false` with no store call; a condition's `ctx`, which the key\n * does not hold; an id the notation could read two ways.\n */\nfunction keyOf(\n\tsubject: Ref | null,\n\tpermission: string,\n\tobject: Ref,\n\toptions: { readonly ctx?: unknown } | undefined,\n): string | null {\n\tif (subject === null || options?.ctx !== undefined) return null;\n\tif (NOTATION.test(object.id) || NOTATION.test(subject.id)) return null;\n\tconst set =\n\t\ttypeof subject.relation === 'string' ? `#${subject.relation}` : '';\n\treturn `${object.type}:${object.id}#${permission}@${subject.type}:${subject.id}${set}`;\n}\n\n/**\n * `access.can` for one request, remembered: a question asked again answers\n * the first answer's promise, so two asked at once share one call. A\n * failure is not remembered — an outage is no answer, and the next question\n * asks again.\n */\nexport function memoized(access: { readonly can: unknown }): Check {\n\tconst can = access.can as Check;\n\tconst answers = new Map<string, Promise<boolean>>();\n\tconst ask: Check = async (subject, permission, object, options) =>\n\t\tcan.call(access, subject, permission, object, options);\n\treturn (subject, permission, object, options) => {\n\t\tconst key = keyOf(subject, permission, object, options);\n\t\tif (key === null) return ask(subject, permission, object, options);\n\t\tconst known = answers.get(key);\n\t\tif (known !== undefined) return known;\n\t\tconst answer = ask(subject, permission, object, options);\n\t\tanswers.set(key, answer);\n\t\tanswer.then(undefined, () => answers.delete(key));\n\t\treturn answer;\n\t};\n}\n",
7
+ "/**\n * `ctx.janus`, built once per request by `useJanus()`: a lazy\n * authentication, and the per-request check `can()` and the directives ask\n * through.\n */\n\nimport type { Authenticated, RequestLike } from '@nxgt/janus';\nimport { memoized } from './memo';\nimport type { JanusOnContext } from './types';\n\n/** One check, as `access.can` answers it — loose: the typing is the caller's. */\nexport type Check = (\n\tsubject: { readonly type: string; readonly id: string } | null,\n\tpermission: string,\n\tobject: { readonly type: string; readonly id: string },\n\toptions?: { readonly ctx?: unknown },\n) => Promise<boolean>;\n\n/** A user as this module reads one: the part every user type has. */\ntype AnyUser = { readonly type: string; readonly id: string };\n\ntype LooseAuth = {\n\tauthenticate(\n\t\trequest: RequestLike,\n\t\toptions?: { readonly type?: string },\n\t): Promise<Authenticated<AnyUser> | null>;\n};\n\ntype Built = JanusOnContext<AnyUser, unknown>;\n\n/**\n * The check of each context this module built. A `WeakMap` rather than a\n * property, so the context's own surface stays `user`, `session` and\n * `access`; it is the one place a request's checks pass through, and so the\n * one place a per-request memo of their answers goes.\n */\nconst checks = new WeakMap<object, { memo: Check; fresh: Check }>();\n\n/** What `createJanusContext` reads from `useJanus()`'s options. */\nexport interface ContextOptions {\n\treadonly auth: LooseAuth;\n\treadonly access?: { readonly can: unknown } | undefined;\n\treadonly type?: string | undefined;\n}\n\n/**\n * `ctx.janus` for one request. `authenticate` runs the first time `user()`\n * or `session()` is asked, once, and its answer — or its failure — is the\n * answer of both from then on.\n *\n * A context with no request — a transport that is not HTTP — answers a\n * `TypeError` from `user()`, not `null`: anonymous is an answer, and nothing\n * here asked.\n */\nexport function createJanusContext(\n\trequest: RequestLike | undefined,\n\toptions: ContextOptions,\n): Built {\n\tconst { auth, access, type } = options;\n\tlet answer: Promise<Authenticated<AnyUser> | null>;\n\tlet asked = false;\n\tconst authenticated = () => {\n\t\tif (!asked) {\n\t\t\tasked = true;\n\t\t\tanswer =\n\t\t\t\trequest === undefined\n\t\t\t\t\t? Promise.reject(\n\t\t\t\t\t\t\tnew TypeError(\n\t\t\t\t\t\t\t\t'useJanus(): the GraphQL context has no request to authenticate — build the context from an HTTP request',\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t)\n\t\t\t\t\t: auth.authenticate(\n\t\t\t\t\t\t\trequest,\n\t\t\t\t\t\t\ttype === undefined ? undefined : { type },\n\t\t\t\t\t\t);\n\t\t}\n\t\treturn answer;\n\t};\n\n\tconst janus = Object.freeze({\n\t\tuser: () => authenticated().then((current) => current?.user ?? null),\n\t\tsession: () => authenticated().then((current) => current?.session ?? null),\n\t\t...(access === undefined ? {} : { access }),\n\t});\n\tif (access !== undefined) {\n\t\tconst can = access.can as Check;\n\t\tchecks.set(janus, {\n\t\t\tmemo: memoized(access),\n\t\t\tfresh: (...args) => can.apply(access, args),\n\t\t});\n\t}\n\treturn janus as Built;\n}\n\n/**\n * The check `ctx.janus` answers through: the one its context was built\n * with — `access.can`, memoized for the request (`memo.ts`) — or\n * `access.can` itself for a context built by hand. `null` when it has no\n * `access` at all.\n *\n * `fresh` skips the memo: a subscription is one request whose checks run\n * again on every event, and must see a `revoke()` made since it started.\n */\nexport function checkOf(janus: object, fresh = false): Check | null {\n\tconst built = checks.get(janus);\n\tif (built !== undefined) return fresh ? built.fresh : built.memo;\n\tconst access = (janus as { readonly access?: { readonly can?: unknown } })\n\t\t.access;\n\treturn typeof access?.can === 'function' ? (access.can as Check) : null;\n}\n\n/**\n * `ctx.janus`, or a wiring `TypeError` naming `caller` when nothing put it\n * there: `useJanus()` is missing from the plugins.\n */\nexport function janusOf(ctx: unknown, caller: string): Built {\n\tconst janus = (ctx as { readonly janus?: Built } | null)?.janus;\n\tif (janus === undefined || typeof janus.user !== 'function') {\n\t\tthrow new TypeError(\n\t\t\t`${caller}: ctx.janus is not set — add useJanus({ auth }) to the plugins`,\n\t\t);\n\t}\n\treturn janus;\n}\n",
8
+ "/**\n * Every refusal as a `GraphQLError` whose `extensions` carry a `code` and the\n * HTTP status Yoga answers with: `{ code, http: { status } }`. This package\n * defines no error class of its own — a denial is a `GraphQLError`, and what\n * `@nxgt/janus` throws stays its own until `janusMaskError()` reads it.\n */\n\nimport { JanusError, type JanusErrorStatus, statusOf } from '@nxgt/janus';\nimport { GraphQLError } from 'graphql';\n\n/** What a guard answers without asking `@nxgt/janus`: no user, or not this one. */\nexport type DenialCode = 'UNAUTHENTICATED' | 'FORBIDDEN' | 'NOT_FOUND';\n\n/** A denial: `UNAUTHENTICATED` 401, `FORBIDDEN` 403 or `NOT_FOUND` 404. */\nexport function denial(code: DenialCode, message?: string): GraphQLError {\n\tconst [status, fallback] = denialOf(code);\n\treturn new GraphQLError(message ?? fallback, {\n\t\textensions: { code, http: { status } },\n\t});\n}\n\nfunction denialOf(code: DenialCode): readonly [number, string] {\n\tswitch (code) {\n\t\tcase 'UNAUTHENTICATED':\n\t\t\treturn [401, 'Not signed in'];\n\t\tcase 'FORBIDDEN':\n\t\t\treturn [403, 'Forbidden'];\n\t\tcase 'NOT_FOUND':\n\t\t\treturn [404, 'Not found'];\n\t}\n}\n\n/**\n * A `JanusError` as the client may read it: its code and status, and only\n * what the client can act on — `issues`, `minLength`, `attemptsLeft`. Never\n * `reason`, `login`, `slot` or a cause: those are for your logs.\n *\n * `STORE_FAILED` is `SERVICE_UNAVAILABLE` 503 — **an outage is never a\n * denial** — and no message of the core's reaches the client.\n */\nexport function janusGraphQLError(error: JanusError): GraphQLError {\n\tconst status = statusOf(error.code);\n\tconst code =\n\t\terror.code === 'STORE_FAILED' ? 'SERVICE_UNAVAILABLE' : error.code;\n\treturn new GraphQLError(messageOf(status), {\n\t\toriginalError: error,\n\t\textensions: { code, http: { status }, ...actionable(error) },\n\t});\n}\n\n/**\n * A fixed message per status: the core's own message names a hash prefix or\n * a version, which are for your logs. The client reads `extensions.code`.\n */\nfunction messageOf(status: JanusErrorStatus): string {\n\tswitch (status) {\n\t\tcase 400:\n\t\t\treturn 'Invalid request';\n\t\tcase 401:\n\t\t\treturn 'Invalid credentials';\n\t\tcase 403:\n\t\t\treturn 'Forbidden';\n\t\tcase 404:\n\t\t\treturn 'Not found';\n\t\tcase 409:\n\t\t\treturn 'Conflict';\n\t\tcase 503:\n\t\t\treturn 'The service is unavailable, retry later';\n\t\tcase 500:\n\t\tcase 501:\n\t\t\treturn 'Internal server error';\n\t}\n}\n\n/** The fields of a refusal a client can act on, as `@nxgt/janus-hono`'s `bodyOf`. */\nfunction actionable(error: JanusError): Record<string, unknown> {\n\tswitch (error.code) {\n\t\tcase 'USER_INVALID':\n\t\t\treturn { issues: error.issues ?? [] };\n\t\tcase 'PASSWORD_TOO_SHORT':\n\t\t\treturn error.minLength === undefined\n\t\t\t\t? {}\n\t\t\t\t: { minLength: error.minLength };\n\t\tcase 'CODE_INVALID':\n\t\t\treturn error.attemptsLeft === undefined\n\t\t\t\t? {}\n\t\t\t\t: { attemptsLeft: error.attemptsLeft };\n\t\tdefault:\n\t\t\treturn {};\n\t}\n}\n\n/**\n * A rejection handler: a `JanusError` becomes its `GraphQLError`, anything\n * else goes on as it is. `.then(undefined, rethrown)`.\n */\nexport function rethrown(error: unknown): never {\n\tthrow error instanceof JanusError ? janusGraphQLError(error) : error;\n}\n\n/** Yoga's and envelop's `maskError` signature. */\nexport type MaskError = (\n\terror: unknown,\n\tmessage: string,\n\tisDev?: boolean,\n) => Error;\n\n/**\n * Yoga's `maskedErrors.maskError`: a `JanusError` a resolver let through is\n * answered with its code and status — `STORE_FAILED` as 503, never masked\n * into a 500 and never a denial — and every other error goes to `fallback`.\n *\n * Without a `fallback`, a `GraphQLError` is kept and anything else becomes\n * `message`, with no detail. Pass Yoga's own `maskError` to keep its\n * development-mode details:\n *\n * ```ts\n * import { createYoga, maskError } from 'graphql-yoga';\n * createYoga({ schema, plugins, maskedErrors: { maskError: janusMaskError(maskError) } });\n * ```\n */\nexport function janusMaskError(fallback: MaskError = masked): MaskError {\n\treturn (error, message, isDev) => {\n\t\tconst original =\n\t\t\terror instanceof GraphQLError ? error.originalError : error;\n\t\tif (original instanceof JanusError) {\n\t\t\tconst answer = janusGraphQLError(original);\n\t\t\treturn error instanceof GraphQLError\n\t\t\t\t? new GraphQLError(answer.message, {\n\t\t\t\t\t\tnodes: error.nodes ?? null,\n\t\t\t\t\t\tpath: error.path ?? null,\n\t\t\t\t\t\toriginalError: original,\n\t\t\t\t\t\textensions: answer.extensions,\n\t\t\t\t\t})\n\t\t\t\t: answer;\n\t\t}\n\t\treturn fallback(error, message, isDev);\n\t};\n}\n\n/** The default `fallback`: a `GraphQLError` of our own kept, anything else `message`. */\nfunction masked(error: unknown, message: string): Error {\n\tif (error instanceof GraphQLError) {\n\t\tconst original = error.originalError;\n\t\tif (original === undefined || original instanceof GraphQLError) {\n\t\t\treturn error;\n\t\t}\n\t\treturn new GraphQLError(message, {\n\t\t\tnodes: error.nodes ?? null,\n\t\t\tpath: error.path ?? null,\n\t\t\textensions: { code: 'INTERNAL_SERVER_ERROR' },\n\t\t});\n\t}\n\treturn new GraphQLError(message, {\n\t\textensions: { code: 'INTERNAL_SERVER_ERROR' },\n\t});\n}\n",
9
+ "/**\n * `@permission`'s `id:` — `args.<path>` or `parent.<path>` — parsed when the\n * schema is built, and read from a request's arguments or parent value when a\n * field is resolved.\n */\n\n/** A parsed `id:`. */\nexport interface IdPath {\n\t/** As written: `args.id`, `parent.wardId`, `args.input.ids`. */\n\treadonly text: string;\n\treadonly root: 'args' | 'parent';\n\t/** The names after the root, at least one. */\n\treadonly segments: readonly string[];\n}\n\nconst NAME = /^[_A-Za-z][_0-9A-Za-z]*$/;\n\n/** `text` parsed, or `null` when it is not `args.<name>…` or `parent.<name>…`. */\nexport function parsePath(text: string): IdPath | null {\n\tconst [root, ...segments] = text.split('.');\n\tif (root !== 'args' && root !== 'parent') return null;\n\tif (segments.length === 0 || !segments.every((name) => NAME.test(name))) {\n\t\treturn null;\n\t}\n\treturn { text, root, segments };\n}\n\n/** One object a check is about: its id, and the value that holds it when that value is the object. */\nexport interface Target {\n\treadonly id: string;\n\t/** The value the id was read from, when `id` is its `id` field under `parent`. */\n\treadonly holder: object | null;\n}\n\n/**\n * The objects `path` names in this request: one, or one per element where a\n * step reads a list. `null` when a step reads nothing, or an id is not a\n * string or an integer: the request resolved no object id. An empty list\n * under `parent` is `null` too — the object's data names nothing to check,\n * and its fields are not open to everyone for it; under `args` it is the\n * client asking for nothing, and there is nothing to refuse.\n */\nexport function targetsOf(\n\tpath: IdPath,\n\tparent: unknown,\n\targs: unknown,\n): readonly Target[] | null {\n\tconst containers = walk(\n\t\t[path.root === 'args' ? args : parent],\n\t\tpath.segments.slice(0, -1),\n\t);\n\tif (containers === null) return null;\n\tconst last = path.segments[path.segments.length - 1] as string;\n\tconst held = path.root === 'parent' && last === 'id';\n\tconst targets: Target[] = [];\n\tfor (const container of containers) {\n\t\tfor (const value of spread((container as Record<string, unknown>)[last])) {\n\t\t\tconst id = idOf(value);\n\t\t\tif (id === null) return null;\n\t\t\ttargets.push({ id, holder: held ? (container as object) : null });\n\t\t}\n\t}\n\treturn path.root === 'parent' && targets.length === 0 ? null : targets;\n}\n\n/** The values `segments` reach from `values`, a list read as its elements. */\nfunction walk(\n\tvalues: readonly unknown[],\n\tsegments: readonly string[],\n): readonly unknown[] | null {\n\tlet current = values;\n\tfor (const segment of segments) {\n\t\tconst next: unknown[] = [];\n\t\tfor (const value of current) {\n\t\t\tif (typeof value !== 'object' || value === null) return null;\n\t\t\tnext.push(...spread((value as Record<string, unknown>)[segment]));\n\t\t}\n\t\tcurrent = next;\n\t}\n\treturn current.every((value) => typeof value === 'object' && value !== null)\n\t\t? current\n\t\t: null;\n}\n\nfunction spread(value: unknown): readonly unknown[] {\n\treturn Array.isArray(value) ? value.flat(Number.POSITIVE_INFINITY) : [value];\n}\n\n/** An `ID` as graphql-js coerces one: a string, or an integer serialised. */\nfunction idOf(value: unknown): string | null {\n\tif (typeof value === 'string') return value;\n\tif (typeof value === 'number' && Number.isSafeInteger(value)) {\n\t\treturn String(value);\n\t}\n\treturn null;\n}\n",
10
+ "/**\n * One `@permission`, asked at request time: the objects its `id:` names, each\n * checked with the request's check — memoized per request — and a denial\n * unless every one holds. A failure to answer is never a denial.\n */\n\nimport type { Check } from '../../context';\nimport { denial, rethrown } from '../../errors';\nimport { type Target, targetsOf } from './path';\nimport type { PermissionCheck } from './validate';\n\ntype ObjectLike = { readonly type: string; readonly id: string };\ntype Subject = { readonly type: string; readonly id: string };\n\n/** What the guard has in hand when it asks. */\nexport interface Asking {\n\treadonly check: Check;\n\treadonly user: Subject;\n\treadonly parent: unknown;\n\treadonly args: unknown;\n\t/** The GraphQL context, which `loaders` and `conditions` receive. */\n\treadonly ctx: unknown;\n}\n\n/** Refuses the request unless `user` holds `permission` on every object its `id:` names. */\nexport async function enforcePermission(\n\tpermission: PermissionCheck,\n\tasking: Asking,\n): Promise<void> {\n\tconst targets = targetsOf(permission.path, asking.parent, asking.args);\n\tif (targets === null) {\n\t\twarnNoId(permission);\n\t\tthrow denial('NOT_FOUND');\n\t}\n\tconst objects = await Promise.all(\n\t\ttargets.map((target) => objectOf(permission, target, asking.ctx)),\n\t).then(undefined, rethrown);\n\tconst found = objects.filter(\n\t\t(object): object is ObjectLike => object !== null,\n\t);\n\tif (found.length < objects.length) throw denial('NOT_FOUND');\n\tconst answers = await Promise.all(\n\t\tfound.map((object) => ask(permission, object, asking)),\n\t).then(undefined, rethrown);\n\tif (answers.includes(false)) throw denial(permission.onDeny);\n}\n\nasync function ask(\n\tpermission: PermissionCheck,\n\tobject: ObjectLike,\n\t{ check, user, ctx }: Asking,\n): Promise<boolean> {\n\tconst options =\n\t\tpermission.condition === null\n\t\t\t? undefined\n\t\t\t: { ctx: await permission.condition(object, ctx) };\n\treturn check(user, permission.name, object, options);\n}\n\n/**\n * What no object id can hold, as `@nxgt/janus` reads the notation: `can()`\n * refuses such an id with a `TypeError`, and one sent by a client is no\n * object's. A copy of the core's `RESERVED`, recorded in AGENTS.md's\n * *Deliberate duplications*.\n */\nconst UNNAMEABLE = /^$|[@#()]/;\n\n/**\n * Whether an id sent by a client can name an object: none of the characters\n * above, no NUL and no lone surrogate — which no store keeps, so `can()`\n * holds them for nobody, and the application's loader must never see them.\n * A copy of the core's `isStorable`, recorded beside `UNNAMEABLE`.\n */\nconst isNameable = (id: string): boolean =>\n\t!UNNAMEABLE.test(id) && !id.includes('\\u0000') && id.isWellFormed();\n\n/**\n * The object `can()` is given for one target: the value that holds the id,\n * the loader's answer, or `{ type, id }`. `null` when there is none: the\n * loader found none, or the id could name no object.\n */\nasync function objectOf(\n\tpermission: PermissionCheck,\n\t{ id, holder }: Target,\n\tctx: unknown,\n): Promise<ObjectLike | null> {\n\tconst { type, load } = permission;\n\tif (!isNameable(id)) return null;\n\tif (holder !== null) return view(holder, type, id);\n\tif (load === null) return { type, id };\n\tconst loaded = await load(id, ctx);\n\tif (loaded === null) return null;\n\tif (typeof loaded !== 'object') {\n\t\tthrow new TypeError(\n\t\t\t`${permission.label}: loaders.${type} answered ${typeof loaded} — answer the object, or null when there is none`,\n\t\t);\n\t}\n\treturn view(loaded, type, id);\n}\n\n/**\n * The object as `can()` reads it: `type` added — and `id`, the string read\n * from the path, so an integer id reads as graphql-js serialises it — every\n * other field read from the object itself, so a getter, a\n * class's `#private` state or an ORM document's accessors answer as they do\n * in the resolver. A spread would copy own enumerable fields only. The\n * proxy's target is an empty object rather than `object`, so a frozen\n * object whose own `type` means something else is read without breaking a\n * proxy invariant.\n */\nfunction view(object: object, type: string, id: string): ObjectLike {\n\treturn new Proxy(Object.create(null) as object, {\n\t\tget: (_, key) =>\n\t\t\tkey === 'type' ? type : key === 'id' ? id : Reflect.get(object, key),\n\t\thas: (_, key) => key === 'type' || Reflect.has(object, key),\n\t}) as ObjectLike;\n}\n\nconst warned = new WeakSet<PermissionCheck>();\n\n/** Says once per directive, on the only channel a library has, why a field answers `NOT_FOUND`. */\nfunction warnNoId(permission: PermissionCheck): void {\n\tif (warned.has(permission)) return;\n\twarned.add(permission);\n\tprocess.emitWarning(\n\t\t`${permission.label} resolved no object id from ${permission.path.text} — answered NOT_FOUND`,\n\t\t{ code: 'JANUS_GRAPHQL_NO_OBJECT_ID' },\n\t);\n}\n",
11
+ "/**\n * What runs before a guarded field's resolver, at request time: the user,\n * then the requirement — its user types, then each `@permission` in order. A\n * denial is a `GraphQLError`; a failure to answer is `SERVICE_UNAVAILABLE`,\n * never a denial.\n */\n\nimport type { GraphQLFieldResolver } from 'graphql';\nimport { checkOf, janusOf } from '../context';\nimport { denial, rethrown } from '../errors';\nimport { enforcePermission } from './permission/enforce';\nimport type { Requirement } from './validate';\n\n/** What the field was asked with. */\nexport interface Resolving {\n\treadonly parent: unknown;\n\treadonly args: unknown;\n\treadonly ctx: unknown;\n\t/** A subscription's: its checks skip the request's memo, and see a revoke. */\n\treadonly fresh: boolean;\n}\n\n/** Refuses the request unless it meets `requirement`. */\nexport async function enforce(\n\trequirement: Requirement,\n\t{ parent, args, ctx, fresh }: Resolving,\n): Promise<void> {\n\tconst janus = janusOf(ctx, requirement.label);\n\tconst user = await janus.user().then(undefined, rethrown);\n\tif (user === null) throw denial('UNAUTHENTICATED');\n\tif (requirement.types !== null && !requirement.types.has(user.type)) {\n\t\tthrow denial('FORBIDDEN');\n\t}\n\tconst [first] = requirement.permissions;\n\tif (first === undefined) return;\n\tconst check = checkOf(janus, fresh);\n\tif (check === null) {\n\t\tthrow new TypeError(\n\t\t\t`${first.label}: ctx.janus.access is not set — pass { access } to useJanus()`,\n\t\t);\n\t}\n\t// In order, one at a time: the first that denies answers, and the ones\n\t// after it are never asked.\n\tfor (const permission of requirement.permissions) {\n\t\tawait enforcePermission(permission, { check, user, parent, args, ctx });\n\t}\n}\n\n/** `resolve`, run only once `requirement` is met. */\nexport function guarded<S, C, A>(\n\tresolve: GraphQLFieldResolver<S, C, A>,\n\trequirement: Requirement,\n): GraphQLFieldResolver<S, C, A> {\n\treturn async (source, args, ctx, info) => {\n\t\tawait enforce(requirement, {\n\t\t\tparent: source,\n\t\t\targs,\n\t\t\tctx,\n\t\t\tfresh: info.operation.operation === 'subscription',\n\t\t});\n\t\treturn resolve(source, args, ctx, info);\n\t};\n}\n",
12
+ "/**\n * Reads the directives that apply to one object field: its own, its object\n * type's, and those of every interface the type implements — on the\n * interface itself and on the interface's field of the same name. Reads, and\n * decides nothing: `validate.ts` decides what they add up to.\n */\n\nimport { getDirective } from '@graphql-tools/utils';\nimport type {\n\tGraphQLFieldConfig,\n\tGraphQLInterfaceType,\n\tGraphQLObjectType,\n\tGraphQLSchema,\n} from 'graphql';\n\n/** One `@authenticated`, and where it was written. */\nexport interface AuthenticatedUse {\n\t/** `Type.field`, `Type`, `Interface.field` or `Interface`. */\n\treadonly where: string;\n\t/** Its `type:` argument, or `null` when it names none: any signed-in user. */\n\treadonly types: readonly string[] | null;\n}\n\n/** One `@permission`, as written, and where. */\nexport interface PermissionUse {\n\t/** `Type.field`, `Type`, `Interface.field` or `Interface`. */\n\treadonly where: string;\n\t/** Written on a type or an interface, rather than on a field. */\n\treadonly onType: boolean;\n\treadonly name: string;\n\treadonly type: string;\n\t/** Its `id:` argument, or `null` when absent. */\n\treadonly id: string | null;\n\treadonly onDeny: 'NOT_FOUND' | 'FORBIDDEN';\n}\n\n/** Everything written on one field's locations. */\nexport interface FieldDirectives {\n\treadonly authenticated: readonly AuthenticatedUse[];\n\t/** In the order they are checked: the type's and interfaces' first, then the field's. */\n\treadonly permission: readonly PermissionUse[];\n}\n\ntype Located = {\n\treadonly where: string;\n\treadonly onType: boolean;\n\treadonly node: Parameters<typeof getDirective>[1];\n};\n\n/**\n * The locations whose directives apply to `typeName.fieldName`, outermost\n * first: the interfaces, the type, the interfaces' fields, the field. A\n * `@permission` on the object a field belongs to is asked before the one on\n * the field, so a field's `FORBIDDEN` never answers for an object its type\n * would have answered `NOT_FOUND` for.\n */\nfunction locations(\n\tschema: GraphQLSchema,\n\tfield: GraphQLFieldConfig<unknown, unknown>,\n\tfieldName: string,\n\ttypeName: string,\n): Located[] {\n\tconst type = schema.getType(typeName) as GraphQLObjectType;\n\tconst faces = type.getInterfaces() as readonly GraphQLInterfaceType[];\n\tconst fields: Located[] = [];\n\tfor (const face of faces) {\n\t\tconst own = face.getFields()[fieldName];\n\t\tif (own !== undefined) {\n\t\t\tfields.push({\n\t\t\t\twhere: `${face.name}.${fieldName}`,\n\t\t\t\tonType: false,\n\t\t\t\tnode: own,\n\t\t\t});\n\t\t}\n\t}\n\treturn [\n\t\t...faces.map((face) => ({ where: face.name, onType: true, node: face })),\n\t\t{ where: typeName, onType: true, node: type },\n\t\t...fields,\n\t\t{ where: `${typeName}.${fieldName}`, onType: false, node: field },\n\t];\n}\n\n/** Every `@authenticated` and `@permission` that applies to one object field. */\nexport function readField(\n\tschema: GraphQLSchema,\n\tfield: GraphQLFieldConfig<unknown, unknown>,\n\tfieldName: string,\n\ttypeName: string,\n): FieldDirectives {\n\tconst authenticated: AuthenticatedUse[] = [];\n\tconst permission: PermissionUse[] = [];\n\tfor (const { where, onType, node } of locations(\n\t\tschema,\n\t\tfield,\n\t\tfieldName,\n\t\ttypeName,\n\t)) {\n\t\tfor (const args of getDirective(schema, node, 'authenticated') ?? []) {\n\t\t\tconst types = args.type as readonly string[] | null | undefined;\n\t\t\tauthenticated.push({ where, types: types ?? null });\n\t\t}\n\t\tfor (const args of getDirective(schema, node, 'permission') ?? []) {\n\t\t\tpermission.push({\n\t\t\t\twhere,\n\t\t\t\tonType,\n\t\t\t\tname: args.name as string,\n\t\t\t\ttype: args.type as string,\n\t\t\t\tid: (args.id as string | null | undefined) ?? null,\n\t\t\t\tonDeny: args.onDeny === 'FORBIDDEN' ? 'FORBIDDEN' : 'NOT_FOUND',\n\t\t\t});\n\t\t}\n\t}\n\treturn { authenticated, permission };\n}\n",
13
+ "/** The phrases the schema-time refusals share. */\n\nexport const PREFIX = 'applyJanusDirectives()';\n\n/** `Query.me`, or `Record (read by Record.title)` for a type's directive. */\nexport function on(where: string, field: string): string {\n\treturn where === field ? field : `${where} (read by ${field})`;\n}\n\n/** `'a', 'b'`. */\nexport function list(names: readonly string[]): string {\n\treturn names.map((name) => `'${name}'`).join(', ');\n}\n",
14
+ "/**\n * What `@permission` reads of the permission model when the schema is built:\n * the object types, the names each declares, whether one reads a field of\n * its object (`fromField`), and whether a permission reaches a condition\n * (`when`). Read from `access.model`, as `defineModel()` answered it.\n */\n\n/** `access.model`, as this module reads it. */\nexport interface ModelLike {\n\treadonly types: readonly string[];\n\treadonly definition: {\n\t\treadonly types: {\n\t\t\treadonly [type: string]: {\n\t\t\t\treadonly related?: { readonly [name: string]: unknown };\n\t\t\t\treadonly permits?: { readonly [name: string]: readonly unknown[] };\n\t\t\t};\n\t\t};\n\t};\n}\n\n/** `access.model`, or `null` when `access` is not what `permissions()` answered. */\nexport function modelOf(access: unknown): ModelLike | null {\n\tconst model = (access as { readonly model?: Partial<ModelLike> } | undefined)\n\t\t?.model;\n\tif (!Array.isArray(model?.types) || model.definition?.types === undefined) {\n\t\treturn null;\n\t}\n\treturn model as ModelLike;\n}\n\n/** The relations and permissions `type` declares: what `can()` may be asked. */\nexport function namesOf(model: ModelLike, type: string): readonly string[] {\n\tconst def = model.definition.types[type];\n\treturn [\n\t\t...Object.keys(def?.related ?? {}),\n\t\t...Object.keys(def?.permits ?? {}),\n\t];\n}\n\ntype FromFieldLike = {\n\treadonly kind: 'fromField';\n\treadonly field: string;\n\treadonly subject: string;\n};\n\nfunction isFromField(value: unknown): value is FromFieldLike {\n\treturn (value as { readonly kind?: unknown } | null)?.kind === 'fromField';\n}\n\n/** The fields `type`'s `fromField`s read; empty when it has none. */\nexport function fieldsOf(model: ModelLike, type: string): readonly string[] {\n\tconst related = model.definition.types[type]?.related ?? {};\n\treturn Object.values(related)\n\t\t.filter(isFromField)\n\t\t.map((def) => def.field);\n}\n\n/**\n * Whether `can(…, name, { type })` reaches a `when` — through the\n * permission's rules, the permissions they name, and arrows into other types\n * — and so needs a `ctx`. What `CtxOf` computes in the compiler.\n */\nexport function reachesCondition(\n\tmodel: ModelLike,\n\ttype: string,\n\tname: string,\n\tseen: Set<string> = new Set(),\n): boolean {\n\tconst key = `${type}.${name}`;\n\tif (seen.has(key)) return false;\n\tseen.add(key);\n\tconst def = model.definition.types[type];\n\tfor (const rule of def?.permits?.[name] ?? []) {\n\t\tif (typeof rule !== 'string') return true; // a when()\n\t\tconst [relation, target] = rule.split('->');\n\t\tif (target === undefined) {\n\t\t\tif (reachesCondition(model, type, rule, seen)) return true;\n\t\t\tcontinue;\n\t\t}\n\t\tfor (const next of arrowTargets(def?.related?.[relation as string])) {\n\t\t\tif (reachesCondition(model, next, target, seen)) return true;\n\t\t}\n\t}\n\treturn false;\n}\n\n/** The object types an arrow through a relation of this definition reaches. */\nfunction arrowTargets(relation: unknown): readonly string[] {\n\tif (isFromField(relation)) {\n\t\treturn [relation.subject];\n\t}\n\tif (!Array.isArray(relation)) return [];\n\treturn relation\n\t\t.filter((holder): holder is string => typeof holder === 'string')\n\t\t.map((holder) => holder.split('#')[0] as string);\n}\n",
15
+ "/**\n * One `@permission`, checked against the model and the field when the schema\n * is built, and turned into what the guard asks at request time. Everything\n * no request could pass is a `TypeError` naming the field.\n */\n\nimport type { PermissionUse } from '../read';\nimport { list, on, PREFIX } from '../words';\nimport {\n\tfieldsOf,\n\ttype ModelLike,\n\tmodelOf,\n\tnamesOf,\n\treachesCondition,\n} from './model';\nimport { type IdPath, parsePath } from './path';\n\n/** `useJanus({ loaders })`'s entry: the object of a type, by id, or `null`. */\nexport type Loader = (id: string, ctx: unknown) => unknown;\n/** `useJanus({ conditions })`'s entry: the `ctx` a type's conditions test. */\nexport type Condition = (object: unknown, ctx: unknown) => unknown;\n\n/** What `@permission` reads from `useJanus()`'s options. */\nexport interface PermissionWiring {\n\treadonly access?: unknown;\n\treadonly loaders?: { readonly [type: string]: unknown } | undefined;\n\treadonly conditions?: { readonly [type: string]: unknown } | undefined;\n}\n\n/** One `@permission`, as the guard asks it. */\nexport interface PermissionCheck {\n\t/** `@permission on Record (read by Record.title)`, for the messages. */\n\treadonly label: string;\n\treadonly name: string;\n\treadonly type: string;\n\treadonly onDeny: 'NOT_FOUND' | 'FORBIDDEN';\n\treadonly path: IdPath;\n\t/** Loads an object named by id alone; `null` passes `{ type, id }`. */\n\treadonly load: Loader | null;\n\t/** Answers the check's `ctx`; `null` when no condition is reachable. */\n\treadonly condition: Condition | null;\n}\n\n/** The field a directive is read for: `Type.field`, and the arguments it takes. */\nexport interface FieldShape {\n\treadonly name: string;\n\treadonly args: readonly string[];\n}\n\n/** `use`, checked, or a `TypeError` naming the field. */\nexport function permissionCheckOf(\n\tuse: PermissionUse,\n\tfield: FieldShape,\n\twiring: PermissionWiring,\n): PermissionCheck {\n\tconst label = `@permission on ${on(use.where, field.name)}`;\n\tconst refuse = (why: string) => new TypeError(`${PREFIX}: ${label} ${why}`);\n\tconst model = modelOf(wiring.access);\n\tif (model === null) {\n\t\tthrow refuse(\n\t\t\t'needs the permissions() instance — pass useJanus({ auth, access }), with access what permissions() answered',\n\t\t);\n\t}\n\tcheckNames(model, use, refuse);\n\tconst text = use.id ?? (use.onType ? 'parent.id' : 'args.id');\n\tconst path = parsePath(text);\n\tif (path === null) {\n\t\tthrow refuse(\n\t\t\t`reads its id from '${text}', which is not args.<name> or parent.<name>`,\n\t\t);\n\t}\n\tconst [first] = path.segments;\n\tif (path.root === 'args' && !field.args.includes(first as string)) {\n\t\tthrow refuse(\n\t\t\t`reads ${path.root}.${first}, and ${field.name} takes no argument ${first}${\n\t\t\t\tuse.id === null && use.onType === false\n\t\t\t\t\t? ' — name the id with id: \"parent.<field>\" or id: \"args.<name>\"'\n\t\t\t\t\t: ''\n\t\t\t}`,\n\t\t);\n\t}\n\treturn {\n\t\tlabel,\n\t\tname: use.name,\n\t\ttype: use.type,\n\t\tonDeny: use.onDeny,\n\t\tpath,\n\t\tload: loaderOf(model, use.type, path, wiring, refuse),\n\t\tcondition: conditionOf(model, use, wiring, refuse),\n\t};\n}\n\ntype Refuse = (why: string) => TypeError;\n\nfunction checkNames(model: ModelLike, use: PermissionUse, refuse: Refuse) {\n\tif (!model.types.includes(use.type)) {\n\t\tthrow refuse(\n\t\t\t`names the object type '${use.type}', which is not one of ${list(model.types)}`,\n\t\t);\n\t}\n\tconst names = namesOf(model, use.type);\n\tif (!names.includes(use.name)) {\n\t\tthrow refuse(\n\t\t\t`asks '${use.name}', which ${use.type} does not declare — it declares ${list(names)}`,\n\t\t);\n\t}\n}\n\n/** The loader a check needs: its type's, required when the object is named by id alone and reads its own fields. */\nfunction loaderOf(\n\tmodel: ModelLike,\n\ttype: string,\n\tpath: IdPath,\n\twiring: PermissionWiring,\n\trefuse: Refuse,\n): Loader | null {\n\tconst load = wiring.loaders?.[type];\n\tif (load !== undefined && typeof load !== 'function') {\n\t\tthrow refuse(`finds loaders.${type}, which is not a function`);\n\t}\n\tconst held = path.root === 'parent' && path.segments.at(-1) === 'id';\n\tif (held) return null;\n\tif (load !== undefined) return load as Loader;\n\tconst fields = fieldsOf(model, type);\n\tif (fields.length > 0) {\n\t\tthrow refuse(\n\t\t\t`reads the ${type}'s id from ${path.text}, and ${type} reads ${list(fields)} of the object itself (fromField) — pass useJanus({ loaders: { ${type}: (id, ctx) => … } })`,\n\t\t);\n\t}\n\treturn null;\n}\n\n/** The type's `conditions` entry, required when the permission reaches a `when`. */\nfunction conditionOf(\n\tmodel: ModelLike,\n\tuse: PermissionUse,\n\twiring: PermissionWiring,\n\trefuse: Refuse,\n): Condition | null {\n\tif (!reachesCondition(model, use.type, use.name)) return null;\n\tconst condition = wiring.conditions?.[use.type];\n\tif (typeof condition !== 'function') {\n\t\tthrow refuse(\n\t\t\t`asks '${use.name}' of ${use.type}, which reaches a when() — pass useJanus({ conditions: { ${use.type}: (object, ctx) => … } })`,\n\t\t);\n\t}\n\treturn condition as Condition;\n}\n",
16
+ "/**\n * Decides, when the schema is built, what the directives on one field add up\n * to — and refuses with a `TypeError` what no request could ever pass: a user\n * type `auth` does not know, a `type: []`, restrictions that exclude each\n * other, or a `@permission` the model, the field or the wiring cannot answer\n * (`permission/validate.ts`).\n */\n\nimport {\n\ttype FieldShape,\n\ttype PermissionCheck,\n\ttype PermissionWiring,\n\tpermissionCheckOf,\n} from './permission/validate';\nimport type { AuthenticatedUse, FieldDirectives } from './read';\nimport { list, on, PREFIX } from './words';\n\n/** What a field requires before its resolver runs. */\nexport interface Requirement {\n\t/** `Type.field`, for the messages. */\n\treadonly where: string;\n\t/** The directive a missing `ctx.janus` is reported against. */\n\treadonly label: string;\n\t/**\n\t * The user types allowed, every `type:` intersected; `null` when none\n\t * narrows it: any signed-in user.\n\t */\n\treadonly types: ReadonlySet<string> | null;\n\t/** The permissions asked after that, in order: every one must hold. */\n\treadonly permissions: readonly PermissionCheck[];\n}\n\n/** The user types a schema may name: `auth.types`, or the one `useJanus({ type })` keeps. */\nexport interface Known {\n\treadonly types: readonly string[];\n}\n\n/**\n * The requirement of one field, or `null` when nothing guards it. Throws a\n * `TypeError` naming the field for a directive no request could pass.\n */\nexport function requirementOf(\n\tfield: FieldShape,\n\tdirectives: FieldDirectives,\n\tknown: Known,\n\twiring: PermissionWiring,\n): Requirement | null {\n\tconst { authenticated, permission } = directives;\n\tif (authenticated.length === 0 && permission.length === 0) return null;\n\tconst [first] = authenticated.length > 0 ? authenticated : permission;\n\tconst name = authenticated.length > 0 ? '@authenticated' : '@permission';\n\treturn {\n\t\twhere: field.name,\n\t\tlabel: `${name} on ${on(first?.where ?? field.name, field.name)}`,\n\t\ttypes: typesOf(field.name, authenticated, known),\n\t\tpermissions: permission.map((use) => permissionCheckOf(use, field, wiring)),\n\t};\n}\n\n/** Every `type:` intersected, or `null` when none names any. */\nfunction typesOf(\n\tfield: string,\n\tuses: readonly AuthenticatedUse[],\n\tknown: Known,\n): ReadonlySet<string> | null {\n\tlet types: Set<string> | null = null;\n\tfor (const { where, types: named } of uses) {\n\t\tif (named === null) continue;\n\t\tif (named.length === 0) {\n\t\t\tthrow new TypeError(\n\t\t\t\t`${PREFIX}: @authenticated on ${on(where, field)} names no user type — leave type: out to admit any signed-in user`,\n\t\t\t);\n\t\t}\n\t\tfor (const name of named) {\n\t\t\tif (!known.types.includes(name)) {\n\t\t\t\tthrow new TypeError(\n\t\t\t\t\t`${PREFIX}: @authenticated on ${on(where, field)} names the user type '${name}', which is not one of ${list(known.types)}`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\ttypes =\n\t\t\ttypes === null\n\t\t\t\t? new Set(named)\n\t\t\t\t: new Set(named.filter((name) => types?.has(name)));\n\t}\n\tif (types !== null && types.size === 0) {\n\t\tthrow new TypeError(\n\t\t\t`${PREFIX}: the @authenticated on ${field} and on its type or interfaces admit no user type in common`,\n\t\t);\n\t}\n\treturn types;\n}\n",
17
+ "/**\n * The two calls a resolver makes itself: `requireUser` where a directive\n * would not do, and `can` for a check the schema cannot express.\n */\n\nimport type {\n\tCheckArgs,\n\tCheckableOf,\n\tModelConfig,\n\tObjectRef,\n\tObjectTypeOf,\n\tPermissions,\n} from '@nxgt/janus/permissions';\nimport { checkOf, janusOf } from './context';\nimport { denial, rethrown } from './errors';\n\n/** What `requireUser()` takes. */\nexport interface RequireUserOptions<T extends string> {\n\t/**\n\t * Only a user of this type, or of one of these — never an empty list,\n\t * which no user could pass: any other is `FORBIDDEN`.\n\t */\n\treadonly type?: T | readonly [T, ...T[]];\n}\n\n/**\n * The signed-in user, narrowed to `type` when it is given — or a denial:\n * `UNAUTHENTICATED` 401 for an anonymous request, `FORBIDDEN` 403 for a user\n * of another type. A store that cannot answer is `SERVICE_UNAVAILABLE` 503,\n * never either of them.\n *\n * ```ts\n * const staff = await requireUser(ctx, { type: 'staff' });\n * staff.username; // typed: a staff member\n * ```\n */\nexport async function requireUser<\n\tU extends { readonly type: string },\n\tconst T extends U['type'] = U['type'],\n>(\n\tctx: { readonly janus: { user(): Promise<U | null> } },\n\toptions: RequireUserOptions<T> = {},\n): Promise<Extract<U, { readonly type: T }>> {\n\tconst { type } = options;\n\tconst allowed: readonly string[] | undefined =\n\t\ttypeof type === 'string' ? [type] : type;\n\tif (allowed?.length === 0) {\n\t\tthrow new TypeError(\n\t\t\t'requireUser(): type is an empty list, which no user could pass — name at least one user type, or leave type out',\n\t\t);\n\t}\n\tconst user = await janusOf(ctx, 'requireUser()')\n\t\t.user()\n\t\t.then(undefined, rethrown);\n\tif (user === null) throw denial('UNAUTHENTICATED');\n\tif (allowed !== undefined && !allowed.includes(user.type)) {\n\t\tthrow denial('FORBIDDEN');\n\t}\n\treturn user as unknown as Extract<U, { readonly type: T }>;\n}\n\n/**\n * Whether the request's user holds `permission` on `object`, typed as\n * `access.can` is: the permission must be one the object's type declares,\n * the object must carry every field its `fromField`s read, and `{ ctx }` is\n * required exactly when a condition is reachable. Anonymous answers `false`.\n *\n * **A failure throws**: a store that cannot answer is `SERVICE_UNAVAILABLE`\n * 503, never `false`.\n *\n * ```ts\n * if (!(await can(ctx, 'edit', { type: 'record', ...record }))) return null;\n * ```\n */\nexport async function can<\n\tC extends ModelConfig,\n\tconst T extends ObjectTypeOf<C>,\n\tconst P extends CheckableOf<C, T>,\n>(\n\tctx: {\n\t\treadonly janus: {\n\t\t\tuser(): Promise<{ readonly type: string; readonly id: string } | null>;\n\t\t\treadonly access: Pick<Permissions<C>, 'can'>;\n\t\t};\n\t},\n\tpermission: P,\n\tobject: ObjectRef<C, T>,\n\t...options: CheckArgs<C, T, P>\n): Promise<boolean> {\n\tconst janus = janusOf(ctx, 'can()');\n\tconst check = checkOf(janus);\n\tif (check === null) {\n\t\tthrow new TypeError(\n\t\t\t'can(): ctx.janus.access is not set — pass { access } to useJanus()',\n\t\t);\n\t}\n\tconst subject = await janus.user().then(undefined, rethrown);\n\treturn check(subject, permission, object, options[0]).then(\n\t\tundefined,\n\t\trethrown,\n\t);\n}\n",
18
+ "/**\n * `useJanus()`, the envelop plugin — for Yoga, or any server built on\n * envelop.\n */\n\nimport type { Plugin } from '@envelop/core';\nimport type { RequestLike } from '@nxgt/janus';\nimport type { GraphQLSchema } from 'graphql';\nimport { createJanusContext } from './context';\nimport { applyJanusDirectives } from './directives/apply';\nimport type { Auth, JanusContext, JanusOptions, UserOfAuth } from './types';\n\n/**\n * The plugin that wires `@nxgt/janus` into a GraphQL server:\n *\n * - every request's context gets `ctx.janus` — `user()` and `session()`,\n * authenticated lazily and once, and `access` when it is given;\n * - every schema the server is given has its directives applied, once, by\n * `applyJanusDirectives()`.\n *\n * ```ts\n * createYoga({\n * schema: createSchema({ typeDefs: [janusTypeDefs, typeDefs], resolvers }),\n * plugins: [useJanus({ auth, access })],\n * maskedErrors: { maskError: janusMaskError() },\n * });\n * ```\n *\n * `type` treats a user of any other type as anonymous, as\n * `auth.authenticate(request, { type })` does. `loaders` and `conditions`\n * are what `@permission` needs beside `access`: the objects it checks by id,\n * and the `ctx` of the conditions it reaches.\n */\nexport function useJanus<\n\tA extends Auth<{ readonly type: string; readonly id: string }>,\n\tP extends object | undefined = undefined,\n\tconst T extends UserOfAuth<A>['type'] = UserOfAuth<A>['type'],\n>(options: JanusOptions<A, P, T>): Plugin<JanusContext<A, P, T>> {\n\tconst { auth, access, type, loaders, conditions } = options;\n\tif (typeof auth?.authenticate !== 'function') {\n\t\tthrow new TypeError(\n\t\t\t'useJanus(): auth is not what janus() answered — pass { auth }',\n\t\t);\n\t}\n\tif (\n\t\taccess !== undefined &&\n\t\ttypeof (access as { readonly can?: unknown } | null)?.can !== 'function'\n\t) {\n\t\tthrow new TypeError(\n\t\t\t'useJanus(): access is not what permissions() answered — pass { auth, access }',\n\t\t);\n\t}\n\tconst applied = new WeakSet<GraphQLSchema>();\n\n\treturn {\n\t\tonContextBuilding: ({ context, extendContext }) => {\n\t\t\tconst { request } = context as { readonly request?: RequestLike };\n\t\t\tconst janus = createJanusContext(request, {\n\t\t\t\tauth,\n\t\t\t\taccess: access as { readonly can: unknown } | undefined,\n\t\t\t\ttype,\n\t\t\t});\n\t\t\textendContext({ janus } as unknown as Partial<JanusContext<A, P, T>>);\n\t\t},\n\t\t// `replaceSchema` calls this hook again with the schema it was given:\n\t\t// the one it made is remembered, or it would transform it for ever.\n\t\tonSchemaChange: ({ schema, replaceSchema }) => {\n\t\t\tif (applied.has(schema)) return;\n\t\t\tconst next = applyJanusDirectives(schema, {\n\t\t\t\tauth,\n\t\t\t\t...(type === undefined ? {} : { type }),\n\t\t\t\taccess: access as { readonly model: unknown } | undefined,\n\t\t\t\tloaders,\n\t\t\t\tconditions,\n\t\t\t});\n\t\t\tapplied.add(next);\n\t\t\treplaceSchema(next);\n\t\t},\n\t};\n}\n",
19
+ "/**\n * The directives and the enum this package reads, as SDL: put it beside your\n * own type definitions. The same text ships as `graphql/janus.graphqls`, for\n * a code generator or an editor that reads files — `sdl.spec.ts` holds that\n * the two are identical.\n *\n * ```ts\n * createSchema({ typeDefs: [janusTypeDefs, typeDefs], resolvers });\n * ```\n */\nexport const janusTypeDefs: string = `\"\"\"\nOnly a signed-in user reaches this field, or every field of this type or\ninterface. With \\`type\\`, only a user of one of these user types: any other is\nanswered FORBIDDEN.\n\"\"\"\ndirective @authenticated(\n\ttype: [String!]\n) on OBJECT | INTERFACE | FIELD_DEFINITION\n\n\"\"\"\nWhat a denied \\`@permission\\` answers.\n\"\"\"\nenum JanusPermissionDenial {\n\t\"\"\"\n\t404: the object is not told to exist. The default.\n\t\"\"\"\n\tNOT_FOUND\n\t\"\"\"\n\t403: the object exists, and this user may not do this to it.\n\t\"\"\"\n\tFORBIDDEN\n}\n\n\"\"\"\nOnly a user holding permission \\`name\\` on the object of \\`type\\` whose id \\`id\\`\nreads reaches this field, or every field of this type or interface. \\`id\\` is\n\\`args.<path>\\` or \\`parent.<path>\\`: \\`args.id\\` on a field and \\`parent.id\\` on a\ntype or an interface when absent. A list of ids requires the permission on\nevery one. Repeated, every one must hold, in the order written.\n\"\"\"\ndirective @permission(\n\tname: String!\n\ttype: String!\n\tid: String\n\tonDeny: JanusPermissionDenial! = NOT_FOUND\n) repeatable on OBJECT | INTERFACE | FIELD_DEFINITION\n`;\n"
20
+ ],
21
+ "mappings": "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",
22
+ "debugId": "D0DEC0D35AFB92A464756E2164756E21",
23
+ "names": []
24
+ }
package/dist/memo.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The per-request memo of permission checks: the same question asked twice
3
+ * in one request — by two fields, by a directive and a resolver's `can()`,
4
+ * or by two fields at once — is one call to `access.can`.
5
+ */
6
+ import type { Check } from './context';
7
+ /**
8
+ * `access.can` for one request, remembered: a question asked again answers
9
+ * the first answer's promise, so two asked at once share one call. A
10
+ * failure is not remembered — an outage is no answer, and the next question
11
+ * asks again.
12
+ */
13
+ export declare function memoized(access: {
14
+ readonly can: unknown;
15
+ }): Check;
16
+ //# sourceMappingURL=memo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memo.d.ts","sourceRoot":"","sources":["../src/memo.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAkCvC;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE;IAAE,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAA;CAAE,GAAG,KAAK,CAejE"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * `useJanus()`, the envelop plugin — for Yoga, or any server built on
3
+ * envelop.
4
+ */
5
+ import type { Plugin } from '@envelop/core';
6
+ import type { Auth, JanusContext, JanusOptions, UserOfAuth } from './types';
7
+ /**
8
+ * The plugin that wires `@nxgt/janus` into a GraphQL server:
9
+ *
10
+ * - every request's context gets `ctx.janus` — `user()` and `session()`,
11
+ * authenticated lazily and once, and `access` when it is given;
12
+ * - every schema the server is given has its directives applied, once, by
13
+ * `applyJanusDirectives()`.
14
+ *
15
+ * ```ts
16
+ * createYoga({
17
+ * schema: createSchema({ typeDefs: [janusTypeDefs, typeDefs], resolvers }),
18
+ * plugins: [useJanus({ auth, access })],
19
+ * maskedErrors: { maskError: janusMaskError() },
20
+ * });
21
+ * ```
22
+ *
23
+ * `type` treats a user of any other type as anonymous, as
24
+ * `auth.authenticate(request, { type })` does. `loaders` and `conditions`
25
+ * are what `@permission` needs beside `access`: the objects it checks by id,
26
+ * and the `ctx` of the conditions it reaches.
27
+ */
28
+ export declare function useJanus<A extends Auth<{
29
+ readonly type: string;
30
+ readonly id: string;
31
+ }>, P extends object | undefined = undefined, const T extends UserOfAuth<A>['type'] = UserOfAuth<A>['type']>(options: JanusOptions<A, P, T>): Plugin<JanusContext<A, P, T>>;
32
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAK5C,OAAO,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE5E;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,QAAQ,CACvB,CAAC,SAAS,IAAI,CAAC;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC,EAC9D,CAAC,SAAS,MAAM,GAAG,SAAS,GAAG,SAAS,EACxC,KAAK,CAAC,CAAC,SAAS,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,EAC5D,OAAO,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CA0C/D"}
package/dist/sdl.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ /**
2
+ * The directives and the enum this package reads, as SDL: put it beside your
3
+ * own type definitions. The same text ships as `graphql/janus.graphqls`, for
4
+ * a code generator or an editor that reads files — `sdl.spec.ts` holds that
5
+ * the two are identical.
6
+ *
7
+ * ```ts
8
+ * createSchema({ typeDefs: [janusTypeDefs, typeDefs], resolvers });
9
+ * ```
10
+ */
11
+ export declare const janusTypeDefs: string;
12
+ //# sourceMappingURL=sdl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sdl.d.ts","sourceRoot":"","sources":["../src/sdl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,eAAO,MAAM,aAAa,EAAE,MAoC3B,CAAC"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * The public types: what `useJanus()` takes, and what it puts on the
3
+ * GraphQL context. Nothing here runs.
4
+ */
5
+ import type { Session, SharedApi } from '@nxgt/janus';
6
+ import type { PermissionWiringOf } from './wiring';
7
+ /** Anything `janus()` answered: the part of it this package calls. */
8
+ export type Auth<U extends {
9
+ readonly type: string;
10
+ }> = Pick<SharedApi<U>, 'authenticate' | 'types'>;
11
+ /** The users an `auth` instance knows, as a union narrowed by `user.type`. */
12
+ export type UserOfAuth<A> = A extends Auth<infer U> ? U : never;
13
+ /**
14
+ * What `useJanus()` takes. `loaders` and `conditions` are what `@permission`
15
+ * needs beside `access`, typed from its model — see `Loaders` and
16
+ * `Conditions`.
17
+ */
18
+ export type JanusOptions<A, P, T extends string> = {
19
+ /** What `janus()` answered. */
20
+ readonly auth: A;
21
+ /**
22
+ * What `permissions()` answered: `ctx.janus.access`, what `can()` and
23
+ * `@permission` ask. Absent, the context has no `access`, and `can()` does
24
+ * not compile.
25
+ */
26
+ readonly access?: P;
27
+ /** Only a user of this type is authenticated here; any other is anonymous. */
28
+ readonly type?: T;
29
+ } & PermissionWiringOf<P, {
30
+ readonly janus: JanusOnContext<Extract<UserOfAuth<A>, {
31
+ readonly type: T;
32
+ }>, P>;
33
+ }>;
34
+ /**
35
+ * `ctx.janus`, one per request.
36
+ *
37
+ * `user()` and `session()` call `auth.authenticate` on the request the first
38
+ * time either is asked, and never when neither is: a query that reads no
39
+ * guarded field costs no store call. Both share that one answer.
40
+ */
41
+ export type JanusOnContext<U, P> = {
42
+ /**
43
+ * Who the request belongs to, or `null` for an anonymous one. **An outage
44
+ * is not anonymous**: a store that cannot answer rejects with
45
+ * `STORE_FAILED`, answered 503.
46
+ */
47
+ user(): Promise<U | null>;
48
+ /** The session the request presented, or `null` for an anonymous one. */
49
+ session(): Promise<Session | null>;
50
+ } & ([P] extends [undefined] ? unknown : {
51
+ readonly access: P;
52
+ });
53
+ /**
54
+ * The context `useJanus()` adds to, for your resolvers:
55
+ * `YogaInitialContext & JanusContext<typeof auth, typeof access>`.
56
+ *
57
+ * `T` narrows the user to the types `useJanus({ type })` was given.
58
+ */
59
+ export type JanusContext<A, P = undefined, T extends UserOfAuth<A>['type'] = UserOfAuth<A>['type']> = {
60
+ readonly janus: JanusOnContext<Extract<UserOfAuth<A>, {
61
+ readonly type: T;
62
+ }>, P>;
63
+ };
64
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAEnD,sEAAsE;AACtE,MAAM,MAAM,IAAI,CAAC,CAAC,SAAS;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,IAAI,IAAI,CAC3D,SAAS,CAAC,CAAC,CAAC,EACZ,cAAc,GAAG,OAAO,CACxB,CAAC;AAEF,8EAA8E;AAC9E,MAAM,MAAM,UAAU,CAAC,CAAC,IAAI,CAAC,SAAS,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;AAEhE;;;;GAIG;AACH,MAAM,MAAM,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,SAAS,MAAM,IAAI;IAClD,+BAA+B;IAC/B,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IACjB;;;;OAIG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACpB,8EAA8E;IAC9E,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;CAClB,GAAG,kBAAkB,CACrB,CAAC,EACD;IACC,QAAQ,CAAC,KAAK,EAAE,cAAc,CAC7B,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAA;KAAE,CAAC,EAC5C,CAAC,CACD,CAAC;CACF,CACD,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,CAAC,CAAC,EAAE,CAAC,IAAI;IAClC;;;;OAIG;IACH,IAAI,IAAI,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAC1B,yEAAyE;IACzE,OAAO,IAAI,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;CACnC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,GAAG,OAAO,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAA;CAAE,CAAC,CAAC;AAEjE;;;;;GAKG;AACH,MAAM,MAAM,YAAY,CACvB,CAAC,EACD,CAAC,GAAG,SAAS,EACb,CAAC,SAAS,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IACpD;IACH,QAAQ,CAAC,KAAK,EAAE,cAAc,CAC7B,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAA;KAAE,CAAC,EAC5C,CAAC,CACD,CAAC;CACF,CAAC"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * The types of what `@permission` takes from `useJanus()` beside `access`:
3
+ * `loaders`, the objects it checks by id, and `conditions`, the `ctx` a
4
+ * condition tests. Typed from the model `access` was built on. Nothing here
5
+ * runs.
6
+ */
7
+ import type { CheckableOf, CtxOf, FieldsOf, ModelConfig, ObjectRef, ObjectTypeOf, PermissionModel } from '@nxgt/janus/permissions';
8
+ type Awaitable<V> = V | Promise<V>;
9
+ /** The model configuration of a `permissions()` instance; `never` for anything else. */
10
+ export type ModelConfigOf<P> = P extends {
11
+ readonly model: PermissionModel<infer C>;
12
+ } ? C : never;
13
+ /**
14
+ * An object of type `T` as a loader answers it: its id, **every field a
15
+ * `fromField` of its type reads**, and whatever else it carries.
16
+ */
17
+ export type LoadedObject<C extends ModelConfig, T extends ObjectTypeOf<C>> = {
18
+ readonly id: string;
19
+ } & {
20
+ readonly [F in FieldsOf<C, T>]: string | null;
21
+ };
22
+ /** Every condition any permission of `T` reaches, as one `ctx`; `never` when none does. */
23
+ export type ConditionCtxOf<C extends ModelConfig, T extends ObjectTypeOf<C>> = CtxOf<C, T, CheckableOf<C, T>>;
24
+ type Loader<C extends ModelConfig, T extends ObjectTypeOf<C>, Ctx> = {
25
+ load(id: string, ctx: Ctx): Awaitable<LoadedObject<C, T> | null>;
26
+ }['load'];
27
+ type Condition<C extends ModelConfig, T extends ObjectTypeOf<C>, Ctx> = {
28
+ condition(object: ObjectRef<C, T>, ctx: Ctx): Awaitable<ConditionCtxOf<C, T>>;
29
+ }['condition'];
30
+ /**
31
+ * `useJanus({ loaders })`: per object type, the object whose id a request
32
+ * names, or `null` when there is none — answered `NOT_FOUND`. Required for a
33
+ * type with a `fromField` whose id `@permission` reads from `args`.
34
+ */
35
+ export type Loaders<C extends ModelConfig, Ctx> = {
36
+ readonly [T in ObjectTypeOf<C>]?: Loader<C, T, Ctx>;
37
+ };
38
+ /**
39
+ * `useJanus({ conditions })`: per object type whose permissions reach a
40
+ * `when()`, the `ctx` it tests, from the object and the request's context.
41
+ */
42
+ export type Conditions<C extends ModelConfig, Ctx> = {
43
+ readonly [T in ObjectTypeOf<C> as [ConditionCtxOf<C, T>] extends [never] ? never : T]?: Condition<C, T, Ctx>;
44
+ };
45
+ /** The options `@permission` reads, typed from `access`; absent without it. */
46
+ export type PermissionWiringOf<P, Ctx> = [ModelConfigOf<P>] extends [never] ? {
47
+ readonly loaders?: never;
48
+ readonly conditions?: never;
49
+ } : {
50
+ /** The objects `@permission` checks by id — see {@link Loaders}. */
51
+ readonly loaders?: Loaders<ModelConfigOf<P>, Ctx>;
52
+ /** The `ctx` of the conditions `@permission` reaches — see {@link Conditions}. */
53
+ readonly conditions?: Conditions<ModelConfigOf<P>, Ctx>;
54
+ };
55
+ export {};
56
+ //# sourceMappingURL=wiring.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wiring.d.ts","sourceRoot":"","sources":["../src/wiring.ts"],"names":[],"mappings":"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"}
package/docs/README.md ADDED
@@ -0,0 +1,18 @@
1
+ # @nxgt/janus-graphql — documentation
2
+
3
+ The [README](../README.md) shows the whole wiring in one example; these pages
4
+ give the detail.
5
+
6
+ | Page | Read it when |
7
+ | --- | --- |
8
+ | [Directives](guide/directives.md) | You are guarding fields, types or interfaces with `@authenticated` or `@permission`: where the id is read, loaders and conditions, lists, the order, the denials, one check per question, and what is refused at start-up |
9
+ | [The context](guide/context.md) | You are reading the user in a resolver, narrowing it to a user type, or checking a permission yourself with `requireUser()` and `can()` |
10
+ | [Errors](guide/errors.md) | You want to know what a client receives for a denial or an outage, what the status of the response is, and how `janusMaskError()` fits Yoga's masking |
11
+ | [Telemetry](guide/telemetry.md) | You want a span per authentication and per permission check, with `@nxgt/janus-telemetry` |
12
+ | [Federation](guide/federation.md) | The server is a subgraph: where the directives are checked, what the router must forward, and `@composeDirective` |
13
+ | [Troubleshooting](troubleshooting.md) | A query answers something you did not expect, the server refuses to start, or the compiler refuses a resolver |
14
+ | [Roadmap](roadmap.md) | You want to know what is coming — subscriptions over graphql-ws, `@fresh` — and what is deliberately not planned |
15
+
16
+ The words — user, user type, session, subject, permission, failure, denial —
17
+ are [`@nxgt/janus`'s vocabulary](https://github.com/softistx/nxgt-janus/blob/develop/packages/janus/docs/guide/vocabulary.md),
18
+ and mean the same here.
@@ -0,0 +1,156 @@
1
+ # The context
2
+
3
+ This page is for everything a resolver reads from `@nxgt/janus`: the user and
4
+ the session on `ctx.janus`, the two helpers, and the types that make `ctx`
5
+ precise. For the directives that guard a field or a type, see
6
+ [directives](directives.md); for what a client receives when a request is
7
+ refused, see [errors](errors.md).
8
+
9
+ ## Wiring
10
+
11
+ ```ts
12
+ import { janusMaskError, janusTypeDefs, useJanus } from '@nxgt/janus-graphql';
13
+ import { createSchema, createYoga } from 'graphql-yoga';
14
+
15
+ export const yoga = createYoga({
16
+ schema: createSchema({ typeDefs: [janusTypeDefs, typeDefs], resolvers }),
17
+ plugins: [useJanus({ auth, access })],
18
+ maskedErrors: { maskError: janusMaskError() },
19
+ });
20
+ ```
21
+
22
+ `useJanus()` does two things, and nothing else:
23
+
24
+ - **Every request's context gets `ctx.janus`**, built from `context.request`,
25
+ the `Request` Yoga puts there.
26
+ - **Every schema the server is given has its directives applied**, once, by
27
+ `applyJanusDirectives()`. A schema set later — a gateway reloading its
28
+ supergraph — is applied too. The schema it made is remembered, so envelop
29
+ calling the hook again with it does not loop.
30
+
31
+ `janusTypeDefs` must be among the type definitions: without it, the schema
32
+ does not build, since `@authenticated` and `@permission` are not declared.
33
+
34
+ | Option | What it does |
35
+ | --- | --- |
36
+ | `auth` | What `janus()` answered. Required |
37
+ | `access` | What `permissions()` answered: `ctx.janus.access`, what `can()` and `@permission` ask. Optional — and required once the schema uses `@permission` |
38
+ | `type` | A user type: a user of any other type is anonymous on this server, as `auth.authenticate(request, { type })` answers |
39
+ | `loaders` | Per object type, `(id, ctx) => object \| null`: the object `@permission` checks when it reads an id alone — from `args`, or a parent field other than `id`. Required for a type with a `fromField` — see [directives](directives.md#reading-the-id) |
40
+ | `conditions` | Per object type, `(object, ctx) => its when()s' ctx`: what `@permission` passes as `{ ctx }` — see [directives](directives.md#conditions) |
41
+
42
+ ## `ctx.janus`
43
+
44
+ ```ts
45
+ import type { JanusContext } from '@nxgt/janus-graphql';
46
+ import type { YogaInitialContext } from 'graphql-yoga';
47
+
48
+ type Context = YogaInitialContext & JanusContext<typeof auth, typeof access>;
49
+
50
+ const resolvers = {
51
+ Query: {
52
+ greeting: async (_: unknown, __: unknown, ctx: Context) => {
53
+ const user = await ctx.janus.user(); // User<'patient', …> | User<'staff', …> | null
54
+ return user === null ? 'Hello' : `Hello, ${user.type}`;
55
+ },
56
+ expires: async (_: unknown, __: unknown, ctx: Context) =>
57
+ (await ctx.janus.session())?.expiresAt.toISOString() ?? null,
58
+ },
59
+ };
60
+ ```
61
+
62
+ | Member | What it answers |
63
+ | --- | --- |
64
+ | `user()` | The user the request belongs to, as a union narrowed by `user.type`, or `null` for an anonymous request |
65
+ | `session()` | The session it presented, or `null` |
66
+ | `access` | The `permissions()` instance, when `useJanus()` was given one |
67
+
68
+ **Lazy, and once per request.** `auth.authenticate(request)` runs the first
69
+ time `user()` or `session()` is asked, and both share its answer: a query
70
+ whose fields ask ten times costs one store call, and a query that asks
71
+ nothing — an introspection, a field no directive guards — costs none.
72
+
73
+ **An outage is not anonymous.** When a store cannot answer, `user()` rejects
74
+ with `STORE_FAILED` rather than answering `null`, and the client receives
75
+ `SERVICE_UNAVAILABLE`, 503 — see [errors](errors.md).
76
+
77
+ `authenticate` reads the request as it does anywhere: `Authorization:
78
+ Bearer`, then `X-Session-Token`, then the cookie. It renews a sliding session
79
+ in passing, and **nothing here sends the renewed cookie back**: a GraphQL
80
+ response carries no `Set-Cookie` from this package.
81
+
82
+ `JanusContext<typeof auth, typeof access, Type>` takes the third argument
83
+ when `useJanus({ type })` narrows the server:
84
+
85
+ ```ts
86
+ type StaffContext = YogaInitialContext & JanusContext<typeof auth, typeof access, 'staff'>;
87
+ // (await ctx.janus.user())?.username — a staff member's field, typed
88
+ ```
89
+
90
+ Without `access`, write `JanusContext<typeof auth>`: `ctx.janus.access` is
91
+ then absent from the type, as it is from the context.
92
+
93
+ ## Directives
94
+
95
+ `@authenticated` and `@permission` guard a field, a type or an interface
96
+ before its resolver runs: see [directives](directives.md). This page covers
97
+ what a resolver checks itself, where a directive does not fit.
98
+
99
+ ## `requireUser(ctx, { type? })`
100
+
101
+ ```ts
102
+ import { requireUser } from '@nxgt/janus-graphql';
103
+
104
+ const resolvers = {
105
+ Mutation: {
106
+ admitPatient: async (_: unknown, args: { id: string }, ctx: Context) => {
107
+ const staff = await requireUser(ctx, { type: 'staff' });
108
+ return wards.admit(args.id, { by: staff.username }); // typed: a staff member
109
+ },
110
+ },
111
+ };
112
+ ```
113
+
114
+ The signed-in user, narrowed to `type` — or a denial: `UNAUTHENTICATED` for
115
+ an anonymous request, `FORBIDDEN` for a user of another type. `type` takes
116
+ one user type or a list, `{ type: ['staff', 'patient'] }`, and the answer is
117
+ typed as the union of them. Without `type`, any signed-in user. An empty list
118
+ is refused — by the compiler, and with a `TypeError` for a list built at run
119
+ time — since no user could pass it.
120
+
121
+ Use it where a directive does not fit: a mutation that must know the user
122
+ anyway, or a type that depends on the arguments.
123
+
124
+ ## `can(ctx, permission, object, options?)`
125
+
126
+ ```ts
127
+ import { can } from '@nxgt/janus-graphql';
128
+
129
+ const resolvers = {
130
+ Query: {
131
+ record: async (_: unknown, { id }: { id: string }, ctx: Context) => {
132
+ const record = await records.find(id);
133
+ if (record === null) return null;
134
+ return (await can(ctx, 'view', { type: 'record', ...record })) ? record : null;
135
+ },
136
+ },
137
+ };
138
+ ```
139
+
140
+ `access.can` for the request's user, **typed as `access.can` is**: the
141
+ permission must be one the object's type declares, the object must carry
142
+ every field its `fromField`s read, and `{ ctx }` is required exactly when the
143
+ permission reaches a condition. An anonymous request answers `false`, with no
144
+ store call. A store that cannot answer is `SERVICE_UNAVAILABLE`, never
145
+ `false`.
146
+
147
+ **It shares the request's checks with `@permission`.** A question a directive
148
+ already asked in this request — the same object, permission and user — is
149
+ answered from that check, and two asked at once share one; a check with
150
+ `{ ctx }` is always asked. In a subscription, whose one request lasts the
151
+ whole stream, call `ctx.janus.access.can` instead, so a `revoke()` is seen. See
152
+ [one check per question](directives.md#one-check-per-question-per-request).
153
+
154
+ It needs `useJanus({ access })`: without it, `can(ctx, …)` does not compile,
155
+ and a context built by hand without `access` throws
156
+ `TypeError: can(): ctx.janus.access is not set — pass { access } to useJanus()`.