diff --git a/.changeset/stale-ligers-appear.md b/.changeset/stale-ligers-appear.md new file mode 100644 index 000000000..5953a671c --- /dev/null +++ b/.changeset/stale-ligers-appear.md @@ -0,0 +1,5 @@ +--- +'houdini': patch +--- + +Adding a new directive @allLists to update all lists after a mutation diff --git a/.graphqlrc.yaml b/.graphqlrc.yaml index 0a92465f7..d276eb918 100644 --- a/.graphqlrc.yaml +++ b/.graphqlrc.yaml @@ -1,9 +1,9 @@ projects: - default: + sveltekit: # 👇 For vscode-graphql and intellisense schema: - - e2e/api/*.graphql - - e2e/$houdini/graphql/schema.graphql + - e2e/_api/*.graphql + - e2e/sveltekit/$houdini/graphql/schema.graphql documents: - - e2e/src/**/*.gql - - e2e/$houdini/graphql/documents.gql + - e2e/sveltekit/src/**/*.gql + - e2e/sveltekit/$houdini/graphql/documents.gql diff --git a/e2e/_api/graphql.mjs b/e2e/_api/graphql.mjs index 8ee4c8d86..f5fd58d75 100644 --- a/e2e/_api/graphql.mjs +++ b/e2e/_api/graphql.mjs @@ -12,38 +12,46 @@ export const typeDefs = sourceFiles.map((filepath) => // Example Cities/Libraries/Books data // Assume a traditional relational database for storage - each table with unique ID. -let cityId = 1; -let libraryId = 1; -let bookId = 1; +let cityId = 1 +let libraryId = 1 +let bookId = 1 // Allow the "database" to be persistent and mutable let cities = [ { - id: cityId++, name: 'Alexandria', libraries: [ + id: cityId++, + name: 'Alexandria', + libraries: [ { - id: libraryId++, name: 'The Library of Alexandria', books: [ + id: libraryId++, + name: 'The Library of Alexandria', + books: [ { id: bookId++, title: 'Callimachus Pinakes' }, { id: bookId++, title: 'Kutubkhana-i-lskandriyya' }, - ] + ], }, { - id: libraryId++, name: 'Bibliotheca Alexandrina', books: [ - { id: bookId++, title: 'Analyze your own personality' }, - ] + id: libraryId++, + name: 'Bibliotheca Alexandrina', + books: [{ id: bookId++, title: 'Analyze your own personality' }], }, - ] + ], }, { - id: cityId++, name: 'Istanbul', libraries: [ + id: cityId++, + name: 'Istanbul', + libraries: [ { - id: libraryId++, name: 'The Imperial Library of Constantinople', books: [ + id: libraryId++, + name: 'The Imperial Library of Constantinople', + books: [ { id: bookId++, title: 'Homer' }, { id: bookId++, title: 'The Hellenistic History' }, - ] + ], }, - ] + ], }, -]; +] // example data const data = [ @@ -88,6 +96,13 @@ export const resolvers = { usersList: (_, args) => { return [...getSnapshot(args.snapshot)].splice(args.offset || 0, args.limit) }, + userNodes: (_, args) => { + const allData = [...getSnapshot(args.snapshot)] + return { + totalCount: allData.length, + nodes: allData.splice(args.offset || 0, args.limit), + } + }, session: (_, args, info) => { let token = null info.request.headers.forEach((value, key) => { @@ -136,7 +151,7 @@ export const resolvers = { } }, cities: () => { - return cities; + return cities }, }, @@ -156,7 +171,7 @@ export const resolvers = { await sleep(args.delay) } const user = { - id: (list.length + 1).toString(), + id: `${args.snapshot}:${list.length + 1}`, name: args.name, birthDate: args.birthDate, enumValue: args.enumValue, @@ -187,7 +202,7 @@ export const resolvers = { try { let data = await processFile(file) return data - } catch (e) { } + } catch (e) {} throw new GraphQLYogaError('ERROR', { code: 500 }) }, multipleUpload: async (_, { files }) => { @@ -209,12 +224,12 @@ export const resolvers = { libraries: [], } - cities.push(city); - return city; + cities.push(city) + return city }, addLibrary: (_, args) => { - const cityId = Number.parseInt(args.city); - const city = cities.find((city) => city.id === cityId); + const cityId = Number.parseInt(args.city) + const city = cities.find((city) => city.id === cityId) if (!city) { throw new GraphQLYogaError('City not found', { code: 404 }) } @@ -224,50 +239,58 @@ export const resolvers = { name: args.name, books: [], } - city.libraries.push(library); - return library; + city.libraries.push(library) + return library }, addBook: (_, args) => { - const libraryId = Number.parseInt(args.library); - const city = cities.find((city) => city.libraries.find((library) => library.id === libraryId)); + const libraryId = Number.parseInt(args.library) + const city = cities.find((city) => + city.libraries.find((library) => library.id === libraryId) + ) if (!city) { throw new GraphQLYogaError('City/Library not found', { code: 404 }) } - const library = city.libraries.find((library) => library.id === libraryId); - + const library = city.libraries.find((library) => library.id === libraryId) + const book = { id: bookId++, title: args.title, } - library.books.push(book); - return book; + library.books.push(book) + return book }, deleteCity: (_, args) => { - const cityId = Number.parseInt(args.city); - const city = cities.find((city) => city.id === cityId); - cities = cities.filter((city) => city.id !== cityId); - return city; + const cityId = Number.parseInt(args.city) + const city = cities.find((city) => city.id === cityId) + cities = cities.filter((city) => city.id !== cityId) + return city }, deleteLibrary: (_, args) => { - const libraryId = Number.parseInt(args.library); - const city = cities.find((city) => city.libraries.find((library) => library.id === libraryId)); + const libraryId = Number.parseInt(args.library) + const city = cities.find((city) => + city.libraries.find((library) => library.id === libraryId) + ) if (!city) { throw new GraphQLYogaError('City/Library not found', { code: 404 }) } - const library = city.libraries.find((library) => library.id === libraryId); - city.libraries = city.libraries.filter((library) => library.id !== libraryId); - return library; + const library = city.libraries.find((library) => library.id === libraryId) + city.libraries = city.libraries.filter((library) => library.id !== libraryId) + return library }, deleteBook: (_, args) => { - const bookId = Number.parseInt(args.book); - const city = cities.find((city) => city.libraries.find((library) => library.books.find((book) => book.id === bookId))); + const bookId = Number.parseInt(args.book) + const city = cities.find((city) => + city.libraries.find((library) => library.books.find((book) => book.id === bookId)) + ) if (!city) { throw new GraphQLYogaError('City/Library/Book not found', { code: 404 }) } - const library = city.libraries.find((library) => library.books.find((book) => book.id === bookId)); - const book = library.books.find((book) => book.id === bookId); - library.books = library.books.filter((book) => book.id !== bookId); - return book; + const library = city.libraries.find((library) => + library.books.find((book) => book.id === bookId) + ) + const book = library.books.find((book) => book.id === bookId) + library.books = library.books.filter((book) => book.id !== bookId) + return book }, }, diff --git a/e2e/_api/schema.graphql b/e2e/_api/schema.graphql index be3609e90..4753c771a 100644 --- a/e2e/_api/schema.graphql +++ b/e2e/_api/schema.graphql @@ -57,6 +57,7 @@ type Query { snapshot: String! ): UserConnection! usersList(limit: Int = 4, offset: Int, snapshot: String!): [User!]! + userNodes(limit: Int = 4, offset: Int, snapshot: String!): UserNodes! session: String cities: [City]! } @@ -85,6 +86,11 @@ type UserEdge { node: User } +type UserNodes { + totalCount: Int + nodes: [User!]! +} + type Book { id: ID! title: String! diff --git a/e2e/sveltekit/package.json b/e2e/sveltekit/package.json index 8bb331be3..8d764b708 100644 --- a/e2e/sveltekit/package.json +++ b/e2e/sveltekit/package.json @@ -21,8 +21,8 @@ "devDependencies": { "@kitql/helper": "^0.5.0", "@playwright/test": "1.25.0", - "@sveltejs/adapter-auto": "1.0.0-next.66", - "@sveltejs/kit": "1.0.0-next.510", + "@sveltejs/adapter-auto": "1.0.0-next.88", + "@sveltejs/kit": "1.0.0-next.547", "@typescript-eslint/eslint-plugin": "^5.10.1", "@typescript-eslint/parser": "^5.10.1", "concurrently": "7.1.0", diff --git a/e2e/sveltekit/src/lib/utils/routes.ts b/e2e/sveltekit/src/lib/utils/routes.ts index 2f5046879..2a60ed2eb 100644 --- a/e2e/sveltekit/src/lib/utils/routes.ts +++ b/e2e/sveltekit/src/lib/utils/routes.ts @@ -6,6 +6,7 @@ export const routes = { Query_param: '/query-param', isFetching_with_load: '/isFetching/with_load', isFetching_without_load: '/isFetching/without_load', + lists_all: '/lists-all?limit=15', Stores_SSR: '/stores/ssr', Stores_Network: '/stores/network', diff --git a/e2e/sveltekit/src/lib/utils/testsHelper.ts b/e2e/sveltekit/src/lib/utils/testsHelper.ts index f45c9ed87..f2c021cf9 100644 --- a/e2e/sveltekit/src/lib/utils/testsHelper.ts +++ b/e2e/sveltekit/src/lib/utils/testsHelper.ts @@ -39,7 +39,7 @@ export async function expect_n_gql( page: Page, selector: string | null, n: number, - action: 'click' | 'hover' = 'click' + action: 'click' | 'hover' | 'press_ArrowUp' | 'press_ArrowDown' = 'click' ) { const start = new Date().valueOf(); const timing: number[] = []; @@ -81,8 +81,14 @@ export async function expect_n_gql( if (selector) { if (action === 'click') { await page.click(selector); - } else { + } else if (action === 'hover') { await page.hover(selector); + } else if (action === 'press_ArrowUp') { + await page.locator(selector).press('ArrowUp'); + } else if (action === 'press_ArrowDown') { + page.locator(selector).press('ArrowDown'); + } else { + throw new Error(`action ${action} not implemented`); } } diff --git a/e2e/sveltekit/src/routes/lists-all/+page.gql b/e2e/sveltekit/src/routes/lists-all/+page.gql new file mode 100644 index 000000000..ec3d271f1 --- /dev/null +++ b/e2e/sveltekit/src/routes/lists-all/+page.gql @@ -0,0 +1,9 @@ +query ListAll($limit: Int!) { + userNodes(limit: $limit, snapshot: "lists-all") { + totalCount + nodes @list(name: "List_All") { + id + name + } + } +} diff --git a/e2e/sveltekit/src/routes/lists-all/+page.svelte b/e2e/sveltekit/src/routes/lists-all/+page.svelte new file mode 100644 index 000000000..a0b1f3375 --- /dev/null +++ b/e2e/sveltekit/src/routes/lists-all/+page.svelte @@ -0,0 +1,37 @@ + + + +
+
+ + +

List

