effective-indexer 0.2.7 → 0.2.9
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/LICENSE +17 -8
- package/README.md +1 -3
- package/dist/index.cjs +116 -139
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +116 -139
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/LICENSE
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
|
-
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
2
3
|
Copyright (c) 2026 Aleksandr Shenshin
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
6
11
|
|
|
7
|
-
|
|
8
|
-
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
9
14
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -345,8 +345,6 @@ npm run check
|
|
|
345
345
|
|
|
346
346
|
## License
|
|
347
347
|
|
|
348
|
-
|
|
349
|
-
Commercial use requires a paid license — see `LICENSE`.
|
|
350
|
-
Contact: Aleksandr Shenshin <shenshin@me.com>.
|
|
348
|
+
MIT — do whatever you want.
|
|
351
349
|
|
|
352
350
|
Repository: [github.com/cybervoid0/effective-indexer](https://github.com/cybervoid0/effective-indexer)
|
package/dist/index.cjs
CHANGED
|
@@ -179,12 +179,22 @@ var RpcProviderLive = effect.Layer.effect(
|
|
|
179
179
|
cause: e
|
|
180
180
|
})
|
|
181
181
|
}).pipe(
|
|
182
|
-
effect.Effect.
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
182
|
+
effect.Effect.flatMap((block) => {
|
|
183
|
+
if (!block) {
|
|
184
|
+
return effect.Effect.fail(
|
|
185
|
+
new RpcError({
|
|
186
|
+
reason: `Block ${blockNumber} not found`,
|
|
187
|
+
method: "eth_getBlockByNumber"
|
|
188
|
+
})
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
return effect.Effect.succeed({
|
|
192
|
+
number: block.number,
|
|
193
|
+
hash: block.hash,
|
|
194
|
+
parentHash: block.parentHash,
|
|
195
|
+
timestamp: block.timestamp
|
|
196
|
+
});
|
|
197
|
+
})
|
|
188
198
|
);
|
|
189
199
|
return { getBlockNumber, getLogs, getBlock };
|
|
190
200
|
})
|
|
@@ -203,7 +213,7 @@ var BlockCursorLive = effect.Layer.effect(
|
|
|
203
213
|
const pollOnce = effect.Effect.gen(function* () {
|
|
204
214
|
const current = yield* rpc.getBlockNumber;
|
|
205
215
|
const confirmations = BigInt(config.network.polling.confirmations);
|
|
206
|
-
const confirmed = current - confirmations;
|
|
216
|
+
const confirmed = current > confirmations ? current - confirmations : 0n;
|
|
207
217
|
const prev = yield* effect.Ref.get(lastSeen);
|
|
208
218
|
const isInitialized = yield* effect.Ref.get(initialized);
|
|
209
219
|
if (!isInitialized) {
|
|
@@ -220,10 +230,11 @@ var BlockCursorLive = effect.Layer.effect(
|
|
|
220
230
|
yield* effect.Effect.logTrace("No new confirmed blocks");
|
|
221
231
|
return [];
|
|
222
232
|
}
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
233
|
+
const count = Number(confirmed - prev);
|
|
234
|
+
const blocks = Array.from(
|
|
235
|
+
{ length: count },
|
|
236
|
+
(_, i) => prev + 1n + BigInt(i)
|
|
237
|
+
);
|
|
227
238
|
yield* effect.Ref.set(lastSeen, confirmed);
|
|
228
239
|
yield* effect.Effect.logTrace("Blocks emitted").pipe(
|
|
229
240
|
effect.Effect.annotateLogs({
|
|
@@ -246,15 +257,31 @@ var wrapSqlError = (operation) => (e) => new StorageError({
|
|
|
246
257
|
operation,
|
|
247
258
|
cause: e
|
|
248
259
|
});
|
|
260
|
+
var buildWhereClause = (query) => {
|
|
261
|
+
const pairs = [
|
|
262
|
+
[query.contractName, "contract_name = ?"],
|
|
263
|
+
[query.eventName, "event_name = ?"],
|
|
264
|
+
[
|
|
265
|
+
query.fromBlock !== void 0 ? Number(query.fromBlock) : void 0,
|
|
266
|
+
"block_number >= ?"
|
|
267
|
+
],
|
|
268
|
+
[
|
|
269
|
+
query.toBlock !== void 0 ? Number(query.toBlock) : void 0,
|
|
270
|
+
"block_number <= ?"
|
|
271
|
+
],
|
|
272
|
+
[query.txHash, "tx_hash = ?"]
|
|
273
|
+
];
|
|
274
|
+
const active = pairs.filter(([v]) => v !== void 0);
|
|
275
|
+
const where = active.length > 0 ? `WHERE ${active.map(([, c]) => c).join(" AND ")}` : "";
|
|
276
|
+
const params = active.map(([v]) => v);
|
|
277
|
+
return { where, params };
|
|
278
|
+
};
|
|
249
279
|
var toJsonValue = (_key, value) => typeof value === "bigint" ? value.toString() : value;
|
|
250
280
|
var INSERT_BATCH_SIZE = 250;
|
|
251
|
-
var chunkEvents = (events, size) =>
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
}
|
|
256
|
-
return chunks;
|
|
257
|
-
};
|
|
281
|
+
var chunkEvents = (events, size) => Array.from(
|
|
282
|
+
{ length: Math.ceil(events.length / size) },
|
|
283
|
+
(_, i) => events.slice(i * size, (i + 1) * size)
|
|
284
|
+
);
|
|
258
285
|
var Storage = class extends effect.Context.Tag("effective-indexer/Storage")() {
|
|
259
286
|
};
|
|
260
287
|
var StorageLive = effect.Layer.effect(
|
|
@@ -262,6 +289,7 @@ var StorageLive = effect.Layer.effect(
|
|
|
262
289
|
effect.Effect.gen(function* () {
|
|
263
290
|
const sql$1 = yield* sql.SqlClient.SqlClient;
|
|
264
291
|
const initialize = effect.Effect.gen(function* () {
|
|
292
|
+
yield* sql$1`PRAGMA journal_mode=WAL`;
|
|
265
293
|
yield* sql$1`
|
|
266
294
|
CREATE TABLE IF NOT EXISTS events (
|
|
267
295
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -297,7 +325,7 @@ var StorageLive = effect.Layer.effect(
|
|
|
297
325
|
if (events.length === 0) {
|
|
298
326
|
return;
|
|
299
327
|
}
|
|
300
|
-
|
|
328
|
+
yield* effect.Effect.forEach(chunkEvents(events, INSERT_BATCH_SIZE), (batch) => {
|
|
301
329
|
const placeholders = batch.map(() => "(?, ?, ?, ?, ?, ?, ?)").join(", ");
|
|
302
330
|
const params = batch.flatMap((event) => {
|
|
303
331
|
const argsJson = JSON.stringify(event.args, toJsonValue);
|
|
@@ -313,44 +341,22 @@ var StorageLive = effect.Layer.effect(
|
|
|
313
341
|
argsJson
|
|
314
342
|
];
|
|
315
343
|
});
|
|
316
|
-
|
|
344
|
+
return sql$1.unsafe(
|
|
317
345
|
`INSERT OR IGNORE INTO events (contract_name, event_name, block_number, tx_hash, log_index, timestamp, args)
|
|
318
346
|
VALUES ${placeholders}`,
|
|
319
347
|
params
|
|
320
348
|
);
|
|
321
|
-
}
|
|
349
|
+
});
|
|
322
350
|
}).pipe(effect.Effect.mapError(wrapSqlError("insertEvents")));
|
|
323
351
|
const deleteEventsFrom = (blockNumber) => sql$1`DELETE FROM events WHERE block_number >= ${Number(blockNumber)}`.pipe(
|
|
324
352
|
effect.Effect.asVoid,
|
|
325
353
|
effect.Effect.mapError(wrapSqlError("deleteEventsFrom"))
|
|
326
354
|
);
|
|
327
355
|
const queryEvents = (query) => effect.Effect.gen(function* () {
|
|
328
|
-
const
|
|
329
|
-
const params = [];
|
|
330
|
-
if (query.contractName !== void 0) {
|
|
331
|
-
conditions.push("contract_name = ?");
|
|
332
|
-
params.push(query.contractName);
|
|
333
|
-
}
|
|
334
|
-
if (query.eventName !== void 0) {
|
|
335
|
-
conditions.push("event_name = ?");
|
|
336
|
-
params.push(query.eventName);
|
|
337
|
-
}
|
|
338
|
-
if (query.fromBlock !== void 0) {
|
|
339
|
-
conditions.push("block_number >= ?");
|
|
340
|
-
params.push(Number(query.fromBlock));
|
|
341
|
-
}
|
|
342
|
-
if (query.toBlock !== void 0) {
|
|
343
|
-
conditions.push("block_number <= ?");
|
|
344
|
-
params.push(Number(query.toBlock));
|
|
345
|
-
}
|
|
346
|
-
if (query.txHash !== void 0) {
|
|
347
|
-
conditions.push("tx_hash = ?");
|
|
348
|
-
params.push(query.txHash);
|
|
349
|
-
}
|
|
350
|
-
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
356
|
+
const { where, params } = buildWhereClause(query);
|
|
351
357
|
const order = query.order === "desc" ? "DESC" : "ASC";
|
|
352
|
-
const limit = query.limit ?? 1e3;
|
|
353
|
-
const offset = query.offset ?? 0;
|
|
358
|
+
const limit = Math.max(1, Math.min(query.limit ?? 1e3, 1e4));
|
|
359
|
+
const offset = Math.max(0, query.offset ?? 0);
|
|
354
360
|
const rows = yield* sql$1.unsafe(
|
|
355
361
|
`SELECT * FROM events ${where} ORDER BY block_number ${order}, log_index ASC LIMIT ? OFFSET ?`,
|
|
356
362
|
[...params, limit, offset]
|
|
@@ -362,32 +368,10 @@ var StorageLive = effect.Layer.effect(
|
|
|
362
368
|
const rows2 = yield* sql$1`SELECT COUNT(*) as count FROM events`;
|
|
363
369
|
return rows2[0]?.count ?? 0;
|
|
364
370
|
}
|
|
365
|
-
const
|
|
366
|
-
const params = [];
|
|
367
|
-
if (query.contractName !== void 0) {
|
|
368
|
-
conditions.push("contract_name = ?");
|
|
369
|
-
params.push(query.contractName);
|
|
370
|
-
}
|
|
371
|
-
if (query.eventName !== void 0) {
|
|
372
|
-
conditions.push("event_name = ?");
|
|
373
|
-
params.push(query.eventName);
|
|
374
|
-
}
|
|
375
|
-
if (query.fromBlock !== void 0) {
|
|
376
|
-
conditions.push("block_number >= ?");
|
|
377
|
-
params.push(Number(query.fromBlock));
|
|
378
|
-
}
|
|
379
|
-
if (query.toBlock !== void 0) {
|
|
380
|
-
conditions.push("block_number <= ?");
|
|
381
|
-
params.push(Number(query.toBlock));
|
|
382
|
-
}
|
|
383
|
-
if (query.txHash !== void 0) {
|
|
384
|
-
conditions.push("tx_hash = ?");
|
|
385
|
-
params.push(query.txHash);
|
|
386
|
-
}
|
|
387
|
-
const where = `WHERE ${conditions.join(" AND ")}`;
|
|
371
|
+
const { where, params } = buildWhereClause(query);
|
|
388
372
|
const rows = yield* sql$1.unsafe(
|
|
389
373
|
`SELECT COUNT(*) as count FROM events ${where}`,
|
|
390
|
-
params
|
|
374
|
+
[...params]
|
|
391
375
|
);
|
|
392
376
|
return rows[0]?.count ?? 0;
|
|
393
377
|
}).pipe(effect.Effect.mapError(wrapSqlError("countEvents")));
|
|
@@ -672,9 +656,7 @@ var ProgressRendererLive = effect.Layer.effect(
|
|
|
672
656
|
});
|
|
673
657
|
logUpdate(lines.join("\n"));
|
|
674
658
|
} else {
|
|
675
|
-
|
|
676
|
-
yield* effect.Effect.log(buildLine(s, config));
|
|
677
|
-
}
|
|
659
|
+
yield* effect.Effect.forEach(snapshots, (s) => effect.Effect.log(buildLine(s, config)));
|
|
678
660
|
}
|
|
679
661
|
});
|
|
680
662
|
return {
|
|
@@ -745,13 +727,13 @@ var fetchLogs = (params) => effect.Stream.unwrap(
|
|
|
745
727
|
const config = yield* Config;
|
|
746
728
|
const rpc = yield* RpcProvider;
|
|
747
729
|
const chunkSize = BigInt(config.network.logs.chunkSize);
|
|
748
|
-
const
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
const
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
}
|
|
730
|
+
const span = params.toBlock - params.fromBlock + 1n;
|
|
731
|
+
const numChunks = span > 0n ? Number((span + chunkSize - 1n) / chunkSize) : 0;
|
|
732
|
+
const chunks = Array.from({ length: numChunks }, (_, i) => {
|
|
733
|
+
const from = params.fromBlock + BigInt(i) * chunkSize;
|
|
734
|
+
const to = from + chunkSize - 1n > params.toBlock ? params.toBlock : from + chunkSize - 1n;
|
|
735
|
+
return { from, to };
|
|
736
|
+
});
|
|
755
737
|
if (chunks.length === 0) {
|
|
756
738
|
return effect.Stream.empty;
|
|
757
739
|
}
|
|
@@ -762,7 +744,7 @@ var fetchLogs = (params) => effect.Stream.unwrap(
|
|
|
762
744
|
const { baseDelayMs, maxDelayMs } = config.network.logs.retry;
|
|
763
745
|
const maxRetries = config.network.logs.maxRetries;
|
|
764
746
|
const attempt = yield* effect.Ref.make(0);
|
|
765
|
-
|
|
747
|
+
const logs = yield* rpc.getLogs({
|
|
766
748
|
address: params.address,
|
|
767
749
|
topics: [params.topics],
|
|
768
750
|
fromBlock: chunk.from,
|
|
@@ -800,15 +782,16 @@ var fetchLogs = (params) => effect.Stream.unwrap(
|
|
|
800
782
|
)
|
|
801
783
|
),
|
|
802
784
|
effect.Effect.tap(
|
|
803
|
-
(
|
|
785
|
+
(result) => effect.Effect.logTrace("Logs fetched").pipe(
|
|
804
786
|
effect.Effect.annotateLogs({
|
|
805
787
|
from: chunk.from.toString(),
|
|
806
788
|
to: chunk.to.toString(),
|
|
807
|
-
count:
|
|
789
|
+
count: result.length.toString()
|
|
808
790
|
})
|
|
809
791
|
)
|
|
810
792
|
)
|
|
811
793
|
);
|
|
794
|
+
return { logs, chunkEnd: chunk.to };
|
|
812
795
|
}),
|
|
813
796
|
{ concurrency }
|
|
814
797
|
)
|
|
@@ -859,13 +842,11 @@ var ReorgDetectorLive = effect.Layer.effect(
|
|
|
859
842
|
}
|
|
860
843
|
}
|
|
861
844
|
yield* effect.Ref.update(blockHashBuffer, (buf) => {
|
|
862
|
-
const newBuf = new Map(buf);
|
|
863
|
-
newBuf.set(block.number, block.hash);
|
|
864
845
|
const minBlock = block.number - BigInt(reorgDepth);
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
return
|
|
846
|
+
const entries = Array.from(buf.entries()).filter(
|
|
847
|
+
([k]) => k >= minBlock
|
|
848
|
+
);
|
|
849
|
+
return new Map([...entries, [block.number, block.hash]]);
|
|
869
850
|
});
|
|
870
851
|
yield* storage.insertBlockHash(block.number, block.hash);
|
|
871
852
|
});
|
|
@@ -949,48 +930,48 @@ var indexContract = (contract) => effect.Stream.unwrap(
|
|
|
949
930
|
toBlock: currentHead
|
|
950
931
|
}).pipe(
|
|
951
932
|
effect.Stream.mapEffect(
|
|
952
|
-
(
|
|
953
|
-
|
|
954
|
-
const
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
rawLogs
|
|
958
|
-
);
|
|
959
|
-
const blockNumbers = [
|
|
960
|
-
...new Set(decoded.map((d) => d.blockNumber))
|
|
961
|
-
];
|
|
962
|
-
const withTimestamp = [];
|
|
963
|
-
for (const bn of blockNumbers) {
|
|
964
|
-
const blockInfo = yield* getBlockWithRetry(bn);
|
|
965
|
-
yield* reorgDetector.verifyBlock(blockInfo);
|
|
966
|
-
for (const event of decoded) {
|
|
967
|
-
if (event.blockNumber === bn) {
|
|
968
|
-
withTimestamp.push({
|
|
969
|
-
...event,
|
|
970
|
-
timestamp: blockInfo.timestamp
|
|
971
|
-
});
|
|
972
|
-
}
|
|
973
|
-
}
|
|
974
|
-
}
|
|
975
|
-
yield* storage.insertEvents(
|
|
976
|
-
withTimestamp.map((e) => ({
|
|
977
|
-
contractName: e.contractName,
|
|
978
|
-
eventName: e.eventName,
|
|
979
|
-
blockNumber: e.blockNumber,
|
|
980
|
-
txHash: e.txHash,
|
|
981
|
-
logIndex: e.logIndex,
|
|
982
|
-
timestamp: e.timestamp,
|
|
983
|
-
args: e.args
|
|
984
|
-
}))
|
|
985
|
-
);
|
|
986
|
-
const lastBlock = withTimestamp[withTimestamp.length - 1];
|
|
987
|
-
if (lastBlock) {
|
|
988
|
-
yield* checkpoint.save(
|
|
933
|
+
(chunk) => effect.Effect.gen(function* () {
|
|
934
|
+
const { logs: rawLogs, chunkEnd } = chunk;
|
|
935
|
+
const blockCache = /* @__PURE__ */ new Map();
|
|
936
|
+
const withTimestamp = rawLogs.length > 0 ? yield* effect.Effect.gen(function* () {
|
|
937
|
+
const decoded = yield* decoder.decodeBatch(
|
|
989
938
|
contract.name,
|
|
990
|
-
|
|
991
|
-
|
|
939
|
+
contract.abi,
|
|
940
|
+
rawLogs
|
|
992
941
|
);
|
|
993
|
-
|
|
942
|
+
const blockNumbers = [
|
|
943
|
+
...new Set(decoded.map((d) => d.blockNumber))
|
|
944
|
+
];
|
|
945
|
+
const enriched = yield* effect.Effect.forEach(
|
|
946
|
+
blockNumbers,
|
|
947
|
+
(bn) => effect.Effect.gen(function* () {
|
|
948
|
+
const blockInfo = yield* getBlockWithRetry(bn);
|
|
949
|
+
blockCache.set(bn, blockInfo);
|
|
950
|
+
yield* reorgDetector.verifyBlock(blockInfo);
|
|
951
|
+
return decoded.filter((e) => e.blockNumber === bn).map((e) => ({
|
|
952
|
+
...e,
|
|
953
|
+
timestamp: blockInfo.timestamp
|
|
954
|
+
}));
|
|
955
|
+
}),
|
|
956
|
+
{ concurrency: 1 }
|
|
957
|
+
);
|
|
958
|
+
const events = enriched.flat();
|
|
959
|
+
yield* storage.insertEvents(
|
|
960
|
+
events.map((e) => ({
|
|
961
|
+
contractName: e.contractName,
|
|
962
|
+
eventName: e.eventName,
|
|
963
|
+
blockNumber: e.blockNumber,
|
|
964
|
+
txHash: e.txHash,
|
|
965
|
+
logIndex: e.logIndex,
|
|
966
|
+
timestamp: e.timestamp,
|
|
967
|
+
args: e.args
|
|
968
|
+
}))
|
|
969
|
+
);
|
|
970
|
+
return events;
|
|
971
|
+
}) : [];
|
|
972
|
+
const cached = blockCache.get(chunkEnd);
|
|
973
|
+
const chunkEndHash = cached ? cached.hash : (yield* getBlockWithRetry(chunkEnd)).hash;
|
|
974
|
+
yield* checkpoint.save(contract.name, chunkEnd, chunkEndHash);
|
|
994
975
|
const chunkSize = BigInt(config.network.logs.chunkSize);
|
|
995
976
|
const lastProcessed = yield* effect.Ref.modify(
|
|
996
977
|
processedBlocksRef,
|
|
@@ -1009,7 +990,7 @@ var indexContract = (contract) => effect.Stream.unwrap(
|
|
|
1009
990
|
yield* effect.Effect.logDebug("Chunk indexed").pipe(
|
|
1010
991
|
effect.Effect.annotateLogs({
|
|
1011
992
|
events: withTimestamp.length.toString(),
|
|
1012
|
-
checkpoint:
|
|
993
|
+
checkpoint: chunkEnd.toString()
|
|
1013
994
|
})
|
|
1014
995
|
);
|
|
1015
996
|
return withTimestamp;
|
|
@@ -1190,15 +1171,11 @@ var QueryApiLive = effect.Layer.effect(
|
|
|
1190
1171
|
return { getEvents, getEventCount, getLatestBlock };
|
|
1191
1172
|
})
|
|
1192
1173
|
);
|
|
1193
|
-
var toConfigMap = (env) =>
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
}
|
|
1199
|
-
}
|
|
1200
|
-
return map;
|
|
1201
|
-
};
|
|
1174
|
+
var toConfigMap = (env) => new Map(
|
|
1175
|
+
Object.entries(env).filter(
|
|
1176
|
+
(entry) => typeof entry[1] === "string"
|
|
1177
|
+
)
|
|
1178
|
+
);
|
|
1202
1179
|
var getProvider = (env) => effect.ConfigProvider.fromMap(toConfigMap(env ?? process.env));
|
|
1203
1180
|
var readOptionalString = (name, provider) => effect.Effect.runSync(
|
|
1204
1181
|
effect.Effect.withConfigProvider(provider)(
|