@bsv/overlay-discovery-services 1.4.0 → 1.4.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.
Files changed (83) hide show
  1. package/dist/cjs/package.json +4 -4
  2. package/dist/cjs/src/LegacyNinjaAdvertiser.js +235 -0
  3. package/dist/cjs/src/LegacyNinjaAdvertiser.js.map +1 -0
  4. package/dist/cjs/src/SHIP/SHIPLookupService.js +43 -11
  5. package/dist/cjs/src/SHIP/SHIPLookupService.js.map +1 -1
  6. package/dist/cjs/src/SHIP/SHIPStorage.js +33 -10
  7. package/dist/cjs/src/SHIP/SHIPStorage.js.map +1 -1
  8. package/dist/cjs/src/SHIP/SHIPTopicManager.js.map +1 -1
  9. package/dist/cjs/src/SLAP/SLAPLookupService.js +57 -22
  10. package/dist/cjs/src/SLAP/SLAPLookupService.js.map +1 -1
  11. package/dist/cjs/src/SLAP/SLAPStorage.js +29 -6
  12. package/dist/cjs/src/SLAP/SLAPStorage.js.map +1 -1
  13. package/dist/cjs/src/utils/generateDocs.js +81 -0
  14. package/dist/cjs/src/utils/generateDocs.js.map +1 -0
  15. package/dist/cjs/src/utils/getDocumentation.js +22 -0
  16. package/dist/cjs/src/utils/getDocumentation.js.map +1 -0
  17. package/dist/cjs/src/utils/isAdvertisableURI.js +21 -21
  18. package/dist/cjs/src/utils/isAdvertisableURI.js.map +1 -1
  19. package/dist/cjs/src/utils/isValidDomain.js +15 -0
  20. package/dist/cjs/src/utils/isValidDomain.js.map +1 -0
  21. package/dist/cjs/src/utils/isValidServiceName.js +14 -0
  22. package/dist/cjs/src/utils/isValidServiceName.js.map +1 -0
  23. package/dist/cjs/src/utils/verifyToken.js +22 -0
  24. package/dist/cjs/src/utils/verifyToken.js.map +1 -0
  25. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  26. package/dist/esm/src/LegacyNinjaAdvertiser.js +233 -0
  27. package/dist/esm/src/LegacyNinjaAdvertiser.js.map +1 -0
  28. package/dist/esm/src/SHIP/SHIPLookupService.js +43 -11
  29. package/dist/esm/src/SHIP/SHIPLookupService.js.map +1 -1
  30. package/dist/esm/src/SHIP/SHIPStorage.js +32 -10
  31. package/dist/esm/src/SHIP/SHIPStorage.js.map +1 -1
  32. package/dist/esm/src/SHIP/SHIPTopicManager.js.map +1 -1
  33. package/dist/esm/src/SLAP/SLAPLookupService.js +57 -22
  34. package/dist/esm/src/SLAP/SLAPLookupService.js.map +1 -1
  35. package/dist/esm/src/SLAP/SLAPStorage.js +28 -6
  36. package/dist/esm/src/SLAP/SLAPStorage.js.map +1 -1
  37. package/dist/esm/src/utils/generateDocs.js +46 -0
  38. package/dist/esm/src/utils/generateDocs.js.map +1 -0
  39. package/dist/esm/src/utils/getDocumentation.js +20 -0
  40. package/dist/esm/src/utils/getDocumentation.js.map +1 -0
  41. package/dist/esm/src/utils/isAdvertisableURI.js +21 -21
  42. package/dist/esm/src/utils/isAdvertisableURI.js.map +1 -1
  43. package/dist/esm/src/utils/isValidDomain.js +11 -0
  44. package/dist/esm/src/utils/isValidDomain.js.map +1 -0
  45. package/dist/esm/src/utils/isValidServiceName.js +10 -0
  46. package/dist/esm/src/utils/isValidServiceName.js.map +1 -0
  47. package/dist/esm/src/utils/verifyToken.js +18 -0
  48. package/dist/esm/src/utils/verifyToken.js.map +1 -0
  49. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  50. package/dist/types/src/LegacyNinjaAdvertiser.d.ts +60 -0
  51. package/dist/types/src/LegacyNinjaAdvertiser.d.ts.map +1 -0
  52. package/dist/types/src/SHIP/SHIPLookupService.d.ts.map +1 -1
  53. package/dist/types/src/SHIP/SHIPStorage.d.ts +8 -5
  54. package/dist/types/src/SHIP/SHIPStorage.d.ts.map +1 -1
  55. package/dist/types/src/SHIP/SHIPTopicManager.d.ts.map +1 -1
  56. package/dist/types/src/SLAP/SLAPLookupService.d.ts.map +1 -1
  57. package/dist/types/src/SLAP/SLAPStorage.d.ts +4 -1
  58. package/dist/types/src/SLAP/SLAPStorage.d.ts.map +1 -1
  59. package/dist/types/src/SLAP/SLAPTopicManager.d.ts.map +1 -1
  60. package/dist/types/src/types.d.ts +8 -0
  61. package/dist/types/src/types.d.ts.map +1 -1
  62. package/dist/types/src/utils/generateDocs.d.ts +2 -0
  63. package/dist/types/src/utils/generateDocs.d.ts.map +1 -0
  64. package/dist/types/src/utils/getDocumentation.d.ts +8 -0
  65. package/dist/types/src/utils/getDocumentation.d.ts.map +1 -0
  66. package/dist/types/src/utils/isAdvertisableURI.d.ts.map +1 -1
  67. package/dist/types/src/utils/isValidDomain.d.ts +7 -0
  68. package/dist/types/src/utils/isValidDomain.d.ts.map +1 -0
  69. package/dist/types/src/utils/isValidServiceName.d.ts +7 -0
  70. package/dist/types/src/utils/isValidServiceName.d.ts.map +1 -0
  71. package/dist/types/src/utils/verifyToken.d.ts +12 -0
  72. package/dist/types/src/utils/verifyToken.d.ts.map +1 -0
  73. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  74. package/package.json +4 -4
  75. package/src/SHIP/SHIPLookupService.ts +57 -17
  76. package/src/SHIP/SHIPStorage.ts +45 -15
  77. package/src/SHIP/SHIPTopicManager.ts +3 -3
  78. package/src/SLAP/SLAPLookup.docs.ts +1 -1
  79. package/src/SLAP/SLAPLookupService.ts +64 -26
  80. package/src/SLAP/SLAPStorage.ts +41 -11
  81. package/src/SLAP/SLAPTopicManager.ts +3 -3
  82. package/src/types.ts +8 -0
  83. package/src/utils/isAdvertisableURI.ts +50 -50
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsv/overlay-discovery-services",
3
- "version": "1.4.0",
3
+ "version": "1.4.2",
4
4
  "type": "module",
