houdini-core 2.0.0-go.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,44 @@
1
+ import type { ClientPlugin, ClientPluginContext } from 'houdini/runtime/documentStore'
2
+ import type { ArtifactKinds, QueryResult } from 'houdini/runtime/types'
3
+ import { ArtifactKind } from 'houdini/runtime/types'
4
+
5
+ export type ThrowOnErrorOperations = 'all' | 'query' | 'mutation' | 'subscription'
6
+
7
+ export type ThrowOnErrorParams = {
8
+ operations: ThrowOnErrorOperations[]
9
+ error?: (
10
+ errors: NonNullable<QueryResult<any, any>['errors']>,
11
+ ctx: ClientPluginContext
12
+ ) => unknown
13
+ }
14
+
15
+ export const throwOnError =
16
+ ({ operations, error }: ThrowOnErrorParams): ClientPlugin =>
17
+ () => {
18
+ // build a map of artifact kinds we will throw on
19
+ const all = operations.includes('all')
20
+ const throwOnKind = (kind: ArtifactKinds) =>
21
+ all ||
22
+ {
23
+ [ArtifactKind.Query]: operations.includes('query'),
24
+ [ArtifactKind.Mutation]: operations.includes('mutation'),
25
+ [ArtifactKind.Fragment]: false,
26
+ [ArtifactKind.Subscription]: operations.includes('subscription'),
27
+ }[kind]
28
+
29
+ return {
30
+ async end(ctx, { value, resolve }) {
31
+ // if we are supposed to throw and there are errors
32
+ if (value.errors && value.errors.length > 0 && throwOnKind(ctx.artifact.kind)) {
33
+ const result = await (error ?? defaultErrorFn)(value.errors, ctx)
34
+ throw result
35
+ }
36
+
37
+ // we're not supposed to throw, move on
38
+ resolve(ctx)
39
+ },
40
+ }
41
+ }
42
+
43
+ const defaultErrorFn: Required<ThrowOnErrorParams>['error'] = async (errors) =>
44
+ new Error(errors.map((error) => error.message).join('. ') + '.')
@@ -0,0 +1,54 @@
1
+ import type { ArtifactKinds } from 'houdini/runtime'
2
+ import type {
3
+ ClientPlugin,
4
+ ClientPluginExitPhase,
5
+ ClientPluginEnterPhase,
6
+ ClientHooks,
7
+ } from 'houdini/runtime/documentStore'
8
+
9
+ export const documentPlugin = (kind: ArtifactKinds, source: () => ClientHooks): ClientPlugin => {
10
+ return () => {
11
+ // pull out the hooks we care about
12
+ const sourceHandlers = source()
13
+
14
+ const enterWrapper = (
15
+ handler?: ClientPluginEnterPhase
16
+ ): ClientPluginEnterPhase | undefined => {
17
+ return !handler
18
+ ? undefined
19
+ : (ctx, handlers) => {
20
+ if (ctx.artifact.kind !== kind) {
21
+ return handlers.next(ctx)
22
+ }
23
+
24
+ return handler(ctx, handlers)
25
+ }
26
+ }
27
+ const exitWrapper = (
28
+ handler?: ClientPluginExitPhase
29
+ ): ClientPluginExitPhase | undefined => {
30
+ return !handler
31
+ ? undefined
32
+ : (ctx, handlers) => {
33
+ if (ctx.artifact.kind !== kind) {
34
+ return handlers.resolve(ctx)
35
+ }
36
+
37
+ return handler(ctx, handlers)
38
+ }
39
+ }
40
+
41
+ // return the modified hooks
42
+ return {
43
+ start: enterWrapper(sourceHandlers.start),
44
+ network: enterWrapper(sourceHandlers.network),
45
+ beforeNetwork: enterWrapper(sourceHandlers.beforeNetwork),
46
+ afterNetwork: exitWrapper(sourceHandlers.afterNetwork),
47
+ end: exitWrapper(sourceHandlers.end),
48
+ catch: sourceHandlers.catch
49
+ ? (ctx, handlers) => sourceHandlers.catch!(ctx, handlers)
50
+ : undefined,
51
+ cleanup: (...args) => sourceHandlers.cleanup?.(...args),
52
+ }
53
+ }
54
+ }
@@ -0,0 +1 @@
1
+ export * from './documentPlugins'
@@ -0,0 +1,121 @@
1
+ import type { Cache as _Cache } from 'houdini/runtime/cache'
2
+ import { marshalInputs } from 'houdini/runtime/scalars'
3
+ import type { QueryArtifact } from 'houdini/runtime/types'
4
+
5
+ import { getCurrentConfig } from '../config'
6
+ import { ListCollection } from './list'
7
+ import { Record } from './record'
8
+ import type {
9
+ ArgType,
10
+ CacheTypeDef,
11
+ IDFields,
12
+ QueryInput,
13
+ QueryList,
14
+ QueryValue,
15
+ TypeFieldNames,
16
+ TypeNames,
17
+ ValidLists,
18
+ } from './types'
19
+
20
+ export class Cache<Def extends CacheTypeDef> {
21
+ _internal_unstable: _Cache
22
+
23
+ constructor(cache: _Cache) {
24
+ this._internal_unstable = cache
25
+ }
26
+
27
+ // return the record proxy for the given type/id combo
28
+ get<T extends TypeNames<Def>>(type: T, data: IDFields<Def, T>): Record<Def, T> {
29
+ // compute the id for the record
30
+ let recordID = this._internal_unstable._internal_unstable.id(type, data)
31
+ if (!recordID) {
32
+ throw new Error('todo')
33
+ }
34
+
35
+ // return the proxy
36
+ return new Record({
37
+ cache: this,
38
+ type: type,
39
+ id: recordID,
40
+ idFields: data,
41
+ })
42
+ }
43
+
44
+ get config() {
45
+ return getCurrentConfig()
46
+ }
47
+
48
+ list<Name extends ValidLists<Def>>(
49
+ name: Name,
50
+ { parentID, allLists }: { parentID?: string; allLists?: boolean } = {}
51
+ ): ListCollection<Def, Name> {
52
+ return new ListCollection<Def, Name>({
53
+ cache: this,
54
+ name,
55
+ parentID,
56
+ allLists,
57
+ })
58
+ }
59
+
60
+ read<_Query extends { artifact: QueryArtifact }>({
61
+ query,
62
+ variables,
63
+ }: {
64
+ query: _Query
65
+ variables?: QueryInput<QueryList<Def>, _Query>
66
+ }): {
67
+ data: QueryValue<QueryList<Def>, _Query> | null
68
+ partial: boolean
69
+ } {
70
+ // @ts-expect-error
71
+ return this._internal_unstable.read({
72
+ selection: query.artifact.selection,
73
+ variables,
74
+ })
75
+ }
76
+
77
+ write<_Query extends { artifact: QueryArtifact }>({
78
+ query,
79
+ variables,
80
+ data,
81
+ }: {
82
+ query: _Query
83
+ data: QueryValue<QueryList<Def>, _Query>
84
+ variables?: QueryInput<QueryList<Def>, _Query>
85
+ }) {
86
+ this._internal_unstable.write({
87
+ selection: query.artifact.selection,
88
+ // @ts-expect-error
89
+ data,
90
+ variables:
91
+ marshalInputs({
92
+ config: this.config,
93
+ artifact: query.artifact,
94
+ input: variables,
95
+ }) ?? {},
96
+ })
97
+
98
+ return
99
+ }
100
+
101
+ /**
102
+ * Mark some elements of the cache stale.
103
+ */
104
+ markStale<_Type extends TypeNames<Def>, _Field extends TypeFieldNames<Def, _Type>>(
105
+ type?: _Type,
106
+ options?: {
107
+ field?: _Field
108
+ when?: ArgType<Def, _Type, _Field>
109
+ }
110
+ ): void {
111
+ return this._internal_unstable.markTypeStale(type ? { ...options, type } : undefined)
112
+ }
113
+
114
+ /**
115
+ * Reset the entire cache by clearing all records and lists
116
+ */
117
+
118
+ reset(): void {
119
+ return this._internal_unstable.reset()
120
+ }
121
+ }
@@ -0,0 +1 @@
1
+ export { Cache } from './cache'
@@ -0,0 +1,164 @@
1
+ import type { GraphQLObject, SubscriptionSelection } from 'houdini/runtime'
2
+ import type { ListCollection as _Collection } from 'houdini/runtime/cache/lists'
3
+
4
+ import type { Cache } from './cache'
5
+ import { Record } from './record'
6
+ import type { CacheTypeDef, ListType, ValidLists, ListFilters } from './types'
7
+
8
+ export class ListCollection<Def extends CacheTypeDef, ListName extends ValidLists<Def>> {
9
+ #parentID: string | undefined
10
+ #allLists: boolean | undefined
11
+ #when: ListFilters<Def, ListName> | undefined
12
+ #cache: Cache<Def>
13
+ #name: ValidLists<Def>
14
+
15
+ constructor({
16
+ parentID,
17
+ allLists,
18
+ when,
19
+ cache,
20
+ name,
21
+ }: {
22
+ name: ValidLists<Def>
23
+ parentID?: string
24
+ allLists?: boolean
25
+ when?: ListFilters<Def, ListName>
26
+ cache: Cache<Def>
27
+ }) {
28
+ this.#parentID = parentID
29
+ this.#allLists = allLists
30
+ this.#when = when
31
+ this.#cache = cache
32
+ this.#name = name
33
+ }
34
+
35
+ append(...records: ListType<Def, ListName>[]) {
36
+ if (!this.#collection) {
37
+ return
38
+ }
39
+
40
+ const { selection, data } = this.#listOperationPayload(records)
41
+ for (const entry of data) {
42
+ if (entry) {
43
+ this.#collection.append({
44
+ selection,
45
+ data: entry,
46
+ })
47
+ }
48
+ }
49
+ }
50
+
51
+ prepend(...records: ListType<Def, ListName>[]) {
52
+ if (!this.#collection) {
53
+ return
54
+ }
55
+
56
+ const { selection, data } = this.#listOperationPayload(records)
57
+ for (const entry of data) {
58
+ if (entry) {
59
+ this.#collection.prepend({
60
+ selection,
61
+ data: entry,
62
+ })
63
+ }
64
+ }
65
+ }
66
+
67
+ toggle(where: 'first' | 'last', ...records: ListType<Def, ListName>[]) {
68
+ if (!this.#collection) {
69
+ return
70
+ }
71
+
72
+ const { selection, data } = this.#listOperationPayload(records)
73
+ for (const entry of data) {
74
+ if (entry) {
75
+ this.#collection.toggleElement({
76
+ selection,
77
+ data: entry,
78
+ where,
79
+ })
80
+ }
81
+ }
82
+ }
83
+
84
+ when(filter: ListFilters<Def, ListName>): ListCollection<Def, ListName> {
85
+ if (!this.#collection) {
86
+ return this
87
+ }
88
+
89
+ return new ListCollection({
90
+ parentID: this.#parentID,
91
+ allLists: this.#allLists,
92
+ when: filter,
93
+ cache: this.#cache,
94
+ name: this.#name,
95
+ })
96
+ }
97
+
98
+ remove(...records: ListType<Def, ListName>[]) {
99
+ if (!this.#collection) {
100
+ return
101
+ }
102
+
103
+ for (const record of records) {
104
+ if (record) {
105
+ this.#collection.remove(record.idFields)
106
+ }
107
+ }
108
+ }
109
+
110
+ *[Symbol.iterator]() {
111
+ for (const entry of this.#collection ?? []) {
112
+ yield entry
113
+ }
114
+ }
115
+
116
+ get #collection(): _Collection | null {
117
+ try {
118
+ const list = this.#cache._internal_unstable.list(
119
+ this.#name,
120
+ this.#parentID,
121
+ this.#allLists
122
+ )
123
+ if (this.#when) {
124
+ return list.when(this.#when)
125
+ }
126
+ return list
127
+ } catch {
128
+ return null
129
+ }
130
+ }
131
+
132
+ #listOperationPayload(records: ListType<Def, ListName>[]): {
133
+ selection: SubscriptionSelection
134
+ data: GraphQLObject[]
135
+ } {
136
+ // we need to build up the selection that describes the key
137
+ // for every type in the list
138
+ let selection: SubscriptionSelection = this.#collection!.selection
139
+ // if the list is a connection, we can't use this selection immediately
140
+ // we need to look for edges.node
141
+ const connectionSelection = selection.fields?.['edges']?.selection?.fields?.node.selection
142
+ if (connectionSelection) {
143
+ selection = connectionSelection
144
+ }
145
+
146
+ // and the actual data for the record
147
+ const data: GraphQLObject[] = []
148
+
149
+ // loop over every record we are adding to build up the necessary structure
150
+ for (const record of records) {
151
+ if (!(record instanceof Record)) {
152
+ throw new Error('You must provide a Record to a list operation')
153
+ }
154
+
155
+ // add the necessary information
156
+ data.push({ __typename: record.type, ...record.idFields })
157
+ }
158
+
159
+ return {
160
+ selection,
161
+ data,
162
+ }
163
+ }
164
+ }
@@ -0,0 +1,113 @@
1
+ import { marshalInputs } from 'houdini/runtime'
2
+ import { keyFieldsForType } from 'houdini/runtime'
3
+ import type { FragmentArtifact, GraphQLObject } from 'houdini/runtime'
4
+ import { rootID } from 'houdini/runtime/cache/stuff'
5
+
6
+ import type { Cache } from './cache'
7
+ import type {
8
+ ArgType,
9
+ CacheTypeDef,
10
+ FragmentList,
11
+ FragmentValue,
12
+ FragmentVariables,
13
+ TypeFieldNames,
14
+ ValidTypes,
15
+ } from './types'
16
+
17
+ export class Record<Def extends CacheTypeDef, Type extends ValidTypes<Def>> {
18
+ #id: string
19
+ #cache: Cache<Def>
20
+
21
+ type: string
22
+ idFields: {}
23
+
24
+ constructor({
25
+ cache,
26
+ type,
27
+ id,
28
+ idFields,
29
+ }: {
30
+ cache: Cache<Def>
31
+ type: string
32
+ idFields: {}
33
+ id: string
34
+ }) {
35
+ this.#cache = cache
36
+ this.#id = id
37
+ this.type = type
38
+ this.idFields = idFields
39
+
40
+ // make sure that we have all of the necessary fields for the id
41
+ if (id !== rootID) {
42
+ for (const key of keyFieldsForType(this.#cache.config, type)) {
43
+ if (!(key in idFields)) {
44
+ throw new Error('Missing key in idFields: ' + key)
45
+ }
46
+ }
47
+ }
48
+ }
49
+
50
+ read<_Fragment extends { artifact: FragmentArtifact }>({
51
+ fragment,
52
+ variables,
53
+ }: {
54
+ fragment: _Fragment
55
+ variables?: FragmentVariables<FragmentList<Def, Type>, _Fragment>
56
+ }): { data: FragmentValue<FragmentList<Def, Type>, _Fragment> | null; partial: boolean } {
57
+ // @ts-expect-error
58
+ return this.#cache._internal_unstable.read({
59
+ selection: fragment.artifact.selection,
60
+ parent: this.#id,
61
+ variables:
62
+ marshalInputs({
63
+ config: this.#cache.config,
64
+ artifact: fragment.artifact,
65
+ input: variables,
66
+ }) ?? undefined,
67
+ })
68
+ }
69
+
70
+ write<_Fragment extends { artifact: FragmentArtifact }, _Variable>(args: {
71
+ fragment: _Fragment
72
+ data: FragmentValue<FragmentList<Def, Type>, _Fragment>
73
+ // TODO: figure out a way to make this required when _Variables has a value
74
+ // and optional when _Variables is never
75
+ variables?: FragmentVariables<FragmentList<Def, Type>, _Fragment>
76
+ forceStale?: boolean
77
+ }) {
78
+ // we have the data and the fragment, just pass them both to the cache
79
+ this.#cache._internal_unstable.write({
80
+ data: args.data as unknown as GraphQLObject,
81
+ selection: args.fragment.artifact.selection,
82
+ parent: this.#id,
83
+ variables:
84
+ marshalInputs({
85
+ config: this.#cache.config,
86
+ artifact: args.fragment.artifact,
87
+ input: args.variables,
88
+ }) ?? undefined,
89
+ forceStale: args.forceStale,
90
+ })
91
+ }
92
+
93
+ delete() {
94
+ this.#cache._internal_unstable.delete(this.#id)
95
+ }
96
+
97
+ /**
98
+ * Mark some elements of the record stale in the cache.
99
+ * @param field
100
+ * @param when
101
+ */
102
+ markStale<Field extends TypeFieldNames<Def, Type>>(
103
+ field?: Field,
104
+ {
105
+ when,
106
+ }: {
107
+ when?: ArgType<Def, Type, Field>
108
+ } = {}
109
+ ): void {
110
+ // mark the record
111
+ this.#cache._internal_unstable.markRecordStale(this.#id, { field, when })
112
+ }
113
+ }
@@ -0,0 +1,166 @@
1
+ import type { Record } from './record'
2
+
3
+ export type CacheTypeDef = {
4
+ types: {
5
+ [typeName: string]: {
6
+ idFields: {
7
+ [fieldName: string]: any
8
+ }
9
+ fields: {
10
+ [fieldName: string]: {
11
+ args: any
12
+ type: any
13
+ }
14
+ }
15
+ // the fragments we know about are passed as a list of pairs
16
+ // that map the tag return type to the data shape and input
17
+ fragments: [any, any, any][]
18
+ }
19
+ }
20
+ lists: {
21
+ [listName: string]: {
22
+ types: any
23
+ filters: any
24
+ }
25
+ }
26
+ // we need to map query tag values to their result
27
+ // to pass be able to pass queries to the read and write methods
28
+ // entries in the tuple are graphql tag, query shape, query input
29
+ queries: [any, any, any][]
30
+
31
+ // a union of valid runtime types (default scalars | config types)
32
+ scalars: any
33
+ }
34
+
35
+ export type ValidTypes<Def extends CacheTypeDef> = keyof Def['types']
36
+
37
+ export type TypeFields<
38
+ Def extends CacheTypeDef,
39
+ Type extends keyof Def['types']
40
+ > = Def['types'][Type]['fields']
41
+
42
+ export type TypeFieldNames<
43
+ Def extends CacheTypeDef,
44
+ Type extends keyof Def['types']
45
+ // Extract is necessary because javascript allows numbers to be used as strings when indexing objects
46
+ // for more information: https://stackoverflow.com/questions/51808160/keyof-inferring-string-number-when-key-is-only-a-string
47
+ > = Extract<keyof TypeFields<Def, Type>, string>
48
+
49
+ export type TypeNames<Def extends CacheTypeDef> = Extract<
50
+ Exclude<ValidTypes<Def>, '__ROOT__'>,
51
+ string
52
+ >
53
+
54
+ export type FragmentList<
55
+ Def extends CacheTypeDef,
56
+ Type extends ValidTypes<Def>
57
+ > = Def['types'][Type]['fragments']
58
+
59
+ export type QueryList<Def extends CacheTypeDef> = Def['queries']
60
+
61
+ export type IDFields<
62
+ Def extends CacheTypeDef,
63
+ Type extends keyof Def['types']
64
+ > = Def['types'][Type]['idFields']
65
+
66
+ export type ProxyUnion<Def extends CacheTypeDef, U> = U extends null
67
+ ? null
68
+ : U extends TypeNames<Def>
69
+ ? Record<Def, U>
70
+ : never
71
+
72
+ export type FieldType<
73
+ Def extends CacheTypeDef,
74
+ Type extends keyof Def['types'],
75
+ Field extends keyof TypeFields<Def, Type>
76
+ > = TypeFields<Def, Type>[Field]['type']
77
+
78
+ export type ArgType<
79
+ Def extends CacheTypeDef,
80
+ Type extends keyof Def['types'],
81
+ Field extends keyof TypeFields<Def, Type>
82
+ > = TypeFields<Def, Type>[Field]['args']
83
+
84
+ export type ValidLists<Def extends CacheTypeDef> = Extract<keyof Def['lists'], string>
85
+
86
+ export type ListFilters<
87
+ Def extends CacheTypeDef,
88
+ ListName extends ValidLists<Def>
89
+ > = Def['lists'][ListName]['filters'] extends any
90
+ ? {
91
+ must?: Def['lists'][ListName]['filters']
92
+ must_not?: Def['lists'][ListName]['filters']
93
+ }
94
+ : never
95
+
96
+ export type ListType<Def extends CacheTypeDef, Name extends ValidLists<Def>> = ProxyUnion<
97
+ Def,
98
+ Def['lists'][Name]['types']
99
+ >
100
+
101
+ // This same structure is repeated because when I dry'd the structure to
102
+ //
103
+ // type FragmentDefinition<_List, _Index, _Target>
104
+ // type FragmentVariables<_Def, _Type, _Target> = FragmentDefinition<FragmentList<...>>
105
+ //
106
+ // then typescript wasn't responding on every change... Having the duplication makes
107
+ // it very responsive.
108
+
109
+ /**
110
+ * Note on the `_Key extends _Target && _Target extends _Key` check:
111
+ * Sometimes two queries might have a similar shape/inputs, e.g.:
112
+ *
113
+ * query Query1($userId: ID!) { | query Query2($userId: ID!) {
114
+ * user(id: $userId) { | user($id: $userId) {
115
+ * id | id
116
+ * name | name
117
+ * } | birthDate
118
+ * } | }
119
+ * | }
120
+ *
121
+ * To TypeScript, it would look like Query2's result would extend Query1's result.
122
+ * But if Query2 was listed in front of Query1 in the queries array above, `_Key extends _Target` will evaluate to true,
123
+ * causing it to return Query2's input/result types, while you were looking for Query1's input/result types.
124
+ * The additional `_Target extends _Key` ensures that the two objects have exactly the same shape, at least prompting the
125
+ * user for the correct fields.
126
+ */
127
+
128
+ export type FragmentVariables<_List, _Target> = _List extends [infer Head, ...infer Rest]
129
+ ? Head extends [infer _Key, infer _Value, infer _Input]
130
+ ? _Key extends _Target
131
+ ? _Target extends _Key
132
+ ? _Input
133
+ : FragmentValue<Rest, _Target>
134
+ : FragmentValue<Rest, _Target>
135
+ : 'Encountered unknown fragment. Please make sure your runtime is up to date (ie, `vite dev` or `vite build`).'
136
+ : 'Encountered unknown fragment. Please make sure your runtime is up to date (ie, `vite dev` or `vite build`).'
137
+
138
+ export type FragmentValue<List, _Target> = List extends [infer Head, ...infer Rest]
139
+ ? Head extends [infer _Key, infer _Value, infer _Input]
140
+ ? _Key extends _Target
141
+ ? _Target extends _Key
142
+ ? _Value
143
+ : FragmentValue<Rest, _Target>
144
+ : FragmentValue<Rest, _Target>
145
+ : 'Encountered unknown fragment. Please make sure your runtime is up to date (ie, `vite dev` or `vite build`).'
146
+ : 'Encountered unknown fragment. Please make sure your runtime is up to date (ie, `vite dev` or `vite build`).'
147
+
148
+ export type QueryValue<List, _Target> = List extends [infer Head, ...infer Rest]
149
+ ? Head extends [infer _Key, infer _Value, infer _Input]
150
+ ? _Key extends _Target
151
+ ? _Target extends _Key
152
+ ? _Value
153
+ : QueryValue<Rest, _Target>
154
+ : QueryValue<Rest, _Target>
155
+ : 'Encountered unknown query.Please make sure your runtime is up to date (ie, `vite dev` or `vite build`).'
156
+ : 'Encountered unknown query.Please make sure your runtime is up to date (ie, `vite dev` or `vite build`).'
157
+
158
+ export type QueryInput<List, _Target> = List extends [infer Head, ...infer Rest]
159
+ ? Head extends [infer _Key, infer _Value, infer _Input]
160
+ ? _Key extends _Target
161
+ ? _Target extends _Key
162
+ ? _Input
163
+ : QueryInput<Rest, _Target>
164
+ : QueryInput<Rest, _Target>
165
+ : 'Encountered unknown query.Please make sure your runtime is up to date (ie, `vite dev` or `vite build`).'
166
+ : 'Encountered unknown query.Please make sure your runtime is up to date (ie, `vite dev` or `vite build`).'
@@ -0,0 +1 @@
1
+ export { Server } from 'houdini/router/server'