@atproto/bsky 0.0.216 → 0.0.217

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 (80) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/api/app/bsky/feed/searchPosts.d.ts.map +1 -1
  3. package/dist/api/app/bsky/feed/searchPosts.js +6 -4
  4. package/dist/api/app/bsky/feed/searchPosts.js.map +1 -1
  5. package/dist/api/app/bsky/graph/getSuggestedFollowsByActor.js.map +1 -1
  6. package/dist/api/app/bsky/unspecced/getPostThreadV2.js +1 -1
  7. package/dist/api/app/bsky/unspecced/getPostThreadV2.js.map +1 -1
  8. package/dist/api/app/bsky/unspecced/getSuggestedOnboardingUsers.d.ts.map +1 -1
  9. package/dist/api/app/bsky/unspecced/getSuggestedOnboardingUsers.js +9 -2
  10. package/dist/api/app/bsky/unspecced/getSuggestedOnboardingUsers.js.map +1 -1
  11. package/dist/api/app/bsky/unspecced/getSuggestedUsers.d.ts.map +1 -1
  12. package/dist/api/app/bsky/unspecced/getSuggestedUsers.js +9 -2
  13. package/dist/api/app/bsky/unspecced/getSuggestedUsers.js.map +1 -1
  14. package/dist/config.d.ts +2 -0
  15. package/dist/config.d.ts.map +1 -1
  16. package/dist/config.js +5 -0
  17. package/dist/config.js.map +1 -1
  18. package/dist/context.d.ts +3 -3
  19. package/dist/context.d.ts.map +1 -1
  20. package/dist/context.js +2 -2
  21. package/dist/context.js.map +1 -1
  22. package/dist/feature-gates/gates.d.ts +5 -0
  23. package/dist/feature-gates/gates.d.ts.map +1 -0
  24. package/dist/feature-gates/gates.js +6 -0
  25. package/dist/feature-gates/gates.js.map +1 -0
  26. package/dist/feature-gates/index.d.ts +24 -0
  27. package/dist/feature-gates/index.d.ts.map +1 -0
  28. package/dist/feature-gates/index.js +135 -0
  29. package/dist/feature-gates/index.js.map +1 -0
  30. package/dist/feature-gates/metrics.d.ts +32 -0
  31. package/dist/feature-gates/metrics.d.ts.map +1 -0
  32. package/dist/feature-gates/metrics.js +100 -0
  33. package/dist/feature-gates/metrics.js.map +1 -0
  34. package/dist/feature-gates/metrics.test.d.ts +2 -0
  35. package/dist/feature-gates/metrics.test.d.ts.map +1 -0
  36. package/dist/feature-gates/metrics.test.js +152 -0
  37. package/dist/feature-gates/metrics.test.js.map +1 -0
  38. package/dist/feature-gates/types.d.ts +49 -0
  39. package/dist/feature-gates/types.d.ts.map +1 -0
  40. package/dist/feature-gates/types.js +3 -0
  41. package/dist/feature-gates/types.js.map +1 -0
  42. package/dist/feature-gates/utils.d.ts +21 -0
  43. package/dist/feature-gates/utils.d.ts.map +1 -0
  44. package/dist/feature-gates/utils.js +85 -0
  45. package/dist/feature-gates/utils.js.map +1 -0
  46. package/dist/hydration/hydrator.d.ts +8 -3
  47. package/dist/hydration/hydrator.d.ts.map +1 -1
  48. package/dist/hydration/hydrator.js +9 -5
  49. package/dist/hydration/hydrator.js.map +1 -1
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/index.js +7 -6
  52. package/dist/index.js.map +1 -1
  53. package/dist/views/index.d.ts.map +1 -1
  54. package/dist/views/index.js +3 -4
  55. package/dist/views/index.js.map +1 -1
  56. package/package.json +10 -10
  57. package/src/api/app/bsky/feed/searchPosts.ts +10 -8
  58. package/src/api/app/bsky/graph/getSuggestedFollowsByActor.ts +0 -1
  59. package/src/api/app/bsky/unspecced/getPostThreadV2.ts +3 -3
  60. package/src/api/app/bsky/unspecced/getSuggestedOnboardingUsers.ts +13 -6
  61. package/src/api/app/bsky/unspecced/getSuggestedUsers.ts +13 -6
  62. package/src/config.ts +8 -0
  63. package/src/context.ts +4 -4
  64. package/src/feature-gates/README.md +47 -0
  65. package/src/feature-gates/gates.ts +9 -0
  66. package/src/feature-gates/index.ts +146 -0
  67. package/src/feature-gates/metrics.test.ts +196 -0
  68. package/src/feature-gates/metrics.ts +107 -0
  69. package/src/feature-gates/types.ts +52 -0
  70. package/src/feature-gates/utils.ts +90 -0
  71. package/src/hydration/hydrator.ts +12 -6
  72. package/src/index.ts +8 -7
  73. package/src/views/index.ts +5 -8
  74. package/tests/views/thread.test.ts +2 -0
  75. package/tsconfig.build.tsbuildinfo +1 -1
  76. package/dist/feature-gates.d.ts +0 -44
  77. package/dist/feature-gates.d.ts.map +0 -1
  78. package/dist/feature-gates.js +0 -133
  79. package/dist/feature-gates.js.map +0 -1
  80. package/src/feature-gates.ts +0 -136
