houdini 0.17.7 → 0.17.9

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 (48) hide show
  1. package/.turbo/turbo-compile.log +2 -2
  2. package/.turbo/turbo-typedefs.log +2 -2
  3. package/CHANGELOG.md +9 -1
  4. package/build/cmd-cjs/index.js +124 -38
  5. package/build/cmd-esm/index.js +124 -38
  6. package/build/codegen-cjs/index.js +112 -36
  7. package/build/codegen-esm/index.js +112 -36
  8. package/build/lib/config.d.ts +3 -0
  9. package/build/lib-cjs/index.js +31 -12
  10. package/build/lib-esm/index.js +31 -12
  11. package/build/runtime/cache/cache.d.ts +1 -1
  12. package/build/runtime/cache/lists.d.ts +1 -1
  13. package/build/runtime/lib/config.d.ts +10 -2
  14. package/build/runtime/lib/types.d.ts +1 -0
  15. package/build/runtime-cjs/cache/cache.d.ts +1 -1
  16. package/build/runtime-cjs/cache/cache.js +6 -6
  17. package/build/runtime-cjs/cache/lists.d.ts +1 -1
  18. package/build/runtime-cjs/cache/lists.js +15 -6
  19. package/build/runtime-cjs/cache/tests/list.test.js +160 -70
  20. package/build/runtime-cjs/lib/config.d.ts +10 -2
  21. package/build/runtime-cjs/lib/types.d.ts +1 -0
  22. package/build/runtime-esm/cache/cache.d.ts +1 -1
  23. package/build/runtime-esm/cache/cache.js +6 -6
  24. package/build/runtime-esm/cache/lists.d.ts +1 -1
  25. package/build/runtime-esm/cache/lists.js +15 -6
  26. package/build/runtime-esm/cache/tests/list.test.js +160 -70
  27. package/build/runtime-esm/lib/config.d.ts +10 -2
  28. package/build/runtime-esm/lib/types.d.ts +1 -0
  29. package/build/test-cjs/index.js +122 -36
  30. package/build/test-esm/index.js +122 -36
  31. package/build/vite-cjs/index.js +122 -36
  32. package/build/vite-esm/index.js +122 -36
  33. package/package.json +1 -1
  34. package/src/codegen/generators/artifacts/artifacts.test.ts +268 -0
  35. package/src/codegen/generators/artifacts/operations.ts +31 -13
  36. package/src/codegen/generators/definitions/schema.test.ts +9 -0
  37. package/src/codegen/transforms/list.ts +0 -1
  38. package/src/codegen/transforms/paginate.test.ts +47 -0
  39. package/src/codegen/transforms/paginate.ts +28 -8
  40. package/src/codegen/transforms/schema.ts +5 -0
  41. package/src/codegen/validators/typeCheck.test.ts +56 -0
  42. package/src/codegen/validators/typeCheck.ts +66 -11
  43. package/src/lib/config.ts +11 -0
  44. package/src/runtime/cache/cache.ts +9 -6
  45. package/src/runtime/cache/lists.ts +24 -10
  46. package/src/runtime/cache/tests/list.test.ts +173 -66
  47. package/src/runtime/lib/config.ts +12 -2
  48. package/src/runtime/lib/types.ts +1 -0
@@ -320,6 +320,8 @@ export default async function typeCheck(
320
320
  listTypes,
321
321
  fragments,
322
322
  }),
323
+ // checkMutationOperation
324
+ checkMutationOperation(config),
323
325
  // pagination directive can only show up on nodes or the query type
324
326
  nodeDirectives(config, [config.paginateDirective]),
325
327
  // this replaces KnownArgumentNamesRule
