@xyo-network/chain-reward-redemption 1.15.10

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.
Files changed (120) hide show
  1. package/LICENSE +165 -0
  2. package/README.md +96 -0
  3. package/dist/node/index.d.ts +2 -0
  4. package/dist/node/index.d.ts.map +1 -0
  5. package/dist/node/index.mjs +461 -0
  6. package/dist/node/index.mjs.map +1 -0
  7. package/dist/node/manifest/getLocator.d.ts +14 -0
  8. package/dist/node/manifest/getLocator.d.ts.map +1 -0
  9. package/dist/node/manifest/getNode.d.ts +15 -0
  10. package/dist/node/manifest/getNode.d.ts.map +1 -0
  11. package/dist/node/manifest/index.d.ts +6 -0
  12. package/dist/node/manifest/index.d.ts.map +1 -0
  13. package/dist/node/manifest/nodeManifest.d.ts +6 -0
  14. package/dist/node/manifest/nodeManifest.d.ts.map +1 -0
  15. package/dist/node/manifest/private/index.d.ts +5 -0
  16. package/dist/node/manifest/private/index.d.ts.map +1 -0
  17. package/dist/node/manifest/public/index.d.ts +6 -0
  18. package/dist/node/manifest/public/index.d.ts.map +1 -0
  19. package/dist/node/server/app.d.ts +4 -0
  20. package/dist/node/server/app.d.ts.map +1 -0
  21. package/dist/node/server/index.d.ts +11 -0
  22. package/dist/node/server/index.d.ts.map +1 -0
  23. package/dist/node/server/instrumentation.d.ts +9 -0
  24. package/dist/node/server/instrumentation.d.ts.map +1 -0
  25. package/dist/node/server/routes/addRoutes.d.ts +3 -0
  26. package/dist/node/server/routes/addRoutes.d.ts.map +1 -0
  27. package/dist/node/server/routes/address/AddressPathParams.d.ts +4 -0
  28. package/dist/node/server/routes/address/AddressPathParams.d.ts.map +1 -0
  29. package/dist/node/server/routes/address/addNodeRoutes.d.ts +3 -0
  30. package/dist/node/server/routes/address/addNodeRoutes.d.ts.map +1 -0
  31. package/dist/node/server/routes/address/get/get.d.ts +4 -0
  32. package/dist/node/server/routes/address/get/get.d.ts.map +1 -0
  33. package/dist/node/server/routes/address/get/index.d.ts +2 -0
  34. package/dist/node/server/routes/address/get/index.d.ts.map +1 -0
  35. package/dist/node/server/routes/address/index.d.ts +2 -0
  36. package/dist/node/server/routes/address/index.d.ts.map +1 -0
  37. package/dist/node/server/routes/address/post/getQueryConfig.d.ts +6 -0
  38. package/dist/node/server/routes/address/post/getQueryConfig.d.ts.map +1 -0
  39. package/dist/node/server/routes/address/post/index.d.ts +2 -0
  40. package/dist/node/server/routes/address/post/index.d.ts.map +1 -0
  41. package/dist/node/server/routes/address/post/post.d.ts +8 -0
  42. package/dist/node/server/routes/address/post/post.d.ts.map +1 -0
  43. package/dist/node/server/routes/dataLake/addDataLakeRoutes.d.ts +3 -0
  44. package/dist/node/server/routes/dataLake/addDataLakeRoutes.d.ts.map +1 -0
  45. package/dist/node/server/routes/dataLake/archivistMiddleware.d.ts +10 -0
  46. package/dist/node/server/routes/dataLake/archivistMiddleware.d.ts.map +1 -0
  47. package/dist/node/server/routes/dataLake/index.d.ts +2 -0
  48. package/dist/node/server/routes/dataLake/index.d.ts.map +1 -0
  49. package/dist/node/server/routes/healthz/get.d.ts +4 -0
  50. package/dist/node/server/routes/healthz/get.d.ts.map +1 -0
  51. package/dist/node/server/routes/healthz/index.d.ts +2 -0
  52. package/dist/node/server/routes/healthz/index.d.ts.map +1 -0
  53. package/dist/node/server/routes/index.d.ts +4 -0
  54. package/dist/node/server/routes/index.d.ts.map +1 -0
  55. package/dist/node/server/routes/rewardRedemption/addRewardRoutes.d.ts +3 -0
  56. package/dist/node/server/routes/rewardRedemption/addRewardRoutes.d.ts.map +1 -0
  57. package/dist/node/server/routes/rewardRedemption/index.d.ts +2 -0
  58. package/dist/node/server/routes/rewardRedemption/index.d.ts.map +1 -0
  59. package/dist/node/server/routes/rewardRedemption/middleware/index.d.ts +2 -0
  60. package/dist/node/server/routes/rewardRedemption/middleware/index.d.ts.map +1 -0
  61. package/dist/node/server/routes/rewardRedemption/middleware/requestHandlerValidator.d.ts +32 -0
  62. package/dist/node/server/routes/rewardRedemption/middleware/requestHandlerValidator.d.ts.map +1 -0
  63. package/dist/node/server/routes/rewardRedemption/routeDefinitions/getRouteDefinitions.d.ts +3 -0
  64. package/dist/node/server/routes/rewardRedemption/routeDefinitions/getRouteDefinitions.d.ts.map +1 -0
  65. package/dist/node/server/routes/rewardRedemption/routeDefinitions/index.d.ts +2 -0
  66. package/dist/node/server/routes/rewardRedemption/routeDefinitions/index.d.ts.map +1 -0
  67. package/dist/node/server/routes/rewardRedemption/routeDefinitions/pathParams/AddressPathParam.d.ts +2 -0
  68. package/dist/node/server/routes/rewardRedemption/routeDefinitions/pathParams/AddressPathParam.d.ts.map +1 -0
  69. package/dist/node/server/routes/rewardRedemption/routeDefinitions/pathParams/index.d.ts +2 -0
  70. package/dist/node/server/routes/rewardRedemption/routeDefinitions/pathParams/index.d.ts.map +1 -0
  71. package/dist/node/server/routes/rewardRedemption/routeDefinitions/routeDefinition.d.ts +8 -0
  72. package/dist/node/server/routes/rewardRedemption/routeDefinitions/routeDefinition.d.ts.map +1 -0
  73. package/dist/node/server/routes/rewardRedemption/routeDefinitions/routes/claim.d.ts +3 -0
  74. package/dist/node/server/routes/rewardRedemption/routeDefinitions/routes/claim.d.ts.map +1 -0
  75. package/dist/node/server/routes/rewardRedemption/routeDefinitions/routes/index.d.ts +3 -0
  76. package/dist/node/server/routes/rewardRedemption/routeDefinitions/routes/index.d.ts.map +1 -0
  77. package/dist/node/server/routes/rewardRedemption/routeDefinitions/routes/redeem.d.ts +3 -0
  78. package/dist/node/server/routes/rewardRedemption/routeDefinitions/routes/redeem.d.ts.map +1 -0
  79. package/dist/node/server/server.d.ts +11 -0
  80. package/dist/node/server/server.d.ts.map +1 -0
  81. package/package.json +137 -0
  82. package/src/index.ts +1 -0
  83. package/src/manifest/getLocator.ts +75 -0
  84. package/src/manifest/getNode.ts +32 -0
  85. package/src/manifest/index.ts +5 -0
  86. package/src/manifest/node.json +32 -0
  87. package/src/manifest/nodeManifest.ts +8 -0
  88. package/src/manifest/private/index.ts +4 -0
  89. package/src/manifest/public/index.ts +6 -0
  90. package/src/server/app.ts +30 -0
  91. package/src/server/index.ts +13 -0
  92. package/src/server/instrumentation.ts +15 -0
  93. package/src/server/routes/addRoutes.ts +9 -0
  94. package/src/server/routes/address/AddressPathParams.ts +3 -0
  95. package/src/server/routes/address/addNodeRoutes.ts +21 -0
  96. package/src/server/routes/address/get/get.ts +33 -0
  97. package/src/server/routes/address/get/index.ts +1 -0
  98. package/src/server/routes/address/index.ts +1 -0
  99. package/src/server/routes/address/post/getQueryConfig.ts +23 -0
  100. package/src/server/routes/address/post/index.ts +1 -0
  101. package/src/server/routes/address/post/post.ts +77 -0
  102. package/src/server/routes/dataLake/addDataLakeRoutes.ts +9 -0
  103. package/src/server/routes/dataLake/archivistMiddleware.ts +86 -0
  104. package/src/server/routes/dataLake/index.ts +1 -0
  105. package/src/server/routes/healthz/get.ts +20 -0
  106. package/src/server/routes/healthz/index.ts +1 -0
  107. package/src/server/routes/index.ts +3 -0
  108. package/src/server/routes/rewardRedemption/addRewardRoutes.ts +10 -0
  109. package/src/server/routes/rewardRedemption/index.ts +1 -0
  110. package/src/server/routes/rewardRedemption/middleware/index.ts +1 -0
  111. package/src/server/routes/rewardRedemption/middleware/requestHandlerValidator.ts +120 -0
  112. package/src/server/routes/rewardRedemption/routeDefinitions/getRouteDefinitions.ts +9 -0
  113. package/src/server/routes/rewardRedemption/routeDefinitions/index.ts +1 -0
  114. package/src/server/routes/rewardRedemption/routeDefinitions/pathParams/AddressPathParam.ts +1 -0
  115. package/src/server/routes/rewardRedemption/routeDefinitions/pathParams/index.ts +1 -0
  116. package/src/server/routes/rewardRedemption/routeDefinitions/routeDefinition.ts +18 -0
  117. package/src/server/routes/rewardRedemption/routeDefinitions/routes/claim.ts +31 -0
  118. package/src/server/routes/rewardRedemption/routeDefinitions/routes/index.ts +2 -0
  119. package/src/server/routes/rewardRedemption/routeDefinitions/routes/redeem.ts +26 -0
  120. package/src/server/server.ts +58 -0
