jinaga-server 3.6.0 → 3.7.1

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 (32) hide show
  1. package/dist/authorization/authorization-keystore.d.ts +7 -1
  2. package/dist/authorization/authorization-keystore.js +16 -5
  3. package/dist/authorization/authorization-keystore.js.map +1 -1
  4. package/dist/feeds/feed-stream-session.d.ts +84 -0
  5. package/dist/feeds/feed-stream-session.js +331 -0
  6. package/dist/feeds/feed-stream-session.js.map +1 -0
  7. package/dist/http/router.d.ts +7 -3
  8. package/dist/http/router.js +130 -238
  9. package/dist/http/router.js.map +1 -1
  10. package/dist/index.d.ts +2 -1
  11. package/dist/index.js +5 -1
  12. package/dist/index.js.map +1 -1
  13. package/dist/jinaga-server.d.ts +7 -0
  14. package/dist/jinaga-server.js +1 -1
  15. package/dist/jinaga-server.js.map +1 -1
  16. package/docs/design/feeds-persistent-proxy-plan.md +11 -1
  17. package/integration-test/jinaga-test/package-lock.json +4 -4
  18. package/integration-test/jinaga-test/package.json +1 -1
  19. package/package.json +2 -2
  20. package/src/authorization/authorization-keystore.ts +33 -7
  21. package/src/feeds/feed-stream-session.ts +386 -0
  22. package/src/http/router.ts +149 -289
  23. package/src/index.ts +2 -1
  24. package/src/jinaga-server.ts +9 -2
  25. package/test/authorization/intermediateProjectionFeedSpec.ts +189 -0
  26. package/test/authorization/subscriptionIntersectionSpec.ts +12 -0
  27. package/test/feeds/feedStreamSessionRegressionSpec.ts +227 -0
  28. package/test/feeds/feedStreamSessionSpec.ts +351 -0
  29. package/test/http/feedStreamSessionIntegrationSpec.ts +202 -0
  30. package/test/http/graphSaveMultiHopAuthSpec.ts +126 -0
  31. package/test/http/lateAuthRecoverySpec.ts +191 -1
  32. package/test/http/streamFeedAnchorSpec.ts +3 -1
package/dist/index.d.ts CHANGED
@@ -2,8 +2,9 @@ export { AuthenticationDevice } from "./authentication/authentication-device";
2
2
  export { AuthenticationSession } from "./authentication/authentication-session";
3
3
  export { AuthorizationKeystore } from "./authorization/authorization-keystore";
4
4
  export { DistributedFactCache } from "./authorization/distributed-fact-cache";
5
+ export { FeedStreamSession, FeedStreamSessionConfig, defaultFeedStreamSessionConfig } from "./feeds/feed-stream-session";
5
6
  export { createLineReader } from "./http/line-reader";
6
- export { HttpRouter, RequestUser } from "./http/router";
7
+ export { FeedNotFound, HttpRouter, RequestUser } from "./http/router";
7
8
  export { Stream } from "./http/stream";
8
9
  export { JinagaServer, JinagaServerConfig, JinagaServerInstance, tracePool } from "./jinaga-server";
9
10
  export { Keystore } from "./keystore";
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PostgresStore = exports.PostgresKeystore = exports.MemoryKeystore = exports.tracePool = exports.JinagaServer = exports.Stream = exports.HttpRouter = exports.createLineReader = exports.DistributedFactCache = exports.AuthorizationKeystore = exports.AuthenticationSession = exports.AuthenticationDevice = void 0;
3
+ exports.PostgresStore = exports.PostgresKeystore = exports.MemoryKeystore = exports.tracePool = exports.JinagaServer = exports.Stream = exports.HttpRouter = exports.FeedNotFound = exports.createLineReader = exports.defaultFeedStreamSessionConfig = exports.FeedStreamSession = exports.DistributedFactCache = exports.AuthorizationKeystore = exports.AuthenticationSession = exports.AuthenticationDevice = void 0;
4
4
  var authentication_device_1 = require("./authentication/authentication-device");
5
5
  Object.defineProperty(exports, "AuthenticationDevice", { enumerable: true, get: function () { return authentication_device_1.AuthenticationDevice; } });
6
6
  var authentication_session_1 = require("./authentication/authentication-session");
