@voltro/plugin-search 0.32.0 → 0.34.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,61 @@
1
+ import { Schema as e } from "effect";
2
+ import { defineAction as t } from "@voltro/protocol";
3
+ //#region src/errors.ts
4
+ var n = class extends e.TaggedError()("SearchIndexNotFound", { index: e.String }) {}, r = class extends e.TaggedError()("SearchFieldRejected", {
5
+ field: e.String,
6
+ where: e.Literal("filter", "facet", "highlight"),
7
+ reason: e.Literal("not-an-identifier", "not-queryable")
8
+ }) {}, i = e.Union(e.String, e.Number, e.Boolean), a = e.Struct({
9
+ field: e.String,
10
+ op: e.Literal("eq", "neq", "gt", "gte", "lt", "lte", "in", "nin"),
11
+ value: e.Union(i, e.Array(i))
12
+ }), o = e.Struct({
13
+ fields: e.optional(e.Array(e.String)),
14
+ preTag: e.optional(e.String),
15
+ postTag: e.optional(e.String)
16
+ }), s = e.Struct({
17
+ doc: e.Record({
18
+ key: e.String,
19
+ value: e.Unknown
20
+ }),
21
+ highlights: e.optional(e.Record({
22
+ key: e.String,
23
+ value: e.String
24
+ }))
25
+ }), c = t({
26
+ name: "search.query",
27
+ input: e.Struct({
28
+ index: e.String,
29
+ q: e.String,
30
+ limit: e.optional(e.Number),
31
+ offset: e.optional(e.Number),
32
+ filters: e.optional(e.Array(a)),
33
+ facets: e.optional(e.Array(e.String)),
34
+ highlight: e.optional(o),
35
+ fuzziness: e.optional(e.Union(e.Number, e.Literal("auto"))),
36
+ engineParams: e.optional(e.Record({
37
+ key: e.String,
38
+ value: e.Unknown
39
+ }))
40
+ }),
41
+ output: e.Struct({
42
+ hits: e.Array(s),
43
+ facets: e.Record({
44
+ key: e.String,
45
+ value: e.Record({
46
+ key: e.String,
47
+ value: e.Number
48
+ })
49
+ })
50
+ }),
51
+ error: e.Union(n, r)
52
+ }), l = [{
53
+ tag: "search.query",
54
+ kind: "action",
55
+ import: {
56
+ module: "@voltro/plugin-search/rpc",
57
+ name: "queryDescriptor"
58
+ }
59
+ }];
60
+ //#endregion
61
+ export { n as a, r as i, c as n, l as r, a as t };
package/dist/rpc.d.ts CHANGED
@@ -3,12 +3,19 @@ import { PluginRpcClientDescriptor } from '@voltro/protocol';
3
3
  import { Schema } from 'effect';
4
4
 
