@far-world-labs/verblets 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +98 -213
- package/dist/index.browser.js +221 -0
- package/dist/index.js +696 -0
- package/dist/shared-CMgpfDG4.js +10714 -0
- package/package.json +38 -15
- package/.cursor/launch.json +0 -30
- package/.cursor/settings.json +0 -20
- package/.github/workflows/branch-protection.yml +0 -22
- package/.github/workflows/ci.yml +0 -165
- package/.husky/pre-commit +0 -4
- package/.prettierrc +0 -6
- package/.release-it.json +0 -12
- package/.vitest.config.examples.js +0 -12
- package/.vitest.config.js +0 -8
- package/.vscode/launch.json +0 -31
- package/AGENTS.md +0 -220
- package/DEVELOPING.md +0 -105
- package/docker-compose.yml +0 -7
- package/eslint.config.js +0 -80
- package/scripts/clear-redis.js +0 -74
- package/scripts/generate-chain/index.js +0 -111
- package/scripts/generate-lib/index.js +0 -68
- package/scripts/generate-test/index.js +0 -137
- package/scripts/generate-verblet/README.md +0 -17
- package/scripts/generate-verblet/index.js +0 -110
- package/scripts/run.sh +0 -15
- package/scripts/runner/index.js +0 -56
- package/scripts/simple-editor/README.md +0 -34
- package/scripts/simple-editor/index.js +0 -79
- package/scripts/summarize-files/index.js +0 -70
- package/src/chains/README.md +0 -30
- package/src/chains/anonymize/README.md +0 -21
- package/src/chains/anonymize/index.examples.js +0 -75
- package/src/chains/anonymize/index.js +0 -121
- package/src/chains/anonymize/index.spec.js +0 -78
- package/src/chains/bulk-central-tendency/index.examples.js +0 -138
- package/src/chains/bulk-central-tendency/index.js +0 -91
- package/src/chains/bulk-filter/README.md +0 -21
- package/src/chains/bulk-filter/index.examples.js +0 -22
- package/src/chains/bulk-filter/index.js +0 -58
- package/src/chains/bulk-filter/index.spec.js +0 -38
- package/src/chains/bulk-find/README.md +0 -16
- package/src/chains/bulk-find/index.examples.js +0 -20
- package/src/chains/bulk-find/index.js +0 -30
- package/src/chains/bulk-find/index.spec.js +0 -26
- package/src/chains/bulk-group/README.md +0 -23
- package/src/chains/bulk-group/index.examples.js +0 -18
- package/src/chains/bulk-group/index.js +0 -34
- package/src/chains/bulk-group/index.spec.js +0 -41
- package/src/chains/bulk-map/README.md +0 -43
- package/src/chains/bulk-map/index.examples.js +0 -17
- package/src/chains/bulk-map/index.js +0 -86
- package/src/chains/bulk-map/index.spec.js +0 -44
- package/src/chains/bulk-reduce/README.md +0 -12
- package/src/chains/bulk-reduce/index.examples.js +0 -15
- package/src/chains/bulk-reduce/index.js +0 -13
- package/src/chains/bulk-reduce/index.spec.js +0 -25
- package/src/chains/bulk-score/README.md +0 -16
- package/src/chains/bulk-score/bulk-score-result.json +0 -18
- package/src/chains/bulk-score/index.examples.js +0 -22
- package/src/chains/bulk-score/index.js +0 -133
- package/src/chains/bulk-score/index.spec.js +0 -30
- package/src/chains/category-samples/README.md +0 -61
- package/src/chains/category-samples/index.examples.js +0 -103
- package/src/chains/category-samples/index.js +0 -134
- package/src/chains/collect-terms/README.md +0 -12
- package/src/chains/collect-terms/index.examples.js +0 -16
- package/src/chains/collect-terms/index.js +0 -44
- package/src/chains/collect-terms/index.spec.js +0 -25
- package/src/chains/conversation/README.md +0 -26
- package/src/chains/conversation/index.examples.js +0 -398
- package/src/chains/conversation/index.js +0 -126
- package/src/chains/conversation/index.spec.js +0 -148
- package/src/chains/conversation/turn-policies.js +0 -93
- package/src/chains/conversation/turn-policies.md +0 -123
- package/src/chains/conversation/turn-policies.spec.js +0 -135
- package/src/chains/date/README.md +0 -12
- package/src/chains/date/index.examples.js +0 -47
- package/src/chains/date/index.js +0 -74
- package/src/chains/date/index.spec.js +0 -62
- package/src/chains/disambiguate/README.md +0 -22
- package/src/chains/disambiguate/disambiguate-meanings-result.json +0 -16
- package/src/chains/disambiguate/index.examples.js +0 -18
- package/src/chains/disambiguate/index.js +0 -92
- package/src/chains/disambiguate/index.spec.js +0 -25
- package/src/chains/dismantle/README.md +0 -67
- package/src/chains/dismantle/dismantle.examples.js +0 -27
- package/src/chains/dismantle/index.examples.js +0 -30
- package/src/chains/dismantle/index.js +0 -303
- package/src/chains/dismantle/index.spec.js +0 -32
- package/src/chains/expect/README.md +0 -171
- package/src/chains/expect/index.examples.js +0 -146
- package/src/chains/expect/index.js +0 -207
- package/src/chains/expect/index.spec.js +0 -324
- package/src/chains/filter-ambiguous/README.md +0 -11
- package/src/chains/filter-ambiguous/index.examples.js +0 -20
- package/src/chains/filter-ambiguous/index.js +0 -49
- package/src/chains/filter-ambiguous/index.spec.js +0 -31
- package/src/chains/glossary/README.md +0 -19
- package/src/chains/glossary/index.examples.js +0 -386
- package/src/chains/glossary/index.js +0 -75
- package/src/chains/glossary/index.spec.js +0 -19
- package/src/chains/intersections/README.md +0 -166
- package/src/chains/intersections/index.examples.js +0 -280
- package/src/chains/intersections/index.js +0 -218
- package/src/chains/intersections/intersection-result.json +0 -38
- package/src/chains/list/index.examples.js +0 -68
- package/src/chains/list/index.js +0 -214
- package/src/chains/list/index.spec.js +0 -67
- package/src/chains/list/list-result.json +0 -16
- package/src/chains/list/schema.json +0 -24
- package/src/chains/llm-logger/README.md +0 -366
- package/src/chains/llm-logger/index.js +0 -591
- package/src/chains/llm-logger/index.spec.js +0 -391
- package/src/chains/llm-logger/schema.json +0 -105
- package/src/chains/questions/index.examples.js +0 -69
- package/src/chains/questions/index.js +0 -135
- package/src/chains/questions/index.spec.js +0 -29
- package/src/chains/scan-js/index.js +0 -116
- package/src/chains/set-interval/README.md +0 -81
- package/src/chains/set-interval/index.examples.js +0 -64
- package/src/chains/set-interval/index.js +0 -152
- package/src/chains/set-interval/index.spec.js +0 -70
- package/src/chains/socratic/README.md +0 -17
- package/src/chains/socratic/index.js +0 -64
- package/src/chains/socratic/index.spec.js +0 -24
- package/src/chains/sort/index.examples.js +0 -36
- package/src/chains/sort/index.js +0 -163
- package/src/chains/sort/index.spec.js +0 -112
- package/src/chains/sort/sort-result.json +0 -16
- package/src/chains/summary-map/README.md +0 -41
- package/src/chains/summary-map/index.examples.js +0 -64
- package/src/chains/summary-map/index.js +0 -226
- package/src/chains/summary-map/index.spec.js +0 -153
- package/src/chains/test/index.js +0 -114
- package/src/chains/test-advice/index.js +0 -35
- package/src/chains/themes/README.md +0 -20
- package/src/chains/themes/index.examples.js +0 -17
- package/src/chains/themes/index.js +0 -28
- package/src/chains/themes/index.spec.js +0 -19
- package/src/chains/veiled-variants/index.examples.js +0 -18
- package/src/chains/veiled-variants/index.js +0 -107
- package/src/chains/veiled-variants/index.spec.js +0 -40
- package/src/constants/common.js +0 -13
- package/src/constants/messages.js +0 -3
- package/src/constants/models.js +0 -184
- package/src/index.js +0 -203
- package/src/json-schemas/README.md +0 -13
- package/src/json-schemas/cars-test.json +0 -11
- package/src/json-schemas/index.js +0 -12
- package/src/json-schemas/intent.json +0 -38
- package/src/json-schemas/schema-dot-org-photograph.json +0 -133
- package/src/json-schemas/schema-dot-org-place.json +0 -129
- package/src/lib/README.md +0 -26
- package/src/lib/any-signal/index.js +0 -28
- package/src/lib/assert/README.md +0 -84
- package/src/lib/assert/index.js +0 -50
- package/src/lib/bulk-filter/README.md +0 -22
- package/src/lib/bulk-filter/index.examples.js +0 -27
- package/src/lib/bulk-filter/index.js +0 -63
- package/src/lib/bulk-filter/index.spec.js +0 -38
- package/src/lib/bulk-find/README.md +0 -18
- package/src/lib/bulk-find/index.examples.js +0 -19
- package/src/lib/bulk-find/index.js +0 -30
- package/src/lib/bulk-find/index.spec.js +0 -41
- package/src/lib/chatgpt/index.js +0 -163
- package/src/lib/combinations/index.js +0 -30
- package/src/lib/combinations/index.spec.js +0 -23
- package/src/lib/editor/index.js +0 -31
- package/src/lib/functional/index.js +0 -28
- package/src/lib/logger-service/index.js +0 -32
- package/src/lib/parse-js-parts/index.js +0 -321
- package/src/lib/parse-js-parts/index.spec.js +0 -156
- package/src/lib/parse-llm-list/README.md +0 -39
- package/src/lib/parse-llm-list/index.js +0 -54
- package/src/lib/parse-llm-list/index.spec.js +0 -59
- package/src/lib/path-aliases/index.js +0 -37
- package/src/lib/path-aliases/index.spec.js +0 -64
- package/src/lib/pave/index.js +0 -34
- package/src/lib/pave/index.spec.js +0 -76
- package/src/lib/prompt-cache/index.js +0 -50
- package/src/lib/retry/index.js +0 -66
- package/src/lib/retry/index.spec.js +0 -86
- package/src/lib/ring-buffer/README.md +0 -82
- package/src/lib/ring-buffer/index.js +0 -235
- package/src/lib/ring-buffer/index.spec.js +0 -388
- package/src/lib/search-best-first/city-walk.spec.js +0 -37
- package/src/lib/search-best-first/index.js +0 -97
- package/src/lib/search-best-first/index.spec.js +0 -35
- package/src/lib/search-js-files/code-features-property-definitions.json +0 -123
- package/src/lib/search-js-files/index.examples.js +0 -22
- package/src/lib/search-js-files/index.js +0 -155
- package/src/lib/search-js-files/index.spec.js +0 -34
- package/src/lib/search-js-files/scan-file.js +0 -242
- package/src/lib/shorten-text/index.js +0 -25
- package/src/lib/shorten-text/index.spec.js +0 -68
- package/src/lib/strip-numeric/index.js +0 -5
- package/src/lib/strip-response/index.js +0 -30
- package/src/lib/template-replace/index.js +0 -23
- package/src/lib/template-replace/index.spec.js +0 -60
- package/src/lib/timed-abort-controller/index.js +0 -41
- package/src/lib/to-bool/index.js +0 -8
- package/src/lib/to-date/index.js +0 -11
- package/src/lib/to-enum/index.js +0 -14
- package/src/lib/to-number/index.js +0 -12
- package/src/lib/to-number-with-units/index.js +0 -51
- package/src/lib/transcribe/index.js +0 -78
- package/src/prompts/README.md +0 -17
- package/src/prompts/as-enum.js +0 -5
- package/src/prompts/as-json-schema.js +0 -9
- package/src/prompts/as-object-with-schema.js +0 -26
- package/src/prompts/as-schema-org-text.js +0 -25
- package/src/prompts/as-schema-org-type.js +0 -1
- package/src/prompts/blog-post.js +0 -7
- package/src/prompts/code-features.js +0 -24
- package/src/prompts/constants.js +0 -101
- package/src/prompts/features-json-schema.js +0 -27
- package/src/prompts/generate-collection.js +0 -26
- package/src/prompts/generate-list.js +0 -48
- package/src/prompts/generate-questions.js +0 -19
- package/src/prompts/index.js +0 -20
- package/src/prompts/intent.js +0 -60
- package/src/prompts/output-succinct-names.js +0 -3
- package/src/prompts/select-from-threshold.js +0 -17
- package/src/prompts/sort.js +0 -31
- package/src/prompts/style.js +0 -38
- package/src/prompts/summarize.js +0 -13
- package/src/prompts/token-budget.js +0 -3
- package/src/prompts/wrap-list.js +0 -11
- package/src/prompts/wrap-variable.js +0 -36
- package/src/services/llm-model/global-overrides.spec.js +0 -432
- package/src/services/llm-model/index.js +0 -308
- package/src/services/llm-model/model.js +0 -21
- package/src/services/llm-model/negotiate.spec.js +0 -447
- package/src/services/redis/index.js +0 -147
- package/src/test/setup.js +0 -20
- package/src/verblets/README.md +0 -26
- package/src/verblets/auto/index.examples.js +0 -31
- package/src/verblets/auto/index.js +0 -28
- package/src/verblets/auto/index.spec.js +0 -32
- package/src/verblets/bool/README.md +0 -36
- package/src/verblets/bool/index.examples.js +0 -80
- package/src/verblets/bool/index.js +0 -25
- package/src/verblets/bool/index.schema.json +0 -14
- package/src/verblets/bool/index.spec.js +0 -33
- package/src/verblets/central-tendency/README.md +0 -166
- package/src/verblets/central-tendency/central-tendency-result.json +0 -24
- package/src/verblets/central-tendency/index.examples.js +0 -196
- package/src/verblets/central-tendency/index.js +0 -171
- package/src/verblets/central-tendency/index.spec.js +0 -148
- package/src/verblets/conversation-turn/README.md +0 -33
- package/src/verblets/conversation-turn/index.examples.js +0 -218
- package/src/verblets/conversation-turn/index.js +0 -68
- package/src/verblets/conversation-turn/index.spec.js +0 -77
- package/src/verblets/conversation-turn-multi/README.md +0 -31
- package/src/verblets/conversation-turn-multi/index.examples.js +0 -160
- package/src/verblets/conversation-turn-multi/index.js +0 -104
- package/src/verblets/conversation-turn-multi/index.spec.js +0 -63
- package/src/verblets/enum/index.examples.js +0 -30
- package/src/verblets/enum/index.js +0 -18
- package/src/verblets/enum/index.spec.js +0 -35
- package/src/verblets/expect/README.md +0 -64
- package/src/verblets/expect/index.examples.js +0 -109
- package/src/verblets/expect/index.js +0 -75
- package/src/verblets/expect/index.spec.js +0 -127
- package/src/verblets/intent/index.examples.js +0 -139
- package/src/verblets/intent/index.js +0 -60
- package/src/verblets/intent/index.spec.js +0 -31
- package/src/verblets/intersection/README.md +0 -16
- package/src/verblets/intersection/index.examples.js +0 -89
- package/src/verblets/intersection/index.js +0 -125
- package/src/verblets/intersection/index.spec.js +0 -60
- package/src/verblets/intersection/intersection-result.json +0 -16
- package/src/verblets/list-expand/README.md +0 -10
- package/src/verblets/list-expand/index.examples.js +0 -14
- package/src/verblets/list-expand/index.js +0 -104
- package/src/verblets/list-expand/index.spec.js +0 -18
- package/src/verblets/list-expand/list-expand-result.json +0 -16
- package/src/verblets/list-filter/README.md +0 -22
- package/src/verblets/list-filter/index.examples.js +0 -26
- package/src/verblets/list-filter/index.js +0 -18
- package/src/verblets/list-filter/index.spec.js +0 -19
- package/src/verblets/list-find/README.md +0 -11
- package/src/verblets/list-find/index.examples.js +0 -15
- package/src/verblets/list-find/index.js +0 -17
- package/src/verblets/list-find/index.spec.js +0 -19
- package/src/verblets/list-group/README.md +0 -16
- package/src/verblets/list-group/index.examples.js +0 -16
- package/src/verblets/list-group/index.js +0 -112
- package/src/verblets/list-group/index.spec.js +0 -35
- package/src/verblets/list-group/list-group-result.json +0 -16
- package/src/verblets/list-map/README.md +0 -11
- package/src/verblets/list-map/index.examples.js +0 -15
- package/src/verblets/list-map/index.js +0 -26
- package/src/verblets/list-map/index.spec.js +0 -17
- package/src/verblets/list-reduce/README.md +0 -10
- package/src/verblets/list-reduce/index.examples.js +0 -14
- package/src/verblets/list-reduce/index.js +0 -21
- package/src/verblets/list-reduce/index.spec.js +0 -27
- package/src/verblets/list-reduce/index.spec.jsx +0 -27
- package/src/verblets/name/README.md +0 -15
- package/src/verblets/name/index.examples.js +0 -28
- package/src/verblets/name/index.js +0 -19
- package/src/verblets/name/index.spec.js +0 -33
- package/src/verblets/name-similar-to/README.md +0 -26
- package/src/verblets/name-similar-to/index.examples.js +0 -18
- package/src/verblets/name-similar-to/index.js +0 -20
- package/src/verblets/name-similar-to/index.spec.js +0 -13
- package/src/verblets/number/index.examples.js +0 -199
- package/src/verblets/number/index.js +0 -25
- package/src/verblets/number/index.spec.js +0 -33
- package/src/verblets/number-with-units/index.examples.js +0 -38
- package/src/verblets/number-with-units/index.js +0 -84
- package/src/verblets/number-with-units/index.spec.js +0 -46
- package/src/verblets/number-with-units/number-with-units-result.json +0 -23
- package/src/verblets/people-list/README.md +0 -28
- package/src/verblets/people-list/index.examples.js +0 -184
- package/src/verblets/people-list/index.js +0 -44
- package/src/verblets/people-list/index.spec.js +0 -49
- package/src/verblets/schema-org/index.examples.js +0 -51
- package/src/verblets/schema-org/index.js +0 -37
- package/src/verblets/schema-org/index.spec.js +0 -39
- package/src/verblets/sentiment/README.md +0 -10
- package/src/verblets/sentiment/index.examples.js +0 -20
- package/src/verblets/sentiment/index.js +0 -9
- package/src/verblets/sentiment/index.spec.js +0 -20
- package/src/verblets/to-object/README.md +0 -38
- package/src/verblets/to-object/index.examples.js +0 -29
- package/src/verblets/to-object/index.js +0 -131
- package/src/verblets/to-object/index.spec.js +0 -71
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import retry from './index.js';
|
|
4
|
-
|
|
5
|
-
const retryDelayGlobal = 10;
|
|
6
|
-
|
|
7
|
-
const mockFn = () => {
|
|
8
|
-
return 'Success';
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
describe('Retry', () => {
|
|
12
|
-
it('Succeeds on first attempt', async () => {
|
|
13
|
-
const result = await retry(mockFn, { retryDelay: retryDelayGlobal });
|
|
14
|
-
expect(result).toStrictEqual('Success');
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it('Succeeds after retrying', async () => {
|
|
18
|
-
let callCount = 0;
|
|
19
|
-
const fn = async () => {
|
|
20
|
-
callCount += 1;
|
|
21
|
-
if (callCount === 1) {
|
|
22
|
-
const error = new Error('Error 429');
|
|
23
|
-
error.response = { status: 429 };
|
|
24
|
-
throw error;
|
|
25
|
-
}
|
|
26
|
-
return 'Success';
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const result = await retry(fn, { retryDelay: retryDelayGlobal });
|
|
30
|
-
expect(result).toStrictEqual('Success');
|
|
31
|
-
expect(callCount).toStrictEqual(2);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it('Fails after maxRetries', async () => {
|
|
35
|
-
const maxRetries = 2;
|
|
36
|
-
let callCount = 0;
|
|
37
|
-
|
|
38
|
-
const fn = async () => {
|
|
39
|
-
callCount += 1;
|
|
40
|
-
const error = new Error('Error 429');
|
|
41
|
-
error.response = { status: 429 };
|
|
42
|
-
throw error;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
try {
|
|
46
|
-
await retry(fn, { maxRetries, retryDelay: retryDelayGlobal });
|
|
47
|
-
} catch (error) {
|
|
48
|
-
expect(error.message).toStrictEqual('Error 429');
|
|
49
|
-
expect(callCount).toStrictEqual(maxRetries + 1);
|
|
50
|
-
}
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('Throws non-retryable error', async () => {
|
|
54
|
-
const mockFnWithOtherError = () => {
|
|
55
|
-
throw new Error('Error 500');
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
try {
|
|
59
|
-
await retry(mockFnWithOtherError, { retryDelay: retryDelayGlobal });
|
|
60
|
-
} catch (error) {
|
|
61
|
-
expect(error.message).toStrictEqual('Error 500');
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it('Retries on all errors when retryOnAll is true', async () => {
|
|
66
|
-
let callCount = 0;
|
|
67
|
-
const maxRetries = 2;
|
|
68
|
-
const fn = async () => {
|
|
69
|
-
callCount += 1;
|
|
70
|
-
const error = new Error('Error 500');
|
|
71
|
-
error.response = { status: 500 };
|
|
72
|
-
throw error;
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
try {
|
|
76
|
-
await retry(fn, {
|
|
77
|
-
maxRetries,
|
|
78
|
-
retryDelay: retryDelayGlobal,
|
|
79
|
-
retryOnAll: true,
|
|
80
|
-
});
|
|
81
|
-
} catch (error) {
|
|
82
|
-
expect(error.message).toStrictEqual('Error 500');
|
|
83
|
-
expect(callCount).toStrictEqual(maxRetries + 1);
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
});
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
# High-Performance Ring Buffer
|
|
2
|
-
|
|
3
|
-
A high-performance ring buffer for single writer, multiple async readers with blocking semantics and offset tracking for coordination.
|
|
4
|
-
|
|
5
|
-
## Key Features
|
|
6
|
-
|
|
7
|
-
- **Double Buffering**: Eliminates wraparound logic for efficient batch reads
|
|
8
|
-
- **Blocking Reads**: Readers block until data is available
|
|
9
|
-
- **Multiple Independent Readers**: Each reader maintains its own position
|
|
10
|
-
- **Offset Tracking**: Returns offset information for external coordination
|
|
11
|
-
- **Zero-Copy Slicing**: Efficient batch operations using array slicing
|
|
12
|
-
|
|
13
|
-
## Example Usage
|
|
14
|
-
|
|
15
|
-
```javascript
|
|
16
|
-
import RingBuffer from './index.js';
|
|
17
|
-
|
|
18
|
-
const buffer = new RingBuffer(100);
|
|
19
|
-
|
|
20
|
-
// Register readers
|
|
21
|
-
const reader1 = buffer.registerReader();
|
|
22
|
-
const reader2 = buffer.registerReader();
|
|
23
|
-
|
|
24
|
-
// Write data
|
|
25
|
-
buffer.write('message1');
|
|
26
|
-
buffer.write('message2');
|
|
27
|
-
buffer.write('message3');
|
|
28
|
-
|
|
29
|
-
// Read single items with offset tracking
|
|
30
|
-
const result1 = await buffer.read(reader1);
|
|
31
|
-
// { data: 'message1', offset: 0 }
|
|
32
|
-
|
|
33
|
-
// Read batches with offset tracking
|
|
34
|
-
const batch = await buffer.readBatch(reader2, 2);
|
|
35
|
-
// { data: ['message1', 'message2'], startOffset: 0, lastOffset: 1 }
|
|
36
|
-
|
|
37
|
-
// Coordinate multiple readers using offsets
|
|
38
|
-
const processedOffsets = new Map();
|
|
39
|
-
processedOffsets.set(reader1, result1.offset);
|
|
40
|
-
processedOffsets.set(reader2, batch.lastOffset);
|
|
41
|
-
|
|
42
|
-
const minOffset = Math.min(...processedOffsets.values());
|
|
43
|
-
console.log(`Safe to cleanup data up to offset ${minOffset}`);
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
## API
|
|
47
|
-
|
|
48
|
-
### Constructor
|
|
49
|
-
```javascript
|
|
50
|
-
const buffer = new RingBuffer(maxSize);
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
### Reader Management
|
|
54
|
-
```javascript
|
|
55
|
-
const readerId = buffer.registerReader();
|
|
56
|
-
buffer.unregisterReader(readerId);
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
### Writing
|
|
60
|
-
```javascript
|
|
61
|
-
const sequence = buffer.write(data);
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
### Reading
|
|
65
|
-
```javascript
|
|
66
|
-
// Single item - blocks until available
|
|
67
|
-
const result = await buffer.read(readerId);
|
|
68
|
-
// Returns: { data: any, offset: number }
|
|
69
|
-
|
|
70
|
-
// Batch - blocks until full batch available
|
|
71
|
-
const batch = await buffer.readBatch(readerId, batchSize);
|
|
72
|
-
// Returns: { data: any[], startOffset: number, lastOffset: number }
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
### Utilities
|
|
76
|
-
```javascript
|
|
77
|
-
const stats = buffer.getStats();
|
|
78
|
-
console.log(`Buffer usage: ${stats.sequence} items written`);
|
|
79
|
-
console.log(`Active readers: ${stats.registeredReaders}`);
|
|
80
|
-
console.log(`Waiting readers: ${stats.waitingReaders}`);
|
|
81
|
-
```
|
|
82
|
-
|
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* High-performance ring buffer with single writer, multiple async readers.
|
|
3
|
-
* Uses double-buffer technique to eliminate wraparound logic.
|
|
4
|
-
* Optimized for performance over safety - assumes buffer is sized appropriately.
|
|
5
|
-
*/
|
|
6
|
-
export default class RingBuffer {
|
|
7
|
-
constructor(maxSize = 1000) {
|
|
8
|
-
this.maxSize = maxSize;
|
|
9
|
-
this.writeIndex = 0;
|
|
10
|
-
this.sequence = 0;
|
|
11
|
-
this.nextReaderId = 0;
|
|
12
|
-
|
|
13
|
-
// Double buffer: second half mirrors first half for wraparound-free reads
|
|
14
|
-
this.buffer = new Array(maxSize * 2);
|
|
15
|
-
|
|
16
|
-
// Reader tracking - Map for O(1) lookups
|
|
17
|
-
this.readers = new Map(); // readerId -> lastReadSequence
|
|
18
|
-
|
|
19
|
-
// Notification system for blocking reads
|
|
20
|
-
this.waitingReaders = new Set(); // Set of { resolve, readerId, batchSize? }
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Register a new reader.
|
|
25
|
-
* @returns {string} Reader ID
|
|
26
|
-
*/
|
|
27
|
-
registerReader() {
|
|
28
|
-
const readerId = `r${this.nextReaderId++}`;
|
|
29
|
-
this.readers.set(readerId, -1);
|
|
30
|
-
return readerId;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Unregister a reader.
|
|
35
|
-
* @param {string} readerId
|
|
36
|
-
*/
|
|
37
|
-
unregisterReader(readerId) {
|
|
38
|
-
this.readers.delete(readerId);
|
|
39
|
-
// Remove any waiting operations for this reader
|
|
40
|
-
for (const waiter of this.waitingReaders) {
|
|
41
|
-
if (waiter.readerId === readerId) {
|
|
42
|
-
if (waiter.batchSize) {
|
|
43
|
-
waiter.resolve({ data: [], startOffset: 0, lastOffset: -1 });
|
|
44
|
-
} else {
|
|
45
|
-
waiter.resolve({ data: null, offset: -1 });
|
|
46
|
-
}
|
|
47
|
-
this.waitingReaders.delete(waiter);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Write data to buffer (single writer only).
|
|
54
|
-
* @param {any} data
|
|
55
|
-
* @returns {number} Sequence number
|
|
56
|
-
*/
|
|
57
|
-
write(data) {
|
|
58
|
-
const seq = this.sequence++;
|
|
59
|
-
const idx = this.writeIndex;
|
|
60
|
-
|
|
61
|
-
// Write to both positions (main and mirror)
|
|
62
|
-
this.buffer[idx] = data;
|
|
63
|
-
this.buffer[idx + this.maxSize] = data;
|
|
64
|
-
|
|
65
|
-
this.writeIndex = (this.writeIndex + 1) % this.maxSize;
|
|
66
|
-
|
|
67
|
-
// Notify waiting readers
|
|
68
|
-
this._notifyWaiters(seq);
|
|
69
|
-
|
|
70
|
-
return seq;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Read latest data for a reader (blocks until available).
|
|
75
|
-
* @param {string} readerId
|
|
76
|
-
* @returns {Promise<{data: any, offset: number}>}
|
|
77
|
-
*/
|
|
78
|
-
read(readerId) {
|
|
79
|
-
const lastSeq = this.readers.get(readerId);
|
|
80
|
-
if (lastSeq === undefined) {
|
|
81
|
-
throw new Error(`Reader ${readerId} not registered`);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Check if new data is available
|
|
85
|
-
if (this.sequence > lastSeq + 1) {
|
|
86
|
-
const nextSeq = lastSeq + 1;
|
|
87
|
-
const data = this._getDataAtSequence(nextSeq);
|
|
88
|
-
this.readers.set(readerId, nextSeq);
|
|
89
|
-
return Promise.resolve({ data, offset: nextSeq });
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// No data available, wait for it
|
|
93
|
-
return new Promise((resolve) => {
|
|
94
|
-
this.waitingReaders.add({ resolve, readerId });
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Read batch of data (blocks until batch is full).
|
|
100
|
-
* @param {string} readerId
|
|
101
|
-
* @param {number} batchSize
|
|
102
|
-
* @returns {Promise<{data: any[], startOffset: number, lastOffset: number}>}
|
|
103
|
-
*/
|
|
104
|
-
readBatch(readerId, batchSize) {
|
|
105
|
-
const lastSeq = this.readers.get(readerId);
|
|
106
|
-
if (lastSeq === undefined) {
|
|
107
|
-
throw new Error(`Reader ${readerId} not registered`);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const availableCount = this.sequence - (lastSeq + 1);
|
|
111
|
-
|
|
112
|
-
if (availableCount >= batchSize) {
|
|
113
|
-
// Batch is ready
|
|
114
|
-
const result = this._readBatchSync(readerId, batchSize);
|
|
115
|
-
return Promise.resolve(result);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Wait for full batch
|
|
119
|
-
return new Promise((resolve) => {
|
|
120
|
-
this.waitingReaders.add({ resolve, readerId, batchSize });
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Get data at specific sequence (uses double buffer for wraparound-free access).
|
|
126
|
-
* @private
|
|
127
|
-
*/
|
|
128
|
-
_getDataAtSequence(seq) {
|
|
129
|
-
const bufferPos = seq % this.maxSize;
|
|
130
|
-
return this.buffer[bufferPos];
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Read batch synchronously when data is available.
|
|
135
|
-
* @private
|
|
136
|
-
*/
|
|
137
|
-
_readBatchSync(readerId, batchSize) {
|
|
138
|
-
const lastSeq = this.readers.get(readerId);
|
|
139
|
-
const startSeq = lastSeq + 1;
|
|
140
|
-
const startPos = startSeq % this.maxSize;
|
|
141
|
-
|
|
142
|
-
// Use double buffer to avoid wraparound logic
|
|
143
|
-
const batch = this.buffer.slice(startPos, startPos + batchSize);
|
|
144
|
-
|
|
145
|
-
this.readers.set(readerId, lastSeq + batchSize);
|
|
146
|
-
return { data: batch, startOffset: startSeq, lastOffset: lastSeq + batchSize };
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Notify waiting readers when new data arrives.
|
|
151
|
-
* @private
|
|
152
|
-
*/
|
|
153
|
-
_notifyWaiters(newSeq) {
|
|
154
|
-
const toRemove = [];
|
|
155
|
-
|
|
156
|
-
for (const waiter of this.waitingReaders) {
|
|
157
|
-
const { resolve, readerId, batchSize } = waiter;
|
|
158
|
-
const lastSeq = this.readers.get(readerId);
|
|
159
|
-
|
|
160
|
-
if (lastSeq === undefined) continue; // Reader was unregistered
|
|
161
|
-
|
|
162
|
-
if (batchSize) {
|
|
163
|
-
// Batch reader
|
|
164
|
-
const availableCount = newSeq - lastSeq;
|
|
165
|
-
if (availableCount >= batchSize) {
|
|
166
|
-
const result = this._readBatchSync(readerId, batchSize);
|
|
167
|
-
resolve(result);
|
|
168
|
-
toRemove.push(waiter);
|
|
169
|
-
}
|
|
170
|
-
} else {
|
|
171
|
-
// Single reader
|
|
172
|
-
if (newSeq > lastSeq) {
|
|
173
|
-
const nextSeq = lastSeq + 1;
|
|
174
|
-
const data = this._getDataAtSequence(nextSeq);
|
|
175
|
-
this.readers.set(readerId, nextSeq);
|
|
176
|
-
resolve({ data, offset: nextSeq });
|
|
177
|
-
toRemove.push(waiter);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// Remove resolved waiters
|
|
183
|
-
for (const waiter of toRemove) {
|
|
184
|
-
this.waitingReaders.delete(waiter);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Get available data count for a reader.
|
|
190
|
-
* @param {string} readerId
|
|
191
|
-
* @returns {number}
|
|
192
|
-
*/
|
|
193
|
-
getAvailableCount(readerId) {
|
|
194
|
-
const lastSeq = this.readers.get(readerId);
|
|
195
|
-
return lastSeq === undefined ? 0 : Math.max(0, this.sequence - (lastSeq + 1));
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Get buffer statistics.
|
|
200
|
-
* @returns {object}
|
|
201
|
-
*/
|
|
202
|
-
getStats() {
|
|
203
|
-
return {
|
|
204
|
-
maxSize: this.maxSize,
|
|
205
|
-
writeIndex: this.writeIndex,
|
|
206
|
-
sequence: this.sequence,
|
|
207
|
-
registeredReaders: this.readers.size,
|
|
208
|
-
waitingReaders: this.waitingReaders.size,
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Clear buffer and reset state.
|
|
214
|
-
*/
|
|
215
|
-
clear() {
|
|
216
|
-
this.writeIndex = 0;
|
|
217
|
-
this.sequence = 0;
|
|
218
|
-
this.buffer.fill(undefined);
|
|
219
|
-
|
|
220
|
-
// Reset reader sequences
|
|
221
|
-
for (const readerId of this.readers.keys()) {
|
|
222
|
-
this.readers.set(readerId, -1);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// Resolve all waiting readers with empty results
|
|
226
|
-
for (const waiter of this.waitingReaders) {
|
|
227
|
-
if (waiter.batchSize) {
|
|
228
|
-
waiter.resolve({ data: [], startOffset: 0, lastOffset: -1 });
|
|
229
|
-
} else {
|
|
230
|
-
waiter.resolve({ data: null, offset: -1 });
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
this.waitingReaders.clear();
|
|
234
|
-
}
|
|
235
|
-
}
|