ts-server-lib 0.0.48

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 (46) hide show
  1. package/LICENSE +1 -0
  2. package/README.md +8 -0
  3. package/db/TSJournal.d.ts +108 -0
  4. package/db/TSJournal.js +229 -0
  5. package/db/TSMongo.d.ts +103 -0
  6. package/db/TSMongo.js +516 -0
  7. package/db/TSRQW.d.ts +625 -0
  8. package/db/TSRQW.js +1204 -0
  9. package/db/TSRedis.d.ts +530 -0
  10. package/db/TSRedis.js +1368 -0
  11. package/db/TSRedisTB.d.ts +80 -0
  12. package/db/TSRedisTB.js +178 -0
  13. package/package.json +85 -0
  14. package/ussd/TSUssdMenu.d.ts +139 -0
  15. package/ussd/TSUssdMenu.js +368 -0
  16. package/ussd/TSUssdScreen.d.ts +58 -0
  17. package/ussd/TSUssdScreen.js +218 -0
  18. package/ussd/index.d.ts +3 -0
  19. package/ussd/index.js +19 -0
  20. package/ussd/providers/AfricasTalking.d.ts +3 -0
  21. package/ussd/providers/AfricasTalking.js +17 -0
  22. package/ussd/providers/AirtelDRC.d.ts +9 -0
  23. package/ussd/providers/AirtelDRC.js +31 -0
  24. package/ussd/providers/OrangeDRC.d.ts +5 -0
  25. package/ussd/providers/OrangeDRC.js +213 -0
  26. package/ussd/providers/VodacomDRC.d.ts +9 -0
  27. package/ussd/providers/VodacomDRC.js +48 -0
  28. package/ussd/providers/_.d.ts +55 -0
  29. package/ussd/providers/_.js +83 -0
  30. package/ussd/providers/index.d.ts +13 -0
  31. package/ussd/providers/index.js +56 -0
  32. package/utils/TSFifo.d.ts +109 -0
  33. package/utils/TSFifo.js +145 -0
  34. package/utils/TSFile.d.ts +36 -0
  35. package/utils/TSFile.js +244 -0
  36. package/utils/TSHash.d.ts +19 -0
  37. package/utils/TSHash.js +71 -0
  38. package/utils/TSRequest.d.ts +248 -0
  39. package/utils/TSRequest.js +689 -0
  40. package/utils/TSStub.d.ts +159 -0
  41. package/utils/TSStub.js +296 -0
  42. package/utils/abort.d.ts +18 -0
  43. package/utils/abort.js +97 -0
  44. package/utils/mime.json +11358 -0
  45. package/utils/object-keys.d.ts +39 -0
  46. package/utils/object-keys.js +52 -0
