@graphcommerce/algolia-insights 9.0.0-canary.84

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,6 @@
1
+ mutation AlgoliaSendEvent($events: [AlgoliaEventsItems_Input]!) {
2
+ algolia_pushEvents(input: { events: $events }) {
3
+ message
4
+ status
5
+ }
6
+ }
@@ -0,0 +1,378 @@
1
+ /* eslint-disable arrow-body-style */
2
+ import { useAlgoliaIndexName, useAlgoliaQuery } from '@graphcommerce/algolia-products'
3
+ import { GoogleEventTypes, sendEvent } from '@graphcommerce/google-datalayer'
4
+ import { useApolloClient } from '@graphcommerce/graphql'
5
+ import type { AlgoliaEventsItems_Input } from '@graphcommerce/graphql-mesh'
6
+ import { CustomerDocument } from '@graphcommerce/magento-customer/hooks/Customer.gql'
7
+ import { isFilterTypeEqual, ProductFilterParams } from '@graphcommerce/magento-product'
8
+ import { cookie } from '@graphcommerce/next-ui'
9
+ import { useDebounce } from '@graphcommerce/react-hook-form'
10
+ import { useEventCallback } from '@mui/material'
11
+ import { useRef } from 'react'
12
+ import { AlgoliaSendEventDocument } from '../graphql/AlgoliaSendEvent.gql'
13
+
14
+ const getSHA256Hash = async (input: string) => {
15
+ const textAsBuffer = new TextEncoder().encode(input)
16
+ const hashBuffer = await window.crypto.subtle.digest('SHA-256', textAsBuffer)
17
+ const hashArray = Array.from(new Uint8Array(hashBuffer))
18
+ const hash = hashArray.map((item) => item.toString(16).padStart(2, '0')).join('')
19
+ return hash
20
+ }
21
+
22
+ export const ALGOLIA_USER_TOKEN_COOKIE_NAME = '_algolia_userToken'
23
+
24
+ function mapSelectedFiltersToAlgoliaEvent(filters: ProductFilterParams['filters']) {
25
+ const flattenedFilters: string[] = []
26
+
27
+ Object.entries(filters).forEach(([key, filter]) => {
28
+ if (isFilterTypeEqual(filter)) {
29
+ const valueArray = (filter.eq ? [filter.eq] : (filter.in ?? [])) as string[]
30
+ valueArray.forEach((value) => {
31
+ if (key === 'category_uid') {
32
+ flattenedFilters.push(`categoryIds:${atob(value)}`)
33
+ } else {
34
+ flattenedFilters.push(`${key}:${encodeURIComponent(value)}`)
35
+ }
36
+ })
37
+ }
38
+
39
+ // if (isFilterTypeMatch(value)) return null
40
+ // if (isFilterTypeRange(value)) return null
41
+ })
42
+
43
+ return flattenedFilters
44
+ }
45
+
46
+ const LOCAL_STORAGE_KEY = '_algolia_conversion'
47
+ function getObjectIDToQuery(): Record<string, { queryID: string; filters: string[] }> {
48
+ return JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY) || 'null') ?? {}
49
+ }
50
+
51
+ function clearAlgoliaIdToQuery() {
52
+ window.localStorage.removeItem(LOCAL_STORAGE_KEY)
53
+ }
54
+
55
+ function saveAlgoliaIdToQuery(
56
+ objectIDs: string[],
57
+ queryID: string,
58
+ incomingFilters: ProductFilterParams['filters'],
59
+ ) {
60
+ const current = getObjectIDToQuery()
61
+ const filters = mapSelectedFiltersToAlgoliaEvent(incomingFilters)
62
+
63
+ objectIDs.forEach((objectID) => {
64
+ current[objectID] = { queryID, filters }
65
+ })
66
+
67
+ window.localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(current))
68
+ }
69
+
70
+ type AlgoliaEventCommon = {
71
+ index: string
72
+ userToken: string
73
+ authenticatedUserToken?: string
74
+ // timestamp: bigint
75
+ queryID?: string
76
+ }
77
+
78
+ const prevFilters: Record<string, string[]> = {}
79
+
80
+ const dataLayerToAlgoliaMap: {
81
+ [K in keyof Partial<GoogleEventTypes>]: (
82
+ eventName: K,
83
+ eventData: GoogleEventTypes[K],
84
+ common: AlgoliaEventCommon,
85
+ ) => AlgoliaEventsItems_Input[]
86
+ } = {
87
+ // todo should we use view_item or view_item_list?
88
+ view_item_list: (eventName, eventData, { queryID, ...common }) => {
89
+ const objectIDs = eventData.items.map((item) => atob(item.item_uid))
90
+
91
+ const events: AlgoliaEventsItems_Input[] = []
92
+
93
+ if (
94
+ // Values of filters are different when using Algolia vs Magento, thus it does not make sense to send the filters
95
+ queryID &&
96
+ eventData.filter_params?.filters &&
97
+ Object.keys(eventData.filter_params?.filters).length > 0
98
+ ) {
99
+ const filters = mapSelectedFiltersToAlgoliaEvent(eventData.filter_params.filters)
100
+
101
+ const newlyAppliedFilters = filters.filter(
102
+ (filter) => !prevFilters[eventData.item_list_name]?.includes(filter),
103
+ )
104
+ prevFilters[eventData.item_list_name] = filters
105
+
106
+ if (newlyAppliedFilters.length > 0) {
107
+ events.push({
108
+ Clicked_filters_Input: {
109
+ eventName: `${eventName}_filter_diff`,
110
+ eventType: 'click',
111
+ filters: newlyAppliedFilters,
112
+ ...common,
113
+ },
114
+ })
115
+ }
116
+
117
+ // There is a max of 10 filters per event, if there are more than 10 items
118
+ // we need to split the event into multiple events
119
+ for (let i = 0; i < filters.length; i += 10) {
120
+ events.push({
121
+ Viewed_filters_Input: {
122
+ eventName: `${eventName}_filters`,
123
+ eventType: 'view',
124
+ filters: filters.slice(i, i + 10),
125
+ ...common,
126
+ },
127
+ })
128
+ }
129
+ }
130
+
131
+ // There is a max of 20 ObjectIDs per event, if there are more than 20 items
132
+ // we need to split the event into multiple events
133
+ for (let i = 0; i < objectIDs.length; i += 20) {
134
+ events.push({
135
+ Viewed_object_IDs_Input: {
136
+ objectIDs: objectIDs.slice(i, i + 20),
137
+ eventName,
138
+ eventType: 'view',
139
+ ...common,
140
+ },
141
+ })
142
+ }
143
+
144
+ return events
145
+ },
146
+
147
+ select_item: (eventName, eventData, { queryID, ...common }) => {
148
+ const objectIDs = eventData.items.map((item) => atob(item.item_uid))
149
+ if (queryID) saveAlgoliaIdToQuery(objectIDs, queryID, eventData.filter_params?.filters ?? {})
150
+
151
+ return queryID
152
+ ? [
153
+ {
154
+ Clicked_object_IDs_after_search_Input: {
155
+ eventName,
156
+ eventType: 'click',
157
+ objectIDs,
158
+ positions: eventData.items.map((item) => item.index + 1),
159
+ queryID,
160
+ ...common,
161
+ },
162
+ } satisfies AlgoliaEventsItems_Input,
163
+ ]
164
+ : [
165
+ {
166
+ Clicked_object_IDs_Input: {
167
+ eventName,
168
+ eventType: 'click',
169
+ objectIDs: eventData.items.map((item) => atob(item.item_uid)),
170
+ ...common,
171
+ },
172
+ } satisfies AlgoliaEventsItems_Input,
173
+ ]
174
+ },
175
+ add_to_cart: (eventName, eventData, { queryID: _, ...common }) => {
176
+ // It seems that these two events are 'simplified' versions of the Add_to_cart events.
177
+ // - Converted_object_IDs_after_search_Input
178
+ // - Converted_object_IDs_Input
179
+
180
+ const events: AlgoliaEventsItems_Input[] = []
181
+
182
+ const mapping = getObjectIDToQuery()
183
+ const objectIDs = eventData.items.map((item) => atob(item.item_uid))
184
+
185
+ const relevant = objectIDs.map((objectID) => mapping[objectID])
186
+ const queryID = relevant?.[0]?.queryID
187
+ const filters = [...new Set(...relevant.map((item) => item.filters))]
188
+
189
+ if (filters.length > 0) {
190
+ // There is a max of 10 filters per event, if there are more than 10 items
191
+ // we need to split the event into multiple events
192
+ for (let i = 0; i < filters.length; i += 10) {
193
+ events.push({
194
+ Converted_filters_Input: {
195
+ eventName: `${eventName}_filters`,
196
+ eventType: 'conversion',
197
+ filters: filters.slice(i, i + 10),
198
+ ...common,
199
+ },
200
+ })
201
+ }
202
+ }
203
+
204
+ if (queryID) {
205
+ events.push({
206
+ Added_to_cart_object_IDs_after_search_Input: {
207
+ queryID,
208
+ eventName,
209
+ eventType: 'conversion',
210
+ eventSubtype: 'addToCart',
211
+ objectIDs: eventData.items.map((item) => atob(item.item_uid)),
212
+ objectData: eventData.items.map((item) => ({
213
+ discount: { Float: Number(item.discount?.toFixed(14)) ?? 0 },
214
+ price: { Float: Number(item.price.toFixed(14)) },
215
+ quantity: item.quantity,
216
+ })),
217
+ currency: eventData.currency,
218
+ value: { Float: Number(eventData.value.toFixed(14)) },
219
+ ...common,
220
+ },
221
+ } satisfies AlgoliaEventsItems_Input)
222
+ } else {
223
+ events.push({
224
+ Added_to_cart_object_IDs_Input: {
225
+ eventName,
226
+ eventType: 'conversion',
227
+ eventSubtype: 'addToCart',
228
+ objectIDs: eventData.items.map((item) => atob(item.item_uid)),
229
+ objectData: eventData.items.map((item) => ({
230
+ discount: { Float: item.discount ?? 0 },
231
+ price: { Float: Number(item.price.toFixed(14)) },
232
+ quantity: item.quantity,
233
+ })),
234
+ currency: eventData.currency,
235
+ value: { Float: Number(eventData.value.toFixed(14)) },
236
+ ...common,
237
+ },
238
+ } satisfies AlgoliaEventsItems_Input)
239
+ }
240
+
241
+ return events
242
+ },
243
+
244
+ purchase: (eventName, eventData, common) => {
245
+ const mapping = getObjectIDToQuery()
246
+ const isAfterSearch = !!eventData.items.find((item) => mapping[atob(item.item_uid)]?.queryID)
247
+
248
+ const events: AlgoliaEventsItems_Input[] = []
249
+
250
+ const objectIDs = eventData.items.map((item) => atob(item.item_uid))
251
+ const relevant = objectIDs.map((objectID) => mapping[objectID])
252
+ const filters = [...new Set(...relevant.map((item) => item.filters))]
253
+
254
+ if (filters.length > 0) {
255
+ // There is a max of 10 filters per event, if there are more than 10 items
256
+ // we need to split the event into multiple events
257
+ for (let i = 0; i < filters.length; i += 10) {
258
+ events.push({
259
+ Converted_filters_Input: {
260
+ eventName: `${eventName}_filters`,
261
+ eventType: 'conversion',
262
+ filters: filters.slice(i, i + 10),
263
+ ...common,
264
+ },
265
+ })
266
+ }
267
+ }
268
+
269
+ if (isAfterSearch) {
270
+ events.push({
271
+ Purchased_object_IDs_after_search_Input: {
272
+ eventName,
273
+ eventType: 'conversion',
274
+ eventSubtype: 'purchase',
275
+ objectIDs: eventData.items.map((item) => atob(item.item_uid)),
276
+ objectData: eventData.items.map((item) => ({
277
+ discount: { Float: Number(item.discount?.toFixed(14)) ?? 0 },
278
+ price: { Float: Number(item.price.toFixed(14)) },
279
+ quantity: item.quantity,
280
+ queryID: mapping[atob(item.item_uid)]?.queryID,
281
+ })),
282
+ currency: eventData.currency,
283
+ value: { Float: Number(eventData.value.toFixed(13)) },
284
+ ...common,
285
+ },
286
+ } satisfies AlgoliaEventsItems_Input)
287
+ } else {
288
+ events.push({
289
+ Purchased_object_IDs_Input: {
290
+ eventName,
291
+ eventType: 'conversion',
292
+ eventSubtype: 'purchase',
293
+ objectIDs: eventData.items.map((item) => atob(item.item_uid)),
294
+ objectData: eventData.items.map((item) => ({
295
+ discount: { Float: Number(item.discount?.toFixed(14)) ?? 0 },
296
+ price: { Float: Number(item.price.toFixed(14)) },
297
+ quantity: item.quantity,
298
+ })),
299
+ currency: eventData.currency,
300
+ value: { Float: Number(eventData.value.toFixed(13)) },
301
+ ...common,
302
+ },
303
+ } satisfies AlgoliaEventsItems_Input)
304
+ }
305
+
306
+ clearAlgoliaIdToQuery()
307
+ return events
308
+ },
309
+ }
310
+
311
+ export function useSendAlgoliaEvent() {
312
+ const client = useApolloClient()
313
+ const index = useAlgoliaIndexName()
314
+ const algoliaQuery = useAlgoliaQuery()
315
+
316
+ const eventsBuffer = useRef<AlgoliaEventsItems_Input[]>([])
317
+ const submit = useDebounce(
318
+ () => {
319
+ if (eventsBuffer.current.length === 0) return
320
+
321
+ const events = eventsBuffer.current
322
+ eventsBuffer.current = []
323
+
324
+ client
325
+ .mutate({
326
+ mutation: AlgoliaSendEventDocument,
327
+ variables: { events },
328
+ })
329
+ .then(({ data, errors }) => {
330
+ const errorMessage = (errors ?? []).map((e) => e.message)
331
+ if (errorMessage.length > 0) {
332
+ console.log('There was a problem sending the Algolia event to the server', errorMessage)
333
+ }
334
+
335
+ const response = data?.algolia_pushEvents
336
+
337
+ if (response && response.status !== 200) {
338
+ console.log(
339
+ 'There was a problem sending the Algolia event to the server Y',
340
+ response.message,
341
+ )
342
+ }
343
+ })
344
+ .catch((e) => {
345
+ console.error('There was a problem sending the Algolia event to the server Z', e)
346
+ })
347
+ },
348
+ 2000,
349
+ { trailing: true },
350
+ )
351
+
352
+ return useEventCallback<typeof sendEvent>(async (eventName, eventData) => {
353
+ const email = client.cache.readQuery({ query: CustomerDocument })?.customer?.email
354
+ const authenticatedUserToken = email ? await getSHA256Hash(email) : undefined
355
+ let userToken = cookie(ALGOLIA_USER_TOKEN_COOKIE_NAME)
356
+ if (!userToken) {
357
+ userToken = (Math.random() + 1).toString(36).substring(2)
358
+ cookie(ALGOLIA_USER_TOKEN_COOKIE_NAME, userToken, { sameSite: true })
359
+ }
360
+
361
+ // if (authenticatedUserToken) {
362
+ // userToken = authenticatedUserToken
363
+ // }
364
+
365
+ const events = dataLayerToAlgoliaMap[eventName]?.(eventName, eventData, {
366
+ index,
367
+ userToken,
368
+ authenticatedUserToken,
369
+ queryID: algoliaQuery.queryID ?? undefined,
370
+ // timestamp: (Math.floor(Date.now() / 1000)),
371
+ })
372
+
373
+ if (events) {
374
+ eventsBuffer.current.push(...events)
375
+ submit()
376
+ }
377
+ })
378
+ }
package/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from './hooks/useSendAlgoliaEvent'
2
+ export * from './graphql/AlgoliaSendEvent.gql'
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@graphcommerce/algolia-insights",
3
+ "homepage": "https://www.graphcommerce.org/",
4
+ "repository": "github:graphcommerce-org/graphcommerce",
5
+ "version": "9.0.0-canary.84",
6
+ "sideEffects": false,
7
+ "prettier": "@graphcommerce/prettier-config-pwa",
8
+ "eslintConfig": {
9
+ "extends": "@graphcommerce/eslint-config-pwa",
10
+ "parserOptions": {
11
+ "project": "./tsconfig.json"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "generate": "tsx scripts/generate-insights-spec.mts"
16
+ },
17
+ "peerDependencies": {
18
+ "@graphcommerce/algolia-products": "^9.0.0-canary.84",
19
+ "@graphcommerce/google-datalayer": "^9.0.0-canary.84",
20
+ "@graphcommerce/graphql": "^9.0.0-canary.84",
21
+ "@graphcommerce/graphql-mesh": "^9.0.0-canary.84",
22
+ "@graphcommerce/magento-customer": "^9.0.0-canary.84",
23
+ "@graphcommerce/magento-product": "^9.0.0-canary.84",
24
+ "@graphcommerce/next-config": "^9.0.0-canary.84",
25
+ "@graphcommerce/next-ui": "^9.0.0-canary.84",
26
+ "@graphcommerce/react-hook-form": "^9.0.0-canary.84",
27
+ "@mui/material": "*",
28
+ "react": "^18.2.0"
29
+ },
30
+ "devDependencies": {
31
+ "graphql": "^16.0.0",
32
+ "tsx": "^4.16.2"
33
+ }
34
+ }
@@ -0,0 +1,16 @@
1
+ import type { getSearchResultsInput as getSearchResultsInputType } from '@graphcommerce/algolia-products'
2
+ import { FunctionPlugin, PluginConfig } from '@graphcommerce/next-config'
3
+
4
+ export const config: PluginConfig = {
5
+ type: 'function',
6
+ module: '@graphcommerce/algolia-products',
7
+ }
8
+
9
+ export const getSearchResultsInput: FunctionPlugin<typeof getSearchResultsInputType> = async (
10
+ prev,
11
+ args,
12
+ context,
13
+ ) => ({
14
+ ...(await prev(args, context)),
15
+ analytics: true,
16
+ })
@@ -0,0 +1,14 @@
1
+ import type { getSearchSuggestionsInput as getSearchSuggestionsInputType } from '@graphcommerce/algolia-products'
2
+ import { FunctionPlugin, PluginConfig } from '@graphcommerce/next-config'
3
+
4
+ export const config: PluginConfig = {
5
+ type: 'function',
6
+ module: '@graphcommerce/algolia-products',
7
+ }
8
+
9
+ export const getSearchSuggestionsInput: FunctionPlugin<
10
+ typeof getSearchSuggestionsInputType
11
+ > = async (prev, search, context) => ({
12
+ ...(await prev(search, context)),
13
+ analytics: true,
14
+ })
@@ -0,0 +1,59 @@
1
+ import type { meshConfig as meshConfigBase } from '@graphcommerce/graphql-mesh/meshConfig'
2
+ import type { FunctionPlugin, PluginConfig } from '@graphcommerce/next-config'
3
+
4
+ export const config: PluginConfig = {
5
+ module: '@graphcommerce/graphql-mesh/meshConfig',
6
+ type: 'function',
7
+ }
8
+
9
+ export const meshConfig: FunctionPlugin<typeof meshConfigBase> = (
10
+ prev,
11
+ baseConfig,
12
+ graphCommerceConfig,
13
+ ) =>
14
+ prev(
15
+ {
16
+ ...baseConfig,
17
+ sources: [
18
+ ...baseConfig.sources,
19
+ {
20
+ name: 'algoliaInsights',
21
+ handler: {
22
+ openapi: {
23
+ endpoint: `https://insights.algolia.io/`,
24
+ source: '@graphcommerce/algolia-insights/algolia-insights-spec.yaml',
25
+ ignoreErrorResponses: true,
26
+ schemaHeaders: {
27
+ 'X-Algolia-Application-Id': graphCommerceConfig.algolia.applicationId,
28
+ 'X-Algolia-API-Key': graphCommerceConfig.algolia.searchOnlyApiKey,
29
+ },
30
+ operationHeaders: {
31
+ 'X-Algolia-Application-Id': graphCommerceConfig.algolia.applicationId,
32
+ 'X-Algolia-API-Key': graphCommerceConfig.algolia.searchOnlyApiKey,
33
+ },
34
+ selectQueryOrMutationField: [{ type: 'Query', fieldName: 'sendEvent' }],
35
+ },
36
+ },
37
+ transforms: [
38
+ {
39
+ prefix: {
40
+ value: 'algolia_',
41
+ includeRootOperations: true,
42
+ includeTypes: false,
43
+ mode: 'bare',
44
+ },
45
+ },
46
+ {
47
+ prefix: {
48
+ value: 'Algolia',
49
+ includeRootOperations: false,
50
+ includeTypes: true,
51
+ mode: 'bare',
52
+ },
53
+ },
54
+ ],
55
+ },
56
+ ],
57
+ },
58
+ graphCommerceConfig,
59
+ )
@@ -0,0 +1,22 @@
1
+ import type {
2
+ sendEvent,
3
+ useSendEvent as useSendEventBase,
4
+ } from '@graphcommerce/google-datalayer/api/sendEvent'
5
+ import type { FunctionPlugin, PluginConfig } from '@graphcommerce/next-config'
6
+ import { useEventCallback } from '@mui/material'
7
+ import { useSendAlgoliaEvent } from '../hooks/useSendAlgoliaEvent'
8
+
9
+ export const config: PluginConfig = {
10
+ module: '@graphcommerce/google-datalayer',
11
+ type: 'function',
12
+ }
13
+
14
+ export const useSendEvent: FunctionPlugin<typeof useSendEventBase> = (prev) => {
15
+ const originalSendEvent = prev()
16
+ const sendAlgoliaEvent = useSendAlgoliaEvent()
17
+
18
+ return useEventCallback<typeof sendEvent>((eventName, eventData) => {
19
+ originalSendEvent(eventName, eventData)
20
+ sendAlgoliaEvent(eventName, eventData)
21
+ })
22
+ }
@@ -0,0 +1,72 @@
1
+ import yaml from 'js-yaml'
2
+ import { writeFile, readFile } from 'node:fs/promises'
3
+ import { OpenAPIV3 } from 'openapi-types'
4
+ import prettier from 'prettier'
5
+ import conf from '@graphcommerce/prettier-config-pwa'
6
+
7
+ const response = await fetch(
8
+ 'https://raw.githubusercontent.com/algolia/api-clients-automation/main/specs/bundled/insights.yml',
9
+ )
10
+
11
+ const openApiSchema = yaml.load(await response.text()) as OpenAPIV3.Document
12
+
13
+ const allMethods = [
14
+ OpenAPIV3.HttpMethods.TRACE,
15
+ OpenAPIV3.HttpMethods.POST,
16
+ OpenAPIV3.HttpMethods.PUT,
17
+ OpenAPIV3.HttpMethods.GET,
18
+ OpenAPIV3.HttpMethods.DELETE,
19
+ OpenAPIV3.HttpMethods.PATCH,
20
+ OpenAPIV3.HttpMethods.OPTIONS,
21
+ OpenAPIV3.HttpMethods.HEAD,
22
+ ]
23
+
24
+ const { info, openapi, components, tags, ...rest } = openApiSchema
25
+
26
+ function filterPaths(
27
+ paths: OpenAPIV3.PathsObject,
28
+ allow: Record<string, OpenAPIV3.HttpMethods[]>,
29
+ ): OpenAPIV3.PathsObject {
30
+ const allowedEntries = Object.entries(allow)
31
+
32
+ return Object.fromEntries(
33
+ Object.entries(paths)
34
+ .map(([path, pathItem]) => {
35
+ if (!pathItem) return [path, pathItem]
36
+ const newValue = pathItem
37
+
38
+ const [allowedPath, allowedMethods] =
39
+ allowedEntries.find(([allowedPath]) => allowedPath === path) ?? []
40
+
41
+ if (!allowedPath || !allowedMethods) return [path, undefined]
42
+
43
+ allMethods
44
+ .filter((method) => !allowedMethods.includes(method))
45
+ .forEach((method) => {
46
+ newValue[method] = undefined
47
+ })
48
+
49
+ return [path, newValue]
50
+ })
51
+ .filter(([path, pathItem]) => {
52
+ if (!pathItem) return false
53
+ if (allMethods.every((key) => !pathItem[key])) return false
54
+ return true
55
+ }),
56
+ )
57
+ }
58
+
59
+ const newSchema: OpenAPIV3.Document = {
60
+ openapi,
61
+ info,
62
+ paths: filterPaths(openApiSchema.paths, { '/1/events': [OpenAPIV3.HttpMethods.POST] }),
63
+ components,
64
+ }
65
+
66
+ await writeFile(
67
+ './algolia-insights-spec.yaml',
68
+ await prettier.format(JSON.stringify(newSchema), {
69
+ parser: 'json',
70
+ ...conf,
71
+ }),
72
+ )
package/tsconfig.json ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "exclude": ["**/node_modules", "**/.*/"],
3
+ "include": ["**/*.ts", "**/*.tsx"],
4
+ "extends": "@graphcommerce/typescript-config-pwa/nextjs.json",
5
+ }