naystack 1.8.0 → 1.8.1

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.
@@ -770,8 +770,16 @@ function resolver(fn, options) {
770
770
  return {
771
771
  ...options,
772
772
  fn,
773
- authCall: getAuthCaller(fn),
774
- call: options.authorized ? getAuthCaller(fn) : getCaller(fn)
773
+ authCall: getAuthCaller(
774
+ fn,
775
+ options.authorized
776
+ ),
777
+ call: options.authorized ? getAuthCaller(
778
+ fn,
779
+ options.authorized
780
+ ) : getCaller(
781
+ fn
782
+ )
775
783
  };
776
784
  }
777
785
  var getUserId = async () => {
@@ -779,11 +787,13 @@ var getUserId = async () => {
779
787
  const refresh = Cookie.get(REFRESH_COOKIE_NAME)?.value;
780
788
  return refresh ? getUserIdFromRefreshToken(refresh) : null;
781
789
  };
782
- function getAuthCaller(fn) {
790
+ function getAuthCaller(fn, authorized) {
783
791
  return (0, import_react.cache)(
784
792
  async (data) => {
793
+ const userId = await getUserId();
794
+ if (authorized && !userId) return null;
785
795
  const ctx = {
786
- userId: await getUserId(),
796
+ userId,
787
797
  isRefreshID: true
788
798
  };
789
799
  return await fn(ctx, data);
@@ -801,11 +811,13 @@ function getCaller(fn) {
801
811
  }
802
812
  );
803
813
  }
804
- function getFieldAuthCaller(fn) {
814
+ function getFieldAuthCaller(fn, authorized) {
805
815
  return (0, import_react.cache)(
806
816
  async (root, data) => {
817
+ const userId = await getUserId();
818
+ if (authorized && !userId) return null;
807
819
  const ctx = {
808
- userId: await getUserId(),
820
+ userId,
809
821
  isRefreshID: true
810
822
  };
811
823
  return await fn(root, ctx, data);
@@ -827,8 +839,8 @@ function field(fn, options) {
827
839
  return {
828
840
  ...options,
829
841
  fn,
830
- authCall: getFieldAuthCaller(fn),
831
- call: options.authorized ? getFieldAuthCaller(fn) : getFieldCaller(
842
+ authCall: getFieldAuthCaller(fn, options.authorized),
843
+ call: options.authorized ? getFieldAuthCaller(fn, options.authorized) : getFieldCaller(
832
844
  fn
833
845
  )
834
846
  };
@@ -903,8 +915,12 @@ function FieldLibrary(type, queries) {
903
915
  const def = queries[key];
904
916
  if (!def) continue;
905
917
  Object.defineProperty(GeneratedResolver.prototype, key, {
918
+ // `alwaysResolve` fields must run `fn` even when the parent pre-hydrated
919
+ // `root[key]`: they gate on `ctx`, and the short-circuit would return the
920
+ // pre-hydrated value without ever applying that check. Such a resolver is
921
+ // responsible for reusing `root[key]` itself, so the N+1 win is preserved.
906
922
  value: async function(root, ctx, input) {
907
- if (root[key]) return root[key];
923
+ if (!def.alwaysResolve && root[key]) return root[key];
908
924
  return def.fn(root, ctx, input);
909
925
  },
910
926
  writable: false
@@ -764,8 +764,16 @@ function resolver(fn, options) {
764
764
  return {
765
765
  ...options,
766
766
  fn,
767
- authCall: getAuthCaller(fn),
768
- call: options.authorized ? getAuthCaller(fn) : getCaller(fn)
767
+ authCall: getAuthCaller(
768
+ fn,
769
+ options.authorized
770
+ ),
771
+ call: options.authorized ? getAuthCaller(
772
+ fn,
773
+ options.authorized
774
+ ) : getCaller(
775
+ fn
776
+ )
769
777
  };
770
778
  }
771
779
  var getUserId = async () => {
@@ -773,11 +781,13 @@ var getUserId = async () => {
773
781
  const refresh = Cookie.get(REFRESH_COOKIE_NAME)?.value;
774
782
  return refresh ? getUserIdFromRefreshToken(refresh) : null;
775
783
  };
776
- function getAuthCaller(fn) {
784
+ function getAuthCaller(fn, authorized) {
777
785
  return cache(
778
786
  async (data) => {
787
+ const userId = await getUserId();
788
+ if (authorized && !userId) return null;
779
789
  const ctx = {
780
- userId: await getUserId(),
790
+ userId,
781
791
  isRefreshID: true
782
792
  };
783
793
  return await fn(ctx, data);
@@ -795,11 +805,13 @@ function getCaller(fn) {
795
805
  }
796
806
  );
797
807
  }
798
- function getFieldAuthCaller(fn) {
808
+ function getFieldAuthCaller(fn, authorized) {
799
809
  return cache(
800
810
  async (root, data) => {
811
+ const userId = await getUserId();
812
+ if (authorized && !userId) return null;
801
813
  const ctx = {
802
- userId: await getUserId(),
814
+ userId,
803
815
  isRefreshID: true
804
816
  };
805
817
  return await fn(root, ctx, data);
@@ -821,8 +833,8 @@ function field(fn, options) {
821
833
  return {
822
834
  ...options,
823
835
  fn,
824
- authCall: getFieldAuthCaller(fn),
825
- call: options.authorized ? getFieldAuthCaller(fn) : getFieldCaller(
836
+ authCall: getFieldAuthCaller(fn, options.authorized),
837
+ call: options.authorized ? getFieldAuthCaller(fn, options.authorized) : getFieldCaller(
826
838
  fn
827
839
  )
828
840
  };
@@ -897,8 +909,12 @@ function FieldLibrary(type, queries) {
897
909
  const def = queries[key];
898
910
  if (!def) continue;
899
911
  Object.defineProperty(GeneratedResolver.prototype, key, {
912
+ // `alwaysResolve` fields must run `fn` even when the parent pre-hydrated
913
+ // `root[key]`: they gate on `ctx`, and the short-circuit would return the
914
+ // pre-hydrated value without ever applying that check. Such a resolver is
915
+ // responsible for reusing `root[key]` itself, so the N+1 win is preserved.
900
916
  value: async function(root, ctx, input) {
901
- if (root[key]) return root[key];
917
+ if (!def.alwaysResolve && root[key]) return root[key];
902
918
  return def.fn(root, ctx, input);
903
919
  },
904
920
  writable: false
@@ -117,8 +117,16 @@ function resolver(fn, options) {
117
117
  return {
118
118
  ...options,
119
119
  fn,
120
- authCall: getAuthCaller(fn),
121
- call: options.authorized ? getAuthCaller(fn) : getCaller(fn)
120
+ authCall: getAuthCaller(
121
+ fn,
122
+ options.authorized
123
+ ),
124
+ call: options.authorized ? getAuthCaller(
125
+ fn,
126
+ options.authorized
127
+ ) : getCaller(
128
+ fn
129
+ )
122
130
  };
123
131
  }
124
132
  var getUserId = async () => {
@@ -126,11 +134,13 @@ var getUserId = async () => {
126
134
  const refresh = Cookie.get(REFRESH_COOKIE_NAME)?.value;
127
135
  return refresh ? getUserIdFromRefreshToken(refresh) : null;
128
136
  };
129
- function getAuthCaller(fn) {
137
+ function getAuthCaller(fn, authorized) {
130
138
  return (0, import_react.cache)(
131
139
  async (data) => {
140
+ const userId = await getUserId();
141
+ if (authorized && !userId) return null;
132
142
  const ctx = {
133
- userId: await getUserId(),
143
+ userId,
134
144
  isRefreshID: true
135
145
  };
136
146
  return await fn(ctx, data);
@@ -148,11 +158,13 @@ function getCaller(fn) {
148
158
  }
149
159
  );
150
160
  }
151
- function getFieldAuthCaller(fn) {
161
+ function getFieldAuthCaller(fn, authorized) {
152
162
  return (0, import_react.cache)(
153
163
  async (root, data) => {
164
+ const userId = await getUserId();
165
+ if (authorized && !userId) return null;
154
166
  const ctx = {
155
- userId: await getUserId(),
167
+ userId,
156
168
  isRefreshID: true
157
169
  };
158
170
  return await fn(root, ctx, data);
@@ -174,8 +186,8 @@ function field(fn, options) {
174
186
  return {
175
187
  ...options,
176
188
  fn,
177
- authCall: getFieldAuthCaller(fn),
178
- call: options.authorized ? getFieldAuthCaller(fn) : getFieldCaller(
189
+ authCall: getFieldAuthCaller(fn, options.authorized),
190
+ call: options.authorized ? getFieldAuthCaller(fn, options.authorized) : getFieldCaller(
179
191
  fn
180
192
  )
181
193
  };
@@ -250,8 +262,12 @@ function FieldLibrary(type, queries) {
250
262
  const def = queries[key];
251
263
  if (!def) continue;
252
264
  Object.defineProperty(GeneratedResolver.prototype, key, {
265
+ // `alwaysResolve` fields must run `fn` even when the parent pre-hydrated
266
+ // `root[key]`: they gate on `ctx`, and the short-circuit would return the
267
+ // pre-hydrated value without ever applying that check. Such a resolver is
268
+ // responsible for reusing `root[key]` itself, so the N+1 win is preserved.
253
269
  value: async function(root, ctx, input) {
254
- if (root[key]) return root[key];
270
+ if (!def.alwaysResolve && root[key]) return root[key];
255
271
  return def.fn(root, ctx, input);
256
272
  },
257
273
  writable: false
@@ -26,6 +26,16 @@ type ParsedGQLTypeWithArray<T, MergeNullUndefined extends boolean> = T extends A
26
26
  type ParsedGQLTypeWithNullability<T, IsNullable extends boolean, MergeNullUndefined extends boolean> = IsNullable extends true ? ParsedGQLTypeWithArray<T, MergeNullUndefined> | null | undefined : ParsedGQLTypeWithArray<T, MergeNullUndefined>;
27
27
  /** Allows resolver to return T or Promise<T>. */
28
28
  type Promisify<T> = T | Promise<T>;
29
+ /**
30
+ * Return type of `.authCall()` / `.call()` for a definition.
31
+ *
32
+ * `authorized: true` promises the resolver an `AuthorizedContext` (non-null
33
+ * `userId`). Server-side callers can't be forced to hold a session, so when
34
+ * there's no refresh cookie the only way to keep that promise is to not call the
35
+ * resolver at all — hence the added `null`, which surfaces the logged-out case in
36
+ * the types instead of passing a null `userId` through the cast.
37
+ */
38
+ type AuthCallReturn<IsAuth extends boolean, R> = IsAuth extends true ? Awaited<R> | null : Awaited<R>;
29
39
  /** Base for query/field definition (output, input, options). */
30
40
  interface BaseDefinition<T, U, IsAuth extends boolean = false, OutputNullable extends boolean = false, InputNullable extends boolean = false> {
31
41
  output: T;
@@ -42,10 +52,16 @@ interface BaseDefinition<T, U, IsAuth extends boolean = false, OutputNullable ex
42
52
  */
43
53
  interface QueryDefinition<T, U, IsAuth extends boolean = false, OutputNullable extends boolean = false, InputNullable extends boolean = false, R extends Promisify<ParsedGQLTypeWithNullability<T, OutputNullable, true>> = Promisify<ParsedGQLTypeWithNullability<T, OutputNullable, true>>> extends BaseDefinition<T, U, IsAuth, OutputNullable, InputNullable> {
44
54
  fn: (ctx: IsAuth extends true ? AuthorizedContext : Context, data: ParsedGQLTypeWithNullability<U, InputNullable, false>) => R;
45
- /** Calls the resolver server-side. For authorized queries, reads the refresh cookie; for non-authorized, passes null userId. */
46
- call: (data: ParsedGQLTypeWithNullability<U, InputNullable, false>) => Promise<Awaited<R>>;
47
- /** Calls the resolver server-side with authentication (always reads the refresh cookie). Use in Server Components. */
48
- authCall: (data: ParsedGQLTypeWithNullability<U, InputNullable, false>) => Promise<Awaited<R>>;
55
+ /**
56
+ * Calls the resolver server-side. For authorized queries, reads the refresh cookie; for non-authorized, passes null userId.
57
+ * Resolves to `null` when `authorized: true` and there is no session.
58
+ */
59
+ call: (data: ParsedGQLTypeWithNullability<U, InputNullable, false>) => Promise<AuthCallReturn<IsAuth, R>>;
60
+ /**
61
+ * Calls the resolver server-side with authentication (always reads the refresh cookie). Use in Server Components.
62
+ * Resolves to `null` when `authorized: true` and there is no session — guard it, or gate the page ahead of it.
63
+ */
64
+ authCall: (data: ParsedGQLTypeWithNullability<U, InputNullable, false>) => Promise<AuthCallReturn<IsAuth, R>>;
49
65
  mutation?: boolean;
50
66
  }
51
67
  /**
@@ -109,10 +125,26 @@ declare function resolver<T, U, IsAuth extends boolean = false, OutputNullable e
109
125
  */
110
126
  interface FieldResolverDefinition<T, U, Root, IsAuth extends boolean = false, OutputNullable extends boolean = false, InputNullable extends boolean = false, R extends Promisify<ParsedGQLTypeWithNullability<T, OutputNullable, true>> = Promisify<ParsedGQLTypeWithNullability<T, OutputNullable, true>>> extends BaseDefinition<T, U, IsAuth, OutputNullable, InputNullable> {
111
127
  fn: (root: Root, ctx: IsAuth extends true ? AuthorizedContext : Context, data: ParsedGQLTypeWithNullability<U, InputNullable, false>) => R;
112
- /** Calls the field resolver server-side. For authorized fields, reads the refresh cookie. */
113
- call: (root: Root, data: ParsedGQLTypeWithNullability<U, InputNullable, false>) => Promise<Awaited<R>>;
114
- /** Calls the field resolver server-side with authentication (always reads the refresh cookie). */
115
- authCall: (root: Root, data: ParsedGQLTypeWithNullability<U, InputNullable, false>) => Promise<Awaited<R>>;
128
+ /**
129
+ * Calls the field resolver server-side. For authorized fields, reads the refresh cookie.
130
+ * Resolves to `null` when `authorized: true` and there is no session.
131
+ */
132
+ call: (root: Root, data: ParsedGQLTypeWithNullability<U, InputNullable, false>) => Promise<AuthCallReturn<IsAuth, R>>;
133
+ /**
134
+ * Calls the field resolver server-side with authentication (always reads the refresh cookie).
135
+ * Resolves to `null` when `authorized: true` and there is no session.
136
+ */
137
+ authCall: (root: Root, data: ParsedGQLTypeWithNullability<U, InputNullable, false>) => Promise<AuthCallReturn<IsAuth, R>>;
138
+ /**
139
+ * Skip `FieldLibrary`'s short-circuit, which returns a pre-hydrated `root[key]`
140
+ * without ever calling this resolver.
141
+ *
142
+ * Set this on any field that gates on `ctx` — an auth check inside the resolver
143
+ * is silently bypassed the moment a parent query selects the field's value into
144
+ * its own columns. The resolver stays responsible for reusing `root[key]` when
145
+ * present, so hydration still avoids the N+1.
146
+ */
147
+ alwaysResolve?: boolean;
116
148
  }
117
149
  /**
118
150
  * Defines a type-graphql field resolver with typed root, context, and input.
@@ -26,6 +26,16 @@ type ParsedGQLTypeWithArray<T, MergeNullUndefined extends boolean> = T extends A
26
26
  type ParsedGQLTypeWithNullability<T, IsNullable extends boolean, MergeNullUndefined extends boolean> = IsNullable extends true ? ParsedGQLTypeWithArray<T, MergeNullUndefined> | null | undefined : ParsedGQLTypeWithArray<T, MergeNullUndefined>;
27
27
  /** Allows resolver to return T or Promise<T>. */
28
28
  type Promisify<T> = T | Promise<T>;
29
+ /**
30
+ * Return type of `.authCall()` / `.call()` for a definition.
31
+ *
32
+ * `authorized: true` promises the resolver an `AuthorizedContext` (non-null
33
+ * `userId`). Server-side callers can't be forced to hold a session, so when
34
+ * there's no refresh cookie the only way to keep that promise is to not call the
35
+ * resolver at all — hence the added `null`, which surfaces the logged-out case in
36
+ * the types instead of passing a null `userId` through the cast.
37
+ */
38
+ type AuthCallReturn<IsAuth extends boolean, R> = IsAuth extends true ? Awaited<R> | null : Awaited<R>;
29
39
  /** Base for query/field definition (output, input, options). */
30
40
  interface BaseDefinition<T, U, IsAuth extends boolean = false, OutputNullable extends boolean = false, InputNullable extends boolean = false> {
31
41
  output: T;
@@ -42,10 +52,16 @@ interface BaseDefinition<T, U, IsAuth extends boolean = false, OutputNullable ex
42
52
  */
43
53
  interface QueryDefinition<T, U, IsAuth extends boolean = false, OutputNullable extends boolean = false, InputNullable extends boolean = false, R extends Promisify<ParsedGQLTypeWithNullability<T, OutputNullable, true>> = Promisify<ParsedGQLTypeWithNullability<T, OutputNullable, true>>> extends BaseDefinition<T, U, IsAuth, OutputNullable, InputNullable> {
44
54
  fn: (ctx: IsAuth extends true ? AuthorizedContext : Context, data: ParsedGQLTypeWithNullability<U, InputNullable, false>) => R;
45
- /** Calls the resolver server-side. For authorized queries, reads the refresh cookie; for non-authorized, passes null userId. */
46
- call: (data: ParsedGQLTypeWithNullability<U, InputNullable, false>) => Promise<Awaited<R>>;
47
- /** Calls the resolver server-side with authentication (always reads the refresh cookie). Use in Server Components. */
48
- authCall: (data: ParsedGQLTypeWithNullability<U, InputNullable, false>) => Promise<Awaited<R>>;
55
+ /**
56
+ * Calls the resolver server-side. For authorized queries, reads the refresh cookie; for non-authorized, passes null userId.
57
+ * Resolves to `null` when `authorized: true` and there is no session.
58
+ */
59
+ call: (data: ParsedGQLTypeWithNullability<U, InputNullable, false>) => Promise<AuthCallReturn<IsAuth, R>>;
60
+ /**
61
+ * Calls the resolver server-side with authentication (always reads the refresh cookie). Use in Server Components.
62
+ * Resolves to `null` when `authorized: true` and there is no session — guard it, or gate the page ahead of it.
63
+ */
64
+ authCall: (data: ParsedGQLTypeWithNullability<U, InputNullable, false>) => Promise<AuthCallReturn<IsAuth, R>>;
49
65
  mutation?: boolean;
50
66
  }
51
67
  /**
@@ -109,10 +125,26 @@ declare function resolver<T, U, IsAuth extends boolean = false, OutputNullable e
109
125
  */
110
126
  interface FieldResolverDefinition<T, U, Root, IsAuth extends boolean = false, OutputNullable extends boolean = false, InputNullable extends boolean = false, R extends Promisify<ParsedGQLTypeWithNullability<T, OutputNullable, true>> = Promisify<ParsedGQLTypeWithNullability<T, OutputNullable, true>>> extends BaseDefinition<T, U, IsAuth, OutputNullable, InputNullable> {
111
127
  fn: (root: Root, ctx: IsAuth extends true ? AuthorizedContext : Context, data: ParsedGQLTypeWithNullability<U, InputNullable, false>) => R;
112
- /** Calls the field resolver server-side. For authorized fields, reads the refresh cookie. */
113
- call: (root: Root, data: ParsedGQLTypeWithNullability<U, InputNullable, false>) => Promise<Awaited<R>>;
114
- /** Calls the field resolver server-side with authentication (always reads the refresh cookie). */
115
- authCall: (root: Root, data: ParsedGQLTypeWithNullability<U, InputNullable, false>) => Promise<Awaited<R>>;
128
+ /**
129
+ * Calls the field resolver server-side. For authorized fields, reads the refresh cookie.
130
+ * Resolves to `null` when `authorized: true` and there is no session.
131
+ */
132
+ call: (root: Root, data: ParsedGQLTypeWithNullability<U, InputNullable, false>) => Promise<AuthCallReturn<IsAuth, R>>;
133
+ /**
134
+ * Calls the field resolver server-side with authentication (always reads the refresh cookie).
135
+ * Resolves to `null` when `authorized: true` and there is no session.
136
+ */
137
+ authCall: (root: Root, data: ParsedGQLTypeWithNullability<U, InputNullable, false>) => Promise<AuthCallReturn<IsAuth, R>>;
138
+ /**
139
+ * Skip `FieldLibrary`'s short-circuit, which returns a pre-hydrated `root[key]`
140
+ * without ever calling this resolver.
141
+ *
142
+ * Set this on any field that gates on `ctx` — an auth check inside the resolver
143
+ * is silently bypassed the moment a parent query selects the field's value into
144
+ * its own columns. The resolver stays responsible for reusing `root[key]` when
145
+ * present, so hydration still avoids the N+1.
146
+ */
147
+ alwaysResolve?: boolean;
116
148
  }
117
149
  /**
118
150
  * Defines a type-graphql field resolver with typed root, context, and input.
@@ -102,8 +102,16 @@ function resolver(fn, options) {
102
102
  return {
103
103
  ...options,
104
104
  fn,
105
- authCall: getAuthCaller(fn),
106
- call: options.authorized ? getAuthCaller(fn) : getCaller(fn)
105
+ authCall: getAuthCaller(
106
+ fn,
107
+ options.authorized
108
+ ),
109
+ call: options.authorized ? getAuthCaller(
110
+ fn,
111
+ options.authorized
112
+ ) : getCaller(
113
+ fn
114
+ )
107
115
  };
108
116
  }
109
117
  var getUserId = async () => {
@@ -111,11 +119,13 @@ var getUserId = async () => {
111
119
  const refresh = Cookie.get(REFRESH_COOKIE_NAME)?.value;
112
120
  return refresh ? getUserIdFromRefreshToken(refresh) : null;
113
121
  };
114
- function getAuthCaller(fn) {
122
+ function getAuthCaller(fn, authorized) {
115
123
  return cache(
116
124
  async (data) => {
125
+ const userId = await getUserId();
126
+ if (authorized && !userId) return null;
117
127
  const ctx = {
118
- userId: await getUserId(),
128
+ userId,
119
129
  isRefreshID: true
120
130
  };
121
131
  return await fn(ctx, data);
@@ -133,11 +143,13 @@ function getCaller(fn) {
133
143
  }
134
144
  );
135
145
  }
136
- function getFieldAuthCaller(fn) {
146
+ function getFieldAuthCaller(fn, authorized) {
137
147
  return cache(
138
148
  async (root, data) => {
149
+ const userId = await getUserId();
150
+ if (authorized && !userId) return null;
139
151
  const ctx = {
140
- userId: await getUserId(),
152
+ userId,
141
153
  isRefreshID: true
142
154
  };
143
155
  return await fn(root, ctx, data);
@@ -159,8 +171,8 @@ function field(fn, options) {
159
171
  return {
160
172
  ...options,
161
173
  fn,
162
- authCall: getFieldAuthCaller(fn),
163
- call: options.authorized ? getFieldAuthCaller(fn) : getFieldCaller(
174
+ authCall: getFieldAuthCaller(fn, options.authorized),
175
+ call: options.authorized ? getFieldAuthCaller(fn, options.authorized) : getFieldCaller(
164
176
  fn
165
177
  )
166
178
  };
@@ -235,8 +247,12 @@ function FieldLibrary(type, queries) {
235
247
  const def = queries[key];
236
248
  if (!def) continue;
237
249
  Object.defineProperty(GeneratedResolver.prototype, key, {
250
+ // `alwaysResolve` fields must run `fn` even when the parent pre-hydrated
251
+ // `root[key]`: they gate on `ctx`, and the short-circuit would return the
252
+ // pre-hydrated value without ever applying that check. Such a resolver is
253
+ // responsible for reusing `root[key]` itself, so the N+1 win is preserved.
238
254
  value: async function(root, ctx, input) {
239
- if (root[key]) return root[key];
255
+ if (!def.alwaysResolve && root[key]) return root[key];
240
256
  return def.fn(root, ctx, input);
241
257
  },
242
258
  writable: false
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "naystack",
3
- "version": "1.8.0",
3
+ "version": "1.8.1",
4
4
  "description": "A stack built with Next + GraphQL + S3 + Auth",
5
5
  "main": "dist/index.cjs.js",
6
6
  "module": "dist/index.esm.js",