@nxgt/shared-graphql 2.0.1 → 3.0.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 (79) hide show
  1. package/README.md +196 -81
  2. package/dist/directives/authenticated.d.ts +42 -0
  3. package/dist/directives/authenticated.d.ts.map +1 -0
  4. package/dist/directives/federation.d.ts +6 -0
  5. package/dist/directives/federation.d.ts.map +1 -1
  6. package/dist/directives/index.d.ts +5 -1
  7. package/dist/directives/index.d.ts.map +1 -1
  8. package/dist/directives/paths.d.ts +20 -0
  9. package/dist/directives/paths.d.ts.map +1 -0
  10. package/dist/directives/permission.d.ts +39 -0
  11. package/dist/directives/permission.d.ts.map +1 -0
  12. package/dist/directives/removed-check.d.ts +15 -0
  13. package/dist/directives/removed-check.d.ts.map +1 -0
  14. package/dist/directives/scope.d.ts +19 -0
  15. package/dist/directives/scope.d.ts.map +1 -0
  16. package/dist/directives/sdl.d.ts +26 -0
  17. package/dist/directives/sdl.d.ts.map +1 -0
  18. package/dist/directives/validate.d.ts +23 -0
  19. package/dist/directives/validate.d.ts.map +1 -0
  20. package/dist/index.js +1048 -564
  21. package/dist/index.js.map +37 -20
  22. package/dist/integrations/hono.d.ts +2 -2
  23. package/dist/integrations/hono.d.ts.map +1 -1
  24. package/dist/plugins/apply-authenticated.d.ts +21 -0
  25. package/dist/plugins/apply-authenticated.d.ts.map +1 -0
  26. package/dist/plugins/apply-keto-checks.d.ts +20 -0
  27. package/dist/plugins/apply-keto-checks.d.ts.map +1 -0
  28. package/dist/plugins/auth.d.ts +17 -1
  29. package/dist/plugins/auth.d.ts.map +1 -1
  30. package/dist/plugins/authenticated.d.ts +14 -0
  31. package/dist/plugins/authenticated.d.ts.map +1 -0
  32. package/dist/plugins/extract-jwt.d.ts +19 -6
  33. package/dist/plugins/extract-jwt.d.ts.map +1 -1
  34. package/dist/plugins/field-guard.d.ts +30 -0
  35. package/dist/plugins/field-guard.d.ts.map +1 -0
  36. package/dist/plugins/gateway-trust.d.ts +47 -0
  37. package/dist/plugins/gateway-trust.d.ts.map +1 -0
  38. package/dist/plugins/index.d.ts +6 -0
  39. package/dist/plugins/index.d.ts.map +1 -1
  40. package/dist/plugins/keto-checker.d.ts +24 -0
  41. package/dist/plugins/keto-checker.d.ts.map +1 -0
  42. package/dist/plugins/keto-checks.d.ts +11 -34
  43. package/dist/plugins/keto-checks.d.ts.map +1 -1
  44. package/dist/plugins/keto-helpers.d.ts +38 -0
  45. package/dist/plugins/keto-helpers.d.ts.map +1 -0
  46. package/dist/plugins/ory-auth.d.ts +5 -5
  47. package/dist/plugins/ory-auth.d.ts.map +1 -1
  48. package/dist/scalars/custom/utils.d.ts.map +1 -1
  49. package/dist/shared.d.ts +1 -1
  50. package/dist/shared.d.ts.map +1 -1
  51. package/dist/utils/errors/create-error.d.ts +3 -0
  52. package/dist/utils/errors/create-error.d.ts.map +1 -0
  53. package/dist/utils/errors/denial.d.ts +20 -0
  54. package/dist/utils/errors/denial.d.ts.map +1 -0
  55. package/dist/utils/errors/format-error.d.ts +12 -0
  56. package/dist/utils/errors/format-error.d.ts.map +1 -0
  57. package/dist/utils/errors/index.d.ts +6 -0
  58. package/dist/utils/errors/index.d.ts.map +1 -0
  59. package/dist/utils/errors/mask-error.d.ts +25 -0
  60. package/dist/utils/errors/mask-error.d.ts.map +1 -0
  61. package/dist/utils/errors/ory-unavailable.d.ts +19 -0
  62. package/dist/utils/errors/ory-unavailable.d.ts.map +1 -0
  63. package/dist/utils/index.d.ts +1 -1
  64. package/dist/utils/index.d.ts.map +1 -1
  65. package/dist/utils/ws-context.d.ts +1 -1
  66. package/docs/README.md +13 -0
  67. package/docs/guide/authentication.md +187 -0
  68. package/docs/guide/errors.md +136 -0
  69. package/docs/guide/migrating-to-3.md +276 -0
  70. package/docs/guide/permissions.md +220 -0
  71. package/docs/roadmap.md +71 -0
  72. package/docs/troubleshooting.md +273 -0
  73. package/graphql/directives/permission.graphqls +73 -0
  74. package/package.json +11 -13
  75. package/dist/directives/check.d.ts +0 -40
  76. package/dist/directives/check.d.ts.map +0 -1
  77. package/dist/utils/errors.utils.d.ts +0 -23
  78. package/dist/utils/errors.utils.d.ts.map +0 -1
  79. package/graphql/directives/check.graphqls +0 -86
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@nxgt/shared-graphql",
3
- "version": "2.0.1",
3
+ "version": "3.0.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
8
8
  "files": [
9
9
  "dist",
10
+ "docs",
10
11
  "graphql",
11
12
  "README.md",
12
13
  "package.json",
@@ -48,22 +49,17 @@
48
49
  "dependencies": {
49
50
  "@apollo/server": "^5.5.1",
50
51
  "@apollo/subgraph": "^2.14.3",
51
- "@envelop/extended-validation": "^7.1.1",
52
- "@envelop/generic-auth": "^11.1.1",
53
- "@graphql-hive/gateway": "^2.11.2",
54
52
  "@graphql-tools/load-files": "^7.0.1",
55
53
  "@graphql-tools/merge": "^9.2.3",
56
54
  "@graphql-tools/utils": "^12.0.0",
57
55
  "@graphql-yoga/redis-event-target": "^3.0.3",
58
- "@hono/zod-validator": "^0.9.0",
59
- "@nxgt/i18n": "^1.0.4",
60
- "@nxgt/security": "^4.0.0",
61
- "@nxgt/shared": "^1.0.4",
56
+ "@nxgt/i18n": "^1.1.0",
57
+ "@nxgt/security": "^4.1.0",
58
+ "@nxgt/shared": "^1.0.5",
62
59
  "@nxgt/shared-exceptions": "^1.0.4",
63
60
  "@nxgt/shared-logging": "^1.0.4",
64
61
  "@nxgt/shared-mongo": "^1.1.4",
65
62
  "dataloader": "^2.2.3",
66
- "graphql": "^16.4.2",
67
63
  "graphql-scalars": "^1.26.0",
68
64
  "graphql-subscriptions": "^3.0.0",
69
65
  "graphql-upload": "^18.0.0",
@@ -72,17 +68,19 @@
72
68
  "hono": "^4.13.4",
73
69
  "ioredis": "^6.0.0",
74
70
  "lodash": "^4.18.1",
75
- "ws": "^8.21.3",
76
- "zod": "^4.4.3"
71
+ "ws": "^8.21.3"
77
72
  },
78
73
  "devDependencies": {
74
+ "@graphql-tools/schema": "^10.1.0",
79
75
  "@types/bun": "^1.4.0",
80
76
  "@types/graphql-upload": "^17.0.0",
81
77
  "@types/lodash": "^4.17.25",
82
- "@types/ws": "^8.18.1"
78
+ "@types/ws": "^8.18.1",
79
+ "graphql": "^16.9.0"
83
80
  },
84
81
  "peerDependencies": {
85
- "@nxgt/ory-sdk": ">=0.1.0",
82
+ "@nxgt/ory-sdk": ">=0.1.0 <1",
83
+ "graphql": "^16.9.0 || ^17.0.0",
86
84
  "stx-sdk": ">=1.1.0",
87
85
  "typescript": "^6.0.3"
88
86
  }
@@ -1,40 +0,0 @@
1
- import { getDirective } from '@graphql-tools/utils';
2
- import { type PermissionRequirement, type PermissionTerm } from '@nxgt/ory-sdk';
3
- import type { GraphQLSchema } from 'graphql';
4
- /**
5
- * `@check` — the Ory-native counterpart to `@policy`.
6
- *
7
- * The declaration itself is SDL, in `graphql/directives/check.graphqls`, so it
8
- * reaches every consumer of `SHARED_SCHEMA_PATH`. This module only reads it.
9
- * Keeping the two in one place would mean two copies of the same grammar, and
10
- * the copy nobody edits is the one that goes wrong.
11
- */
12
- export declare const CHECK_DIRECTIVE_NAME = "check";
13
- export type CheckDenial = 'NOT_FOUND' | 'FORBIDDEN';
14
- export type CheckArgs = {
15
- permissions: PermissionRequirement;
16
- onDeny: CheckDenial;
17
- /** i18n key; the shared `errors.*` one when the field does not say. */
18
- message?: string;
19
- };
20
- /**
21
- * Every `@check` on a field, in declaration order.
22
- *
23
- * `@check` is repeatable, so this is a list and the order is load-bearing:
24
- * `view` then `edit` is what turns a denial into 404 for a stranger and 403
25
- * for a viewer. `getDirective` reads the arguments against the schema's own
26
- * definition, which is what applies the `id` and `onDeny` defaults — the AST
27
- * node carries only what was written.
28
- */
29
- export declare function readChecks(schema: GraphQLSchema, node: Parameters<typeof getDirective>[1], where: string): CheckArgs[];
30
- /**
31
- * Validated here, when the schema is transformed, and not when a request
32
- * arrives: a path naming neither root is a wiring mistake, and it should stop
33
- * the server from booting rather than 500 on the one query nobody tried.
34
- */
35
- export declare function assertReadablePath(term: PermissionTerm, where: string): void;
36
- /** Reads `args.x` / `source.x.y` off a resolver's inputs. */
37
- export declare function readPath(path: string, source: unknown, args: Record<string, unknown>): unknown;
38
- /** The ids one term has to clear: one value, or every element of a list. */
39
- export declare function objectIds(value: unknown): string[];
40
- //# sourceMappingURL=check.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../src/directives/check.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAEN,KAAK,qBAAqB,EAC1B,KAAK,cAAc,EACnB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,UAAU,CAAC;AAE5C,MAAM,MAAM,WAAW,GAAG,WAAW,GAAG,WAAW,CAAC;AAEpD,MAAM,MAAM,SAAS,GAAG;IACvB,WAAW,EAAE,qBAAqB,CAAC;IACnC,MAAM,EAAE,WAAW,CAAC;IACpB,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CACzB,MAAM,EAAE,aAAa,EACrB,IAAI,EAAE,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,CAAC,CAAC,EACxC,KAAK,EAAE,MAAM,GACX,SAAS,EAAE,CAeb;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,QAMrE;AAED,6DAA6D;AAC7D,wBAAgB,QAAQ,CACvB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,OAAO,EACf,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC3B,OAAO,CAOT;AAED,4EAA4E;AAC5E,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,CAKlD"}
@@ -1,23 +0,0 @@
1
- import type { ApolloServerOptions } from '@apollo/server';
2
- import { type LocaleKey, type TranslationContext } from '@nxgt/i18n';
3
- import { GraphQLError, type GraphQLErrorOptions } from 'graphql';
4
- import type { MaskError } from 'graphql-yoga';
5
- import type { GraphQLBaseContext } from '../types';
6
- export declare function createGraphQLError(message: string, options?: GraphQLErrorOptions): GraphQLError;
7
- export declare function createFormatError<K extends LocaleKey, C extends GraphQLBaseContext = GraphQLBaseContext>(translate?: (message: K, context?: TranslationContext) => string, production?: boolean): ApolloServerOptions<C>['formatError'];
8
- /**
9
- * Yoga's `maskedErrors.maskError`, taught this repo's exceptions.
10
- *
11
- * Yoga masks anything that is not a `GraphQLError` as "Unexpected error." —
12
- * and `CustomException` extends `Error`, so out of the box a service's
13
- * `notFound()` or `forbidden()` reaches the client as an opaque 500. This
14
- * turns one into a `GraphQLError` with the translated message, the
15
- * exception's `code` in `extensions`, and a matching HTTP status; a Mongoose
16
- * error goes through `castError` first, as `createFormatError` does for
17
- * Apollo. `OryUnavailable` (@nxgt/ory-sdk) becomes a 503 `SERVICE_UNAVAILABLE`,
18
- * never a denial. Everything else is masked exactly as before.
19
- *
20
- * createYoga({ maskedErrors: { maskError: createMaskError(translate) } })
21
- */
22
- export declare function createMaskError<K extends LocaleKey>(translate?: (message: K, context?: TranslationContext) => string): MaskError;
23
- //# sourceMappingURL=errors.utils.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"errors.utils.d.ts","sourceRoot":"","sources":["../../src/utils/errors.utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAK1D,OAAO,EACN,KAAK,SAAS,EACd,KAAK,kBAAkB,EAEvB,MAAM,YAAY,CAAC;AAIpB,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,SAAS,CAAC;AACjE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAEnD,wBAAgB,kBAAkB,CACjC,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,mBAAmB,GAC3B,YAAY,CAEd;AAED,wBAAgB,iBAAiB,CAChC,CAAC,SAAS,SAAS,EACnB,CAAC,SAAS,kBAAkB,GAAG,kBAAkB,EAEjD,SAAS,GAAE,CACV,OAAO,EAAE,CAAC,EACV,OAAO,CAAC,EAAE,kBAAkB,KACxB,MAAsB,EAC3B,UAAU,CAAC,EAAE,OAAO,GAClB,mBAAmB,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAoDvC;AAYD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,SAAS,EAClD,SAAS,GAAE,CACV,OAAO,EAAE,CAAC,EACV,OAAO,CAAC,EAAE,kBAAkB,KACxB,MAAsB,GACzB,SAAS,CAkDX"}
@@ -1,86 +0,0 @@
1
- # The Ory-native counterpart to @policy.
2
- #
3
- # @policy asks whether the caller CARRIES an authority. An Ory principal
4
- # carries none — `toPrincipal` sets `authorities: []` on purpose — because
5
- # Keto answers per OBJECT: "may this subject `view` `Note:<id>`". @check is how
6
- # a schema asks that, and `useKetoChecks(ory)` is what answers it.
7
- #
8
- # It lives here, in the shipped SDL, rather than in SHARED_TYPE_DEFS, so that
9
- # every consumer of SHARED_SCHEMA_PATH sees it — the subgraphs built with
10
- # `buildSubgraphSchema` included, which never load SHARED_TYPE_DEFS.
11
-
12
- """
13
- One question, with the object left as a path instead of a value.
14
- """
15
- input CheckPermission {
16
- """
17
- The Keto namespace, e.g. "Note" — from the stack's OPL document.
18
- """
19
- namespace: String!
20
-
21
- """
22
- The permit asked of it, e.g. "view". A plain relation works too; Keto
23
- answers `false`, not an error, for a name it does not know.
24
- """
25
- permit: String!
26
-
27
- """
28
- Where the object id is read: `args.<path>` or `source.<path>`, dotted paths
29
- allowed. A value that turns out to be a LIST requires the permit on every
30
- element — that is what a mutation taking `ids: [ID!]!` means.
31
- """
32
- id: String = "args.id"
33
- }
34
-
35
- """
36
- What a denial looks like from outside.
37
- """
38
- enum CheckDenial {
39
- """
40
- The same answer as for an id that never existed, so ids cannot be probed.
41
- The default, and the one to keep unless the caller already knows the object
42
- is there.
43
- """
44
- NOT_FOUND
45
-
46
- """
47
- For a second check on an object the caller can already see: a viewer asked
48
- to edit already knows it exists, and "you may not change it" is honest.
49
- """
50
- FORBIDDEN
51
- }
52
-
53
- """
54
- The permission a field requires, in disjunctive normal form: the OUTER list is
55
- OR, the INNER list is AND. `[[A, B], [C]]` reads "(A and B) or C" — the same
56
- shape `@policy(policies: [["ADMIN"]])` uses.
57
-
58
- Repeatable, and evaluated in declaration order, each with its own `onDeny`.
59
- That is how the 404-then-403 ladder is written:
60
-
61
- updateNote(id: ID!, input: UpdateNoteInput!): Note!
62
- @check(permissions: [[{ namespace: "Note", permit: "view" }]])
63
- @check(permissions: [[{ namespace: "Note", permit: "edit" }]], onDeny: FORBIDDEN)
64
-
65
- NOT for a field that answers a LIST the caller is entitled to. "Which notes may
66
- I see" is not a check, it is a Keto query folded into the database filter
67
- before the read. A directive there would have to fetch everything and filter
68
- after, which makes `totalCount` and the cursors lie.
69
- """
70
- directive @check(
71
- permissions: [[CheckPermission!]!]!
72
- onDeny: CheckDenial! = NOT_FOUND
73
-
74
- """
75
- The i18n key the denial carries, e.g. "notes.errors.not-found". Defaults to
76
- `errors.not-found` / `errors.insufficient-permissions`, the shared keys.
77
-
78
- Set it whenever the API's own service layer answers the same refusal with a
79
- domain message. Two layers guard these fields — the directive, and the
80
- `require<M>Access` the service calls — and if they word the same 404
81
- differently, the wording tells a caller WHICH one refused: a generic message
82
- means "you may not", a domain one means "it is gone". That is precisely the
83
- distinction NOT_FOUND exists to hide.
84
- """
85
- message: String
86
- ) repeatable on FIELD_DEFINITION