@@ -9,9 +9,13 @@ var authorization_keystore_1 = require("./authorization/authorization-keystore")
9
9
  Object.defineProperty(exports, "AuthorizationKeystore", { enumerable: true, get: function () { return authorization_keystore_1.AuthorizationKeystore; } });
10
10
  var distributed_fact_cache_1 = require("./authorization/distributed-fact-cache");
11
11
  Object.defineProperty(exports, "DistributedFactCache", { enumerable: true, get: function () { return distributed_fact_cache_1.DistributedFactCache; } });
12
+ var feed_stream_session_1 = require("./feeds/feed-stream-session");
13
+ Object.defineProperty(exports, "FeedStreamSession", { enumerable: true, get: function () { return feed_stream_session_1.FeedStreamSession; } });
14
+ Object.defineProperty(exports, "defaultFeedStreamSessionConfig", { enumerable: true, get: function () { return feed_stream_session_1.defaultFeedStreamSessionConfig; } });
12
15
  var line_reader_1 = require("./http/line-reader");
13
16
  Object.defineProperty(exports, "createLineReader", { enumerable: true, get: function () { return line_reader_1.createLineReader; } });
14
17
  var router_1 = require("./http/router");
18
+ Object.defineProperty(exports, "FeedNotFound", { enumerable: true, get: function () { return router_1.FeedNotFound; } });
15
19
  Object.defineProperty(exports, "HttpRouter", { enumerable: true, get: function () { return router_1.HttpRouter; } });
16
20
  var stream_1 = require("./http/stream");
17
21
  Object.defineProperty(exports, "Stream", { enumerable: true, get: function () { return stream_1.Stream; } });
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,gFAA8E;AAArE,6HAAA,oBAAoB,OAAA;AAC7B,kFAAgF;AAAvE,+HAAA,qBAAqB,OAAA;AAC9B,iFAA+E;AAAtE,+HAAA,qBAAqB,OAAA;AAC9B,iFAA8E;AAArE,8HAAA,oBAAoB,OAAA;AAC7B,kDAAsD;AAA7C,+GAAA,gBAAgB,OAAA;AACzB,wCAAwD;AAA/C,oGAAA,UAAU,OAAA;AACnB,wCAAuC;AAA9B,gGAAA,MAAM,OAAA;AACf,iDAAoG;AAA3F,6GAAA,YAAY,OAAA;AAA4C,0GAAA,SAAS,OAAA;AAE1E,4DAA0D;AAAjD,iHAAA,cAAc,OAAA;AACvB,kEAAgE;AAAvD,qHAAA,gBAAgB,OAAA;AACzB,4DAA0D;AAAjD,+GAAA,aAAa,OAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,gFAA8E;AAArE,6HAAA,oBAAoB,OAAA;AAC7B,kFAAgF;AAAvE,+HAAA,qBAAqB,OAAA;AAC9B,iFAA+E;AAAtE,+HAAA,qBAAqB,OAAA;AAC9B,iFAA8E;AAArE,8HAAA,oBAAoB,OAAA;AAC7B,mEAAyH;AAAhH,wHAAA,iBAAiB,OAAA;AAA2B,qIAAA,8BAA8B,OAAA;AACnF,kDAAsD;AAA7C,+GAAA,gBAAgB,OAAA;AACzB,wCAAsE;AAA7D,sGAAA,YAAY,OAAA;AAAE,oGAAA,UAAU,OAAA;AACjC,wCAAuC;AAA9B,gGAAA,MAAM,OAAA;AACf,iDAAoG;AAA3F,6GAAA,YAAY,OAAA;AAA4C,0GAAA,SAAS,OAAA;AAE1E,4DAA0D;AAAjD,iHAAA,cAAc,OAAA;AACvB,kEAAgE;AAAvD,qHAAA,gBAAgB,OAAA;AACzB,4DAA0D;AAAjD,+GAAA,aAAa,OAAA"}
@@ -1,6 +1,7 @@
1
1
  import { Handler, Request } from "express";
2
2
  import { AuthorizationRules, DistributionRules, FactManager, Jinaga, Model, PurgeConditions } from "jinaga";
3
3
  import { Pool } from "pg";