@@ -0,0 +1,8 @@
1
+ import type { PackageManifestPayload } from '@xyo-network/manifest-model'
2
+
3
+ import node from './node.json' with { type: 'json' }
4
+
5
+ /**
6
+ * Root Node Manifest
7
+ */
8
+ export const NodeManifest = node as PackageManifestPayload
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Private Child Manifests
3
+ */
4
+ export const PrivateChildManifests = []
@@ -0,0 +1,6 @@
1
+ import type { ModuleManifest, PackageManifestPayload } from '@xyo-network/manifest-model'
2
+
3
+ /**
4
+ * Public Child Manifests
5
+ */
6
+ export const PublicChildManifests: ModuleManifest[] = []
@@ -0,0 +1,30 @@
1
+ import {
2
+ customPoweredByHeader, disableCaseSensitiveRouting, disableExpressDefaultPoweredByHeader, getJsonBodyParser, getJsonBodyParserOptions, responseProfiler,
3
+ standardErrors, standardResponses,
4
+ } from '@xylabs/express'
5
+ import type { NodeInstance } from '@xyo-network/node-model'
6
+ import compression from 'compression'
7
+ import cors from 'cors'
8
+ import type { Express } from 'express'
9
+ import express from 'express'
10
+
11
+ import { addInstrumentation } from './instrumentation.ts'
12
+ import { addRoutes } from './routes/index.ts'
13
+
14
+ export const getApp = (node: NodeInstance): Express => {
15
+ addInstrumentation()
16
+ const app = express()
17
+ app.set('etag', false)
18
+ app.use(cors())
19
+ app.use(compression())
20
+ app.use(responseProfiler)
21
+ app.use(getJsonBodyParser(getJsonBodyParserOptions({ limit: '1mb' })))
22
+ app.use(standardResponses)
23
+ disableExpressDefaultPoweredByHeader(app)
24
+ app.use(customPoweredByHeader)
25
+ disableCaseSensitiveRouting(app)
26
+ app.node = node
27
+ addRoutes(app)
28
+ app.use(standardErrors)
29
+ return app
30
+ }
@@ -0,0 +1,13 @@
1
+ export * from './app.ts'
2
+ export * from './server.ts'
3
+
4
+ import type { NodeInstance } from '@xyo-network/node-model'
5
+
6
+ declare global {
7
+ // eslint-disable-next-line @typescript-eslint/no-namespace
8
+ namespace Express {
9
+ interface Application {
10
+ node: NodeInstance
11
+ }
12
+ }
13
+ }
@@ -0,0 +1,15 @@
1
+ import { registerInstrumentations } from '@opentelemetry/instrumentation'
2
+ import { ExpressInstrumentation } from '@opentelemetry/instrumentation-express'
3
+ import { HttpInstrumentation } from '@opentelemetry/instrumentation-http'
4
+
5
+ /**
6
+ * Registers OpenTelemetry instrumentations for HTTP and Express.
7
+ * This function is used to set up the necessary instrumentations for monitoring
8
+ * HTTP requests and Express applications. Since it monkey patches the Express
9
+ * router & middleware system, it should be called before any Express applications
10
+ * are defined.
11
+ */
12
+ export const addInstrumentation = () => {
13
+ const instrumentations = [new HttpInstrumentation(), new ExpressInstrumentation()]
14
+ registerInstrumentations({ instrumentations })
15
+ }
@@ -0,0 +1,9 @@
1
+ import type { Express } from 'express'
2
+
3
+ import { addDataLakeRoutes } from './dataLake/index.ts'
4
+ import { addRewardRedemptionRoutes } from './rewardRedemption/index.ts'
5
+
6
+ export const addRoutes = (app: Express) => {
7
+ addDataLakeRoutes(app)
8
+ addRewardRedemptionRoutes(app)
9
+ }
@@ -0,0 +1,3 @@
1
+ export type AddressPathParams = {
2
+ address: string
3
+ }
@@ -0,0 +1,21 @@
1
+ import type { Express } from 'express'
2
+ import { StatusCodes } from 'http-status-codes'
3
+
4
+ import { getAddress } from './get/index.ts'
5
+ import { postAddress } from './post/index.ts'
6
+
7
+ export const addNodeRoutes = (app: Express) => {
8
+ const defaultModule = app.node
9
+ const address = defaultModule.address
10
+ const defaultModuleEndpoint = `/${address}`
11
+ app.get('/', (_req, res) => res.redirect(StatusCodes.MOVED_TEMPORARILY, defaultModuleEndpoint))
12
+ app.post('/', (_req, res) => res.redirect(StatusCodes.TEMPORARY_REDIRECT, defaultModuleEndpoint))
13
+ app.get('/:address', getAddress)
14
+ app.post('/:address', postAddress)
15
+ app.get('/:hash', (_req, res) => {
16
+ res.sendStatus(StatusCodes.NOT_FOUND)
17
+ })
18
+ app.post('/:hash', (_req, res) => {
19
+ res.sendStatus(StatusCodes.NOT_FOUND)
20
+ })
21
+ }
@@ -0,0 +1,33 @@
1
+ import { assertEx } from '@xylabs/assert'
2
+ import { asyncHandler } from '@xylabs/express'
3
+ import { asAddress, toAddress } from '@xylabs/hex'
4
+ import { isModuleIdentifierPart } from '@xyo-network/module-model'
5
+ import type { Payload } from '@xyo-network/payload-model'
6
+ import type { RequestHandler } from 'express'
7
+ import { StatusCodes } from 'http-status-codes'
8
+
9
+ import type { AddressPathParams } from '../AddressPathParams.ts'
10
+
11
+ const handler: RequestHandler<AddressPathParams, Payload[]> = async (req, res, next) => {
12
+ const { address: rawAddress } = req.params
13
+ const { node } = req.app
14
+ const address = asAddress(rawAddress)
15
+ if (address !== undefined) {
16
+ let mod = node.address === address ? node : (await node.resolve(address, { direction: 'down' }))
17
+ if (mod) {
18
+ res.json(await mod.state())
19
+ return
20
+ }
21
+ }
22
+ if (isModuleIdentifierPart(rawAddress)) {
23
+ const moduleIdentifier = toAddress(rawAddress)
24
+ const mod = await node.resolve(moduleIdentifier, { direction: 'down' })
25
+ if (mod) {
26
+ const moduleAddress = assertEx(mod?.address, () => 'Error redirecting to module by address')
27
+ res.redirect(StatusCodes.MOVED_TEMPORARILY, `/${moduleAddress}`)
28
+ return
29
+ }
30
+ }
31
+ next('route')
32
+ }
33
+ export const getAddress = asyncHandler(handler)
@@ -0,0 +1 @@
1
+ export * from './get.ts'
@@ -0,0 +1 @@
1
+ export * from './addNodeRoutes.ts'
@@ -0,0 +1,23 @@
1
+ import type { BoundWitness, QueryBoundWitness } from '@xyo-network/boundwitness-model'
2
+ import { BoundWitnessSchema } from '@xyo-network/boundwitness-model'
3
+ import type { ModuleConfig, ModuleInstance } from '@xyo-network/module-model'
4
+ import { ModuleConfigSchema } from '@xyo-network/module-model'
5
+ import type { Payload } from '@xyo-network/payload-model'
6
+ import type { Request } from 'express'
7
+
8
+ const DEFAULT_DEPTH = 5 as const
9
+
10
+ export const getQueryConfig = (mod: ModuleInstance, req: Request, bw: QueryBoundWitness, payloads?: Payload[]): ModuleConfig | undefined => {
11
+ // TODO: Filter based on query addresses?
12
+ // Recurse through payloads for nested BWs
13
+ const nestedBwAddresses
14
+ = payloads
15
+ ?.flat(DEFAULT_DEPTH)
16
+ .filter<BoundWitness>((payload): payload is BoundWitness => payload?.schema === BoundWitnessSchema)
17
+ .map(bw => bw.addresses) || []
18
+ // TODO: Do we want to end up with a list of addresses or a list of address lists?
19
+ const addresses = [bw.addresses, ...nestedBwAddresses].filter(address => address.length > 0)
20
+ const allowed = addresses.length > 0 ? Object.fromEntries(mod.queries.map(schema => [schema, addresses])) : {}
21
+ const security = { allowed }
22
+ return { schema: ModuleConfigSchema, security }
23
+ }
@@ -0,0 +1 @@
1
+ export * from './post.ts'
@@ -0,0 +1,77 @@
1
+ import { assertEx } from '@xylabs/assert'
2
+ import { asyncHandler } from '@xylabs/express'
3
+ import {
4
+ asAddress, isAddress,
5
+ toAddress,
6
+ } from '@xylabs/hex'
7
+ import type { JsonObject } from '@xylabs/object'
8
+ import { isQueryBoundWitness, type QueryBoundWitness } from '@xyo-network/boundwitness-model'
9
+ import { ModuleErrorBuilder } from '@xyo-network/module-abstract'
10
+ import type { ModuleInstance, ModuleQueryResult } from '@xyo-network/module-model'
11
+ import type { ModuleError, Payload } from '@xyo-network/payload-model'
12
+ import type { RequestHandler } from 'express'
13
+ import { StatusCodes } from 'http-status-codes'
14
+
15
+ import type { AddressPathParams } from '../AddressPathParams.ts'
16
+ import { getQueryConfig } from './getQueryConfig.ts'
17
+
18
+ type PostAddressRequestBody = [QueryBoundWitness, undefined | Payload[]]
19
+
20
+ const handler: RequestHandler<AddressPathParams, ModuleQueryResult | ModuleError, PostAddressRequestBody> = async (req, res, next) => {
21
+ const returnError = (code: number, message = 'An error occurred', details?: JsonObject) => {
22
+ const error = new ModuleErrorBuilder().message(message).details(details).build()
23
+ res.locals.rawResponse = false
24
+ res.status(code).json(error)
25
+ next()
26
+ }
27
+
28
+ const { address } = req.params
29
+ const { node } = req.app
30
+ const [bw, payloads] = Array.isArray(req.body) ? req.body : []
31
+ if (!isAddress(address)) {
32
+ return returnError(StatusCodes.BAD_REQUEST, 'Missing address')
33
+ }
34
+
35
+ if (!bw) {
36
+ return returnError(StatusCodes.BAD_REQUEST, 'Missing boundwitness')
37
+ }
38
+
39
+ if (!isQueryBoundWitness(bw)) {
40
+ return returnError(StatusCodes.BAD_REQUEST, 'Invalid query boundwitness')
41
+ }
42
+
43
+ let modules: ModuleInstance[] = []
44
+ const normalizedAddress = toAddress(address)
45
+ if (node.address === normalizedAddress) modules = [node]
46
+ else {
47
+ const typedAddress = asAddress(address)
48
+ const byAddress = (typedAddress === undefined) ? undefined : await node.resolve(typedAddress, { maxDepth: 10 })
49
+
50
+ if (byAddress) modules = [byAddress]
51
+ else {
52
+ const byName = await node.resolve(address, { direction: 'down' })
53
+ if (byName) {
54
+ const moduleAddress = assertEx(byName?.address, () => 'Error redirecting to module by address')
55
+ res.redirect(StatusCodes.TEMPORARY_REDIRECT, `/${moduleAddress}`)
56
+ return
57
+ } else {
58
+ return returnError(StatusCodes.NOT_FOUND, 'Module not found', { address })
59
+ }
60
+ }
61
+ }
62
+
63
+ if (modules.length > 0) {
64
+ const mod = modules[0]
65
+ const queryConfig = getQueryConfig(mod, req, bw, payloads)
66
+ try {
67
+ const queryResult = await mod.query(bw, payloads, queryConfig)
68
+ res.json(queryResult)
69
+ } catch (ex) {
70
+ return returnError(StatusCodes.INTERNAL_SERVER_ERROR, 'Query Failed', { message: (ex as Error)?.message ?? 'Unknown Error' })
71
+ }
72
+ } else {
73
+ return returnError(StatusCodes.NOT_FOUND, 'Module not found', { address })
74
+ }
75
+ }
76
+
77
+ export const postAddress = asyncHandler(handler)
@@ -0,0 +1,9 @@
1
+ import type { Express } from 'express'
2
+
3
+ import { archivistMiddleware } from './archivistMiddleware.ts'
4
+
5
+ export const addDataLakeRoutes = (app: Express) => {
6
+ const { node } = app
7
+ const archivistModuleIdentifier = 'RewardsArchivist'
8
+ app.use('/data', archivistMiddleware({ node, archivistModuleIdentifier }))
9
+ }
@@ -0,0 +1,86 @@
1
+ import { setRawResponseFormat } from '@xylabs/express'
2
+ import { asHash } from '@xylabs/hex'
3
+ import { isDefined } from '@xylabs/typeof'
4
+ import type {
5
+ ArchivistInstance,
6
+ ArchivistNextOptions, NextOptions,
7
+ } from '@xyo-network/archivist-model'
8
+ import { asArchivistInstance } from '@xyo-network/archivist-model'
9
+ import type { ModuleIdentifier } from '@xyo-network/module-model'
10
+ import type { NodeInstance } from '@xyo-network/node-model'
11
+ import { PayloadBuilder } from '@xyo-network/payload-builder'
12
+ import type { Payload } from '@xyo-network/payload-model'
13
+ import { isAnyPayload, isSequence } from '@xyo-network/payload-model'
14
+ import type { Router } from 'express'
15
+ import express from 'express'
16
+ import type { Request } from 'express-serve-static-core'
17
+
18
+ const resolveArchivist = async (node: NodeInstance, archivistModuleIdentifier: ModuleIdentifier): Promise<ArchivistInstance> => {
19
+ const mod = await node.resolve(archivistModuleIdentifier)
20
+ return asArchivistInstance(mod, { required: true })
21
+ }
22
+
23
+ let archivistInstance: ArchivistInstance | undefined
24
+
25
+ const getArchivist = async (node: NodeInstance, archivistModuleIdentifier: ModuleIdentifier): Promise<ArchivistInstance> => {
26
+ if (isDefined(archivistInstance)) return archivistInstance
27
+ archivistInstance = await resolveArchivist(node, archivistModuleIdentifier)
28
+ return archivistInstance
29
+ }
30
+
31
+ type ArchivistMiddlewareOptions = {
32
+ archivistModuleIdentifier: ModuleIdentifier
33
+ node: NodeInstance
34
+ }
35
+
36
+ export const archivistMiddleware = (options: ArchivistMiddlewareOptions): Router => {
37
+ const { node, archivistModuleIdentifier } = options
38
+ const router = express.Router({ mergeParams: true })
39
+
40
+ router.post('/insert', async (req, res) => {
41
+ setRawResponseFormat(res)
42
+ const body = Array.isArray(req.body) ? req.body : [req.body]
43
+ const payloads = (await PayloadBuilder.hashPairs<Payload>(body)).map(p => p[0])
44
+ const archivist = await getArchivist(node, archivistModuleIdentifier)
45
+ const result = await archivist.insert(payloads)
46
+ res.status(200).json(result)
47
+ })
48
+
49
+ router.get('/next', async (req: Request<Partial<NextOptions>>, res) => {
50
+ setRawResponseFormat(res)
51
+ const cursor = isSequence(req.query.cursor) ? req.query.cursor : undefined
52
+ const limit = isDefined(req.query.limit) ? Number(req.query.limit) : undefined
53
+ const open = isDefined(req.query.open) ? Boolean(req.query.open) : undefined
54
+ const order = req.query.order === 'asc' ? 'asc' : 'desc'
55
+ const options: ArchivistNextOptions = {
56
+ limit, open, order, cursor,
57
+ }
58
+ const archivist = await getArchivist(node, archivistModuleIdentifier)
59
+ const result = await archivist.next(options)
60
+ res.status(200).json(result)
61
+ })
62
+ router.post('/next', async (req: Request<{}, {}, ArchivistNextOptions | undefined>, res) => {
63
+ setRawResponseFormat(res)
64
+ const options = req.body
65
+ const archivist = await getArchivist(node, archivistModuleIdentifier)
66
+ const result = await (isDefined(options) ? archivist.next(options) : archivist.next())
67
+ res.status(200).json(result)
68
+ })
69
+
70
+ router.get('/get/:hash', async (req, res) => {
71
+ setRawResponseFormat(res)
72
+ const { hash: rawHash } = req.params
73
+ const hash = asHash(rawHash)
74
+ if (isDefined(hash)) {
75
+ const archivist = await getArchivist(node, archivistModuleIdentifier)
76
+ const [payload] = await archivist.get([hash])
77
+ if (isAnyPayload(payload)) {
78
+ res.json(payload)
79
+ return
80
+ }
81
+ }
82
+ res.status(400).send()
83
+ })
84
+
85
+ return router
86
+ }
@@ -0,0 +1 @@
1
+ export * from './addDataLakeRoutes.ts'
@@ -0,0 +1,20 @@
1
+ import type { NoReqParams } from '@xylabs/express'
2
+ import { setRawResponseFormat } from '@xylabs/express'
3
+ import type { RequestHandler } from 'express'
4
+ import { ReasonPhrases } from 'http-status-codes'
5
+
6
+ const message = ReasonPhrases.OK
7
+
8
+ const handler: RequestHandler<NoReqParams> = (_req, res) => {
9
+ setRawResponseFormat(res)
10
+ const date = new Date()
11
+ const {
12
+ cpuUsage, memoryUsage, uptime,
13
+ } = process
14
+ const data = {
15
+ cpuUsage: cpuUsage(), date, memoryUsage: memoryUsage(), message, uptime: uptime(),
16
+ }
17
+ res.status(200).send(data)
18
+ }
19
+
20
+ export const getHealthz: RequestHandler<NoReqParams> = handler
@@ -0,0 +1 @@
1
+ export * from './get.ts'
@@ -0,0 +1,3 @@
1
+ export * from './address/index.ts'
2
+ export * from './addRoutes.ts'
3
+ export * from './healthz/index.ts'
@@ -0,0 +1,10 @@
1
+ import type { Express } from 'express'
2
+
3
+ import { getRouteDefinitions } from './routeDefinitions/index.ts'
4
+
5
+ export const addRewardRedemptionRoutes = (app: Express) => {
6
+ const routeDefinitions = getRouteDefinitions()
7
+ for (const definition of routeDefinitions) {
8
+ app[definition.method](definition.path, definition.handlers)
9
+ }
10
+ }
@@ -0,0 +1 @@
1
+ export * from './addRewardRoutes.ts'
@@ -0,0 +1 @@
1
+ export * from './requestHandlerValidator.ts'
@@ -0,0 +1,120 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import type { ExpressError } from '@xylabs/express'
3
+ import { isPromise } from '@xylabs/typeof'
4
+ import type {
5
+ NextFunction, Request, RequestHandler, Response,
6
+ } from 'express'
7
+ import { ReasonPhrases, StatusCodes } from 'http-status-codes'
8
+ import type { ZodType } from 'zod'
9
+ import { z } from 'zod'
10
+
11
+ /**
12
+ * Empty Zod schema for requests with no parameters.
13
+ */
14
+ export const EmptyParamsZod = z.object({}).catchall(z.string())
15
+
16
+ /**
17
+ * Empty Zod schema for requests with no query parameters.
18
+ */
19
+ export const EmptyQueryParamsZod = z.object({}).catchall(z.union([z.string(), z.array(z.string())]))
20
+
21
+ /**
22
+ * Default validation schemas for request handler validator.
23
+ */
24
+ export const ValidateRequestDefaults = {
25
+ params: EmptyParamsZod,
26
+ query: EmptyQueryParamsZod,
27
+ body: z.json(),
28
+ response: z.json(),
29
+ }
30
+
31
+ type ValidatableRequestKey = 'params' | 'query' | 'body'
32
+
33
+ /**
34
+ * Factory for Express middleware that validates request and response objects using Zod schemas.
35
+ * @param schemas The Zod schemas to use for validation.
36
+ * @returns A middleware function for validating requests and responses.
37
+ */
38
+ export function requestHandlerValidator<
39
+ TParams extends typeof EmptyQueryParamsZod | ZodType<Record<string, string>> = typeof EmptyQueryParamsZod,
40
+ TQuery extends typeof EmptyQueryParamsZod | ZodType<Record<string, string | string[]>> = typeof EmptyQueryParamsZod,
41
+ TBody extends ZodType<unknown> = ZodType<unknown>,
42
+ TResponse extends ZodType<unknown> = ZodType<unknown>,
43
+ >(schemas?: Partial<{
44
+ body: TBody
45
+ params: TParams
46
+ query: TQuery
47
+ response: TResponse
48
+ }>) {
49
+ type Params = z.infer<TParams>
50
+ type Query = z.infer<TQuery>
51
+ type Body = z.infer<TBody>
52
+ type Res = z.infer<TResponse>
53
+ const validators = { ...ValidateRequestDefaults, ...schemas }
54
+
55
+ return (handler: (req: Request<Params, Res, Body, Query>, res: Response<Res>, next: NextFunction) => unknown): RequestHandler => {
56
+ return async (req: Request, res: Response, next: NextFunction) => {
57
+ const originalJson = res.json.bind(res)
58
+ try {
59
+ // Validate incoming request
60
+ const errors: string[] = []
61
+ const keys: ValidatableRequestKey[] = ['params', 'query', 'body']
62
+ for (const key of keys) {
63
+ const validator = validators[key]
64
+ const result = validator.safeParse(req[key])
65
+ if (result.success) {
66
+ Object.assign(req[key], result.data)
67
+ } else {
68
+ errors.push(
69
+ ...result.error.issues.map(
70
+ issue => (issue.path.length === 0)
71
+ ? `${key}: ${issue.message}`
72
+ : `${key}.${issue.path.join('.')}: ${issue.message}`,
73
+ ),
74
+ )
75
+ }
76
+ }
77
+
78
+ // If there were validation errors, short-circuit and return Bad Request
79
+ if (errors.length > 0) {
80
+ const message = errors.join('; ')
81
+ const err: ExpressError = new Error(message)
82
+ err.name = ReasonPhrases.BAD_REQUEST
83
+ err.statusCode = StatusCodes.BAD_REQUEST
84
+ next(err)
85
+ return false
86
+ }
87
+
88
+ // Wrap res.json to validate outgoing response
89
+ res.json = (data: any) => {
90
+ const result = validators.response.safeParse(data)
91
+ if (result.success) {
92
+ return originalJson(result.data)
93
+ } else {
94
+ const message = result.error.issues.map(
95
+ issue => (issue.path.length === 0)
96
+ ? `response: ${issue.message}`
97
+ : `response.${issue.path.join('.')}: ${issue.message}`,
98
+ ).join('; ')
99
+ const err: ExpressError = new Error(message)
100
+ err.name = ReasonPhrases.INTERNAL_SERVER_ERROR
101
+ err.statusCode = StatusCodes.INTERNAL_SERVER_ERROR
102
+
103
+ // Restore original json function in case the error handler wants to use it
104
+ res.json = originalJson
105
+ throw err
106
+ }
107
+ }
108
+
109
+ // Automatically handle async errors
110
+ const result = handler(req as any, res as any, next)
111
+ if (result && isPromise(result)) {
112
+ await result
113
+ }
114
+ } catch (err) {
115
+ res.json = originalJson
116
+ next(err)
117
+ }
118
+ }
119
+ }
120
+ }
@@ -0,0 +1,9 @@
1
+ import type { RouteDefinition } from './routeDefinition.ts'
2
+ import { postClaim, postRedeem } from './routes/index.ts'
3
+
4
+ export const getRouteDefinitions = (): RouteDefinition[] => {
5
+ return [
6
+ postClaim,
7
+ postRedeem,
8
+ ]
9
+ }
@@ -0,0 +1 @@
1
+ export * from './getRouteDefinitions.ts'
@@ -0,0 +1 @@
1
+ export { EthAddressFromStringZod as AddressPathParam } from '@xylabs/hex'
@@ -0,0 +1 @@
1
+ export * from './AddressPathParam.ts'
@@ -0,0 +1,18 @@
1
+ import type { RequestHandler } from 'express'
2
+
3
+ export type HttpMethod
4
+ = | 'get'
5
+ | 'post'
6
+ | 'put'
7
+ | 'patch'
8
+ | 'delete'
9
+ | 'options'
10
+ | 'head'
11
+
12
+ export interface RouteDefinition<
13
+ H extends RequestHandler = RequestHandler,
14
+ > {
15
+ handlers: H[] | H
16
+ method: HttpMethod
17
+ path: string | RegExp
18
+ }
@@ -0,0 +1,31 @@
1
+ import { PayloadZodLoose } from '@xyo-network/payload-model'
2
+ import { z } from 'zod'
3
+
4
+ import { requestHandlerValidator } from '../../middleware/index.ts'
5
+ import { AddressPathParam } from '../pathParams/index.ts'
6
+ import type { RouteDefinition } from '../routeDefinition.ts'
7
+
8
+ const params = z.object({ address: AddressPathParam })
9
+ const body = PayloadZodLoose
10
+
11
+ const response = PayloadZodLoose
12
+
13
+ const validateRequest = requestHandlerValidator({
14
+ params,
15
+ body,
16
+ response,
17
+ })
18
+
19
+ export const postClaim: RouteDefinition = {
20
+ method: 'post',
21
+ path: '/rewards/claim/:address',
22
+ handlers: validateRequest(async (req, res) => {
23
+ const { address } = req.params
24
+ // TODO: Validate ETH address
25
+ // TODO: Redeem claim for existing rewards
26
+ // TODO: Return response payload
27
+ await Promise.resolve()
28
+ const observation = { schema: 'network.xyo.test' }
29
+ res.json(observation)
30
+ }),
31
+ }
@@ -0,0 +1,2 @@
1
+ export * from './claim.ts'
2
+ export * from './redeem.ts'
@@ -0,0 +1,26 @@
1
+ import { PayloadZodLoose } from '@xyo-network/payload-model'
2
+ import { z } from 'zod'
3
+
4
+ import { requestHandlerValidator } from '../../middleware/index.ts'
5
+ import { AddressPathParam } from '../pathParams/index.ts'
6
+ import type { RouteDefinition } from '../routeDefinition.ts'
7
+
8
+ const params = z.object({ address: AddressPathParam })
9
+ const body = PayloadZodLoose
10
+ const response = PayloadZodLoose
11
+ const validateRequest = requestHandlerValidator({
12
+ params,
13
+ body,
14
+ response,
15
+ })
16
+
17
+ export const postRedeem: RouteDefinition = {
18
+ method: 'post',
19
+ path: '/rewards/redeem/:address',
20
+ handlers: validateRequest(async (req, res) => {
21
+ const { body } = req
22
+ await Promise.resolve()
23
+ const bridgeObservation = { schema: 'network.xyo.test' }
24
+ res.json(bridgeObservation)
25
+ }),
26
+ }