@northstartech/cipher 0.0.0-stage → 1.0.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.
Files changed (41) hide show
  1. package/README.md +541 -2
  2. package/dist/client.d.ts +237 -0
  3. package/dist/client.js +555 -0
  4. package/dist/client.js.map +1 -0
  5. package/dist/crypto.d.ts +72 -0
  6. package/dist/crypto.js +244 -0
  7. package/dist/crypto.js.map +1 -0
  8. package/dist/encoding.d.ts +17 -0
  9. package/dist/encoding.js +79 -0
  10. package/dist/encoding.js.map +1 -0
  11. package/dist/errors.d.ts +14 -0
  12. package/dist/errors.js +22 -0
  13. package/dist/errors.js.map +1 -0
  14. package/dist/index.d.ts +8 -0
  15. package/dist/index.js +6 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/integrations/drizzle.d.ts +96 -0
  18. package/dist/integrations/drizzle.js +285 -0
  19. package/dist/integrations/drizzle.js.map +1 -0
  20. package/dist/integrations/mongoose.d.ts +72 -0
  21. package/dist/integrations/mongoose.js +582 -0
  22. package/dist/integrations/mongoose.js.map +1 -0
  23. package/dist/integrations/prisma.d.ts +65 -0
  24. package/dist/integrations/prisma.js +588 -0
  25. package/dist/integrations/prisma.js.map +1 -0
  26. package/dist/integrations/sequelize.d.ts +62 -0
  27. package/dist/integrations/sequelize.js +677 -0
  28. package/dist/integrations/sequelize.js.map +1 -0
  29. package/dist/integrations/shared.d.ts +268 -0
  30. package/dist/integrations/shared.js +513 -0
  31. package/dist/integrations/shared.js.map +1 -0
  32. package/dist/integrations/typeorm.d.ts +109 -0
  33. package/dist/integrations/typeorm.js +457 -0
  34. package/dist/integrations/typeorm.js.map +1 -0
  35. package/dist/object.d.ts +53 -0
  36. package/dist/object.js +226 -0
  37. package/dist/object.js.map +1 -0
  38. package/dist/search.d.ts +250 -0
  39. package/dist/search.js +258 -0
  40. package/dist/search.js.map +1 -0
  41. package/package.json +101 -4
