jinaga-server 3.5.2 → 3.5.4
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/dist/authorization/authorization-keystore.js +2 -2
- package/dist/authorization/authorization-keystore.js.map +1 -1
- package/dist/http/csv-metadata.d.ts +24 -0
- package/dist/http/csv-metadata.js +3 -0
- package/dist/http/csv-metadata.js.map +1 -0
- package/dist/http/csv-validator.d.ts +23 -0
- package/dist/http/csv-validator.js +83 -0
- package/dist/http/csv-validator.js.map +1 -0
- package/dist/http/output-formatters.d.ts +28 -0
- package/dist/http/output-formatters.js +197 -0
- package/dist/http/output-formatters.js.map +1 -0
- package/dist/http/result-stream.d.ts +35 -0
- package/dist/http/result-stream.js +78 -0
- package/dist/http/result-stream.js.map +1 -0
- package/dist/http/router.d.ts +7 -1
- package/dist/http/router.js +98 -47
- package/dist/http/router.js.map +1 -1
- package/dist/postgres/postgres-store.js +7 -5
- package/dist/postgres/postgres-store.js.map +1 -1
- package/dist/postgres/purge-sql.js +15 -2
- package/dist/postgres/purge-sql.js.map +1 -1
- package/dist/postgres/specification-result-sql.d.ts +2 -0
- package/dist/postgres/specification-result-sql.js +49 -5
- package/dist/postgres/specification-result-sql.js.map +1 -1
- package/dist/postgres/specification-sql.js +28 -6
- package/dist/postgres/specification-sql.js.map +1 -1
- package/docs/design/feeds-persistent-proxy-plan.md +294 -0
- package/integration-test/integration-test/jinaga-test/package-lock.json +6 -0
- package/integration-test/jinaga-test/Dockerfile +1 -6
- package/integration-test/jinaga-test/http-test-helpers.js +77 -0
- package/integration-test/jinaga-test/jinaga.test.js +9 -9
- package/integration-test/jinaga-test/package-lock.json +329 -32
- package/integration-test/jinaga-test/package.json +5 -1
- package/integration-test/jinaga-test/read-csv.test.js +350 -0
- package/integration-test/jinaga-test/read-endpoint.test.js +442 -0
- package/integration-test/jinaga-test/read-performance.test.js +221 -0
- package/integration-test/jinaga-test/read-streaming.test.js +199 -0
- package/integration-test/jinaga-test/specification-conditions.test.js +147 -0
- package/integration-test/jinaga-test/test-helpers.js +12 -0
- package/integration-test/jinaga-test/time-projection.test.js +235 -0
- package/package.json +3 -2
- package/src/authorization/authorization-keystore.ts +4 -4
- package/src/http/csv-metadata.ts +34 -0
- package/src/http/csv-validator.ts +90 -0
- package/src/http/output-formatters.ts +196 -0
- package/src/http/result-stream.ts +69 -0
- package/src/http/router.ts +117 -34
- package/src/postgres/postgres-store.ts +13 -6
- package/src/postgres/purge-sql.ts +18 -2
- package/src/postgres/specification-result-sql.ts +70 -8
- package/src/postgres/specification-sql.ts +36 -8
- package/test/http/csv-validatorSpec.ts +205 -0
- package/test/http/result-streamSpec.ts +151 -0
- package/test/postgres/postgresReadSpec.ts +192 -14
- package/test/postgres/postgresSpecificationSpec.ts +3 -3
- package/test/postgres/specificationConditionsSpec.ts +268 -0
- package/documentation/streaming-feeds-full-results-solution.md +0 -230
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
# Solution: Full Results Delivery for Streamed Feeds Endpoint
|
|
2
|
-
|
|
3
|
-
## Problem Analysis
|
|
4
|
-
|
|
5
|
-
The current streaming feeds endpoint has a critical limitation: it only delivers the first 100 results from the initial query and then switches to push-only mode. This means clients never receive complete historical data for feeds with more than 100 matching results.
|
|
6
|
-
|
|
7
|
-
## Proposed Solution: Continuous Initial Query with Pagination
|
|
8
|
-
|
|
9
|
-
### Core Design Principle
|
|
10
|
-
|
|
11
|
-
Modify the streaming implementation to **continue fetching and delivering all historical results** before transitioning to real-time push mode, while maintaining the streaming connection throughout the process.
|
|
12
|
-
|
|
13
|
-
### Implementation Strategy
|
|
14
|
-
|
|
15
|
-
#### 1. Enhanced `streamFeed` Method
|
|
16
|
-
|
|
17
|
-
```typescript
|
|
18
|
-
private async streamFeed(user: RequestUser | null, params: { [key: string]: string }, query: qs.ParsedQs): Promise<Stream<FeedResponse> | null> {
|
|
19
|
-
const feedHash = params["hash"];
|
|
20
|
-
if (!feedHash) {
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const feedDefinition = await this.feedCache.getFeed(feedHash);
|
|
25
|
-
if (!feedDefinition) {
|
|
26
|
-
return null;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
let bookmark = query["b"] as string ?? "";
|
|
30
|
-
const userIdentity = serializeUserIdentity(user);
|
|
31
|
-
const start = feedDefinition.feed.given.map(label => feedDefinition.namedStart[label.name]);
|
|
32
|
-
const givenHash = computeObjectHash(feedDefinition.namedStart);
|
|
33
|
-
|
|
34
|
-
const stream = new Stream<FeedResponse>();
|
|
35
|
-
|
|
36
|
-
// NEW: Continuous initial query until exhausted
|
|
37
|
-
bookmark = await this.streamAllInitialResults(userIdentity, feedDefinition, start, bookmark, stream);
|
|
38
|
-
|
|
39
|
-
// Set up real-time listeners after initial data is complete
|
|
40
|
-
const inverses = invertSpecification(feedDefinition.feed);
|
|
41
|
-
const listeners = inverses.map(inverse => this.factManager.addSpecificationListener(
|
|
42
|
-
inverse.inverseSpecification,
|
|
43
|
-
async (results) => {
|
|
44
|
-
const matchingResults = results.filter(pr =>
|
|
45
|
-
givenHash === computeTupleSubsetHash(pr.tuple, inverse.givenSubset));
|
|
46
|
-
if (matchingResults.length != 0) {
|
|
47
|
-
bookmark = await this.streamFeedResponse(userIdentity, feedDefinition, start, bookmark, stream, true);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
));
|
|
51
|
-
|
|
52
|
-
stream.done(() => {
|
|
53
|
-
for (const listener of listeners) {
|
|
54
|
-
this.factManager.removeSpecificationListener(listener);
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
return stream;
|
|
59
|
-
}
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
#### 2. New `streamAllInitialResults` Method
|
|
63
|
-
|
|
64
|
-
```typescript
|
|
65
|
-
private async streamAllInitialResults(
|
|
66
|
-
userIdentity: UserIdentity | null,
|
|
67
|
-
feedDefinition: FeedObject,
|
|
68
|
-
start: FactReference[],
|
|
69
|
-
initialBookmark: string,
|
|
70
|
-
stream: Stream<FeedResponse>
|
|
71
|
-
): Promise<string> {
|
|
72
|
-
let bookmark = initialBookmark;
|
|
73
|
-
let hasMoreResults = true;
|
|
74
|
-
let pageCount = 0;
|
|
75
|
-
const maxPages = 1000; // Safety limit to prevent infinite loops
|
|
76
|
-
|
|
77
|
-
while (hasMoreResults && pageCount < maxPages) {
|
|
78
|
-
const results = await this.authorization.feed(userIdentity, feedDefinition.feed, start, bookmark);
|
|
79
|
-
|
|
80
|
-
// Check if we got results
|
|
81
|
-
if (results.tuples.length === 0) {
|
|
82
|
-
hasMoreResults = false;
|
|
83
|
-
break;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Process and send results
|
|
87
|
-
const references = results.tuples.flatMap(t => t.facts).filter((value, index, self) =>
|
|
88
|
-
self.findIndex(f => f.hash === value.hash && f.type === value.type) === index
|
|
89
|
-
);
|
|
90
|
-
|
|
91
|
-
if (references.length > 0) {
|
|
92
|
-
const response: FeedResponse = {
|
|
93
|
-
references,
|
|
94
|
-
bookmark: results.bookmark
|
|
95
|
-
};
|
|
96
|
-
stream.feed(response);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Update bookmark for next iteration
|
|
100
|
-
const newBookmark = results.bookmark;
|
|
101
|
-
if (newBookmark === bookmark) {
|
|
102
|
-
// No progress made, avoid infinite loop
|
|
103
|
-
hasMoreResults = false;
|
|
104
|
-
} else {
|
|
105
|
-
bookmark = newBookmark;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Check if we got fewer results than the page size (indicates end)
|
|
109
|
-
if (results.tuples.length < 100) {
|
|
110
|
-
hasMoreResults = false;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
pageCount++;
|
|
114
|
-
|
|
115
|
-
// Add small delay to prevent overwhelming the database
|
|
116
|
-
if (hasMoreResults && pageCount % 10 === 0) {
|
|
117
|
-
await new Promise(resolve => setTimeout(resolve, 10));
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
return bookmark;
|
|
122
|
-
}
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
### Key Design Features
|
|
126
|
-
|
|
127
|
-
#### 1. **Continuous Pagination**
|
|
128
|
-
- Loops through all available pages until no more results
|
|
129
|
-
- Uses bookmark progression to detect completion
|
|
130
|
-
- Includes safety limits to prevent infinite loops
|
|
131
|
-
|
|
132
|
-
#### 2. **Stream Integrity**
|
|
133
|
-
- Maintains single persistent connection throughout
|
|
134
|
-
- Delivers results incrementally as pages are fetched
|
|
135
|
-
- No interruption to client connection
|
|
136
|
-
|
|
137
|
-
#### 3. **Performance Safeguards**
|
|
138
|
-
- Maximum page limit (1000 pages = 100,000 results max)
|
|
139
|
-
- Periodic delays to prevent database overload
|
|
140
|
-
- Early termination on duplicate bookmarks
|
|
141
|
-
|
|
142
|
-
#### 4. **Backward Compatibility**
|
|
143
|
-
- Preserves existing API contract
|
|
144
|
-
- Maintains same response format
|
|
145
|
-
- No changes required to client implementations
|
|
146
|
-
|
|
147
|
-
### Alternative Approaches Considered
|
|
148
|
-
|
|
149
|
-
#### Option A: Async Background Fetching
|
|
150
|
-
**Rejected**: Would complicate error handling and resource cleanup
|
|
151
|
-
|
|
152
|
-
#### Option B: Configurable Page Limits
|
|
153
|
-
**Rejected**: Adds complexity without solving the core issue
|
|
154
|
-
|
|
155
|
-
#### Option C: Separate Historical vs Real-time Endpoints
|
|
156
|
-
**Rejected**: Breaks the unified streaming model
|
|
157
|
-
|
|
158
|
-
### Implementation Considerations
|
|
159
|
-
|
|
160
|
-
#### 1. **Database Impact**
|
|
161
|
-
- Multiple sequential queries instead of single query
|
|
162
|
-
- Potential increased load on connection pool
|
|
163
|
-
- Mitigation: Rate limiting and connection reuse
|
|
164
|
-
|
|
165
|
-
#### 2. **Memory Management**
|
|
166
|
-
- Stream queue may grow during rapid pagination
|
|
167
|
-
- Mitigation: Client processing should keep pace with delivery
|
|
168
|
-
- Consider: Optional memory pressure monitoring
|
|
169
|
-
|
|
170
|
-
#### 3. **Timeout Handling**
|
|
171
|
-
- 5-minute stream timeout may be insufficient for large datasets
|
|
172
|
-
- Consider: Dynamic timeout based on data volume
|
|
173
|
-
- Alternative: Configurable timeout per feed
|
|
174
|
-
|
|
175
|
-
#### 4. **Error Recovery**
|
|
176
|
-
- Database errors during pagination should not break stream
|
|
177
|
-
- Implement: Retry logic for individual page failures
|
|
178
|
-
- Fallback: Continue from last successful bookmark
|
|
179
|
-
|
|
180
|
-
### Testing Strategy
|
|
181
|
-
|
|
182
|
-
#### 1. **Unit Tests**
|
|
183
|
-
- Test pagination logic with various result set sizes
|
|
184
|
-
- Verify bookmark progression and termination conditions
|
|
185
|
-
- Test error scenarios and recovery
|
|
186
|
-
|
|
187
|
-
#### 2. **Integration Tests**
|
|
188
|
-
- Large dataset streaming (1000+ results)
|
|
189
|
-
- Concurrent stream handling
|
|
190
|
-
- Database connection pool behavior under load
|
|
191
|
-
|
|
192
|
-
#### 3. **Performance Tests**
|
|
193
|
-
- Memory usage during large result streaming
|
|
194
|
-
- Database query performance impact
|
|
195
|
-
- Client processing capability validation
|
|
196
|
-
|
|
197
|
-
### Deployment Considerations
|
|
198
|
-
|
|
199
|
-
#### 1. **Gradual Rollout**
|
|
200
|
-
- Feature flag to enable/disable full pagination
|
|
201
|
-
- Monitor database performance impact
|
|
202
|
-
- Rollback capability if issues arise
|
|
203
|
-
|
|
204
|
-
#### 2. **Monitoring**
|
|
205
|
-
- Track average pages per stream
|
|
206
|
-
- Monitor database connection pool utilization
|
|
207
|
-
- Alert on streams exceeding safety limits
|
|
208
|
-
|
|
209
|
-
#### 3. **Configuration**
|
|
210
|
-
- Make page limit configurable
|
|
211
|
-
- Allow timeout adjustment per deployment
|
|
212
|
-
- Enable/disable rate limiting
|
|
213
|
-
|
|
214
|
-
## Expected Outcomes
|
|
215
|
-
|
|
216
|
-
### Benefits
|
|
217
|
-
1. **Complete Data Delivery**: Clients receive all historical results
|
|
218
|
-
2. **Unified Experience**: Single endpoint for both historical and real-time data
|
|
219
|
-
3. **Streaming Efficiency**: Maintains connection throughout data delivery
|
|
220
|
-
4. **Backward Compatibility**: No breaking changes to existing clients
|
|
221
|
-
|
|
222
|
-
### Trade-offs
|
|
223
|
-
1. **Increased Database Load**: More queries per stream initialization
|
|
224
|
-
2. **Longer Initial Response Time**: Complete data delivery takes longer
|
|
225
|
-
3. **Memory Usage**: Larger stream queues during pagination
|
|
226
|
-
4. **Complexity**: More sophisticated error handling required
|
|
227
|
-
|
|
228
|
-
## Conclusion
|
|
229
|
-
|
|
230
|
-
This solution addresses the core limitation while maintaining the streaming architecture's benefits. The continuous pagination approach ensures complete data delivery without breaking the real-time streaming model, providing a robust foundation for applications requiring both historical completeness and live updates.
|