@basaltkit/search 1.1.0 → 1.3.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.
package/dist/index.d.ts CHANGED
@@ -103,6 +103,12 @@ declare class MeilisearchError extends BasaltError {
103
103
  readonly httpStatus: number;
104
104
  constructor(httpStatus: number, message: string);
105
105
  }
106
+ declare class SearchFilterFieldError extends BasaltError {
107
+ constructor(field: string);
108
+ }
109
+ declare class SearchIndexNameError extends BasaltError {
110
+ constructor(name: string);
111
+ }
106
112
  interface MeilisearchDriverOptions {
107
113
  host: string;
108
114
  apiKey?: string;
@@ -166,4 +172,4 @@ interface SearchPluginOptions {
166
172
  }
167
173
  declare function searchPlugin(options?: SearchPluginOptions): _basaltkit_core.BasaltPlugin<unknown>;
168
174
 
169
- export { type IndexDefinition, MeilisearchDriver, type MeilisearchDriverOptions, MeilisearchError, MemorySearchDriver, SEARCH, Search, type SearchDocument, type SearchDriver, type SearchHit, type SearchOptions, type SearchPluginOptions, type SearchQuery, type SearchResult, type SyncRule, TenantRequiredError, defineIndex, searchPlugin, syncRule };
175
+ export { type IndexDefinition, MeilisearchDriver, type MeilisearchDriverOptions, MeilisearchError, MemorySearchDriver, SEARCH, Search, type SearchDocument, type SearchDriver, SearchFilterFieldError, type SearchHit, SearchIndexNameError, type SearchOptions, type SearchPluginOptions, type SearchQuery, type SearchResult, type SyncRule, TenantRequiredError, defineIndex, searchPlugin, syncRule };
package/dist/index.js CHANGED
@@ -128,6 +128,22 @@ var MeilisearchError = class extends BasaltError2 {
128
128
  }
129
129
  httpStatus;
130
130
  };
131
+ var SearchFilterFieldError = class extends BasaltError2 {
132
+ constructor(field) {
133
+ super("SEARCH_INVALID_FILTER_FIELD", `Invalid filter field name: ${JSON.stringify(field)}.`);
134
+ }
135
+ };
136
+ var SearchIndexNameError = class extends BasaltError2 {
137
+ constructor(name) {
138
+ super("SEARCH_INVALID_INDEX_NAME", `Invalid index name: ${JSON.stringify(name)}.`);
139
+ }
140
+ };
141
+ var SAFE_INDEX_NAME = /^[A-Za-z0-9_-]+$/;
142
+ var assertValidIndexName = (name) => {
143
+ if (!SAFE_INDEX_NAME.test(name)) throw new SearchIndexNameError(name);
144
+ return name;
145
+ };
146
+ var SAFE_FILTER_FIELD = /^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*$/;
131
147
  var primaryKey = (tenantId, id) => Buffer.from(`${tenantId}::${id}`).toString("base64url");
132
148
  var quote = (value) => JSON.stringify(value);
133
149
  var MeilisearchDriver = class {
@@ -138,6 +154,7 @@ var MeilisearchDriver = class {
138
154
  options;
139
155
  fetch;
140
156
  async register(index) {
157
+ assertValidIndexName(index.name);
141
158
  try {
142
159
  await this.request("POST", "/indexes", { uid: index.name, primaryKey: "_pk" });
143
160
  } catch {
@@ -151,16 +168,20 @@ var MeilisearchDriver = class {
151
168
  await this.bulk(indexName, [document]);
152
169
  }
153
170
  async bulk(indexName, documents) {
171
+ assertValidIndexName(indexName);
154
172
  const withPk = documents.map((d) => ({ ...d, _pk: primaryKey(d.tenantId, d.id) }));
155
173
  await this.request("PUT", `/indexes/${indexName}/documents`, withPk);
156
174
  }
157
175
  async remove(indexName, tenantId, id) {
176
+ assertValidIndexName(indexName);
158
177
  await this.request("DELETE", `/indexes/${indexName}/documents/${primaryKey(tenantId, id)}`);
159
178
  }
160
179
  async clear(indexName) {
180
+ assertValidIndexName(indexName);
161
181
  await this.request("DELETE", `/indexes/${indexName}/documents`);
162
182
  }
163
183
  async search(indexName, query) {
184
+ assertValidIndexName(indexName);
164
185
  const result = await this.request("POST", `/indexes/${indexName}/search`, {
165
186
  q: query.q,
166
187
  filter: this.buildFilter(query.tenantId, query.filters),
@@ -182,6 +203,7 @@ var MeilisearchDriver = class {
182
203
  buildFilter(tenantId, filters) {
183
204
  const parts = [`tenantId = ${quote(tenantId)}`];
184
205
  for (const [field, value] of Object.entries(filters ?? {})) {
206
+ if (!SAFE_FILTER_FIELD.test(field)) throw new SearchFilterFieldError(field);
185
207
  parts.push(Array.isArray(value) ? `${field} IN [${value.map(quote).join(", ")}]` : `${field} = ${quote(value)}`);
186
208
  }
187
209
  return parts.join(" AND ");
@@ -254,6 +276,8 @@ export {
254
276
  MemorySearchDriver,
255
277
  SEARCH,
256
278
  Search,
279
+ SearchFilterFieldError,
280
+ SearchIndexNameError,
257
281
  TenantRequiredError,
258
282
  defineIndex,
259
283
  searchPlugin,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@basaltkit/search",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "description": "Full-text search for Basalt: tenant-scoped indexing and querying with a pluggable driver (in-memory for dev/test, Meilisearch for production) and automatic sync from domain events.",
5
5
  "license": "MIT",
6
6
  "type": "module",