5
5
  "description": "BSV Blockchain Overlay Services Engine",
6
6
  "main": "dist/cjs/mod.js",
@@ -62,9 +62,9 @@
62
62
  "typescript": "^5.2.2"
63
63
  },
64
64
  "dependencies": {
65
- "@bsv/overlay": "^0.4.0",
66
- "@bsv/sdk": "^1.6.8",
67
- "@bsv/wallet-toolbox-client": "^1.5.0",
65
+ "@bsv/overlay": "^0.4.4",
66
+ "@bsv/sdk": "^1.6.19",
67
+ "@bsv/wallet-toolbox-client": "^1.5.18",
68
68
  "mongodb": "^6.11.0"
69
69
  }
70
70
  }
@@ -13,9 +13,9 @@ import SHIPLookupDocs from './SHIPLookup.docs.js'
13
13
  export class SHIPLookupService implements LookupService {
14
14
  admissionMode: AdmissionMode = 'locking-script'
15
15
  spendNotificationMode: SpendNotificationMode = 'none'
16
- constructor(public storage: SHIPStorage) { }
16
+ constructor (public storage: SHIPStorage) { }
17
17
 
18
- async outputAdmittedByTopic(payload: OutputAdmittedByTopic): Promise<void> {
18
+ async outputAdmittedByTopic (payload: OutputAdmittedByTopic): Promise<void> {
19
19
  if (payload.mode !== 'locking-script') throw new Error('Invalid payload')
20
20
  const { topic, lockingScript, txid, outputIndex } = payload
21
21
  if (topic !== 'tm_ship') return
@@ -28,7 +28,7 @@ export class SHIPLookupService implements LookupService {
28
28
  await this.storage.storeSHIPRecord(txid, outputIndex, identityKey, domain, topicSupported)
29
29
  }
30
30
 
31
- async outputSpent(payload: OutputSpent): Promise<void> {
31
+ async outputSpent (payload: OutputSpent): Promise<void> {
32
32
  if (payload.mode !== 'none') throw new Error('Invalid payload')
33
33
  const { topic, txid, outputIndex } = payload
34
34
  if (topic !== 'tm_ship') return
@@ -39,7 +39,7 @@ export class SHIPLookupService implements LookupService {
39
39
  await this.storage.deleteSHIPRecord(txid, outputIndex)
40
40
  }
41
41
 
42
- async lookup(question: LookupQuestion): Promise<LookupFormula> {
42
+ async lookup (question: LookupQuestion): Promise<LookupFormula> {
43
43
  if (question.query === undefined || question.query === null) {
44
44
  throw new Error('A valid query must be provided!')
45
45
  }
@@ -47,29 +47,69 @@ export class SHIPLookupService implements LookupService {
47
47
  throw new Error('Lookup service not supported!')
48
48
  }
49
49
 
50
+ // Handle legacy "findAll" string query
50
51
  if (question.query === 'findAll') {
51
52
  return await this.storage.findAll()
52
53
  }
53
54
 
54
- // Validate lookup query
55
- const { domain, topics, identityKey } = question.query as SHIPQuery
56
- if (typeof domain !== 'string' && typeof domain !== 'undefined') {
57
- throw new Error('query.domain must be a string if provided')
58
- }
59
- if (!Array.isArray(topics) && typeof topics !== 'undefined') {
60
- throw new Error('query.topics must be an array of strings if provided')
61
- }
62
- if (typeof identityKey !== 'string' && typeof identityKey !== 'undefined') {
63
- throw new Error('query.identityKey must be a string if provided')
55
+ // Handle object-based query
56
+ if (typeof question.query === 'object') {
57
+ const query = question.query as SHIPQuery
58
+
59
+ // Handle new findAll mode with pagination
60
+ if (query.findAll) {
61
+ const { limit, skip, sortOrder } = query
62
+
63
+ // Validate pagination parameters
64
+ if (typeof limit !== 'undefined' && (typeof limit !== 'number' || limit < 0)) {
65
+ throw new Error('query.limit must be a positive number if provided')
66
+ }
67
+ if (typeof skip !== 'undefined' && (typeof skip !== 'number' || skip < 0)) {
68
+ throw new Error('query.skip must be a non-negative number if provided')
69
+ }
70
+ if (typeof sortOrder !== 'undefined' && sortOrder !== 'asc' && sortOrder !== 'desc') {
71
+ throw new Error('query.sortOrder must be "asc" or "desc" if provided')
72
+ }
73
+
74
+ return await this.storage.findAll(limit, skip, sortOrder)
75
+ }
76
+
77
+ // Handle specific query with domain, topics, identityKey
78
+ const { domain, topics, identityKey, limit, skip, sortOrder } = query
79
+
80
+ // Validate query parameters
81
+ if (typeof domain !== 'string' && typeof domain !== 'undefined') {
82
+ throw new Error('query.domain must be a string if provided')
83
+ }
84
+ if (!Array.isArray(topics) && typeof topics !== 'undefined') {
85
+ throw new Error('query.topics must be an array of strings if provided')
86
+ }
87
+ if (typeof identityKey !== 'string' && typeof identityKey !== 'undefined') {
88
+ throw new Error('query.identityKey must be a string if provided')
89
+ }
90
+
91
+ // Validate pagination parameters
92
+ if (typeof limit !== 'undefined' && (typeof limit !== 'number' || limit < 0)) {
93
+ throw new Error('query.limit must be a positive number if provided')
94
+ }
95
+ if (typeof skip !== 'undefined' && (typeof skip !== 'number' || skip < 0)) {
96
+ throw new Error('query.skip must be a non-negative number if provided')
97
+ }
98
+ if (typeof sortOrder !== 'undefined' && sortOrder !== 'asc' && sortOrder !== 'desc') {
99
+ throw new Error('query.sortOrder must be "asc" or "desc" if provided')
100
+ }
101
+
102
+ return await this.storage.findRecord({ domain, topics, identityKey, limit, skip, sortOrder })
64
103
  }
65
- return await this.storage.findRecord({ domain, topics, identityKey })
104
+
105
+ throw new Error('Invalid query format. Query must be "findAll" string or an object with valid parameters.')
66
106
  }
67
107
 
68
- async getDocumentation(): Promise<string> {
108
+ async getDocumentation (): Promise<string> {
69
109
  return SHIPLookupDocs
70
110
  }
71
111
 
72
- async getMetaData(): Promise<{
112
+ async getMetaData (): Promise<{
73
113
  name: string
74
114
  shortDescription: string
75
115
  iconURL?: string
@@ -11,14 +11,14 @@ export class SHIPStorage {
11
11
  * Constructs a new SHIPStorage instance
12
12
  * @param {Db} db - connected mongo database instance
13
13
  */
14
- constructor(private readonly db: Db) {
14
+ constructor (private readonly db: Db) {
15
15
  this.shipRecords = db.collection<SHIPRecord>('shipRecords')
16
16
  }
17
17
 
18
18
  /**
19
19
  * Ensures the necessary indexes are created for the collections.
20
20
  */
21
- async ensureIndexes(): Promise<void> {
21
+ async ensureIndexes (): Promise<void> {
22
22
  await this.shipRecords.createIndex({ domain: 1, topic: 1 })
23
23
  }
24
24
 
@@ -30,7 +30,7 @@ export class SHIPStorage {
30
30
  * @param {string} domain domain name
31
31
  * @param {string} topic topic name
32
32
  */
33
- async storeSHIPRecord(txid: string, outputIndex: number, identityKey: string, domain: string, topic: string): Promise<void> {
33
+ async storeSHIPRecord (txid: string, outputIndex: number, identityKey: string, domain: string, topic: string): Promise<void> {
34
34
  await this.shipRecords.insertOne({
35
35
  txid,
36
36
  outputIndex,
@@ -46,16 +46,16 @@ export class SHIPStorage {
46
46
  * @param {string} txid transaction id
47
47
  * @param {number} outputIndex index of the UTXO
48
48
  */
49
- async deleteSHIPRecord(txid: string, outputIndex: number): Promise<void> {
49
+ async deleteSHIPRecord (txid: string, outputIndex: number): Promise<void> {
50
50
  await this.shipRecords.deleteOne({ txid, outputIndex })
51
51
  }
52
52
 
53
53
  /**
54
54
  * Finds SHIP records based on a given query object.
55
- * @param {Object} query The query object which may contain properties for domain, topics, and/or identityKey.
55
+ * @param {Object} query The query object which may contain properties for domain, topics, identityKey, limit, and skip.
56
56
  * @returns {Promise<UTXOReference[]>} Returns matching UTXO references.
57
57
  */
58
- async findRecord(query: SHIPQuery): Promise<UTXOReference[]> {
58
+ async findRecord (query: SHIPQuery): Promise<UTXOReference[]> {
59
59
  const mongoQuery: any = {}
60
60
 
61
61
  // Add domain to the query if provided
@@ -73,25 +73,55 @@ export class SHIPStorage {
73
73
  mongoQuery.identityKey = query.identityKey
74
74
  }
75
75
 
76
- return await this.shipRecords
76
+ // Build the query with pagination
77
+ let cursor = this.shipRecords
77
78
  .find(mongoQuery)
78
- .project<UTXOReference>({ txid: 1, outputIndex: 1 })
79
+ .project<UTXOReference>({ txid: 1, outputIndex: 1, createdAt: 1 })
80
+
81
+ cursor.sort({ createdAt: query.sortOrder ?? -1 })
82
+
83
+ // Apply pagination if provided
84
+ if (typeof query.skip === 'number' && query.skip > 0) {
85
+ cursor = cursor.skip(query.skip)
86
+ }
87
+
88
+ if (typeof query.limit === 'number' && query.limit > 0) {
89
+ cursor = cursor.limit(query.limit)
90
+ }
91
+
92
+ return await cursor
79
93
  .toArray()
80
94
  .then((results) =>
81
95
  results.map((record) => ({
82
96
  txid: record.txid,
83
- outputIndex: record.outputIndex,
97
+ outputIndex: record.outputIndex
84
98
  }))
85
99
  )
86
100
  }
87
101
 
88
102
  /**
89
- * Returns all results tracked by the overlay
90
- * @returns {Promise<UTXOReference[]>} returns matching UTXO references
91
- */
92
- async findAll(): Promise<UTXOReference[]> {
93
- return await this.shipRecords.find({})
94
- .project<UTXOReference>({ txid: 1, outputIndex: 1 })
103
+ * Returns all results tracked by the overlay
104
+ * @param {number} limit Optional limit for pagination
105
+ * @param {number} skip Optional skip for pagination
106
+ * @param {string} sortOrder Optional sort order
107
+ * @returns {Promise<UTXOReference[]>} returns matching UTXO references
108
+ */
109
+ async findAll (limit?: number, skip?: number, sortOrder?: 'asc' | 'desc'): Promise<UTXOReference[]> {
110
+ let cursor = this.shipRecords.find({})
111
+ .project<UTXOReference>({ txid: 1, outputIndex: 1, createdAt: 1 })
112
+
113
+ // Apply pagination if provided
114
+ cursor.sort({ createdAt: sortOrder ?? -1 })
115
+
116
+ if (typeof skip === 'number' && skip > 0) {
117
+ cursor = cursor.skip(skip)
118
+ }
119
+
120
+ if (typeof limit === 'number' && limit > 0) {
121
+ cursor = cursor.limit(limit)
122
+ }
123
+
124
+ return await cursor
95
125
  .toArray()
96
126
  .then(results => results.map(shipRecords => ({
97
127
  txid: shipRecords.txid,
@@ -19,7 +19,7 @@ export class SHIPTopicManager implements TopicManager {
19
19
  * @param previousCoins - The previous coins to consider.
20
20
  * @returns A promise that resolves with the admittance instructions.
21
21
  */
22
- async identifyAdmissibleOutputs(beef: number[], previousCoins: number[]): Promise<AdmittanceInstructions> {
22
+ async identifyAdmissibleOutputs (beef: number[], previousCoins: number[]): Promise<AdmittanceInstructions> {
23
23
  const outputsToAdmit: number[] = []
24
24
  try {
25
25
  const parsedTransaction = Transaction.fromBEEF(beef)
@@ -75,7 +75,7 @@ export class SHIPTopicManager implements TopicManager {
75
75
  * Returns documentation specific to the SHIP topic manager.
76
76
  * @returns A promise that resolves to the documentation string.
77
77
  */
78
- async getDocumentation(): Promise<string> {
78
+ async getDocumentation (): Promise<string> {
79
79
  return SHIPTopicDocs
80
80
  }
81
81
 
@@ -83,7 +83,7 @@ export class SHIPTopicManager implements TopicManager {
83
83
  * Returns metadata associated with this topic manager.
84
84
  * @returns A promise that resolves to an object containing metadata.
85
85
  */
86
- async getMetaData(): Promise<{
86
+ async getMetaData (): Promise<{
87
87
  name: string
88
88
  shortDescription: string
89
89
  iconURL?: string
@@ -91,4 +91,4 @@ You will typically provide a [LookupQuestion](https://www.npmjs.com/package/@bsv
91
91
 
92
92
  - **SLAPTopicManager**: Learn how SLAP outputs are detected and admitted.
93
93
  - **BRC-101 Overlays**: The higher-level specification for modular overlay availability schemes.
94
- `
94
+ `
@@ -14,9 +14,9 @@ import SLAPLookupDocs from './SLAPLookup.docs.js'
14
14
  export class SLAPLookupService implements LookupService {
15
15
  admissionMode: AdmissionMode = 'locking-script'
16
16
  spendNotificationMode: SpendNotificationMode = 'none'
17
- constructor(public storage: SLAPStorage) { }
17
+ constructor (public storage: SLAPStorage) { }
18
18
 
19
- async outputAdmittedByTopic(payload: OutputAdmittedByTopic): Promise<void> {
19
+ async outputAdmittedByTopic (payload: OutputAdmittedByTopic): Promise<void> {
20
20
  if (payload.mode !== 'locking-script') throw new Error('Invalid mode')
21
21
  const { txid, outputIndex, lockingScript, topic } = payload
22
22
  if (topic !== 'tm_slap') return
@@ -29,7 +29,7 @@ export class SLAPLookupService implements LookupService {
29
29
  await this.storage.storeSLAPRecord(txid, outputIndex, identityKey, domain, service)
30
30
  }
31
31
 
32
- async outputSpent(payload: OutputSpent): Promise<void> {
32
+ async outputSpent (payload: OutputSpent): Promise<void> {
33
33
  if (payload.mode !== 'none') throw new Error('Invalid payload')
34
34
  const { topic, txid, outputIndex } = payload
35
35
  if (topic !== 'tm_slap') return
@@ -40,7 +40,7 @@ export class SLAPLookupService implements LookupService {
40
40
  await this.storage.deleteSLAPRecord(txid, outputIndex)
41
41
  }
42
42
 
43
- async lookup(question: LookupQuestion): Promise<LookupFormula> {
43
+ async lookup (question: LookupQuestion): Promise<LookupFormula> {
44
44
  if (question.query === undefined || question.query === null) {
45
45
  throw new Error('A valid query must be provided!')
46
46
  }
@@ -48,40 +48,78 @@ export class SLAPLookupService implements LookupService {
48
48
  throw new Error('Lookup service not supported!')
49
49
  }
50
50
 
51
+ // Handle legacy "findAll" string query
51
52
  if (question.query === 'findAll') {
52
53
  return await this.storage.findAll()
53
54
  }
54
55
 
55
- // Validate lookup query
56
- const { domain, service, identityKey } = question.query as SLAPQuery
56
+ // Handle object-based query
57
+ if (typeof question.query === 'object') {
58
+ const query = question.query as SLAPQuery
57
59
 
58
- // Validate that provided values are strings.
59
- if (domain !== undefined && typeof domain !== 'string') {
60
- throw new Error('query.domain must be a string if provided')
61
- }
62
- if (service !== undefined && typeof service !== 'string') {
63
- throw new Error('query.service must be a string if provided')
64
- }
65
- if (identityKey !== undefined && typeof identityKey !== 'string') {
66
- throw new Error('query.identityKey must be a string if provided')
67
- }
60
+ // Handle new findAll mode with pagination
61
+ if (query.findAll) {
62
+ const { limit, skip, sortOrder } = query
63
+
64
+ // Validate pagination parameters
65
+ if (typeof limit !== 'undefined' && (typeof limit !== 'number' || limit < 0)) {
66
+ throw new Error('query.limit must be a positive number if provided')
67
+ }
68
+ if (typeof skip !== 'undefined' && (typeof skip !== 'number' || skip < 0)) {
69
+ throw new Error('query.skip must be a non-negative number if provided')
70
+ }
71
+ if (typeof sortOrder !== 'undefined' && sortOrder !== 'asc' && sortOrder !== 'desc') {
72
+ throw new Error('query.sortOrder must be "asc" or "desc" if provided')
73
+ }
74
+
75
+ return await this.storage.findAll(limit, skip, sortOrder)
76
+ }
68
77
 
69
- // Build the query object dynamically to omit any undefined values.
70
- const query: Partial<SLAPQuery> = {}
71
- if (domain !== undefined) query.domain = domain
72
- if (service !== undefined) query.service = service
73
- if (identityKey !== undefined) query.identityKey = identityKey
78
+ // Handle specific query with domain, service, identityKey
79
+ const { domain, service, identityKey, limit, skip, sortOrder } = query
80
+
81
+ // Validate query parameters
82
+ if (typeof domain !== 'undefined' && typeof domain !== 'string') {
83
+ throw new Error('query.domain must be a string if provided')
84
+ }
85
+ if (typeof service !== 'undefined' && typeof service !== 'string') {
86
+ throw new Error('query.service must be a string if provided')
87
+ }
88
+ if (typeof identityKey !== 'undefined' && typeof identityKey !== 'string') {
89
+ throw new Error('query.identityKey must be a string if provided')
90
+ }
91
+
92
+ // Validate pagination parameters
93
+ if (typeof limit !== 'undefined' && (typeof limit !== 'number' || limit < 0)) {
94
+ throw new Error('query.limit must be a positive number if provided')
95
+ }
96
+ if (typeof skip !== 'undefined' && (typeof skip !== 'number' || skip < 0)) {
97
+ throw new Error('query.skip must be a non-negative number if provided')
98
+ }
99
+ if (typeof sortOrder !== 'undefined' && sortOrder !== 'asc' && sortOrder !== 'desc') {
100
+ throw new Error('query.sortOrder must be "asc" or "desc" if provided')
101
+ }
102
+
103
+ // Build the query object dynamically to omit any undefined values
104
+ const queryParams: Partial<SLAPQuery> = {}
105
+ if (domain !== undefined) queryParams.domain = domain
106
+ if (service !== undefined) queryParams.service = service
107
+ if (identityKey !== undefined) queryParams.identityKey = identityKey
108
+ if (limit !== undefined) queryParams.limit = limit
109
+ if (skip !== undefined) queryParams.skip = skip
110
+ if (sortOrder !== undefined) queryParams.sortOrder = sortOrder
111
+
112
+ return await this.storage.findRecord(queryParams)
113
+ }
74
114
 
75
- const result = await this.storage.findRecord(query)
76
- console.log('LOOKUP RESULT', result)
77
- return result
115
+ throw new Error('Invalid query format. Query must be "findAll" string or an object with valid parameters.')
78
116
  }
79
117
 
80
- async getDocumentation(): Promise<string> {
118
+ async getDocumentation (): Promise<string> {
81
119
  return SLAPLookupDocs
82
120
  }
83
121
 
84
- async getMetaData(): Promise<{
122
+ async getMetaData (): Promise<{
85
123
  name: string
86
124
  shortDescription: string
87
125
  iconURL?: string
@@ -11,14 +11,14 @@ export class SLAPStorage {
11
11
  * Constructs a new SLAPStorage instance
12
12
  * @param {Db} db - connected mongo database instance
13
13
  */
14
- constructor(private readonly db: Db) {
14
+ constructor (private readonly db: Db) {
15
15
  this.slapRecords = db.collection<SLAPRecord>('slapRecords')
16
16
  }
17
17
 
18
18
  /**
19
19
  * Ensures the necessary indexes are created for the collections.
20
20
  */
21
- async ensureIndexes(): Promise<void> {
21
+ async ensureIndexes (): Promise<void> {
22
22
  await this.slapRecords.createIndex({ domain: 1, service: 1 })
23
23
  }
24
24
 
@@ -30,7 +30,7 @@ export class SLAPStorage {
30
30
  * @param {string} domain domain name
31
31
  * @param {string} service service name
32
32
  */
33
- async storeSLAPRecord(txid: string, outputIndex: number, identityKey: string, domain: string, service: string): Promise<void> {
33
+ async storeSLAPRecord (txid: string, outputIndex: number, identityKey: string, domain: string, service: string): Promise<void> {
34
34
  await this.slapRecords.insertOne({
35
35
  txid,
36
36
  outputIndex,
@@ -46,7 +46,7 @@ export class SLAPStorage {
46
46
  * @param {string} txid transaction id
47
47
  * @param {number} outputIndex index of the UTXO
48
48
  */
49
- async deleteSLAPRecord(txid: string, outputIndex: number): Promise<void> {
49
+ async deleteSLAPRecord (txid: string, outputIndex: number): Promise<void> {
50
50
  await this.slapRecords.deleteOne({ txid, outputIndex })
51
51
  }
52
52
 
@@ -55,7 +55,7 @@ export class SLAPStorage {
55
55
  * @param {Object} query The query object which may contain properties for domain, service, and/or identityKey.
56
56
  * @returns {Promise<UTXOReference[]>} returns matching UTXO references
57
57
  */
58
- async findRecord(query: SLAPQuery): Promise<UTXOReference[]> {
58
+ async findRecord (query: SLAPQuery): Promise<UTXOReference[]> {
59
59
  const mongoQuery: any = {}
60
60
 
61
61
  // Add domain to the query if provided
@@ -72,10 +72,24 @@ export class SLAPStorage {
72
72
  if (typeof query.identityKey === 'string') {
73
73
  mongoQuery.identityKey = query.identityKey
74
74
  }
75
- console.log(mongoQuery)
76
75
 
77
- return await this.slapRecords.find(mongoQuery)
78
- .project<UTXOReference>({ txid: 1, outputIndex: 1 })
76
+ // Build the query with pagination
77
+ let cursor = this.slapRecords
78
+ .find(mongoQuery)
79
+ .project<UTXOReference>({ txid: 1, outputIndex: 1, createdAt: 1 })
80
+
81
+ cursor.sort({ createdAt: query.sortOrder ?? -1 })
82
+
83
+ // Apply pagination if provided
84
+ if (typeof query.skip === 'number' && query.skip > 0) {
85
+ cursor = cursor.skip(query.skip)
86
+ }
87
+
88
+ if (typeof query.limit === 'number' && query.limit > 0) {
89
+ cursor = cursor.limit(query.limit)
90
+ }
91
+
92
+ return await cursor
79
93
  .toArray()
80
94
  .then(results => results.map(record => ({
81
95
  txid: record.txid,
@@ -85,11 +99,27 @@ export class SLAPStorage {
85
99
 
86
100
  /**
87
101
  * Returns all results tracked by the overlay
102
+ * @param {number} limit Optional limit for pagination
103
+ * @param {number} skip Optional skip for pagination
104
+ * @param {string} sortOrder Optional sort order
88
105
  * @returns {Promise<UTXOReference[]>} returns matching UTXO references
89
106
  */
90
- async findAll(): Promise<UTXOReference[]> {
91
- return await this.slapRecords.find({})
92
- .project<UTXOReference>({ txid: 1, outputIndex: 1 })
107
+ async findAll (limit?: number, skip?: number, sortOrder?: 'asc' | 'desc'): Promise<UTXOReference[]> {
108
+ let cursor = this.slapRecords.find({})
109
+ .project<UTXOReference>({ txid: 1, outputIndex: 1, createdAt: 1 })
110
+
111
+ // Apply pagination if provided
112
+ cursor.sort({ createdAt: sortOrder ?? -1 })
113
+
114
+ if (typeof skip === 'number' && skip > 0) {
115
+ cursor = cursor.skip(skip)
116
+ }
117
+
118
+ if (typeof limit === 'number' && limit > 0) {
119
+ cursor = cursor.limit(limit)
120
+ }
121
+
122
+ return await cursor
93
123
  .toArray()
94
124
  .then(results => results.map(slapRecords => ({
95
125
  txid: slapRecords.txid,
@@ -19,7 +19,7 @@ export class SLAPTopicManager implements TopicManager {
19
19
  * @param previousCoins - The previous coins to consider.
20
20
  * @returns A promise that resolves with the admittance instructions.
21
21
  */
22
- async identifyAdmissibleOutputs(
22
+ async identifyAdmissibleOutputs (
23
23
  beef: number[],
24
24
  previousCoins: number[]
25
25
  ): Promise<AdmittanceInstructions> {
@@ -79,7 +79,7 @@ export class SLAPTopicManager implements TopicManager {
79
79
  * Returns documentation specific to the SLAP topic manager.
80
80
  * @returns A promise that resolves to the documentation string.
81
81
  */
82
- async getDocumentation(): Promise<string> {
82
+ async getDocumentation (): Promise<string> {
83
83
  return SLAPTopicDocs
84
84
  }
85
85
 
@@ -87,7 +87,7 @@ export class SLAPTopicManager implements TopicManager {
87
87
  * Returns metadata associated with this topic manager.
88
88
  * @returns A promise that resolves to an object containing metadata.
89
89
  */
90
- async getMetaData(): Promise<{
90
+ async getMetaData (): Promise<{
91
91
  name: string
92
92
  shortDescription: string
93
93
  iconURL?: string
package/src/types.ts CHANGED
@@ -22,13 +22,21 @@ export interface SLAPRecord {
22
22
  }
23
23
 
24
24
  export interface SHIPQuery {
25
+ findAll?: boolean
25
26
  domain?: string
26
27
  topics?: string[]
27
28
  identityKey?: string
29
+ limit?: number
30
+ skip?: number
31
+ sortOrder?: 'asc' | 'desc'
28
32
  }
29
33
 
30
34
  export interface SLAPQuery {
35
+ findAll?: boolean
31
36
  domain?: string
32
37
  service?: string
33
38
  identityKey?: string
39
+ limit?: number
40
+ skip?: number
41
+ sortOrder?: 'asc' | 'desc'
34
42
  }