+
+ {#each $ListAll.data?.userNodes.nodes ?? [] as user} +
{user?.name}
+ {/each} +
diff --git a/e2e/sveltekit/src/routes/lists-all/+page.ts b/e2e/sveltekit/src/routes/lists-all/+page.ts new file mode 100644 index 000000000..1de46ca7b --- /dev/null +++ b/e2e/sveltekit/src/routes/lists-all/+page.ts @@ -0,0 +1,10 @@ +import type { PageLoad } from './$types'; +import { load_ListAll } from '$houdini'; + +export const load: PageLoad = async (event) => { + const limit = parseInt(event.url.searchParams.get('limit') ?? '1', 10); + + return { + ...(await load_ListAll({ event, variables: { limit } })) + }; +}; diff --git a/e2e/sveltekit/src/routes/lists-all/AddUser.gql b/e2e/sveltekit/src/routes/lists-all/AddUser.gql new file mode 100644 index 000000000..4988c268b --- /dev/null +++ b/e2e/sveltekit/src/routes/lists-all/AddUser.gql @@ -0,0 +1,5 @@ +mutation ListAll_AddUser { + addUser(name: "Omar Sy", birthDate: 254143016000, snapshot: "List_All") { + ...List_All_insert @allLists @prepend + } +} diff --git a/e2e/sveltekit/src/routes/lists-all/spec.ts b/e2e/sveltekit/src/routes/lists-all/spec.ts new file mode 100644 index 000000000..8b552c7c3 --- /dev/null +++ b/e2e/sveltekit/src/routes/lists-all/spec.ts @@ -0,0 +1,44 @@ +import { test } from '@playwright/test'; +import { routes } from '../../lib/utils/routes.js'; +import { expectToBe, expect_n_gql, goto } from '../../lib/utils/testsHelper.js'; + +test.describe('lists-all', () => { + test('With 3 lists to append', async ({ page }) => { + await goto(page, routes.lists_all); + + // select the input + await page.locator('input[type="number"]').click(); + // add 1 to the input to load a second list + await expect_n_gql(page, 'input[type="number"]', 1, 'press_ArrowUp'); + // add 1 to the input to load a third list + await expect_n_gql(page, 'input[type="number"]', 1, 'press_ArrowUp'); + + // expect to have the righ data + await expectToBe( + page, + 'Bruce WillisSamuel JacksonMorgan FreemanTom HanksWill SmithHarrison FordEddie MurphyClint Eastwood' + ); + + // mutation to add a new actor (Expect to have 1 mutation) + await expect_n_gql(page, 'text=Add User', 1); + + // expect to have the data added + await expectToBe( + page, + 'Omar SyBruce WillisSamuel JacksonMorgan FreemanTom HanksWill SmithHarrison FordEddie MurphyClint Eastwood' + ); + + // select the input + await page.locator('input[type="number"]').click(); + // go back 1 list, expect no graphql request (from cache!) + await expect_n_gql(page, 'input[type="number"]', 0, 'press_ArrowDown'); + // go back 1 list, expect no graphql request (from cache!) + await expect_n_gql(page, 'input[type="number"]', 0, 'press_ArrowDown'); + + // expect the data to still contain the new actor + await expectToBe( + page, + 'Omar SyBruce WillisSamuel JacksonMorgan FreemanTom HanksWill SmithHarrison FordEddie MurphyClint Eastwood' + ); + }); +}); diff --git a/example/package.json b/example/package.json index d2378ae63..dca63892c 100644 --- a/example/package.json +++ b/example/package.json @@ -13,7 +13,7 @@ "devDependencies": { "@graphql-yoga/node": "^2.8.0", "@kitql/vite-plugin-watch-and-run": "^0.4.0", - "@sveltejs/kit": "1.0.0-next.510", + "@sveltejs/kit": "1.0.0-next.547", "concurrently": "^6.2.1", "graphql": "^15.8.0", "houdini": "workspace:^", diff --git a/packages/houdini-svelte/package.json b/packages/houdini-svelte/package.json index b973a9675..5c0eaebff 100644 --- a/packages/houdini-svelte/package.json +++ b/packages/houdini-svelte/package.json @@ -10,7 +10,7 @@ }, "dependencies": { "@kitql/helper": "^0.5.0", - "@sveltejs/kit": "1.0.0-next.505", + "@sveltejs/kit": "1.0.0-next.547", "ast-types": "^0.15.1", "estree-walker": "^3.0.1", "graphql": "^15.8.0", diff --git a/packages/houdini-svelte/src/runtime/session.ts b/packages/houdini-svelte/src/runtime/session.ts index 18c80ecb4..7df88c34d 100644 --- a/packages/houdini-svelte/src/runtime/session.ts +++ b/packages/houdini-svelte/src/runtime/session.ts @@ -27,7 +27,7 @@ export class RequestContext { throw error(status, typeof message === 'string' ? message : message.message) } - redirect(status: number, location: string): any { + redirect(status: 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308, location: string): any { throw redirect(status, location) } diff --git a/packages/houdini/src/codegen/generators/artifacts/artifacts.test.ts b/packages/houdini/src/codegen/generators/artifacts/artifacts.test.ts index 81f42bdc3..8f02da52c 100644 --- a/packages/houdini/src/codegen/generators/artifacts/artifacts.test.ts +++ b/packages/houdini/src/codegen/generators/artifacts/artifacts.test.ts @@ -869,6 +869,274 @@ describe('mutation artifacts', function () { `) }) + test('insert operation allList', async function () { + const mutationDocs = [ + mockCollectedDoc( + `mutation A { + addFriend { + friend { + ...All_Users_insert @allLists + } + } + }` + ), + mockCollectedDoc( + `query TestQuery { + users(stringValue: "foo") @list(name: "All_Users") { + firstName + } + }` + ), + ] + + // execute the generator + await runPipeline(config, mutationDocs) + + expect(mutationDocs[0]).toMatchInlineSnapshot(` + export default { + name: "A", + kind: "HoudiniMutation", + hash: "7cc5c23ffd19603e2c7c727d1ac2726d4d87ee6b0470ced7d28c7f0ed88a05c2", + + raw: \`mutation A { + addFriend { + friend { + ...All_Users_insert + id + } + } + } + + fragment All_Users_insert on User { + firstName + id + } + \`, + + rootType: "Mutation", + + selection: { + addFriend: { + type: "AddFriendOutput", + keyRaw: "addFriend", + + fields: { + friend: { + type: "User", + keyRaw: "friend", + + operations: [{ + action: "insert", + list: "All_Users", + position: "last", + target: "all" + }], + + fields: { + firstName: { + type: "String", + keyRaw: "firstName" + }, + + id: { + type: "ID", + keyRaw: "id" + } + } + } + } + } + } + }; + + "HoudiniHash=90d93ca64a69bec0880925b8af471b0da1cf76964df0b6b6c3af30b6fd877217"; + `) + }) + + test('insert operation allList by default in config', async function () { + const mutationDocs = [ + mockCollectedDoc( + `mutation A { + addFriend { + friend { + ...All_Users_insert + } + } + }` + ), + mockCollectedDoc( + `query TestQuery { + users(stringValue: "foo") @list(name: "All_Users") { + firstName + } + }` + ), + ] + + let configUpdate = testConfig() + configUpdate.defaultListTarget = 'all' + + // execute the generator + await runPipeline(configUpdate, mutationDocs) + + // verify contents + expect(mutationDocs[0]).toMatchInlineSnapshot(` + export default { + name: "A", + kind: "HoudiniMutation", + hash: "7cc5c23ffd19603e2c7c727d1ac2726d4d87ee6b0470ced7d28c7f0ed88a05c2", + + raw: \`mutation A { + addFriend { + friend { + ...All_Users_insert + id + } + } + } + + fragment All_Users_insert on User { + firstName + id + } + \`, + + rootType: "Mutation", + + selection: { + addFriend: { + type: "AddFriendOutput", + keyRaw: "addFriend", + + fields: { + friend: { + type: "User", + keyRaw: "friend", + + operations: [{ + action: "insert", + list: "All_Users", + position: "last", + target: "all" + }], + + fields: { + firstName: { + type: "String", + keyRaw: "firstName" + }, + + id: { + type: "ID", + keyRaw: "id" + } + } + } + } + } + } + }; + + "HoudiniHash=c2cee63cc2dfd5eabad47ed394b64c91f6e19378bbf018b80c6e3391c3a56e5b"; + `) + }) + + test('insert operation cosition first by default in config', async function () { + const mutationDocs = [ + mockCollectedDoc( + `mutation A { + addFriend { + friend { + ...All_Users_insert + } + } + }` + ), + mockCollectedDoc( + `query TestQuery { + users(stringValue: "foo") @list(name: "All_Users") { + firstName + } + }` + ), + ] + + let configUpdate = testConfig() + configUpdate.internalListPosition = 'first' + + // execute the generator + await runPipeline(configUpdate, mutationDocs) + + // load the contents of the file + const queryContents = await fs.readFile( + path.join(configUpdate.artifactPath(mutationDocs[0].document)) + ) + expect(queryContents).toBeTruthy() + // parse the contents + const parsedQuery: ProgramKind = recast.parse(queryContents!, { + parser: typeScriptParser, + }).program + // verify contents + expect(parsedQuery).toMatchInlineSnapshot(` + export default { + name: "A", + kind: "HoudiniMutation", + hash: "7cc5c23ffd19603e2c7c727d1ac2726d4d87ee6b0470ced7d28c7f0ed88a05c2", + + raw: \`mutation A { + addFriend { + friend { + ...All_Users_insert + id + } + } + } + + fragment All_Users_insert on User { + firstName + id + } + \`, + + rootType: "Mutation", + + selection: { + addFriend: { + type: "AddFriendOutput", + keyRaw: "addFriend", + + fields: { + friend: { + type: "User", + keyRaw: "friend", + + operations: [{ + action: "insert", + list: "All_Users", + position: "first" + }], + + fields: { + firstName: { + type: "String", + keyRaw: "firstName" + }, + + id: { + type: "ID", + keyRaw: "id" + } + } + } + } + } + } + }; + + "HoudiniHash=c2cee63cc2dfd5eabad47ed394b64c91f6e19378bbf018b80c6e3391c3a56e5b"; + `) + }) + test('toggle operation', async function () { const mutationDocs = [ mockCollectedDoc( diff --git a/packages/houdini/src/codegen/generators/artifacts/operations.ts b/packages/houdini/src/codegen/generators/artifacts/operations.ts index 9480bcfe1..02a3a2d03 100644 --- a/packages/houdini/src/codegen/generators/artifacts/operations.ts +++ b/packages/houdini/src/codegen/generators/artifacts/operations.ts @@ -1,6 +1,6 @@ import * as graphql from 'graphql' -import { Config, HoudiniError, parentTypeFromAncestors } from '../../../lib' +import { Config, parentTypeFromAncestors } from '../../../lib' import { MutationOperation } from '../../../runtime/lib/types' import { convertValue } from './utils' @@ -98,7 +98,8 @@ function operationObject({ let parentID let parentKind: 'Variable' | 'String' = 'String' - let position: MutationOperation['position'] = 'last' + let position: MutationOperation['position'] = config.internalListPosition + let allLists: MutationOperation['target'] = config.defaultListTarget ?? undefined let operationWhen: MutationOperation['when'] | undefined const internalDirectives = selection.directives?.filter((directive) => @@ -113,20 +114,32 @@ function operationObject({ const append = internalDirectives.find( ({ name }) => name.value === config.listAppendDirective ) - // is when applied? - const when = internalDirectives.find(({ name }) => name.value === 'when') - // is when_not applied? - const when_not = internalDirectives.find(({ name }) => name.value === 'when_not') + + // if both are applied, there's a problem, this should never happen has it's checked in validation step + if (append) { + position = 'last' + } + if (prepend) { + position = 'first' + } + + // is allLists applied? + const allListsDirective = internalDirectives.find( + ({ name }) => name.value === config.listAllListsDirective + ) + // look for the parentID directive let parent = internalDirectives.find( ({ name }) => name.value === config.listParentDirective ) - // if both are applied, there's a problem - if (append && prepend) { - throw new HoudiniError({ filepath, message: 'you have both applied' }) - } - position = prepend ? 'first' : 'last' + // if both are applied, there's a problem, this should never happen has it's checked in validation step + allLists = allListsDirective ? 'all' : undefined + + // is when applied? + const when = internalDirectives.find(({ name }) => name.value === 'when') + // is when_not applied? + const when_not = internalDirectives.find(({ name }) => name.value === 'when_not') // the parent ID can be provided a few ways, either as an argument to the prepend // and append directives or with the parentID directive. @@ -220,9 +233,14 @@ function operationObject({ operation.type = type } - // only add the position argument if we are inserting something + // only add the position argument if we are inserting or toggling something if (operationKind === 'insert' || operationKind === 'toggle') { - operation.position = position || 'last' + operation.position = position + } + + // only add the position argument if we are inserting something + if (operationKind === 'insert' && allLists) { + operation.target = 'all' } // if there is a parent id diff --git a/packages/houdini/src/codegen/generators/definitions/schema.test.ts b/packages/houdini/src/codegen/generators/definitions/schema.test.ts index 1fe9b7ce5..0896d5477 100644 --- a/packages/houdini/src/codegen/generators/definitions/schema.test.ts +++ b/packages/houdini/src/codegen/generators/definitions/schema.test.ts @@ -45,6 +45,9 @@ test('adds internal documents to schema', async function () { """@append is used to tell the runtime to add the result to the start of the list""" directive @append(parentID: ID) on FRAGMENT_SPREAD + """@allLists is used to tell the runtime to add the result to all list""" + directive @allLists on FRAGMENT_SPREAD + """ @parentID is used to provide a parentID without specifying position or in situations where it doesn't make sense (eg when deleting a node.) @@ -112,6 +115,9 @@ test('list operations are included', async function () { """@append is used to tell the runtime to add the result to the start of the list""" directive @append(parentID: ID) on FRAGMENT_SPREAD + """@allLists is used to tell the runtime to add the result to all list""" + directive @allLists on FRAGMENT_SPREAD + """ @parentID is used to provide a parentID without specifying position or in situations where it doesn't make sense (eg when deleting a node.) @@ -198,6 +204,9 @@ test("writing twice doesn't duplicate definitions", async function () { """@append is used to tell the runtime to add the result to the start of the list""" directive @append(parentID: ID) on FRAGMENT_SPREAD + """@allLists is used to tell the runtime to add the result to all list""" + directive @allLists on FRAGMENT_SPREAD + """ @parentID is used to provide a parentID without specifying position or in situations where it doesn't make sense (eg when deleting a node.) diff --git a/packages/houdini/src/codegen/transforms/list.ts b/packages/houdini/src/codegen/transforms/list.ts index ce5ada5db..3361f97d4 100644 --- a/packages/houdini/src/codegen/transforms/list.ts +++ b/packages/houdini/src/codegen/transforms/list.ts @@ -27,7 +27,6 @@ export default async function addListFragments( } = {} const errors: Error[] = [] - // look at every document for (const doc of documents) { doc.document = graphql.visit(doc.document, { diff --git a/packages/houdini/src/codegen/transforms/paginate.test.ts b/packages/houdini/src/codegen/transforms/paginate.test.ts index 16094156c..0595ca560 100644 --- a/packages/houdini/src/codegen/transforms/paginate.test.ts +++ b/packages/houdini/src/codegen/transforms/paginate.test.ts @@ -851,6 +851,53 @@ test('query with forwards cursor paginate', async function () { `) }) +test('query with custom first args', async function () { + const docs = [ + mockCollectedDoc( + ` + query Users ($limit: Int!){ + usersByForwardsCursor(first: $limit) @paginate { + edges { + node { + id + } + } + } + } + ` + ), + ] + + // run the pipeline + const config = testConfig() + await runPipeline(config, docs) + + // load the contents of the file + expect(docs[0]?.document).toMatchInlineSnapshot(` + query Users($limit: Int!, $after: String) { + usersByForwardsCursor(first: $limit, after: $after) @paginate { + edges { + node { + id + } + } + edges { + cursor + node { + __typename + } + } + pageInfo { + hasPreviousPage + hasNextPage + startCursor + endCursor + } + } + } + `) +}) + test('query with backwards cursor paginate', async function () { const docs = [ mockCollectedDoc( diff --git a/packages/houdini/src/codegen/transforms/paginate.ts b/packages/houdini/src/codegen/transforms/paginate.ts index c0a61d92d..839db085c 100644 --- a/packages/houdini/src/codegen/transforms/paginate.ts +++ b/packages/houdini/src/codegen/transforms/paginate.ts @@ -21,7 +21,12 @@ import { unwrapType, wrapType } from '../utils' // - generate the query with the fragment embedded using @with to pass query variables through type PaginationFlags = { - [fieldName: string]: { enabled: boolean; type: string; defaultValue?: any } + [fieldName: string]: { + enabled: boolean + type: string + defaultValue?: any + variableName?: string + } } // paginate transform adds the necessary fields for a paginated field @@ -199,13 +204,18 @@ export default async function paginate( let newVariables: Record = Object.fromEntries( Object.entries(flags) - .filter(([, spec]) => spec.enabled) + .filter( + ([, spec]) => + // let's tale the spec enabled AND where we don't have a dedicated variable for it + spec.enabled && spec.variableName === undefined + ) .map(([fieldName, spec]) => [ fieldName, staticVariableDefinition( fieldName, spec.type, - spec.defaultValue + spec.defaultValue, + spec.variableName ), ]) ) @@ -543,10 +553,15 @@ function replaceArgumentsWithVariables( flags[arg.name.value].defaultValue = spec.type === 'Int' ? parseInt(oldValue) : oldValue } + // if we have a variable + if (arg.value.kind === 'Variable') { + flags[arg.name.value].variableName = arg.value.name.value + } + seenArgs[arg.name.value] = true // turn the field into a variable - return variableAsArgument(arg.name.value) + return variableAsArgument(arg.name.value, flags[arg.name.value].variableName) }) // any fields that are enabled but don't have values need to have variable references add @@ -575,7 +590,7 @@ function replaceArgumentsWithVariables( return newArgs } -function variableAsArgument(name: string): graphql.ArgumentNode { +function variableAsArgument(name: string, variable?: string): graphql.ArgumentNode { return { kind: graphql.Kind.ARGUMENT, name: { @@ -586,13 +601,18 @@ function variableAsArgument(name: string): graphql.ArgumentNode { kind: graphql.Kind.VARIABLE, name: { kind: graphql.Kind.NAME, - value: name, + value: variable ?? name, }, }, } } -function staticVariableDefinition(name: string, type: string, defaultValue?: string) { +function staticVariableDefinition( + name: string, + type: string, + defaultValue?: string, + variableName?: string +) { return { kind: graphql.Kind.VARIABLE_DEFINITION, type: { @@ -606,7 +626,7 @@ function staticVariableDefinition(name: string, type: string, defaultValue?: str kind: graphql.Kind.VARIABLE, name: { kind: graphql.Kind.NAME, - value: name, + value: variableName ?? name, }, }, defaultValue: !defaultValue diff --git a/packages/houdini/src/codegen/transforms/schema.ts b/packages/houdini/src/codegen/transforms/schema.ts index 5429d0ebf..a8c6e81c9 100644 --- a/packages/houdini/src/codegen/transforms/schema.ts +++ b/packages/houdini/src/codegen/transforms/schema.ts @@ -43,6 +43,11 @@ directive @${config.listPrependDirective}( """ directive @${config.listAppendDirective}(${config.listDirectiveParentIDArg}: ID) on FRAGMENT_SPREAD +""" + @${config.listAllListsDirective} is used to tell the runtime to add the result to all list +""" +directive @${config.listAllListsDirective} on FRAGMENT_SPREAD + """ @${ config.listParentDirective diff --git a/packages/houdini/src/codegen/validators/typeCheck.test.ts b/packages/houdini/src/codegen/validators/typeCheck.test.ts index 939ff1d04..68631fb00 100755 --- a/packages/houdini/src/codegen/validators/typeCheck.test.ts +++ b/packages/houdini/src/codegen/validators/typeCheck.test.ts @@ -139,6 +139,56 @@ const table: Row[] = [ `, ], }, + { + title: '@prepend & @append on _insert', + pass: false, + documents: [ + `query TestQuery { + user { + friends { + friends @list(name: "Friends") { + id + } + } + } + }`, + `mutation MutationM1 { + addFriend { + ...Friends_insert @prepend @append @allLists + } + }`, + `mutation MutationM2 { + addFriend { + ...Friends_insert @prepend @append @allLists + } + }`, + ], + }, + { + title: '@parentID @allLists on _insert', + pass: false, + documents: [ + `query TestQuery { + user { + friends { + friends @list(name: "Friends") { + id + } + } + } + }`, + `mutation MutationM1 { + addFriend { + ...Friends_insert @parentID @allLists + } + }`, + `mutation MutationM2 { + addFriend { + ...Friends_insert @parentID @allLists + } + }`, + ], + }, { title: '@list name must be unique', pass: false, @@ -894,6 +944,12 @@ for (const { title, pass, documents, check } of table) { ? undefined : check || function (e: Error | Error[]) { + if (title === '@prepend & @append on _insert') { + console.log(`e`, e) + } + + // We want to check that all errors are grouped into 1 throw + // having an array or errors. expect(e).toHaveLength(2) } ) diff --git a/packages/houdini/src/codegen/validators/typeCheck.ts b/packages/houdini/src/codegen/validators/typeCheck.ts index c2e049382..f55a60765 100755 --- a/packages/houdini/src/codegen/validators/typeCheck.ts +++ b/packages/houdini/src/codegen/validators/typeCheck.ts @@ -320,6 +320,8 @@ export default async function typeCheck( listTypes, fragments, }), + // checkMutationOperation + checkMutationOperation(config), // pagination directive can only show up on nodes or the query type nodeDirectives(config, [config.paginateDirective]), // this replaces KnownArgumentNamesRule @@ -415,29 +417,43 @@ const validateLists = ({ return } + // Do we have the parentId another way? + let parentIdFound = false // look for one of the list directives directive = node.directives?.find(({ name }) => [ [config.listPrependDirective, config.listAppendDirective].includes(name.value), ]) - // if there is no directive - if (!directive) { - ctx.reportError( - new graphql.GraphQLError('parentID is required for this list fragment') + if (directive) { + // find the argument holding the parent ID + let parentArg = directive.arguments?.find( + (arg) => arg.name.value === config.listDirectiveParentIDArg ) + if (parentArg) { + parentIdFound = true + } + } + + if (parentIdFound) { + // parentId was found, so we're good to go return } - // find the argument holding the parent ID - let parentArg = directive.arguments?.find( - (arg) => arg.name.value === config.listDirectiveParentIDArg + // look for allLists directive + const allLists = node.directives?.find( + ({ name }) => config.listAllListsDirective === name.value ) - if (!parentArg) { - ctx.reportError( - new graphql.GraphQLError('parentID is required for this list fragment') - ) + // if there is the directive or it's + if (allLists || config.defaultListTarget === 'all') { return } + + ctx.reportError( + new graphql.GraphQLError( + `For this list fragment, you need to add or @${config.listParentDirective} or @${config.listAllListsDirective} directive to specify the behavior` + ) + ) + return }, // if we run into a directive that points to a list, make sure that list exists Directive(node) { @@ -925,6 +941,45 @@ function nodeDirectives(config: Config, directives: string[]) { } } +function checkMutationOperation(config: Config) { + return function (ctx: graphql.ValidationContext): graphql.ASTVisitor { + return { + FragmentSpread(node, _, __, ___, ancestors) { + const append = node.directives?.find( + (c) => c.name.value === config.listAppendDirective + ) + + const prepend = node.directives?.find( + (c) => c.name.value === config.listPrependDirective + ) + if (append && prepend) { + ctx.reportError( + new graphql.GraphQLError( + `You can't apply both @${config.listPrependDirective} and @${config.listAppendDirective} at the same time` + ) + ) + return + } + + const parentId = node.directives?.find( + (c) => c.name.value === config.listParentDirective + ) + const allLists = node.directives?.find( + (c) => c.name.value === config.listAllListsDirective + ) + if (parentId && allLists) { + ctx.reportError( + new graphql.GraphQLError( + `You can't apply both @${config.listParentDirective} and @${config.listAllListsDirective} at the same time` + ) + ) + return + } + }, + } + } +} + export function getAndVerifyNodeInterface(config: Config): graphql.GraphQLInterfaceType | null { const { schema } = config diff --git a/packages/houdini/src/lib/config.ts b/packages/houdini/src/lib/config.ts index 8afce405e..30022fb0b 100644 --- a/packages/houdini/src/lib/config.ts +++ b/packages/houdini/src/lib/config.ts @@ -38,6 +38,8 @@ export class Config { cacheBufferSize?: number defaultCachePolicy: CachePolicy defaultPartial: boolean + internalListPosition: 'first' | 'last' + defaultListTarget: 'all' | null = null definitionsFolder?: string newSchema: string = '' newDocuments: string = '' @@ -77,6 +79,8 @@ export class Config { definitionsPath, defaultCachePolicy = CachePolicy.CacheOrNetwork, defaultPartial = false, + defaultListPosition = 'append', + defaultListTarget = null, defaultKeys, types = {}, logLevel, @@ -116,6 +120,8 @@ export class Config { this.cacheBufferSize = cacheBufferSize this.defaultCachePolicy = defaultCachePolicy this.defaultPartial = defaultPartial + this.internalListPosition = defaultListPosition === 'append' ? 'last' : 'first' + this.defaultListTarget == defaultListTarget this.definitionsFolder = definitionsPath this.logLevel = ((logLevel as LogLevel) || LogLevel.Summary).toLowerCase() as LogLevel this.disableMasking = disableMasking @@ -470,6 +476,10 @@ export class Config { return 'parentID' } + get listAllListsDirective() { + return 'allLists' + } + get listNameArg() { return 'name' } @@ -578,6 +588,7 @@ export class Config { this.listPrependDirective, this.listAppendDirective, this.listDirectiveParentIDArg, + this.listAllListsDirective, this.whenDirective, this.whenNotDirective, this.argumentsDirective, diff --git a/packages/houdini/src/runtime/cache/cache.ts b/packages/houdini/src/runtime/cache/cache.ts index 561f35259..ee87d095a 100644 --- a/packages/houdini/src/runtime/cache/cache.ts +++ b/packages/houdini/src/runtime/cache/cache.ts @@ -110,8 +110,8 @@ export class Cache { } // return the list handler to mutate a named list in the cache - list(name: string, parentID?: string | {}): ListCollection { - const handler = this._internal_unstable.lists.get(name, parentID) + list(name: string, parentID?: string, allLists?: boolean): ListCollection { + const handler = this._internal_unstable.lists.get(name, parentID, allLists) if (!handler) { throw new Error( `Cannot find list with name: ${name}${ @@ -570,7 +570,10 @@ class CacheInternal { } // if the necessary list doesn't exist, don't do anything - if (operation.list && !this.lists.get(operation.list, parentID)) { + if ( + operation.list && + !this.lists.get(operation.list, parentID, operation.target === 'all') + ) { continue } @@ -585,7 +588,7 @@ class CacheInternal { operation.list ) { this.cache - .list(operation.list, parentID) + .list(operation.list, parentID, operation.target === 'all') .when(operation.when) .addToList(fields, target, variables, operation.position || 'last') } @@ -598,7 +601,7 @@ class CacheInternal { operation.list ) { this.cache - .list(operation.list, parentID) + .list(operation.list, parentID, operation.target === 'all') .when(operation.when) .remove(target, variables) } @@ -624,7 +627,7 @@ class CacheInternal { operation.list ) { this.cache - .list(operation.list, parentID) + .list(operation.list, parentID, operation.target === 'all') .when(operation.when) .toggleElement(fields, target, variables, operation.position || 'last') } diff --git a/packages/houdini/src/runtime/cache/lists.ts b/packages/houdini/src/runtime/cache/lists.ts index 8d09d39b2..0c036863e 100644 --- a/packages/houdini/src/runtime/cache/lists.ts +++ b/packages/houdini/src/runtime/cache/lists.ts @@ -16,7 +16,7 @@ export class ListManager { private listsByField: Map> = new Map() - get(listName: string, id?: string | {}) { + get(listName: string, id?: string, allLists?: boolean) { const matches = this.lists.get(listName) // if we don't have a list by that name, we're done @@ -24,11 +24,29 @@ export class ListManager { return null } + // if we want to update all list, return all matches + if (allLists) { + return new ListCollection( + Array.from(matches, ([key, value]) => [...value.lists]).flat() + ) + } + const head = [...matches.values()][0] + // the provided id won't match the cache's ID so we have to compute the internal ID, using + // one of the matches to figure out the type of the list element + const { recordType } = head.lists[0] + const parentID = id ? this.cache._internal_unstable.id(recordType || '', id)! : this.rootID + // if there is only one list with that name, return it if (matches?.size === 1) { - return head + // if there is no provided id, just use the first one + if (!id) { + return head + } + + // otherwise we're only safe to use the head if it matches the parentID + return parentID === Array.from(matches.keys())[0] ? head : null } // there are multiple versions of the list so the user must @@ -36,17 +54,13 @@ export class ListManager { // it would have been caught in the size === 1 check above since // root's ID is fixed if (!id) { - throw new Error( - `Found multiple instances of "${listName}". Please provide a ` + - `parentID that corresponds to the object containing the field marked with @list or @paginate.` + console.error( + `Found multiple instances of "${listName}". Please provide one of @parentID or @allLists directives to ` + + `help identify which list you want modify. For more information, visit this guide: https://www.houdinigraphql.com/api/graphql#parentidvalue-string ` ) + return null } - // the provided id won't match the cache's ID so we have to compute the internal ID, using - // one of the matches to figure out the type of the list element - const { recordType } = head.lists[0] - const parentID = id ? this.cache._internal_unstable.id(recordType || '', id)! : this.rootID - // return the list pointing to the correct parent return this.lists.get(listName)?.get(parentID) } diff --git a/packages/houdini/src/runtime/cache/tests/list.test.ts b/packages/houdini/src/runtime/cache/tests/list.test.ts index ca81f9fb0..47cfaedb7 100644 --- a/packages/houdini/src/runtime/cache/tests/list.test.ts +++ b/packages/houdini/src/runtime/cache/tests/list.test.ts @@ -1624,10 +1624,6 @@ test('append operation', function () { { action: 'insert', list: 'All_Users', - parentID: { - kind: 'String', - value: cache._internal_unstable.id('User', '1')!, - }, }, ], fields: { @@ -1646,7 +1642,7 @@ test('append operation', function () { }) // make sure we just added to the list - expect([...cache.list('All_Users', cache._internal_unstable.id('User', '1')!)]).toHaveLength(1) + expect([...cache.list('All_Users', '1')]).toHaveLength(1) }) test('append from list', function () { @@ -1716,10 +1712,6 @@ test('append from list', function () { { action: 'insert', list: 'All_Users', - parentID: { - kind: 'String', - value: cache._internal_unstable.id('User', '1')!, - }, }, ], fields: { @@ -1736,7 +1728,7 @@ test('append from list', function () { }) // make sure we just added to the list - expect([...cache.list('All_Users', cache._internal_unstable.id('User', '1')!)]).toHaveLength(2) + expect([...cache.list('All_Users', '1')]).toHaveLength(2) }) test('toggle list', function () { @@ -1823,10 +1815,6 @@ test('toggle list', function () { { action: 'toggle', list: 'All_Users', - parentID: { - kind: 'String', - value: cache._internal_unstable.id('User', '1')!, - }, }, ], fields: { @@ -1841,23 +1829,15 @@ test('toggle list', function () { // write some data to a different location with a new user // that should be added to the list cache.write({ selection: toggleSelection, data: { newUser: { id: '3' } } }) - expect([...cache.list('All_Users', cache._internal_unstable.id('User', '1')!)]).toEqual([ - 'User:5', - 'User:3', - ]) + expect([...cache.list('All_Users', '1')]).toEqual(['User:5', 'User:3']) // toggle the user again to remove the user cache.write({ selection: toggleSelection, data: { newUser: { id: '3' } } }) - expect([...cache.list('All_Users', cache._internal_unstable.id('User', '1')!)]).toEqual([ - 'User:5', - ]) + expect([...cache.list('All_Users', '1')]).toEqual(['User:5']) // toggle the user again to add the user back cache.write({ selection: toggleSelection, data: { newUser: { id: '3' } } }) - expect([...cache.list('All_Users', cache._internal_unstable.id('User', '1')!)]).toEqual([ - 'User:5', - 'User:3', - ]) + expect([...cache.list('All_Users', '1')]).toEqual(['User:5', 'User:3']) }) test('append when operation', function () { @@ -1933,10 +1913,6 @@ test('append when operation', function () { { action: 'insert', list: 'All_Users', - parentID: { - kind: 'String', - value: cache._internal_unstable.id('User', '1')!, - }, when: { must: { value: 'not-foo', @@ -1960,7 +1936,7 @@ test('append when operation', function () { }) // make sure we just added to the list - expect([...cache.list('All_Users', cache._internal_unstable.id('User', '1')!)]).toHaveLength(0) + expect([...cache.list('All_Users', '1')]).toHaveLength(0) }) test('prepend when operation', function () { @@ -2036,10 +2012,6 @@ test('prepend when operation', function () { { action: 'insert', list: 'All_Users', - parentID: { - kind: 'String', - value: cache._internal_unstable.id('User', '1')!, - }, position: 'first', when: { must: { @@ -2064,7 +2036,7 @@ test('prepend when operation', function () { }) // make sure we just added to the list - expect([...cache.list('All_Users', cache._internal_unstable.id('User', '1')!)]).toHaveLength(0) + expect([...cache.list('All_Users', '1')]).toHaveLength(0) }) test('prepend operation', function () { @@ -2154,10 +2126,6 @@ test('prepend operation', function () { { action: 'insert', list: 'All_Users', - parentID: { - kind: 'String', - value: cache._internal_unstable.id('User', '1')!, - }, position: 'first', }, ], @@ -2177,10 +2145,7 @@ test('prepend operation', function () { }) // make sure we just added to the list - expect([...cache.list('All_Users', cache._internal_unstable.id('User', '1')!)]).toEqual([ - 'User:3', - 'User:2', - ]) + expect([...cache.list('All_Users', '1')]).toEqual(['User:3', 'User:2']) }) test('remove operation', function () { @@ -2265,10 +2230,6 @@ test('remove operation', function () { { action: 'remove', list: 'All_Users', - parentID: { - kind: 'String', - value: cache._internal_unstable.id('User', '1')!, - }, }, ], fields: { @@ -2287,7 +2248,7 @@ test('remove operation', function () { }) // make sure we removed the element from the list - expect([...cache.list('All_Users', cache._internal_unstable.id('User', '1')!)]).toHaveLength(0) + expect([...cache.list('All_Users', '1')]).toHaveLength(0) }) test('remove operation from list', function () { @@ -2375,10 +2336,6 @@ test('remove operation from list', function () { { action: 'remove', list: 'All_Users', - parentID: { - kind: 'String', - value: cache._internal_unstable.id('User', '1')!, - }, }, ], fields: { @@ -2395,7 +2352,7 @@ test('remove operation from list', function () { }) // make sure we removed the element from the list - expect([...cache.list('All_Users', cache._internal_unstable.id('User', '1')!)]).toHaveLength(0) + expect([...cache.list('All_Users', '1')]).toHaveLength(0) }) test('delete operation', function () { @@ -2498,7 +2455,7 @@ test('delete operation', function () { }) // make sure we removed the element from the list - expect([...cache.list('All_Users', cache._internal_unstable.id('User', '1')!)]).toHaveLength(0) + expect([...cache.list('All_Users', '1')]).toHaveLength(0) expect(cache._internal_unstable.storage.topLayer.operations['User:2'].deleted).toBeTruthy() }) @@ -2606,7 +2563,7 @@ test('delete operation from list', function () { }) // make sure we removed the element from the list - expect([...cache.list('All_Users', cache._internal_unstable.id('User', '1')!)]).toHaveLength(0) + expect([...cache.list('All_Users', '1')]).toHaveLength(0) expect(cache._internal_unstable.storage.topLayer.operations['User:2'].deleted).toBeTruthy() expect(cache._internal_unstable.storage.topLayer.operations['User:3'].deleted).toBeTruthy() @@ -2753,7 +2710,7 @@ test('delete operation from connection', function () { }) // make sure we removed the element from the list - expect([...cache.list('All_Users', cache._internal_unstable.id('User', '1')!)]).toHaveLength(0) + expect([...cache.list('All_Users', '1')]).toHaveLength(0) expect(cache._internal_unstable.storage.topLayer.operations['User:2'].deleted).toBeTruthy() }) @@ -3225,10 +3182,6 @@ test('list operations fail silently', function () { { action: 'insert', list: 'All_Users', - parentID: { - kind: 'String', - value: cache._internal_unstable.id('User', '1')!, - }, }, ], fields: { @@ -3399,7 +3352,7 @@ test('parentID must be passed if there are multiple instances of a list handler' // instantiate a cache const cache = new Cache(config) - const friendsSelection = { + const friendsSelection: SubscriptionSelection = { friends: { type: 'User', keyRaw: 'friends', @@ -3471,11 +3424,73 @@ test('parentID must be passed if there are multiple instances of a list handler' {} ) - // looking up the list without a parent id should fail - expect(() => cache.list('All_Users')).toThrow() - expect(cache.list('All_Users', '1')!.lists[0].recordID).toEqual( - cache._internal_unstable.id('User', '1') - ) + // append a value to the store + const writeSelectionNoParentID: SubscriptionSelection = { + user: { + type: 'User', + keyRaw: 'user', + operations: [ + { + action: 'insert', + list: 'All_Users', + }, + ], + fields: { + id: { + type: 'ID', + keyRaw: 'id', + }, + firstName: { + type: 'String', + keyRaw: 'firstName', + }, + }, + }, + } + const writeSelectionWithParentID: SubscriptionSelection = { + user: { + type: 'User', + keyRaw: 'user', + operations: [ + { + action: 'insert', + list: 'All_Users', + parentID: { + kind: 'String', + value: '1', + }, + }, + ], + fields: { + id: { + type: 'ID', + keyRaw: 'id', + }, + firstName: { + type: 'String', + keyRaw: 'firstName', + }, + }, + }, + } + + // write the value without a parent ID + cache.write({ + selection: writeSelectionNoParentID, + data: { user: { id: '2', firstName: 'test' } }, + }) + // make sure we didn't modify the lists + expect([...cache.list('All_Users', '1')]).toHaveLength(1) + expect([...cache.list('All_Users', '2')]).toHaveLength(0) + + // write the value with a parent ID + cache.write({ + selection: writeSelectionWithParentID, + data: { user: { id: '2', firstName: 'test' } }, + }) + // make sure we modified the correct list + expect([...cache.list('All_Users', '1')]).toHaveLength(2) + expect([...cache.list('All_Users', '2')]).toHaveLength(0) }) test('append in abstract list', function () { @@ -3745,3 +3760,95 @@ test('list operations on interface fields without a well defined parent update t }, }) }) + +test("parentID ignores single lists that don't match", function () { + // instantiate a cache + const cache = new Cache(config) + + // create a list we will add to + cache.write({ + selection: { + viewer: { + type: 'User', + keyRaw: 'viewer', + fields: { + id: { + type: 'ID', + keyRaw: 'id', + }, + }, + }, + }, + data: { + viewer: { + id: '1', + }, + }, + }) + + // subscribe to the data to register the list + cache.subscribe( + { + rootType: 'User', + selection: { + friends: { + type: 'User', + keyRaw: 'friends', + list: { + name: 'All_Users', + connection: false, + type: 'User', + }, + fields: { + id: { + type: 'ID', + keyRaw: 'id', + }, + firstName: { + type: 'String', + keyRaw: 'firstName', + }, + }, + }, + }, + parentID: cache._internal_unstable.id('User', '1')!, + set: vi.fn(), + }, + {} + ) + + // write some data to a different location with a new user + // that should be added to the list + cache.write({ + selection: { + newUser: { + type: 'User', + keyRaw: 'newUser', + operations: [ + { + action: 'insert', + list: 'All_Users', + parentID: { + kind: 'String', + value: '2', + }, + }, + ], + fields: { + id: { + type: 'ID', + keyRaw: 'id', + }, + }, + }, + }, + data: { + newUser: { + id: '3', + }, + }, + }) + + // make sure we just added to the list + expect([...cache.list('All_Users', '1')]).toHaveLength(0) +}) diff --git a/packages/houdini/src/runtime/lib/config.ts b/packages/houdini/src/runtime/lib/config.ts index a2e07f52d..67b36acf9 100644 --- a/packages/houdini/src/runtime/lib/config.ts +++ b/packages/houdini/src/runtime/lib/config.ts @@ -100,13 +100,13 @@ export type ConfigFile = { definitionsPath?: string /** - * One of "kit" or "svelte". Used to tell the preprocessor what kind of loading paradigm to generate for you. (default: kit) + * One of "kit" or "svelte". Used to tell the preprocessor what kind of loading paradigm to generate for you. (default: `kit`) * @deprecated please follow the steps here: http://www.houdinigraphql.com/guides/release-notes#0170 */ framework?: 'kit' | 'svelte' /** - * One of "esm" or "commonjs". Tells the artifact generator what kind of modules to create. (default: esm) + * One of "esm" or "commonjs". Tells the artifact generator what kind of modules to create. (default: `esm`) */ module?: 'esm' | 'commonjs' @@ -125,6 +125,16 @@ export type ConfigFile = { */ defaultPartial?: boolean + /** + * Specifies whether mutations should append or prepend list. For more information: https://www.houdinigraphql.com/api/graphql (default: `append`) + */ + defaultListPosition?: 'append' | 'prepend' + + /** + * 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`) + */ + defaultListTarget?: 'all' | null + /** * 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 */ diff --git a/packages/houdini/src/runtime/lib/types.ts b/packages/houdini/src/runtime/lib/types.ts index 30dd824e9..90d9150af 100644 --- a/packages/houdini/src/runtime/lib/types.ts +++ b/packages/houdini/src/runtime/lib/types.ts @@ -121,6 +121,7 @@ export type MutationOperation = { value: string } position?: 'first' | 'last' + target?: 'all' when?: ListWhen } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aea38677d..c5a94074c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: 5.4 +lockfileVersion: 5.4 importers: @@ -57,32 +57,6 @@ importers: graphql-ws: 5.11.2_graphql@15.8.0 ws: 8.8.1 - e2e/my-app: - specifiers: - '@sveltejs/adapter-auto': next - '@sveltejs/kit': next - graphql: ^15.8.0 - houdini: ^0.17.5 - houdini-svelte: ^0.17.5 - svelte: ^3.44.0 - svelte-check: ^2.7.1 - svelte-preprocess: ^4.10.6 - tslib: ^2.3.1 - typescript: ^4.7.4 - vite: ^3.1.0 - devDependencies: - '@sveltejs/adapter-auto': 1.0.0-next.87 - '@sveltejs/kit': 1.0.0-next.539_svelte@3.52.0+vite@3.1.6 - graphql: 15.8.0 - houdini: link:../../packages/houdini - houdini-svelte: link:../../packages/houdini-svelte - svelte: 3.52.0 - svelte-check: 2.8.1_svelte@3.52.0 - svelte-preprocess: 4.10.7_besnmoibwkhwtentvwuriss7pa - tslib: 2.4.0 - typescript: 4.8.4 - vite: 3.1.6 - e2e/next: specifiers: '@types/node': ^18.7.23 @@ -116,8 +90,8 @@ importers: specifiers: '@kitql/helper': ^0.5.0 '@playwright/test': 1.25.0 - '@sveltejs/adapter-auto': 1.0.0-next.66 - '@sveltejs/kit': 1.0.0-next.510 + '@sveltejs/adapter-auto': 1.0.0-next.88 + '@sveltejs/kit': 1.0.0-next.547 '@typescript-eslint/eslint-plugin': ^5.10.1 '@typescript-eslint/parser': ^5.10.1 concurrently: 7.1.0 @@ -139,8 +113,8 @@ importers: devDependencies: '@kitql/helper': 0.5.0 '@playwright/test': 1.25.0 - '@sveltejs/adapter-auto': 1.0.0-next.66 - '@sveltejs/kit': 1.0.0-next.510_svelte@3.52.0+vite@3.1.4 + '@sveltejs/adapter-auto': 1.0.0-next.88 + '@sveltejs/kit': 1.0.0-next.547_svelte@3.52.0+vite@3.1.4 '@typescript-eslint/eslint-plugin': 5.35.1_hy4by47wjjtoupqk2r7jy5xf2e '@typescript-eslint/parser': 5.35.1_pyvvhc3zqdua4akflcggygkl44 concurrently: 7.1.0 @@ -164,7 +138,7 @@ importers: specifiers: '@graphql-yoga/node': ^2.8.0 '@kitql/vite-plugin-watch-and-run': ^0.4.0 - '@sveltejs/kit': 1.0.0-next.510 + '@sveltejs/kit': 1.0.0-next.547 concurrently: ^6.2.1 graphql: ^15.8.0 graphql-relay: 0.10.0 @@ -185,7 +159,7 @@ importers: devDependencies: '@graphql-yoga/node': 2.13.13_graphql@15.8.0 '@kitql/vite-plugin-watch-and-run': 0.4.2 - '@sveltejs/kit': 1.0.0-next.510_svelte@3.50.1+vite@3.1.4 + '@sveltejs/kit': 1.0.0-next.547_svelte@3.50.1+vite@3.1.4 concurrently: 6.5.1 graphql: 15.8.0 houdini: link:../packages/houdini @@ -312,7 +286,7 @@ importers: packages/houdini-svelte: specifiers: '@kitql/helper': ^0.5.0 - '@sveltejs/kit': 1.0.0-next.505 + '@sveltejs/kit': 1.0.0-next.547 '@types/minimatch': ^5.1.2 ast-types: ^0.15.1 estree-walker: ^3.0.1 @@ -325,7 +299,7 @@ importers: vitest: ^0.23.4 dependencies: '@kitql/helper': 0.5.0 - '@sveltejs/kit': 1.0.0-next.505_svelte@3.52.0+vite@3.1.6 + '@sveltejs/kit': 1.0.0-next.547_svelte@3.52.0+vite@3.2.4 ast-types: 0.15.2 estree-walker: 3.0.1 graphql: 15.8.0 @@ -1134,10 +1108,7 @@ packages: /@cloudflare/workers-types/3.14.1: resolution: {integrity: sha512-B1/plF62pt+H2IJHvApK8fdOJAVsvojvacuac8x8s+JIyqbropMyqNqHTKLm3YD8ZFLGwYeFTudU+PQ7vGvBdA==} - - /@cloudflare/workers-types/3.18.0: - resolution: {integrity: sha512-ehKOJVLMeR+tZkYhWEaLYQxl0TaIZu/kE86HF3/RidR8Xv5LuQxpbh+XXAoKVqsaphWLhIgBhgnlN5HGdheXSQ==} - dev: true + dev: false /@envelop/core/2.5.0_graphql@15.8.0: resolution: {integrity: sha512-nlDC9q75bjvS/ajbkkVlwGPSYlWhZOQ6StxMTEjvUVefL4o49NpMlGgxfN2mJ64y1CJ3MI/bIemZ3jOHmiv3Og==} @@ -1196,6 +1167,7 @@ packages: cpu: [loong64] os: [linux] requiresBuild: true + dev: false optional: true /@esbuild/linux-loong64/0.15.10: @@ -1334,6 +1306,7 @@ packages: /@iarna/toml/2.2.5: resolution: {integrity: sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==} + dev: false /@istanbuljs/load-nyc-config/1.1.0: resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} @@ -1482,6 +1455,7 @@ packages: transitivePeerDependencies: - encoding - supports-color + dev: false /@mdx-js/util/1.6.22: resolution: {integrity: sha512-H1rQc1ZOHANWBvPcW+JpGwr+juXSxM8Q8YCkm3GhZd8REu1fHR3z99CErO1p9pkcfcxZnMdIZdIsXkOHY0NilA==} @@ -1663,6 +1637,7 @@ packages: dependencies: estree-walker: 2.0.2 picomatch: 2.3.1 + dev: false /@rushstack/eslint-patch/1.2.0: resolution: {integrity: sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==} @@ -1682,26 +1657,10 @@ packages: - supports-color dev: false - /@sveltejs/adapter-auto/1.0.0-next.66: - resolution: {integrity: sha512-p78AQaSDHkLS5EFGqCF2xrLHMjKxx6wTLUvnP26cu2llh/VV4NihQ0rheVNgPWL+tGZpVznhrUG8fWmJxPciug==} - dependencies: - '@sveltejs/adapter-cloudflare': 1.0.0-next.32 - '@sveltejs/adapter-netlify': 1.0.0-next.72 - '@sveltejs/adapter-vercel': 1.0.0-next.68 - transitivePeerDependencies: - - encoding - - supports-color - dev: true - - /@sveltejs/adapter-auto/1.0.0-next.87: - resolution: {integrity: sha512-0MPCKo3aY1i3oESI6ZZikOB+MDV89WlWj4ot+/WEsP1J2uDA2HSirCZYWDnLB5i00HDHzqwBOfDDwHJ00pPC4w==} + /@sveltejs/adapter-auto/1.0.0-next.88: + resolution: {integrity: sha512-WcbELnu0Dz/72T1gbhDHxmNevVMEnQeNVi2IYrEj/uEKSMet4LOIrPv3DAAl8NETgBfzNJbMQpE7r9yL67CMTA==} dependencies: - '@sveltejs/adapter-cloudflare': 1.0.0-next.40 - '@sveltejs/adapter-netlify': 1.0.0-next.84 - '@sveltejs/adapter-vercel': 1.0.0-next.81 - transitivePeerDependencies: - - encoding - - supports-color + import-meta-resolve: 2.1.0 dev: true /@sveltejs/adapter-cloudflare/1.0.0-next.31: @@ -1712,22 +1671,6 @@ packages: worktop: 0.8.0-next.14 dev: false - /@sveltejs/adapter-cloudflare/1.0.0-next.32: - resolution: {integrity: sha512-tzkUsdQlBk9xUjcGUOBYos4HKaeaXvz9v4TQ1QS2yIHEtL5xvMEDPZ94/DB2gPL4LZCnYbdY2lsy5HCsoN0hkQ==} - dependencies: - '@cloudflare/workers-types': 3.14.1 - esbuild: 0.14.54 - worktop: 0.8.0-next.14 - dev: true - - /@sveltejs/adapter-cloudflare/1.0.0-next.40: - resolution: {integrity: sha512-KT4TK40T9pl24nPFWHgw1QwAv9AjOkUymjFpS07Ro2zeBHJVgga1Jl0OA1bsiyEiLNRivNRwaWHFySlZ2JJpxQ==} - dependencies: - '@cloudflare/workers-types': 3.18.0 - esbuild: 0.15.13 - worktop: 0.8.0-next.14 - dev: true - /@sveltejs/adapter-netlify/1.0.0-next.71: resolution: {integrity: sha512-la1CGtWO1xul1L3zEoFAoc4EX2uxZjrZcOMS3tkKB8drxhbQsNbnTE6fmSSMFiZXhxaikczrBgQwqIaDkLTmZg==} dependencies: @@ -1737,23 +1680,6 @@ packages: tiny-glob: 0.2.9 dev: false - /@sveltejs/adapter-netlify/1.0.0-next.72: - resolution: {integrity: sha512-g570hYAMkgrJfo/TRg3DZFmlR7bNFHECFPOMgc8R+f28ROap/nXA8ICbiSBF7+zJ5JXvJbqHGjERSsyhEq+59g==} - dependencies: - '@iarna/toml': 2.2.5 - esbuild: 0.14.54 - set-cookie-parser: 2.5.1 - tiny-glob: 0.2.9 - dev: true - - /@sveltejs/adapter-netlify/1.0.0-next.84: - resolution: {integrity: sha512-i4vf3to0sV/iI39UPPhlVjOP+jZCZ048M4oHkqDM1FfJwACwgXaysdF2t4X0DV3loLmrkfarwbatjbGIECA9uQ==} - dependencies: - '@iarna/toml': 2.2.5 - esbuild: 0.15.13 - set-cookie-parser: 2.5.1 - dev: true - /@sveltejs/adapter-vercel/1.0.0-next.67: resolution: {integrity: sha512-xg85d/vlivbTaZu70zmaPNkrY1YZhDrcxljuwVWO0LCzA4DACIA7CnXI9klUiXM5SPpsB8BhY6dS8sW5cDYWzw==} dependencies: @@ -1764,26 +1690,6 @@ packages: - supports-color dev: false - /@sveltejs/adapter-vercel/1.0.0-next.68: - resolution: {integrity: sha512-ImM+fDwGkVaf920Wzh284nfAfu/WoPXCpMwog0kveIODVgCozbpJY55fO860LccqdS0YDyeFqOUrZJCqcYNx4w==} - dependencies: - '@vercel/nft': 0.21.0 - esbuild: 0.14.54 - transitivePeerDependencies: - - encoding - - supports-color - dev: true - - /@sveltejs/adapter-vercel/1.0.0-next.81: - resolution: {integrity: sha512-cuNolQSqabSs97J2hn9bnRDOscihIO+VEYltsc+POLU/ecv7pbUm1qdRakeG3+ehK1mfZ9dub6vEVuLKhm+Qng==} - dependencies: - '@vercel/nft': 0.22.1 - esbuild: 0.15.13 - transitivePeerDependencies: - - encoding - - supports-color - dev: true - /@sveltejs/kit/1.0.0-next.405_svelte@3.49.0+vite@3.0.9: resolution: {integrity: sha512-jHSa74F7k+hC+0fof75g/xm/+1M5sM66Qt6v8eLLMSgjkp36Lb5xOioBhbl6w0NYoE5xysLsBWuu+yHytfvCBA==} engines: {node: '>=16.9'} @@ -1804,93 +1710,64 @@ packages: - supports-color dev: false - /@sveltejs/kit/1.0.0-next.505_svelte@3.52.0+vite@3.1.6: - resolution: {integrity: sha512-vg2WvWiUB7useJZOKbdM8WOXv10WD7SVDER74IXd1t/TWb/uXHzwMQ/ZAsXZFdxQsz1cDGFub3iC76PW+j58Pg==} + /@sveltejs/kit/1.0.0-next.547_svelte@3.50.1+vite@3.1.4: + resolution: {integrity: sha512-QhmoRt0nHISyl+0KRUoo/lod79eBSBgBqxb3E1hWLQLKF7vX5bCkXcZ84WReQtDwHkqvPrmJK2IBcF6za5nhjw==} engines: {node: '>=16.14'} hasBin: true requiresBuild: true peerDependencies: svelte: ^3.44.0 - vite: ^3.1.0 - dependencies: - '@sveltejs/vite-plugin-svelte': 1.0.5_svelte@3.52.0+vite@3.1.6 - '@types/cookie': 0.5.1 - cookie: 0.5.0 - devalue: 3.1.3 - kleur: 4.1.5 - magic-string: 0.26.3 - mime: 3.0.0 - node-fetch: 3.2.10 - sade: 1.8.1 - set-cookie-parser: 2.5.1 - sirv: 2.0.2 - svelte: 3.52.0 - tiny-glob: 0.2.9 - undici: 5.11.0 - vite: 3.1.6 - transitivePeerDependencies: - - diff-match-patch - - supports-color - dev: false - - /@sveltejs/kit/1.0.0-next.510_svelte@3.50.1+vite@3.1.4: - resolution: {integrity: sha512-i96sRqEzNP1dOaQQ2aR38H6emdQJhc1qr5KqNyjqi3Wb0sDAG49cQxMRUOdydkVKZkG/o7PvC5qdyblVfalAdA==} - engines: {node: '>=16.14'} - hasBin: true - requiresBuild: true - peerDependencies: - svelte: ^3.44.0 - vite: ^3.1.0 + vite: ^3.2.0 dependencies: - '@sveltejs/vite-plugin-svelte': 1.0.5_svelte@3.50.1+vite@3.1.4 + '@sveltejs/vite-plugin-svelte': 1.2.0_svelte@3.50.1+vite@3.1.4 '@types/cookie': 0.5.1 cookie: 0.5.0 - devalue: 4.0.0 + devalue: 4.2.0 kleur: 4.1.5 - magic-string: 0.26.3 + magic-string: 0.26.7 mime: 3.0.0 sade: 1.8.1 set-cookie-parser: 2.5.1 sirv: 2.0.2 svelte: 3.50.1 tiny-glob: 0.2.9 - undici: 5.11.0 + undici: 5.12.0 vite: 3.1.4 transitivePeerDependencies: - diff-match-patch - supports-color dev: true - /@sveltejs/kit/1.0.0-next.510_svelte@3.52.0+vite@3.1.4: - resolution: {integrity: sha512-i96sRqEzNP1dOaQQ2aR38H6emdQJhc1qr5KqNyjqi3Wb0sDAG49cQxMRUOdydkVKZkG/o7PvC5qdyblVfalAdA==} + /@sveltejs/kit/1.0.0-next.547_svelte@3.52.0+vite@3.1.4: + resolution: {integrity: sha512-QhmoRt0nHISyl+0KRUoo/lod79eBSBgBqxb3E1hWLQLKF7vX5bCkXcZ84WReQtDwHkqvPrmJK2IBcF6za5nhjw==} engines: {node: '>=16.14'} hasBin: true requiresBuild: true peerDependencies: svelte: ^3.44.0 - vite: ^3.1.0 + vite: ^3.2.0 dependencies: - '@sveltejs/vite-plugin-svelte': 1.0.5_svelte@3.52.0+vite@3.1.4 + '@sveltejs/vite-plugin-svelte': 1.2.0_svelte@3.52.0+vite@3.1.4 '@types/cookie': 0.5.1 cookie: 0.5.0 - devalue: 4.0.0 + devalue: 4.2.0 kleur: 4.1.5 - magic-string: 0.26.3 + magic-string: 0.26.7 mime: 3.0.0 sade: 1.8.1 set-cookie-parser: 2.5.1 sirv: 2.0.2 svelte: 3.52.0 tiny-glob: 0.2.9 - undici: 5.11.0 + undici: 5.12.0 vite: 3.1.4 transitivePeerDependencies: - diff-match-patch - supports-color dev: true - /@sveltejs/kit/1.0.0-next.539_svelte@3.52.0+vite@3.1.6: - resolution: {integrity: sha512-BCD9lMiNoz1S5k1R+4s0pkyMwiqtut5kAXgC7qo3WaJnzXxBJziJfkyJZT0fM6mrEHlcdJ/o5hBkswyVHsxqtA==} + /@sveltejs/kit/1.0.0-next.547_svelte@3.52.0+vite@3.2.4: + resolution: {integrity: sha512-QhmoRt0nHISyl+0KRUoo/lod79eBSBgBqxb3E1hWLQLKF7vX5bCkXcZ84WReQtDwHkqvPrmJK2IBcF6za5nhjw==} engines: {node: '>=16.14'} hasBin: true requiresBuild: true @@ -1898,7 +1775,7 @@ packages: svelte: ^3.44.0 vite: ^3.2.0 dependencies: - '@sveltejs/vite-plugin-svelte': 1.1.0_svelte@3.52.0+vite@3.1.6 + '@sveltejs/vite-plugin-svelte': 1.2.0_svelte@3.52.0+vite@3.2.4 '@types/cookie': 0.5.1 cookie: 0.5.0 devalue: 4.2.0 @@ -1911,11 +1788,11 @@ packages: svelte: 3.52.0 tiny-glob: 0.2.9 undici: 5.12.0 - vite: 3.1.6 + vite: 3.2.4 transitivePeerDependencies: - diff-match-patch - supports-color - dev: true + dev: false /@sveltejs/vite-plugin-svelte/1.0.1_svelte@3.49.0+vite@3.0.9: resolution: {integrity: sha512-PorCgUounn0VXcpeJu+hOweZODKmGuLHsLomwqSj+p26IwjjGffmYQfVHtiTWq+NqaUuuHWWG7vPge6UFw4Aeg==} @@ -1932,7 +1809,7 @@ packages: debug: 4.3.4 deepmerge: 4.2.2 kleur: 4.1.5 - magic-string: 0.26.3 + magic-string: 0.26.7 svelte: 3.49.0 svelte-hmr: 0.14.12_svelte@3.49.0 vite: 3.0.9 @@ -1940,8 +1817,8 @@ packages: - supports-color dev: false - /@sveltejs/vite-plugin-svelte/1.0.5_svelte@3.50.1+vite@3.1.4: - resolution: {integrity: sha512-CmSdSow0Dr5ua1A11BQMtreWnE0JZmkVIcRU/yG3PKbycKUpXjNdgYTWFSbStLB0vdlGnBbm2+Y4sBVj+C+TIw==} + /@sveltejs/vite-plugin-svelte/1.2.0_svelte@3.50.1+vite@3.1.4: + resolution: {integrity: sha512-DT2oUkWAloH1tO7X5cQ4uDxQofaIS76skyFMElKtoqT6HJao+D82LI5i+0jPaSSmO7ex3Pa6jGYMlWy9ZJ1cdQ==} engines: {node: ^14.18.0 || >= 16} peerDependencies: diff-match-patch: ^1.0.5 @@ -1951,20 +1828,20 @@ packages: diff-match-patch: optional: true dependencies: - '@rollup/pluginutils': 4.2.1 debug: 4.3.4 deepmerge: 4.2.2 kleur: 4.1.5 - magic-string: 0.26.3 + magic-string: 0.26.7 svelte: 3.50.1 - svelte-hmr: 0.14.12_svelte@3.50.1 + svelte-hmr: 0.15.1_svelte@3.50.1 vite: 3.1.4 + vitefu: 0.2.1_vite@3.1.4 transitivePeerDependencies: - supports-color dev: true - /@sveltejs/vite-plugin-svelte/1.0.5_svelte@3.52.0+vite@3.1.4: - resolution: {integrity: sha512-CmSdSow0Dr5ua1A11BQMtreWnE0JZmkVIcRU/yG3PKbycKUpXjNdgYTWFSbStLB0vdlGnBbm2+Y4sBVj+C+TIw==} + /@sveltejs/vite-plugin-svelte/1.2.0_svelte@3.52.0+vite@3.1.4: + resolution: {integrity: sha512-DT2oUkWAloH1tO7X5cQ4uDxQofaIS76skyFMElKtoqT6HJao+D82LI5i+0jPaSSmO7ex3Pa6jGYMlWy9ZJ1cdQ==} engines: {node: ^14.18.0 || >= 16} peerDependencies: diff-match-patch: ^1.0.5 @@ -1974,43 +1851,20 @@ packages: diff-match-patch: optional: true dependencies: - '@rollup/pluginutils': 4.2.1 debug: 4.3.4 deepmerge: 4.2.2 kleur: 4.1.5 - magic-string: 0.26.3 + magic-string: 0.26.7 svelte: 3.52.0 - svelte-hmr: 0.14.12_svelte@3.52.0 + svelte-hmr: 0.15.1_svelte@3.52.0 vite: 3.1.4 + vitefu: 0.2.1_vite@3.1.4 transitivePeerDependencies: - supports-color dev: true - /@sveltejs/vite-plugin-svelte/1.0.5_svelte@3.52.0+vite@3.1.6: - resolution: {integrity: sha512-CmSdSow0Dr5ua1A11BQMtreWnE0JZmkVIcRU/yG3PKbycKUpXjNdgYTWFSbStLB0vdlGnBbm2+Y4sBVj+C+TIw==} - engines: {node: ^14.18.0 || >= 16} - peerDependencies: - diff-match-patch: ^1.0.5 - svelte: ^3.44.0 - vite: ^3.0.0 - peerDependenciesMeta: - diff-match-patch: - optional: true - dependencies: - '@rollup/pluginutils': 4.2.1 - debug: 4.3.4 - deepmerge: 4.2.2 - kleur: 4.1.5 - magic-string: 0.26.3 - svelte: 3.52.0 - svelte-hmr: 0.14.12_svelte@3.52.0 - vite: 3.1.6 - transitivePeerDependencies: - - supports-color - dev: false - - /@sveltejs/vite-plugin-svelte/1.1.0_svelte@3.52.0+vite@3.1.6: - resolution: {integrity: sha512-cFRfEdztubtj1c/rYh7ArK7XCfFJn6wG6+J8/e9amFsKtEJILovoBrK0/mxt1AjPQg0vaX+fHPKvhx+q8mTPaQ==} + /@sveltejs/vite-plugin-svelte/1.2.0_svelte@3.52.0+vite@3.2.4: + resolution: {integrity: sha512-DT2oUkWAloH1tO7X5cQ4uDxQofaIS76skyFMElKtoqT6HJao+D82LI5i+0jPaSSmO7ex3Pa6jGYMlWy9ZJ1cdQ==} engines: {node: ^14.18.0 || >= 16} peerDependencies: diff-match-patch: ^1.0.5 @@ -2025,11 +1879,12 @@ packages: kleur: 4.1.5 magic-string: 0.26.7 svelte: 3.52.0 - svelte-hmr: 0.15.0_svelte@3.52.0 - vite: 3.1.6 + svelte-hmr: 0.15.1_svelte@3.52.0 + vite: 3.2.4 + vitefu: 0.2.1_vite@3.2.4 transitivePeerDependencies: - supports-color - dev: true + dev: false /@swc/helpers/0.4.11: resolution: {integrity: sha512-rEUrBSGIoSFuYxwBYtlUFMlE2CwGhmW+w9355/5oduSw8e5h2+Tj4UrAGNNgP9915++wj5vkQo0UuOBqOAq4nw==} @@ -2642,26 +2497,7 @@ packages: transitivePeerDependencies: - encoding - supports-color - - /@vercel/nft/0.22.1: - resolution: {integrity: sha512-lYYZIoxRurqDOSoVIdBicGnpUIpfyaS5qVjdPq+EfI285WqtZK3NK/dyCkiyBul+X2U2OEhRyeMdXPCHGJbohw==} - hasBin: true - dependencies: - '@mapbox/node-pre-gyp': 1.0.9 - acorn: 8.8.0 - async-sema: 3.1.1 - bindings: 1.5.0 - estree-walker: 2.0.2 - glob: 7.2.3 - graceful-fs: 4.2.10 - micromatch: 4.0.5 - node-gyp-build: 4.5.0 - resolve-from: 5.0.0 - rollup-pluginutils: 2.8.2 - transitivePeerDependencies: - - encoding - - supports-color - dev: true + dev: false /@whatwg-node/fetch/0.3.2: resolution: {integrity: sha512-Bs5zAWQs0tXsLa4mRmLw7Psps1EN78vPtgcLpw3qPY8s6UYPUM67zFZ9cy+7tZ64PXhfwzxJn+m7RH2Lq48RNQ==} @@ -2673,13 +2509,14 @@ packages: form-data-encoder: 1.7.2 formdata-node: 4.3.3 node-fetch: 2.6.7 - undici: 5.11.0 + undici: 5.12.0 web-streams-polyfill: 3.2.1 transitivePeerDependencies: - encoding /abbrev/1.1.1: resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} + dev: false /abort-controller/3.0.0: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} @@ -2706,6 +2543,7 @@ packages: debug: 4.3.4 transitivePeerDependencies: - supports-color + dev: false /aggregate-error/3.1.0: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} @@ -2775,6 +2613,7 @@ packages: /aproba/2.0.0: resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} + dev: false /are-we-there-yet/2.0.0: resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==} @@ -2782,6 +2621,7 @@ packages: dependencies: delegates: 1.0.0 readable-stream: 3.6.0 + dev: false /argparse/1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} @@ -2864,6 +2704,7 @@ packages: /async-sema/3.1.1: resolution: {integrity: sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==} + dev: false /axe-core/4.4.3: resolution: {integrity: sha512-32+ub6kkdhhWick/UjvEwRchgoetXqTK14INLqbGm5U2TzBkBNF3nQtLYm8ovxSkQWArjEQvftCKryjZaATu3w==} @@ -2932,6 +2773,7 @@ packages: resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} dependencies: file-uri-to-path: 1.0.0 + dev: false /boolbase/1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} @@ -3101,6 +2943,7 @@ packages: /chownr/2.0.0: resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} engines: {node: '>=10'} + dev: false /ci-info/3.4.0: resolution: {integrity: sha512-t5QdPT5jq3o262DOQ8zA6E1tlH2upmUc4Hlvrbx1pGYJuiiHl7O7rvVNI+l8HTVhd/q3Qc9vqimkNk5yiXsAug==} @@ -3202,6 +3045,7 @@ packages: /color-support/1.1.3: resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} hasBin: true + dev: false /colorette/2.0.19: resolution: {integrity: sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==} @@ -3250,6 +3094,7 @@ packages: /console-control-strings/1.1.0: resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} + dev: false /convert-source-map/1.8.0: resolution: {integrity: sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==} @@ -3448,6 +3293,7 @@ packages: /delegates/1.0.0: resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} + dev: false /detect-indent/6.1.0: resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} @@ -3456,18 +3302,10 @@ packages: /detect-libc/2.0.1: resolution: {integrity: sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==} engines: {node: '>=8'} - - /devalue/3.1.3: - resolution: {integrity: sha512-9KO89Cb+qjzf2CqdrH+NuLaqdk9GhDP5EhR4zlkR51dvuIaiqtlkDkGzLMShDemwUy21raSMdu+kpX8Enw3yGQ==} dev: false - /devalue/4.0.0: - resolution: {integrity: sha512-w25siwXyuMUqMr7jPlEjyNCp1vn0Jzj/fNg3qVt/r/Dpe8HjESh2V92L0jmh3uq4iJt0BvjH+Azk1pQzkcnDWA==} - dev: true - /devalue/4.2.0: resolution: {integrity: sha512-mbjoAaCL2qogBKgeFxFPOXAUsZchircF+B/79LD4sHH0+NHfYm8gZpQrskKDn5gENGt35+5OI1GUF7hLVnkPDw==} - dev: true /diff-sequences/29.0.0: resolution: {integrity: sha512-7Qe/zd1wxSDL4D/X/FPjOMB+ZMDt71W94KYaq05I2l0oQqgXgs7s4ftYYmV38gBSrPz2vcygxfs1xn0FT+rKNA==} @@ -3609,6 +3447,7 @@ packages: cpu: [x64] os: [android] requiresBuild: true + dev: false optional: true /esbuild-android-64/0.15.10: @@ -3633,6 +3472,7 @@ packages: cpu: [arm64] os: [android] requiresBuild: true + dev: false optional: true /esbuild-android-arm64/0.15.10: @@ -3657,6 +3497,7 @@ packages: cpu: [x64] os: [darwin] requiresBuild: true + dev: false optional: true /esbuild-darwin-64/0.15.10: @@ -3681,6 +3522,7 @@ packages: cpu: [arm64] os: [darwin] requiresBuild: true + dev: false optional: true /esbuild-darwin-arm64/0.15.10: @@ -3705,6 +3547,7 @@ packages: cpu: [x64] os: [freebsd] requiresBuild: true + dev: false optional: true /esbuild-freebsd-64/0.15.10: @@ -3729,6 +3572,7 @@ packages: cpu: [arm64] os: [freebsd] requiresBuild: true + dev: false optional: true /esbuild-freebsd-arm64/0.15.10: @@ -3753,6 +3597,7 @@ packages: cpu: [ia32] os: [linux] requiresBuild: true + dev: false optional: true /esbuild-linux-32/0.15.10: @@ -3777,6 +3622,7 @@ packages: cpu: [x64] os: [linux] requiresBuild: true + dev: false optional: true /esbuild-linux-64/0.15.10: @@ -3801,6 +3647,7 @@ packages: cpu: [arm] os: [linux] requiresBuild: true + dev: false optional: true /esbuild-linux-arm/0.15.10: @@ -3825,6 +3672,7 @@ packages: cpu: [arm64] os: [linux] requiresBuild: true + dev: false optional: true /esbuild-linux-arm64/0.15.10: @@ -3849,6 +3697,7 @@ packages: cpu: [mips64el] os: [linux] requiresBuild: true + dev: false optional: true /esbuild-linux-mips64le/0.15.10: @@ -3873,6 +3722,7 @@ packages: cpu: [ppc64] os: [linux] requiresBuild: true + dev: false optional: true /esbuild-linux-ppc64le/0.15.10: @@ -3897,6 +3747,7 @@ packages: cpu: [riscv64] os: [linux] requiresBuild: true + dev: false optional: true /esbuild-linux-riscv64/0.15.10: @@ -3921,6 +3772,7 @@ packages: cpu: [s390x] os: [linux] requiresBuild: true + dev: false optional: true /esbuild-linux-s390x/0.15.10: @@ -3945,6 +3797,7 @@ packages: cpu: [x64] os: [netbsd] requiresBuild: true + dev: false optional: true /esbuild-netbsd-64/0.15.10: @@ -3969,6 +3822,7 @@ packages: cpu: [x64] os: [openbsd] requiresBuild: true + dev: false optional: true /esbuild-openbsd-64/0.15.10: @@ -4003,6 +3857,7 @@ packages: cpu: [x64] os: [sunos] requiresBuild: true + dev: false optional: true /esbuild-sunos-64/0.15.10: @@ -4027,6 +3882,7 @@ packages: cpu: [ia32] os: [win32] requiresBuild: true + dev: false optional: true /esbuild-windows-32/0.15.10: @@ -4051,6 +3907,7 @@ packages: cpu: [x64] os: [win32] requiresBuild: true + dev: false optional: true /esbuild-windows-64/0.15.10: @@ -4075,6 +3932,7 @@ packages: cpu: [arm64] os: [win32] requiresBuild: true + dev: false optional: true /esbuild-windows-arm64/0.15.10: @@ -4120,6 +3978,7 @@ packages: esbuild-windows-32: 0.14.54 esbuild-windows-64: 0.14.54 esbuild-windows-arm64: 0.14.54 + dev: false /esbuild/0.15.10: resolution: {integrity: sha512-N7wBhfJ/E5fzn/SpNgX+oW2RLRjwaL8Y0ezqNqhjD6w0H2p0rDuEz2FKZqpqLnO8DCaWumKe8dsC/ljvVSSxng==} @@ -4672,9 +4531,11 @@ packages: /estree-walker/0.6.1: resolution: {integrity: sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==} + dev: false /estree-walker/2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + dev: false /estree-walker/3.0.1: resolution: {integrity: sha512-woY0RUD87WzMBUiZLx8NsYr23N5BKsOMZHhu2hoNRVh6NXGfoiT1KOL8G3UHlJAnEDGmfa5ubNA/AacfG+Kb0g==} @@ -4801,6 +4662,7 @@ packages: /file-uri-to-path/1.0.0: resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + dev: false /fill-range/7.0.1: resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} @@ -4901,6 +4763,7 @@ packages: engines: {node: '>= 8'} dependencies: minipass: 3.3.4 + dev: false /fs-monkey/1.0.3: resolution: {integrity: sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==} @@ -4947,6 +4810,7 @@ packages: string-width: 4.2.3 strip-ansi: 6.0.1 wide-align: 1.1.5 + dev: false /gensync/1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} @@ -5130,6 +4994,7 @@ packages: /has-unicode/2.0.1: resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} + dev: false /has/1.0.3: resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} @@ -5177,6 +5042,7 @@ packages: debug: 4.3.4 transitivePeerDependencies: - supports-color + dev: false /human-id/1.0.2: resolution: {integrity: sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw==} @@ -5216,6 +5082,10 @@ packages: parent-module: 1.0.1 resolve-from: 4.0.0 + /import-meta-resolve/2.1.0: + resolution: {integrity: sha512-yG9pxkWJVTy4cmRsNWE3ztFdtFuYIV8G4N+cbCkO8b+qngkLyIUhxQFuZ0qJm67+0nUOxjMPT7nfksPKza1v2g==} + dev: true + /imurmurhash/0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} @@ -5804,24 +5674,18 @@ packages: dependencies: sourcemap-codec: 1.4.8 - /magic-string/0.26.3: - resolution: {integrity: sha512-u1Po0NDyFcwdg2nzHT88wSK0+Rih0N1M+Ph1Sp08k8yvFFU3KR72wryS7e1qMPJypt99WB7fIFVCA92mQrMjrg==} - engines: {node: '>=12'} - dependencies: - sourcemap-codec: 1.4.8 - /magic-string/0.26.7: resolution: {integrity: sha512-hX9XH3ziStPoPhJxLq1syWuZMxbDvGNbVchfrdCtanC7D13888bMFow61x8axrx+GfHLtVeAx2kxL7tTGRl+Ow==} engines: {node: '>=12'} dependencies: sourcemap-codec: 1.4.8 - dev: true /make-dir/3.1.0: resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} engines: {node: '>=8'} dependencies: semver: 6.3.0 + dev: false /makeerror/1.0.12: resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} @@ -5984,6 +5848,7 @@ packages: engines: {node: '>=8'} dependencies: yallist: 4.0.0 + dev: false /minizlib/2.1.2: resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} @@ -5991,6 +5856,7 @@ packages: dependencies: minipass: 3.3.4 yallist: 4.0.0 + dev: false /mixme/0.5.4: resolution: {integrity: sha512-3KYa4m4Vlqx98GPdOHghxSdNtTvcP8E0kkaJ5Dlh+h2DRzF7zpuVVcA8B0QpKd11YJeP9QQ7ASkKzOeu195Wzw==} @@ -6007,6 +5873,7 @@ packages: resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} engines: {node: '>=10'} hasBin: true + dev: false /mri/1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} @@ -6104,6 +5971,7 @@ packages: /node-gyp-build/4.5.0: resolution: {integrity: sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg==} hasBin: true + dev: false /node-html-parser/5.4.1: resolution: {integrity: sha512-xy/O2wOEBJsIRLs4avwa1lVY7tIpXXOoHHUJLa0GvnoPPqMG1hgBVl1tNI3GHOwRktTVZy+Y6rjghk4B9/NLyg==} @@ -6125,6 +5993,7 @@ packages: hasBin: true dependencies: abbrev: 1.1.1 + dev: false /normalize-package-data/2.5.0: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} @@ -6160,6 +6029,7 @@ packages: console-control-strings: 1.1.0 gauge: 3.0.2 set-blocking: 2.0.0 + dev: false /npx-import/1.1.3: resolution: {integrity: sha512-zy6249FJ81OtPsvz2y0+rgis31EN5wbdwBG2umtEh65W/4onYArHuoUSZ+W+T7BQYK7YF+h9G4CuGPusMCcLOw==} @@ -6428,6 +6298,15 @@ packages: picocolors: 1.0.0 source-map-js: 1.0.2 + /postcss/8.4.19: + resolution: {integrity: sha512-h+pbPsyhlYj6N2ozBmHhHrs9DzGmbaarbLvWipMRO7RLS+v4onj26MPFXA5OBYFxyqYhUJK456SwDcY9H2/zsA==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.4 + picocolors: 1.0.0 + source-map-js: 1.0.2 + dev: false + /preferred-pm/3.0.3: resolution: {integrity: sha512-+wZgbxNES/KlJs9q40F/1sfOd/j7f1O9JaHcW5Dsn3aUUOZg3L2bjpVUcKV2jvtElYfoTuQiNeMfQJ4kwUAhCQ==} engines: {node: '>=10'} @@ -6588,6 +6467,7 @@ packages: inherits: 2.0.4 string_decoder: 1.3.0 util-deprecate: 1.0.2 + dev: false /readdirp/3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} @@ -6632,6 +6512,7 @@ packages: /regexparam/2.0.1: resolution: {integrity: sha512-zRgSaYemnNYxUv+/5SeoHI0eJIgTL/A2pUtXUPLHQxUldagouJ9p+K6IbIZ/JiQuCEv2E2B1O11SjVQy3aMCkw==} engines: {node: '>=8'} + dev: false /regexpp/3.2.0: resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==} @@ -6805,6 +6686,7 @@ packages: resolution: {integrity: sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==} dependencies: estree-walker: 0.6.1 + dev: false /rollup/2.77.3: resolution: {integrity: sha512-/qxNTG7FbmefJWoeeYJFbHehJ2HNWnjkAFRKzWN/45eNBBF/r8lo992CwcJXEzyVxs5FmfId+vTSTQDb+bxA+g==} @@ -6820,6 +6702,7 @@ packages: hasBin: true optionalDependencies: fsevents: 2.3.2 + dev: true /rollup/2.79.1: resolution: {integrity: sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==} @@ -6857,6 +6740,7 @@ packages: /safe-buffer/5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + dev: false /safe-regex-test/1.0.0: resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==} @@ -7133,6 +7017,7 @@ packages: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} dependencies: safe-buffer: 5.2.1 + dev: false /stringify-entities/3.1.0: resolution: {integrity: sha512-3FP+jGMmMV/ffZs86MoghGqAoqXAdxLrJP4GUdrDN1aIScYih5tuIO3eF4To5AJZ79KDZ8Fpdy7QJnK8SsL1Vg==} @@ -7294,8 +7179,8 @@ packages: svelte: 3.49.0 dev: false - /svelte-hmr/0.14.12_svelte@3.50.1: - resolution: {integrity: sha512-4QSW/VvXuqVcFZ+RhxiR8/newmwOCTlbYIezvkeN6302YFRE8cXy0naamHcjz8Y9Ce3ITTZtrHrIL0AGfyo61w==} + /svelte-hmr/0.15.1_svelte@3.50.1: + resolution: {integrity: sha512-BiKB4RZ8YSwRKCNVdNxK/GfY+r4Kjgp9jCLEy0DuqAKfmQtpL38cQK3afdpjw4sqSs4PLi3jIPJIFp259NkZtA==} engines: {node: ^12.20 || ^14.13.1 || >= 16} peerDependencies: svelte: '>=3.19.0' @@ -7303,22 +7188,13 @@ packages: svelte: 3.50.1 dev: true - /svelte-hmr/0.14.12_svelte@3.52.0: - resolution: {integrity: sha512-4QSW/VvXuqVcFZ+RhxiR8/newmwOCTlbYIezvkeN6302YFRE8cXy0naamHcjz8Y9Ce3ITTZtrHrIL0AGfyo61w==} - engines: {node: ^12.20 || ^14.13.1 || >= 16} - peerDependencies: - svelte: '>=3.19.0' - dependencies: - svelte: 3.52.0 - - /svelte-hmr/0.15.0_svelte@3.52.0: - resolution: {integrity: sha512-Aw21SsyoohyVn4yiKXWPNCSW2DQNH/76kvUnE9kpt4h9hcg9tfyQc6xshx9hzgMfGF0kVx0EGD8oBMWSnATeOg==} + /svelte-hmr/0.15.1_svelte@3.52.0: + resolution: {integrity: sha512-BiKB4RZ8YSwRKCNVdNxK/GfY+r4Kjgp9jCLEy0DuqAKfmQtpL38cQK3afdpjw4sqSs4PLi3jIPJIFp259NkZtA==} engines: {node: ^12.20 || ^14.13.1 || >= 16} peerDependencies: svelte: '>=3.19.0' dependencies: svelte: 3.52.0 - dev: true /svelte-kit-cookie-session/3.0.6: resolution: {integrity: sha512-8HmnhTxLgf2nqNTW22FwzzXHepdN7gLXH5mBU1zhZX93JXyq/OOLsgbWhX9Hv9JNUlYknwq/ZE54iMrEFaH5BA==} @@ -7613,6 +7489,7 @@ packages: minizlib: 2.1.2 mkdirp: 1.0.4 yallist: 4.0.0 + dev: false /term-size/2.2.1: resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} @@ -7889,18 +7766,11 @@ packages: has-symbols: 1.0.3 which-boxed-primitive: 1.0.2 - /undici/5.11.0: - resolution: {integrity: sha512-oWjWJHzFet0Ow4YZBkyiJwiK5vWqEYoH7BINzJAJOLedZ++JpAlCbUktW2GQ2DS2FpKmxD/JMtWUUWl1BtghGw==} - engines: {node: '>=12.18'} - dependencies: - busboy: 1.6.0 - /undici/5.12.0: resolution: {integrity: sha512-zMLamCG62PGjd9HHMpo05bSLvvwWOZgGeiWlN/vlqu3+lRo3elxktVGEyLMX+IO7c2eflLjcW74AlkhEZm15mg==} engines: {node: '>=12.18'} dependencies: busboy: 1.6.0 - dev: true /unherit/1.1.3: resolution: {integrity: sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ==} @@ -8041,6 +7911,7 @@ packages: /util-deprecate/1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + dev: false /uuid/8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} @@ -8197,6 +8068,62 @@ packages: rollup: 2.78.1 optionalDependencies: fsevents: 2.3.2 + dev: true + + /vite/3.2.4: + resolution: {integrity: sha512-Z2X6SRAffOUYTa+sLy3NQ7nlHFU100xwanq1WDwqaiFiCe+25zdxP1TfCS5ojPV2oDDcXudHIoPnI1Z/66B7Yw==} + engines: {node: ^14.18.0 || >=16.0.0} + hasBin: true + peerDependencies: + '@types/node': '>= 14' + less: '*' + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + esbuild: 0.15.13 + postcss: 8.4.19 + resolve: 1.22.1 + rollup: 2.79.1 + optionalDependencies: + fsevents: 2.3.2 + dev: false + + /vitefu/0.2.1_vite@3.1.4: + resolution: {integrity: sha512-clkvXTAeUf+XQKm3bhWUhT4pye+3acm6YCTGaWhxxIvZZ/QjnA3JA8Zud+z/mO5y5XYvJJhevs5Sjkv/FI8nRw==} + peerDependencies: + vite: ^3.0.0 + peerDependenciesMeta: + vite: + optional: true + dependencies: + vite: 3.1.4 + dev: true + + /vitefu/0.2.1_vite@3.2.4: + resolution: {integrity: sha512-clkvXTAeUf+XQKm3bhWUhT4pye+3acm6YCTGaWhxxIvZZ/QjnA3JA8Zud+z/mO5y5XYvJJhevs5Sjkv/FI8nRw==} + peerDependencies: + vite: ^3.0.0 + peerDependenciesMeta: + vite: + optional: true + dependencies: + vite: 3.2.4 + dev: false /vitest/0.23.4: resolution: {integrity: sha512-iukBNWqQAv8EKDBUNntspLp9SfpaVFbmzmM0sNcnTxASQZMzRw3PsM6DMlsHiI+I6GeO5/sYDg3ecpC+SNFLrQ==} @@ -8316,6 +8243,7 @@ packages: resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} dependencies: string-width: 4.2.3 + dev: false /word-wrap/1.2.3: resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} @@ -8327,6 +8255,7 @@ packages: dependencies: mrmime: 1.0.1 regexparam: 2.0.1 + dev: false /wrap-ansi/6.2.0: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} diff --git a/site/src/routes/api/config.svx b/site/src/routes/api/config.svx index cbdd8379b..03c167518 100644 --- a/site/src/routes/api/config.svx +++ b/site/src/routes/api/config.svx @@ -38,6 +38,8 @@ By default, your config file can contain the following values: - `disableMasking` (optional, default: `false`): A boolean indicating whether fields from referenced fragments should be included in a document's selection set (can be overridden individually) - `schemaPollHeaders` (optional): An object specifying the headers to use when pulling your schema. Keys of the object are header names and its values can be either a strings or a function that takes the current `process.env` and returns the the value to use. If you want to access an environment variable, prefix your string with `env:`, ie `env:API_KEY`. - `schemaPollInterval` (optional, default: `2000`): Configures the schema polling behavior for the kit plugin. If its value is greater than `0`, the plugin will poll the set number of milliseconds. If set to `0`, the plugin will only pull the schema when you first run `dev`. If you set to `null`, the plugin will never look for schema changes. You can see use the [pull-schema command](/api/cli#pull-schema) to get updates. +- `defaultListTarget` (optional): Can be set to `all` for all list operations to ignore parent ID and affect all lists with the name. +- `defaultListPosition` (optional, default: "first"): One of `"first"` or `"last"` to indicate the default location for list operations. - `plugins` (optional): An object containing the set of plugins you want to add to your houdini application. The keys are plugin names, the values are plugin-specific configuration. The actual plugin API is undocumented and considered unstable while we try out various things internally. For an overview of your framework plugin's specific configuration, see below. ## Svelte Plugin diff --git a/site/src/routes/api/graphql.svx b/site/src/routes/api/graphql.svx index b4ee361b5..a0ae7bd7b 100644 --- a/site/src/routes/api/graphql.svx +++ b/site/src/routes/api/graphql.svx @@ -41,6 +41,10 @@ For more information on using these fragments, head over to the [mutation docs]( `@append` is used in conjunction with the [list operation fragments](#list-operations) to tell the runtime to add the element at the end of the list. Can be used with both `_insert` and `_toggle` fragments. If the list is a member of a fragment, you will have to pass a value for the `parentID` so the correct list can be updated, otherwise the argument should be omitted. +### `@allLists` + +`@allLists` is used in conjunction with the [list operation fragments](#list-operations) to tell the runtime to add the element to all lists. Can be used with both `_insert` and `_toggle` fragments. You can't have `parentID` at the same time. + ### `@when` `@when` provides a conditional under which the [list operation](#list-operations) should be executed. It takes arguments that match the arguments of the field tagged with `@list` or `@paginate`. For more information, check out the [mutation docs](/api/mutation#lists).