5
5
  export declare const queryDescriptor: ActionProcedureDescriptor<"search.query", Schema.Struct<{
6
+ /** One of the indexes the app declared. An unregistered name is refused
7
+ * (`SearchIndexNotFound`) — it has no spec, so it has no tenant field to
8
+ * scope by, and answering it would query an unscoped collection. */
6
9
  index: typeof Schema.String;
7
10
  q: typeof Schema.String;
8
11
  limit: Schema.optional<typeof Schema.Number>;
9
12
  offset: Schema.optional<typeof Schema.Number>;
10
13
  /** Clauses ANDed together — equality, range, negation, set membership. */
11
14
  filters: Schema.optional<Schema.Array$<Schema.Struct<{
15
+ /** A doc field path. Validated server-side against the index's declared
16
+ * `queryableFields` (or a plain-identifier pattern) before it is spliced
17
+ * into the engine's filter DSL — a mismatch is a typed
18
+ * `SearchFieldRejected`, not a query. */
12
19
  field: typeof Schema.String;
13
20
  op: Schema.Literal<["eq", "neq", "gt", "gte", "lt", "lte", "in", "nin"]>;
14
21
  value: Schema.Union<[Schema.Union<[typeof Schema.String, typeof Schema.Number, typeof Schema.Boolean]>, Schema.Array$<Schema.Union<[typeof Schema.String, typeof Schema.Number, typeof Schema.Boolean]>>]>;
@@ -23,7 +30,10 @@ export declare const queryDescriptor: ActionProcedureDescriptor<"search.query",
23
30
  }>>;
24
31
  /** Typo tolerance — a max edit distance (0 = exact) or `'auto'`. */
25
32
  fuzziness: Schema.optional<Schema.Union<[typeof Schema.Number, Schema.Literal<["auto"]>]>>;
26
- /** Escape hatch — forwarded verbatim to the underlying engine's search call. */
33
+ /** Escape hatch — the engine's presentation-only params (paging, ordering,
34
+ * typo tolerance, highlight shaping). Anything that could select a
35
+ * different document set is dropped server-side and logged; express a
36
+ * filter as a `filters[]` clause instead. */
27
37
  engineParams: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;
28
38
  }>, Schema.Struct<{
29
39
  hits: Schema.Array$<Schema.Struct<{
@@ -32,14 +42,60 @@ export declare const queryDescriptor: ActionProcedureDescriptor<"search.query",
32
42
  }>>;
33
43
  /** field → (value → count) for the requested facets. */
34
44
  facets: Schema.Record$<typeof Schema.String, Schema.Record$<typeof Schema.String, typeof Schema.Number>>;
35
- }>, typeof Schema.Never>;
45
+ }>, Schema.Union<[typeof SearchIndexNotFound, typeof SearchFieldRejected]>>;
46
+
47
+ /**
48
+ * A field name the caller supplied — in `filters[].field`, `facets[]` or
49
+ * `highlight.fields[]` — was refused before it could reach the engine.
50
+ *
51
+ * `reason: 'not-an-identifier'` — the name is not a plain field path
52
+ * (`^[A-Za-z_][A-Za-z0-9_.]*$`). Every vendor adapter interpolates the name
53
+ * into that engine's filter DSL, so anything else is a control-plane injection
54
+ * (a Typesense `filter_by` is ONE flat string with `||`, so a crafted name
55
+ * re-groups the boolean tree around the appended tenant clause).
56
+ *
57
+ * `reason: 'not-queryable'` — the name is well-formed but not in the index's
58
+ * declared `queryableFields` allowlist.
59
+ */
60
+ declare class SearchFieldRejected extends SearchFieldRejected_base {
61
+ }
62
+
63
+ declare const SearchFieldRejected_base: Schema.TaggedErrorClass<SearchFieldRejected, "SearchFieldRejected", {
64
+ readonly _tag: Schema.tag<"SearchFieldRejected">;
65
+ } & {
66
+ field: typeof Schema.String;
67
+ where: Schema.Literal<["filter", "facet", "highlight"]>;
68
+ reason: Schema.Literal<["not-an-identifier", "not-queryable"]>;
69
+ }>;
36
70
 
37
71
  export declare const SearchFilterSchema: Schema.Struct<{
72
+ /** A doc field path. Validated server-side against the index's declared
73
+ * `queryableFields` (or a plain-identifier pattern) before it is spliced
74
+ * into the engine's filter DSL — a mismatch is a typed
75
+ * `SearchFieldRejected`, not a query. */
38
76
  field: typeof Schema.String;
39
77
  op: Schema.Literal<["eq", "neq", "gt", "gte", "lt", "lte", "in", "nin"]>;
40
78
  value: Schema.Union<[Schema.Union<[typeof Schema.String, typeof Schema.Number, typeof Schema.Boolean]>, Schema.Array$<Schema.Union<[typeof Schema.String, typeof Schema.Number, typeof Schema.Boolean]>>]>;
41
79
  }>;
42
80
 
81
+ /**
82
+ * The queried index is not one the app declared in `searchPlugin({ indexes })`.
83
+ *
84
+ * This is a REFUSAL, not an empty result, and the distinction is the whole
85
+ * point: an unknown index has no `IndexSpec`, therefore no `tenantField`,
86
+ * therefore no tenant clause — so answering it would run an unfiltered query
87
+ * against whatever collection of that name exists on the (usually shared)
88
+ * engine. Fail instead of querying something we cannot scope.
89
+ */
90
+ declare class SearchIndexNotFound extends SearchIndexNotFound_base {
91
+ }
92
+
93
+ declare const SearchIndexNotFound_base: Schema.TaggedErrorClass<SearchIndexNotFound, "SearchIndexNotFound", {
94
+ readonly _tag: Schema.tag<"SearchIndexNotFound">;
95
+ } & {
96
+ index: typeof Schema.String;
97
+ }>;
98
+
43
99
  /** Declared for the codegen (`VoltroPlugin.rpcClientDescriptors`) so each tag is
44
100
  * emitted into the generated client group. Kept in lockstep with the exports. */
45
101
  export declare const searchRpcClientImports: ReadonlyArray<PluginRpcClientDescriptor>;