@@ -0,0 +1,152 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ /// <reference types="jest" />
4
+ const logger_1 = require("../logger");
5
+ const metrics_1 = require("./metrics");
6
+ jest.mock('../logger', () => ({
7
+ featureGatesLogger: {
8
+ error: jest.fn(),
9
+ },
10
+ }));
11
+ // Helper to flush promises and timers
12
+ const flushPromises = () => new Promise((r) => setImmediate(r));
13
+ describe('MetricsClient', () => {
14
+ let fetchMock;
15
+ let fetchRequests;
16
+ let client;
17
+ beforeEach(() => {
18
+ jest.useFakeTimers({ doNotFake: ['setImmediate', 'performance'] });
19
+ fetchRequests = [];
20
+ fetchMock = jest.fn().mockImplementation(async (_url, options) => {
21
+ const body = JSON.parse(options.body);
22
+ fetchRequests.push({ body });
23
+ return { ok: true, status: 200, text: async () => '' };
24
+ });
25
+ global.fetch = fetchMock;
26
+ });
27
+ afterEach(() => {
28
+ client?.stop();
29
+ jest.useRealTimers();
30
+ jest.clearAllMocks();
31
+ });
32
+ it('flushes events on interval', async () => {
33
+ client = new metrics_1.MetricsClient({
34
+ trackingEndpoint: 'https://test.metrics.api',
35
+ });
36
+ client.track('click', { button: 'submit' });
37
+ client.track('view', { screen: 'home' });
38
+ expect(fetchRequests).toHaveLength(0);
39
+ // Advance past the 10 second interval
40
+ jest.advanceTimersByTime(10000);
41
+ await flushPromises();
42
+ expect(fetchRequests).toHaveLength(1);
43
+ expect(fetchRequests[0].body.events).toHaveLength(2);
44
+ expect(fetchRequests[0].body.events[0].event).toBe('click');
45
+ expect(fetchRequests[0].body.events[1].event).toBe('view');
46
+ });
47
+ it('flushes when maxBatchSize is exceeded', async () => {
48
+ client = new metrics_1.MetricsClient({
49
+ trackingEndpoint: 'https://test.metrics.api',
50
+ });
51
+ client.maxBatchSize = 5;
52
+ // Add events up to maxBatchSize (should not flush yet)
53
+ for (let i = 0; i < 5; i++) {
54
+ client.track('click', { button: `btn-${i}` });
55
+ }
56
+ expect(fetchRequests).toHaveLength(0);
57
+ // One more event should trigger flush (> maxBatchSize)
58
+ client.track('click', { button: 'btn-trigger' });
59
+ await flushPromises();
60
+ expect(fetchRequests).toHaveLength(1);
61
+ expect(fetchRequests[0].body.events).toHaveLength(6);
62
+ });
63
+ it('logs error on failed request', async () => {
64
+ fetchMock.mockImplementation(async () => {
65
+ return {
66
+ ok: false,
67
+ status: 500,
68
+ text: async () => 'Internal Server Error',
69
+ };
70
+ });
71
+ client = new metrics_1.MetricsClient({
72
+ trackingEndpoint: 'https://test.metrics.api',
73
+ });
74
+ client.track('click', { button: 'submit' });
75
+ // Trigger flush via interval
76
+ jest.advanceTimersByTime(10000);
77
+ await flushPromises();
78
+ expect(fetchMock).toHaveBeenCalledTimes(1);
79
+ expect(logger_1.featureGatesLogger.error).toHaveBeenCalledWith(expect.objectContaining({
80
+ err: expect.any(Error),
81
+ }), 'Failed to send metrics');
82
+ });
83
+ it('handles fetch text() error gracefully', async () => {
84
+ fetchMock.mockImplementation(async () => {
85
+ return {
86
+ ok: false,
87
+ status: 500,
88
+ text: async () => {
89
+ throw new Error('Failed to read response');
90
+ },
91
+ };
92
+ });
93
+ client = new metrics_1.MetricsClient({
94
+ trackingEndpoint: 'https://test.metrics.api',
95
+ });
96
+ client.track('click', { button: 'submit' });
97
+ // Trigger flush - should not throw
98
+ jest.advanceTimersByTime(10000);
99
+ await flushPromises();
100
+ expect(fetchMock).toHaveBeenCalledTimes(1);
101
+ expect(logger_1.featureGatesLogger.error).toHaveBeenCalledWith(expect.objectContaining({
102
+ err: expect.objectContaining({
103
+ message: expect.stringContaining('Unknown error'),
104
+ }),
105
+ }), 'Failed to send metrics');
106
+ });
107
+ it('flushes when stop() is called', async () => {
108
+ client = new metrics_1.MetricsClient({
109
+ trackingEndpoint: 'https://test.metrics.api',
110
+ });
111
+ client.track('click', { button: 'submit' });
112
+ expect(fetchRequests).toHaveLength(0);
113
+ // Stop should flush remaining events
114
+ client.stop();
115
+ await flushPromises();
116
+ expect(fetchRequests).toHaveLength(1);
117
+ expect(fetchRequests[0].body.events).toHaveLength(1);
118
+ expect(fetchRequests[0].body.events[0].event).toBe('click');
119
+ });
120
+ it('does not send if trackingEndpoint is not configured', async () => {
121
+ client = new metrics_1.MetricsClient({});
122
+ client.track('click', { button: 'submit' });
123
+ // Trigger flush via interval
124
+ jest.advanceTimersByTime(10000);
125
+ await flushPromises();
126
+ expect(fetchMock).not.toHaveBeenCalled();
127
+ });
128
+ it('start() is idempotent', async () => {
129
+ client = new metrics_1.MetricsClient({
130
+ trackingEndpoint: 'https://test.metrics.api',
131
+ });
132
+ // track() calls start() internally
133
+ client.track('click', { button: 'submit' });
134
+ client.start();
135
+ client.start();
136
+ // Advance past interval - should only flush once
137
+ jest.advanceTimersByTime(10000);
138
+ await flushPromises();
139
+ expect(fetchRequests).toHaveLength(1);
140
+ });
141
+ it('does not flush if queue is empty', async () => {
142
+ client = new metrics_1.MetricsClient({
143
+ trackingEndpoint: 'https://test.metrics.api',
144
+ });
145
+ client.start();
146
+ // Advance past interval with empty queue
147
+ jest.advanceTimersByTime(10000);
148
+ await flushPromises();
149
+ expect(fetchMock).not.toHaveBeenCalled();
150
+ });
151
+ });
152
+ //# sourceMappingURL=metrics.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.test.js","sourceRoot":"","sources":["../../src/feature-gates/metrics.test.ts"],"names":[],"mappings":";;AAAA,8BAA8B;AAC9B,sCAA8C;AAC9C,uCAAyC;AAEzC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,EAAE,CAAC,CAAC;IAC5B,kBAAkB,EAAE;QAClB,KAAK,EAAE,IAAI,CAAC,EAAE,EAAE;KACjB;CACF,CAAC,CAAC,CAAA;AAOH,sCAAsC;AACtC,MAAM,aAAa,GAAG,GAAG,EAAE,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAA;AAE/D,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,IAAI,SAAoB,CAAA;IACxB,IAAI,aAA8B,CAAA;IAClC,IAAI,MAAiC,CAAA;IAErC,UAAU,CAAC,GAAG,EAAE;QACd,IAAI,CAAC,aAAa,CAAC,EAAE,SAAS,EAAE,CAAC,cAAc,EAAE,aAAa,CAAC,EAAE,CAAC,CAAA;QAClE,aAAa,GAAG,EAAE,CAAA;QAClB,SAAS,GAAG,IAAI,CAAC,EAAE,EAAE,CAAC,kBAAkB,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE;YAC/D,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;YACrC,aAAa,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,CAAA;YAC5B,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC,EAAE,EAAE,CAAA;QACxD,CAAC,CAAC,CAAA;QACF,MAAM,CAAC,KAAK,GAAG,SAAS,CAAA;IAC1B,CAAC,CAAC,CAAA;IAEF,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,EAAE,IAAI,EAAE,CAAA;QACd,IAAI,CAAC,aAAa,EAAE,CAAA;QACpB,IAAI,CAAC,aAAa,EAAE,CAAA;IACtB,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,4BAA4B,EAAE,KAAK,IAAI,EAAE;QAC1C,MAAM,GAAG,IAAI,uBAAa,CAAa;YACrC,gBAAgB,EAAE,0BAA0B;SAC7C,CAAC,CAAA;QACF,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAA;QAC3C,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;QAExC,MAAM,CAAC,aAAa,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAA;QAErC,sCAAsC;QACtC,IAAI,CAAC,mBAAmB,CAAC,KAAM,CAAC,CAAA;QAChC,MAAM,aAAa,EAAE,CAAA;QAErB,MAAM,CAAC,aAAa,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAA;QACrC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAA;QACpD,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QAC3D,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IAC5D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,uCAAuC,EAAE,KAAK,IAAI,EAAE;QACrD,MAAM,GAAG,IAAI,uBAAa,CAAa;YACrC,gBAAgB,EAAE,0BAA0B;SAC7C,CAAC,CAAA;QACF,MAAM,CAAC,YAAY,GAAG,CAAC,CAAA;QAEvB,uDAAuD;QACvD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC3B,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,CAAA;QAC/C,CAAC;QAED,MAAM,CAAC,aAAa,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAA;QAErC,uDAAuD;QACvD,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC,CAAA;QAChD,MAAM,aAAa,EAAE,CAAA;QAErB,MAAM,CAAC,aAAa,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAA;QACrC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAA;IACtD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,8BAA8B,EAAE,KAAK,IAAI,EAAE;QAC5C,SAAS,CAAC,kBAAkB,CAAC,KAAK,IAAI,EAAE;YACtC,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,MAAM,EAAE,GAAG;gBACX,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC,uBAAuB;aAC1C,CAAA;QACH,CAAC,CAAC,CAAA;QAEF,MAAM,GAAG,IAAI,uBAAa,CAAa;YACrC,gBAAgB,EAAE,0BAA0B;SAC7C,CAAC,CAAA;QACF,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAA;QAE3C,6BAA6B;QAC7B,IAAI,CAAC,mBAAmB,CAAC,KAAM,CAAC,CAAA;QAChC,MAAM,aAAa,EAAE,CAAA;QAErB,MAAM,CAAC,SAAS,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAA;QAC1C,MAAM,CAAC,2BAAkB,CAAC,KAAK,CAAC,CAAC,oBAAoB,CACnD,MAAM,CAAC,gBAAgB,CAAC;YACtB,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC;SACvB,CAAC,EACF,wBAAwB,CACzB,CAAA;IACH,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,uCAAuC,EAAE,KAAK,IAAI,EAAE;QACrD,SAAS,CAAC,kBAAkB,CAAC,KAAK,IAAI,EAAE;YACtC,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,MAAM,EAAE,GAAG;gBACX,IAAI,EAAE,KAAK,IAAI,EAAE;oBACf,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAA;gBAC5C,CAAC;aACF,CAAA;QACH,CAAC,CAAC,CAAA;QAEF,MAAM,GAAG,IAAI,uBAAa,CAAa;YACrC,gBAAgB,EAAE,0BAA0B;SAC7C,CAAC,CAAA;QACF,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAA;QAE3C,mCAAmC;QACnC,IAAI,CAAC,mBAAmB,CAAC,KAAM,CAAC,CAAA;QAChC,MAAM,aAAa,EAAE,CAAA;QAErB,MAAM,CAAC,SAAS,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAA;QAC1C,MAAM,CAAC,2BAAkB,CAAC,KAAK,CAAC,CAAC,oBAAoB,CACnD,MAAM,CAAC,gBAAgB,CAAC;YACtB,GAAG,EAAE,MAAM,CAAC,gBAAgB,CAAC;gBAC3B,OAAO,EAAE,MAAM,CAAC,gBAAgB,CAAC,eAAe,CAAC;aAClD,CAAC;SACH,CAAC,EACF,wBAAwB,CACzB,CAAA;IACH,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,+BAA+B,EAAE,KAAK,IAAI,EAAE;QAC7C,MAAM,GAAG,IAAI,uBAAa,CAAa;YACrC,gBAAgB,EAAE,0BAA0B;SAC7C,CAAC,CAAA;QACF,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAA;QAE3C,MAAM,CAAC,aAAa,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAA;QAErC,qCAAqC;QACrC,MAAM,CAAC,IAAI,EAAE,CAAA;QACb,MAAM,aAAa,EAAE,CAAA;QAErB,MAAM,CAAC,aAAa,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAA;QACrC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAA;QACpD,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IAC7D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qDAAqD,EAAE,KAAK,IAAI,EAAE;QACnE,MAAM,GAAG,IAAI,uBAAa,CAAa,EAAE,CAAC,CAAA;QAC1C,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAA;QAE3C,6BAA6B;QAC7B,IAAI,CAAC,mBAAmB,CAAC,KAAM,CAAC,CAAA;QAChC,MAAM,aAAa,EAAE,CAAA;QAErB,MAAM,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAA;IAC1C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,uBAAuB,EAAE,KAAK,IAAI,EAAE;QACrC,MAAM,GAAG,IAAI,uBAAa,CAAa;YACrC,gBAAgB,EAAE,0BAA0B;SAC7C,CAAC,CAAA;QAEF,mCAAmC;QACnC,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAA;QAC3C,MAAM,CAAC,KAAK,EAAE,CAAA;QACd,MAAM,CAAC,KAAK,EAAE,CAAA;QAEd,iDAAiD;QACjD,IAAI,CAAC,mBAAmB,CAAC,KAAM,CAAC,CAAA;QAChC,MAAM,aAAa,EAAE,CAAA;QAErB,MAAM,CAAC,aAAa,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAA;IACvC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,kCAAkC,EAAE,KAAK,IAAI,EAAE;QAChD,MAAM,GAAG,IAAI,uBAAa,CAAa;YACrC,gBAAgB,EAAE,0BAA0B;SAC7C,CAAC,CAAA;QACF,MAAM,CAAC,KAAK,EAAE,CAAA;QAEd,yCAAyC;QACzC,IAAI,CAAC,mBAAmB,CAAC,KAAM,CAAC,CAAA;QAChC,MAAM,aAAa,EAAE,CAAA;QAErB,MAAM,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAA;IAC1C,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA","sourcesContent":["/// <reference types=\"jest\" />\nimport { featureGatesLogger } from '../logger'\nimport { MetricsClient } from './metrics'\n\njest.mock('../logger', () => ({\n featureGatesLogger: {\n error: jest.fn(),\n },\n}))\n\ntype TestEvents = {\n click: { button: string }\n view: { screen: string }\n}\n\n// Helper to flush promises and timers\nconst flushPromises = () => new Promise((r) => setImmediate(r))\n\ndescribe('MetricsClient', () => {\n let fetchMock: jest.Mock\n let fetchRequests: { body: any }[]\n let client: MetricsClient<TestEvents>\n\n beforeEach(() => {\n jest.useFakeTimers({ doNotFake: ['setImmediate', 'performance'] })\n fetchRequests = []\n fetchMock = jest.fn().mockImplementation(async (_url, options) => {\n const body = JSON.parse(options.body)\n fetchRequests.push({ body })\n return { ok: true, status: 200, text: async () => '' }\n })\n global.fetch = fetchMock\n })\n\n afterEach(() => {\n client?.stop()\n jest.useRealTimers()\n jest.clearAllMocks()\n })\n\n it('flushes events on interval', async () => {\n client = new MetricsClient<TestEvents>({\n trackingEndpoint: 'https://test.metrics.api',\n })\n client.track('click', { button: 'submit' })\n client.track('view', { screen: 'home' })\n\n expect(fetchRequests).toHaveLength(0)\n\n // Advance past the 10 second interval\n jest.advanceTimersByTime(10_000)\n await flushPromises()\n\n expect(fetchRequests).toHaveLength(1)\n expect(fetchRequests[0].body.events).toHaveLength(2)\n expect(fetchRequests[0].body.events[0].event).toBe('click')\n expect(fetchRequests[0].body.events[1].event).toBe('view')\n })\n\n it('flushes when maxBatchSize is exceeded', async () => {\n client = new MetricsClient<TestEvents>({\n trackingEndpoint: 'https://test.metrics.api',\n })\n client.maxBatchSize = 5\n\n // Add events up to maxBatchSize (should not flush yet)\n for (let i = 0; i < 5; i++) {\n client.track('click', { button: `btn-${i}` })\n }\n\n expect(fetchRequests).toHaveLength(0)\n\n // One more event should trigger flush (> maxBatchSize)\n client.track('click', { button: 'btn-trigger' })\n await flushPromises()\n\n expect(fetchRequests).toHaveLength(1)\n expect(fetchRequests[0].body.events).toHaveLength(6)\n })\n\n it('logs error on failed request', async () => {\n fetchMock.mockImplementation(async () => {\n return {\n ok: false,\n status: 500,\n text: async () => 'Internal Server Error',\n }\n })\n\n client = new MetricsClient<TestEvents>({\n trackingEndpoint: 'https://test.metrics.api',\n })\n client.track('click', { button: 'submit' })\n\n // Trigger flush via interval\n jest.advanceTimersByTime(10_000)\n await flushPromises()\n\n expect(fetchMock).toHaveBeenCalledTimes(1)\n expect(featureGatesLogger.error).toHaveBeenCalledWith(\n expect.objectContaining({\n err: expect.any(Error),\n }),\n 'Failed to send metrics',\n )\n })\n\n it('handles fetch text() error gracefully', async () => {\n fetchMock.mockImplementation(async () => {\n return {\n ok: false,\n status: 500,\n text: async () => {\n throw new Error('Failed to read response')\n },\n }\n })\n\n client = new MetricsClient<TestEvents>({\n trackingEndpoint: 'https://test.metrics.api',\n })\n client.track('click', { button: 'submit' })\n\n // Trigger flush - should not throw\n jest.advanceTimersByTime(10_000)\n await flushPromises()\n\n expect(fetchMock).toHaveBeenCalledTimes(1)\n expect(featureGatesLogger.error).toHaveBeenCalledWith(\n expect.objectContaining({\n err: expect.objectContaining({\n message: expect.stringContaining('Unknown error'),\n }),\n }),\n 'Failed to send metrics',\n )\n })\n\n it('flushes when stop() is called', async () => {\n client = new MetricsClient<TestEvents>({\n trackingEndpoint: 'https://test.metrics.api',\n })\n client.track('click', { button: 'submit' })\n\n expect(fetchRequests).toHaveLength(0)\n\n // Stop should flush remaining events\n client.stop()\n await flushPromises()\n\n expect(fetchRequests).toHaveLength(1)\n expect(fetchRequests[0].body.events).toHaveLength(1)\n expect(fetchRequests[0].body.events[0].event).toBe('click')\n })\n\n it('does not send if trackingEndpoint is not configured', async () => {\n client = new MetricsClient<TestEvents>({})\n client.track('click', { button: 'submit' })\n\n // Trigger flush via interval\n jest.advanceTimersByTime(10_000)\n await flushPromises()\n\n expect(fetchMock).not.toHaveBeenCalled()\n })\n\n it('start() is idempotent', async () => {\n client = new MetricsClient<TestEvents>({\n trackingEndpoint: 'https://test.metrics.api',\n })\n\n // track() calls start() internally\n client.track('click', { button: 'submit' })\n client.start()\n client.start()\n\n // Advance past interval - should only flush once\n jest.advanceTimersByTime(10_000)\n await flushPromises()\n\n expect(fetchRequests).toHaveLength(1)\n })\n\n it('does not flush if queue is empty', async () => {\n client = new MetricsClient<TestEvents>({\n trackingEndpoint: 'https://test.metrics.api',\n })\n client.start()\n\n // Advance past interval with empty queue\n jest.advanceTimersByTime(10_000)\n await flushPromises()\n\n expect(fetchMock).not.toHaveBeenCalled()\n })\n})\n"]}
@@ -0,0 +1,49 @@
1
+ import type express from 'express';
2
+ import { FeatureGate } from './gates';
3
+ /**
4
+ * The user context passed to the feature gates client for evaluation and
5
+ * tracking purposes.
6
+ */
7
+ export type RawUserContext = {
8
+ /**
9
+ * The user's DID
10
+ */
11
+ viewer: string | null;
12
+ /**
13
+ * The express request object, used to extract analytics headers for the user context
14
+ */
15
+ req: express.Request;
16
+ };
17
+ /**
18
+ * Extracted values from the `RawUserContext`. These values should match the
19
+ * `attributes` we've configured for GrowthBook in our GB dashboard. We also
20
+ * send these same values as properties in our analytics events, so we want to
21
+ * make sure they are consistent.
22
+ */
23
+ export type ParsedUserContext = {
24
+ did?: string | null;
25
+ deviceId: string;
26
+ sessionId: string;
27
+ };
28
+ /**
29
+ * This loosely matches the metadata we send from the client for analytics
30
+ * events. We want to make sure we have the same properties in both places so
31
+ * that we can correlate feature gate evaluations with analytics events.
32
+ *
33
+ * @see https://github.com/bluesky-social/social-app/blob/76109a58dc7aafccdfbd07a81cbd9925e065d1c0/src/analytics/metadata.ts
34
+ */
35
+ export type TrackingMetadata = {
36
+ base: {
37
+ deviceId: string;
38
+ sessionId: string;
39
+ };
40
+ session: {
41
+ did: string | undefined;
42
+ };
43
+ };
44
+ /**
45
+ * Pre-evaluated feature gates map, the result of
46
+ * `ctx.FeatureGatesClient.checkGates()`
47
+ */
48
+ export type CheckedFeatureGatesMap = Map<FeatureGate, boolean>;
49
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/feature-gates/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,SAAS,CAAA;AAClC,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAErC;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B;;OAEG;IACH,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB;;OAEG;IACH,GAAG,EAAE,OAAO,CAAC,OAAO,CAAA;CACrB,CAAA;AAED;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAED;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE;QACJ,QAAQ,EAAE,MAAM,CAAA;QAChB,SAAS,EAAE,MAAM,CAAA;KAClB,CAAA;IACD,OAAO,EAAE;QACP,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;KACxB,CAAA;CACF,CAAA;AAED;;;GAGG;AACH,MAAM,MAAM,sBAAsB,GAAG,GAAG,CAAC,WAAW,EAAE,OAAO,CAAC,CAAA"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/feature-gates/types.ts"],"names":[],"mappings":"","sourcesContent":["import type express from 'express'\nimport { FeatureGate } from './gates'\n\n/**\n * The user context passed to the feature gates client for evaluation and\n * tracking purposes.\n */\nexport type RawUserContext = {\n /**\n * The user's DID\n */\n viewer: string | null\n /**\n * The express request object, used to extract analytics headers for the user context\n */\n req: express.Request\n}\n\n/**\n * Extracted values from the `RawUserContext`. These values should match the\n * `attributes` we've configured for GrowthBook in our GB dashboard. We also\n * send these same values as properties in our analytics events, so we want to\n * make sure they are consistent.\n */\nexport type ParsedUserContext = {\n did?: string | null\n deviceId: string\n sessionId: string\n}\n\n/**\n * This loosely matches the metadata we send from the client for analytics\n * events. We want to make sure we have the same properties in both places so\n * that we can correlate feature gate evaluations with analytics events.\n *\n * @see https://github.com/bluesky-social/social-app/blob/76109a58dc7aafccdfbd07a81cbd9925e065d1c0/src/analytics/metadata.ts\n */\nexport type TrackingMetadata = {\n base: {\n deviceId: string\n sessionId: string\n }\n session: {\n did: string | undefined\n }\n}\n\n/**\n * Pre-evaluated feature gates map, the result of\n * `ctx.FeatureGatesClient.checkGates()`\n */\nexport type CheckedFeatureGatesMap = Map<FeatureGate, boolean>\n"]}
@@ -0,0 +1,21 @@
1
+ import { type UserContext as GrowthBookUserContext } from '@growthbook/growthbook';
2
+ import { ParsedUserContext, RawUserContext, TrackingMetadata } from './types';
3
+ /**
4
+ * Parse the `RawUserContext` into a `ParsedUserContext` that is used as
5
+ * GrowthBook `attributes` as well as the metadata payload for our analytics
6
+ * events. This ensures that the same user properties are used for both feature
7
+ * gate targeting and analytics.
8
+ */
9
+ export declare function parseRawUserContext(userContext: RawUserContext): ParsedUserContext;
10
+ /**
11
+ * Extract the `ParsedUserContext` from the GrowthBook `UserContext`, which we
12
+ * passed into `isOn` as `attributes`.
13
+ */
14
+ export declare function extractParsedUserContextFromGrowthBookUserContext(userContext: GrowthBookUserContext): ParsedUserContext;
15
+ /**
16
+ * Convert the `ParsedUserContext` into the `TrackingMetadata` format that we
17
+ * use for our analytics events. This ensures that we have the same user
18
+ * properties as we do for events from our client app.
19
+ */
20
+ export declare function parsedUserContextToTrackingMetadata(parsedUserContext: ParsedUserContext): TrackingMetadata;
21
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/feature-gates/utils.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,WAAW,IAAI,qBAAqB,EAAE,MAAM,wBAAwB,CAAA;AAClF,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAQ7E;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,cAAc,GAC1B,iBAAiB,CAsCnB;AAED;;;GAGG;AACH,wBAAgB,iDAAiD,CAC/D,WAAW,EAAE,qBAAqB,GACjC,iBAAiB,CAMnB;AAED;;;;GAIG;AACH,wBAAgB,mCAAmC,CACjD,iBAAiB,EAAE,iBAAiB,GACnC,gBAAgB,CAUlB"}
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.parseRawUserContext = parseRawUserContext;
7
+ exports.extractParsedUserContextFromGrowthBookUserContext = extractParsedUserContextFromGrowthBookUserContext;
8
+ exports.parsedUserContextToTrackingMetadata = parsedUserContextToTrackingMetadata;
9
+ const node_crypto_1 = __importDefault(require("node:crypto"));
10
+ /**
11
+ * These need to match what the client sends
12
+ */
13
+ const ANALYTICS_HEADER_DEVICE_ID = 'X-Bsky-Device-Id';
14
+ const ANALYTICS_HEADER_SESSION_ID = 'X-Bsky-Session-Id';
15
+ /**
16
+ * Parse the `RawUserContext` into a `ParsedUserContext` that is used as
17
+ * GrowthBook `attributes` as well as the metadata payload for our analytics
18
+ * events. This ensures that the same user properties are used for both feature
19
+ * gate targeting and analytics.
20
+ */
21
+ function parseRawUserContext(userContext) {
22
+ const did = userContext.viewer;
23
+ // prioritize passthrough header
24
+ let deviceId = userContext.req.header(ANALYTICS_HEADER_DEVICE_ID);
25
+ if (!deviceId) {
26
+ if (did) {
27
+ /*
28
+ * If we don't have a device header, fall back to the DID. Our event
29
+ * proxy ensures ordering based on this deviceId (also called a stableId
30
+ * in the proxy), so if we have a DID, we want to use it to ensure client
31
+ * and server events are properly ordered.
32
+ */
33
+ deviceId = did;
34
+ }
35
+ else {
36
+ /*
37
+ * Without any better option for identifying the user, we generate a
38
+ * random deviceId.
39
+ */
40
+ deviceId = `anon-${node_crypto_1.default.randomUUID()}`;
41
+ }
42
+ }
43
+ // prioritize passthrough header
44
+ let sessionId = userContext.req.header(ANALYTICS_HEADER_SESSION_ID);
45
+ if (!sessionId) {
46
+ /*
47
+ * Without any better option for identifying the user, we generate a
48
+ * random deviceId.
49
+ */
50
+ sessionId = `anon-${node_crypto_1.default.randomUUID()}`;
51
+ }
52
+ return {
53
+ did,
54
+ deviceId,
55
+ sessionId,
56
+ };
57
+ }
58
+ /**
59
+ * Extract the `ParsedUserContext` from the GrowthBook `UserContext`, which we
60
+ * passed into `isOn` as `attributes`.
61
+ */
62
+ function extractParsedUserContextFromGrowthBookUserContext(userContext) {
63
+ return {
64
+ did: userContext.attributes?.did,
65
+ deviceId: userContext.attributes?.deviceId,
66
+ sessionId: userContext.attributes?.sessionId,
67
+ };
68
+ }
69
+ /**
70
+ * Convert the `ParsedUserContext` into the `TrackingMetadata` format that we
71
+ * use for our analytics events. This ensures that we have the same user
72
+ * properties as we do for events from our client app.
73
+ */
74
+ function parsedUserContextToTrackingMetadata(parsedUserContext) {
75
+ return {
76
+ base: {
77
+ deviceId: parsedUserContext.deviceId,
78
+ sessionId: parsedUserContext.sessionId,
79
+ },
80
+ session: {
81
+ did: parsedUserContext.did ?? undefined,
82
+ },
83
+ };
84
+ }
85
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/feature-gates/utils.ts"],"names":[],"mappings":";;;;;AAgBA,kDAwCC;AAMD,8GAQC;AAOD,kFAYC;AAzFD,8DAAgC;AAIhC;;GAEG;AACH,MAAM,0BAA0B,GAAG,kBAAkB,CAAA;AACrD,MAAM,2BAA2B,GAAG,mBAAmB,CAAA;AAEvD;;;;;GAKG;AACH,SAAgB,mBAAmB,CACjC,WAA2B;IAE3B,MAAM,GAAG,GAAG,WAAW,CAAC,MAAM,CAAA;IAE9B,gCAAgC;IAChC,IAAI,QAAQ,GAAG,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,0BAA0B,CAAC,CAAA;IACjE,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,IAAI,GAAG,EAAE,CAAC;YACR;;;;;eAKG;YACH,QAAQ,GAAG,GAAG,CAAA;QAChB,CAAC;aAAM,CAAC;YACN;;;eAGG;YACH,QAAQ,GAAG,QAAQ,qBAAM,CAAC,UAAU,EAAE,EAAE,CAAA;QAC1C,CAAC;IACH,CAAC;IAED,gCAAgC;IAChC,IAAI,SAAS,GAAG,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,2BAA2B,CAAC,CAAA;IACnE,IAAI,CAAC,SAAS,EAAE,CAAC;QACf;;;WAGG;QACH,SAAS,GAAG,QAAQ,qBAAM,CAAC,UAAU,EAAE,EAAE,CAAA;IAC3C,CAAC;IAED,OAAO;QACL,GAAG;QACH,QAAQ;QACR,SAAS;KACV,CAAA;AACH,CAAC;AAED;;;GAGG;AACH,SAAgB,iDAAiD,CAC/D,WAAkC;IAElC,OAAO;QACL,GAAG,EAAE,WAAW,CAAC,UAAU,EAAE,GAAG;QAChC,QAAQ,EAAE,WAAW,CAAC,UAAU,EAAE,QAAQ;QAC1C,SAAS,EAAE,WAAW,CAAC,UAAU,EAAE,SAAS;KAC7C,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,SAAgB,mCAAmC,CACjD,iBAAoC;IAEpC,OAAO;QACL,IAAI,EAAE;YACJ,QAAQ,EAAE,iBAAiB,CAAC,QAAQ;YACpC,SAAS,EAAE,iBAAiB,CAAC,SAAS;SACvC;QACD,OAAO,EAAE;YACP,GAAG,EAAE,iBAAiB,CAAC,GAAG,IAAI,SAAS;SACxC;KACF,CAAA;AACH,CAAC","sourcesContent":["import crypto from 'node:crypto'\nimport { type UserContext as GrowthBookUserContext } from '@growthbook/growthbook'\nimport { ParsedUserContext, RawUserContext, TrackingMetadata } from './types'\n\n/**\n * These need to match what the client sends\n */\nconst ANALYTICS_HEADER_DEVICE_ID = 'X-Bsky-Device-Id'\nconst ANALYTICS_HEADER_SESSION_ID = 'X-Bsky-Session-Id'\n\n/**\n * Parse the `RawUserContext` into a `ParsedUserContext` that is used as\n * GrowthBook `attributes` as well as the metadata payload for our analytics\n * events. This ensures that the same user properties are used for both feature\n * gate targeting and analytics.\n */\nexport function parseRawUserContext(\n userContext: RawUserContext,\n): ParsedUserContext {\n const did = userContext.viewer\n\n // prioritize passthrough header\n let deviceId = userContext.req.header(ANALYTICS_HEADER_DEVICE_ID)\n if (!deviceId) {\n if (did) {\n /*\n * If we don't have a device header, fall back to the DID. Our event\n * proxy ensures ordering based on this deviceId (also called a stableId\n * in the proxy), so if we have a DID, we want to use it to ensure client\n * and server events are properly ordered.\n */\n deviceId = did\n } else {\n /*\n * Without any better option for identifying the user, we generate a\n * random deviceId.\n */\n deviceId = `anon-${crypto.randomUUID()}`\n }\n }\n\n // prioritize passthrough header\n let sessionId = userContext.req.header(ANALYTICS_HEADER_SESSION_ID)\n if (!sessionId) {\n /*\n * Without any better option for identifying the user, we generate a\n * random deviceId.\n */\n sessionId = `anon-${crypto.randomUUID()}`\n }\n\n return {\n did,\n deviceId,\n sessionId,\n }\n}\n\n/**\n * Extract the `ParsedUserContext` from the GrowthBook `UserContext`, which we\n * passed into `isOn` as `attributes`.\n */\nexport function extractParsedUserContextFromGrowthBookUserContext(\n userContext: GrowthBookUserContext,\n): ParsedUserContext {\n return {\n did: userContext.attributes?.did,\n deviceId: userContext.attributes?.deviceId,\n sessionId: userContext.attributes?.sessionId,\n }\n}\n\n/**\n * Convert the `ParsedUserContext` into the `TrackingMetadata` format that we\n * use for our analytics events. This ensures that we have the same user\n * properties as we do for events from our client app.\n */\nexport function parsedUserContextToTrackingMetadata(\n parsedUserContext: ParsedUserContext,\n): TrackingMetadata {\n return {\n base: {\n deviceId: parsedUserContext.deviceId,\n sessionId: parsedUserContext.sessionId,\n },\n session: {\n did: parsedUserContext.did ?? undefined,\n },\n }\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import { DataPlaneClient } from '../data-plane/client';
2
- import { type CheckedFeatureGatesMap } from '../feature-gates';
2
+ import { type CheckedFeatureGatesMap } from '../feature-gates/types';
3
3
  import { Record as ProfileRecord } from '../lexicon/types/app/bsky/actor/profile';
4
4
  import { Bookmark, BookmarkInfo, Notification } from '../proto/bsky_pb';
5
5
  import { ParsedLabelers } from '../util';
@@ -16,7 +16,12 @@ export declare class HydrateCtx {
16
16
  overrideIncludeTakedownsForActor: boolean | undefined;
17
17
  include3pBlocks: boolean | undefined;
18
18
  includeDebugField: boolean | undefined;
19
- featureGates: CheckedFeatureGatesMap;
19
+ /**
20
+ * Cache of evaluated feature gates to be used in a given request lifecycle.
21
+ * The actual evaluations happen at the top of the route handler and the
22
+ * results are stored in this map.
23
+ */
24
+ featureGatesMap: CheckedFeatureGatesMap;
20
25
  constructor(vals: HydrateCtxVals);
21
26
  get skipCacheForViewer(): string[] | undefined;
22
27
  copy<V extends Partial<HydrateCtxVals>>(vals?: V): HydrateCtx & V;
@@ -28,7 +33,7 @@ export type HydrateCtxVals = {
28
33
  overrideIncludeTakedownsForActor?: boolean;
29
34
  include3pBlocks?: boolean;
30
35
  includeDebugField?: boolean;
31
- featureGates?: CheckedFeatureGatesMap;
36
+ featureGatesMap?: CheckedFeatureGatesMap;
32
37
  };
33
38
  export type HydrationState = {
34
39
  ctx?: HydrateCtx;
@@ -1 +1 @@
1
- {"version":3,"file":"hydrator.d.ts","sourceRoot":"","sources":["../../src/hydration/hydrator.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"hydrator.d.ts","sourceRoot":"","sources":["../../src/hydration/hydrator.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AACtD,OAAO,EAAE,KAAK,sBAAsB,EAAE,MAAM,wBAAwB,CAAA;AAEpE,OAAO,EAAE,MAAM,IAAI,aAAa,EAAE,MAAM,yCAAyC,CAAA;AAKjF,OAAO,EACL,QAAQ,EACR,YAAY,EACZ,YAAY,EAEb,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAExC,OAAO,EACL,0BAA0B,EAC1B,aAAa,EACb,MAAM,EACN,oBAAoB,EACpB,WAAW,EAEX,mBAAmB,EACpB,MAAM,SAAS,CAAA;AAChB,OAAO,EACL,WAAW,EACX,mBAAmB,EACnB,QAAQ,EACR,YAAY,EACZ,QAAQ,EACR,KAAK,wBAAwB,EAC7B,KAAK,EACL,IAAI,EACJ,QAAQ,EACR,gBAAgB,EAChB,SAAS,EACT,KAAK,EACL,OAAO,EACP,cAAc,EAEd,WAAW,EACZ,MAAM,QAAQ,CAAA;AACf,OAAO,EAEL,OAAO,EACP,aAAa,EACb,QAAQ,EACR,SAAS,EAET,oBAAoB,EACpB,gBAAgB,EAChB,KAAK,EAEL,eAAe,EACf,YAAY,EACZ,aAAa,EACd,MAAM,SAAS,CAAA;AAChB,OAAO,EACL,aAAa,EACb,WAAW,EACX,mBAAmB,EACnB,QAAQ,EACR,MAAM,EACP,MAAM,SAAS,CAAA;AAChB,OAAO,EACL,YAAY,EACZ,OAAO,EACP,UAAU,EAKX,MAAM,QAAQ,CAAA;AAEf,qBAAa,UAAU;IAcT,OAAO,CAAC,IAAI;IAbxB,QAAQ,iBAAqB;IAC7B,MAAM,gBAAuE;IAC7E,gBAAgB,sBAA6B;IAC7C,gCAAgC,sBAA6C;IAC7E,eAAe,sBAA4B;IAC3C,iBAAiB,sBAA8B;IAC/C;;;;OAIG;IACH,eAAe,EAAE,sBAAsB,CACC;gBACpB,IAAI,EAAE,cAAc;IAExC,IAAI,kBAAkB,yBAGrB;IACD,IAAI,CAAC,CAAC,SAAS,OAAO,CAAC,cAAc,CAAC,EAAE,IAAI,CAAC,EAAE,CAAC,GAAG,UAAU,GAAG,CAAC;CAGlE;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,cAAc,CAAA;IACxB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,gCAAgC,CAAC,EAAE,OAAO,CAAA;IAC1C,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,eAAe,CAAC,EAAE,sBAAsB,CAAA;CACzC,CAAA;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,GAAG,CAAC,EAAE,UAAU,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,cAAc,CAAC,EAAE,mBAAmB,CAAA;IACpC,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB,KAAK,CAAC,EAAE,KAAK,CAAA;IACb,QAAQ,CAAC,EAAE,QAAQ,CAAA;IACnB,WAAW,CAAC,EAAE,gBAAgB,CAAA;IAC9B,cAAc,CAAC,EAAE,cAAc,CAAA;IAC/B,UAAU,CAAC,EAAE,UAAU,CAAA;IACvB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,YAAY,CAAC,EAAE,YAAY,CAAA;IAC3B,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,KAAK,CAAC,EAAE,KAAK,CAAA;IACb,QAAQ,CAAC,EAAE,QAAQ,CAAA;IACnB,eAAe,CAAC,EAAE,oBAAoB,CAAA;IACtC,WAAW,CAAC,EAAE,gBAAgB,CAAA;IAC9B,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,KAAK,CAAC,EAAE,KAAK,CAAA;IACb,UAAU,CAAC,EAAE,UAAU,CAAA;IACvB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,QAAQ,CAAA;IACnB,cAAc,CAAC,EAAE,mBAAmB,CAAA;IACpC,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB,YAAY,CAAC,EAAE,YAAY,CAAA;IAC3B,eAAe,CAAC,EAAE,eAAe,CAAA;IACjC,QAAQ,CAAC,EAAE,QAAQ,CAAA;IACnB,cAAc,CAAC,EAAE,mBAAmB,CAAA;IACpC,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB,cAAc,CAAC,EAAE,oBAAoB,CAAA;IACrC,qBAAqB,CAAC,EAAE,0BAA0B,CAAA;IAClD,mBAAmB,CAAC,EAAE,mBAAmB,CAAA;IACzC,aAAa,CAAC,EAAE,aAAa,CAAA;IAC7B,SAAS,CAAC,EAAE,SAAS,CAAA;CACtB,CAAA;AAED,MAAM,MAAM,SAAS,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAA;AAC1E,MAAM,MAAM,UAAU,GAAG,YAAY,CAAC,SAAS,CAAC,CAAA;AAOhD,MAAM,MAAM,SAAS,GAAG,OAAO,CAAA;AAC/B,MAAM,MAAM,UAAU,GAAG,YAAY,CAAC,SAAS,CAAC,CAAA;AAEhD,MAAM,MAAM,WAAW,GAAG,OAAO,CAAA;AACjC,MAAM,MAAM,YAAY,GAAG,YAAY,CAAC,WAAW,CAAC,CAAA;AAEpD,MAAM,MAAM,mBAAmB,GAAG,YAAY,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAA;AAGrE,MAAM,MAAM,SAAS,GAAG,YAAY,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAA;AAE5D;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,qBAAqB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CACnC,CAAA;AAED,qBAAa,QAAQ;IASV,SAAS,EAAE,eAAe;IARnC,KAAK,EAAE,aAAa,CAAA;IACpB,IAAI,EAAE,YAAY,CAAA;IAClB,KAAK,EAAE,aAAa,CAAA;IACpB,KAAK,EAAE,aAAa,CAAA;IACpB,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IAC5B,MAAM,EAAE,cAAc,CAAA;gBAGb,SAAS,EAAE,eAAe,EACjC,eAAe,EAAE,MAAM,EAAE,YAAK,EAC9B,MAAM,EAAE,cAAc;IAclB,qBAAqB,CACzB,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,UAAU,GACd,OAAO,CAAC,cAAc,CAAC;IA0BpB,eAAe,CACnB,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,UAAU,GACd,OAAO,CAAC,cAAc,CAAC;IA6BpB,oBAAoB,CACxB,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,UAAU,GACd,OAAO,CAAC,cAAc,CAAC;IAYpB,uBAAuB,CAC3B,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,UAAU,GACd,OAAO,CAAC,cAAc,CAAC;IAgEpB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,cAAc,CAAC;IAYtE,iBAAiB,CACrB,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,UAAU,EACf,IAAI,CAAC,EAAE;QAAE,WAAW,EAAE,OAAO,CAAA;KAAE,GAC9B,OAAO,CAAC,cAAc,CAAC;IA4BpB,gBAAgB,CACpB,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,UAAU,GACd,OAAO,CAAC,cAAc,CAAC;IAYpB,sBAAsB,CAC1B,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,UAAU,GACd,OAAO,CAAC,cAAc,CAAC;IAgDpB,YAAY,CAChB,IAAI,EAAE,OAAO,EAAE,EACf,GAAG,EAAE,UAAU,EACf,KAAK,GAAE,cAAmB,EAC1B,OAAO,GAAE,IAAI,CAAC,wBAAwB,EAAE,2BAA2B,CAAM,GACxE,OAAO,CAAC,cAAc,CAAC;YAkKZ,iBAAiB;IAiEzB,gBAAgB,CACpB,KAAK,EAAE,QAAQ,EAAE,EACjB,GAAG,EAAE,UAAU,GACd,OAAO,CAAC,cAAc,CAAC;IAyDpB,kBAAkB,CACtB,IAAI,EAAE,OAAO,EAAE,EACf,GAAG,EAAE,UAAU,GACd,OAAO,CAAC,cAAc,CAAC;IAoCpB,eAAe,CACnB,IAAI,EAAE,MAAM,EAAE,EAAE,kCAAkC;IAClD,GAAG,EAAE,UAAU,GACd,OAAO,CAAC,cAAc,CAAC;IA+BpB,wBAAwB,CAC5B,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,UAAU,GACd,OAAO,CAAC,cAAc,CAAC;IA+BpB,mBAAmB,CACvB,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,UAAU,GACd,OAAO,CAAC,cAAc,CAAC;IAgFpB,YAAY,CAChB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,UAAU,GACd,OAAO,CAAC,cAAc,CAAC;IA6BpB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,UAAU;IAY9C,oBAAoB,CACxB,MAAM,EAAE,YAAY,EAAE,EACtB,GAAG,EAAE,UAAU,GACd,OAAO,CAAC,cAAc,CAAC;IAqDpB,gBAAgB,CACpB,aAAa,EAAE,YAAY,EAAE,EAC7B,GAAG,EAAE,UAAU,GACd,OAAO,CAAC,cAAc,CAAC;IAsCpB,cAAc,CAClB,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,UAAU,GACd,OAAO,CAAC,cAAc,CAAC;IAuBpB,0BAA0B,CAC9B,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,eAAe;IAC9C,GAAG,EAAE,UAAU,GACd,OAAO,CAAC,mBAAmB,CAAC;IAqEzB,eAAe,CACnB,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,UAAU,GACd,OAAO,CAAC,cAAc,CAAC;IAqBpB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,gBAAgB,UAAQ;IAgH/C,aAAa,CAAC,IAAI,EAAE,cAAc;IA4BlC,UAAU,CAAC,MAAM,EAAE,MAAM;CAOhC;AA0HD,eAAO,MAAM,WAAW,GACtB,QAAQ,cAAc,EACtB,QAAQ,cAAc,KACrB,cAqDF,CAAA;AAED,eAAO,MAAM,eAAe,GAAI,GAAG,QAAQ,cAAc,EAAE,mBAE1D,CAAA"}
@@ -7,7 +7,6 @@ exports.mergeManyStates = exports.mergeStates = exports.Hydrator = exports.Hydra
7
7
  const node_assert_1 = __importDefault(require("node:assert"));
8
8
  const common_1 = require("@atproto/common");
9
9
  const syntax_1 = require("@atproto/syntax");
10
- const feature_gates_1 = require("../feature-gates");
11
10
  const lexicons_1 = require("../lexicon/lexicons");
12
11
  const record_1 = require("../lexicon/types/app/bsky/embed/record");
13
12
  const recordWithMedia_1 = require("../lexicon/types/app/bsky/embed/recordWithMedia");
@@ -63,11 +62,16 @@ class HydrateCtx {
63
62
  writable: true,
64
63
  value: this.vals.includeDebugField
65
64
  });
66
- Object.defineProperty(this, "featureGates", {
65
+ /**
66
+ * Cache of evaluated feature gates to be used in a given request lifecycle.
67
+ * The actual evaluations happen at the top of the route handler and the
68
+ * results are stored in this map.
69
+ */
70
+ Object.defineProperty(this, "featureGatesMap", {
67
71
  enumerable: true,
68
72
  configurable: true,
69
73
  writable: true,
70
- value: this.vals.featureGates || new Map()
74
+ value: this.vals.featureGatesMap || new Map()
71
75
  });
72
76
  }
73
77
  // Convenience with use with dataplane.getActors cache control
@@ -553,7 +557,7 @@ class Hydrator {
553
557
  // - list basic
554
558
  async hydrateThreadPosts(refs, ctx) {
555
559
  const postsState = await this.hydratePosts(refs, ctx, undefined, {
556
- processDynamicTagsForView: ctx.featureGates.get(feature_gates_1.FeatureGateID.ThreadsReplyRankingExplorationEnable)
560
+ processDynamicTagsForView: ctx.featureGatesMap.get('threads:reply_ranking_exploration:enable')
557
561
  ? 'thread'
558
562
  : undefined,
559
563
  });
@@ -1002,7 +1006,7 @@ class Hydrator {
1002
1006
  includeTakedowns: vals.includeTakedowns,
1003
1007
  include3pBlocks: vals.include3pBlocks,
1004
1008
  includeDebugField,
1005
- featureGates: vals.featureGates,
1009
+ featureGatesMap: vals.featureGatesMap,
1006
1010
  });
1007
1011
  }
1008
1012
  async resolveUri(uriStr) {