git.iko.soy/rowtype-yoga/purescript-yoga-elasticsearch
on main help sign in
Initial: yoga-elasticsearch
commitfd68f4e9c80be87ad9dda760a546c5b0f656023d authorMark <mark.eibes@gmail.com> dateWed 4 Feb 2026, 21:57 (8 months ago)
6 files changed+914
.github/workflows/ci.yml+9 .gitignore+3 package.json+12 spago.yaml+17 src/Yoga/Elasticsearch/Elasticsearch.js+348 src/Yoga/Elasticsearch/Elasticsearch.purs+525
.github/workflows/ci.ymlnew file+9
@@ -0,0 +1,9 @@
1+name: CI
2+on: [push, pull_request]
3+jobs:
4+ build:
5+ runs-on: ubuntu-latest
6+ steps:
7+ - uses: actions/checkout@v4
8+ - uses: rowtype-yoga/purescript-yoga-actions/.github/actions/setup@main
9+ - run: spago build
.gitignorenew file+3
@@ -0,0 +1,3 @@
1+node_modules/
2+.spago/
3+output*/
package.jsonnew file+12
@@ -0,0 +1,12 @@
1+{
2+ "name": "purescript-yoga-elasticsearch",
3+ "version": "0.1.0",
4+ "description": "PureScript FFI bindings for Elasticsearch",
5+ "license": "MIT",
6+ "repository": {
7+ "type": "git",
8+ "url": "https://github.com/rowtype-yoga/purescript-yoga-backend-stack.git",
9+ "directory": "packages/yoga-elasticsearch"
10+ },
11+ "keywords": ["purescript", "ffi", "bindings", "yoga"]
12+}
spago.yamlnew file+17
@@ -0,0 +1,17 @@
1+workspace:
2+ package_set:
3+ registry: 51.1.0
4+
5+package:
6+ name: yoga-elasticsearch
7+ publish:
8+ version: 0.1.0
9+ license: MIT
10+ location:
11+ githubOwner: rowtype-yoga
12+ githubRepo: purescript-yoga-elasticsearch
13+ dependencies:
14+ - aff: ">=7.0.0 <8.0.0"
15+ - effect: ">=4.0.0 <5.0.0"
16+ - prelude: ">=6.0.0 <7.0.0"
17+ - foreign-object: ">=4.0.0 <5.0.0"
src/Yoga/Elasticsearch/Elasticsearch.jsnew file+348
@@ -0,0 +1,348 @@
1+import { Client } from '@elastic/elasticsearch';
2+
3+// Create Elasticsearch client
4+export const createClientImpl = (config) => {
5+ const clientConfig = {};
6+
7+ // Handle node/nodes
8+ if (config.node) {
9+ clientConfig.node = config.node;
10+ }
11+ if (config.nodes) {
12+ clientConfig.nodes = config.nodes;
13+ }
14+
15+ // Handle cloud configuration
16+ if (config.cloud) {
17+ clientConfig.cloud = config.cloud;
18+ }
19+
20+ // Handle authentication
21+ if (config.auth && config.auth.apiKey) {
22+ clientConfig.auth = {
23+ apiKey: {
24+ id: config.auth.apiKey.id,
25+ api_key: config.auth.apiKey.api_key
26+ }
27+ };
28+ } else if (config.basicAuth) {
29+ clientConfig.auth = {
30+ username: config.basicAuth.username,
31+ password: config.basicAuth.password
32+ };
33+ } else if (config.bearerToken) {
34+ clientConfig.auth = {
35+ bearer: config.bearerToken
36+ };
37+ }
38+
39+ // Handle timeouts and retries
40+ if (config.requestTimeout) {
41+ clientConfig.requestTimeout = config.requestTimeout;
42+ }
43+ if (config.maxRetries) {
44+ clientConfig.maxRetries = config.maxRetries;
45+ }
46+
47+ // Handle compression
48+ if (config.compression !== undefined) {
49+ clientConfig.compression = config.compression;
50+ }
51+
52+ // Handle SSL
53+ if (config.ssl) {
54+ clientConfig.ssl = config.ssl;
55+ }
56+
57+ // Pass through any other options
58+ Object.keys(config).forEach(key => {
59+ if (!clientConfig[key] && key !== 'basicAuth' && key !== 'bearerToken') {
60+ clientConfig[key] = config[key];
61+ }
62+ });
63+
64+ return new Client(clientConfig);
65+};
66+
67+// Info operation
68+export const infoImpl = async (client) => {
69+ const response = await client.info();
70+ return {
71+ name: response.name,
72+ cluster_name: response.cluster_name,
73+ cluster_uuid: response.cluster_uuid,
74+ version: {
75+ number: response.version.number,
76+ build_flavor: response.version.build_flavor,
77+ build_type: response.version.build_type,
78+ build_hash: response.version.build_hash,
79+ build_date: response.version.build_date,
80+ build_snapshot: response.version.build_snapshot,
81+ lucene_version: response.version.lucene_version,
82+ minimum_wire_compatibility_version: response.version.minimum_wire_compatibility_version,
83+ minimum_index_compatibility_version: response.version.minimum_index_compatibility_version
84+ },
85+ tagline: response.tagline
86+ };
87+};
88+
89+// Ping operation
90+export const pingImpl = async (client) => {
91+ try {
92+ await client.ping();
93+ return true;
94+ } catch (err) {
95+ return false;
96+ }
97+};
98+
99+// Index operations
100+
101+// Create index
102+export const createIndexImpl = async (client, index, options) => {
103+ const response = await client.indices.create({
104+ index,
105+ ...options
106+ });
107+
108+ return {
109+ acknowledged: response.acknowledged,
110+ shards_acknowledged: response.shards_acknowledged,
111+ index: response.index
112+ };
113+};
114+
115+// Delete index
116+export const deleteIndexImpl = async (client, index) => {
117+ const response = await client.indices.delete({ index });
118+ return {
119+ acknowledged: response.acknowledged
120+ };
121+};
122+
123+// Check if index exists
124+export const indexExistsImpl = async (client, index) => {
125+ try {
126+ const response = await client.indices.exists({ index });
127+ return response;
128+ } catch (err) {
129+ return false;
130+ }
131+};
132+
133+// Document operations
134+
135+// Index a document
136+export const indexDocumentImpl = async (client, index, document, options) => {
137+ const response = await client.index({
138+ index,
139+ document,
140+ ...options
141+ });
142+
143+ return {
144+ _index: response._index,
145+ _id: response._id,
146+ _version: response._version,
147+ result: response.result,
148+ _shards: {
149+ total: response._shards.total,
150+ successful: response._shards.successful,
151+ failed: response._shards.failed
152+ }
153+ };
154+};
155+
156+// Get document
157+export const getDocumentImpl = async (client, index, id, options) => {
158+ try {
159+ const response = await client.get({
160+ index,
161+ id,
162+ ...options
163+ });
164+
165+ return {
166+ _index: response._index,
167+ _id: response._id,
168+ _version: response._version,
169+ _seq_no: response._seq_no,
170+ _primary_term: response._primary_term,
171+ found: response.found,
172+ _source: response._source || null
173+ };
174+ } catch (err) {
175+ // Document not found
176+ if (err.meta && err.meta.statusCode === 404) {
177+ return {
178+ _index: index,
179+ _id: id,
180+ _version: 0,
181+ _seq_no: 0,
182+ _primary_term: 0,
183+ found: false,
184+ _source: null
185+ };
186+ }
187+ throw err;
188+ }
189+};
190+
191+// Update document
192+export const updateDocumentImpl = async (client, index, id, options) => {
193+ const response = await client.update({
194+ index,
195+ id,
196+ ...options
197+ });
198+
199+ return {
200+ _index: response._index,
201+ _id: response._id,
202+ _version: response._version,
203+ result: response.result
204+ };
205+};
206+
207+// Delete document
208+export const deleteDocumentImpl = async (client, index, id, options) => {
209+ const response = await client.delete({
210+ index,
211+ id,
212+ ...options
213+ });
214+
215+ return {
216+ _index: response._index,
217+ _id: response._id,
218+ _version: response._version,
219+ result: response.result
220+ };
221+};
222+
223+// Search operations
224+
225+export const searchImpl = async (client, index, options) => {
226+ const searchParams = { ...options };
227+
228+ // Only add index if provided (null means search all indices)
229+ if (index !== null) {
230+ searchParams.index = index;
231+ }
232+
233+ const response = await client.search(searchParams);
234+
235+ return {
236+ took: response.took,
237+ timed_out: response.timed_out,
238+ _shards: {
239+ total: response._shards.total,
240+ successful: response._shards.successful,
241+ skipped: response._shards.skipped,
242+ failed: response._shards.failed
243+ },
244+ hits: {
245+ total: {
246+ value: response.hits.total.value,
247+ relation: response.hits.total.relation
248+ },
249+ max_score: response.hits.max_score !== null ? response.hits.max_score : null,
250+ hits: response.hits.hits.map(hit => ({
251+ _index: hit._index,
252+ _id: hit._id,
253+ _score: hit._score !== null ? hit._score : null,
254+ _source: hit._source
255+ }))
256+ },
257+ _scroll_id: response._scroll_id || null
258+ };
259+};
260+
261+// Scroll API
262+export const scrollImpl = async (client, scrollId, options) => {
263+ const response = await client.scroll({
264+ scroll_id: scrollId,
265+ ...options
266+ });
267+
268+ return {
269+ took: response.took,
270+ timed_out: response.timed_out,
271+ _shards: {
272+ total: response._shards.total,
273+ successful: response._shards.successful,
274+ skipped: response._shards.skipped,
275+ failed: response._shards.failed
276+ },
277+ hits: {
278+ total: {
279+ value: response.hits.total.value,
280+ relation: response.hits.total.relation
281+ },
282+ max_score: response.hits.max_score !== null ? response.hits.max_score : null,
283+ hits: response.hits.hits.map(hit => ({
284+ _index: hit._index,
285+ _id: hit._id,
286+ _score: hit._score !== null ? hit._score : null,
287+ _source: hit._source
288+ }))
289+ },
290+ _scroll_id: response._scroll_id || null
291+ };
292+};
293+
294+// Clear scroll
295+export const clearScrollImpl = async (client, scrollId) => {
296+ const response = await client.clearScroll({
297+ scroll_id: scrollId
298+ });
299+ return {
300+ succeeded: response.succeeded
301+ };
302+};
303+
304+// Bulk operations
305+export const bulkImpl = async (client, operations) => {
306+ const response = await client.bulk({
307+ operations
308+ });
309+
310+ return {
311+ took: response.took,
312+ errors: response.errors,
313+ items: response.items
314+ };
315+};
316+
317+// Count documents
318+export const countImpl = async (client, index, options) => {
319+ const countParams = { ...options };
320+
321+ // Only add index if provided (null means count all indices)
322+ if (index !== null) {
323+ countParams.index = index;
324+ }
325+
326+ const response = await client.count(countParams);
327+
328+ return {
329+ count: response.count
330+ };
331+};
332+
333+// Refresh index
334+export const refreshImpl = async (client, index) => {
335+ const response = await client.indices.refresh({ index });
336+ return {
337+ _shards: {
338+ total: response._shards.total,
339+ successful: response._shards.successful,
340+ failed: response._shards.failed
341+ }
342+ };
343+};
344+
345+// Close client
346+export const closeImpl = async (client) => {
347+ await client.close();
348+};
src/Yoga/Elasticsearch/Elasticsearch.pursnew file+525
@@ -0,0 +1,525 @@
1+module Yoga.Elasticsearch.Elasticsearch where
2+
3+import Prelude
4+
5+import Data.Maybe (Maybe(..))
6+import Data.Newtype (class Newtype)
7+import Data.Nullable (Nullable)
8+import Data.Nullable as Nullable
9+import Data.Time.Duration (Milliseconds)
10+import Effect (Effect)
11+import Effect.Aff (Aff)
12+import Effect.Uncurried (EffectFn1, EffectFn2, EffectFn3, EffectFn4, runEffectFn1, runEffectFn2, runEffectFn3, runEffectFn4)
13+import Foreign (Foreign)
14+import Prim.Row (class Union)
15+import Promise (Promise)
16+import Promise.Aff (toAffE) as Promise
17+
18+-- Opaque Elasticsearch types
19+foreign import data Client :: Type
20+
21+-- Newtypes for type safety
22+
23+-- Connection configuration
24+newtype Node = Node String
25+derive instance Newtype Node _
26+derive newtype instance Eq Node
27+derive newtype instance Show Node
28+
29+newtype ApiKeyId = ApiKeyId String
30+derive instance Newtype ApiKeyId _
31+derive newtype instance Eq ApiKeyId
32+derive newtype instance Show ApiKeyId
33+
34+newtype ApiKeySecret = ApiKeySecret String
35+derive instance Newtype ApiKeySecret _
36+derive newtype instance Eq ApiKeySecret
37+derive newtype instance Show ApiKeySecret
38+
39+newtype Username = Username String
40+derive instance Newtype Username _
41+derive newtype instance Eq Username
42+derive newtype instance Show Username
43+
44+newtype Password = Password String
45+derive instance Newtype Password _
46+derive newtype instance Eq Password
47+derive newtype instance Show Password
48+
49+newtype BearerToken = BearerToken String
50+derive instance Newtype BearerToken _
51+derive newtype instance Eq BearerToken
52+derive newtype instance Show BearerToken
53+
54+newtype CloudId = CloudId String
55+derive instance Newtype CloudId _
56+derive newtype instance Eq CloudId
57+derive newtype instance Show CloudId
58+
59+newtype RequestTimeout = RequestTimeout Milliseconds
60+derive instance Newtype RequestTimeout _
61+derive newtype instance Eq RequestTimeout
62+derive newtype instance Ord RequestTimeout
63+derive newtype instance Show RequestTimeout
64+
65+newtype MaxRetries = MaxRetries Int
66+derive instance Newtype MaxRetries _
67+derive newtype instance Eq MaxRetries
68+derive newtype instance Ord MaxRetries
69+derive newtype instance Show MaxRetries
70+
71+-- Index and document types
72+newtype IndexName = IndexName String
73+derive instance Newtype IndexName _
74+derive newtype instance Eq IndexName
75+derive newtype instance Show IndexName
76+
77+newtype DocumentId = DocumentId String
78+derive instance Newtype DocumentId _
79+derive newtype instance Eq DocumentId
80+derive newtype instance Show DocumentId
81+
82+newtype Routing = Routing String
83+derive instance Newtype Routing _
84+derive newtype instance Eq Routing
85+derive newtype instance Show Routing
86+
87+newtype RefreshPolicy = RefreshPolicy String
88+derive instance Newtype RefreshPolicy _
89+derive newtype instance Eq RefreshPolicy
90+derive newtype instance Show RefreshPolicy
91+
92+-- Convenience refresh policy constructors
93+refreshTrue :: RefreshPolicy
94+refreshTrue = RefreshPolicy "true"
95+
96+refreshFalse :: RefreshPolicy
97+refreshFalse = RefreshPolicy "false"
98+
99+refreshWaitFor :: RefreshPolicy
100+refreshWaitFor = RefreshPolicy "wait_for"
101+
102+-- Search types
103+newtype ScrollId = ScrollId String
104+derive instance Newtype ScrollId _
105+derive newtype instance Eq ScrollId
106+derive newtype instance Show ScrollId
107+
108+newtype ScrollTimeout = ScrollTimeout String
109+derive instance Newtype ScrollTimeout _
110+derive newtype instance Eq ScrollTimeout
111+derive newtype instance Show ScrollTimeout
112+
113+-- Cluster types
114+newtype ClusterName = ClusterName String
115+derive instance Newtype ClusterName _
116+derive newtype instance Eq ClusterName
117+derive newtype instance Show ClusterName
118+
119+newtype ClusterUUID = ClusterUUID String
120+derive instance Newtype ClusterUUID _
121+derive newtype instance Eq ClusterUUID
122+derive newtype instance Show ClusterUUID
123+
124+newtype Version = Version String
125+derive instance Newtype Version _
126+derive newtype instance Eq Version
127+derive newtype instance Show Version
128+
129+newtype BuildHash = BuildHash String
130+derive instance Newtype BuildHash _
131+derive newtype instance Eq BuildHash
132+derive newtype instance Show BuildHash
133+
134+-- Document and query types
135+newtype Document = Document Foreign
136+derive instance Newtype Document _
137+
138+newtype Query = Query Foreign
139+derive instance Newtype Query _
140+
141+newtype Script = Script Foreign
142+derive instance Newtype Script _
143+
144+newtype Mappings = Mappings Foreign
145+derive instance Newtype Mappings _
146+
147+newtype Settings = Settings Foreign
148+derive instance Newtype Settings _
149+
150+newtype Sort = Sort Foreign
151+derive instance Newtype Sort _
152+
153+-- Document metadata types
154+newtype DocumentVersion = DocumentVersion Int
155+derive instance Newtype DocumentVersion _
156+derive newtype instance Eq DocumentVersion
157+derive newtype instance Ord DocumentVersion
158+derive newtype instance Show DocumentVersion
159+
160+newtype SequenceNumber = SequenceNumber Int
161+derive instance Newtype SequenceNumber _
162+derive newtype instance Eq SequenceNumber
163+derive newtype instance Ord SequenceNumber
164+derive newtype instance Show SequenceNumber
165+
166+newtype PrimaryTerm = PrimaryTerm Int
167+derive instance Newtype PrimaryTerm _
168+derive newtype instance Eq PrimaryTerm
169+derive newtype instance Ord PrimaryTerm
170+derive newtype instance Show PrimaryTerm
171+
172+newtype IndexResult = IndexResult String
173+derive instance Newtype IndexResult _
174+derive newtype instance Eq IndexResult
175+derive newtype instance Show IndexResult
176+
177+-- Shard types
178+newtype ShardCount = ShardCount Int
179+derive instance Newtype ShardCount _
180+derive newtype instance Eq ShardCount
181+derive newtype instance Ord ShardCount
182+derive newtype instance Show ShardCount
183+
184+type ShardInfo =
185+ { total :: ShardCount
186+ , successful :: ShardCount
187+ , failed :: ShardCount
188+ }
189+
190+-- Search result types
191+newtype Score = Score Number
192+derive instance Newtype Score _
193+derive newtype instance Eq Score
194+derive newtype instance Ord Score
195+derive newtype instance Show Score
196+
197+newtype TookMilliseconds = TookMilliseconds Int
198+derive instance Newtype TookMilliseconds _
199+derive newtype instance Eq TookMilliseconds
200+derive newtype instance Ord TookMilliseconds
201+derive newtype instance Show TookMilliseconds
202+
203+newtype HitCount = HitCount Int
204+derive instance Newtype HitCount _
205+derive newtype instance Eq HitCount
206+derive newtype instance Ord HitCount
207+derive newtype instance Show HitCount
208+
209+newtype SkippedCount = SkippedCount Int
210+derive instance Newtype SkippedCount _
211+derive newtype instance Eq SkippedCount
212+derive newtype instance Ord SkippedCount
213+derive newtype instance Show SkippedCount
214+
215+-- Operation configuration types
216+newtype RetryCount = RetryCount Int
217+derive instance Newtype RetryCount _
218+derive newtype instance Eq RetryCount
219+derive newtype instance Ord RetryCount
220+derive newtype instance Show RetryCount
221+
222+newtype FromOffset = FromOffset Int
223+derive instance Newtype FromOffset _
224+derive newtype instance Eq FromOffset
225+derive newtype instance Ord FromOffset
226+derive newtype instance Show FromOffset
227+
228+newtype ResultSize = ResultSize Int
229+derive instance Newtype ResultSize _
230+derive newtype instance Eq ResultSize
231+derive newtype instance Ord ResultSize
232+derive newtype instance Show ResultSize
233+
234+-- Authentication configuration
235+type ApiKeyAuth =
236+ { id :: ApiKeyId
237+ , api_key :: ApiKeySecret
238+ }
239+
240+type BasicAuth =
241+ { username :: Username
242+ , password :: Password
243+ }
244+
245+-- Client configuration
246+type ClientConfigImpl =
247+ ( node :: Node
248+ , nodes :: Array Node
249+ , cloud :: { id :: CloudId }
250+ , auth :: { apiKey :: ApiKeyAuth }
251+ , basicAuth :: BasicAuth
252+ , bearerToken :: BearerToken
253+ , requestTimeout :: RequestTimeout
254+ , maxRetries :: MaxRetries
255+ , compression :: Boolean
256+ , ssl :: { rejectUnauthorized :: Boolean }
257+ )
258+
259+foreign import createClientImpl :: forall opts. EffectFn1 { | opts } Client
260+
261+createClient :: forall opts opts_. Union opts opts_ ClientConfigImpl => { | opts } -> Effect Client
262+createClient opts = runEffectFn1 createClientImpl opts
263+
264+-- Info operation
265+type ClusterInfo =
266+ { name :: ClusterName
267+ , cluster_name :: ClusterName
268+ , cluster_uuid :: ClusterUUID
269+ , version ::
270+ { number :: Version
271+ , build_flavor :: String
272+ , build_type :: String
273+ , build_hash :: BuildHash
274+ , build_date :: String
275+ , build_snapshot :: Boolean
276+ , lucene_version :: Version
277+ , minimum_wire_compatibility_version :: Version
278+ , minimum_index_compatibility_version :: Version
279+ }
280+ , tagline :: String
281+ }
282+
283+foreign import infoImpl :: EffectFn1 Client (Promise ClusterInfo)
284+
285+info :: Client -> Aff ClusterInfo
286+info = runEffectFn1 infoImpl >>> Promise.toAffE
287+
288+-- Ping operation
289+foreign import pingImpl :: EffectFn1 Client (Promise Boolean)
290+
291+ping :: Client -> Aff Boolean
292+ping = runEffectFn1 pingImpl >>> Promise.toAffE
293+
294+-- Index operations
295+
296+-- Create index
297+type CreateIndexOptionsImpl =
298+ ( mappings :: Mappings
299+ , settings :: Settings
300+ )
301+
302+type CreateIndexResponse =
303+ { acknowledged :: Boolean
304+ , shards_acknowledged :: Boolean
305+ , index :: IndexName
306+ }
307+
308+foreign import createIndexImpl :: forall opts. EffectFn3 Client IndexName { | opts } (Promise CreateIndexResponse)
309+
310+createIndex :: forall opts opts_. Union opts opts_ CreateIndexOptionsImpl => IndexName -> { | opts } -> Client -> Aff CreateIndexResponse
311+createIndex index opts client = runEffectFn3 createIndexImpl client index opts # Promise.toAffE
312+
313+-- Delete index
314+type DeleteIndexResponse =
315+ { acknowledged :: Boolean
316+ }
317+
318+foreign import deleteIndexImpl :: EffectFn2 Client IndexName (Promise DeleteIndexResponse)
319+
320+deleteIndex :: IndexName -> Client -> Aff DeleteIndexResponse
321+deleteIndex index client = runEffectFn2 deleteIndexImpl client index # Promise.toAffE
322+
323+-- Check if index exists
324+foreign import indexExistsImpl :: EffectFn2 Client IndexName (Promise Boolean)
325+
326+indexExists :: IndexName -> Client -> Aff Boolean
327+indexExists index client = runEffectFn2 indexExistsImpl client index # Promise.toAffE
328+
329+-- Document operations
330+
331+-- Index a document
332+type IndexDocumentOptionsImpl =
333+ ( id :: DocumentId
334+ , routing :: Routing
335+ , refresh :: RefreshPolicy
336+ , timeout :: RequestTimeout
337+ )
338+
339+type IndexDocumentResponse =
340+ { _index :: IndexName
341+ , _id :: DocumentId
342+ , _version :: DocumentVersion
343+ , result :: IndexResult
344+ , _shards :: ShardInfo
345+ }
346+
347+foreign import indexDocumentImpl :: forall opts. EffectFn4 Client IndexName Document { | opts } (Promise IndexDocumentResponse)
348+
349+indexDocument :: forall opts opts_. Union opts opts_ IndexDocumentOptionsImpl => IndexName -> Document -> { | opts } -> Client -> Aff IndexDocumentResponse
350+indexDocument index doc opts client = runEffectFn4 indexDocumentImpl client index doc opts # Promise.toAffE
351+
352+-- Get document
353+type GetDocumentOptionsImpl =
354+ ( routing :: Routing
355+ , _source :: Boolean
356+ , _source_includes :: Array String
357+ , _source_excludes :: Array String
358+ )
359+
360+type GetDocumentResponse =
361+ { _index :: IndexName
362+ , _id :: DocumentId
363+ , _version :: DocumentVersion
364+ , _seq_no :: SequenceNumber
365+ , _primary_term :: PrimaryTerm
366+ , found :: Boolean
367+ , _source :: Nullable Foreign
368+ }
369+
370+foreign import getDocumentImpl :: forall opts. EffectFn4 Client IndexName DocumentId { | opts } (Promise GetDocumentResponse)
371+
372+getDocument :: forall opts opts_. Union opts opts_ GetDocumentOptionsImpl => IndexName -> DocumentId -> { | opts } -> Client -> Aff GetDocumentResponse
373+getDocument index docId opts client = runEffectFn4 getDocumentImpl client index docId opts # Promise.toAffE
374+
375+-- Get document simple (returns Maybe)
376+getDocumentMaybe :: IndexName -> DocumentId -> Client -> Aff (Maybe Foreign)
377+getDocumentMaybe index docId client = do
378+ result <- getDocument index docId {} client
379+ pure $ if result.found
380+ then Nullable.toMaybe result._source
381+ else Nothing
382+
383+-- Update document
384+type UpdateDocumentOptionsImpl =
385+ ( doc :: Document
386+ , script :: Script
387+ , routing :: Routing
388+ , refresh :: RefreshPolicy
389+ , retry_on_conflict :: RetryCount
390+ )
391+
392+type UpdateDocumentResponse =
393+ { _index :: IndexName
394+ , _id :: DocumentId
395+ , _version :: DocumentVersion
396+ , result :: IndexResult
397+ }
398+
399+foreign import updateDocumentImpl :: forall opts. EffectFn4 Client IndexName DocumentId { | opts } (Promise UpdateDocumentResponse)
400+
401+updateDocument :: forall opts opts_. Union opts opts_ UpdateDocumentOptionsImpl => IndexName -> DocumentId -> { | opts } -> Client -> Aff UpdateDocumentResponse
402+updateDocument index docId opts client = runEffectFn4 updateDocumentImpl client index docId opts # Promise.toAffE
403+
404+-- Delete document
405+type DeleteDocumentOptionsImpl =
406+ ( routing :: Routing
407+ , refresh :: RefreshPolicy
408+ )
409+
410+type DeleteDocumentResponse =
411+ { _index :: IndexName
412+ , _id :: DocumentId
413+ , _version :: DocumentVersion
414+ , result :: IndexResult
415+ }
416+
417+foreign import deleteDocumentImpl :: forall opts. EffectFn4 Client IndexName DocumentId { | opts } (Promise DeleteDocumentResponse)
418+
419+deleteDocument :: forall opts opts_. Union opts opts_ DeleteDocumentOptionsImpl => IndexName -> DocumentId -> { | opts } -> Client -> Aff DeleteDocumentResponse
420+deleteDocument index docId opts client = runEffectFn4 deleteDocumentImpl client index docId opts # Promise.toAffE
421+
422+-- Search operations
423+
424+type SearchOptionsImpl =
425+ ( query :: Query
426+ , from :: FromOffset
427+ , size :: ResultSize
428+ , sort :: Sort
429+ , _source :: Boolean
430+ , _source_includes :: Array String
431+ , _source_excludes :: Array String
432+ , track_total_hits :: Boolean
433+ , scroll :: ScrollTimeout
434+ , routing :: Routing
435+ )
436+
437+type SearchHit =
438+ { _index :: IndexName
439+ , _id :: DocumentId
440+ , _score :: Nullable Score
441+ , _source :: Foreign
442+ }
443+
444+type SearchResponse =
445+ { took :: TookMilliseconds
446+ , timed_out :: Boolean
447+ , _shards ::
448+ { total :: ShardCount
449+ , successful :: ShardCount
450+ , skipped :: SkippedCount
451+ , failed :: ShardCount
452+ }
453+ , hits ::
454+ { total :: { value :: HitCount, relation :: String }
455+ , max_score :: Nullable Score
456+ , hits :: Array SearchHit
457+ }
458+ , _scroll_id :: Nullable ScrollId
459+ }
460+
461+foreign import searchImpl :: forall opts. EffectFn3 Client (Nullable IndexName) { | opts } (Promise SearchResponse)
462+
463+search :: forall opts opts_. Union opts opts_ SearchOptionsImpl => Maybe IndexName -> { | opts } -> Client -> Aff SearchResponse
464+search index opts client = runEffectFn3 searchImpl client (Nullable.toNullable index) opts # Promise.toAffE
465+
466+-- Search all indices
467+searchAll :: forall opts opts_. Union opts opts_ SearchOptionsImpl => { | opts } -> Client -> Aff SearchResponse
468+searchAll opts client = search Nothing opts client
469+
470+-- Scroll API for pagination
471+type ScrollOptionsImpl =
472+ ( scroll :: ScrollTimeout
473+ )
474+
475+foreign import scrollImpl :: forall opts. EffectFn3 Client ScrollId { | opts } (Promise SearchResponse)
476+
477+scroll :: forall opts opts_. Union opts opts_ ScrollOptionsImpl => ScrollId -> { | opts } -> Client -> Aff SearchResponse
478+scroll scrollId opts client = runEffectFn3 scrollImpl client scrollId opts # Promise.toAffE
479+
480+-- Clear scroll
481+foreign import clearScrollImpl :: EffectFn2 Client ScrollId (Promise { succeeded :: Boolean })
482+
483+clearScroll :: ScrollId -> Client -> Aff { succeeded :: Boolean }
484+clearScroll scrollId client = runEffectFn2 clearScrollImpl client scrollId # Promise.toAffE
485+
486+-- Bulk operations
487+
488+type BulkOperation = Foreign
489+
490+type BulkResponse =
491+ { took :: TookMilliseconds
492+ , errors :: Boolean
493+ , items :: Array Foreign
494+ }
495+
496+foreign import bulkImpl :: EffectFn2 Client (Array BulkOperation) (Promise BulkResponse)
497+
498+bulk :: Array BulkOperation -> Client -> Aff BulkResponse
499+bulk operations client = runEffectFn2 bulkImpl client operations # Promise.toAffE
500+
501+-- Count documents
502+type CountOptionsImpl =
503+ ( query :: Query
504+ )
505+
506+type CountResponse =
507+ { count :: HitCount
508+ }
509+
510+foreign import countImpl :: forall opts. EffectFn3 Client (Nullable IndexName) { | opts } (Promise CountResponse)
511+
512+count :: forall opts opts_. Union opts opts_ CountOptionsImpl => Maybe IndexName -> { | opts } -> Client -> Aff CountResponse
513+count index opts client = runEffectFn3 countImpl client (Nullable.toNullable index) opts # Promise.toAffE
514+
515+-- Refresh index
516+foreign import refreshImpl :: EffectFn2 Client IndexName (Promise { _shards :: ShardInfo })
517+
518+refresh :: IndexName -> Client -> Aff { _shards :: ShardInfo }
519+refresh index client = runEffectFn2 refreshImpl client index # Promise.toAffE
520+
521+-- Close client
522+foreign import closeImpl :: EffectFn1 Client (Promise Unit)
523+
524+close :: Client -> Aff Unit
525+close = runEffectFn1 closeImpl >>> Promise.toAffE