envio 2.27.6 → 2.28.0-alpha.2
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/package.json +5 -5
- package/rescript.json +3 -0
- package/src/FetchState.res +21 -14
- package/src/FetchState.res.js +14 -5
- package/src/Hasura.res +31 -12
- package/src/Hasura.res.js +31 -13
- package/src/Internal.res +7 -4
- package/src/InternalConfig.res +20 -0
- package/src/InternalConfig.res.js +2 -0
- package/src/Js.shim.ts +11 -0
- package/src/LoadManager.res +12 -6
- package/src/LoadManager.res.js +13 -6
- package/src/Persistence.res +25 -33
- package/src/Persistence.res.js +18 -20
- package/src/PgStorage.res +162 -102
- package/src/PgStorage.res.js +146 -103
- package/src/Prometheus.res +2 -2
- package/src/Prometheus.res.js +2 -3
- package/src/bindings/Pino.res +1 -1
- package/src/bindings/Pino.res.js +2 -1
- package/src/bindings/Postgres.res +1 -1
- package/src/db/EntityHistory.res +18 -17
- package/src/db/EntityHistory.res.js +28 -26
- package/src/db/InternalTable.gen.ts +43 -0
- package/src/db/InternalTable.res +430 -0
- package/src/db/InternalTable.res.js +315 -0
- package/src/vendored/Rest.res +11 -2
- package/src/vendored/Rest.res.js +44 -35
package/src/PgStorage.res
CHANGED
|
@@ -57,11 +57,19 @@ let makeCreateTableQuery = (table: Table.table, ~pgSchema) => {
|
|
|
57
57
|
let makeInitializeTransaction = (
|
|
58
58
|
~pgSchema,
|
|
59
59
|
~pgUser,
|
|
60
|
-
~
|
|
60
|
+
~chainConfigs=[],
|
|
61
61
|
~entities=[],
|
|
62
62
|
~enums=[],
|
|
63
63
|
~isEmptyPgSchema=false,
|
|
64
64
|
) => {
|
|
65
|
+
let generalTables = [
|
|
66
|
+
InternalTable.EventSyncState.table,
|
|
67
|
+
InternalTable.Chains.table,
|
|
68
|
+
InternalTable.PersistedState.table,
|
|
69
|
+
InternalTable.EndOfBlockRangeScannedData.table,
|
|
70
|
+
InternalTable.RawEvents.table,
|
|
71
|
+
]
|
|
72
|
+
|
|
65
73
|
let allTables = generalTables->Array.copy
|
|
66
74
|
let allEntityTables = []
|
|
67
75
|
entities->Js.Array2.forEach((entity: Internal.entityConfig) => {
|
|
@@ -113,7 +121,8 @@ GRANT ALL ON SCHEMA "${pgSchema}" TO public;`,
|
|
|
113
121
|
|
|
114
122
|
// Add derived indices
|
|
115
123
|
entities->Js.Array2.forEach((entity: Internal.entityConfig) => {
|
|
116
|
-
functionsQuery :=
|
|
124
|
+
functionsQuery :=
|
|
125
|
+
functionsQuery.contents ++ "\n" ++ entity.entityHistory.makeInsertFnQuery(~pgSchema)
|
|
117
126
|
|
|
118
127
|
entity.table
|
|
119
128
|
->Table.getDerivedFromFields
|
|
@@ -131,6 +140,16 @@ GRANT ALL ON SCHEMA "${pgSchema}" TO public;`,
|
|
|
131
140
|
})
|
|
132
141
|
})
|
|
133
142
|
|
|
143
|
+
// Create views for Hasura integration
|
|
144
|
+
query := query.contents ++ "\n" ++ InternalTable.Views.makeMetaViewQuery(~pgSchema)
|
|
145
|
+
query := query.contents ++ "\n" ++ InternalTable.Views.makeChainMetadataViewQuery(~pgSchema)
|
|
146
|
+
|
|
147
|
+
// Populate initial chain data
|
|
148
|
+
switch InternalTable.Chains.makeInitialValuesQuery(~pgSchema, ~chainConfigs) {
|
|
149
|
+
| Some(initialChainsValuesQuery) => query := query.contents ++ "\n" ++ initialChainsValuesQuery
|
|
150
|
+
| None => ()
|
|
151
|
+
}
|
|
152
|
+
|
|
134
153
|
// Add cache row count function
|
|
135
154
|
functionsQuery :=
|
|
136
155
|
functionsQuery.contents ++
|
|
@@ -162,6 +181,10 @@ let makeLoadByIdsQuery = (~pgSchema, ~tableName) => {
|
|
|
162
181
|
`SELECT * FROM "${pgSchema}"."${tableName}" WHERE id = ANY($1::text[]);`
|
|
163
182
|
}
|
|
164
183
|
|
|
184
|
+
let makeLoadAllQuery = (~pgSchema, ~tableName) => {
|
|
185
|
+
`SELECT * FROM "${pgSchema}"."${tableName}";`
|
|
186
|
+
}
|
|
187
|
+
|
|
165
188
|
let makeInsertUnnestSetQuery = (~pgSchema, ~table: Table.table, ~itemSchema, ~isRawEvents) => {
|
|
166
189
|
let {quotedFieldNames, quotedNonPrimaryFieldNames, arrayFieldTypes} =
|
|
167
190
|
table->Table.toSqlParams(~schema=itemSchema, ~pgSchema)
|
|
@@ -234,21 +257,19 @@ VALUES${placeholders.contents}` ++
|
|
|
234
257
|
} ++ ";"
|
|
235
258
|
}
|
|
236
259
|
|
|
237
|
-
// Should move this to a better place
|
|
238
|
-
// We need it for the isRawEvents check in makeTableBatchSet
|
|
239
|
-
// to always apply the unnest optimization.
|
|
240
|
-
// This is needed, because even though it has JSON fields,
|
|
241
|
-
// they are always guaranteed to be an object.
|
|
242
|
-
// FIXME what about Fuel params?
|
|
243
|
-
let rawEventsTableName = "raw_events"
|
|
244
|
-
let eventSyncStateTableName = "event_sync_state"
|
|
245
|
-
|
|
246
260
|
// Constants for chunking
|
|
247
261
|
let maxItemsPerQuery = 500
|
|
248
262
|
|
|
249
263
|
let makeTableBatchSetQuery = (~pgSchema, ~table: Table.table, ~itemSchema: S.t<'item>) => {
|
|
250
264
|
let {dbSchema, hasArrayField} = table->Table.toSqlParams(~schema=itemSchema, ~pgSchema)
|
|
251
|
-
|
|
265
|
+
|
|
266
|
+
// Should move this to a better place
|
|
267
|
+
// We need it for the isRawEvents check in makeTableBatchSet
|
|
268
|
+
// to always apply the unnest optimization.
|
|
269
|
+
// This is needed, because even though it has JSON fields,
|
|
270
|
+
// they are always guaranteed to be an object.
|
|
271
|
+
// FIXME what about Fuel params?
|
|
272
|
+
let isRawEvents = table.tableName === InternalTable.RawEvents.table.tableName
|
|
252
273
|
|
|
253
274
|
// Should experiment how much it'll affect performance
|
|
254
275
|
// Although, it should be fine not to perform the validation check,
|
|
@@ -401,8 +422,7 @@ let setEntityHistoryOrThrow = (
|
|
|
401
422
|
~shouldCopyCurrentEntity=?,
|
|
402
423
|
~shouldRemoveInvalidUtf8=false,
|
|
403
424
|
) => {
|
|
404
|
-
rows
|
|
405
|
-
->Belt.Array.map(historyRow => {
|
|
425
|
+
rows->Belt.Array.map(historyRow => {
|
|
406
426
|
let row = historyRow->S.reverseConvertToJsonOrThrow(entityHistory.schema)
|
|
407
427
|
if shouldRemoveInvalidUtf8 {
|
|
408
428
|
[row]->removeInvalidUtf8InPlace
|
|
@@ -418,10 +438,19 @@ let setEntityHistoryOrThrow = (
|
|
|
418
438
|
!containsRollbackDiffChange
|
|
419
439
|
}
|
|
420
440
|
},
|
|
421
|
-
)
|
|
441
|
+
)->Promise.catch(exn => {
|
|
442
|
+
let reason = exn->Utils.prettifyExn
|
|
443
|
+
let detail = %raw(`reason?.detail || ""`)
|
|
444
|
+
raise(
|
|
445
|
+
Persistence.StorageError({
|
|
446
|
+
message: `Failed to insert history item into table "${entityHistory.table.tableName}".${detail !== ""
|
|
447
|
+
? ` Details: ${detail}`
|
|
448
|
+
: ""}`,
|
|
449
|
+
reason,
|
|
450
|
+
}),
|
|
451
|
+
)
|
|
452
|
+
})
|
|
422
453
|
})
|
|
423
|
-
->Promise.all
|
|
424
|
-
->(Utils.magic: promise<array<unit>> => promise<unit>)
|
|
425
454
|
}
|
|
426
455
|
|
|
427
456
|
type schemaTableName = {
|
|
@@ -539,12 +568,95 @@ let make = (
|
|
|
539
568
|
let isInitialized = async () => {
|
|
540
569
|
let envioTables =
|
|
541
570
|
await sql->Postgres.unsafe(
|
|
542
|
-
`SELECT table_schema FROM information_schema.tables WHERE table_schema = '${pgSchema}' AND table_name = '${
|
|
571
|
+
`SELECT table_schema FROM information_schema.tables WHERE table_schema = '${pgSchema}' AND table_name = '${InternalTable.EventSyncState.table.tableName}' OR table_name = '${InternalTable.Chains.table.tableName}';`,
|
|
543
572
|
)
|
|
544
573
|
envioTables->Utils.Array.notEmpty
|
|
545
574
|
}
|
|
546
575
|
|
|
547
|
-
let
|
|
576
|
+
let restoreEffectCache = async (~withUpload) => {
|
|
577
|
+
if withUpload {
|
|
578
|
+
// Try to restore cache tables from binary files
|
|
579
|
+
let nothingToUploadErrorMessage = "Nothing to upload."
|
|
580
|
+
|
|
581
|
+
switch await Promise.all2((
|
|
582
|
+
NodeJs.Fs.Promises.readdir(cacheDirPath)
|
|
583
|
+
->Promise.thenResolve(e => Ok(e))
|
|
584
|
+
->Promise.catch(_ => Promise.resolve(Error(nothingToUploadErrorMessage))),
|
|
585
|
+
getConnectedPsqlExec(~pgUser, ~pgHost, ~pgDatabase, ~pgPort),
|
|
586
|
+
)) {
|
|
587
|
+
| (Ok(entries), Ok(psqlExec)) => {
|
|
588
|
+
let cacheFiles = entries->Js.Array2.filter(entry => {
|
|
589
|
+
entry->Js.String2.endsWith(".tsv")
|
|
590
|
+
})
|
|
591
|
+
|
|
592
|
+
let _ =
|
|
593
|
+
await cacheFiles
|
|
594
|
+
->Js.Array2.map(entry => {
|
|
595
|
+
let effectName = entry->Js.String2.slice(~from=0, ~to_=-4) // Remove .tsv extension
|
|
596
|
+
let table = Internal.makeCacheTable(~effectName)
|
|
597
|
+
|
|
598
|
+
sql
|
|
599
|
+
->Postgres.unsafe(makeCreateTableQuery(table, ~pgSchema))
|
|
600
|
+
->Promise.then(() => {
|
|
601
|
+
let inputFile = NodeJs.Path.join(cacheDirPath, entry)->NodeJs.Path.toString
|
|
602
|
+
|
|
603
|
+
let command = `${psqlExec} -c 'COPY "${pgSchema}"."${table.tableName}" FROM STDIN WITH (FORMAT text, HEADER);' < ${inputFile}`
|
|
604
|
+
|
|
605
|
+
Promise.make(
|
|
606
|
+
(resolve, reject) => {
|
|
607
|
+
NodeJs.ChildProcess.execWithOptions(
|
|
608
|
+
command,
|
|
609
|
+
psqlExecOptions,
|
|
610
|
+
(~error, ~stdout, ~stderr as _) => {
|
|
611
|
+
switch error {
|
|
612
|
+
| Value(error) => reject(error)
|
|
613
|
+
| Null => resolve(stdout)
|
|
614
|
+
}
|
|
615
|
+
},
|
|
616
|
+
)
|
|
617
|
+
},
|
|
618
|
+
)
|
|
619
|
+
})
|
|
620
|
+
})
|
|
621
|
+
->Promise.all
|
|
622
|
+
|
|
623
|
+
Logging.info("Successfully uploaded cache.")
|
|
624
|
+
}
|
|
625
|
+
| (Error(message), _)
|
|
626
|
+
| (_, Error(message)) =>
|
|
627
|
+
if message === nothingToUploadErrorMessage {
|
|
628
|
+
Logging.info("No cache found to upload.")
|
|
629
|
+
} else {
|
|
630
|
+
Logging.error(`Failed to upload cache, continuing without it. ${message}`)
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
let cacheTableInfo: array<schemaCacheTableInfo> =
|
|
636
|
+
await sql->Postgres.unsafe(makeSchemaCacheTableInfoQuery(~pgSchema))
|
|
637
|
+
|
|
638
|
+
if withUpload && cacheTableInfo->Utils.Array.notEmpty {
|
|
639
|
+
// Integration with other tools like Hasura
|
|
640
|
+
switch onNewTables {
|
|
641
|
+
| Some(onNewTables) =>
|
|
642
|
+
await onNewTables(
|
|
643
|
+
~tableNames=cacheTableInfo->Js.Array2.map(info => {
|
|
644
|
+
info.tableName
|
|
645
|
+
}),
|
|
646
|
+
)
|
|
647
|
+
| None => ()
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
let cache = Js.Dict.empty()
|
|
652
|
+
cacheTableInfo->Js.Array2.forEach(({tableName, count}) => {
|
|
653
|
+
let effectName = tableName->Js.String2.sliceToEnd(~from=cacheTablePrefixLength)
|
|
654
|
+
cache->Js.Dict.set(effectName, ({effectName, count}: Persistence.effectCacheRecord))
|
|
655
|
+
})
|
|
656
|
+
cache
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
let initialize = async (~chainConfigs=[], ~entities=[], ~enums=[]): Persistence.initialState => {
|
|
548
660
|
let schemaTableNames: array<schemaTableName> =
|
|
549
661
|
await sql->Postgres.unsafe(makeSchemaTableNamesQuery(~pgSchema))
|
|
550
662
|
|
|
@@ -557,7 +669,11 @@ let make = (
|
|
|
557
669
|
schemaTableNames->Utils.Array.notEmpty &&
|
|
558
670
|
// Otherwise should throw if there's a table, but no envio specific one
|
|
559
671
|
// This means that the schema is used for something else than envio.
|
|
560
|
-
!(
|
|
672
|
+
!(
|
|
673
|
+
schemaTableNames->Js.Array2.some(table =>
|
|
674
|
+
table.tableName === InternalTable.EventSyncState.table.tableName
|
|
675
|
+
)
|
|
676
|
+
)
|
|
561
677
|
) {
|
|
562
678
|
Js.Exn.raiseError(
|
|
563
679
|
`Cannot run Envio migrations on PostgreSQL schema "${pgSchema}" because it contains non-Envio tables. Running migrations would delete all data in this schema.\n\nTo resolve this:\n1. If you want to use this schema, first backup any important data, then drop it with: "pnpm envio local db-migrate down"\n2. Or specify a different schema name by setting the "ENVIO_PG_PUBLIC_SCHEMA" environment variable\n3. Or manually drop the schema in your database if you're certain the data is not needed.`,
|
|
@@ -567,21 +683,31 @@ let make = (
|
|
|
567
683
|
let queries = makeInitializeTransaction(
|
|
568
684
|
~pgSchema,
|
|
569
685
|
~pgUser,
|
|
570
|
-
~generalTables,
|
|
571
686
|
~entities,
|
|
572
687
|
~enums,
|
|
688
|
+
~chainConfigs,
|
|
573
689
|
~isEmptyPgSchema=schemaTableNames->Utils.Array.isEmpty,
|
|
574
690
|
)
|
|
575
691
|
// Execute all queries within a single transaction for integrity
|
|
576
692
|
let _ = await sql->Postgres.beginSql(sql => {
|
|
577
|
-
|
|
693
|
+
// Promise.all might be not safe to use here,
|
|
694
|
+
// but it's just how it worked before.
|
|
695
|
+
Promise.all(queries->Js.Array2.map(query => sql->Postgres.unsafe(query)))
|
|
578
696
|
})
|
|
579
697
|
|
|
698
|
+
let cache = await restoreEffectCache(~withUpload=true)
|
|
699
|
+
|
|
580
700
|
// Integration with other tools like Hasura
|
|
581
701
|
switch onInitialize {
|
|
582
702
|
| Some(onInitialize) => await onInitialize()
|
|
583
703
|
| None => ()
|
|
584
704
|
}
|
|
705
|
+
|
|
706
|
+
{
|
|
707
|
+
cleanRun: true,
|
|
708
|
+
cache,
|
|
709
|
+
chains: chainConfigs->Js.Array2.map(InternalTable.Chains.initialFromConfig),
|
|
710
|
+
}
|
|
585
711
|
}
|
|
586
712
|
|
|
587
713
|
let loadByIdsOrThrow = async (~ids, ~table: Table.table, ~rowsSchema) => {
|
|
@@ -767,97 +893,31 @@ let make = (
|
|
|
767
893
|
}
|
|
768
894
|
}
|
|
769
895
|
|
|
770
|
-
let
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
getConnectedPsqlExec(~pgUser, ~pgHost, ~pgDatabase, ~pgPort),
|
|
780
|
-
)) {
|
|
781
|
-
| (Ok(entries), Ok(psqlExec)) => {
|
|
782
|
-
let cacheFiles = entries->Js.Array2.filter(entry => {
|
|
783
|
-
entry->Js.String2.endsWith(".tsv")
|
|
784
|
-
})
|
|
785
|
-
|
|
786
|
-
let _ =
|
|
787
|
-
await cacheFiles
|
|
788
|
-
->Js.Array2.map(entry => {
|
|
789
|
-
let effectName = entry->Js.String2.slice(~from=0, ~to_=-4) // Remove .tsv extension
|
|
790
|
-
let table = Internal.makeCacheTable(~effectName)
|
|
791
|
-
|
|
792
|
-
sql
|
|
793
|
-
->Postgres.unsafe(makeCreateTableQuery(table, ~pgSchema))
|
|
794
|
-
->Promise.then(() => {
|
|
795
|
-
let inputFile = NodeJs.Path.join(cacheDirPath, entry)->NodeJs.Path.toString
|
|
796
|
-
|
|
797
|
-
let command = `${psqlExec} -c 'COPY "${pgSchema}"."${table.tableName}" FROM STDIN WITH (FORMAT text, HEADER);' < ${inputFile}`
|
|
798
|
-
|
|
799
|
-
Promise.make(
|
|
800
|
-
(resolve, reject) => {
|
|
801
|
-
NodeJs.ChildProcess.execWithOptions(
|
|
802
|
-
command,
|
|
803
|
-
psqlExecOptions,
|
|
804
|
-
(~error, ~stdout, ~stderr as _) => {
|
|
805
|
-
switch error {
|
|
806
|
-
| Value(error) => reject(error)
|
|
807
|
-
| Null => resolve(stdout)
|
|
808
|
-
}
|
|
809
|
-
},
|
|
810
|
-
)
|
|
811
|
-
},
|
|
812
|
-
)
|
|
813
|
-
})
|
|
814
|
-
})
|
|
815
|
-
->Promise.all
|
|
816
|
-
|
|
817
|
-
Logging.info("Successfully uploaded cache.")
|
|
818
|
-
}
|
|
819
|
-
| (Error(message), _)
|
|
820
|
-
| (_, Error(message)) =>
|
|
821
|
-
if message === nothingToUploadErrorMessage {
|
|
822
|
-
Logging.info("No cache found to upload.")
|
|
823
|
-
} else {
|
|
824
|
-
Logging.error(`Failed to upload cache, continuing without it. ${message}`)
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
let cacheTableInfo: array<schemaCacheTableInfo> =
|
|
830
|
-
await sql->Postgres.unsafe(makeSchemaCacheTableInfoQuery(~pgSchema))
|
|
896
|
+
let loadInitialState = async (): Persistence.initialState => {
|
|
897
|
+
let (cache, chains) = await Promise.all2((
|
|
898
|
+
restoreEffectCache(~withUpload=false),
|
|
899
|
+
sql
|
|
900
|
+
->Postgres.unsafe(
|
|
901
|
+
makeLoadAllQuery(~pgSchema, ~tableName=InternalTable.Chains.table.tableName),
|
|
902
|
+
)
|
|
903
|
+
->(Utils.magic: promise<array<unknown>> => promise<array<InternalTable.Chains.t>>),
|
|
904
|
+
))
|
|
831
905
|
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
await onNewTables(
|
|
837
|
-
~tableNames=cacheTableInfo->Js.Array2.map(info => {
|
|
838
|
-
info.tableName
|
|
839
|
-
}),
|
|
840
|
-
)
|
|
841
|
-
| None => ()
|
|
842
|
-
}
|
|
906
|
+
{
|
|
907
|
+
cleanRun: false,
|
|
908
|
+
cache,
|
|
909
|
+
chains,
|
|
843
910
|
}
|
|
844
|
-
|
|
845
|
-
cacheTableInfo->Js.Array2.map((info): Persistence.effectCacheRecord => {
|
|
846
|
-
{
|
|
847
|
-
effectName: info.tableName->Js.String2.sliceToEnd(~from=cacheTablePrefixLength),
|
|
848
|
-
count: info.count,
|
|
849
|
-
}
|
|
850
|
-
})
|
|
851
911
|
}
|
|
852
912
|
|
|
853
913
|
{
|
|
854
914
|
isInitialized,
|
|
855
915
|
initialize,
|
|
916
|
+
loadInitialState,
|
|
856
917
|
loadByFieldOrThrow,
|
|
857
918
|
loadByIdsOrThrow,
|
|
858
919
|
setOrThrow,
|
|
859
920
|
setEffectCacheOrThrow,
|
|
860
921
|
dumpEffectCache,
|
|
861
|
-
restoreEffectCache,
|
|
862
922
|
}
|
|
863
923
|
}
|