@thru/indexer 0.2.20 → 0.2.22
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/README.md +3 -3
- package/dist/index.cjs +6 -308
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -206
- package/dist/index.d.ts +2 -206
- package/dist/index.mjs +16 -309
- package/dist/index.mjs.map +1 -1
- package/package.json +11 -14
package/dist/index.mjs
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { pgTable, timestamp, text, bigint, boolean, integer, index } from 'drizzle-orm/pg-core';
|
|
2
|
-
import { z
|
|
2
|
+
import { z } from 'zod';
|
|
3
3
|
import { createConsoleLogger, createEventReplay, createAccountsByOwnerReplay, AccountView } from '@thru/replay';
|
|
4
|
-
import { eq,
|
|
5
|
-
import { encodeAddress } from '@thru/helpers';
|
|
6
|
-
import { z, OpenAPIHono, createRoute } from '@hono/zod-openapi';
|
|
7
|
-
import { createSelectSchema, createInsertSchema } from 'drizzle-zod';
|
|
4
|
+
import { eq, sql } from 'drizzle-orm';
|
|
5
|
+
import { encodeAddress } from '@thru/sdk/helpers';
|
|
8
6
|
|
|
9
7
|
// src/schema/builder.ts
|
|
10
8
|
function createBuilder(state) {
|
|
@@ -162,29 +160,29 @@ function generateZodSchema(schema) {
|
|
|
162
160
|
let zodType;
|
|
163
161
|
switch (colDef._columnType) {
|
|
164
162
|
case "text":
|
|
165
|
-
zodType = z
|
|
163
|
+
zodType = z.string();
|
|
166
164
|
break;
|
|
167
165
|
case "bigint":
|
|
168
|
-
zodType = z
|
|
166
|
+
zodType = z.bigint();
|
|
169
167
|
break;
|
|
170
168
|
case "integer":
|
|
171
|
-
zodType = z
|
|
169
|
+
zodType = z.number().int();
|
|
172
170
|
break;
|
|
173
171
|
case "boolean":
|
|
174
|
-
zodType = z
|
|
172
|
+
zodType = z.boolean();
|
|
175
173
|
break;
|
|
176
174
|
case "timestamp":
|
|
177
|
-
zodType = z
|
|
175
|
+
zodType = z.date();
|
|
178
176
|
break;
|
|
179
177
|
default:
|
|
180
|
-
zodType = z
|
|
178
|
+
zodType = z.unknown();
|
|
181
179
|
}
|
|
182
180
|
if (colDef._nullable) {
|
|
183
181
|
zodType = zodType.nullable();
|
|
184
182
|
}
|
|
185
183
|
shape[key] = zodType;
|
|
186
184
|
}
|
|
187
|
-
return z
|
|
185
|
+
return z.object(shape);
|
|
188
186
|
}
|
|
189
187
|
function validateParsedData(schema, data, streamName) {
|
|
190
188
|
const zodSchema = generateZodSchema(schema);
|
|
@@ -192,7 +190,7 @@ function validateParsedData(schema, data, streamName) {
|
|
|
192
190
|
if (result.success) {
|
|
193
191
|
return { success: true, data: result.data };
|
|
194
192
|
}
|
|
195
|
-
const errorMessages = result.error.
|
|
193
|
+
const errorMessages = result.error.issues.map((e) => ` - ${e.path.join(".")}: ${e.message}`).join("\n");
|
|
196
194
|
return {
|
|
197
195
|
success: false,
|
|
198
196
|
error: `Stream "${streamName}" parse returned invalid data:
|
|
@@ -287,18 +285,18 @@ async function getAllCheckpoints(db) {
|
|
|
287
285
|
// src/checkpoint/index.ts
|
|
288
286
|
function getSchemaExports(config) {
|
|
289
287
|
const { eventStreams = [], accountStreams = [], tableNames = {} } = config;
|
|
290
|
-
const exports
|
|
288
|
+
const exports = {
|
|
291
289
|
checkpointTable
|
|
292
290
|
};
|
|
293
291
|
for (const stream of eventStreams) {
|
|
294
292
|
const exportName = tableNames[stream.name] ?? `${stream.name}Table`;
|
|
295
|
-
exports
|
|
293
|
+
exports[exportName] = stream.table;
|
|
296
294
|
}
|
|
297
295
|
for (const stream of accountStreams) {
|
|
298
296
|
const exportName = tableNames[stream.name] ?? `${stream.name.replace(/-/g, "")}Table`;
|
|
299
|
-
exports
|
|
297
|
+
exports[exportName] = stream.table;
|
|
300
298
|
}
|
|
301
|
-
return exports
|
|
299
|
+
return exports;
|
|
302
300
|
}
|
|
303
301
|
|
|
304
302
|
// src/streams/processor.ts
|
|
@@ -742,297 +740,6 @@ async function runAccountStreamProcessor(stream, options, abortSignal) {
|
|
|
742
740
|
);
|
|
743
741
|
return stats;
|
|
744
742
|
}
|
|
745
|
-
var paginationQuerySchema = z.object({
|
|
746
|
-
limit: z.coerce.number().int().min(1).max(100).default(20).openapi({
|
|
747
|
-
description: "Number of results to return (1-100)",
|
|
748
|
-
example: 20
|
|
749
|
-
}),
|
|
750
|
-
offset: z.coerce.number().int().min(0).default(0).openapi({
|
|
751
|
-
description: "Number of results to skip",
|
|
752
|
-
example: 0
|
|
753
|
-
}),
|
|
754
|
-
cursor: z.string().optional().openapi({
|
|
755
|
-
description: "Cursor for pagination (format: slot:id)",
|
|
756
|
-
example: "3181195:abc123"
|
|
757
|
-
})
|
|
758
|
-
});
|
|
759
|
-
var paginationResponseSchema = z.object({
|
|
760
|
-
limit: z.number().openapi({ example: 20 }),
|
|
761
|
-
offset: z.number().openapi({ example: 0 }),
|
|
762
|
-
hasMore: z.boolean().openapi({ example: true }),
|
|
763
|
-
nextCursor: z.string().nullable().openapi({ example: "3181195:abc123" })
|
|
764
|
-
});
|
|
765
|
-
function dataResponse(schema) {
|
|
766
|
-
return z.object({ data: schema });
|
|
767
|
-
}
|
|
768
|
-
function listResponse(schema) {
|
|
769
|
-
return z.object({
|
|
770
|
-
data: z.array(schema),
|
|
771
|
-
pagination: paginationResponseSchema
|
|
772
|
-
});
|
|
773
|
-
}
|
|
774
|
-
var errorSchema = z.object({
|
|
775
|
-
error: z.string().openapi({ example: "Not found" }),
|
|
776
|
-
code: z.string().optional().openapi({ example: "NOT_FOUND" })
|
|
777
|
-
}).openapi("Error");
|
|
778
|
-
function paginate(rows, query, getCursor) {
|
|
779
|
-
const hasMore = rows.length > query.limit;
|
|
780
|
-
const data = hasMore ? rows.slice(0, -1) : rows;
|
|
781
|
-
const lastItem = data[data.length - 1];
|
|
782
|
-
let nextCursor = null;
|
|
783
|
-
if (hasMore && lastItem) {
|
|
784
|
-
if (getCursor) {
|
|
785
|
-
nextCursor = getCursor(lastItem);
|
|
786
|
-
} else {
|
|
787
|
-
const item = lastItem;
|
|
788
|
-
if (item.slot !== void 0 && item.id !== void 0) {
|
|
789
|
-
nextCursor = `${item.slot}:${item.id}`;
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
return {
|
|
794
|
-
data,
|
|
795
|
-
pagination: {
|
|
796
|
-
limit: query.limit,
|
|
797
|
-
offset: query.offset,
|
|
798
|
-
hasMore,
|
|
799
|
-
nextCursor
|
|
800
|
-
}
|
|
801
|
-
};
|
|
802
|
-
}
|
|
803
|
-
function parseCursor(cursor) {
|
|
804
|
-
const colonIndex = cursor.indexOf(":");
|
|
805
|
-
if (colonIndex === -1) return null;
|
|
806
|
-
try {
|
|
807
|
-
const slot = BigInt(cursor.slice(0, colonIndex));
|
|
808
|
-
const id = cursor.slice(colonIndex + 1);
|
|
809
|
-
return { slot, id };
|
|
810
|
-
} catch {
|
|
811
|
-
return null;
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
function pascalCase3(str) {
|
|
815
|
-
return str.split(/[-_\s]+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
|
|
816
|
-
}
|
|
817
|
-
function generateSchemas(table, name, suffix = "") {
|
|
818
|
-
const rowSchema = createSelectSchema(table);
|
|
819
|
-
const insertSchema = createInsertSchema(table);
|
|
820
|
-
const apiFields = {};
|
|
821
|
-
const columns = getTableColumns(table);
|
|
822
|
-
for (const [colName, col] of Object.entries(columns)) {
|
|
823
|
-
const dataType = col.dataType;
|
|
824
|
-
const notNull = col.notNull;
|
|
825
|
-
let fieldSchema;
|
|
826
|
-
switch (dataType) {
|
|
827
|
-
case "bigint":
|
|
828
|
-
fieldSchema = z.string().openapi({ description: `${colName} (bigint)` });
|
|
829
|
-
break;
|
|
830
|
-
case "date":
|
|
831
|
-
fieldSchema = z.string().openapi({ description: `${colName} (ISO timestamp)` });
|
|
832
|
-
break;
|
|
833
|
-
case "string":
|
|
834
|
-
fieldSchema = z.string();
|
|
835
|
-
break;
|
|
836
|
-
case "number":
|
|
837
|
-
fieldSchema = z.number();
|
|
838
|
-
break;
|
|
839
|
-
case "boolean":
|
|
840
|
-
fieldSchema = z.boolean();
|
|
841
|
-
break;
|
|
842
|
-
default:
|
|
843
|
-
fieldSchema = z.any();
|
|
844
|
-
}
|
|
845
|
-
if (!notNull) {
|
|
846
|
-
fieldSchema = fieldSchema.nullable();
|
|
847
|
-
}
|
|
848
|
-
apiFields[colName] = fieldSchema;
|
|
849
|
-
}
|
|
850
|
-
const schemaName = suffix ? `${pascalCase3(name)}${suffix}` : pascalCase3(name);
|
|
851
|
-
const apiSchema = z.object(apiFields).openapi(schemaName);
|
|
852
|
-
const serialize = (row) => {
|
|
853
|
-
const result = {};
|
|
854
|
-
for (const [key, value] of Object.entries(row)) {
|
|
855
|
-
if (typeof value === "bigint") {
|
|
856
|
-
result[key] = value.toString();
|
|
857
|
-
} else if (value instanceof Date) {
|
|
858
|
-
result[key] = value.toISOString();
|
|
859
|
-
} else {
|
|
860
|
-
result[key] = value;
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
return result;
|
|
864
|
-
};
|
|
865
|
-
return {
|
|
866
|
-
row: rowSchema,
|
|
867
|
-
insert: insertSchema,
|
|
868
|
-
api: apiSchema,
|
|
869
|
-
serialize
|
|
870
|
-
};
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
// src/api/routes.ts
|
|
874
|
-
function pascalCase4(str) {
|
|
875
|
-
return str.split(/[-_\s]+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
|
|
876
|
-
}
|
|
877
|
-
function buildRoutes(config) {
|
|
878
|
-
const {
|
|
879
|
-
db,
|
|
880
|
-
name,
|
|
881
|
-
table,
|
|
882
|
-
schemas,
|
|
883
|
-
filters,
|
|
884
|
-
idField,
|
|
885
|
-
resourceType,
|
|
886
|
-
includeSlotFilters,
|
|
887
|
-
sortField,
|
|
888
|
-
secondarySortField
|
|
889
|
-
} = config;
|
|
890
|
-
const router = new OpenAPIHono();
|
|
891
|
-
const tag = pascalCase4(name);
|
|
892
|
-
const filterFields = {};
|
|
893
|
-
for (const field of filters) {
|
|
894
|
-
filterFields[field] = z.string().optional().openapi({
|
|
895
|
-
description: `Filter by ${field}`
|
|
896
|
-
});
|
|
897
|
-
}
|
|
898
|
-
if (includeSlotFilters) {
|
|
899
|
-
filterFields.fromSlot = z.string().optional().openapi({
|
|
900
|
-
description: "Minimum slot number"
|
|
901
|
-
});
|
|
902
|
-
filterFields.toSlot = z.string().optional().openapi({
|
|
903
|
-
description: "Maximum slot number"
|
|
904
|
-
});
|
|
905
|
-
}
|
|
906
|
-
const listQuerySchema = paginationQuerySchema.extend(filterFields);
|
|
907
|
-
const listRoute = createRoute({
|
|
908
|
-
method: "get",
|
|
909
|
-
path: "/",
|
|
910
|
-
tags: [tag],
|
|
911
|
-
summary: `List ${name} ${resourceType}s`,
|
|
912
|
-
description: `Returns a paginated list of ${name} ${resourceType}s with optional filtering.`,
|
|
913
|
-
request: { query: listQuerySchema },
|
|
914
|
-
responses: {
|
|
915
|
-
200: {
|
|
916
|
-
description: `List of ${name} ${resourceType}s`,
|
|
917
|
-
content: { "application/json": { schema: listResponse(schemas.api) } }
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
|
-
});
|
|
921
|
-
router.openapi(listRoute, async (c) => {
|
|
922
|
-
const query = c.req.valid("query");
|
|
923
|
-
const conditions = [];
|
|
924
|
-
for (const field of filters) {
|
|
925
|
-
const value = query[field];
|
|
926
|
-
if (value !== void 0 && value !== "") {
|
|
927
|
-
conditions.push(eq(table[field], value));
|
|
928
|
-
}
|
|
929
|
-
}
|
|
930
|
-
if (includeSlotFilters) {
|
|
931
|
-
if (query.fromSlot) {
|
|
932
|
-
conditions.push(gte(table.slot, BigInt(query.fromSlot)));
|
|
933
|
-
}
|
|
934
|
-
if (query.toSlot) {
|
|
935
|
-
conditions.push(lte(table.slot, BigInt(query.toSlot)));
|
|
936
|
-
}
|
|
937
|
-
}
|
|
938
|
-
if (query.cursor && secondarySortField) {
|
|
939
|
-
const parsed = parseCursor(query.cursor);
|
|
940
|
-
if (parsed) {
|
|
941
|
-
conditions.push(
|
|
942
|
-
sql`(${table.slot} < ${parsed.slot} OR (${table.slot} = ${parsed.slot} AND ${table[secondarySortField]} < ${parsed.id}))`
|
|
943
|
-
);
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
const orderBy = secondarySortField ? [desc(table[sortField]), desc(table[secondarySortField])] : [desc(table[sortField])];
|
|
947
|
-
const rows = await db.select().from(table).where(conditions.length > 0 ? and(...conditions) : void 0).orderBy(...orderBy).limit(query.limit + 1).offset(query.cursor ? 0 : query.offset);
|
|
948
|
-
const result = paginate(rows, query);
|
|
949
|
-
return c.json({
|
|
950
|
-
data: result.data.map(
|
|
951
|
-
(row) => schemas.serialize(row)
|
|
952
|
-
),
|
|
953
|
-
pagination: result.pagination
|
|
954
|
-
});
|
|
955
|
-
});
|
|
956
|
-
const getRoute = createRoute({
|
|
957
|
-
method: "get",
|
|
958
|
-
path: `/{${idField}}`,
|
|
959
|
-
tags: [tag],
|
|
960
|
-
summary: `Get ${name} ${resourceType} by ${idField}`,
|
|
961
|
-
description: `Returns a single ${name} ${resourceType} by its ${idField}.`,
|
|
962
|
-
request: {
|
|
963
|
-
params: z.object({
|
|
964
|
-
[idField]: z.string().openapi({ description: `${pascalCase4(resourceType)} ${idField}` })
|
|
965
|
-
})
|
|
966
|
-
},
|
|
967
|
-
responses: {
|
|
968
|
-
200: {
|
|
969
|
-
description: `${pascalCase4(name)} ${resourceType} found`,
|
|
970
|
-
content: { "application/json": { schema: dataResponse(schemas.api) } }
|
|
971
|
-
},
|
|
972
|
-
404: {
|
|
973
|
-
description: `${pascalCase4(resourceType)} not found`,
|
|
974
|
-
content: { "application/json": { schema: errorSchema } }
|
|
975
|
-
}
|
|
976
|
-
}
|
|
977
|
-
});
|
|
978
|
-
router.openapi(getRoute, async (c) => {
|
|
979
|
-
const id = c.req.param(idField);
|
|
980
|
-
const [row] = await db.select().from(table).where(eq(table[idField], id)).limit(1);
|
|
981
|
-
if (!row) {
|
|
982
|
-
return c.json({ error: "Not found" }, 404);
|
|
983
|
-
}
|
|
984
|
-
return c.json({
|
|
985
|
-
data: schemas.serialize(row)
|
|
986
|
-
});
|
|
987
|
-
});
|
|
988
|
-
return router;
|
|
989
|
-
}
|
|
990
|
-
function mountStreamRoutes(app, options) {
|
|
991
|
-
const {
|
|
992
|
-
db,
|
|
993
|
-
eventStreams = [],
|
|
994
|
-
accountStreams = [],
|
|
995
|
-
pathPrefix = "/api/v1"
|
|
996
|
-
} = options;
|
|
997
|
-
for (const stream of eventStreams) {
|
|
998
|
-
if (stream.api?.enabled === false) continue;
|
|
999
|
-
const schemas = generateSchemas(stream.table, stream.name, "Event");
|
|
1000
|
-
const filters = stream.api?.filters ?? [];
|
|
1001
|
-
const idField = stream.api?.idField ?? "id";
|
|
1002
|
-
const router = buildRoutes({
|
|
1003
|
-
db,
|
|
1004
|
-
name: stream.name,
|
|
1005
|
-
table: stream.table,
|
|
1006
|
-
schemas,
|
|
1007
|
-
filters,
|
|
1008
|
-
idField,
|
|
1009
|
-
resourceType: "event",
|
|
1010
|
-
includeSlotFilters: true,
|
|
1011
|
-
sortField: "slot",
|
|
1012
|
-
secondarySortField: "id"
|
|
1013
|
-
});
|
|
1014
|
-
app.route(`${pathPrefix}/${stream.name}`, router);
|
|
1015
|
-
}
|
|
1016
|
-
for (const stream of accountStreams) {
|
|
1017
|
-
if (stream.api?.enabled === false) continue;
|
|
1018
|
-
const schemas = generateSchemas(stream.table, stream.name, "Account");
|
|
1019
|
-
const filters = stream.api?.filters ?? [];
|
|
1020
|
-
const idField = stream.api?.idField ?? "address";
|
|
1021
|
-
const router = buildRoutes({
|
|
1022
|
-
db,
|
|
1023
|
-
name: stream.name,
|
|
1024
|
-
table: stream.table,
|
|
1025
|
-
schemas,
|
|
1026
|
-
filters,
|
|
1027
|
-
idField,
|
|
1028
|
-
resourceType: "account",
|
|
1029
|
-
includeSlotFilters: false,
|
|
1030
|
-
sortField: "updatedAt",
|
|
1031
|
-
secondarySortField: void 0
|
|
1032
|
-
});
|
|
1033
|
-
app.route(`${pathPrefix}/${stream.name}`, router);
|
|
1034
|
-
}
|
|
1035
|
-
}
|
|
1036
743
|
var Indexer = class {
|
|
1037
744
|
config;
|
|
1038
745
|
abortController = null;
|
|
@@ -1223,6 +930,6 @@ var Indexer = class {
|
|
|
1223
930
|
}
|
|
1224
931
|
};
|
|
1225
932
|
|
|
1226
|
-
export { Indexer, checkpointTable, columnBuilder,
|
|
933
|
+
export { Indexer, checkpointTable, columnBuilder, defineAccountStream, defineEventStream, deleteCheckpoint, generateZodSchema, getAllCheckpoints, getCheckpoint, getSchemaExports, t, updateCheckpoint, validateParsedData };
|
|
1227
934
|
//# sourceMappingURL=index.mjs.map
|
|
1228
935
|
//# sourceMappingURL=index.mjs.map
|