package/dist/rpc.js CHANGED
@@ -1,56 +1,2 @@
1
- import { Schema as e } from "effect";
2
- import { defineAction as t } from "@voltro/protocol";
3
- //#region src/rpc.ts
4
- var n = e.Union(e.String, e.Number, e.Boolean), r = e.Struct({
5
- field: e.String,
6
- op: e.Literal("eq", "neq", "gt", "gte", "lt", "lte", "in", "nin"),
7
- value: e.Union(n, e.Array(n))
8
- }), i = e.Struct({
9
- fields: e.optional(e.Array(e.String)),
10
- preTag: e.optional(e.String),
11
- postTag: e.optional(e.String)
12
- }), a = e.Struct({
13
- doc: e.Record({
14
- key: e.String,
15
- value: e.Unknown
16
- }),
17
- highlights: e.optional(e.Record({
18
- key: e.String,
19
- value: e.String
20
- }))
21
- }), o = t({
22
- name: "search.query",
23
- input: e.Struct({
24
- index: e.String,
25
- q: e.String,
26
- limit: e.optional(e.Number),
27
- offset: e.optional(e.Number),
28
- filters: e.optional(e.Array(r)),
29
- facets: e.optional(e.Array(e.String)),
30
- highlight: e.optional(i),
31
- fuzziness: e.optional(e.Union(e.Number, e.Literal("auto"))),
32
- engineParams: e.optional(e.Record({
33
- key: e.String,
34
- value: e.Unknown
35
- }))
36
- }),
37
- output: e.Struct({
38
- hits: e.Array(a),
39
- facets: e.Record({
40
- key: e.String,
41
- value: e.Record({
42
- key: e.String,
43
- value: e.Number
44
- })
45
- })
46
- })
47
- }), s = [{
48
- tag: "search.query",
49
- kind: "action",
50
- import: {
51
- module: "@voltro/plugin-search/rpc",
52
- name: "queryDescriptor"
53
- }
54
- }];
55
- //#endregion
56
- export { r as SearchFilterSchema, o as queryDescriptor, s as searchRpcClientImports };
1
+ import { n as e, r as t, t as n } from "./rpc-BQBNr0OU.js";
2
+ export { n as SearchFilterSchema, e as queryDescriptor, t as searchRpcClientImports };
package/dist/web.d.ts CHANGED
@@ -13,11 +13,14 @@ export declare interface SearchHitOut<Doc> {
13
13
  export declare interface SearchOptions {
14
14
  readonly limit?: number;
15
15
  readonly offset?: number;
16
- /** Clauses ANDed together — equality, range, negation, set. */
16
+ /** Clauses ANDed together — equality, range, negation, set. Each `field` is
17
+ * validated server-side (index allowlist, else a plain-identifier pattern);
18
+ * a mismatch is a typed `SearchFieldRejected`. */
17
19
  readonly filters?: ReadonlyArray<SearchFilterInput>;
18
- /** Facet-count fields (returned in `facets`). */
20
+ /** Facet-count fields (returned in `facets`). Validated like `filters[].field`. */
19
21
  readonly facets?: ReadonlyArray<string>;
20
- /** Matched-term highlighting (returned per-hit). */
22
+ /** Matched-term highlighting (returned per-hit). `fields` is validated like
23
+ * `filters[].field`. */
21
24
  readonly highlight?: {
22
25
  readonly fields?: ReadonlyArray<string>;
23
26
  readonly preTag?: string;
@@ -25,7 +28,10 @@ export declare interface SearchOptions {
25
28
  };
26
29
  /** Typo tolerance — a max edit distance (0 = exact) or `'auto'`. */
27
30
  readonly fuzziness?: number | 'auto';
28
- /** Escape hatch forwarded verbatim to the underlying engine. */
31
+ /** Escape hatch, narrowed to the engine's presentation-only params (paging,
32
+ * ordering, typo tolerance, highlight shaping). Keys that could select a
33
+ * different document set are dropped server-side — express a filter as a
34
+ * `filters[]` clause, which is ANDed with the tenant scope. */
29
35
  readonly engineParams?: Record<string, unknown>;
30
36
  }
31
37
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voltro/plugin-search",
3
- "version": "0.32.0",
3
+ "version": "0.34.0",
4
4
  "description": "Keep an external search index (Typesense / Meilisearch / Algolia) in sync with framework tables via the post-commit ChangeEvent tap, then query it — tenant-scoped, with a backfill path and a useSearch() hook.",
5
5
  "keywords": [
6
6
  "voltro",
@@ -32,7 +32,8 @@
32
32
  "types": "./dist/rpc.d.ts",
33
33
  "import": "./dist/rpc.js",
34
34
  "default": "./dist/rpc.js"
35
- }
35
+ },
36
+ "./package.json": "./package.json"
36
37
  },
37
38
  "main": "./dist/index.js",
38
39
  "module": "./dist/index.js",
@@ -42,9 +43,9 @@
42
43
  "node": ">=24.0.0"
43
44
  },
44
45
  "dependencies": {
45
- "@voltro/client": "0.32.0",
46
- "@voltro/database": "0.32.0",
47
- "@voltro/protocol": "0.32.0"
46
+ "@voltro/client": "0.34.0",
47
+ "@voltro/database": "0.34.0",
48
+ "@voltro/protocol": "0.34.0"
48
49
  },
49
50
  "optionalDependencies": {
50
51
  "algoliasearch": "^5.56.0",