package/dist/object.js ADDED
@@ -0,0 +1,226 @@
1
+ /**
2
+ * Object encryption, format NSOB v1 (protocol §13): AES-256-GCM in blocks under
3
+ * a content key derived from a random per-object key and the object's context.
4
+ * Pure functions; `NorthstarCipher.encryptObject()` / `decryptObject()` generate
5
+ * the object key and protect it with Cipher.
6
+ *
7
+ * ```
8
+ * header = "NSOB" ‖ 0x01 ‖ log2(B) ‖ 0^10 ‖ object_id (16)
9
+ * content_key = HKDF-SHA-256(object_key, salt = object_id, info = "northstar-cipher/v1/object" ‖ 0x00 ‖ ctx)
10
+ * block i = AES-256-GCM(content_key, 0^4 ‖ u64(i), aad = header ‖ u64(i) ‖ u8(last))
11
+ * ```
12
+ */
13
+ import { wireContext } from './client.js';
14
+ import { subtle } from './crypto.js';
15
+ import { buf, concat, lp, u32, utf8 } from './encoding.js';
16
+ import { CipherError } from './errors.js';
17
+ const MAGIC = [0x4e, 0x53, 0x4f, 0x42]; // "NSOB"
18
+ const VERSION = 1;
19
+ const HEADER_BYTES = 32;
20
+ const TAG_BYTES = 16;
21
+ const KEY_BYTES = 32;
22
+ const ID_BYTES = 16;
23
+ const MIN_BLOCK_LOG2 = 12;
24
+ const MAX_BLOCK_LOG2 = 24;
25
+ const DEFAULT_BLOCK_LOG2 = 16;
26
+ const INFO = 'northstar-cipher/v1/object';
27
+ const CONTEXT_KEY = /^[a-z][a-z0-9_]{0,63}$/;
28
+ const LONE_SURROGATE = /\p{Cs}/u;
29
+ /**
30
+ * Result of `NorthstarCipher.encryptObject()`. Store `wrappedKey` with the record
31
+ * (it is an ordinary `nsc:v1:` ciphertext) and `object` in object storage.
32
+ * `toString()` and `util.inspect` never show the wrapped key or the object bytes.
33
+ */
34
+ export class EncryptedObject {
35
+ /** The object key encrypted by Cipher for the object's context (`nsc:v1:…`). */
36
+ wrappedKey;
37
+ /** The NSOB v1 object. */
38
+ object;
39
+ keyId;
40
+ keyVersion;
41
+ constructor(wrappedKey, object, keyId, keyVersion) {
42
+ this.wrappedKey = wrappedKey;
43
+ this.object = object;
44
+ this.keyId = keyId;
45
+ this.keyVersion = keyVersion;
46
+ }
47
+ toString() {
48
+ return `EncryptedObject { wrappedKey: [ciphertext], object: ${this.object.length} bytes, keyId: ${this.keyId}, keyVersion: ${this.keyVersion} }`;
49
+ }
50
+ }
51
+ Object.defineProperty(EncryptedObject.prototype, Symbol.for('nodejs.util.inspect.custom'), {
52
+ value(_depth, options, inspect) {
53
+ const shown = { wrappedKey: '[ciphertext]', object: `${this.object.length} bytes`, keyId: this.keyId, keyVersion: this.keyVersion };
54
+ return inspect ? `EncryptedObject ${inspect(shown, options)}` : this.toString();
55
+ },
56
+ });
57
+ /**
58
+ * Seal `plaintext` under `objectKey` (32 random bytes, never reused) for `context`.
59
+ * `objectId` is 16 random bytes; `blockLog2` is 12 … 24 (default 16, 64 KiB blocks).
60
+ */
61
+ export async function sealObject(objectKey, objectId, context, plaintext, blockLog2 = DEFAULT_BLOCK_LOG2) {
62
+ checkKey(objectKey);
63
+ if (!(objectId instanceof Uint8Array) || objectId.length !== ID_BYTES)
64
+ throw new CipherError('INVALID_REQUEST', 'object id must be 16 bytes');
65
+ checkBlockLog2(blockLog2);
66
+ if (typeof plaintext !== 'string' && !(plaintext instanceof Uint8Array))
67
+ throw new CipherError('INVALID_REQUEST', 'plaintext must be a string or a Uint8Array');
68
+ const ctx = canonicalContext(context);
69
+ const bytes = typeof plaintext === 'string' ? utf8(plaintext) : plaintext;
70
+ // WebCrypto takes ArrayBuffer-backed views; anything else is copied, and every copy is wiped.
71
+ const pt = (bytes.buffer instanceof ArrayBuffer ? bytes : buf(bytes));
72
+ try {
73
+ const block = 2 ** blockLog2;
74
+ const n = Math.max(1, Math.ceil(pt.length / block));
75
+ if (n >= 2 ** 32)
76
+ throw new CipherError('INVALID_REQUEST', 'object is too large for this block size');
77
+ const header = new Uint8Array(HEADER_BYTES);
78
+ header.set(MAGIC);
79
+ header[4] = VERSION;
80
+ header[5] = blockLog2;
81
+ header.set(objectId, 16);
82
+ const key = await deriveContentKey(objectKey, objectId, ctx, 'encrypt');
83
+ const out = new Uint8Array(HEADER_BYTES + pt.length + TAG_BYTES * n);
84
+ out.set(header);
85
+ for (let i = 0; i < n; i++) {
86
+ const sealed = new Uint8Array(await subtle().encrypt(blockParams(header, i, i === n - 1), key, pt.subarray(i * block, (i + 1) * block)));
87
+ out.set(sealed, HEADER_BYTES + i * (block + TAG_BYTES));
88
+ }
89
+ return out;
90
+ }
91
+ finally {
92
+ if (pt !== plaintext)
93
+ pt.fill(0);
94
+ }
95
+ }
96
+ /**
97
+ * Open an NSOB v1 object. Every block is authenticated before the plaintext is
98
+ * returned. Any failure is `INVALID_CIPHERTEXT` with one message.
99
+ */
100
+ export async function openObject(objectKey, context, object) {
101
+ checkKey(objectKey);
102
+ const ctx = canonicalContext(context);
103
+ if (!(object instanceof Uint8Array) ||
104
+ object.length < HEADER_BYTES ||
105
+ MAGIC.some((b, i) => object[i] !== b) ||
106
+ object[4] !== VERSION ||
107
+ object[5] < MIN_BLOCK_LOG2 ||
108
+ object[5] > MAX_BLOCK_LOG2 ||
109
+ object.subarray(6, 16).some((b) => b !== 0)) {
110
+ throw invalidObject();
111
+ }
112
+ const header = object.slice(0, HEADER_BYTES);
113
+ const stride = 2 ** header[5] + TAG_BYTES;
114
+ const body = object.length - HEADER_BYTES;
115
+ if (body < TAG_BYTES)
116
+ throw invalidObject();
117
+ const n = Math.ceil(body / stride);
118
+ const last = body - (n - 1) * stride;
119
+ if (last < TAG_BYTES || last > stride)
120
+ throw invalidObject();
121
+ const src = (object.buffer instanceof ArrayBuffer ? object : buf(object)); // WebCrypto takes ArrayBuffer-backed views
122
+ const key = await deriveContentKey(objectKey, header.subarray(16), ctx, 'decrypt');
123
+ const out = new Uint8Array(body - TAG_BYTES * n);
124
+ try {
125
+ for (let i = 0; i < n; i++) {
126
+ const start = HEADER_BYTES + i * stride;
127
+ let pt;
128
+ try {
129
+ pt = new Uint8Array(await subtle().decrypt(blockParams(header, i, i === n - 1), key, src.subarray(start, start + (i === n - 1 ? last : stride))));
130
+ }
131
+ catch {
132
+ throw invalidObject();
133
+ }
134
+ out.set(pt, i * (stride - TAG_BYTES));
135
+ pt.fill(0);
136
+ }
137
+ return out;
138
+ }
139
+ catch (e) {
140
+ out.fill(0);
141
+ throw e;
142
+ }
143
+ }
144
+ /**
145
+ * The content key (32 bytes): HKDF-SHA-256(object_key, salt = object_id,
146
+ * info = "northstar-cipher/v1/object" ‖ 0x00 ‖ canonical context). For the vectors.
147
+ */
148
+ export async function objectContentKey(objectKey, objectId, context) {
149
+ checkKey(objectKey);
150
+ if (!(objectId instanceof Uint8Array) || objectId.length !== ID_BYTES)
151
+ throw new CipherError('INVALID_REQUEST', 'object id must be 16 bytes');
152
+ const { base, params } = await hkdf(objectKey, objectId, canonicalContext(context));
153
+ return new Uint8Array(await subtle().deriveBits(params, base, 256));
154
+ }
155
+ /**
156
+ * Canonical context bytes (protocol §3.1): u32(count) ‖ for each (k, v) sorted by k: lp(k) ‖ lp(v).
157
+ * camelCase keys become snake_case and `undefined`/`null` values are dropped, as for every request.
158
+ */
159
+ export function canonicalContext(context) {
160
+ const ctx = wireContext(context);
161
+ const keys = Object.keys(ctx);
162
+ if (keys.length < 1 || keys.length > 16)
163
+ throw new CipherError('INVALID_CONTEXT', 'context must have 1 to 16 entries');
164
+ const entries = keys.map((k) => {
165
+ const v = ctx[k];
166
+ const value = utf8(v);
167
+ if (!CONTEXT_KEY.test(k) || LONE_SURROGATE.test(v) || value.length < 1 || value.length > 512) {
168
+ throw new CipherError('INVALID_CONTEXT', 'context keys must be snake_case and values 1–512 bytes of UTF-8');
169
+ }
170
+ return { key: utf8(k), value };
171
+ });
172
+ entries.sort((a, b) => compareBytes(a.key, b.key));
173
+ return concat(u32(entries.length), ...entries.flatMap((e) => [lp(e.key), lp(e.value)]));
174
+ }
175
+ /** Size of the object for a plaintext of `length` bytes. */
176
+ export function objectLength(length, blockLog2 = DEFAULT_BLOCK_LOG2) {
177
+ checkBlockLog2(blockLog2);
178
+ if (!Number.isSafeInteger(length) || length < 0)
179
+ throw new CipherError('INVALID_REQUEST', 'length must be a non-negative integer');
180
+ return HEADER_BYTES + length + TAG_BYTES * Math.max(1, Math.ceil(length / 2 ** blockLog2));
181
+ }
182
+ async function hkdf(objectKey, objectId, ctx) {
183
+ const raw = buf(objectKey);
184
+ try {
185
+ const base = await subtle().importKey('raw', raw, 'HKDF', false, ['deriveBits', 'deriveKey']);
186
+ return { base, params: { name: 'HKDF', hash: 'SHA-256', salt: buf(objectId), info: buf(concat(utf8(INFO), new Uint8Array([0]), ctx)) } };
187
+ }
188
+ finally {
189
+ raw.fill(0);
190
+ }
191
+ }
192
+ /** The content key as a non-extractable AES-256-GCM key. */
193
+ async function deriveContentKey(objectKey, objectId, ctx, usage) {
194
+ const { base, params } = await hkdf(objectKey, objectId, ctx);
195
+ return subtle().deriveKey(params, base, { name: 'AES-GCM', length: 256 }, false, [usage]);
196
+ }
197
+ /** nonce = 0^4 ‖ u64(i), aad = header ‖ u64(i) ‖ u8(last). */
198
+ function blockParams(header, i, last) {
199
+ const iv = new Uint8Array(12);
200
+ new DataView(iv.buffer).setBigUint64(4, BigInt(i));
201
+ const aad = new Uint8Array(HEADER_BYTES + 9);
202
+ aad.set(header);
203
+ new DataView(aad.buffer).setBigUint64(HEADER_BYTES, BigInt(i));
204
+ aad[HEADER_BYTES + 8] = last ? 1 : 0;
205
+ return { name: 'AES-GCM', iv, additionalData: aad, tagLength: 128 };
206
+ }
207
+ function checkKey(objectKey) {
208
+ if (!(objectKey instanceof Uint8Array) || objectKey.length !== KEY_BYTES)
209
+ throw new CipherError('INVALID_REQUEST', 'object key must be 32 bytes');
210
+ }
211
+ function checkBlockLog2(blockLog2) {
212
+ if (!Number.isInteger(blockLog2) || blockLog2 < MIN_BLOCK_LOG2 || blockLog2 > MAX_BLOCK_LOG2) {
213
+ throw new CipherError('INVALID_REQUEST', 'block size must be 2^12 … 2^24 bytes');
214
+ }
215
+ }
216
+ function compareBytes(a, b) {
217
+ for (let i = 0; i < Math.min(a.length, b.length); i++)
218
+ if (a[i] !== b[i])
219
+ return a[i] - b[i];
220
+ return a.length - b.length;
221
+ }
222
+ /** The single error for every failure to open an object (also a wrapped key that is not an object key). */
223
+ export function invalidObject() {
224
+ return new CipherError('INVALID_CIPHERTEXT', 'object could not be decrypted (corrupted, truncated, or wrong key or context)');
225
+ }
226
+ //# sourceMappingURL=object.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"object.js","sourceRoot":"","sources":["../src/object.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,250 @@
1
+ /**
2
+ * Northstar Search: encrypted full-text search that runs inside the attested
3
+ * enclave. Index files are encrypted at rest under a key only the enclave can
4
+ * unwrap; documents and queries travel sealed to the enclave session.
5
+ *
6
+ * ```ts
7
+ * const customers = cipher.index('customers')
8
+ * await customers.ensure({ language: 'spanish', fields: { name: 'text', email: 'keyword', city: 'keyword', balance: 'number' } })
9
+ * await customers.upsert({ 42: { name: 'Ana García', city: 'CDMX', balance: 1500 } })
10
+ * const r = await customers.search({ q: 'ana garsia', filter: { city: 'cdmx' } })
11
+ * // r.hits = [{ id: '42', score: 3.1 }], r.total = 1
12
+ * ```
13
+ *
14
+ * Values may be ciphertexts: a string starting with `nsc:v1:` is decrypted in
15
+ * the enclave with context (index or `collection`, field, id), or pass
16
+ * `SearchIndex.ciphertext(ct, context)` to give the context explicitly.
17
+ */
18
+ import { type CipherContext } from './client.js';
19
+ import { CipherError } from './errors.js';
20
+ export type SearchFieldType = 'text' | 'keyword' | 'integer' | 'number' | 'date' | 'bool';
21
+ export type SearchLanguage = 'english' | 'spanish' | 'portuguese' | 'french' | 'german' | 'italian' | 'dutch' | 'none';
22
+ export interface SearchField {
23
+ name: string;
24
+ type: SearchFieldType;
25
+ /** Relevance weight of a text field, in (0, 100]. */
26
+ boost?: number;
27
+ /** Stemming language of a text field (default: the schema's). */
28
+ language?: SearchLanguage;
29
+ }
30
+ /** Schema in wire form. */
31
+ export interface SearchSchema {
32
+ language?: SearchLanguage;
33
+ fields: SearchField[];
34
+ }
35
+ /** Schema accepted by `ensure()`: fields as a list, or as a map `{ name: 'text', city: { type: 'keyword' } }`. */
36
+ export interface SearchSchemaInput {
37
+ language?: SearchLanguage;
38
+ fields: SearchField[] | Record<string, SearchFieldType | Omit<SearchField, 'name'>>;
39
+ }
40
+ /** A field value the enclave decrypts before indexing (see {@link SearchIndex.ciphertext}). */
41
+ export interface SearchCiphertext {
42
+ ciphertext: string;
43
+ context: Record<string, string>;
44
+ }
45
+ export type SearchScalar = string | number | boolean | Date | null;
46
+ /** A field value: a scalar, a list of scalars, or a ciphertext. Dates as `Date`, RFC 3339 / `YYYY-MM-DD` strings or epoch ms. */
47
+ export type SearchValue = SearchScalar | SearchScalar[] | SearchCiphertext;
48
+ export type SearchFields = Record<string, SearchValue | undefined>;
49
+ export interface SearchDocument {
50
+ id: string | number;
51
+ fields: SearchFields;
52
+ }
53
+ export interface SearchIndexVersion {
54
+ version: number;
55
+ state: 'active' | 'building';
56
+ /** Searchable documents (as of the last commit). */
57
+ documents: number;
58
+ /** Encrypted bytes on disk. */
59
+ diskBytes: number;
60
+ createdAt: Date;
61
+ schema: SearchSchema;
62
+ }
63
+ export interface SearchIndexInfo {
64
+ index: string;
65
+ /** Key protecting the index. */
66
+ keyId: string;
67
+ createdAt: Date;
68
+ /** Last write committed. */
69
+ updatedAt: Date;
70
+ /** Live version (queries). */
71
+ active: SearchIndexVersion;
72
+ /** Version being built (receives writes too). */
73
+ building?: SearchIndexVersion;
74
+ /** `ensure()`: `created` | `unchanged` | `rebuilding`; `activate()`: `activated`. */
75
+ outcome?: 'created' | 'unchanged' | 'rebuilding' | 'activated';
76
+ }
77
+ export type SearchWriteMode = 'replace' | 'merge';
78
+ /** A raw write (`write()`): any of upserts, deletions, `wait`, `optimize`. */
79
+ export interface SearchWriteRequest {
80
+ /** Documents to add or update (≤ 5 000 documents and deletions per request). */
81
+ upsert?: SearchDocument[];
82
+ delete?: (string | number)[];
83
+ /** `replace` (default) or `merge` (only the given fields change; `null` removes one). */
84
+ mode?: SearchWriteMode;
85
+ /** Context collection of `nsc:v1:` shorthand values (default: the index name). */
86
+ collection?: string;
87
+ /** Return once everything written is searchable. */
88
+ wait?: boolean;
89
+ /** Merge index segments after this write (end of a bulk load). */
90
+ optimize?: boolean;
91
+ }
92
+ export interface SearchWriteResult {
93
+ upserted: number;
94
+ deleted: number;
95
+ /** Ciphertext values decrypted inside the enclave. */
96
+ decrypted: number;
97
+ /** Documents that were not indexed (the others were). */
98
+ errors: {
99
+ id: string;
100
+ error: CipherError;
101
+ }[];
102
+ /** Already searchable (`wait`); otherwise within about a second. */
103
+ committed: boolean;
104
+ }
105
+ export type SearchFilterValue = string | number | boolean | Date;
106
+ /** A value, any of a list, a range, presence, or exclusion. */
107
+ export type SearchFilter = SearchFilterValue | SearchFilterValue[] | {
108
+ gt?: SearchFilterValue;
109
+ gte?: SearchFilterValue;
110
+ lt?: SearchFilterValue;
111
+ lte?: SearchFilterValue;
112
+ } | {
113
+ exists: boolean;
114
+ } | {
115
+ not: SearchFilterValue | SearchFilterValue[];
116
+ };
117
+ export interface SearchQuery {
118
+ /** Free text (`"quoted phrases"` supported). Empty: every document. */
119
+ q?: string;
120
+ /** Fields to search (default: every text and keyword field). */
121
+ fields?: string[];
122
+ /** Typo tolerance (default `auto`). */
123
+ fuzzy?: 'auto' | 'off' | 'one' | 'two';
124
+ /** Treat the last word as a prefix (search-as-you-type). Default true. */
125
+ prefix?: boolean;
126
+ /** Every word must match (`all`, default) or any. */
127
+ mode?: 'all' | 'any';
128
+ filter?: Record<string, SearchFilter>;
129
+ /** Default: relevance. `_score` sorts by relevance. */
130
+ sort?: {
131
+ field: string;
132
+ desc?: boolean;
133
+ }[];
134
+ /** 0–1 000 (0: counts and facets only). Default 20. */
135
+ limit?: number;
136
+ /** ≤ 10 000. */
137
+ offset?: number;
138
+ /** Keyword fields to count values for. */
139
+ facets?: string[];
140
+ /** Buckets per facet (≤ 100, default 10). */
141
+ facetLimit?: number;
142
+ /** Fields to return with each hit (`['*']`: every field). Returning values is a decryption. */
143
+ select?: string[];
144
+ /** Text/keyword fields to return with matches wrapped in `<mark>` (HTML-escaped). */
145
+ highlight?: string[];
146
+ /** Why plaintext is returned (`select` / `highlight`), recorded in the audit log and matched by policy. */
147
+ purpose?: string;
148
+ }
149
+ export interface SearchHit {
150
+ id: string;
151
+ /** Relevance (relevance order only). */
152
+ score?: number;
153
+ /** Selected fields. */
154
+ fields?: Record<string, unknown>;
155
+ /** Highlighted fragments: field → HTML with matches in `<mark>`. */
156
+ highlights?: Record<string, string>;
157
+ }
158
+ export interface SearchResult {
159
+ hits: SearchHit[];
160
+ /** Total matching documents. */
161
+ total: number;
162
+ /** Facet counts: field → buckets. */
163
+ facets: Record<string, {
164
+ value: string;
165
+ count: number;
166
+ }[]>;
167
+ /** Engine time. */
168
+ tookMs: number;
169
+ }
170
+ /** @internal How a {@link SearchIndex} reaches the enclave (the client's sealed `call`). */
171
+ export interface SearchTransport {
172
+ call<T>(path: string, payload: unknown): Promise<T>;
173
+ readonly keyId: string;
174
+ }
175
+ interface WireVersion {
176
+ version: number;
177
+ state: 'active' | 'building';
178
+ documents: number;
179
+ disk_bytes: number;
180
+ created_at: number;
181
+ schema: SearchSchema;
182
+ }
183
+ interface WireInfo {
184
+ index: string;
185
+ key_id: string;
186
+ created_at: number;
187
+ updated_at: number;
188
+ active: WireVersion;
189
+ building?: WireVersion;
190
+ outcome?: SearchIndexInfo['outcome'];
191
+ }
192
+ /** @internal */
193
+ export declare function indexInfo(r: WireInfo): SearchIndexInfo;
194
+ /**
195
+ * A Northstar Search index. Get one with `cipher.index(name)`; the handle is
196
+ * cheap and does no I/O until a method is called.
197
+ */
198
+ export declare class SearchIndex {
199
+ #private;
200
+ readonly name: string;
201
+ /** @internal Use `cipher.index(name)`. */
202
+ constructor(transport: SearchTransport, name: string);
203
+ /**
204
+ * A field value the enclave decrypts before indexing, with an explicit context
205
+ * (normalized like `encrypt`'s: `recordId` → `record_id`, values as strings).
206
+ */
207
+ static ciphertext(ciphertext: string, context: CipherContext): SearchCiphertext;
208
+ /** Normalize a schema: the map shorthand `{ fields: { name: 'text' } }` becomes the wire list form. */
209
+ static schema(input: SearchSchemaInput): SearchSchema;
210
+ /**
211
+ * Create the index, or confirm it exists with this schema (`outcome`:
212
+ * `created` | `unchanged`). A different schema fails with `CONFLICT` unless
213
+ * `rebuild` is set: then a fresh version is built next to the live one
214
+ * (writes reach both, queries keep using the live one until {@link activate}).
215
+ * `key`: key alias or id protecting the index (default: the client's key).
216
+ */
217
+ ensure(schema: SearchSchemaInput, opts?: {
218
+ key?: string;
219
+ rebuild?: boolean;
220
+ }): Promise<SearchIndexInfo>;
221
+ describe(): Promise<SearchIndexInfo>;
222
+ /** Make the version being built (or `version`) live; the previous one is deleted. */
223
+ activate(version?: number): Promise<SearchIndexInfo>;
224
+ /** Delete the index and its files. `false` when there was none. */
225
+ drop(): Promise<boolean>;
226
+ /**
227
+ * Add or replace documents: `[{ id, fields }]` or `{ [id]: fields }`. Sent in
228
+ * chunks of 1 000; `wait` applies to the last chunk (so to all of them).
229
+ * Rejected documents are listed in `errors`; the others are indexed.
230
+ */
231
+ upsert(documents: SearchDocument[] | Record<string, SearchFields>, opts?: {
232
+ mode?: SearchWriteMode;
233
+ wait?: boolean;
234
+ collection?: string;
235
+ }): Promise<SearchWriteResult>;
236
+ /** Remove documents by id (absent ids are ignored). */
237
+ delete(ids: (string | number)[], opts?: {
238
+ wait?: boolean;
239
+ }): Promise<SearchWriteResult>;
240
+ /** Wait until everything written so far is searchable; `optimize` also merges segments (after bulk loads). */
241
+ flush(opts?: {
242
+ optimize?: boolean;
243
+ }): Promise<SearchWriteResult>;
244
+ /** Raw write (≤ 5 000 documents and deletions): `upsert`, `delete`, `mode`, `collection`, `wait`, `optimize`. */
245
+ write(request: SearchWriteRequest): Promise<SearchWriteResult>;
246
+ private send;
247
+ /** Search. Without `q`, every document matches (filters, facets and sorting still apply). */
248
+ search(query?: SearchQuery): Promise<SearchResult>;
249
+ }
250
+ export {};