@@ -0,0 +1,9 @@
1
+ export declare function response(text: string, { response }: {
2
+ response: {
3
+ set: (key: string, val: string) => void;
4
+ };
5
+ }): string;
6
+ export declare function request(request: {
7
+ body?: Record<string, unknown>;
8
+ }, response: Record<string, unknown>): Record<string, unknown>;
9
+ export declare function text(prev: string | undefined, body: Record<string, unknown>): string;
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.response = response;
4
+ exports.request = request;
5
+ exports.text = text;
6
+ const _1 = require("./_");
7
+ function response(text, { response }) {
8
+ const action = text.startsWith('CON ');
9
+ response.set('Freeflow', action ? 'FC' : 'FB');
10
+ text = text.replace(/CON\s|END\s/g, '');
11
+ return text;
12
+ }
13
+ function request(request, response) {
14
+ const body = (0, _1.body)(request);
15
+ response.type('text/html');
16
+ response.charset = 'UTF-8';
17
+ if (!String(body.MBILE_NUMBER ?? '').startsWith('+')) {
18
+ body.MBILE_NUMBER = '+' + String(body.MBILE_NUMBER);
19
+ }
20
+ if (Object.hasOwn(body, 'INPUT')) {
21
+ body.message = body.INPUT;
22
+ }
23
+ request.body = (0, _1.mapArgs)(body, _1.providersMap[_1.TSUssdProviders.airteldrc]);
24
+ return request.body;
25
+ }
26
+ function text(prev = '', body) {
27
+ if (Object.hasOwn(body, 'message') && body.message !== body.serviceCode) {
28
+ prev += '*' + (0, _1.purify)(String(body.message ?? ''));
29
+ }
30
+ return prev;
31
+ }
@@ -0,0 +1,5 @@
1
+ export declare function response(text: string, _rr?: unknown): string;
2
+ export declare function request(request: {
3
+ body?: Record<string, unknown>;
4
+ }, response: Record<string, unknown>): Record<string, unknown>;
5
+ export declare function text(prev: string | undefined, body: Record<string, unknown>): string;
@@ -0,0 +1,213 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.response = response;
4
+ exports.request = request;
5
+ exports.text = text;
6
+ const _1 = require("./_");
7
+ class OrangeTextUSSD {
8
+ text;
9
+ description;
10
+ attribute;
11
+ constructor() {
12
+ this.text = '';
13
+ this.description = String(Math.random().toFixed(9));
14
+ this.attribute = { nav: 'default' };
15
+ }
16
+ /**
17
+ * Append a line text without <br/>
18
+ * @param text
19
+ */
20
+ appendText(text) {
21
+ this.text += text;
22
+ }
23
+ /**
24
+ * Append a line text with <br/>
25
+ * @param text
26
+ */
27
+ appendLineText(text) {
28
+ this.text += '<br/>' + text;
29
+ }
30
+ /**
31
+ * define the text of page
32
+ * @param text
33
+ */
34
+ setText(text) {
35
+ this.text = text;
36
+ }
37
+ /**
38
+ * define the value of description of the page
39
+ * @param value
40
+ */
41
+ setPageDescription(value) {
42
+ this.description = value;
43
+ }
44
+ /**
45
+ * add a attribute in page header
46
+ * @param key
47
+ * @param value
48
+ */
49
+ setAttribute(key, value) {
50
+ this.attribute[key] = value;
51
+ }
52
+ /**
53
+ * remove a key in a header page
54
+ * @param key
55
+ */
56
+ removeAttribute(key) {
57
+ delete this.attribute[key];
58
+ }
59
+ /**
60
+ * get the value of the key in the header page
61
+ * @param key
62
+ * @return {*}
63
+ */
64
+ getAttribute(key) {
65
+ return this.attribute[key];
66
+ }
67
+ /**
68
+ * get all attributes values in a object
69
+ * @return {{count: boolean}|*}
70
+ */
71
+ getAllAttributes() {
72
+ return this.attribute;
73
+ }
74
+ /**
75
+ * generate a string of key and value
76
+ * @return {string}
77
+ * @private
78
+ */
79
+ __attributeToString() {
80
+ const attr = this.attribute;
81
+ let result = '';
82
+ for (const key in attr) {
83
+ if (Object.hasOwn(attr, key)) {
84
+ result += key + '=\'' + attr[key] + '\' ';
85
+ }
86
+ }
87
+ return result;
88
+ }
89
+ /**
90
+ * Generate the body of page
91
+ * @return {string|*}
92
+ * @protected
93
+ */
94
+ __toGenerateBody() {
95
+ return this.text;
96
+ }
97
+ /**
98
+ * generate the page
99
+ * @param body the body of page
100
+ * @return {string}
101
+ * @private
102
+ */
103
+ __toGeneratePage(body) {
104
+ let page = '<?xml version=\'1.0\' encoding=\'UTF-8\'?>';
105
+ page += '<!DOCTYPE pages SYSTEM \'cellflash-1.3.dtd\'>';
106
+ page += '<pages descr=\'' + this.description + '\'>';
107
+ page += '<page ' + this.__attributeToString() + '>';
108
+ page += body;
109
+ page += '</page></pages>';
110
+ return page;
111
+ }
112
+ /**
113
+ * generate the form
114
+ * @param text
115
+ * @param action
116
+ * @param name
117
+ * @return {string}
118
+ */
119
+ form(text = ' ', action = '', name = '_input') {
120
+ return '<form keywords=\'disable\' action=\'' + action + '\'><entry var=\'' + name + '\'><prompt>' + text + '</prompt></entry></form>';
121
+ }
122
+ /**
123
+ * generate a anchor
124
+ * @param key
125
+ * @param href
126
+ * @param text
127
+ * @return {string}
128
+ */
129
+ anchor(text = ' ', href, key) {
130
+ let attr = '';
131
+ if (key) {
132
+ attr += 'key=\'' + key + '\' ';
133
+ }
134
+ if (href) {
135
+ attr += 'href=\'' + href + '\'';
136
+ }
137
+ return '<a ' + attr + '>' + text + '</a>';
138
+ }
139
+ /**
140
+ * generate the page
141
+ * @return {string}
142
+ */
143
+ toXml() {
144
+ return this.__toGeneratePage(this.__toGenerateBody());
145
+ }
146
+ escapeXml(unsafe) {
147
+ return this.unEscapeXml(unsafe).replace(/[&<>'"]/g, c => {
148
+ if (c === '&')
149
+ return '&amp;';
150
+ if (c === '<')
151
+ return '&lt;';
152
+ if (c === '>')
153
+ return '&gt;';
154
+ if (c === '\'')
155
+ return '&apos;';
156
+ if (c === '"')
157
+ return '&quot;';
158
+ return c;
159
+ });
160
+ }
161
+ unEscapeXml(text) {
162
+ const entities = [
163
+ ['amp', '&'], ['apos', '\''], ['#x27', '\''], ['#x2F', '/'],
164
+ ['nbsp', ' '], ['#39', '\''], ['#47', '/'], ['lt', '<'], ['gt', '>'], ['quot', '"']
165
+ ];
166
+ for (let i = 0, max = entities.length; i < max; ++i) {
167
+ text = text.replace(new RegExp('&' + entities[i][0] + ';', 'g'), entities[i][1]);
168
+ }
169
+ return text;
170
+ }
171
+ }
172
+ function response(text, _rr) {
173
+ let action = 'request', oText;
174
+ const otu = new OrangeTextUSSD();
175
+ if (text.startsWith('END ')) {
176
+ otu.setAttribute('nav', 'end');
177
+ action = 'end';
178
+ }
179
+ oText = otu.escapeXml(text.replace(/CON\s|END\s/g, '')).replace(/\n/g, '<br />');
180
+ otu.setAttribute('volatile', true);
181
+ otu.setAttribute('menutext', 'notext');
182
+ otu.setAttribute('backtext', 'notext');
183
+ if (action !== 'end') {
184
+ oText += otu.form();
185
+ }
186
+ if (oText.match(/#0/g)) {
187
+ // outputText = outputText.replace('#0)', otu.anchor('#0)', '/?_input=#0'));
188
+ }
189
+ if (oText.match(/#1/g)) {
190
+ // outputText = outputText.replace('#1)', otu.anchor('#1)', '/?_input=#1'));
191
+ }
192
+ otu.setText(oText);
193
+ return otu.toXml();
194
+ }
195
+ function request(request, response) {
196
+ const body = (0, _1.body)(request);
197
+ response.type('application/xml');
198
+ response.charset = 'UTF-8';
199
+ if (!String(body['user-msisdn'] ?? '').startsWith('+')) {
200
+ body['user-msisdn'] = '+' + body['user-msisdn'];
201
+ }
202
+ if (Object.hasOwn(body, '_input')) {
203
+ body.message = body._input;
204
+ }
205
+ request.body = (0, _1.mapArgs)(body, _1.providersMap[_1.TSUssdProviders.orangedrc]);
206
+ return request.body;
207
+ }
208
+ function text(prev = '', body) {
209
+ if (Object.hasOwn(body, 'message')) {
210
+ prev += '*' + (0, _1.purify)(String(body.message ?? ''));
211
+ }
212
+ return prev;
213
+ }
@@ -0,0 +1,9 @@
1
+ export declare function response(text: string, { request: { body } }: {
2
+ request: {
3
+ body: Record<string, unknown>;
4
+ };
5
+ }): string;
6
+ export declare function request(request: {
7
+ body?: Record<string, unknown>;
8
+ }, response: Record<string, unknown>): Record<string, unknown>;
9
+ export declare function text(prev: string | undefined, body: Record<string, unknown>): string;
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.response = response;
4
+ exports.request = request;
5
+ exports.text = text;
6
+ const _1 = require("./_");
7
+ function response(text, { request: { body } }) {
8
+ let action = 'request';
9
+ if (text.startsWith('END ')) {
10
+ action = 'end';
11
+ }
12
+ text = text.replace(/CON\s|END\s/g, '');
13
+ text = '<?xml version="1.0" encoding="UTF-8" ?>' +
14
+ '<methodResponse><params><param><value><struct>' +
15
+ '<member><name>USSDResponseString</name><value><string>' + text + '</string></value></member>' +
16
+ '<member><name>action</name><value><string>' + action + '</string></value></member>' +
17
+ '<member><name>TransactionTime</name><value><dateTime.iso8601>' + body.transactionTime + '</dateTime.iso8601></value></member>' +
18
+ '<member><name>TransactionId</name><value><string>' + body.sessionId + '</string></value></member>' +
19
+ '</struct></value></param></params></methodResponse>';
20
+ return text;
21
+ }
22
+ function request(request, response) {
23
+ const body = (0, _1.body)(request);
24
+ response.type('text/xml');
25
+ response.charset = 'UTF-8';
26
+ const transTime = body.TransactionTime;
27
+ body.transactionTime = transTime?.['datetime.iso8601']?.[0];
28
+ if (!String(body.MSISDN ?? '').startsWith('+')) {
29
+ body.MSISDN = '+' + String(body.MSISDN);
30
+ }
31
+ delete body.TransactionTime;
32
+ request.body = (0, _1.mapArgs)(body, {
33
+ vodacom: 'provider',
34
+ TransactionId: 'sessionId',
35
+ USSDServiceCode: 'serviceCode',
36
+ MSISDN: 'phoneNumber',
37
+ networkCode: 'networkCode',
38
+ USSDRequestString: 'message'
39
+ });
40
+ request.body = (0, _1.mapArgs)(body, _1.providersMap[_1.TSUssdProviders.vodacomdrc]);
41
+ return request.body;
42
+ }
43
+ function text(prev = '', body) {
44
+ if (!/(null|fecofa)/i.test(String(body.message ?? ''))) {
45
+ prev += '*' + (0, _1.purify)(String(body.message ?? ''));
46
+ }
47
+ return prev;
48
+ }
@@ -0,0 +1,55 @@
1
+ export declare enum TSUssdProviders {
2
+ vodacomdrc = "vodacom",
3
+ orangedrc = "orange",
4
+ airteldrc = "airtel",
5
+ africastalking = "africastalking"
6
+ }
7
+ export declare function body(request: {
8
+ headers?: Record<string, unknown>;
9
+ body?: Record<string, unknown>;
10
+ query?: Record<string, unknown>;
11
+ params?: Record<string, unknown>;
12
+ }): {
13
+ [x: string]: unknown;
14
+ };
15
+ export declare function mapArgs(args?: Record<string, unknown>, map?: Record<string, unknown>): Record<string, unknown>;
16
+ export declare function purify(text?: string): string;
17
+ export declare function provider({ headers, body, query }: {
18
+ headers?: Record<string, unknown>;
19
+ body?: Record<string, unknown>;
20
+ query?: Record<string, unknown>;
21
+ }): TSUssdProviders;
22
+ export declare const providersMap: {
23
+ africastalking: {
24
+ africastalking: string;
25
+ sessionId: string;
26
+ serviceCode: string;
27
+ phoneNumber: string;
28
+ networkCode: string;
29
+ text: string;
30
+ };
31
+ vodacom: {
32
+ vodacom: string;
33
+ TransactionId: string;
34
+ USSDServiceCode: string;
35
+ MSISDN: string;
36
+ networkCode: string;
37
+ USSDRequestString: string;
38
+ };
39
+ orange: {
40
+ orange: string;
41
+ 'user-session': string;
42
+ 'user-imsi': string;
43
+ 'user-msisdn': string;
44
+ networkCode: string;
45
+ 'user-identity': string;
46
+ };
47
+ airtel: {
48
+ airtel: string;
49
+ MBILE_NUMBER: string;
50
+ USSDServiceCode: string;
51
+ SESSION_ID: string;
52
+ INPUT: string;
53
+ IMEI: string;
54
+ };
55
+ };
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.providersMap = exports.TSUssdProviders = void 0;
4
+ exports.body = body;
5
+ exports.mapArgs = mapArgs;
6
+ exports.purify = purify;
7
+ exports.provider = provider;
8
+ var TSUssdProviders;
9
+ (function (TSUssdProviders) {
10
+ TSUssdProviders["vodacomdrc"] = "vodacom";
11
+ TSUssdProviders["orangedrc"] = "orange";
12
+ TSUssdProviders["airteldrc"] = "airtel";
13
+ TSUssdProviders["africastalking"] = "africastalking";
14
+ })(TSUssdProviders || (exports.TSUssdProviders = TSUssdProviders = {}));
15
+ function body(request) {
16
+ const { headers = {}, body = {}, query = {}, params = {} } = request;
17
+ return { ...headers, ...body, ...query, ...params };
18
+ }
19
+ function mapArgs(args = {}, map = {}) {
20
+ const results = { provider: map.provider };
21
+ for (const p in args) {
22
+ if (Object.hasOwn(args, p) && Object.hasOwn(map, p)) {
23
+ results[String(map[p])] = args[p];
24
+ }
25
+ else if (Object.hasOwn(args, p)) {
26
+ results[p] = args[p];
27
+ }
28
+ }
29
+ return results;
30
+ }
31
+ function purify(text = '') {
32
+ return text ? text.replace(/[^\w\s]/g, '') : '';
33
+ }
34
+ function provider({ headers = {}, body, query }) {
35
+ const h = headers;
36
+ if (body && typeof body === 'object' && 'methodcall' in body) {
37
+ return TSUssdProviders.vodacomdrc;
38
+ }
39
+ else if (h['user-session'] && h['user-msisdn']) {
40
+ return TSUssdProviders.orangedrc;
41
+ }
42
+ else if (query && typeof query === 'object' && 'MBILE_NUMBER' in query) {
43
+ return TSUssdProviders.airteldrc;
44
+ }
45
+ else if (body && typeof body === 'object' && 'text' in body) {
46
+ return TSUssdProviders.africastalking;
47
+ }
48
+ return TSUssdProviders.africastalking;
49
+ }
50
+ exports.providersMap = {
51
+ [TSUssdProviders.africastalking]: {
52
+ africastalking: 'provider',
53
+ sessionId: 'sessionId',
54
+ serviceCode: 'serviceCode',
55
+ phoneNumber: 'phoneNumber',
56
+ networkCode: 'networkCode',
57
+ text: 'message'
58
+ },
59
+ [TSUssdProviders.vodacomdrc]: {
60
+ vodacom: 'provider',
61
+ TransactionId: 'sessionId',
62
+ USSDServiceCode: 'serviceCode',
63
+ MSISDN: 'phoneNumber',
64
+ networkCode: 'networkCode',
65
+ USSDRequestString: 'message'
66
+ },
67
+ [TSUssdProviders.orangedrc]: {
68
+ orange: 'provider',
69
+ 'user-session': 'sessionId',
70
+ 'user-imsi': 'serviceCode',
71
+ 'user-msisdn': 'phoneNumber',
72
+ networkCode: 'networkCode',
73
+ 'user-identity': 'message'
74
+ },
75
+ [TSUssdProviders.airteldrc]: {
76
+ airtel: 'provider',
77
+ MBILE_NUMBER: 'phoneNumber',
78
+ USSDServiceCode: 'serviceCode',
79
+ SESSION_ID: 'sessionId',
80
+ INPUT: 'message',
81
+ IMEI: 'networkCode'
82
+ }
83
+ };
@@ -0,0 +1,13 @@
1
+ import * as africastalking from './AfricasTalking';
2
+ import * as airteldrc from './AirtelDRC';
3
+ import * as orangedrc from './OrangeDRC';
4
+ import * as vodacomdrc from './VodacomDRC';
5
+ import { TSUssdProviders } from './_';
6
+ export * from './_';
7
+ export declare const providers: {
8
+ vodacom: typeof vodacomdrc;
9
+ orange: typeof orangedrc;
10
+ airtel: typeof airteldrc;
11
+ africastalking: typeof africastalking;
12
+ };
13
+ export declare const providersAvailable: TSUssdProviders[];
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
36
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.providersAvailable = exports.providers = void 0;
40
+ const africastalking = __importStar(require("./AfricasTalking"));
41
+ const airteldrc = __importStar(require("./AirtelDRC"));
42
+ const orangedrc = __importStar(require("./OrangeDRC"));
43
+ const vodacomdrc = __importStar(require("./VodacomDRC"));
44
+ const _1 = require("./_");
45
+ __exportStar(require("./_"), exports);
46
+ exports.providers = {
47
+ [_1.TSUssdProviders.vodacomdrc]: vodacomdrc,
48
+ [_1.TSUssdProviders.orangedrc]: orangedrc,
49
+ [_1.TSUssdProviders.airteldrc]: airteldrc,
50
+ [_1.TSUssdProviders.africastalking]: africastalking
51
+ };
52
+ exports.providersAvailable = [
53
+ _1.TSUssdProviders.vodacomdrc,
54
+ _1.TSUssdProviders.orangedrc,
55
+ _1.TSUssdProviders.airteldrc
56
+ ];
@@ -0,0 +1,109 @@
1
+ /**
2
+ * TSFifo — O(1) FIFO queue. The canonical replacement for `array.shift()` and `array.splice(0, n)`.
3
+ *
4
+ * ### Why this exists as a primitive
5
+ *
6
+ * `shift()` re-indexes every remaining element, so draining a queue of N with `shift()` is O(N²). The
7
+ * cost arrives at exactly the worst moment: queues are only deep when the system is already stressed, so
8
+ * the drain gets more expensive the more there is to drain. That is a positive feedback loop, not a
9
+ * constant factor — the same shape as the O(N) in-flight scan that was removed from the sports feed's
10
+ * pressure signal for the same reason.
11
+ *
12
+ * ### Why it lives HERE, in ts-server-lib
13
+ *
14
+ * It started in core-service, which was one layer too high: {@link TSRQWPool}'s own task backlog — the
15
+ * FIFTH instance of this defect — could not use it, because core-service depends on ts-server-lib and not
16
+ * the reverse. Copying it down would have produced two implementations of a module whose entire purpose is
17
+ * being the only one. So it sits at the bottom layer and core-service re-exports it, leaving
18
+ * `import { TSFifo } from 'core-service'` working for every service-side caller.
19
+ *
20
+ * It had already been diagnosed and hand-fixed once (`sports-service` `capacity-pump.ts`, head pointer plus
21
+ * periodic compaction, with the reasoning written out) while four other queues kept their own shape:
22
+ *
23
+ * - `feed/amqp/dispatch-admission.ts` — the admission gate's `hotQueue` / `moneyQueue`. Its own doc
24
+ * records `hotQueued` reaching **683** with 24.2 s waits; releasing 683 waiters by `shift()` is ~233 K
25
+ * element moves, on the money path, during a backlog.
26
+ * - `feed/amqp/worker.ts` — `pendingOutcomePublishes`, an uncapped array drained `splice(0, 32)` at a
27
+ * time, so each batch re-indexes everything still queued behind it.
28
+ * - `resolvers/outcomes.ts` — a 64-cap subscriber backlog. Cheap at that size, but it is the site that
29
+ * proves the point: its correct eviction is `'drop-oldest'`, the OPPOSITE of the admission queue's, and
30
+ * that decision was previously an unexplained `queue.shift()` in a callback.
31
+ * - `db/TSRQW.ts` `TSRQWPool.tasks` — the parse-pool backlog, right in the AMQP drain path. Inert while
32
+ * the pool keeps up (`parseQueued ~0` measured) and quadratic exactly when it stops keeping up, which
33
+ * is the moment a super-linear drain hurts most.
34
+ *
35
+ * Five queues, five shapes, one of them correct. That is what a per-site fix always converges to, so the
36
+ * shape is not the interesting part of this module — having exactly one of it is, and naming the overflow
37
+ * policy so the difference between those sites is a type rather than a comment.
38
+ *
39
+ * ALL FIVE now use this class; a sixth hand-rolled queue is the thing to reject in review.
40
+ *
41
+ * ### Design
42
+ *
43
+ * A head index makes `shift` O(1); the consumed prefix is reclaimed in one `splice` once it passes
44
+ * {@link TSFIFO_COMPACT_THRESHOLD}, which keeps the backing array from growing without bound while keeping
45
+ * amortised cost O(1). `maxSize` is optional and, when set, makes overflow an explicit, observable event
46
+ * rather than unbounded memory — a queue with no ceiling is a slower way to run out of RAM.
47
+ */
48
+ /**
49
+ * Consumed slots tolerated before the backing array is compacted.
50
+ *
51
+ * Compaction is one O(size) `splice` amortised over that many O(1) shifts, so a larger value trades
52
+ * transient memory for fewer memmoves. 1024 matches the value `capacity-pump` arrived at empirically.
53
+ */
54
+ export declare const TSFIFO_COMPACT_THRESHOLD = 1024;
55
+ export interface TSFifoOptions {
56
+ /**
57
+ * Hard cap on queued items. Omit for unbounded (correct only when an upstream gate already bounds the
58
+ * producer — e.g. a dedup key set, where the ceiling is the key space and not the event rate).
59
+ */
60
+ maxSize?: number;
61
+ /**
62
+ * WHICH END to sacrifice when `maxSize` is reached. There is no safe default for this question, which is
63
+ * why it is a named option rather than a per-site `if`:
64
+ *
65
+ * - `'reject-new'` (default) — keep the queued items, refuse the arrival. Correct when order carries
66
+ * meaning and the oldest entry is the one that has waited longest for its turn: an admission queue,
67
+ * a heal backlog. `push` returns false and the caller MUST handle it.
68
+ * - `'drop-oldest'` — evict the head to make room. Correct for a LIVE stream, where a late consumer
69
+ * wants current state and a replay of stale values is worse than a gap: an odds subscription.
70
+ *
71
+ * Getting this backwards is silent: both shapes "work", one just serves the wrong data.
72
+ */
73
+ overflow?: 'reject-new' | 'drop-oldest';
74
+ /** Compaction threshold override — see {@link TSFIFO_COMPACT_THRESHOLD}. */
75
+ compactThreshold?: number;
76
+ }
77
+ export declare class TSFifo<T> {
78
+ private items;
79
+ private head;
80
+ private peak;
81
+ private lost;
82
+ private readonly compactThreshold;
83
+ private readonly maxSize;
84
+ private readonly overflow;
85
+ constructor(opts?: TSFifoOptions);
86
+ /** Items currently queued. */
87
+ get size(): number;
88
+ /** Highest `size` observed since construction — the number a gauge should report. */
89
+ get peakSize(): number;
90
+ /**
91
+ * Items lost to `maxSize` — rejected arrivals under `'reject-new'`, evicted heads under `'drop-oldest'`.
92
+ * Non-zero means the cap is doing something and should be visible: a silent drop is how data loss hides.
93
+ */
94
+ get dropped(): number;
95
+ /**
96
+ * Append. Returns false only under `'reject-new'` overflow when the queue is full — nothing was
97
+ * enqueued. Under `'drop-oldest'` it always returns true, having evicted the head to make room.
98
+ */
99
+ push(item: T): boolean;
100
+ /** Remove and return the oldest item, or undefined when empty. O(1). */
101
+ shift(): T | undefined;
102
+ /** Oldest item without removing it, or undefined when empty. */
103
+ peek(): T | undefined;
104
+ /** Drain up to `max` items, oldest first. Amortised O(returned), never O(remaining). */
105
+ drain(max: number): T[];
106
+ /** Discard everything. Use on shutdown/reset paths. */
107
+ clear(): void;
108
+ private compactIfNeeded;
109
+ }