4
+ import { FeedStreamSessionConfig } from "./feeds/feed-stream-session";
4
5
  export type JinagaServerConfig = {
5
6
  pgStore?: string | Pool;
6
7
  pgStoreSchema?: string;
@@ -14,6 +15,12 @@ export type JinagaServerConfig = {
14
15
  distribution?: (d: DistributionRules) => DistributionRules;
15
16
  purgeConditions?: (p: PurgeConditions) => PurgeConditions;
16
17
  origin?: string | string[] | ((origin: string, callback: (err: Error | null, allow?: boolean) => void) => void);
18
+ /**
19
+ * Tuning for the per-connection feed streaming session
20
+ * (/feeds/:hash with Accept: application/x-jinaga-feed-stream).
21
+ * See FeedStreamSessionConfig for the available limits and defaults.
22
+ */
23
+ feedStream?: Partial<FeedStreamSessionConfig>;
17
24
  };
18
25
  export type JinagaServerInstance = {
19
26
  handler: Handler;
@@ -42,7 +42,7 @@ class JinagaServer {
42
42
  const purgeConditions = createPurgeConditions(config);
43
43
  const factManager = new jinaga_1.FactManager(fork, source, store, network, purgeConditions);
44
44
  const authorization = createAuthorization(authorizationRules, distributionRules, factManager, store, keystore);
45
- const router = new router_1.HttpRouter(factManager, authorization, feedCache, config.origin || '*');
45
+ const router = new router_1.HttpRouter(factManager, authorization, feedCache, config.origin || '*', config.feedStream || {});
46
46
  const j = new jinaga_1.Jinaga(authentication, factManager, syncStatusNotifier);
47
47
  function close() {
48
48
  return __awaiter(this, void 0, void 0, function* () {
@@ -1 +1 @@
1
- {"version":3,"file":"jinaga-server.js","sourceRoot":"","sources":["../src/jinaga-server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"jinaga-server.js","sourceRoot":"","sources":["../src/jinaga-server.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,18 @@
1
1
  # Feeds: Persistent Client Proxy with Waitlist
2
2
  Author: @michaellperry
3
- Status: Draft
3
+ Status: Implemented
4
4
  Scope: `/feeds/:hash` streaming path (`Accept: application/x-jinaga-feed-stream`)
5
5
 
6
+ > **Implementation note.** The session described here lives in
7
+ > `src/feeds/feed-stream-session.ts` (`FeedStreamSession`) and is wired into
8
+ > the `/feeds/:hash` streaming handler in `src/http/router.ts`. Limits are
9
+ > configurable through `JinagaServerConfig.feedStream`
10
+ > (`FeedStreamSessionConfig`). Note that stores which return the full result
11
+ > set with a non-advancing bookmark (e.g. the in-memory store) are treated as
12
+ > exhausted after the first page, at which point the session emits SYNC.
13
+ > Coverage: `test/feeds/feedStreamSessionSpec.ts` (unit) and
14
+ > `test/http/feedStreamSessionIntegrationSpec.ts` (integration).
15
+
6
16
  ## Summary
7
17
 
8
18
  Introduce a per-connection "FeedStreamSession" (client proxy) that:
@@ -14,7 +14,7 @@
14
14
  "express": "^4.21.2",
15
15
  "jest": "^29.7.0",
16
16
  "jest-compact-reporter": "^1.2.9",
17
- "jinaga": "^6.8.0",
17
+ "jinaga": "^6.8.3",
18
18
  "node-forge": "^1.3.0",
19
19
  "pg": "^8.13.1"
20
20
  },
@@ -3195,9 +3195,9 @@
3195
3195
  }
3196
3196
  },
3197
3197
  "node_modules/jinaga": {
3198
- "version": "6.8.0",
3199
- "resolved": "https://registry.npmjs.org/jinaga/-/jinaga-6.8.0.tgz",
3200
- "integrity": "sha512-YN2CtA3PcIDpLR/2fQQTSX5IeoxynEVXLtvHWfK2bUpDrPzfDjsxc8ALbN7OZlEVMeOZuxvtQdgHYH79qqe0iQ==",
3198
+ "version": "6.8.3",
3199
+ "resolved": "https://registry.npmjs.org/jinaga/-/jinaga-6.8.3.tgz",
3200
+ "integrity": "sha512-v3dvRQKlXj++uT9D/AxFfRIeKwzitqYAs9Fxx5Q+wbCyYM+wHv4eRH9qCRcOIjv/jnl8iO/3vGGFZz+z2eMQgw==",
3201
3201
  "license": "MIT",
3202
3202
  "dependencies": {
3203
3203
  "@stablelib/base64": "^1.0.1",
@@ -19,7 +19,7 @@
19
19
  "express": "^4.21.2",
20
20
  "jest": "^29.7.0",
21
21
  "jest-compact-reporter": "^1.2.9",
22
- "jinaga": "^6.8.0",
22
+ "jinaga": "^6.8.3",
23
23
  "node-forge": "^1.3.0",
24
24
  "pg": "^8.13.1"
25
25
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jinaga-server",
3
- "version": "3.6.0",
3
+ "version": "3.7.1",
4
4
  "description": "Data management for web and mobile applications.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -49,7 +49,7 @@
49
49
  "dependencies": {
50
50
  "csv-stringify": "^6.6.0",
51
51
  "express": "^4.21.2",
52
- "jinaga": "^6.8.0",
52
+ "jinaga": "^6.11.1",
53
53
  "pg": "^8.13.1"
54
54
  }
55
55
  }
@@ -2,6 +2,7 @@ import {
2
2
  Authorization,
3
3
  AuthorizationEngine,
4
4
  AuthorizationRules,
5
+ DistributionDenialCode,
5
6
  DistributionEngine,
6
7
  DistributionRules,
7
8
  FactEnvelope,
@@ -22,9 +23,20 @@ export interface DistributionIntersectionBranch {
22
23
  specification: Specification;
23
24
  }
24
25
 
26
+ // The engine failure that made a subscription reactive (authorized via
27
+ // intersection) or denied. Carried so POST /feeds can report the structured
28
+ // per-feed decision (issue #168 S1) without recomputing distribution.
29
+ export interface DistributionDenial {
30
+ reason: string;
31
+ code: DistributionDenialCode;
32
+ }
33
+
25
34
  export type DistributionBranchesResult =
26
- | { type: "success"; branches: DistributionIntersectionBranch[] }
27
- | { type: "denied"; reason: string };
35
+ // Pass-through authorized when `denial` is absent; authorized-via-
36
+ // intersection (reactive) when `denial` carries the engine failure that
37
+ // triggered the intersection.
38
+ | { type: "success"; branches: DistributionIntersectionBranch[]; denial?: DistributionDenial }
39
+ | { type: "denied"; reason: string; code: DistributionDenialCode };
28
40
 
29
41
  export type FeedResult =
30
42
  | { type: "success"; feed: FactFeed }
@@ -53,6 +65,10 @@ export interface SubscriptionAuthorizer {
53
65
  import { Keystore } from "../keystore";
54
66
  import { DistributedFactCache } from "./distributed-fact-cache";
55
67
 
68
+ function factKey(reference: FactReference): string {
69
+ return `${reference.type}:${reference.hash}`;
70
+ }
71
+
56
72
  export class AuthorizationKeystore implements Authorization, SubscriptionAuthorizer {
57
73
  private authorizationEngine: AuthorizationEngine | null;
58
74
  private distributionEngine: DistributionEngine | null;
@@ -176,14 +192,17 @@ export class AuthorizationKeystore implements Authorization, SubscriptionAuthori
176
192
  const signedFacts = (userIdentity && factsToSign.length > 0)
177
193
  ? await this.keystore.signFacts(userIdentity, factsToSign)
178
194
  : [];
195
+ const envelopeByFact = new Map<string, FactEnvelope>(
196
+ envelopes.map(e => [factKey(e.fact), e]));
197
+ const signedFactByFact = new Map<string, FactEnvelope>(
198
+ signedFacts.map(f => [factKey(f.fact), f]));
179
199
  const authorizedEnvelopes: FactEnvelope[] = results.map(r => {
180
- const isFact = factReferenceEquals(r.fact);
181
- const envelope = envelopes.find(e => isFact(e.fact));
200
+ const envelope = envelopeByFact.get(factKey(r.fact));
182
201
  if (!envelope) {
183
202
  throw new Error("Fact not found in envelopes.");
184
203
  }
185
204
  if (r.verdict === "Accept") {
186
- const signedFact = signedFacts.find(f => isFact(f.fact));
205
+ const signedFact = signedFactByFact.get(factKey(r.fact));
187
206
  const userSignatures = signedFact
188
207
  ? signedFact.signatures
189
208
  : [];
@@ -241,10 +260,17 @@ export class AuthorizationKeystore implements Authorization, SubscriptionAuthori
241
260
  if (canDistribute.type === "success") {
242
261
  return { type: "success", branches: [{ start, specification }] };
243
262
  }
263
+ // Preserve the structured engine failure so the caller can classify
264
+ // the feed (issue #168 S1). `canDistribute` is narrowed to a failure
265
+ // here, so `code`/`reason` are available.
266
+ const denial: DistributionDenial = { reason: canDistribute.reason, code: canDistribute.code };
244
267
  const result = await this.distributionEngine.intersectForSubscribe(start, specification, userReference);
245
268
  if (!result.intersected) {
246
- return { type: "denied", reason: canDistribute.reason };
269
+ return { type: "denied", reason: denial.reason, code: denial.code };
247
270
  }
248
- return { type: "success", branches: result.branches };
271
+ // Authorized via intersection: the spec is denied for this user right
272
+ // now (denial carries why) but self-heals once the authorizing fact
273
+ // arrives. The router reports this as `reactive`.
274
+ return { type: "success", branches: result.branches, denial };
249
275
  }
250
276
  }
@@ -0,0 +1,386 @@
1
+ import {
2
+ computeTupleSubsetHash,
3
+ FactManager,
4
+ FactReference,
5
+ FeedObject,
6
+ FeedResponse,
7
+ invertSpecification,
8
+ ProjectedResult,
9
+ Specification,
10
+ SpecificationListener,
11
+ Trace
12
+ } from "jinaga";
13
+ import { FeedResult } from "../authorization/authorization-keystore";
14
+ import { Stream } from "../http/stream";
15
+
16
+ /**
17
+ * A function that queries the feed for the next page of tuples, starting
18
+ * from the supplied bookmark. Returns a {@link FeedResult} so the session
19
+ * can keep the stream alive when distribution is denied at query time.
20
+ */
21
+ export type FeedQuery = (bookmark: string) => Promise<FeedResult>;
22
+
23
+ export interface FeedStreamSessionConfig {
24
+ /** Maximum number of pages to stream before giving up (infinite-loop guard). */
25
+ maxInitialPages: number;
26
+ /** Maximum number of references the waitlist may hold before the stream is dropped. */
27
+ waitlistCap: number;
28
+ /** Insert a small delay every N pages to avoid overwhelming the database. */
29
+ pageDelayEvery: number;
30
+ /** Length of the delay inserted by {@link pageDelayEvery}, in milliseconds. */
31
+ pageDelayMs: number;
32
+ }
33
+
34
+ export const defaultFeedStreamSessionConfig: FeedStreamSessionConfig = {
35
+ maxInitialPages: 1000,
36
+ waitlistCap: 50000,
37
+ pageDelayEvery: 10,
38
+ pageDelayMs: 10
39
+ };
40
+
41
+ interface FeedStreamSessionTelemetry {
42
+ initialPages: number;
43
+ referencesStreamed: number;
44
+ cycles: number;
45
+ syncFrames: number;
46
+ waitlistRestarts: number;
47
+ waitlistHighWater: number;
48
+ }
49
+
50
+ function referenceKey(reference: FactReference): string {
51
+ return `${reference.type}:${reference.hash}`;
52
+ }
53
+
54
+ function dedupeReferences(references: FactReference[]): FactReference[] {
55
+ const seen = new Set<string>();
56
+ const result: FactReference[] = [];
57
+ for (const reference of references) {
58
+ const key = referenceKey(reference);
59
+ if (!seen.has(key)) {
60
+ seen.add(key);
61
+ result.push(reference);
62
+ }
63
+ }
64
+ return result;
65
+ }
66
+
67
+ /**
68
+ * Per-connection proxy for a streaming feed subscription.
69
+ *
70
+ * The session funnels every fetch and stream operation through a single
71
+ * "query cycle" driven by the current bookmark. Inverse observers never
72
+ * fetch or stream directly; they only enqueue fact references onto a
73
+ * waitlist and wake the cycle. This serialization removes the race between
74
+ * listener callbacks and bookmark mutation, guarantees tuple completeness,
75
+ * and ensures no update is lost when facts arrive between pages.
76
+ *
77
+ * See docs/design/feeds-persistent-proxy-plan.md for the full design.
78
+ */
79
+ export class FeedStreamSession {
80
+ private bookmark: string;
81
+ private readonly config: FeedStreamSessionConfig;
82
+
83
+ // Waitlist of fact references surfaced by inverse observers, keyed by
84
+ // "{type}:{hash}" so duplicates collapse. A reference stays on the
85
+ // waitlist until the tuple that contains it is streamed, preserving
86
+ // tuple completeness.
87
+ private readonly waitlist = new Map<string, FactReference>();
88
+
89
+ // Single-cycle execution guard. Only one runCycle() executes at a time;
90
+ // wake-ups that arrive while a cycle runs set needsCycle so the loop
91
+ // re-evaluates before returning.
92
+ private activeCycle = false;
93
+ private needsCycle = false;
94
+ private shuttingDown = false;
95
+
96
+ private listeners: SpecificationListener[] = [];
97
+ private readonly telemetry: FeedStreamSessionTelemetry = {
98
+ initialPages: 0,
99
+ referencesStreamed: 0,
100
+ cycles: 0,
101
+ syncFrames: 0,
102
+ waitlistRestarts: 0,
103
+ waitlistHighWater: 0
104
+ };
105
+
106
+ constructor(
107
+ private readonly query: FeedQuery,
108
+ private readonly factManager: FactManager,
109
+ private readonly feedDefinition: FeedObject,
110
+ private readonly startReferences: FactReference[],
111
+ private readonly givenHash: string,
112
+ private readonly stream: Stream<FeedResponse>,
113
+ initialBookmark: string,
114
+ private readonly connectionId: string = Math.random().toString(36).substring(2, 10),
115
+ config: Partial<FeedStreamSessionConfig> = {}
116
+ ) {
117
+ this.bookmark = initialBookmark;
118
+ this.config = { ...defaultFeedStreamSessionConfig, ...config };
119
+ }
120
+
121
+ /** Current number of references on the waitlist (observability/testing). */
122
+ get waitlistSize(): number {
123
+ return this.waitlist.size;
124
+ }
125
+
126
+ /**
127
+ * Register inverse and anchor observers, then kick off the initial
128
+ * paginated backfill. Resolves once the observers are registered; the
129
+ * cycle continues to run asynchronously, feeding the stream.
130
+ */
131
+ start(): void {
132
+ this.registerInverseListeners();
133
+ this.registerAnchorListeners();
134
+ // Kick off the initial backfill. Errors are isolated so a failed
135
+ // cycle never rejects the caller's setup path.
136
+ this.wake();
137
+ }
138
+
139
+ /**
140
+ * Tear down all observers and prevent any further cycles. Idempotent.
141
+ */
142
+ dispose(): void {
143
+ if (this.shuttingDown) {
144
+ return;
145
+ }
146
+ this.shuttingDown = true;
147
+ for (const listener of this.listeners) {
148
+ try {
149
+ this.factManager.removeSpecificationListener(listener);
150
+ } catch (error) {
151
+ Trace.error(error);
152
+ }
153
+ }
154
+ this.listeners = [];
155
+ // Use a stable metric name so a metrics backend does not see
156
+ // unbounded cardinality from per-connection IDs. The connection ID
157
+ // is logged separately for correlation.
158
+ Trace.info(`Feed stream session ${this.connectionId} closed`);
159
+ Trace.metric("Feed stream session closed", {
160
+ initialPages: this.telemetry.initialPages,
161
+ referencesStreamed: this.telemetry.referencesStreamed,
162
+ cycles: this.telemetry.cycles,
163
+ syncFrames: this.telemetry.syncFrames,
164
+ waitlistRestarts: this.telemetry.waitlistRestarts,
165
+ waitlistHighWater: this.telemetry.waitlistHighWater
166
+ });
167
+ }
168
+
169
+ /**
170
+ * Add fact references to the waitlist and wake the cycle. Called by the
171
+ * inverse observers; never fetches or streams directly.
172
+ */
173
+ onInverseMatch(references: FactReference[]): void {
174
+ if (this.shuttingDown) {
175
+ return;
176
+ }
177
+ for (const reference of references) {
178
+ this.waitlist.set(referenceKey(reference), reference);
179
+ }
180
+ if (this.waitlist.size > this.telemetry.waitlistHighWater) {
181
+ this.telemetry.waitlistHighWater = this.waitlist.size;
182
+ }
183
+ if (this.waitlist.size > this.config.waitlistCap) {
184
+ Trace.warn(`Feed stream session ${this.connectionId} waitlist exceeded cap ` +
185
+ `(${this.waitlist.size} > ${this.config.waitlistCap}); closing stream.`);
186
+ this.dispose();
187
+ this.stream.close();
188
+ return;
189
+ }
190
+ this.wake();
191
+ }
192
+
193
+ /**
194
+ * Start a cycle if one is not already running. If a cycle is active,
195
+ * mark that another pass is needed once it completes.
196
+ */
197
+ private wake(): void {
198
+ if (this.shuttingDown) {
199
+ return;
200
+ }
201
+ if (this.activeCycle) {
202
+ this.needsCycle = true;
203
+ return;
204
+ }
205
+ // Run the cycle without awaiting; isolate failures.
206
+ void this.runCycle().catch(error => Trace.error(error));
207
+ }
208
+
209
+ /**
210
+ * The single serialized query cycle. Pages through the feed until both
211
+ * the query returns no tuples and the waitlist is empty, then emits a
212
+ * SYNC frame. Guarded by activeCycle so no two cycles run concurrently.
213
+ */
214
+ private async runCycle(): Promise<void> {
215
+ if (this.activeCycle) {
216
+ this.needsCycle = true;
217
+ return;
218
+ }
219
+ this.activeCycle = true;
220
+ this.telemetry.cycles++;
221
+ try {
222
+ let pagesThisCycle = 0;
223
+ let keepGoing = true;
224
+ while (keepGoing && !this.shuttingDown) {
225
+ this.needsCycle = false;
226
+
227
+ const result = await this.query(this.bookmark);
228
+ if (this.shuttingDown) {
229
+ break;
230
+ }
231
+ if (result.type === "denied") {
232
+ // Distribution denied at query time. Keep the stream
233
+ // alive (an authorizing fact may arrive later and wake a
234
+ // new cycle) but stop the loop without emitting SYNC,
235
+ // since we are not actually caught up.
236
+ Trace.info(`Feed stream session ${this.connectionId} query denied: ${result.reason}`);
237
+ break;
238
+ }
239
+
240
+ const feed = result.feed;
241
+ if (feed.tuples.length > 0) {
242
+ const references = dedupeReferences(feed.tuples.flatMap(t => t.facts));
243
+ this.streamPage(references, feed.bookmark);
244
+ this.removeFromWaitlist(references);
245
+ pagesThisCycle++;
246
+ this.telemetry.initialPages++;
247
+
248
+ if (feed.bookmark === this.bookmark) {
249
+ // The store returned results but the bookmark did not
250
+ // advance, meaning it cannot paginate further (e.g.
251
+ // MemoryStore returns the full result set on every
252
+ // call). Everything available has been delivered, so
253
+ // treat this as caught-up: drop any waitlist entries
254
+ // that the full page did not satisfy and emit SYNC.
255
+ // Re-querying with the same bookmark would loop
256
+ // forever re-delivering identical tuples.
257
+ this.waitlist.clear();
258
+ this.streamSync();
259
+ keepGoing = false;
260
+ }
261
+ else {
262
+ this.bookmark = feed.bookmark;
263
+ if (pagesThisCycle >= this.config.maxInitialPages) {
264
+ Trace.warn(`Feed stream session ${this.connectionId} hit max page limit ` +
265
+ `(${this.config.maxInitialPages}); ending cycle.`);
266
+ break;
267
+ }
268
+ if (this.config.pageDelayEvery > 0 &&
269
+ pagesThisCycle % this.config.pageDelayEvery === 0) {
270
+ await this.delay(this.config.pageDelayMs);
271
+ }
272
+ // Continue paging.
273
+ }
274
+ }
275
+ else {
276
+ // No tuples this query.
277
+ if (this.waitlist.size === 0) {
278
+ this.streamSync();
279
+ keepGoing = false;
280
+ }
281
+ else if (this.needsCycle) {
282
+ // A wake arrived during this query; re-run so the
283
+ // just-arrived facts get a chance to produce tuples.
284
+ this.telemetry.waitlistRestarts++;
285
+ }
286
+ else {
287
+ // The waitlist holds references whose tuples are not
288
+ // completable at the current bookmark (e.g. a missing
289
+ // predecessor). They cannot produce a tuple now, so
290
+ // drop them to avoid spinning and emit SYNC. A later
291
+ // trigger will re-add them once they are completable.
292
+ this.waitlist.clear();
293
+ this.streamSync();
294
+ keepGoing = false;
295
+ }
296
+ }
297
+ }
298
+ } finally {
299
+ this.activeCycle = false;
300
+ // A wake that arrived as the loop exited must start a fresh
301
+ // cycle, since this one already cleared activeCycle.
302
+ if (this.needsCycle && !this.shuttingDown) {
303
+ this.needsCycle = false;
304
+ this.wake();
305
+ }
306
+ }
307
+ }
308
+
309
+ private streamPage(references: FactReference[], bookmark: string): void {
310
+ const response: FeedResponse = { references, bookmark };
311
+ this.telemetry.referencesStreamed += references.length;
312
+ this.stream.feed(response);
313
+ }
314
+
315
+ private streamSync(): void {
316
+ const response: FeedResponse = { references: [], bookmark: this.bookmark };
317
+ this.telemetry.syncFrames++;
318
+ this.stream.feed(response);
319
+ }
320
+
321
+ private removeFromWaitlist(references: FactReference[]): void {
322
+ for (const reference of references) {
323
+ this.waitlist.delete(referenceKey(reference));
324
+ }
325
+ }
326
+
327
+ private delay(ms: number): Promise<void> {
328
+ return new Promise(resolve => setTimeout(resolve, ms));
329
+ }
330
+
331
+ private registerInverseListeners(): void {
332
+ const inverses = invertSpecification(this.feedDefinition.feed);
333
+ for (const inverse of inverses) {
334
+ const listener = this.factManager.addSpecificationListener(
335
+ inverse.inverseSpecification,
336
+ async (results: ProjectedResult[]) => {
337
+ if (this.shuttingDown) {
338
+ return;
339
+ }
340
+ // Only enqueue results whose given subset matches this
341
+ // subscription's given.
342
+ const matching = results.filter(pr =>
343
+ this.givenHash === computeTupleSubsetHash(pr.tuple, inverse.givenSubset));
344
+ if (matching.length === 0) {
345
+ return;
346
+ }
347
+ const references = dedupeReferences(
348
+ matching.flatMap(pr => Object.values(pr.tuple)));
349
+ this.onInverseMatch(references);
350
+ });
351
+ this.listeners.push(listener);
352
+ }
353
+ }
354
+
355
+ private registerAnchorListeners(): void {
356
+ // Anchor listeners (jinaga.js#129): the inverse-listener path only
357
+ // fires when a descendant of the given arrives. If the given fact
358
+ // itself isn't yet in the store when the client subscribes, no
359
+ // inverse can deliver results until the given is recorded. Register
360
+ // a listener per unique anchor that wakes the cycle the moment a
361
+ // saved fact matches the anchor's (type, hash). The anchor itself is
362
+ // not added to the waitlist; the query will surface its descendants.
363
+ const uniqueAnchors = this.startReferences.filter((s, i, arr) =>
364
+ arr.findIndex(other => other.type === s.type && other.hash === s.hash) === i);
365
+ for (const anchor of uniqueAnchors) {
366
+ const anchorSpec: Specification = {
367
+ given: [{ label: { name: "x", type: anchor.type }, conditions: [] }],
368
+ matches: [],
369
+ projection: { type: "fact", label: "x" }
370
+ };
371
+ const listener = this.factManager.addSpecificationListener(anchorSpec, async (results: ProjectedResult[]) => {
372
+ if (this.shuttingDown) {
373
+ return;
374
+ }
375
+ const matched = results.some(pr => {
376
+ const ref = pr.tuple && (pr.tuple as any).x;
377
+ return ref && ref.type === anchor.type && ref.hash === anchor.hash;
378
+ });
379
+ if (matched) {
380
+ this.wake();
381
+ }
382
+ });
383
+ this.listeners.push(listener);
384
+ }
385
+ }
386
+ }