houdini 2.0.0-next.35 → 2.0.0-next.37

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.
package/build/cmd/init.js CHANGED
@@ -472,12 +472,12 @@ async function packageJSON(targetPath, frameworkInfo) {
472
472
  }
473
473
  packageJSON2.devDependencies = {
474
474
  ...packageJSON2.devDependencies,
475
- houdini: "^2.0.0-next.35"
475
+ houdini: "^2.0.0-next.37"
476
476
  };
477
477
  if (frameworkInfo.framework === "svelte" || frameworkInfo.framework === "kit") {
478
478
  packageJSON2.devDependencies = {
479
479
  ...packageJSON2.devDependencies,
480
- "houdini-svelte": "^3.0.0-next.33"
480
+ "houdini-svelte": "^3.0.0-next.35"
481
481
  };
482
482
  } else {
483
483
  throw new Error(`Unmanaged framework: "${JSON.stringify(frameworkInfo)}"`);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "houdini",
3
- "version": "2.0.0-next.35",
3
+ "version": "2.0.0-next.37",
4
4
  "description": "The disappearing GraphQL clients",
5
5
  "keywords": [
6
6
  "typescript",
@@ -10,6 +10,7 @@ export type RouteParam = {
10
10
  type?: string;
11
11
  };
12
12
  export type ParamMatcher = (param: string) => boolean;
13
+ export declare function find_prefix_match<_ComponentType>(manifest: RouterManifest<_ComponentType>, url: string): RouterPageManifest<_ComponentType> | null;
13
14
  export declare function find_match<_ComponentType>(config: ConfigFile, manifest: RouterManifest<_ComponentType>, current: string, allowNull: true): [RouterPageManifest<_ComponentType> | null, GraphQLVariables];
14
15
  export declare function find_match<_ComponentType>(config: ConfigFile, manifest: RouterManifest<_ComponentType>, current: string, allowNull?: false): [RouterPageManifest<_ComponentType>, GraphQLVariables];
15
16
  /**
@@ -1,4 +1,30 @@
1
1
  const param_pattern = /^(\[)?(\.\.\.)?(\w+)(?:=(\w+))?(\])?$/;
2
+ function find_prefix_match(manifest, url) {
3
+ const urlSegments = url.split("/").filter(Boolean);
4
+ let best = null;
5
+ let bestCount = -1;
6
+ for (const page of Object.values(manifest.pages)) {
7
+ const pageSegments = page.url.split("/").filter(Boolean);
8
+ let staticCount = 0;
9
+ for (const seg of pageSegments) {
10
+ if (seg.startsWith("[") || seg.startsWith("(") || seg === "*") break;
11
+ staticCount++;
12
+ }
13
+ if (staticCount > urlSegments.length) continue;
14
+ let matches = true;
15
+ for (let i = 0; i < staticCount; i++) {
16
+ if (pageSegments[i] !== urlSegments[i]) {
17
+ matches = false;
18
+ break;
19
+ }
20
+ }
21
+ if (matches && staticCount > bestCount) {
22
+ bestCount = staticCount;
23
+ best = page;
24
+ }
25
+ }
26
+ return best;
27
+ }
2
28
  function find_match(config, manifest, current, allowNull = true) {
3
29
  let match = null;
4
30
  let matchVariables = null;
@@ -161,6 +187,7 @@ function parseScalar(config, type, value) {
161
187
  export {
162
188
  exec,
163
189
  find_match,
190
+ find_prefix_match,
164
191
  get_route_segments,
165
192
  parseScalar,
166
193
  parse_page_pattern
@@ -17,6 +17,7 @@ export declare function _serverHandler<ComponentType = unknown>({ schema, server
17
17
  on_render: (args: {
18
18
  url: string;
19
19
  match: RouterPageManifest<ComponentType> | null;
20
+ is404: boolean;
20
21
  manifest: RouterManifest<unknown>;
21
22
  session: App.Session;
22
23
  componentCache: Record<string, any>;
@@ -3,7 +3,7 @@ import {
3
3
  YogaServer
4
4
  } from "graphql-yoga";
5
5
  import { serialize as encodeCookie } from "./cookies.js";
6
- import { find_match } from "./match.js";
6
+ import { find_match, find_prefix_match } from "./match.js";
7
7
  import { get_session, handle_request, session_cookie_name } from "./session.js";
8
8
  function _serverHandler({
9
9
  schema,
@@ -70,10 +70,13 @@ function _serverHandler({
70
70
  if (authResponse) {
71
71
  return authResponse;
72
72
  }
73
- const [match] = find_match(config_file, manifest, url);
73
+ const [exactMatch] = find_match(config_file, manifest, url);
74
+ const is404 = !exactMatch;
75
+ const match = exactMatch ?? find_prefix_match(manifest, url);
74
76
  const rendered = await on_render({
75
77
  url,
76
78
  match,
79
+ is404,
77
80
  session: await get_session(request.headers, session_keys),
78
81
  manifest,
79
82
  componentCache
@@ -10,7 +10,7 @@ function evaluateKey(key, variables = null) {
10
10
  continue;
11
11
  }
12
12
  const value = variables?.[varName.slice(1)];
13
- evaluated += typeof value !== "undefined" ? JSON.stringify(value) : "undefined";
13
+ evaluated += JSON.stringify(value ?? null);
14
14
  varName = "";
15
15
  }
16
16
  if (char === "$" && !inString) {
@@ -65,6 +65,7 @@ export type ClientPluginContext = {
65
65
  disableWrite?: boolean;
66
66
  disableRead?: boolean;
67
67
  disableSubscriptions?: boolean;
68
+ disablePartial?: boolean;
68
69
  applyUpdates?: string[];
69
70
  serverSideFallback?: boolean;
70
71
  };
@@ -397,7 +397,7 @@ class ClientPluginContextWrapper {
397
397
  const hasChanged = Object.keys(changed).length > 0 || firstInit;
398
398
  if (hasChanged) {
399
399
  const newVariables = {
400
- ...ctx.stuff.inputs?.marshaled,
400
+ ...source.stuff.inputs?.marshaled,
401
401
  ...marshalInputs({
402
402
  artifact,
403
403
  input: changed,
@@ -1,12 +1,14 @@
1
1
  import type { SendParams } from './documentStore.js';
2
2
  import type { CursorHandlers, FetchFn, GraphQLObject, GraphQLVariables, QueryArtifact, QueryResult, FetchParams } from './types.js';
3
- export declare function cursorHandlers<_Data extends GraphQLObject, _Input extends GraphQLVariables | null | undefined>({ artifact, fetchUpdate: parentFetchUpdate, fetch: parentFetch, getState, getVariables, getSession, }: {
3
+ export declare function cursorHandlers<_Data extends GraphQLObject, _Input extends GraphQLVariables | null | undefined>({ artifact, fetchUpdate: parentFetchUpdate, fetch: parentFetch, getState, getVariables, getSession, previousCursors, nextCursors, }: {
4
4
  artifact: QueryArtifact;
5
5
  getState: () => _Data | null;
6
6
  getVariables: () => NonNullable<_Input>;
7
7
  getSession: () => Promise<App.Session>;
8
8
  fetch: FetchFn<_Data, _Input>;
9
9
  fetchUpdate: (arg: SendParams, updates: string[]) => ReturnType<FetchFn<_Data, _Input>>;
10
+ previousCursors?: (string | null)[];
11
+ nextCursors?: (string | null)[];
10
12
  }): CursorHandlers<_Data, _Input>;
11
13
  export declare function offsetHandlers<_Data extends GraphQLObject, _Input extends GraphQLVariables | null | undefined>({ artifact, storeName: _storeName, getState, getVariables, fetch: parentFetch, fetchUpdate: parentFetchUpdate, getSession, }: {
12
14
  artifact: QueryArtifact;
@@ -1,5 +1,6 @@
1
1
  import { deepEquals } from "./deepEquals.js";
2
2
  import { countPage, extractPageInfo, missingPageSizeError } from "./pageInfo.js";
3
+ import { defaultConfigValues, getCurrentConfig, keyFieldsForType } from "./config.js";
3
4
  import { CachePolicy, DataSource } from "./types.js";
4
5
  function cursorHandlers({
5
6
  artifact,
@@ -7,8 +8,23 @@ function cursorHandlers({
7
8
  fetch: parentFetch,
8
9
  getState,
9
10
  getVariables,
10
- getSession
11
+ getSession,
12
+ previousCursors = [],
13
+ nextCursors = []
11
14
  }) {
15
+ const targetType = artifact.refetch?.targetType;
16
+ const getEntityVars = () => {
17
+ if (!targetType || targetType === "Query") return {};
18
+ const config = defaultConfigValues(getCurrentConfig());
19
+ const typeConfig = config.types?.[targetType];
20
+ const state = getState();
21
+ if (!state) return {};
22
+ if (typeConfig?.resolve?.arguments) {
23
+ return typeConfig.resolve.arguments(state) ?? {};
24
+ }
25
+ const keys = keyFieldsForType(config, targetType);
26
+ return Object.fromEntries(keys.map((key) => [key, state[key]]));
27
+ };
12
28
  const loadPage = async ({
13
29
  pageSizeVar,
14
30
  input,
@@ -18,6 +34,8 @@ function cursorHandlers({
18
34
  where
19
35
  }) => {
20
36
  const loadVariables = {
37
+ ...artifact.input?.defaults ?? {},
38
+ ...getEntityVars(),
21
39
  ...getVariables(),
22
40
  ...input
23
41
  };
@@ -25,12 +43,13 @@ function cursorHandlers({
25
43
  throw missingPageSizeError(functionName);
26
44
  }
27
45
  const isSinglePage = artifact.refetch?.mode === "SinglePage";
46
+ const policy = isSinglePage ? artifact.policy : CachePolicy.NetworkOnly;
28
47
  return (isSinglePage ? parentFetch : parentFetchUpdate)(
29
48
  {
30
49
  variables: loadVariables,
31
50
  fetch,
32
51
  metadata,
33
- policy: isSinglePage ? artifact.policy : CachePolicy.NetworkOnly,
52
+ policy,
34
53
  session: await getSession()
35
54
  },
36
55
  isSinglePage ? [] : [where === "start" ? "prepend" : "append"]
@@ -46,6 +65,51 @@ function cursorHandlers({
46
65
  fetch,
47
66
  metadata
48
67
  } = {}) => {
68
+ const isSinglePage = artifact.refetch?.mode === "SinglePage";
69
+ const direction = artifact.refetch?.direction;
70
+ if (isSinglePage && direction === "backward") {
71
+ if (nextCursors.length === 0) {
72
+ return Promise.resolve({
73
+ data: getState(),
74
+ errors: null,
75
+ fetching: false,
76
+ partial: false,
77
+ stale: false,
78
+ source: DataSource.Cache,
79
+ variables: getVariables()
80
+ });
81
+ }
82
+ const beforeCursor = nextCursors.pop();
83
+ return loadPage({
84
+ pageSizeVar: "last",
85
+ functionName: "loadNextPage",
86
+ input: {
87
+ before: beforeCursor,
88
+ last: first ?? artifact.refetch.pageSize,
89
+ first: null,
90
+ after: null
91
+ },
92
+ fetch,
93
+ metadata,
94
+ where: "start"
95
+ });
96
+ }
97
+ if (isSinglePage && direction === "both" && nextCursors.length > 0) {
98
+ const beforeCursor = nextCursors.pop();
99
+ return loadPage({
100
+ pageSizeVar: "last",
101
+ functionName: "loadNextPage",
102
+ input: {
103
+ before: beforeCursor,
104
+ last: first ?? artifact.refetch.pageSize,
105
+ first: null,
106
+ after: null
107
+ },
108
+ fetch,
109
+ metadata,
110
+ where: "start"
111
+ });
112
+ }
49
113
  const currentPageInfo = getPageInfo();
50
114
  if (!currentPageInfo.hasNextPage) {
51
115
  return Promise.resolve({
@@ -58,6 +122,9 @@ function cursorHandlers({
58
122
  variables: getVariables()
59
123
  });
60
124
  }
125
+ if (isSinglePage && (direction === "forward" || direction === "both")) {
126
+ previousCursors.push(getVariables()?.after ?? null);
127
+ }
61
128
  const input = {
62
129
  first: first ?? artifact.refetch.pageSize,
63
130
  after: after ?? currentPageInfo.endCursor,
@@ -79,6 +146,35 @@ function cursorHandlers({
79
146
  fetch,
80
147
  metadata
81
148
  } = {}) => {
149
+ const isSinglePage = artifact.refetch?.mode === "SinglePage";
150
+ const direction = artifact.refetch?.direction;
151
+ if (isSinglePage && (direction === "forward" || direction === "both") && previousCursors.length > 0) {
152
+ const afterCursor = previousCursors.pop();
153
+ return loadPage({
154
+ pageSizeVar: "first",
155
+ functionName: "loadPreviousPage",
156
+ input: {
157
+ after: afterCursor,
158
+ first: last ?? artifact.refetch.pageSize,
159
+ before: null,
160
+ last: null
161
+ },
162
+ fetch,
163
+ metadata,
164
+ where: "end"
165
+ });
166
+ }
167
+ if (isSinglePage && direction === "forward") {
168
+ return Promise.resolve({
169
+ data: getState(),
170
+ errors: null,
171
+ fetching: false,
172
+ partial: false,
173
+ stale: false,
174
+ source: DataSource.Cache,
175
+ variables: getVariables()
176
+ });
177
+ }
82
178
  const currentPageInfo = getPageInfo();
83
179
  if (!currentPageInfo.hasPreviousPage) {
84
180
  return Promise.resolve({
@@ -91,6 +187,11 @@ function cursorHandlers({
91
187
  variables: getVariables()
92
188
  });
93
189
  }
190
+ const pVars = getVariables();
191
+ const isForwardPage = pVars?.first != null || pVars?.after != null;
192
+ if (isSinglePage && (direction === "backward" || direction === "both") && !isForwardPage) {
193
+ nextCursors.push(pVars?.before ?? null);
194
+ }
94
195
  const input = {
95
196
  before: before ?? currentPageInfo.startCursor,
96
197
  last: last ?? artifact.refetch.pageSize,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "houdini",
3
- "version": "2.0.0-next.35",
3
+ "version": "2.0.0-next.37",
4
4
  "description": "The disappearing GraphQL clients",
5
5
  "keywords": [
6
6
  "typescript",
@@ -50,7 +50,7 @@
50
50
  "recast": "^0.23.11",
51
51
  "sql.js": "^1.14.1",
52
52
  "ws": "^8.21.0",
53
- "houdini-core": "^2.0.0-next.22"
53
+ "houdini-core": "^2.0.0-next.24"
54
54
  },
55
55
  "peerDependencies": {
56
56
  "graphql": ">=16",