actly 1.1.5 → 1.3.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/CHANGELOG.md +234 -0
- package/MIGRATION.md +141 -0
- package/README.md +334 -428
- package/SECURITY.md +56 -0
- package/dist/core/act.cjs +630 -126
- package/dist/core/act.d.ts +6 -84
- package/dist/core/act.d.ts.map +1 -1
- package/dist/core/act.js +630 -129
- package/dist/core/executor.cjs +1 -26
- package/dist/core/executor.d.ts +2 -38
- package/dist/core/executor.d.ts.map +1 -1
- package/dist/core/executor.js +1 -27
- package/dist/core/health.cjs +163 -0
- package/dist/core/health.d.ts +33 -0
- package/dist/core/health.d.ts.map +1 -0
- package/dist/core/health.js +150 -0
- package/dist/core/shutdown.cjs +71 -0
- package/dist/core/shutdown.d.ts +5 -0
- package/dist/core/shutdown.d.ts.map +1 -0
- package/dist/core/shutdown.js +65 -0
- package/dist/core/tenant.cjs +113 -0
- package/dist/core/tenant.d.ts +24 -0
- package/dist/core/tenant.d.ts.map +1 -0
- package/dist/core/tenant.js +109 -0
- package/dist/errors.cjs +149 -0
- package/dist/errors.d.ts +94 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +134 -0
- package/dist/index.cjs +51 -8
- package/dist/index.d.ts +22 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -6
- package/dist/observability.cjs +6 -0
- package/dist/observability.d.ts +86 -0
- package/dist/observability.d.ts.map +1 -0
- package/dist/observability.js +3 -0
- package/dist/policies/bulkhead.cjs +137 -0
- package/dist/policies/bulkhead.d.ts +3 -0
- package/dist/policies/bulkhead.d.ts.map +1 -0
- package/dist/policies/bulkhead.js +134 -0
- package/dist/policies/cache.cjs +84 -67
- package/dist/policies/cache.d.ts +0 -33
- package/dist/policies/cache.d.ts.map +1 -1
- package/dist/policies/cache.js +84 -68
- package/dist/policies/circuitBreaker.cjs +179 -0
- package/dist/policies/circuitBreaker.d.ts +3 -0
- package/dist/policies/circuitBreaker.d.ts.map +1 -0
- package/dist/policies/circuitBreaker.js +176 -0
- package/dist/policies/dedupe.cjs +46 -64
- package/dist/policies/dedupe.d.ts +0 -41
- package/dist/policies/dedupe.d.ts.map +1 -1
- package/dist/policies/dedupe.js +46 -65
- package/dist/policies/noop.cjs +6 -0
- package/dist/policies/noop.d.ts +3 -0
- package/dist/policies/noop.d.ts.map +1 -0
- package/dist/policies/noop.js +3 -0
- package/dist/policies/rateLimit.cjs +45 -0
- package/dist/policies/rateLimit.d.ts +3 -0
- package/dist/policies/rateLimit.d.ts.map +1 -0
- package/dist/policies/rateLimit.js +42 -0
- package/dist/policies/retry.cjs +129 -57
- package/dist/policies/retry.d.ts +0 -27
- package/dist/policies/retry.d.ts.map +1 -1
- package/dist/policies/retry.js +129 -58
- package/dist/policies/timeout.cjs +47 -103
- package/dist/policies/timeout.d.ts +2 -46
- package/dist/policies/timeout.d.ts.map +1 -1
- package/dist/policies/timeout.js +46 -102
- package/dist/stores/base.cjs +0 -10
- package/dist/stores/base.d.ts +4 -67
- package/dist/stores/base.d.ts.map +1 -1
- package/dist/stores/base.js +0 -11
- package/dist/stores/memory.cjs +147 -87
- package/dist/stores/memory.d.ts +11 -59
- package/dist/stores/memory.d.ts.map +1 -1
- package/dist/stores/memory.js +146 -88
- package/dist/testing/index.cjs +56 -0
- package/dist/testing/index.d.ts +6 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +52 -0
- package/dist/types/index.cjs +0 -1
- package/dist/types/index.d.ts +62 -179
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +0 -2
- package/dist/utils/abort.cjs +46 -73
- package/dist/utils/abort.d.ts +4 -51
- package/dist/utils/abort.d.ts.map +1 -1
- package/dist/utils/abort.js +46 -74
- package/dist/utils/abortPool.cjs +25 -0
- package/dist/utils/abortPool.d.ts +4 -0
- package/dist/utils/abortPool.d.ts.map +1 -0
- package/dist/utils/abortPool.js +20 -0
- package/dist/utils/backoff.cjs +8 -15
- package/dist/utils/backoff.d.ts +0 -11
- package/dist/utils/backoff.d.ts.map +1 -1
- package/dist/utils/backoff.js +8 -16
- package/dist/utils/decorator.cjs +44 -0
- package/dist/utils/decorator.d.ts +3 -0
- package/dist/utils/decorator.d.ts.map +1 -0
- package/dist/utils/decorator.js +41 -0
- package/dist/utils/key.cjs +45 -0
- package/dist/utils/key.d.ts +2 -0
- package/dist/utils/key.d.ts.map +1 -0
- package/dist/utils/key.js +42 -0
- package/dist/utils/limits.cjs +22 -0
- package/dist/utils/limits.d.ts +21 -0
- package/dist/utils/limits.d.ts.map +1 -0
- package/dist/utils/limits.js +19 -0
- package/dist/utils/safeCall.cjs +28 -0
- package/dist/utils/safeCall.d.ts +2 -0
- package/dist/utils/safeCall.d.ts.map +1 -0
- package/dist/utils/safeCall.js +25 -0
- package/dist/utils/sanitize.cjs +50 -0
- package/dist/utils/sanitize.d.ts +3 -0
- package/dist/utils/sanitize.d.ts.map +1 -0
- package/dist/utils/sanitize.js +46 -0
- package/dist/utils/validate.cjs +163 -26
- package/dist/utils/validate.d.ts +6 -8
- package/dist/utils/validate.d.ts.map +1 -1
- package/dist/utils/validate.js +158 -27
- package/package.json +42 -7
- package/dist/core/act.js.map +0 -1
- package/dist/core/executor.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/policies/cache.js.map +0 -1
- package/dist/policies/dedupe.js.map +0 -1
- package/dist/policies/retry.js.map +0 -1
- package/dist/policies/timeout.js.map +0 -1
- package/dist/state/store.cjs +0 -14
- package/dist/state/store.d.ts +0 -11
- package/dist/state/store.d.ts.map +0 -1
- package/dist/state/store.js +0 -11
- package/dist/state/store.js.map +0 -1
- package/dist/stores/base.js.map +0 -1
- package/dist/stores/memory.js.map +0 -1
- package/dist/types/index.js.map +0 -1
- package/dist/utils/abort.js.map +0 -1
- package/dist/utils/backoff.js.map +0 -1
- package/dist/utils/validate.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,22 +1,65 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// ─── Primary API ──────────────────────────────────────────────────────────────
|
|
3
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.TotalTimeoutError = exports.TimeoutError = exports.isAsyncStore = exports.isSyncStore = exports.InMemoryStore = exports.REQUIRES_SYNC_STORE = exports.execute = exports.withStore = exports.invalidate = exports.act = void 0;
|
|
3
|
+
exports.usePolicy = exports.noopPolicy = exports.poolSize = exports.releaseController = exports.acquireController = exports.sanitizeError = exports.sanitizeErrorMessage = exports.createAsyncTenantStore = exports.createTenantStore = exports.drainAll = exports.drain = exports.disableWatchdog = exports.unregisterWatchdogHooks = exports.registerWatchdogHooks = exports.enableWatchdog = exports.createHealthCheck = exports.ResourceExhaustedError = exports.RateLimitError = exports.BulkheadOverflowError = exports.CircuitBreakerOpenError = exports.LIMITS = exports.computeDelay = exports.sanitizeKey = exports.isAbortError = exports.linkSignal = exports.sleep = exports.raceAbort = exports.anySignal = exports.isActlyError = exports.ValidationError = exports.RetryExhaustedError = exports.TotalTimeoutError = exports.TimeoutError = exports.ActlyAbortError = exports.ActlyError = exports.isAsyncStore = exports.isSyncStore = exports.InMemoryStore = exports.REQUIRES_SYNC_STORE = exports.execute = exports.HedgeTimeoutError = exports.withStore = exports.invalidate = exports.act = void 0;
|
|
5
4
|
var act_js_1 = require("./core/act.js");
|
|
6
5
|
Object.defineProperty(exports, "act", { enumerable: true, get: function () { return act_js_1.act; } });
|
|
7
6
|
Object.defineProperty(exports, "invalidate", { enumerable: true, get: function () { return act_js_1.invalidate; } });
|
|
8
7
|
Object.defineProperty(exports, "withStore", { enumerable: true, get: function () { return act_js_1.withStore; } });
|
|
9
|
-
|
|
8
|
+
var errors_js_1 = require("./errors.js");
|
|
9
|
+
Object.defineProperty(exports, "HedgeTimeoutError", { enumerable: true, get: function () { return errors_js_1.HedgeTimeoutError; } });
|
|
10
10
|
var executor_js_1 = require("./core/executor.js");
|
|
11
11
|
Object.defineProperty(exports, "execute", { enumerable: true, get: function () { return executor_js_1.execute; } });
|
|
12
12
|
Object.defineProperty(exports, "REQUIRES_SYNC_STORE", { enumerable: true, get: function () { return executor_js_1.REQUIRES_SYNC_STORE; } });
|
|
13
|
-
// ─── Stores ───────────────────────────────────────────────────────────────────
|
|
14
13
|
var memory_js_1 = require("./stores/memory.js");
|
|
15
14
|
Object.defineProperty(exports, "InMemoryStore", { enumerable: true, get: function () { return memory_js_1.InMemoryStore; } });
|
|
16
15
|
var base_js_1 = require("./stores/base.js");
|
|
17
16
|
Object.defineProperty(exports, "isSyncStore", { enumerable: true, get: function () { return base_js_1.isSyncStore; } });
|
|
18
17
|
Object.defineProperty(exports, "isAsyncStore", { enumerable: true, get: function () { return base_js_1.isAsyncStore; } });
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
Object.defineProperty(exports, "
|
|
22
|
-
Object.defineProperty(exports, "
|
|
18
|
+
var errors_js_2 = require("./errors.js");
|
|
19
|
+
Object.defineProperty(exports, "ActlyError", { enumerable: true, get: function () { return errors_js_2.ActlyError; } });
|
|
20
|
+
Object.defineProperty(exports, "ActlyAbortError", { enumerable: true, get: function () { return errors_js_2.ActlyAbortError; } });
|
|
21
|
+
Object.defineProperty(exports, "TimeoutError", { enumerable: true, get: function () { return errors_js_2.TimeoutError; } });
|
|
22
|
+
Object.defineProperty(exports, "TotalTimeoutError", { enumerable: true, get: function () { return errors_js_2.TotalTimeoutError; } });
|
|
23
|
+
Object.defineProperty(exports, "RetryExhaustedError", { enumerable: true, get: function () { return errors_js_2.RetryExhaustedError; } });
|
|
24
|
+
Object.defineProperty(exports, "ValidationError", { enumerable: true, get: function () { return errors_js_2.ValidationError; } });
|
|
25
|
+
Object.defineProperty(exports, "isActlyError", { enumerable: true, get: function () { return errors_js_2.isActlyError; } });
|
|
26
|
+
var abort_js_1 = require("./utils/abort.js");
|
|
27
|
+
Object.defineProperty(exports, "anySignal", { enumerable: true, get: function () { return abort_js_1.anySignal; } });
|
|
28
|
+
Object.defineProperty(exports, "raceAbort", { enumerable: true, get: function () { return abort_js_1.raceAbort; } });
|
|
29
|
+
Object.defineProperty(exports, "sleep", { enumerable: true, get: function () { return abort_js_1.sleep; } });
|
|
30
|
+
Object.defineProperty(exports, "linkSignal", { enumerable: true, get: function () { return abort_js_1.linkSignal; } });
|
|
31
|
+
Object.defineProperty(exports, "isAbortError", { enumerable: true, get: function () { return abort_js_1.isAbortError; } });
|
|
32
|
+
var key_js_1 = require("./utils/key.js");
|
|
33
|
+
Object.defineProperty(exports, "sanitizeKey", { enumerable: true, get: function () { return key_js_1.sanitizeKey; } });
|
|
34
|
+
var backoff_js_1 = require("./utils/backoff.js");
|
|
35
|
+
Object.defineProperty(exports, "computeDelay", { enumerable: true, get: function () { return backoff_js_1.computeDelay; } });
|
|
36
|
+
var limits_js_1 = require("./utils/limits.js");
|
|
37
|
+
Object.defineProperty(exports, "LIMITS", { enumerable: true, get: function () { return limits_js_1.LIMITS; } });
|
|
38
|
+
var errors_js_3 = require("./errors.js");
|
|
39
|
+
Object.defineProperty(exports, "CircuitBreakerOpenError", { enumerable: true, get: function () { return errors_js_3.CircuitBreakerOpenError; } });
|
|
40
|
+
Object.defineProperty(exports, "BulkheadOverflowError", { enumerable: true, get: function () { return errors_js_3.BulkheadOverflowError; } });
|
|
41
|
+
Object.defineProperty(exports, "RateLimitError", { enumerable: true, get: function () { return errors_js_3.RateLimitError; } });
|
|
42
|
+
Object.defineProperty(exports, "ResourceExhaustedError", { enumerable: true, get: function () { return errors_js_3.ResourceExhaustedError; } });
|
|
43
|
+
var health_js_1 = require("./core/health.js");
|
|
44
|
+
Object.defineProperty(exports, "createHealthCheck", { enumerable: true, get: function () { return health_js_1.createHealthCheck; } });
|
|
45
|
+
Object.defineProperty(exports, "enableWatchdog", { enumerable: true, get: function () { return health_js_1.enableWatchdog; } });
|
|
46
|
+
Object.defineProperty(exports, "registerWatchdogHooks", { enumerable: true, get: function () { return health_js_1.registerWatchdogHooks; } });
|
|
47
|
+
Object.defineProperty(exports, "unregisterWatchdogHooks", { enumerable: true, get: function () { return health_js_1.unregisterWatchdogHooks; } });
|
|
48
|
+
Object.defineProperty(exports, "disableWatchdog", { enumerable: true, get: function () { return health_js_1.disableWatchdog; } });
|
|
49
|
+
var shutdown_js_1 = require("./core/shutdown.js");
|
|
50
|
+
Object.defineProperty(exports, "drain", { enumerable: true, get: function () { return shutdown_js_1.drain; } });
|
|
51
|
+
Object.defineProperty(exports, "drainAll", { enumerable: true, get: function () { return shutdown_js_1.drainAll; } });
|
|
52
|
+
var tenant_js_1 = require("./core/tenant.js");
|
|
53
|
+
Object.defineProperty(exports, "createTenantStore", { enumerable: true, get: function () { return tenant_js_1.createTenantStore; } });
|
|
54
|
+
Object.defineProperty(exports, "createAsyncTenantStore", { enumerable: true, get: function () { return tenant_js_1.createAsyncTenantStore; } });
|
|
55
|
+
var sanitize_js_1 = require("./utils/sanitize.js");
|
|
56
|
+
Object.defineProperty(exports, "sanitizeErrorMessage", { enumerable: true, get: function () { return sanitize_js_1.sanitizeErrorMessage; } });
|
|
57
|
+
Object.defineProperty(exports, "sanitizeError", { enumerable: true, get: function () { return sanitize_js_1.sanitizeError; } });
|
|
58
|
+
var abortPool_js_1 = require("./utils/abortPool.js");
|
|
59
|
+
Object.defineProperty(exports, "acquireController", { enumerable: true, get: function () { return abortPool_js_1.acquireController; } });
|
|
60
|
+
Object.defineProperty(exports, "releaseController", { enumerable: true, get: function () { return abortPool_js_1.releaseController; } });
|
|
61
|
+
Object.defineProperty(exports, "poolSize", { enumerable: true, get: function () { return abortPool_js_1.poolSize; } });
|
|
62
|
+
var noop_js_1 = require("./policies/noop.js");
|
|
63
|
+
Object.defineProperty(exports, "noopPolicy", { enumerable: true, get: function () { return noop_js_1.noopPolicy; } });
|
|
64
|
+
var decorator_js_1 = require("./utils/decorator.js");
|
|
65
|
+
Object.defineProperty(exports, "usePolicy", { enumerable: true, get: function () { return decorator_js_1.usePolicy; } });
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,29 @@
|
|
|
1
1
|
export { act, invalidate, withStore, } from './core/act.js';
|
|
2
2
|
export type { ScopedActSync, ScopedActAsync, } from './core/act.js';
|
|
3
|
+
export { HedgeTimeoutError, } from './errors.js';
|
|
3
4
|
export { execute, REQUIRES_SYNC_STORE, } from './core/executor.js';
|
|
5
|
+
export type { ExecutorInput, } from './core/executor.js';
|
|
6
|
+
export type { ObservabilityContext, } from './observability.js';
|
|
4
7
|
export { InMemoryStore } from './stores/memory.js';
|
|
5
8
|
export type { InMemoryStoreOptions } from './stores/memory.js';
|
|
6
9
|
export { isSyncStore, isAsyncStore, } from './stores/base.js';
|
|
7
|
-
export { TimeoutError, TotalTimeoutError, } from './
|
|
8
|
-
export type {
|
|
9
|
-
SyncStateStore, AsyncStateStore, AnyStateStore, } from './types/index.js';
|
|
10
|
+
export { ActlyError, ActlyAbortError, TimeoutError, TotalTimeoutError, RetryExhaustedError, ValidationError, isActlyError, } from './errors.js';
|
|
11
|
+
export type { ActlyEventType, ActlyEventBase, AttemptEvent, RetryEvent, CacheHitEvent, CacheMissEvent, DedupeJoinEvent, TimeoutEvent, FinalSuccessEvent, FinalFailureEvent, BackpressureEvent, WatchdogEvent, ActlyEvent, ObservabilityHooks, } from './observability.js';
|
|
12
|
+
export type { ActFn, ActResult, ActSuccess, ActFailure, ActSource, ActOptions, RetryOptions, TimeoutOptions, DedupeOptions, CacheOptions, PolicyApplier, PolicyContext, RunMeta, StateStore, SyncStateStore, AsyncStateStore, AnyStateStore, } from './types/index.js';
|
|
13
|
+
export { anySignal, raceAbort, sleep, linkSignal, isAbortError, } from './utils/abort.js';
|
|
14
|
+
export { sanitizeKey, } from './utils/key.js';
|
|
15
|
+
export { computeDelay, } from './utils/backoff.js';
|
|
16
|
+
export { LIMITS, } from './utils/limits.js';
|
|
17
|
+
export type { Limits } from './utils/limits.js';
|
|
18
|
+
export { CircuitBreakerOpenError, BulkheadOverflowError, RateLimitError, ResourceExhaustedError, } from './errors.js';
|
|
19
|
+
export { createHealthCheck, enableWatchdog, registerWatchdogHooks, unregisterWatchdogHooks, disableWatchdog, } from './core/health.js';
|
|
20
|
+
export type { HealthStatus, HealthCheckFn } from './core/health.js';
|
|
21
|
+
export { drain, drainAll, } from './core/shutdown.js';
|
|
22
|
+
export { createTenantStore, createAsyncTenantStore, } from './core/tenant.js';
|
|
23
|
+
export type { TenantStoreOptions, TenantManager } from './core/tenant.js';
|
|
24
|
+
export { sanitizeErrorMessage, sanitizeError, } from './utils/sanitize.js';
|
|
25
|
+
export { acquireController, releaseController, poolSize, } from './utils/abortPool.js';
|
|
26
|
+
export type { CircuitBreakerOptions, BulkheadOptions, RateLimitOptions, HedgeOptions, FallbackOptions, AuditOptions, AuditEntry, } from './types/index.js';
|
|
27
|
+
export { noopPolicy, } from './policies/noop.js';
|
|
28
|
+
export { usePolicy, } from './utils/decorator.js';
|
|
10
29
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,GAAG,EACH,UAAU,EACV,SAAS,GACV,MAAM,eAAe,CAAA;AAEtB,YAAY,EACV,aAAa,EACb,cAAc,GACf,MAAM,eAAe,CAAA;AAItB,OAAO,EACL,OAAO,EACP,mBAAmB,GACpB,MAAM,oBAAoB,CAAA;AAI3B,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAClD,YAAY,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AAE9D,OAAO,EACL,WAAW,EACX,YAAY,GACb,MAAM,kBAAkB,CAAA;AAIzB,OAAO,EACL,YAAY,EACZ,iBAAiB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,GAAG,EACH,UAAU,EACV,SAAS,GACV,MAAM,eAAe,CAAA;AAEtB,YAAY,EACV,aAAa,EACb,cAAc,GACf,MAAM,eAAe,CAAA;AAItB,OAAO,EACL,iBAAiB,GAClB,MAAM,aAAa,CAAA;AAIpB,OAAO,EACL,OAAO,EACP,mBAAmB,GACpB,MAAM,oBAAoB,CAAA;AAI3B,YAAY,EACV,aAAa,GACd,MAAM,oBAAoB,CAAA;AAI3B,YAAY,EACV,oBAAoB,GACrB,MAAM,oBAAoB,CAAA;AAI3B,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAClD,YAAY,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AAE9D,OAAO,EACL,WAAW,EACX,YAAY,GACb,MAAM,kBAAkB,CAAA;AAIzB,OAAO,EACL,UAAU,EACV,eAAe,EACf,YAAY,EACZ,iBAAiB,EACjB,mBAAmB,EACnB,eAAe,EACf,YAAY,GACb,MAAM,aAAa,CAAA;AAIpB,YAAY,EACV,cAAc,EACd,cAAc,EACd,YAAY,EACZ,UAAU,EACV,aAAa,EACb,cAAc,EACd,eAAe,EACf,YAAY,EACZ,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EACjB,aAAa,EACb,UAAU,EACV,kBAAkB,GACnB,MAAM,oBAAoB,CAAA;AAI3B,YAAY,EAEV,KAAK,EACL,SAAS,EACT,UAAU,EACV,UAAU,EACV,SAAS,EACT,UAAU,EAGV,YAAY,EACZ,cAAc,EACd,aAAa,EACb,YAAY,EAGZ,aAAa,EACb,aAAa,EACb,OAAO,EAGP,UAAU,EACV,cAAc,EACd,eAAe,EACf,aAAa,GACd,MAAM,kBAAkB,CAAA;AAIzB,OAAO,EACL,SAAS,EACT,SAAS,EACT,KAAK,EACL,UAAU,EACV,YAAY,GACb,MAAM,kBAAkB,CAAA;AAEzB,OAAO,EACL,WAAW,GACZ,MAAM,gBAAgB,CAAA;AAEvB,OAAO,EACL,YAAY,GACb,MAAM,oBAAoB,CAAA;AAE3B,OAAO,EACL,MAAM,GACP,MAAM,mBAAmB,CAAA;AAE1B,YAAY,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAI/C,OAAO,EACL,uBAAuB,EACvB,qBAAqB,EACrB,cAAc,EACd,sBAAsB,GACvB,MAAM,aAAa,CAAA;AAGpB,OAAO,EACL,iBAAiB,EACjB,cAAc,EACd,qBAAqB,EACrB,uBAAuB,EACvB,eAAe,GAChB,MAAM,kBAAkB,CAAA;AACzB,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAEnE,OAAO,EACL,KAAK,EACL,QAAQ,GACT,MAAM,oBAAoB,CAAA;AAI3B,OAAO,EACL,iBAAiB,EACjB,sBAAsB,GACvB,MAAM,kBAAkB,CAAA;AACzB,YAAY,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAIzE,OAAO,EACL,oBAAoB,EACpB,aAAa,GACd,MAAM,qBAAqB,CAAA;AAI5B,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,QAAQ,GACT,MAAM,sBAAsB,CAAA;AAI7B,YAAY,EACV,qBAAqB,EACrB,eAAe,EACf,gBAAgB,EAChB,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,UAAU,GACX,MAAM,kBAAkB,CAAA;AAIzB,OAAO,EACL,UAAU,GACX,MAAM,oBAAoB,CAAA;AAI3B,OAAO,EACL,SAAS,GACV,MAAM,sBAAsB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
|
-
// ─── Primary API ──────────────────────────────────────────────────────────────
|
|
2
1
|
export { act, invalidate, withStore, } from './core/act.js';
|
|
3
|
-
|
|
2
|
+
export { HedgeTimeoutError, } from './errors.js';
|
|
4
3
|
export { execute, REQUIRES_SYNC_STORE, } from './core/executor.js';
|
|
5
|
-
// ─── Stores ───────────────────────────────────────────────────────────────────
|
|
6
4
|
export { InMemoryStore } from './stores/memory.js';
|
|
7
5
|
export { isSyncStore, isAsyncStore, } from './stores/base.js';
|
|
8
|
-
|
|
9
|
-
export {
|
|
10
|
-
|
|
6
|
+
export { ActlyError, ActlyAbortError, TimeoutError, TotalTimeoutError, RetryExhaustedError, ValidationError, isActlyError, } from './errors.js';
|
|
7
|
+
export { anySignal, raceAbort, sleep, linkSignal, isAbortError, } from './utils/abort.js';
|
|
8
|
+
export { sanitizeKey, } from './utils/key.js';
|
|
9
|
+
export { computeDelay, } from './utils/backoff.js';
|
|
10
|
+
export { LIMITS, } from './utils/limits.js';
|
|
11
|
+
export { CircuitBreakerOpenError, BulkheadOverflowError, RateLimitError, ResourceExhaustedError, } from './errors.js';
|
|
12
|
+
export { createHealthCheck, enableWatchdog, registerWatchdogHooks, unregisterWatchdogHooks, disableWatchdog, } from './core/health.js';
|
|
13
|
+
export { drain, drainAll, } from './core/shutdown.js';
|
|
14
|
+
export { createTenantStore, createAsyncTenantStore, } from './core/tenant.js';
|
|
15
|
+
export { sanitizeErrorMessage, sanitizeError, } from './utils/sanitize.js';
|
|
16
|
+
export { acquireController, releaseController, poolSize, } from './utils/abortPool.js';
|
|
17
|
+
export { noopPolicy, } from './policies/noop.js';
|
|
18
|
+
export { usePolicy, } from './utils/decorator.js';
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { ActSource, ActlyFailedBy } from './types/index.js';
|
|
2
|
+
export type ActlyEventType = 'attempt' | 'retry' | 'cache-hit' | 'cache-miss' | 'dedupe-join' | 'timeout' | 'final-success' | 'final-failure' | 'backpressure' | 'watchdog';
|
|
3
|
+
export interface ActlyEventBase {
|
|
4
|
+
readonly key: string;
|
|
5
|
+
readonly traceId: string;
|
|
6
|
+
readonly timestamp: number;
|
|
7
|
+
readonly type: ActlyEventType;
|
|
8
|
+
}
|
|
9
|
+
export interface AttemptEvent extends ActlyEventBase {
|
|
10
|
+
readonly type: 'attempt';
|
|
11
|
+
readonly attempt: number;
|
|
12
|
+
readonly durationMs?: number;
|
|
13
|
+
readonly error?: unknown;
|
|
14
|
+
}
|
|
15
|
+
export interface RetryEvent extends ActlyEventBase {
|
|
16
|
+
readonly type: 'retry';
|
|
17
|
+
readonly attempt: number;
|
|
18
|
+
readonly delayMs: number;
|
|
19
|
+
readonly error: unknown;
|
|
20
|
+
}
|
|
21
|
+
export interface CacheHitEvent extends ActlyEventBase {
|
|
22
|
+
readonly type: 'cache-hit';
|
|
23
|
+
readonly ageMs: number;
|
|
24
|
+
}
|
|
25
|
+
export interface CacheMissEvent extends ActlyEventBase {
|
|
26
|
+
readonly type: 'cache-miss';
|
|
27
|
+
}
|
|
28
|
+
export interface DedupeJoinEvent extends ActlyEventBase {
|
|
29
|
+
readonly type: 'dedupe-join';
|
|
30
|
+
readonly joinerPosition: number;
|
|
31
|
+
}
|
|
32
|
+
export interface TimeoutEvent extends ActlyEventBase {
|
|
33
|
+
readonly type: 'timeout';
|
|
34
|
+
readonly kind: 'per-attempt' | 'total';
|
|
35
|
+
readonly ms: number;
|
|
36
|
+
}
|
|
37
|
+
export interface FinalSuccessEvent extends ActlyEventBase {
|
|
38
|
+
readonly type: 'final-success';
|
|
39
|
+
readonly source: ActSource;
|
|
40
|
+
readonly attempts: number;
|
|
41
|
+
readonly durationMs: number;
|
|
42
|
+
}
|
|
43
|
+
export interface FinalFailureEvent extends ActlyEventBase {
|
|
44
|
+
readonly type: 'final-failure';
|
|
45
|
+
readonly attempts: number;
|
|
46
|
+
readonly durationMs: number;
|
|
47
|
+
readonly failedBy: ActlyFailedBy;
|
|
48
|
+
readonly error: unknown;
|
|
49
|
+
}
|
|
50
|
+
export type ActlyEvent = AttemptEvent | RetryEvent | CacheHitEvent | CacheMissEvent | DedupeJoinEvent | TimeoutEvent | FinalSuccessEvent | FinalFailureEvent | BackpressureEvent | WatchdogEvent;
|
|
51
|
+
export interface BackpressureEvent extends ActlyEventBase {
|
|
52
|
+
readonly type: 'backpressure';
|
|
53
|
+
readonly source: 'bulkhead';
|
|
54
|
+
readonly queueLength: number;
|
|
55
|
+
readonly maxConcurrent: number;
|
|
56
|
+
readonly maxQueueSize: number;
|
|
57
|
+
readonly utilization: number;
|
|
58
|
+
}
|
|
59
|
+
export interface WatchdogEvent extends ActlyEventBase {
|
|
60
|
+
readonly type: 'watchdog';
|
|
61
|
+
readonly elapsedMs: number;
|
|
62
|
+
readonly scope: string;
|
|
63
|
+
}
|
|
64
|
+
export interface ObservabilityHooks {
|
|
65
|
+
onAttempt?: (event: AttemptEvent) => void;
|
|
66
|
+
onRetry?: (event: RetryEvent) => void;
|
|
67
|
+
onCacheHit?: (event: CacheHitEvent) => void;
|
|
68
|
+
onCacheMiss?: (event: CacheMissEvent) => void;
|
|
69
|
+
onDedupeJoin?: (event: DedupeJoinEvent) => void;
|
|
70
|
+
onTimeout?: (event: TimeoutEvent) => void;
|
|
71
|
+
onFinalSuccess?: (event: FinalSuccessEvent) => void;
|
|
72
|
+
onFinalFailure?: (event: FinalFailureEvent) => void;
|
|
73
|
+
onBackpressure?: (event: BackpressureEvent) => void;
|
|
74
|
+
onWatchdog?: (event: WatchdogEvent) => void;
|
|
75
|
+
}
|
|
76
|
+
export interface ObservabilityContext {
|
|
77
|
+
traceId: string;
|
|
78
|
+
hooks: ObservabilityHooks;
|
|
79
|
+
joinerCounter: number;
|
|
80
|
+
}
|
|
81
|
+
export declare function hasObservers(ctx: {
|
|
82
|
+
observability?: ObservabilityContext;
|
|
83
|
+
}): ctx is {
|
|
84
|
+
observability: ObservabilityContext;
|
|
85
|
+
};
|
|
86
|
+
//# sourceMappingURL=observability.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"observability.d.ts","sourceRoot":"","sources":["../src/observability.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.bulkheadPolicy = bulkheadPolicy;
|
|
4
|
+
const executor_js_1 = require("../core/executor.js");
|
|
5
|
+
const errors_js_1 = require("../errors.js");
|
|
6
|
+
const safeCall_js_1 = require("../utils/safeCall.js");
|
|
7
|
+
const NS = 'bulk:';
|
|
8
|
+
function getState(store, key) {
|
|
9
|
+
return store.get(NS + key) ?? { active: 0, queue: [] };
|
|
10
|
+
}
|
|
11
|
+
function setState(store, key, state) {
|
|
12
|
+
store.set(NS + key, state);
|
|
13
|
+
}
|
|
14
|
+
function bulkheadPolicy(opts) {
|
|
15
|
+
const maxConcurrent = Math.max(1, Math.floor(opts.maxConcurrent));
|
|
16
|
+
const queueTimeoutMs = opts.queueTimeoutMs ?? 0;
|
|
17
|
+
const maxQueueSize = opts.maxQueueSize ?? Number.POSITIVE_INFINITY;
|
|
18
|
+
const applier = (fn, ctx) => {
|
|
19
|
+
const syncCtx = ctx;
|
|
20
|
+
return async (signal) => {
|
|
21
|
+
const key = syncCtx.key;
|
|
22
|
+
const acquireSlot = () => {
|
|
23
|
+
if (signal.aborted)
|
|
24
|
+
return Promise.reject(signal.reason);
|
|
25
|
+
const state = getState(syncCtx.store, key);
|
|
26
|
+
if (state.active < maxConcurrent) {
|
|
27
|
+
state.active++;
|
|
28
|
+
setState(syncCtx.store, key, state);
|
|
29
|
+
return Promise.resolve();
|
|
30
|
+
}
|
|
31
|
+
if (queueTimeoutMs === 0) {
|
|
32
|
+
throw new errors_js_1.BulkheadOverflowError(key, maxConcurrent);
|
|
33
|
+
}
|
|
34
|
+
const currentState = getState(syncCtx.store, key);
|
|
35
|
+
if (currentState.queue.length >= maxQueueSize) {
|
|
36
|
+
throw new errors_js_1.BulkheadOverflowError(key, maxConcurrent);
|
|
37
|
+
}
|
|
38
|
+
return new Promise((resolve, reject) => {
|
|
39
|
+
const state2 = getState(syncCtx.store, key);
|
|
40
|
+
const entry = {
|
|
41
|
+
resolve,
|
|
42
|
+
reject,
|
|
43
|
+
timer: undefined,
|
|
44
|
+
onAbort: undefined,
|
|
45
|
+
signal,
|
|
46
|
+
};
|
|
47
|
+
entry.onAbort = () => {
|
|
48
|
+
const s = getState(syncCtx.store, key);
|
|
49
|
+
const idx = s.queue.indexOf(entry);
|
|
50
|
+
if (idx >= 0) {
|
|
51
|
+
s.queue.splice(idx, 1);
|
|
52
|
+
setState(syncCtx.store, key, s);
|
|
53
|
+
}
|
|
54
|
+
if (entry.timer)
|
|
55
|
+
clearTimeout(entry.timer);
|
|
56
|
+
reject(signal.reason);
|
|
57
|
+
};
|
|
58
|
+
if (queueTimeoutMs > 0) {
|
|
59
|
+
entry.timer = setTimeout(() => {
|
|
60
|
+
const s = getState(syncCtx.store, key);
|
|
61
|
+
const idx = s.queue.indexOf(entry);
|
|
62
|
+
if (idx >= 0)
|
|
63
|
+
s.queue.splice(idx, 1);
|
|
64
|
+
setState(syncCtx.store, key, s);
|
|
65
|
+
signal.removeEventListener('abort', entry.onAbort);
|
|
66
|
+
reject(new errors_js_1.BulkheadOverflowError(key, maxConcurrent));
|
|
67
|
+
}, queueTimeoutMs);
|
|
68
|
+
}
|
|
69
|
+
signal.addEventListener('abort', entry.onAbort, { once: true });
|
|
70
|
+
state2.queue.push(entry);
|
|
71
|
+
const obs = syncCtx.observability;
|
|
72
|
+
if (obs && maxQueueSize !== Number.POSITIVE_INFINITY) {
|
|
73
|
+
const utilization = state2.queue.length / maxQueueSize;
|
|
74
|
+
const wasOver80 = state2.backpressureEmitted === true;
|
|
75
|
+
if (utilization >= 0.8 && !wasOver80) {
|
|
76
|
+
;
|
|
77
|
+
state2.backpressureEmitted = true;
|
|
78
|
+
(0, safeCall_js_1.safeCall)(obs.hooks.onBackpressure, {
|
|
79
|
+
type: 'backpressure',
|
|
80
|
+
key: syncCtx.key,
|
|
81
|
+
traceId: obs.traceId,
|
|
82
|
+
timestamp: Date.now(),
|
|
83
|
+
source: 'bulkhead',
|
|
84
|
+
queueLength: state2.queue.length,
|
|
85
|
+
maxConcurrent,
|
|
86
|
+
maxQueueSize,
|
|
87
|
+
utilization,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
else if (utilization < 0.8 && wasOver80) {
|
|
91
|
+
;
|
|
92
|
+
state2.backpressureEmitted = false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
setState(syncCtx.store, key, state2);
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
|
+
const releaseSlot = () => {
|
|
99
|
+
const state = getState(syncCtx.store, key);
|
|
100
|
+
state.active--;
|
|
101
|
+
if (state.queue.length > 0) {
|
|
102
|
+
const next = state.queue.shift();
|
|
103
|
+
state.active++;
|
|
104
|
+
if (next.timer)
|
|
105
|
+
clearTimeout(next.timer);
|
|
106
|
+
if (next.onAbort && next.signal) {
|
|
107
|
+
next.signal.removeEventListener('abort', next.onAbort);
|
|
108
|
+
}
|
|
109
|
+
next.resolve();
|
|
110
|
+
}
|
|
111
|
+
if (state.active < 0)
|
|
112
|
+
state.active = 0;
|
|
113
|
+
if (maxQueueSize !== Number.POSITIVE_INFINITY && state.backpressureEmitted === true) {
|
|
114
|
+
if (state.queue.length / maxQueueSize < 0.8) {
|
|
115
|
+
;
|
|
116
|
+
state.backpressureEmitted = false;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (state.active === 0 && state.queue.length === 0) {
|
|
120
|
+
syncCtx.store.delete(NS + key);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
setState(syncCtx.store, key, state);
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
await acquireSlot();
|
|
127
|
+
try {
|
|
128
|
+
return await fn(signal);
|
|
129
|
+
}
|
|
130
|
+
finally {
|
|
131
|
+
releaseSlot();
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
applier[executor_js_1.REQUIRES_SYNC_STORE] = true;
|
|
136
|
+
return applier;
|
|
137
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bulkhead.d.ts","sourceRoot":"","sources":["../../src/policies/bulkhead.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAS,aAAa,EAAiB,eAAe,EAAE,MAAM,mBAAmB,CAAA;AA2B7F,wBAAgB,cAAc,CAAC,CAAC,EAAE,IAAI,EAAE,eAAe,GAAG,aAAa,CAAC,CAAC,CAAC,CAkJzE"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { REQUIRES_SYNC_STORE } from '../core/executor.js';
|
|
2
|
+
import { BulkheadOverflowError } from '../errors.js';
|
|
3
|
+
import { safeCall } from '../utils/safeCall.js';
|
|
4
|
+
const NS = 'bulk:';
|
|
5
|
+
function getState(store, key) {
|
|
6
|
+
return store.get(NS + key) ?? { active: 0, queue: [] };
|
|
7
|
+
}
|
|
8
|
+
function setState(store, key, state) {
|
|
9
|
+
store.set(NS + key, state);
|
|
10
|
+
}
|
|
11
|
+
export function bulkheadPolicy(opts) {
|
|
12
|
+
const maxConcurrent = Math.max(1, Math.floor(opts.maxConcurrent));
|
|
13
|
+
const queueTimeoutMs = opts.queueTimeoutMs ?? 0;
|
|
14
|
+
const maxQueueSize = opts.maxQueueSize ?? Number.POSITIVE_INFINITY;
|
|
15
|
+
const applier = (fn, ctx) => {
|
|
16
|
+
const syncCtx = ctx;
|
|
17
|
+
return async (signal) => {
|
|
18
|
+
const key = syncCtx.key;
|
|
19
|
+
const acquireSlot = () => {
|
|
20
|
+
if (signal.aborted)
|
|
21
|
+
return Promise.reject(signal.reason);
|
|
22
|
+
const state = getState(syncCtx.store, key);
|
|
23
|
+
if (state.active < maxConcurrent) {
|
|
24
|
+
state.active++;
|
|
25
|
+
setState(syncCtx.store, key, state);
|
|
26
|
+
return Promise.resolve();
|
|
27
|
+
}
|
|
28
|
+
if (queueTimeoutMs === 0) {
|
|
29
|
+
throw new BulkheadOverflowError(key, maxConcurrent);
|
|
30
|
+
}
|
|
31
|
+
const currentState = getState(syncCtx.store, key);
|
|
32
|
+
if (currentState.queue.length >= maxQueueSize) {
|
|
33
|
+
throw new BulkheadOverflowError(key, maxConcurrent);
|
|
34
|
+
}
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
const state2 = getState(syncCtx.store, key);
|
|
37
|
+
const entry = {
|
|
38
|
+
resolve,
|
|
39
|
+
reject,
|
|
40
|
+
timer: undefined,
|
|
41
|
+
onAbort: undefined,
|
|
42
|
+
signal,
|
|
43
|
+
};
|
|
44
|
+
entry.onAbort = () => {
|
|
45
|
+
const s = getState(syncCtx.store, key);
|
|
46
|
+
const idx = s.queue.indexOf(entry);
|
|
47
|
+
if (idx >= 0) {
|
|
48
|
+
s.queue.splice(idx, 1);
|
|
49
|
+
setState(syncCtx.store, key, s);
|
|
50
|
+
}
|
|
51
|
+
if (entry.timer)
|
|
52
|
+
clearTimeout(entry.timer);
|
|
53
|
+
reject(signal.reason);
|
|
54
|
+
};
|
|
55
|
+
if (queueTimeoutMs > 0) {
|
|
56
|
+
entry.timer = setTimeout(() => {
|
|
57
|
+
const s = getState(syncCtx.store, key);
|
|
58
|
+
const idx = s.queue.indexOf(entry);
|
|
59
|
+
if (idx >= 0)
|
|
60
|
+
s.queue.splice(idx, 1);
|
|
61
|
+
setState(syncCtx.store, key, s);
|
|
62
|
+
signal.removeEventListener('abort', entry.onAbort);
|
|
63
|
+
reject(new BulkheadOverflowError(key, maxConcurrent));
|
|
64
|
+
}, queueTimeoutMs);
|
|
65
|
+
}
|
|
66
|
+
signal.addEventListener('abort', entry.onAbort, { once: true });
|
|
67
|
+
state2.queue.push(entry);
|
|
68
|
+
const obs = syncCtx.observability;
|
|
69
|
+
if (obs && maxQueueSize !== Number.POSITIVE_INFINITY) {
|
|
70
|
+
const utilization = state2.queue.length / maxQueueSize;
|
|
71
|
+
const wasOver80 = state2.backpressureEmitted === true;
|
|
72
|
+
if (utilization >= 0.8 && !wasOver80) {
|
|
73
|
+
;
|
|
74
|
+
state2.backpressureEmitted = true;
|
|
75
|
+
safeCall(obs.hooks.onBackpressure, {
|
|
76
|
+
type: 'backpressure',
|
|
77
|
+
key: syncCtx.key,
|
|
78
|
+
traceId: obs.traceId,
|
|
79
|
+
timestamp: Date.now(),
|
|
80
|
+
source: 'bulkhead',
|
|
81
|
+
queueLength: state2.queue.length,
|
|
82
|
+
maxConcurrent,
|
|
83
|
+
maxQueueSize,
|
|
84
|
+
utilization,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
else if (utilization < 0.8 && wasOver80) {
|
|
88
|
+
;
|
|
89
|
+
state2.backpressureEmitted = false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
setState(syncCtx.store, key, state2);
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
const releaseSlot = () => {
|
|
96
|
+
const state = getState(syncCtx.store, key);
|
|
97
|
+
state.active--;
|
|
98
|
+
if (state.queue.length > 0) {
|
|
99
|
+
const next = state.queue.shift();
|
|
100
|
+
state.active++;
|
|
101
|
+
if (next.timer)
|
|
102
|
+
clearTimeout(next.timer);
|
|
103
|
+
if (next.onAbort && next.signal) {
|
|
104
|
+
next.signal.removeEventListener('abort', next.onAbort);
|
|
105
|
+
}
|
|
106
|
+
next.resolve();
|
|
107
|
+
}
|
|
108
|
+
if (state.active < 0)
|
|
109
|
+
state.active = 0;
|
|
110
|
+
if (maxQueueSize !== Number.POSITIVE_INFINITY && state.backpressureEmitted === true) {
|
|
111
|
+
if (state.queue.length / maxQueueSize < 0.8) {
|
|
112
|
+
;
|
|
113
|
+
state.backpressureEmitted = false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (state.active === 0 && state.queue.length === 0) {
|
|
117
|
+
syncCtx.store.delete(NS + key);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
setState(syncCtx.store, key, state);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
await acquireSlot();
|
|
124
|
+
try {
|
|
125
|
+
return await fn(signal);
|
|
126
|
+
}
|
|
127
|
+
finally {
|
|
128
|
+
releaseSlot();
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
};
|
|
132
|
+
applier[REQUIRES_SYNC_STORE] = true;
|
|
133
|
+
return applier;
|
|
134
|
+
}
|