@@ -415,29 +417,43 @@ const validateLists = ({
415
417
  return
416
418
  }
417
419
 
420
+ // Do we have the parentId another way?
421
+ let parentIdFound = false
418
422
  // look for one of the list directives
419
423
  directive = node.directives?.find(({ name }) => [
420
424
  [config.listPrependDirective, config.listAppendDirective].includes(name.value),
421
425
  ])
422
- // if there is no directive
423
- if (!directive) {
424
- ctx.reportError(
425
- new graphql.GraphQLError('parentID is required for this list fragment')
426
+ if (directive) {
427
+ // find the argument holding the parent ID
428
+ let parentArg = directive.arguments?.find(
429
+ (arg) => arg.name.value === config.listDirectiveParentIDArg
426
430
  )
431
+ if (parentArg) {
432
+ parentIdFound = true
433
+ }
434
+ }
435
+
436
+ if (parentIdFound) {
437
+ // parentId was found, so we're good to go
427
438
  return
428
439
  }
429
440
 
430
- // find the argument holding the parent ID
431
- let parentArg = directive.arguments?.find(
432
- (arg) => arg.name.value === config.listDirectiveParentIDArg
441
+ // look for allLists directive
442
+ const allLists = node.directives?.find(
443
+ ({ name }) => config.listAllListsDirective === name.value
433
444
  )
434
445
 
435
- if (!parentArg) {
436
- ctx.reportError(
437
- new graphql.GraphQLError('parentID is required for this list fragment')
438
- )
446
+ // if there is the directive or it's
447
+ if (allLists || config.defaultListTarget === 'all') {
439
448
  return
440
449
  }
450
+
451
+ ctx.reportError(
452
+ new graphql.GraphQLError(
453
+ `For this list fragment, you need to add or @${config.listParentDirective} or @${config.listAllListsDirective} directive to specify the behavior`
454
+ )
455
+ )
456
+ return
441
457
  },
442
458
  // if we run into a directive that points to a list, make sure that list exists
443
459
  Directive(node) {
@@ -925,6 +941,45 @@ function nodeDirectives(config: Config, directives: string[]) {
925
941
  }
926
942
  }
927
943
 
944
+ function checkMutationOperation(config: Config) {
945
+ return function (ctx: graphql.ValidationContext): graphql.ASTVisitor {
946
+ return {
947
+ FragmentSpread(node, _, __, ___, ancestors) {
948
+ const append = node.directives?.find(
949
+ (c) => c.name.value === config.listAppendDirective
950
+ )
951
+
952
+ const prepend = node.directives?.find(
953
+ (c) => c.name.value === config.listPrependDirective
954
+ )
955
+ if (append && prepend) {
956
+ ctx.reportError(
957
+ new graphql.GraphQLError(
958
+ `You can't apply both @${config.listPrependDirective} and @${config.listAppendDirective} at the same time`
959
+ )
960
+ )
961
+ return
962
+ }
963
+
964
+ const parentId = node.directives?.find(
965
+ (c) => c.name.value === config.listParentDirective
966
+ )
967
+ const allLists = node.directives?.find(
968
+ (c) => c.name.value === config.listAllListsDirective
969
+ )
970
+ if (parentId && allLists) {
971
+ ctx.reportError(
972
+ new graphql.GraphQLError(
973
+ `You can't apply both @${config.listParentDirective} and @${config.listAllListsDirective} at the same time`
974
+ )
975
+ )
976
+ return
977
+ }
978
+ },
979
+ }
980
+ }
981
+ }
982
+
928
983
  export function getAndVerifyNodeInterface(config: Config): graphql.GraphQLInterfaceType | null {
929
984
  const { schema } = config
930
985
 
package/src/lib/config.ts CHANGED
@@ -38,6 +38,8 @@ export class Config {
38
38
  cacheBufferSize?: number
39
39
  defaultCachePolicy: CachePolicy
40
40
  defaultPartial: boolean
41
+ internalListPosition: 'first' | 'last'
42
+ defaultListTarget: 'all' | null = null
41
43
  definitionsFolder?: string
42
44
  newSchema: string = ''
43
45
  newDocuments: string = ''
@@ -77,6 +79,8 @@ export class Config {
77
79
  definitionsPath,
78
80
  defaultCachePolicy = CachePolicy.CacheOrNetwork,
79
81
  defaultPartial = false,
82
+ defaultListPosition = 'append',
83
+ defaultListTarget = null,
80
84
  defaultKeys,
81
85
  types = {},
82
86
  logLevel,
@@ -116,6 +120,8 @@ export class Config {
116
120
  this.cacheBufferSize = cacheBufferSize
117
121
  this.defaultCachePolicy = defaultCachePolicy
118
122
  this.defaultPartial = defaultPartial
123
+ this.internalListPosition = defaultListPosition === 'append' ? 'last' : 'first'
124
+ this.defaultListTarget == defaultListTarget
119
125
  this.definitionsFolder = definitionsPath
120
126
  this.logLevel = ((logLevel as LogLevel) || LogLevel.Summary).toLowerCase() as LogLevel
121
127
  this.disableMasking = disableMasking
@@ -470,6 +476,10 @@ export class Config {
470
476
  return 'parentID'
471
477
  }
472
478
 
479
+ get listAllListsDirective() {
480
+ return 'allLists'
481
+ }
482
+
473
483
  get listNameArg() {
474
484
  return 'name'
475
485
  }
@@ -578,6 +588,7 @@ export class Config {
578
588
  this.listPrependDirective,
579
589
  this.listAppendDirective,
580
590
  this.listDirectiveParentIDArg,
591
+ this.listAllListsDirective,
581
592
  this.whenDirective,
582
593
  this.whenNotDirective,
583
594
  this.argumentsDirective,
@@ -110,8 +110,8 @@ export class Cache {
110
110
  }
111
111
 
112
112
  // return the list handler to mutate a named list in the cache
113
- list(name: string, parentID?: string | {}): ListCollection {
114
- const handler = this._internal_unstable.lists.get(name, parentID)
113
+ list(name: string, parentID?: string, allLists?: boolean): ListCollection {
114
+ const handler = this._internal_unstable.lists.get(name, parentID, allLists)
115
115
  if (!handler) {
116
116
  throw new Error(
117
117
  `Cannot find list with name: ${name}${
@@ -570,7 +570,10 @@ class CacheInternal {
570
570
  }
571
571
 
572
572
  // if the necessary list doesn't exist, don't do anything
573
- if (operation.list && !this.lists.get(operation.list, parentID)) {
573
+ if (
574
+ operation.list &&
575
+ !this.lists.get(operation.list, parentID, operation.target === 'all')
576
+ ) {
574
577
  continue
575
578
  }
576
579
 
@@ -585,7 +588,7 @@ class CacheInternal {
585
588
  operation.list
586
589
  ) {
587
590
  this.cache
588
- .list(operation.list, parentID)
591
+ .list(operation.list, parentID, operation.target === 'all')
589
592
  .when(operation.when)
590
593
  .addToList(fields, target, variables, operation.position || 'last')
591
594
  }
@@ -598,7 +601,7 @@ class CacheInternal {
598
601
  operation.list
599
602
  ) {
600
603
  this.cache
601
- .list(operation.list, parentID)
604
+ .list(operation.list, parentID, operation.target === 'all')
602
605
  .when(operation.when)
603
606
  .remove(target, variables)
604
607
  }
@@ -624,7 +627,7 @@ class CacheInternal {
624
627
  operation.list
625
628
  ) {
626
629
  this.cache
627
- .list(operation.list, parentID)
630
+ .list(operation.list, parentID, operation.target === 'all')
628
631
  .when(operation.when)
629
632
  .toggleElement(fields, target, variables, operation.position || 'last')
630
633
  }
@@ -16,7 +16,7 @@ export class ListManager {
16
16
 
17
17
  private listsByField: Map<string, Map<string, List[]>> = new Map()
18
18
 
19
- get(listName: string, id?: string | {}) {
19
+ get(listName: string, id?: string, allLists?: boolean) {
20
20
  const matches = this.lists.get(listName)
21
21
 
22
22
  // if we don't have a list by that name, we're done
@@ -24,11 +24,29 @@ export class ListManager {
24
24
  return null
25
25
  }
26
26
 
27
+ // if we want to update all list, return all matches
28
+ if (allLists) {
29
+ return new ListCollection(
30
+ Array.from(matches, ([key, value]) => [...value.lists]).flat()
31
+ )
32
+ }
33
+
27
34
  const head = [...matches.values()][0]
28
35
 
36
+ // the provided id won't match the cache's ID so we have to compute the internal ID, using
37
+ // one of the matches to figure out the type of the list element
38
+ const { recordType } = head.lists[0]
39
+ const parentID = id ? this.cache._internal_unstable.id(recordType || '', id)! : this.rootID
40
+
29
41
  // if there is only one list with that name, return it
30
42
  if (matches?.size === 1) {
31
- return head
43
+ // if there is no provided id, just use the first one
44
+ if (!id) {
45
+ return head
46
+ }
47
+
48
+ // otherwise we're only safe to use the head if it matches the parentID
49
+ return parentID === Array.from(matches.keys())[0] ? head : null
32
50
  }
33
51
 
34
52
  // there are multiple versions of the list so the user must
@@ -36,17 +54,13 @@ export class ListManager {
36
54
  // it would have been caught in the size === 1 check above since
37
55
  // root's ID is fixed
38
56
  if (!id) {
39
- throw new Error(
40
- `Found multiple instances of "${listName}". Please provide a ` +
41
- `parentID that corresponds to the object containing the field marked with @list or @paginate.`
57
+ console.error(
58
+ `Found multiple instances of "${listName}". Please provide one of @parentID or @allLists directives to ` +
59
+ `help identify which list you want modify. For more information, visit this guide: https://www.houdinigraphql.com/api/graphql#parentidvalue-string `
42
60
  )
61
+ return null
43
62
  }
44
63
 
45
- // the provided id won't match the cache's ID so we have to compute the internal ID, using
46
- // one of the matches to figure out the type of the list element
47
- const { recordType } = head.lists[0]
48
- const parentID = id ? this.cache._internal_unstable.id(recordType || '', id)! : this.rootID
49
-
50
64
  // return the list pointing to the correct parent
51
65
  return this.lists.get(listName)?.get(parentID)
52
66
  }
@@ -1624,10 +1624,6 @@ test('append operation', function () {
1624
1624
  {
1625
1625
  action: 'insert',
1626
1626
  list: 'All_Users',
1627
- parentID: {
1628
- kind: 'String',
1629
- value: cache._internal_unstable.id('User', '1')!,
1630
- },
1631
1627
  },
1632
1628
  ],
1633
1629
  fields: {
@@ -1646,7 +1642,7 @@ test('append operation', function () {
1646
1642
  })
1647
1643
 
1648
1644
  // make sure we just added to the list
1649
- expect([...cache.list('All_Users', cache._internal_unstable.id('User', '1')!)]).toHaveLength(1)
1645
+ expect([...cache.list('All_Users', '1')]).toHaveLength(1)
1650
1646
  })
1651
1647
 
1652
1648
  test('append from list', function () {
@@ -1716,10 +1712,6 @@ test('append from list', function () {
1716
1712
  {
1717
1713
  action: 'insert',
1718
1714
  list: 'All_Users',
1719
- parentID: {
1720
- kind: 'String',
1721
- value: cache._internal_unstable.id('User', '1')!,
1722
- },
1723
1715
  },
1724
1716
  ],
1725
1717
  fields: {
@@ -1736,7 +1728,7 @@ test('append from list', function () {
1736
1728
  })
1737
1729
 
1738
1730
  // make sure we just added to the list
1739
- expect([...cache.list('All_Users', cache._internal_unstable.id('User', '1')!)]).toHaveLength(2)
1731
+ expect([...cache.list('All_Users', '1')]).toHaveLength(2)
1740
1732
  })
1741
1733
 
1742
1734
  test('toggle list', function () {
@@ -1823,10 +1815,6 @@ test('toggle list', function () {
1823
1815
  {
1824
1816
  action: 'toggle',
1825
1817
  list: 'All_Users',
1826
- parentID: {
1827
- kind: 'String',
1828
- value: cache._internal_unstable.id('User', '1')!,
1829
- },
1830
1818
  },
1831
1819
  ],
1832
1820
  fields: {
@@ -1841,23 +1829,15 @@ test('toggle list', function () {
1841
1829
  // write some data to a different location with a new user
1842
1830
  // that should be added to the list
1843
1831
  cache.write({ selection: toggleSelection, data: { newUser: { id: '3' } } })
1844
- expect([...cache.list('All_Users', cache._internal_unstable.id('User', '1')!)]).toEqual([
1845
- 'User:5',
1846
- 'User:3',
1847
- ])
1832
+ expect([...cache.list('All_Users', '1')]).toEqual(['User:5', 'User:3'])
1848
1833
 
1849
1834
  // toggle the user again to remove the user
1850
1835
  cache.write({ selection: toggleSelection, data: { newUser: { id: '3' } } })
1851
- expect([...cache.list('All_Users', cache._internal_unstable.id('User', '1')!)]).toEqual([
1852
- 'User:5',
1853
- ])
1836
+ expect([...cache.list('All_Users', '1')]).toEqual(['User:5'])
1854
1837
 
1855
1838
  // toggle the user again to add the user back
1856
1839
  cache.write({ selection: toggleSelection, data: { newUser: { id: '3' } } })
1857
- expect([...cache.list('All_Users', cache._internal_unstable.id('User', '1')!)]).toEqual([
1858
- 'User:5',
1859
- 'User:3',
1860
- ])
1840
+ expect([...cache.list('All_Users', '1')]).toEqual(['User:5', 'User:3'])
1861
1841
  })
1862
1842
 
1863
1843
  test('append when operation', function () {
@@ -1933,10 +1913,6 @@ test('append when operation', function () {
1933
1913
  {
1934
1914
  action: 'insert',
1935
1915
  list: 'All_Users',
1936
- parentID: {
1937
- kind: 'String',
1938
- value: cache._internal_unstable.id('User', '1')!,
1939
- },
1940
1916
  when: {
1941
1917
  must: {
1942
1918
  value: 'not-foo',
@@ -1960,7 +1936,7 @@ test('append when operation', function () {
1960
1936
  })
1961
1937
 
1962
1938
  // make sure we just added to the list
1963
- expect([...cache.list('All_Users', cache._internal_unstable.id('User', '1')!)]).toHaveLength(0)
1939
+ expect([...cache.list('All_Users', '1')]).toHaveLength(0)
1964
1940
  })
1965
1941
 
1966
1942
  test('prepend when operation', function () {
@@ -2036,10 +2012,6 @@ test('prepend when operation', function () {
2036
2012
  {
2037
2013
  action: 'insert',
2038
2014
  list: 'All_Users',
2039
- parentID: {
2040
- kind: 'String',
2041
- value: cache._internal_unstable.id('User', '1')!,
2042
- },
2043
2015
  position: 'first',
2044
2016
  when: {
2045
2017
  must: {
@@ -2064,7 +2036,7 @@ test('prepend when operation', function () {
2064
2036
  })
2065
2037
 
2066
2038
  // make sure we just added to the list
2067
- expect([...cache.list('All_Users', cache._internal_unstable.id('User', '1')!)]).toHaveLength(0)
2039
+ expect([...cache.list('All_Users', '1')]).toHaveLength(0)
2068
2040
  })
2069
2041
 
2070
2042
  test('prepend operation', function () {
@@ -2154,10 +2126,6 @@ test('prepend operation', function () {
2154
2126
  {
2155
2127
  action: 'insert',
2156
2128
  list: 'All_Users',
2157
- parentID: {
2158
- kind: 'String',
2159
- value: cache._internal_unstable.id('User', '1')!,
2160
- },
2161
2129
  position: 'first',
2162
2130
  },
2163
2131
  ],
@@ -2177,10 +2145,7 @@ test('prepend operation', function () {
2177
2145
  })
2178
2146
 
2179
2147
  // make sure we just added to the list
2180
- expect([...cache.list('All_Users', cache._internal_unstable.id('User', '1')!)]).toEqual([
2181
- 'User:3',
2182
- 'User:2',
2183
- ])
2148
+ expect([...cache.list('All_Users', '1')]).toEqual(['User:3', 'User:2'])
2184
2149
  })
2185
2150
 
2186
2151
  test('remove operation', function () {
@@ -2265,10 +2230,6 @@ test('remove operation', function () {
2265
2230
  {
2266
2231
  action: 'remove',
2267
2232
  list: 'All_Users',
2268
- parentID: {
2269
- kind: 'String',
2270
- value: cache._internal_unstable.id('User', '1')!,
2271
- },
2272
2233
  },
2273
2234
  ],
2274
2235
  fields: {
@@ -2287,7 +2248,7 @@ test('remove operation', function () {
2287
2248
  })
2288
2249
 
2289
2250
  // make sure we removed the element from the list
2290
- expect([...cache.list('All_Users', cache._internal_unstable.id('User', '1')!)]).toHaveLength(0)
2251
+ expect([...cache.list('All_Users', '1')]).toHaveLength(0)
2291
2252
  })
2292
2253
 
2293
2254
  test('remove operation from list', function () {
@@ -2375,10 +2336,6 @@ test('remove operation from list', function () {
2375
2336
  {
2376
2337
  action: 'remove',
2377
2338
  list: 'All_Users',
2378
- parentID: {
2379
- kind: 'String',
2380
- value: cache._internal_unstable.id('User', '1')!,
2381
- },
2382
2339
  },
2383
2340
  ],
2384
2341
  fields: {
@@ -2395,7 +2352,7 @@ test('remove operation from list', function () {
2395
2352
  })
2396
2353
 
2397
2354
  // make sure we removed the element from the list
2398
- expect([...cache.list('All_Users', cache._internal_unstable.id('User', '1')!)]).toHaveLength(0)
2355
+ expect([...cache.list('All_Users', '1')]).toHaveLength(0)
2399
2356
  })
2400
2357
 
2401
2358
  test('delete operation', function () {
@@ -2498,7 +2455,7 @@ test('delete operation', function () {
2498
2455
  })
2499
2456
 
2500
2457
  // make sure we removed the element from the list
2501
- expect([...cache.list('All_Users', cache._internal_unstable.id('User', '1')!)]).toHaveLength(0)
2458
+ expect([...cache.list('All_Users', '1')]).toHaveLength(0)
2502
2459
 
2503
2460
  expect(cache._internal_unstable.storage.topLayer.operations['User:2'].deleted).toBeTruthy()
2504
2461
  })
@@ -2606,7 +2563,7 @@ test('delete operation from list', function () {
2606
2563
  })
2607
2564
 
2608
2565
  // make sure we removed the element from the list
2609
- expect([...cache.list('All_Users', cache._internal_unstable.id('User', '1')!)]).toHaveLength(0)
2566
+ expect([...cache.list('All_Users', '1')]).toHaveLength(0)
2610
2567
 
2611
2568
  expect(cache._internal_unstable.storage.topLayer.operations['User:2'].deleted).toBeTruthy()
2612
2569
  expect(cache._internal_unstable.storage.topLayer.operations['User:3'].deleted).toBeTruthy()
@@ -2753,7 +2710,7 @@ test('delete operation from connection', function () {
2753
2710
  })
2754
2711
 
2755
2712
  // make sure we removed the element from the list
2756
- expect([...cache.list('All_Users', cache._internal_unstable.id('User', '1')!)]).toHaveLength(0)
2713
+ expect([...cache.list('All_Users', '1')]).toHaveLength(0)
2757
2714
  expect(cache._internal_unstable.storage.topLayer.operations['User:2'].deleted).toBeTruthy()
2758
2715
  })
2759
2716
 
@@ -3225,10 +3182,6 @@ test('list operations fail silently', function () {
3225
3182
  {
3226
3183
  action: 'insert',
3227
3184
  list: 'All_Users',
3228
- parentID: {
3229
- kind: 'String',
3230
- value: cache._internal_unstable.id('User', '1')!,
3231
- },
3232
3185
  },
3233
3186
  ],
3234
3187
  fields: {
@@ -3399,7 +3352,7 @@ test('parentID must be passed if there are multiple instances of a list handler'
3399
3352
  // instantiate a cache
3400
3353
  const cache = new Cache(config)
3401
3354
 
3402
- const friendsSelection = {
3355
+ const friendsSelection: SubscriptionSelection = {
3403
3356
  friends: {
3404
3357
  type: 'User',
3405
3358
  keyRaw: 'friends',
@@ -3471,11 +3424,73 @@ test('parentID must be passed if there are multiple instances of a list handler'
3471
3424
  {}
3472
3425
  )
3473
3426
 
3474
- // looking up the list without a parent id should fail
3475
- expect(() => cache.list('All_Users')).toThrow()
3476
- expect(cache.list('All_Users', '1')!.lists[0].recordID).toEqual(
3477
- cache._internal_unstable.id('User', '1')
3478
- )
3427
+ // append a value to the store
3428
+ const writeSelectionNoParentID: SubscriptionSelection = {
3429
+ user: {
3430
+ type: 'User',
3431
+ keyRaw: 'user',
3432
+ operations: [
3433
+ {
3434
+ action: 'insert',
3435
+ list: 'All_Users',
3436
+ },
3437
+ ],
3438
+ fields: {
3439
+ id: {
3440
+ type: 'ID',
3441
+ keyRaw: 'id',
3442
+ },
3443
+ firstName: {
3444
+ type: 'String',
3445
+ keyRaw: 'firstName',
3446
+ },
3447
+ },
3448
+ },
3449
+ }
3450
+ const writeSelectionWithParentID: SubscriptionSelection = {
3451
+ user: {
3452
+ type: 'User',
3453
+ keyRaw: 'user',
3454
+ operations: [
3455
+ {
3456
+ action: 'insert',
3457
+ list: 'All_Users',
3458
+ parentID: {
3459
+ kind: 'String',
3460
+ value: '1',
3461
+ },
3462
+ },
3463
+ ],
3464
+ fields: {
3465
+ id: {
3466
+ type: 'ID',
3467
+ keyRaw: 'id',
3468
+ },
3469
+ firstName: {
3470
+ type: 'String',
3471
+ keyRaw: 'firstName',
3472
+ },
3473
+ },
3474
+ },
3475
+ }
3476
+
3477
+ // write the value without a parent ID
3478
+ cache.write({
3479
+ selection: writeSelectionNoParentID,
3480
+ data: { user: { id: '2', firstName: 'test' } },
3481
+ })
3482
+ // make sure we didn't modify the lists
3483
+ expect([...cache.list('All_Users', '1')]).toHaveLength(1)
3484
+ expect([...cache.list('All_Users', '2')]).toHaveLength(0)
3485
+
3486
+ // write the value with a parent ID
3487
+ cache.write({
3488
+ selection: writeSelectionWithParentID,
3489
+ data: { user: { id: '2', firstName: 'test' } },
3490
+ })
3491
+ // make sure we modified the correct list
3492
+ expect([...cache.list('All_Users', '1')]).toHaveLength(2)
3493
+ expect([...cache.list('All_Users', '2')]).toHaveLength(0)
3479
3494
  })
3480
3495
 
3481
3496
  test('append in abstract list', function () {
@@ -3745,3 +3760,95 @@ test('list operations on interface fields without a well defined parent update t
3745
3760
  },
3746
3761
  })
3747
3762
  })
3763
+
3764
+ test("parentID ignores single lists that don't match", function () {
3765
+ // instantiate a cache
3766
+ const cache = new Cache(config)
3767
+
3768
+ // create a list we will add to
3769
+ cache.write({
3770
+ selection: {
3771
+ viewer: {
3772
+ type: 'User',
3773
+ keyRaw: 'viewer',
3774
+ fields: {
3775
+ id: {
3776
+ type: 'ID',
3777
+ keyRaw: 'id',
3778
+ },
3779
+ },
3780
+ },
3781
+ },
3782
+ data: {
3783
+ viewer: {
3784
+ id: '1',
3785
+ },
3786
+ },
3787
+ })
3788
+
3789
+ // subscribe to the data to register the list
3790
+ cache.subscribe(
3791
+ {
3792
+ rootType: 'User',
3793
+ selection: {
3794
+ friends: {
3795
+ type: 'User',
3796
+ keyRaw: 'friends',
3797
+ list: {
3798
+ name: 'All_Users',
3799
+ connection: false,
3800
+ type: 'User',
3801
+ },
3802
+ fields: {
3803
+ id: {
3804
+ type: 'ID',
3805
+ keyRaw: 'id',
3806
+ },
3807
+ firstName: {
3808
+ type: 'String',
3809
+ keyRaw: 'firstName',
3810
+ },
3811
+ },
3812
+ },
3813
+ },
3814
+ parentID: cache._internal_unstable.id('User', '1')!,
3815
+ set: vi.fn(),
3816
+ },
3817
+ {}
3818
+ )
3819
+
3820
+ // write some data to a different location with a new user
3821
+ // that should be added to the list
3822
+ cache.write({
3823
+ selection: {
3824
+ newUser: {
3825
+ type: 'User',
3826
+ keyRaw: 'newUser',
3827
+ operations: [
3828
+ {
3829
+ action: 'insert',
3830
+ list: 'All_Users',
3831
+ parentID: {
3832
+ kind: 'String',
3833
+ value: '2',
3834
+ },
3835
+ },
3836
+ ],
3837
+ fields: {
3838
+ id: {
3839
+ type: 'ID',
3840
+ keyRaw: 'id',
3841
+ },
3842
+ },
3843
+ },
3844
+ },
3845
+ data: {
3846
+ newUser: {
3847
+ id: '3',
3848
+ },
3849
+ },
3850
+ })
3851
+
3852
+ // make sure we just added to the list
3853
+ expect([...cache.list('All_Users', '1')]).toHaveLength(0)
3854
+ })
@@ -100,13 +100,13 @@ export type ConfigFile = {
100
100
  definitionsPath?: string
101
101
 
102
102
  /**
103
- * One of "kit" or "svelte". Used to tell the preprocessor what kind of loading paradigm to generate for you. (default: kit)
103
+ * One of "kit" or "svelte". Used to tell the preprocessor what kind of loading paradigm to generate for you. (default: `kit`)
104
104
  * @deprecated please follow the steps here: http://www.houdinigraphql.com/guides/release-notes#0170
105
105
  */
106
106
  framework?: 'kit' | 'svelte'
107
107
 
108
108
  /**
109
- * One of "esm" or "commonjs". Tells the artifact generator what kind of modules to create. (default: esm)
109
+ * One of "esm" or "commonjs". Tells the artifact generator what kind of modules to create. (default: `esm`)
110
110
  */
111
111
  module?: 'esm' | 'commonjs'
112
112
 
@@ -125,6 +125,16 @@ export type ConfigFile = {
125
125
  */
126
126
  defaultPartial?: boolean
127
127
 
128
+ /**
129
+ * Specifies whether mutations should append or prepend list. For more information: https://www.houdinigraphql.com/api/graphql (default: `append`)
130
+ */
131
+ defaultListPosition?: 'append' | 'prepend'
132
+
133
+ /**
134
+ * Specifies whether mutation should apply a specific target list. When you set `all`, it's like adding the directive `@allLists` to all _insert fragment (default: `null`)
135
+ */
136
+ defaultListTarget?: 'all' | null
137
+
128
138
  /**
129
139
  * A list of fields to use when computing a record’s id. The default value is ['id']. For more information: https://www.houdinigraphql.com/guides/caching-data#custom-ids
130
140
  */