svelte-algolia
Advanced tools
Comparing version 0.2.7 to 0.2.10
export function onClickOutside(node, cb) { | ||
const detectClickOutside = (event) => { | ||
if (node && !node.contains(event.target) && !event.defaultPrevented) { | ||
node.dispatchEvent(new CustomEvent(`clickOutside`, node)) | ||
if (cb) cb() | ||
} | ||
} | ||
document.addEventListener(`click`, detectClickOutside) | ||
return { | ||
destroy: () => document.removeEventListener(`click`, detectClickOutside), | ||
} | ||
const dispatchOnClickOutside = (event) => { | ||
const clickWasOutside = node && !node.contains(event.target); | ||
if (clickWasOutside && !event.defaultPrevented) { | ||
node.dispatchEvent(new CustomEvent(`clickOutside`)); | ||
if (cb) | ||
cb(); | ||
} | ||
}; | ||
document.addEventListener(`click`, dispatchOnClickOutside); | ||
return { | ||
destroy() { | ||
document.removeEventListener(`click`, dispatchOnClickOutside); | ||
}, | ||
}; | ||
} |
@@ -1,1 +0,1 @@ | ||
export { default } from './Search.svelte' | ||
export { default } from './Search.svelte'; |
303
main.js
@@ -1,178 +0,153 @@ | ||
/* eslint-disable no-console */ | ||
import algoliasearch from 'algoliasearch' | ||
import algoliasearch from 'algoliasearch'; | ||
const defaultConfig = { | ||
partialUpdates: false, | ||
verbosity: 1, | ||
matchFields: [], | ||
settings: {}, | ||
partialUpdates: false, | ||
verbosity: 1, | ||
matchFields: [], | ||
settings: {}, | ||
}; | ||
// Partial<T> makes all keys in T optional | ||
export function deepEqual(obj1, obj2) { | ||
return obj1 && obj2 && typeof obj1 === `object` && typeof obj1 === typeof obj2 | ||
? Object.keys(obj1).length === Object.keys(obj2).length && | ||
Object.keys(obj1).every((key) => deepEqual(obj1[key], obj2[key])) | ||
: obj1 === obj2; | ||
} | ||
export function deepEqual(x, y) { | ||
return x && y && typeof x === `object` && typeof x === typeof y | ||
? Object.keys(x).length === Object.keys(y).length && | ||
Object.keys(x).every((key) => deepEqual(x[key], y[key])) | ||
: x === y | ||
export async function indexAlgolia({ appId, apiKey, indices, ...rest }) { | ||
const client = algoliasearch(appId, apiKey); | ||
const config = { ...defaultConfig, ...rest }; | ||
if (config.verbosity > 0) { | ||
if (config.partialUpdates) | ||
console.log(`svelte-algolia: Partial updates enabled`); | ||
console.log(`svelte-algolia: ${indices.length} ${indices.length > 1 ? `indices` : `index`} to update`); | ||
} | ||
try { | ||
await Promise.all(indices.map(updateIndex(client, config))); | ||
} | ||
catch (err) { | ||
console.error(`failed to index to Algolia`, err); | ||
} | ||
} | ||
export async function indexAlgolia({ appId, apiKey, indices, ...config }) { | ||
const client = algoliasearch(appId, apiKey) | ||
config = { ...defaultConfig, ...config } | ||
if (config.verbosity > 0) { | ||
if (config.partialUpdates) | ||
console.log(`svelte-algolia: Partial updates enabled`) | ||
console.log( | ||
`svelte-algolia: ${indices.length} ${ | ||
indices.length > 1 ? `indices` : `index` | ||
} to update` | ||
) | ||
} | ||
try { | ||
await Promise.all(indices.map(updateIndex(client, config))) | ||
} catch (err) { | ||
console.error(`failed to index to Algolia`, err) | ||
} | ||
} | ||
const updateIndex = (client, config) => async (indexObj) => { | ||
const { partialUpdates = false, matchFields: mainMatchFields } = config | ||
const { name, getData, matchFields = mainMatchFields } = indexObj | ||
let { settings = config.settings } = indexObj | ||
const index = client.initIndex(name) | ||
const data = await callGetter(getData) | ||
// if user specified any settings, apply them | ||
// if the index doesn't exist yet, applying settings (even if empty) will create it | ||
// see https://algolia.com/doc/api-client/methods/manage-indices#create-an-index | ||
const { taskID } = await index.setSettings(settings) | ||
await index.waitTask(taskID) | ||
if (partialUpdates) { | ||
// get all match fields for all indices to minimize calls to the api | ||
const allMatchFields = [...new Set([...mainMatchFields, ...matchFields])] | ||
await partialUpdate(index, data, allMatchFields, config) | ||
} else { | ||
// if partialUpdates isn't true, overwrite old index with all of data | ||
await overwriteUpdate(index, data, client, config) | ||
} | ||
} | ||
const updateIndex = (client, config) => { | ||
return async (indexConfig) => { | ||
const { partialUpdates = false, matchFields: mainMatchFields } = config; | ||
const { name, getData, matchFields = mainMatchFields } = indexConfig; | ||
const { settings = config.settings || {} } = indexConfig; | ||
const index = client.initIndex(name); | ||
const data = await callGetter(getData); | ||
// if user specified any settings, apply them | ||
// if the index doesn't exist yet, applying settings (even if empty) will create it | ||
// see https://algolia.com/doc/api-client/methods/manage-indices#create-an-index | ||
const { taskID } = await index.setSettings(settings); | ||
await index.waitTask(taskID); | ||
if (partialUpdates) { | ||
// get all match fields for all indices to minimize calls to the api | ||
const allMatchFields = [...new Set([...mainMatchFields, ...matchFields])]; | ||
await partialUpdate(index, data, allMatchFields, config); | ||
} | ||
else { | ||
// if partialUpdates isn't true, overwrite old index with all of data | ||
await overwriteUpdate(index, data, client, config); | ||
} | ||
}; | ||
}; | ||
async function callGetter(getter) { | ||
const results = await getter() | ||
if (results.errors) | ||
console.error( | ||
`failed to index to Algolia, ` + | ||
`errors:\n ${JSON.stringify(results.errors, null, 2)}` | ||
) | ||
results.forEach((obj) => { | ||
if (!obj.objectID && !obj.id && !obj._id) | ||
console.error( | ||
`failed to index to svelte-algolia: ${JSON.stringify(obj, null, 2)}` + | ||
` has neither an 'objectID' nor 'id' key` | ||
) | ||
// convert to string to prevent processing items with integer IDs as new in partialUpdate | ||
obj.objectID = `${obj.objectID || obj.id || obj._id}` | ||
}) | ||
return results | ||
try { | ||
const results = await getter(); | ||
results.forEach((obj) => { | ||
if (!obj.objectID && !obj.id && !obj._id) | ||
console.error(`failed to index to svelte-algolia: ${JSON.stringify(obj, null, 2)}` + | ||
` has neither an 'objectID' nor 'id' key`); | ||
// convert to string to prevent processing items with integer IDs as new in partialUpdate | ||
obj.objectID = `${obj.objectID || obj.id || obj._id}`; | ||
}); | ||
return results; | ||
} | ||
catch (err) { | ||
console.error(`failed to index to Algolia due to ${err}`); | ||
} | ||
} | ||
async function overwriteUpdate(index, data, client, config) { | ||
const { indexName: name } = index | ||
try { | ||
const tmpIndex = client.initIndex(`${name}_tmp`) | ||
// copy settings from old to new index | ||
const settings = await index.getSettings() | ||
await tmpIndex.setSettings(settings) | ||
await tmpIndex.saveObjects(data).wait() | ||
// move the tmp index to the existing index, overwrites the latter | ||
await client.moveIndex(`${name}_tmp`, name).wait() | ||
if (config.verbosity > 0) | ||
console.log(`index '${name}': wrote ${data.length} items`) | ||
} catch (err) { | ||
console.error(err) | ||
// clean up by removing temporary index in case of errors | ||
await client.moveIndex(`${name}_tmp`, name).wait() | ||
} | ||
const { indexName: name } = index; | ||
try { | ||
const tmpIndex = client.initIndex(`${name}_tmp`); | ||
// copy settings from old to new index | ||
const settings = await index.getSettings(); | ||
await tmpIndex.setSettings(settings); | ||
await tmpIndex.saveObjects(data).wait(); | ||
// move the tmp index to the existing index, overwrites the latter | ||
await client.moveIndex(`${name}_tmp`, name).wait(); | ||
if (config.verbosity > 0) | ||
console.log(`index '${name}': wrote ${data.length} items`); | ||
} | ||
catch (err) { | ||
console.error(err); | ||
// clean up by removing temporary index in case of errors | ||
await client.moveIndex(`${name}_tmp`, name).wait(); | ||
} | ||
} | ||
async function partialUpdate(index, data, matchFields, config) { | ||
const { indexName: name } = index | ||
const existingObjects = await fetchExistingData(index, matchFields) | ||
const existingIDs = existingObjects.map((obj) => obj.objectID) | ||
const newIDs = data.map((obj) => obj.objectID) | ||
// objects to be added/updated | ||
const toIndex = data.filter((newObj) => { | ||
const { objectID: id } = newObj | ||
// object with new ID, needs to be indexed | ||
if (!existingIDs.includes(id)) return true | ||
if (matchFields) { | ||
// id matches existing object, so compare match fields to check if existing object neeeds to be updated | ||
if (!matchFields.every((field) => newObj[field])) { | ||
console.error( | ||
`when partialUpdates is true, the objects must have at least one of the match fields.` + | ||
`Current object:\n${JSON.stringify(newObj, null, 2)}` + | ||
`\nexpected one of these fields:\n${matchFields.join(`\n`)}` | ||
) | ||
} | ||
const existingObj = existingObjects.find((obj) => obj.objectID === id) | ||
// check if one or more fields differ, if so update object | ||
if (matchFields.some((field) => existingObj[field] !== newObj[field])) | ||
return true | ||
} else { | ||
// if user did not specify matchFields compare entire object for differences | ||
if (!deepEqual(oldObj, newObj)) { | ||
return true | ||
} | ||
const { indexName: name } = index; | ||
const existingObjects = await fetchExistingData(index, matchFields); | ||
const existingIDs = existingObjects.map((obj) => obj.objectID); | ||
const newIDs = data.map((obj) => obj.objectID); | ||
// objects to be added/updated | ||
const toIndex = data.filter((newObj) => { | ||
const { objectID: id } = newObj; | ||
// object with new ID, needs to be indexed | ||
if (!existingIDs.includes(id)) | ||
return true; | ||
if (matchFields) { | ||
// id matches existing object, so compare match fields to check if existing object needs to be updated | ||
if (!matchFields.every((field) => newObj[field])) { | ||
console.error(`when partialUpdates is true, the objects must have at least one of the match fields.` + | ||
`Current object:\n${JSON.stringify(newObj, null, 2)}` + | ||
`\nexpected one of these fields:\n${matchFields.join(`\n`)}`); | ||
} | ||
const existingObj = existingObjects.find((obj) => obj.objectID === id); | ||
// check if one or more fields differ, if so update object | ||
if (matchFields.some((field) => existingObj[field] !== newObj[field])) | ||
return true; | ||
} | ||
else { | ||
// if user did not specify matchFields compare entire object for differences | ||
if (!deepEqual(oldObj, newObj)) { | ||
return true; | ||
} | ||
} | ||
return false; // neither new nor changed object, no need to index | ||
}); | ||
// stale objects to be removed | ||
const toRemove = existingIDs.filter((id) => !newIDs.includes(id)); | ||
if (toIndex.length) { | ||
if (config.verbosity > 0) | ||
console.log(`index '${name}': found ${toIndex.length} new or modified objects; indexing...`); | ||
if (config.verbosity > 1) | ||
console.log(JSON.stringify(toIndex, null, 2)); | ||
await index.saveObjects(toIndex).wait(); | ||
} | ||
return false // neither new nor changed object, no need to index | ||
}) | ||
// stale objects to be removed | ||
const toRemove = existingIDs.filter((id) => !newIDs.includes(id)) | ||
if (toIndex.length) { | ||
if (config.verbosity > 0) | ||
console.log( | ||
`index '${name}': found ${toIndex.length} new or modified objects; indexing...` | ||
) | ||
if (config.verbosity > 1) console.log(JSON.stringify(toIndex, null, 2)) | ||
await index.saveObjects(toIndex).wait() | ||
} | ||
if (toRemove.length) { | ||
if (config.verbosity > 0) | ||
console.log( | ||
`index '${name}': found ${toRemove.length} stale objects; removing...` | ||
) | ||
if (config.verbosity > 1) console.log(JSON.stringify(toRemove, null, 2)) | ||
await index.deleteObjects(toRemove) | ||
} | ||
if (config.verbosity > 0) { | ||
if (toIndex.length === 0 && toRemove.length === 0) { | ||
console.log(`index '${name}': no updates necessary; skipping!`) | ||
} else { | ||
console.log(`index '${name}': done updating`) | ||
if (toRemove.length) { | ||
if (config.verbosity > 0) | ||
console.log(`index '${name}': found ${toRemove.length} stale objects; removing...`); | ||
if (config.verbosity > 1) | ||
console.log(JSON.stringify(toRemove, null, 2)); | ||
await index.deleteObjects(toRemove); | ||
} | ||
} | ||
if (config.verbosity > 0) { | ||
if (toIndex.length === 0 && toRemove.length === 0) { | ||
console.log(`index '${name}': no updates necessary; skipping!`); | ||
} | ||
else { | ||
console.log(`index '${name}': done updating`); | ||
} | ||
} | ||
} | ||
// Fetches all records for the current index from Algolia | ||
async function fetchExistingData(index, attributesToRetrieve) { | ||
const hits = [] | ||
await index.browseObjects({ | ||
query: ``, // Empty query matches all records | ||
batch: (batch) => hits.push(...batch), | ||
attributesToRetrieve, | ||
}) | ||
return hits | ||
const hits = []; | ||
await index.browseObjects({ | ||
query: ``, | ||
batch: (batch) => hits.push(...batch), | ||
attributesToRetrieve, | ||
}); | ||
return hits; | ||
} |
{ | ||
"name": "svelte-algolia", | ||
"version": "0.2.7", | ||
"description": "Algolia server-side index updater and client-side search component for Svelte projects", | ||
"author": "Janosh Riebesell <janosh.riebesell@gmail.com>", | ||
"homepage": "https://svelte-algolia.netlify.app", | ||
"repository": "https://github.com/janosh/svelte-algolia", | ||
"license": "MIT", | ||
"version": "0.2.10", | ||
"type": "module", | ||
"svelte": "Search.svelte", | ||
"keywords": [ | ||
@@ -13,13 +19,31 @@ "svelte", | ||
], | ||
"homepage": "https://svelte-algolia.netlify.app", | ||
"license": "MIT", | ||
"author": "Janosh Riebesell <janosh.riebesell@gmail.com>", | ||
"repository": "https://github.com/janosh/svelte-algolia", | ||
"dependencies": { | ||
"algoliasearch": "^4.10.2" | ||
"algoliasearch": "^4.11.0" | ||
}, | ||
"devDependencies": { | ||
"@sveltejs/adapter-static": "^1.0.0-next.24", | ||
"@sveltejs/kit": "^1.0.0-next.211", | ||
"@typescript-eslint/eslint-plugin": "^5.8.1", | ||
"@typescript-eslint/parser": "^5.8.1", | ||
"ava": "^3.15.0", | ||
"dotenv": "^10.0.0", | ||
"eslint": "^8.5.0", | ||
"eslint-plugin-svelte3": "^3.2.1", | ||
"hastscript": "^7.0.2", | ||
"mdsvex": "^0.9.8", | ||
"prettier": "^2.5.1", | ||
"prettier-plugin-svelte": "^2.5.1", | ||
"rehype-autolink-headings": "^6.1.1", | ||
"rehype-slug": "^5.0.1", | ||
"svelte": "^3.44.3", | ||
"svelte-check": "^2.2.11", | ||
"svelte-preprocess": "^4.10.1", | ||
"svelte-toc": "^0.2.0", | ||
"svelte2tsx": "^0.4.12", | ||
"typescript": "^4.5.4", | ||
"vite": "^2.7.10" | ||
}, | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"type": "module", | ||
"exports": { | ||
@@ -29,7 +53,6 @@ "./package.json": "./package.json", | ||
"./SearchIcon.svelte": "./SearchIcon.svelte", | ||
"./actions.js": "./actions.js", | ||
"./index.js": "./index.js", | ||
"./main.js": "./main.js", | ||
".": "./index.js" | ||
"./actions": "./actions.js", | ||
".": "./index.js", | ||
"./main": "./main.js" | ||
} | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
29854
13
248
21
261
1
Updatedalgoliasearch@^4.11.0