@thru/indexer 0.2.21 → 0.2.23

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