@zshannon/streamstore 0.22.3
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/LICENSE +21 -0
- package/README.md +93 -0
- package/dist/cjs/accessTokens.d.ts +51 -0
- package/dist/cjs/accessTokens.d.ts.map +1 -0
- package/dist/cjs/accessTokens.js +127 -0
- package/dist/cjs/accessTokens.js.map +1 -0
- package/dist/cjs/auth/biscuit.d.ts +42 -0
- package/dist/cjs/auth/biscuit.d.ts.map +1 -0
- package/dist/cjs/auth/biscuit.js +72 -0
- package/dist/cjs/auth/biscuit.js.map +1 -0
- package/dist/cjs/auth/index.d.ts +5 -0
- package/dist/cjs/auth/index.d.ts.map +1 -0
- package/dist/cjs/auth/index.js +14 -0
- package/dist/cjs/auth/index.js.map +1 -0
- package/dist/cjs/auth/pki-auth.d.ts +60 -0
- package/dist/cjs/auth/pki-auth.d.ts.map +1 -0
- package/dist/cjs/auth/pki-auth.js +102 -0
- package/dist/cjs/auth/pki-auth.js.map +1 -0
- package/dist/cjs/auth/sign.d.ts +39 -0
- package/dist/cjs/auth/sign.d.ts.map +1 -0
- package/dist/cjs/auth/sign.js +128 -0
- package/dist/cjs/auth/sign.js.map +1 -0
- package/dist/cjs/auth/signing-key.d.ts +42 -0
- package/dist/cjs/auth/signing-key.d.ts.map +1 -0
- package/dist/cjs/auth/signing-key.js +66 -0
- package/dist/cjs/auth/signing-key.js.map +1 -0
- package/dist/cjs/basin.d.ts +33 -0
- package/dist/cjs/basin.d.ts.map +1 -0
- package/dist/cjs/basin.js +54 -0
- package/dist/cjs/basin.js.map +1 -0
- package/dist/cjs/basins.d.ts +62 -0
- package/dist/cjs/basins.d.ts.map +1 -0
- package/dist/cjs/basins.js +201 -0
- package/dist/cjs/basins.js.map +1 -0
- package/dist/cjs/batch-transform.d.ts +60 -0
- package/dist/cjs/batch-transform.d.ts.map +1 -0
- package/dist/cjs/batch-transform.js +171 -0
- package/dist/cjs/batch-transform.js.map +1 -0
- package/dist/cjs/common.d.ts +156 -0
- package/dist/cjs/common.d.ts.map +1 -0
- package/dist/cjs/common.js +54 -0
- package/dist/cjs/common.js.map +1 -0
- package/dist/cjs/endpoints.d.ts +63 -0
- package/dist/cjs/endpoints.d.ts.map +1 -0
- package/dist/cjs/endpoints.js +120 -0
- package/dist/cjs/endpoints.js.map +1 -0
- package/dist/cjs/error.d.ts +119 -0
- package/dist/cjs/error.d.ts.map +1 -0
- package/dist/cjs/error.js +373 -0
- package/dist/cjs/error.js.map +1 -0
- package/dist/cjs/generated/client/client.gen.d.ts +3 -0
- package/dist/cjs/generated/client/client.gen.d.ts.map +1 -0
- package/dist/cjs/generated/client/client.gen.js +209 -0
- package/dist/cjs/generated/client/client.gen.js.map +1 -0
- package/dist/cjs/generated/client/index.d.ts +9 -0
- package/dist/cjs/generated/client/index.d.ts.map +1 -0
- package/dist/cjs/generated/client/index.js +18 -0
- package/dist/cjs/generated/client/index.js.map +1 -0
- package/dist/cjs/generated/client/types.gen.d.ts +125 -0
- package/dist/cjs/generated/client/types.gen.d.ts.map +1 -0
- package/dist/cjs/generated/client/types.gen.js +4 -0
- package/dist/cjs/generated/client/types.gen.js.map +1 -0
- package/dist/cjs/generated/client/utils.gen.d.ts +34 -0
- package/dist/cjs/generated/client/utils.gen.d.ts.map +1 -0
- package/dist/cjs/generated/client/utils.gen.js +243 -0
- package/dist/cjs/generated/client/utils.gen.js.map +1 -0
- package/dist/cjs/generated/client.gen.d.ts +13 -0
- package/dist/cjs/generated/client.gen.d.ts.map +1 -0
- package/dist/cjs/generated/client.gen.js +9 -0
- package/dist/cjs/generated/client.gen.js.map +1 -0
- package/dist/cjs/generated/core/auth.gen.d.ts +19 -0
- package/dist/cjs/generated/core/auth.gen.d.ts.map +1 -0
- package/dist/cjs/generated/core/auth.gen.js +19 -0
- package/dist/cjs/generated/core/auth.gen.js.map +1 -0
- package/dist/cjs/generated/core/bodySerializer.gen.d.ts +26 -0
- package/dist/cjs/generated/core/bodySerializer.gen.d.ts.map +1 -0
- package/dist/cjs/generated/core/bodySerializer.gen.js +61 -0
- package/dist/cjs/generated/core/bodySerializer.gen.js.map +1 -0
- package/dist/cjs/generated/core/params.gen.d.ts +44 -0
- package/dist/cjs/generated/core/params.gen.d.ts.map +1 -0
- package/dist/cjs/generated/core/params.gen.js +105 -0
- package/dist/cjs/generated/core/params.gen.js.map +1 -0
- package/dist/cjs/generated/core/pathSerializer.gen.d.ts +34 -0
- package/dist/cjs/generated/core/pathSerializer.gen.d.ts.map +1 -0
- package/dist/cjs/generated/core/pathSerializer.gen.js +124 -0
- package/dist/cjs/generated/core/pathSerializer.gen.js.map +1 -0
- package/dist/cjs/generated/core/queryKeySerializer.gen.d.ts +19 -0
- package/dist/cjs/generated/core/queryKeySerializer.gen.d.ts.map +1 -0
- package/dist/cjs/generated/core/queryKeySerializer.gen.js +106 -0
- package/dist/cjs/generated/core/queryKeySerializer.gen.js.map +1 -0
- package/dist/cjs/generated/core/serverSentEvents.gen.d.ts +72 -0
- package/dist/cjs/generated/core/serverSentEvents.gen.d.ts.map +1 -0
- package/dist/cjs/generated/core/serverSentEvents.gen.js +140 -0
- package/dist/cjs/generated/core/serverSentEvents.gen.js.map +1 -0
- package/dist/cjs/generated/core/types.gen.d.ts +79 -0
- package/dist/cjs/generated/core/types.gen.d.ts.map +1 -0
- package/dist/cjs/generated/core/types.gen.js +4 -0
- package/dist/cjs/generated/core/types.gen.js.map +1 -0
- package/dist/cjs/generated/core/utils.gen.d.ts +20 -0
- package/dist/cjs/generated/core/utils.gen.d.ts.map +1 -0
- package/dist/cjs/generated/core/utils.gen.js +94 -0
- package/dist/cjs/generated/core/utils.gen.js.map +1 -0
- package/dist/cjs/generated/index.d.ts +3 -0
- package/dist/cjs/generated/index.d.ts.map +1 -0
- package/dist/cjs/generated/index.js +19 -0
- package/dist/cjs/generated/index.js.map +1 -0
- package/dist/cjs/generated/proto/s2.d.ts +250 -0
- package/dist/cjs/generated/proto/s2.d.ts.map +1 -0
- package/dist/cjs/generated/proto/s2.js +426 -0
- package/dist/cjs/generated/proto/s2.js.map +1 -0
- package/dist/cjs/generated/sdk.gen.d.ts +100 -0
- package/dist/cjs/generated/sdk.gen.d.ts.map +1 -0
- package/dist/cjs/generated/sdk.gen.js +374 -0
- package/dist/cjs/generated/sdk.gen.js.map +1 -0
- package/dist/cjs/generated/types.gen.d.ts +1064 -0
- package/dist/cjs/generated/types.gen.d.ts.map +1 -0
- package/dist/cjs/generated/types.gen.js +4 -0
- package/dist/cjs/generated/types.gen.js.map +1 -0
- package/dist/cjs/index.d.ts +42 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +81 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/internal/case-transform.d.ts +59 -0
- package/dist/cjs/internal/case-transform.d.ts.map +1 -0
- package/dist/cjs/internal/case-transform.js +80 -0
- package/dist/cjs/internal/case-transform.js.map +1 -0
- package/dist/cjs/internal/mappers.d.ts +51 -0
- package/dist/cjs/internal/mappers.d.ts.map +1 -0
- package/dist/cjs/internal/mappers.js +225 -0
- package/dist/cjs/internal/mappers.js.map +1 -0
- package/dist/cjs/internal/sdk-types.d.ts +127 -0
- package/dist/cjs/internal/sdk-types.d.ts.map +1 -0
- package/dist/cjs/internal/sdk-types.js +9 -0
- package/dist/cjs/internal/sdk-types.js.map +1 -0
- package/dist/cjs/lib/base64.d.ts +12 -0
- package/dist/cjs/lib/base64.d.ts.map +1 -0
- package/dist/cjs/lib/base64.js +172 -0
- package/dist/cjs/lib/base64.js.map +1 -0
- package/dist/cjs/lib/event-stream.d.ts +26 -0
- package/dist/cjs/lib/event-stream.d.ts.map +1 -0
- package/dist/cjs/lib/event-stream.js +154 -0
- package/dist/cjs/lib/event-stream.js.map +1 -0
- package/dist/cjs/lib/paginate.d.ts +61 -0
- package/dist/cjs/lib/paginate.d.ts.map +1 -0
- package/dist/cjs/lib/paginate.js +51 -0
- package/dist/cjs/lib/paginate.js.map +1 -0
- package/dist/cjs/lib/redacted.d.ts +17 -0
- package/dist/cjs/lib/redacted.d.ts.map +1 -0
- package/dist/cjs/lib/redacted.js +34 -0
- package/dist/cjs/lib/redacted.js.map +1 -0
- package/dist/cjs/lib/result.d.ts +57 -0
- package/dist/cjs/lib/result.d.ts.map +1 -0
- package/dist/cjs/lib/result.js +43 -0
- package/dist/cjs/lib/result.js.map +1 -0
- package/dist/cjs/lib/retry.d.ts +167 -0
- package/dist/cjs/lib/retry.d.ts.map +1 -0
- package/dist/cjs/lib/retry.js +1011 -0
- package/dist/cjs/lib/retry.js.map +1 -0
- package/dist/cjs/lib/stream/factory.d.ts +14 -0
- package/dist/cjs/lib/stream/factory.d.ts.map +1 -0
- package/dist/cjs/lib/stream/factory.js +35 -0
- package/dist/cjs/lib/stream/factory.js.map +1 -0
- package/dist/cjs/lib/stream/runtime.d.ts +27 -0
- package/dist/cjs/lib/stream/runtime.d.ts.map +1 -0
- package/dist/cjs/lib/stream/runtime.js +70 -0
- package/dist/cjs/lib/stream/runtime.js.map +1 -0
- package/dist/cjs/lib/stream/transport/fetch/index.d.ts +64 -0
- package/dist/cjs/lib/stream/transport/fetch/index.d.ts.map +1 -0
- package/dist/cjs/lib/stream/transport/fetch/index.js +462 -0
- package/dist/cjs/lib/stream/transport/fetch/index.js.map +1 -0
- package/dist/cjs/lib/stream/transport/fetch/shared.d.ts +11 -0
- package/dist/cjs/lib/stream/transport/fetch/shared.d.ts.map +1 -0
- package/dist/cjs/lib/stream/transport/fetch/shared.js +118 -0
- package/dist/cjs/lib/stream/transport/fetch/shared.js.map +1 -0
- package/dist/cjs/lib/stream/transport/proto.d.ts +9 -0
- package/dist/cjs/lib/stream/transport/proto.d.ts.map +1 -0
- package/dist/cjs/lib/stream/transport/proto.js +118 -0
- package/dist/cjs/lib/stream/transport/proto.js.map +1 -0
- package/dist/cjs/lib/stream/transport/s2s/framing.d.ts +47 -0
- package/dist/cjs/lib/stream/transport/s2s/framing.d.ts.map +1 -0
- package/dist/cjs/lib/stream/transport/s2s/framing.js +123 -0
- package/dist/cjs/lib/stream/transport/s2s/framing.js.map +1 -0
- package/dist/cjs/lib/stream/transport/s2s/index.d.ts +24 -0
- package/dist/cjs/lib/stream/transport/s2s/index.d.ts.map +1 -0
- package/dist/cjs/lib/stream/transport/s2s/index.js +823 -0
- package/dist/cjs/lib/stream/transport/s2s/index.js.map +1 -0
- package/dist/cjs/lib/stream/types.d.ts +199 -0
- package/dist/cjs/lib/stream/types.d.ts.map +1 -0
- package/dist/cjs/lib/stream/types.js +21 -0
- package/dist/cjs/lib/stream/types.js.map +1 -0
- package/dist/cjs/metrics.d.ts +46 -0
- package/dist/cjs/metrics.d.ts.map +1 -0
- package/dist/cjs/metrics.js +127 -0
- package/dist/cjs/metrics.js.map +1 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/producer.d.ts +82 -0
- package/dist/cjs/producer.d.ts.map +1 -0
- package/dist/cjs/producer.js +305 -0
- package/dist/cjs/producer.js.map +1 -0
- package/dist/cjs/s2.d.ts +41 -0
- package/dist/cjs/s2.d.ts.map +1 -0
- package/dist/cjs/s2.js +119 -0
- package/dist/cjs/s2.js.map +1 -0
- package/dist/cjs/stream.d.ts +78 -0
- package/dist/cjs/stream.d.ts.map +1 -0
- package/dist/cjs/stream.js +176 -0
- package/dist/cjs/stream.js.map +1 -0
- package/dist/cjs/streams.d.ts +61 -0
- package/dist/cjs/streams.d.ts.map +1 -0
- package/dist/cjs/streams.js +201 -0
- package/dist/cjs/streams.js.map +1 -0
- package/dist/cjs/types.d.ts +633 -0
- package/dist/cjs/types.d.ts.map +1 -0
- package/dist/cjs/types.js +129 -0
- package/dist/cjs/types.js.map +1 -0
- package/dist/cjs/utils.d.ts +25 -0
- package/dist/cjs/utils.d.ts.map +1 -0
- package/dist/cjs/utils.js +108 -0
- package/dist/cjs/utils.js.map +1 -0
- package/dist/cjs/version.d.ts +8 -0
- package/dist/cjs/version.d.ts.map +1 -0
- package/dist/cjs/version.js +11 -0
- package/dist/cjs/version.js.map +1 -0
- package/dist/esm/accessTokens.d.ts +51 -0
- package/dist/esm/accessTokens.d.ts.map +1 -0
- package/dist/esm/accessTokens.js +123 -0
- package/dist/esm/accessTokens.js.map +1 -0
- package/dist/esm/auth/biscuit.d.ts +42 -0
- package/dist/esm/auth/biscuit.d.ts.map +1 -0
- package/dist/esm/auth/biscuit.js +68 -0
- package/dist/esm/auth/biscuit.js.map +1 -0
- package/dist/esm/auth/index.d.ts +5 -0
- package/dist/esm/auth/index.d.ts.map +1 -0
- package/dist/esm/auth/index.js +5 -0
- package/dist/esm/auth/index.js.map +1 -0
- package/dist/esm/auth/pki-auth.d.ts +60 -0
- package/dist/esm/auth/pki-auth.d.ts.map +1 -0
- package/dist/esm/auth/pki-auth.js +99 -0
- package/dist/esm/auth/pki-auth.js.map +1 -0
- package/dist/esm/auth/sign.d.ts +39 -0
- package/dist/esm/auth/sign.d.ts.map +1 -0
- package/dist/esm/auth/sign.js +125 -0
- package/dist/esm/auth/sign.js.map +1 -0
- package/dist/esm/auth/signing-key.d.ts +42 -0
- package/dist/esm/auth/signing-key.d.ts.map +1 -0
- package/dist/esm/auth/signing-key.js +62 -0
- package/dist/esm/auth/signing-key.js.map +1 -0
- package/dist/esm/basin.d.ts +33 -0
- package/dist/esm/basin.d.ts.map +1 -0
- package/dist/esm/basin.js +50 -0
- package/dist/esm/basin.js.map +1 -0
- package/dist/esm/basins.d.ts +62 -0
- package/dist/esm/basins.d.ts.map +1 -0
- package/dist/esm/basins.js +197 -0
- package/dist/esm/basins.js.map +1 -0
- package/dist/esm/batch-transform.d.ts +60 -0
- package/dist/esm/batch-transform.d.ts.map +1 -0
- package/dist/esm/batch-transform.js +167 -0
- package/dist/esm/batch-transform.js.map +1 -0
- package/dist/esm/common.d.ts +156 -0
- package/dist/esm/common.d.ts.map +1 -0
- package/dist/esm/common.js +49 -0
- package/dist/esm/common.js.map +1 -0
- package/dist/esm/endpoints.d.ts +63 -0
- package/dist/esm/endpoints.d.ts.map +1 -0
- package/dist/esm/endpoints.js +115 -0
- package/dist/esm/endpoints.js.map +1 -0
- package/dist/esm/error.d.ts +119 -0
- package/dist/esm/error.d.ts.map +1 -0
- package/dist/esm/error.js +358 -0
- package/dist/esm/error.js.map +1 -0
- package/dist/esm/generated/client/client.gen.d.ts +3 -0
- package/dist/esm/generated/client/client.gen.d.ts.map +1 -0
- package/dist/esm/generated/client/client.gen.js +205 -0
- package/dist/esm/generated/client/client.gen.js.map +1 -0
- package/dist/esm/generated/client/index.d.ts +9 -0
- package/dist/esm/generated/client/index.d.ts.map +1 -0
- package/dist/esm/generated/client/index.js +7 -0
- package/dist/esm/generated/client/index.js.map +1 -0
- package/dist/esm/generated/client/types.gen.d.ts +125 -0
- package/dist/esm/generated/client/types.gen.d.ts.map +1 -0
- package/dist/esm/generated/client/types.gen.js +3 -0
- package/dist/esm/generated/client/types.gen.js.map +1 -0
- package/dist/esm/generated/client/utils.gen.d.ts +34 -0
- package/dist/esm/generated/client/utils.gen.d.ts.map +1 -0
- package/dist/esm/generated/client/utils.gen.js +232 -0
- package/dist/esm/generated/client/utils.gen.js.map +1 -0
- package/dist/esm/generated/client.gen.d.ts +13 -0
- package/dist/esm/generated/client.gen.d.ts.map +1 -0
- package/dist/esm/generated/client.gen.js +6 -0
- package/dist/esm/generated/client.gen.js.map +1 -0
- package/dist/esm/generated/core/auth.gen.d.ts +19 -0
- package/dist/esm/generated/core/auth.gen.d.ts.map +1 -0
- package/dist/esm/generated/core/auth.gen.js +15 -0
- package/dist/esm/generated/core/auth.gen.js.map +1 -0
- package/dist/esm/generated/core/bodySerializer.gen.d.ts +26 -0
- package/dist/esm/generated/core/bodySerializer.gen.d.ts.map +1 -0
- package/dist/esm/generated/core/bodySerializer.gen.js +58 -0
- package/dist/esm/generated/core/bodySerializer.gen.js.map +1 -0
- package/dist/esm/generated/core/params.gen.d.ts +44 -0
- package/dist/esm/generated/core/params.gen.d.ts.map +1 -0
- package/dist/esm/generated/core/params.gen.js +101 -0
- package/dist/esm/generated/core/params.gen.js.map +1 -0
- package/dist/esm/generated/core/pathSerializer.gen.d.ts +34 -0
- package/dist/esm/generated/core/pathSerializer.gen.d.ts.map +1 -0
- package/dist/esm/generated/core/pathSerializer.gen.js +115 -0
- package/dist/esm/generated/core/pathSerializer.gen.js.map +1 -0
- package/dist/esm/generated/core/queryKeySerializer.gen.d.ts +19 -0
- package/dist/esm/generated/core/queryKeySerializer.gen.d.ts.map +1 -0
- package/dist/esm/generated/core/queryKeySerializer.gen.js +100 -0
- package/dist/esm/generated/core/queryKeySerializer.gen.js.map +1 -0
- package/dist/esm/generated/core/serverSentEvents.gen.d.ts +72 -0
- package/dist/esm/generated/core/serverSentEvents.gen.d.ts.map +1 -0
- package/dist/esm/generated/core/serverSentEvents.gen.js +136 -0
- package/dist/esm/generated/core/serverSentEvents.gen.js.map +1 -0
- package/dist/esm/generated/core/types.gen.d.ts +79 -0
- package/dist/esm/generated/core/types.gen.d.ts.map +1 -0
- package/dist/esm/generated/core/types.gen.js +3 -0
- package/dist/esm/generated/core/types.gen.js.map +1 -0
- package/dist/esm/generated/core/utils.gen.d.ts +20 -0
- package/dist/esm/generated/core/utils.gen.d.ts.map +1 -0
- package/dist/esm/generated/core/utils.gen.js +88 -0
- package/dist/esm/generated/core/utils.gen.js.map +1 -0
- package/dist/esm/generated/index.d.ts +3 -0
- package/dist/esm/generated/index.d.ts.map +1 -0
- package/dist/esm/generated/index.js +3 -0
- package/dist/esm/generated/index.js.map +1 -0
- package/dist/esm/generated/proto/s2.d.ts +250 -0
- package/dist/esm/generated/proto/s2.d.ts.map +1 -0
- package/dist/esm/generated/proto/s2.js +423 -0
- package/dist/esm/generated/proto/s2.js.map +1 -0
- package/dist/esm/generated/sdk.gen.d.ts +100 -0
- package/dist/esm/generated/sdk.gen.d.ts.map +1 -0
- package/dist/esm/generated/sdk.gen.js +350 -0
- package/dist/esm/generated/sdk.gen.js.map +1 -0
- package/dist/esm/generated/types.gen.d.ts +1064 -0
- package/dist/esm/generated/types.gen.d.ts.map +1 -0
- package/dist/esm/generated/types.gen.js +3 -0
- package/dist/esm/generated/types.gen.js.map +1 -0
- package/dist/esm/index.d.ts +42 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +47 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/internal/case-transform.d.ts +59 -0
- package/dist/esm/internal/case-transform.d.ts.map +1 -0
- package/dist/esm/internal/case-transform.js +76 -0
- package/dist/esm/internal/case-transform.js.map +1 -0
- package/dist/esm/internal/mappers.d.ts +51 -0
- package/dist/esm/internal/mappers.d.ts.map +1 -0
- package/dist/esm/internal/mappers.js +218 -0
- package/dist/esm/internal/mappers.js.map +1 -0
- package/dist/esm/internal/sdk-types.d.ts +127 -0
- package/dist/esm/internal/sdk-types.d.ts.map +1 -0
- package/dist/esm/internal/sdk-types.js +8 -0
- package/dist/esm/internal/sdk-types.js.map +1 -0
- package/dist/esm/lib/base64.d.ts +12 -0
- package/dist/esm/lib/base64.d.ts.map +1 -0
- package/dist/esm/lib/base64.js +165 -0
- package/dist/esm/lib/base64.js.map +1 -0
- package/dist/esm/lib/event-stream.d.ts +26 -0
- package/dist/esm/lib/event-stream.d.ts.map +1 -0
- package/dist/esm/lib/event-stream.js +150 -0
- package/dist/esm/lib/event-stream.js.map +1 -0
- package/dist/esm/lib/paginate.d.ts +61 -0
- package/dist/esm/lib/paginate.d.ts.map +1 -0
- package/dist/esm/lib/paginate.js +48 -0
- package/dist/esm/lib/paginate.js.map +1 -0
- package/dist/esm/lib/redacted.d.ts +17 -0
- package/dist/esm/lib/redacted.d.ts.map +1 -0
- package/dist/esm/lib/redacted.js +28 -0
- package/dist/esm/lib/redacted.js.map +1 -0
- package/dist/esm/lib/result.d.ts +57 -0
- package/dist/esm/lib/result.d.ts.map +1 -0
- package/dist/esm/lib/result.js +37 -0
- package/dist/esm/lib/result.js.map +1 -0
- package/dist/esm/lib/retry.d.ts +167 -0
- package/dist/esm/lib/retry.d.ts.map +1 -0
- package/dist/esm/lib/retry.js +1003 -0
- package/dist/esm/lib/retry.js.map +1 -0
- package/dist/esm/lib/stream/factory.d.ts +14 -0
- package/dist/esm/lib/stream/factory.d.ts.map +1 -0
- package/dist/esm/lib/stream/factory.js +32 -0
- package/dist/esm/lib/stream/factory.js.map +1 -0
- package/dist/esm/lib/stream/runtime.d.ts +27 -0
- package/dist/esm/lib/stream/runtime.d.ts.map +1 -0
- package/dist/esm/lib/stream/runtime.js +71 -0
- package/dist/esm/lib/stream/runtime.js.map +1 -0
- package/dist/esm/lib/stream/transport/fetch/index.d.ts +64 -0
- package/dist/esm/lib/stream/transport/fetch/index.d.ts.map +1 -0
- package/dist/esm/lib/stream/transport/fetch/index.js +456 -0
- package/dist/esm/lib/stream/transport/fetch/index.js.map +1 -0
- package/dist/esm/lib/stream/transport/fetch/shared.d.ts +11 -0
- package/dist/esm/lib/stream/transport/fetch/shared.d.ts.map +1 -0
- package/dist/esm/lib/stream/transport/fetch/shared.js +114 -0
- package/dist/esm/lib/stream/transport/fetch/shared.js.map +1 -0
- package/dist/esm/lib/stream/transport/proto.d.ts +9 -0
- package/dist/esm/lib/stream/transport/proto.d.ts.map +1 -0
- package/dist/esm/lib/stream/transport/proto.js +110 -0
- package/dist/esm/lib/stream/transport/proto.js.map +1 -0
- package/dist/esm/lib/stream/transport/s2s/framing.d.ts +47 -0
- package/dist/esm/lib/stream/transport/s2s/framing.d.ts.map +1 -0
- package/dist/esm/lib/stream/transport/s2s/framing.js +118 -0
- package/dist/esm/lib/stream/transport/s2s/framing.js.map +1 -0
- package/dist/esm/lib/stream/transport/s2s/index.d.ts +24 -0
- package/dist/esm/lib/stream/transport/s2s/index.d.ts.map +1 -0
- package/dist/esm/lib/stream/transport/s2s/index.js +819 -0
- package/dist/esm/lib/stream/transport/s2s/index.js.map +1 -0
- package/dist/esm/lib/stream/types.d.ts +199 -0
- package/dist/esm/lib/stream/types.d.ts.map +1 -0
- package/dist/esm/lib/stream/types.js +18 -0
- package/dist/esm/lib/stream/types.js.map +1 -0
- package/dist/esm/metrics.d.ts +46 -0
- package/dist/esm/metrics.d.ts.map +1 -0
- package/dist/esm/metrics.js +122 -0
- package/dist/esm/metrics.js.map +1 -0
- package/dist/esm/producer.d.ts +82 -0
- package/dist/esm/producer.d.ts.map +1 -0
- package/dist/esm/producer.js +300 -0
- package/dist/esm/producer.js.map +1 -0
- package/dist/esm/s2.d.ts +41 -0
- package/dist/esm/s2.d.ts.map +1 -0
- package/dist/esm/s2.js +115 -0
- package/dist/esm/s2.js.map +1 -0
- package/dist/esm/stream.d.ts +78 -0
- package/dist/esm/stream.d.ts.map +1 -0
- package/dist/esm/stream.js +172 -0
- package/dist/esm/stream.js.map +1 -0
- package/dist/esm/streams.d.ts +61 -0
- package/dist/esm/streams.d.ts.map +1 -0
- package/dist/esm/streams.js +197 -0
- package/dist/esm/streams.js.map +1 -0
- package/dist/esm/types.d.ts +633 -0
- package/dist/esm/types.d.ts.map +1 -0
- package/dist/esm/types.js +126 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/esm/utils.d.ts +25 -0
- package/dist/esm/utils.d.ts.map +1 -0
- package/dist/esm/utils.js +103 -0
- package/dist/esm/utils.js.map +1 -0
- package/dist/esm/version.d.ts +8 -0
- package/dist/esm/version.d.ts.map +1 -0
- package/dist/esm/version.js +8 -0
- package/dist/esm/version.js.map +1 -0
- package/package.json +47 -0
|
@@ -0,0 +1,1011 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RetryAppendSession = exports.RetryReadSession = exports.DEFAULT_RETRY_CONFIG = void 0;
|
|
4
|
+
exports.isRetryable = isRetryable;
|
|
5
|
+
exports.calculateDelay = calculateDelay;
|
|
6
|
+
exports.sleep = sleep;
|
|
7
|
+
exports.withRetries = withRetries;
|
|
8
|
+
const debug_1 = require("debug");
|
|
9
|
+
const error_js_1 = require("../error.js");
|
|
10
|
+
const utils_js_1 = require("../utils.js");
|
|
11
|
+
const result_js_1 = require("./result.js");
|
|
12
|
+
const types_js_1 = require("./stream/types.js");
|
|
13
|
+
const debugWith = (0, debug_1.default)("s2:retry:with");
|
|
14
|
+
const debugRead = (0, debug_1.default)("s2:retry:read");
|
|
15
|
+
const debugSession = (0, debug_1.default)("s2:retry:session");
|
|
16
|
+
/** Type guard for errors with a code property (e.g., Node.js errors). */
|
|
17
|
+
function hasErrorCode(err, code) {
|
|
18
|
+
return (typeof err === "object" &&
|
|
19
|
+
err !== null &&
|
|
20
|
+
"code" in err &&
|
|
21
|
+
err.code === code);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Convert generated StreamPosition to SDK StreamPosition.
|
|
25
|
+
*/
|
|
26
|
+
function toSDKStreamPosition(pos) {
|
|
27
|
+
return {
|
|
28
|
+
seqNum: pos.seq_num,
|
|
29
|
+
timestamp: new Date(pos.timestamp),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Convert internal ReadRecord (with headers as object for strings) to SDK ReadRecord (with headers as array).
|
|
34
|
+
*/
|
|
35
|
+
function toSDKReadRecord(record) {
|
|
36
|
+
if (record.headers &&
|
|
37
|
+
typeof record.headers === "object" &&
|
|
38
|
+
!Array.isArray(record.headers)) {
|
|
39
|
+
// String format: headers is an object, convert to array of tuples
|
|
40
|
+
const result = {
|
|
41
|
+
seqNum: record.seq_num,
|
|
42
|
+
timestamp: new Date(record.timestamp),
|
|
43
|
+
body: record.body ?? "",
|
|
44
|
+
headers: Object.entries(record.headers),
|
|
45
|
+
};
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
// Bytes format: headers is already an array
|
|
50
|
+
const result = {
|
|
51
|
+
seqNum: record.seq_num,
|
|
52
|
+
timestamp: new Date(record.timestamp),
|
|
53
|
+
body: record.body ?? new Uint8Array(),
|
|
54
|
+
headers: record.headers ?? [],
|
|
55
|
+
};
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Default retry configuration.
|
|
61
|
+
*/
|
|
62
|
+
exports.DEFAULT_RETRY_CONFIG = {
|
|
63
|
+
maxAttempts: 3,
|
|
64
|
+
minBaseDelayMillis: 100,
|
|
65
|
+
maxBaseDelayMillis: 1000,
|
|
66
|
+
appendRetryPolicy: "all",
|
|
67
|
+
requestTimeoutMillis: 5000, // 5 seconds
|
|
68
|
+
connectionTimeoutMillis: 3000, // 3 seconds
|
|
69
|
+
};
|
|
70
|
+
const RETRYABLE_STATUS_CODES = new Set([
|
|
71
|
+
408, // request_timeout
|
|
72
|
+
429, // too_many_requests
|
|
73
|
+
500, // internal_server_error
|
|
74
|
+
502, // bad_gateway
|
|
75
|
+
503, // service_unavailable
|
|
76
|
+
504, // gateway_timeout
|
|
77
|
+
]);
|
|
78
|
+
/**
|
|
79
|
+
* Determines if an error should be retried based on its characteristics.
|
|
80
|
+
* 400-level errors (except 408, 429) are non-retryable validation/client errors.
|
|
81
|
+
*/
|
|
82
|
+
function isRetryable(error) {
|
|
83
|
+
if (!error.status)
|
|
84
|
+
return false;
|
|
85
|
+
// Explicit retryable codes (including some 4xx like 408, 429)
|
|
86
|
+
if (RETRYABLE_STATUS_CODES.has(error.status)) {
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
// 400-level errors are generally non-retryable (validation, bad request)
|
|
90
|
+
if (error.status >= 400 && error.status < 500) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Calculates the delay before the next retry attempt using exponential backoff
|
|
97
|
+
* with additive jitter.
|
|
98
|
+
*
|
|
99
|
+
* Formula:
|
|
100
|
+
* baseDelay = min(minBaseDelayMillis * 2^attempt, maxBaseDelayMillis)
|
|
101
|
+
* jitter = random(0, baseDelay)
|
|
102
|
+
* delay = baseDelay + jitter
|
|
103
|
+
*
|
|
104
|
+
* @param attempt - Zero-based retry attempt number (0 = first retry)
|
|
105
|
+
* @param minBaseDelayMillis - Minimum delay for exponential backoff
|
|
106
|
+
* @param maxBaseDelayMillis - Maximum base delay (actual delay can be up to 2x with jitter)
|
|
107
|
+
*/
|
|
108
|
+
function calculateDelay(attempt, minBaseDelayMillis, maxBaseDelayMillis) {
|
|
109
|
+
// Calculate exponential backoff: minDelay * 2^attempt, capped at maxDelay
|
|
110
|
+
const baseDelay = Math.min(minBaseDelayMillis * Math.pow(2, attempt), maxBaseDelayMillis);
|
|
111
|
+
// Add jitter: random value in [0, baseDelay)
|
|
112
|
+
const jitter = Math.random() * baseDelay;
|
|
113
|
+
// Total delay is base + jitter
|
|
114
|
+
return Math.floor(baseDelay + jitter);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Sleeps for the specified duration.
|
|
118
|
+
*/
|
|
119
|
+
function sleep(ms) {
|
|
120
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Executes an async function with automatic retry logic for transient failures.
|
|
124
|
+
*
|
|
125
|
+
* @param retryConfig Retry configuration (max attempts, backoff duration)
|
|
126
|
+
* @param fn The async function to execute
|
|
127
|
+
* @returns The result of the function
|
|
128
|
+
* @throws The last error if all retry attempts are exhausted
|
|
129
|
+
*/
|
|
130
|
+
async function withRetries(retryConfig, fn, isPolicyCompliant = () => true) {
|
|
131
|
+
const config = {
|
|
132
|
+
...exports.DEFAULT_RETRY_CONFIG,
|
|
133
|
+
...retryConfig,
|
|
134
|
+
};
|
|
135
|
+
// Enforce minimum of 1 attempt (1 = no retries)
|
|
136
|
+
if (config.maxAttempts < 1)
|
|
137
|
+
config.maxAttempts = 1;
|
|
138
|
+
let lastError = undefined;
|
|
139
|
+
// attemptNo is 1-based: 1..maxAttempts
|
|
140
|
+
for (let attemptNo = 1; attemptNo <= config.maxAttempts; attemptNo++) {
|
|
141
|
+
try {
|
|
142
|
+
const result = await fn();
|
|
143
|
+
if (attemptNo > 1) {
|
|
144
|
+
debugWith("succeeded after %d retries", attemptNo - 1);
|
|
145
|
+
}
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
// withRetry only handles S2Errors (withS2Error should be called first)
|
|
150
|
+
if (!(error instanceof error_js_1.S2Error)) {
|
|
151
|
+
debugWith("non-S2Error thrown, rethrowing immediately: %s", error);
|
|
152
|
+
throw error;
|
|
153
|
+
}
|
|
154
|
+
lastError = error;
|
|
155
|
+
// Don't retry if this is the last attempt
|
|
156
|
+
if (attemptNo === config.maxAttempts) {
|
|
157
|
+
debugWith("max attempts exhausted, throwing error");
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
// Check if error is retryable
|
|
161
|
+
if (!isPolicyCompliant(config, lastError) || !isRetryable(lastError)) {
|
|
162
|
+
debugWith("error not retryable, throwing immediately");
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
165
|
+
// Calculate delay and wait before retrying
|
|
166
|
+
const delay = calculateDelay(attemptNo - 1, config.minBaseDelayMillis, config.maxBaseDelayMillis);
|
|
167
|
+
debugWith("retryable error, backing off for %dms, status=%s", delay, error.status);
|
|
168
|
+
await sleep(delay);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
throw lastError;
|
|
172
|
+
}
|
|
173
|
+
class RetryReadSession extends ReadableStream {
|
|
174
|
+
_nextReadPosition = undefined;
|
|
175
|
+
_lastObservedTail = undefined;
|
|
176
|
+
_recordsRead = 0;
|
|
177
|
+
_bytesRead = 0;
|
|
178
|
+
static async create(generator, args = {}, config) {
|
|
179
|
+
const retryConfig = {
|
|
180
|
+
...exports.DEFAULT_RETRY_CONFIG,
|
|
181
|
+
...config,
|
|
182
|
+
};
|
|
183
|
+
// Establish connection eagerly to fail fast on connection errors
|
|
184
|
+
// This way readSession() throws if we can't connect, rather than
|
|
185
|
+
// returning a stream that immediately errors
|
|
186
|
+
let attempt = 0;
|
|
187
|
+
let lastError;
|
|
188
|
+
while (true) {
|
|
189
|
+
try {
|
|
190
|
+
const session = await generator(args);
|
|
191
|
+
// Connection succeeded - return the retry wrapper
|
|
192
|
+
return new RetryReadSession(args, generator, config, session);
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
const error = (0, error_js_1.s2Error)(err);
|
|
196
|
+
lastError = error;
|
|
197
|
+
const effectiveMax = Math.max(1, retryConfig.maxAttempts);
|
|
198
|
+
if (isRetryable(error) && attempt < effectiveMax - 1) {
|
|
199
|
+
const delay = calculateDelay(attempt, retryConfig.minBaseDelayMillis, retryConfig.maxBaseDelayMillis);
|
|
200
|
+
debugRead("connection error in create, will retry after %dms, status=%s", delay, error.status);
|
|
201
|
+
await sleep(delay);
|
|
202
|
+
attempt++;
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
// Not retryable or attempts exhausted
|
|
206
|
+
throw lastError;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
constructor(args, generator, config, initialSession) {
|
|
211
|
+
const retryConfig = {
|
|
212
|
+
...exports.DEFAULT_RETRY_CONFIG,
|
|
213
|
+
...config,
|
|
214
|
+
};
|
|
215
|
+
let session = initialSession;
|
|
216
|
+
const startTimeMs = performance.now(); // Capture start time before super()
|
|
217
|
+
super({
|
|
218
|
+
start: async (controller) => {
|
|
219
|
+
let nextArgs = { ...args };
|
|
220
|
+
// Capture original request budget so retries compute from a stable baseline
|
|
221
|
+
const baselineCount = args?.count;
|
|
222
|
+
const baselineBytes = args?.bytes;
|
|
223
|
+
const baselineWait = args?.wait;
|
|
224
|
+
let attempt = 0;
|
|
225
|
+
while (true) {
|
|
226
|
+
// Use pre-established session on first iteration if provided
|
|
227
|
+
if (!session) {
|
|
228
|
+
debugRead("starting read session with args: %o", nextArgs);
|
|
229
|
+
// Try to create session - may throw on connection errors
|
|
230
|
+
try {
|
|
231
|
+
session = await generator(nextArgs);
|
|
232
|
+
}
|
|
233
|
+
catch (err) {
|
|
234
|
+
// Convert to S2Error if needed
|
|
235
|
+
const error = (0, error_js_1.s2Error)(err);
|
|
236
|
+
// Check if we can retry connection errors
|
|
237
|
+
const effectiveMax = Math.max(1, retryConfig.maxAttempts);
|
|
238
|
+
if (isRetryable(error) && attempt < effectiveMax - 1) {
|
|
239
|
+
const delay = calculateDelay(attempt, retryConfig.minBaseDelayMillis, retryConfig.maxBaseDelayMillis);
|
|
240
|
+
debugRead("connection error, will retry after %dms, status=%s", delay, error.status);
|
|
241
|
+
await sleep(delay);
|
|
242
|
+
attempt++;
|
|
243
|
+
continue; // Retry creating session
|
|
244
|
+
}
|
|
245
|
+
// Error is not retryable or attempts exhausted
|
|
246
|
+
debugRead("connection error not retryable: %s", error);
|
|
247
|
+
controller.error(error);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
const reader = session.getReader();
|
|
252
|
+
while (true) {
|
|
253
|
+
const { done, value: result } = await reader.read();
|
|
254
|
+
// Update last observed tail if transport exposes it
|
|
255
|
+
try {
|
|
256
|
+
const tail = session.lastObservedTail?.();
|
|
257
|
+
if (tail)
|
|
258
|
+
this._lastObservedTail = tail;
|
|
259
|
+
}
|
|
260
|
+
catch { }
|
|
261
|
+
if (done) {
|
|
262
|
+
reader.releaseLock();
|
|
263
|
+
controller.close();
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
// Check if result is an error
|
|
267
|
+
if (!result.ok) {
|
|
268
|
+
reader.releaseLock();
|
|
269
|
+
const error = result.error;
|
|
270
|
+
// Check if we can retry (track session attempts, not record reads)
|
|
271
|
+
const effectiveMax = Math.max(1, retryConfig.maxAttempts);
|
|
272
|
+
if (isRetryable(error) && attempt < effectiveMax - 1) {
|
|
273
|
+
if (this._nextReadPosition) {
|
|
274
|
+
nextArgs.seq_num = this._nextReadPosition.seq_num;
|
|
275
|
+
// Clear alternative start position fields to avoid conflicting params
|
|
276
|
+
delete nextArgs.timestamp;
|
|
277
|
+
delete nextArgs.tail_offset;
|
|
278
|
+
}
|
|
279
|
+
// Compute planned backoff delay now so we can subtract it from wait budget
|
|
280
|
+
const delay = calculateDelay(attempt, retryConfig.minBaseDelayMillis, retryConfig.maxBaseDelayMillis);
|
|
281
|
+
// Recompute remaining budget from original request each time to avoid double-subtraction
|
|
282
|
+
if (baselineCount !== undefined) {
|
|
283
|
+
nextArgs.count = Math.max(0, baselineCount - this._recordsRead);
|
|
284
|
+
}
|
|
285
|
+
if (baselineBytes !== undefined) {
|
|
286
|
+
nextArgs.bytes = Math.max(0, baselineBytes - this._bytesRead);
|
|
287
|
+
}
|
|
288
|
+
// Adjust wait from original budget based on total elapsed time since start
|
|
289
|
+
if (baselineWait !== undefined) {
|
|
290
|
+
const elapsedSeconds = (performance.now() - startTimeMs) / 1000;
|
|
291
|
+
nextArgs.wait = Math.max(0, Math.floor(baselineWait - (elapsedSeconds + delay / 1000)));
|
|
292
|
+
}
|
|
293
|
+
// Proactively cancel the current transport session before retrying
|
|
294
|
+
try {
|
|
295
|
+
await session.cancel?.("retry");
|
|
296
|
+
}
|
|
297
|
+
catch { }
|
|
298
|
+
// Clear session so a new one gets created on retry
|
|
299
|
+
session = undefined;
|
|
300
|
+
debugRead("will retry after %dms, status=%s", delay, error.status);
|
|
301
|
+
await sleep(delay);
|
|
302
|
+
attempt++;
|
|
303
|
+
break; // Break inner loop to retry
|
|
304
|
+
}
|
|
305
|
+
// Error is not retryable or attempts exhausted
|
|
306
|
+
debugRead("error in retry loop: %s", error);
|
|
307
|
+
controller.error(error);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
// Success: enqueue the record and reset retry attempt counter
|
|
311
|
+
const record = result.value;
|
|
312
|
+
this._nextReadPosition = {
|
|
313
|
+
seq_num: record.seq_num + 1,
|
|
314
|
+
timestamp: record.timestamp,
|
|
315
|
+
};
|
|
316
|
+
this._recordsRead++;
|
|
317
|
+
this._bytesRead += (0, utils_js_1.meteredBytes)(record);
|
|
318
|
+
attempt = 0;
|
|
319
|
+
controller.enqueue(toSDKReadRecord(record));
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
},
|
|
323
|
+
cancel: async (reason) => {
|
|
324
|
+
try {
|
|
325
|
+
await session?.cancel(reason);
|
|
326
|
+
}
|
|
327
|
+
catch (err) {
|
|
328
|
+
// Ignore ERR_INVALID_STATE - stream may already be closed/cancelled
|
|
329
|
+
if (!hasErrorCode(err, "ERR_INVALID_STATE")) {
|
|
330
|
+
throw err;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
async [Symbol.asyncDispose]() {
|
|
337
|
+
await this.cancel("disposed");
|
|
338
|
+
}
|
|
339
|
+
// Polyfill for older browsers / Node.js environments
|
|
340
|
+
[Symbol.asyncIterator]() {
|
|
341
|
+
const proto = ReadableStream.prototype;
|
|
342
|
+
const fn = proto[Symbol.asyncIterator];
|
|
343
|
+
if (typeof fn === "function") {
|
|
344
|
+
try {
|
|
345
|
+
return fn.call(this);
|
|
346
|
+
}
|
|
347
|
+
catch {
|
|
348
|
+
// Native method may throw "Illegal invocation" when called on subclass
|
|
349
|
+
// Fall through to manual implementation
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
const reader = this.getReader();
|
|
353
|
+
return {
|
|
354
|
+
next: async () => {
|
|
355
|
+
const r = await reader.read();
|
|
356
|
+
if (r.done) {
|
|
357
|
+
reader.releaseLock();
|
|
358
|
+
return { done: true, value: undefined };
|
|
359
|
+
}
|
|
360
|
+
return { done: false, value: r.value };
|
|
361
|
+
},
|
|
362
|
+
throw: async (e) => {
|
|
363
|
+
try {
|
|
364
|
+
await reader.cancel(e);
|
|
365
|
+
}
|
|
366
|
+
catch (err) {
|
|
367
|
+
if (!hasErrorCode(err, "ERR_INVALID_STATE"))
|
|
368
|
+
throw err;
|
|
369
|
+
}
|
|
370
|
+
reader.releaseLock();
|
|
371
|
+
return { done: true, value: undefined };
|
|
372
|
+
},
|
|
373
|
+
return: async () => {
|
|
374
|
+
try {
|
|
375
|
+
await reader.cancel("done");
|
|
376
|
+
}
|
|
377
|
+
catch (err) {
|
|
378
|
+
if (!hasErrorCode(err, "ERR_INVALID_STATE"))
|
|
379
|
+
throw err;
|
|
380
|
+
}
|
|
381
|
+
reader.releaseLock();
|
|
382
|
+
return { done: true, value: undefined };
|
|
383
|
+
},
|
|
384
|
+
[Symbol.asyncIterator]() {
|
|
385
|
+
return this;
|
|
386
|
+
},
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
lastObservedTail() {
|
|
390
|
+
return this._lastObservedTail
|
|
391
|
+
? toSDKStreamPosition(this._lastObservedTail)
|
|
392
|
+
: undefined;
|
|
393
|
+
}
|
|
394
|
+
nextReadPosition() {
|
|
395
|
+
return this._nextReadPosition
|
|
396
|
+
? toSDKStreamPosition(this._nextReadPosition)
|
|
397
|
+
: undefined;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
exports.RetryReadSession = RetryReadSession;
|
|
401
|
+
const MIN_MAX_INFLIGHT_BYTES = 1 * 1024 * 1024; // 1 MiB minimum
|
|
402
|
+
const DEFAULT_MAX_INFLIGHT_BYTES = 3 * 1024 * 1024; // 3 MiB default
|
|
403
|
+
class RetryAppendSession {
|
|
404
|
+
generator;
|
|
405
|
+
sessionOptions;
|
|
406
|
+
requestTimeoutMillis;
|
|
407
|
+
maxQueuedBytes;
|
|
408
|
+
maxInflightBatches;
|
|
409
|
+
retryConfig;
|
|
410
|
+
inflight = [];
|
|
411
|
+
capacityWaiters = []; // Queue of waiters for capacity
|
|
412
|
+
session;
|
|
413
|
+
queuedBytes = 0;
|
|
414
|
+
pendingBytes = 0;
|
|
415
|
+
pendingBatches = 0;
|
|
416
|
+
consecutiveFailures = 0;
|
|
417
|
+
currentAttempt = 0;
|
|
418
|
+
pumpPromise;
|
|
419
|
+
pumpStopped = false;
|
|
420
|
+
closing = false;
|
|
421
|
+
pumpWakeup;
|
|
422
|
+
closed = false;
|
|
423
|
+
fatalError;
|
|
424
|
+
_lastAckedPosition;
|
|
425
|
+
acksController;
|
|
426
|
+
readable;
|
|
427
|
+
writable;
|
|
428
|
+
streamName;
|
|
429
|
+
/**
|
|
430
|
+
* If the session has failed, returns the original fatal error that caused
|
|
431
|
+
* the pump to stop. Returns undefined when the session has not failed.
|
|
432
|
+
*/
|
|
433
|
+
failureCause() {
|
|
434
|
+
return this.fatalError;
|
|
435
|
+
}
|
|
436
|
+
constructor(generator, sessionOptions, config, streamName) {
|
|
437
|
+
this.generator = generator;
|
|
438
|
+
this.sessionOptions = sessionOptions;
|
|
439
|
+
this.streamName = streamName ?? "unknown";
|
|
440
|
+
this.retryConfig = {
|
|
441
|
+
...exports.DEFAULT_RETRY_CONFIG,
|
|
442
|
+
...config,
|
|
443
|
+
};
|
|
444
|
+
this.requestTimeoutMillis = this.retryConfig.requestTimeoutMillis;
|
|
445
|
+
// Clamp maxInflightBytes to at least 1 MiB
|
|
446
|
+
this.maxQueuedBytes = Math.max(MIN_MAX_INFLIGHT_BYTES, this.sessionOptions?.maxInflightBytes ?? DEFAULT_MAX_INFLIGHT_BYTES);
|
|
447
|
+
// Clamp maxInflightBatches to at least 1 if set
|
|
448
|
+
this.maxInflightBatches =
|
|
449
|
+
this.sessionOptions?.maxInflightBatches !== undefined
|
|
450
|
+
? Math.max(1, this.sessionOptions.maxInflightBatches)
|
|
451
|
+
: undefined;
|
|
452
|
+
this.readable = new ReadableStream({
|
|
453
|
+
start: (controller) => {
|
|
454
|
+
this.acksController = controller;
|
|
455
|
+
},
|
|
456
|
+
});
|
|
457
|
+
this.writable = new WritableStream({
|
|
458
|
+
write: async (chunk) => {
|
|
459
|
+
if (this.closed || this.closing) {
|
|
460
|
+
throw new error_js_1.S2Error({ message: "AppendSession is closed" });
|
|
461
|
+
}
|
|
462
|
+
// chunk is already AppendInput with meteredBytes computed
|
|
463
|
+
// Reuse submit() to leverage shared backpressure/pump logic.
|
|
464
|
+
const ticket = await this.submit(chunk);
|
|
465
|
+
// Writable stream API only needs enqueue semantics, so drop ack but
|
|
466
|
+
// suppress rejection noise (pump surfaces fatal errors elsewhere).
|
|
467
|
+
ticket.ack().catch(() => {
|
|
468
|
+
// Intentionally ignored.
|
|
469
|
+
});
|
|
470
|
+
},
|
|
471
|
+
close: async () => {
|
|
472
|
+
await this.close();
|
|
473
|
+
},
|
|
474
|
+
abort: async (reason) => {
|
|
475
|
+
const error = (0, error_js_1.abortedError)(`AppendSession aborted: ${reason}`);
|
|
476
|
+
await this.abort(error);
|
|
477
|
+
},
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
static async create(generator, sessionOptions, config, streamName) {
|
|
481
|
+
return new RetryAppendSession(generator, sessionOptions, config, streamName);
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Wait for capacity to be available for the given batch size.
|
|
485
|
+
* Call this before submit() to apply backpressure based on maxInflightBatches/maxInflightBytes.
|
|
486
|
+
*
|
|
487
|
+
* @param bytes - Size in bytes (use meteredBytes() to calculate)
|
|
488
|
+
* @param numBatches - Number of batches (default: 1)
|
|
489
|
+
* @returns Promise that resolves when capacity is available
|
|
490
|
+
*/
|
|
491
|
+
async waitForCapacity(bytes, numBatches = 1) {
|
|
492
|
+
debugSession("[%s] [CAPACITY] checking for %d bytes, %d batches: queuedBytes=%d, pendingBytes=%d, maxQueuedBytes=%d, inflight=%d, pendingBatches=%d, maxInflightBatches=%s", this.streamName, bytes, numBatches, this.queuedBytes, this.pendingBytes, this.maxQueuedBytes, this.inflight.length, this.pendingBatches, this.maxInflightBatches ?? "unlimited");
|
|
493
|
+
// Check if we have capacity
|
|
494
|
+
while (true) {
|
|
495
|
+
// Check for fatal error before adding to pendingBytes
|
|
496
|
+
if (this.fatalError) {
|
|
497
|
+
debugSession("[%s] [CAPACITY] fatal error detected, rejecting: %s", this.streamName, this.fatalError.message);
|
|
498
|
+
throw this.fatalError;
|
|
499
|
+
}
|
|
500
|
+
// Byte-based gating
|
|
501
|
+
if (this.queuedBytes + this.pendingBytes + bytes <= this.maxQueuedBytes) {
|
|
502
|
+
// Batch-based gating (if configured)
|
|
503
|
+
if (this.maxInflightBatches === undefined ||
|
|
504
|
+
this.inflight.length + this.pendingBatches + numBatches <=
|
|
505
|
+
this.maxInflightBatches) {
|
|
506
|
+
debugSession("[%s] [CAPACITY] capacity available, adding %d to pendingBytes and %d to pendingBatches", this.streamName, bytes, numBatches);
|
|
507
|
+
this.pendingBytes += bytes;
|
|
508
|
+
this.pendingBatches += numBatches;
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
// No capacity - wait in queue
|
|
513
|
+
debugSession("[%s] [CAPACITY] no capacity, waiting for release", this.streamName);
|
|
514
|
+
await new Promise((resolve) => {
|
|
515
|
+
this.capacityWaiters.push({
|
|
516
|
+
resolve,
|
|
517
|
+
bytes,
|
|
518
|
+
batches: numBatches,
|
|
519
|
+
});
|
|
520
|
+
});
|
|
521
|
+
debugSession("[%s] [CAPACITY] woke up, rechecking", this.streamName);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Submit an append request.
|
|
526
|
+
* Returns a promise that resolves to a submit ticket once the batch is enqueued (has capacity).
|
|
527
|
+
* The ticket's ack() can be awaited to get the AppendAck once the batch is durable.
|
|
528
|
+
* This method applies backpressure and will block if capacity limits are reached.
|
|
529
|
+
*/
|
|
530
|
+
async submit(input) {
|
|
531
|
+
if (this.closed || this.closing) {
|
|
532
|
+
return Promise.reject(new error_js_1.S2Error({ message: "AppendSession is closed" }));
|
|
533
|
+
}
|
|
534
|
+
// Use cached metered size from AppendInput
|
|
535
|
+
const batchMeteredSize = input.meteredBytes;
|
|
536
|
+
// This needs to happen in the sync path.
|
|
537
|
+
this.ensurePump();
|
|
538
|
+
// Wait for capacity (this is where backpressure is applied - outer promise resolves when enqueued)
|
|
539
|
+
await this.waitForCapacity(batchMeteredSize, 1);
|
|
540
|
+
// Move reserved bytes and batches to queued accounting before submission
|
|
541
|
+
this.pendingBytes = Math.max(0, this.pendingBytes - batchMeteredSize);
|
|
542
|
+
this.pendingBatches = Math.max(0, this.pendingBatches - 1);
|
|
543
|
+
// Create the inner promise that resolves when durable
|
|
544
|
+
const innerPromise = this.submitInternal(input, batchMeteredSize).then((result) => {
|
|
545
|
+
if (result.ok) {
|
|
546
|
+
return result.value;
|
|
547
|
+
}
|
|
548
|
+
else {
|
|
549
|
+
throw result.error;
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
// Prevent early rejections from surfacing as unhandled when callers delay ack()
|
|
553
|
+
innerPromise.catch(() => { });
|
|
554
|
+
// Return ticket immediately (outer promise has resolved via waitForCapacity)
|
|
555
|
+
return new types_js_1.BatchSubmitTicket(innerPromise, batchMeteredSize, input.records.length);
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* Internal submit that returns discriminated union.
|
|
559
|
+
* Creates inflight entry and starts pump if needed.
|
|
560
|
+
*/
|
|
561
|
+
submitInternal(input, batchMeteredSize) {
|
|
562
|
+
// Check for fatal error (e.g., from abort())
|
|
563
|
+
if (this.fatalError) {
|
|
564
|
+
debugSession("[%s] [SUBMIT] rejecting due to fatal error: %s", this.streamName, this.fatalError.message);
|
|
565
|
+
return Promise.resolve((0, result_js_1.err)(this.fatalError));
|
|
566
|
+
}
|
|
567
|
+
// Create promise for submit() callers
|
|
568
|
+
return new Promise((resolve) => {
|
|
569
|
+
// Create inflight entry (innerPromise will be set when pump processes it)
|
|
570
|
+
const entry = {
|
|
571
|
+
input,
|
|
572
|
+
expectedCount: input.records.length,
|
|
573
|
+
innerPromise: new Promise(() => { }), // Never-resolving placeholder
|
|
574
|
+
maybeResolve: resolve,
|
|
575
|
+
needsSubmit: true, // Mark for pump to submit
|
|
576
|
+
};
|
|
577
|
+
debugSession("[%s] [SUBMIT] enqueueing %d records (%d bytes), match_seq_num=%s: inflight=%d->%d, queuedBytes=%d->%d", this.streamName, input.records.length, batchMeteredSize, input.matchSeqNum ?? "none", this.inflight.length, this.inflight.length + 1, this.queuedBytes, this.queuedBytes + batchMeteredSize);
|
|
578
|
+
this.inflight.push(entry);
|
|
579
|
+
this.queuedBytes += batchMeteredSize;
|
|
580
|
+
// Wake pump if it's sleeping
|
|
581
|
+
if (this.pumpWakeup) {
|
|
582
|
+
this.pumpWakeup();
|
|
583
|
+
}
|
|
584
|
+
// Start pump if not already running
|
|
585
|
+
this.ensurePump();
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Release capacity and wake waiter if present.
|
|
590
|
+
*/
|
|
591
|
+
releaseCapacity(bytes) {
|
|
592
|
+
debugSession("[%s] [CAPACITY] releasing %d bytes: queuedBytes=%d->%d, pendingBytes=%d->%d, pendingBatches=%d, numWaiters=%d", this.streamName, bytes, this.queuedBytes, this.queuedBytes - bytes, this.pendingBytes, Math.max(0, this.pendingBytes - bytes), this.pendingBatches, this.capacityWaiters.length);
|
|
593
|
+
this.queuedBytes -= bytes;
|
|
594
|
+
this.pendingBytes = Math.max(0, this.pendingBytes - bytes);
|
|
595
|
+
this.wakeCapacityWaiters();
|
|
596
|
+
}
|
|
597
|
+
wakeCapacityWaiters() {
|
|
598
|
+
if (this.capacityWaiters.length === 0) {
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
let availableBytes = Math.max(0, this.maxQueuedBytes - (this.queuedBytes + this.pendingBytes));
|
|
602
|
+
let availableBatches = this.maxInflightBatches === undefined
|
|
603
|
+
? Number.POSITIVE_INFINITY
|
|
604
|
+
: Math.max(0, this.maxInflightBatches -
|
|
605
|
+
(this.inflight.length + this.pendingBatches));
|
|
606
|
+
while (this.capacityWaiters.length > 0) {
|
|
607
|
+
const next = this.capacityWaiters[0];
|
|
608
|
+
const needsBytes = next.bytes;
|
|
609
|
+
const needsBatches = next.batches;
|
|
610
|
+
const hasBatchCapacity = this.maxInflightBatches === undefined ||
|
|
611
|
+
needsBatches <= availableBatches;
|
|
612
|
+
if (needsBytes <= availableBytes && hasBatchCapacity) {
|
|
613
|
+
this.capacityWaiters.shift();
|
|
614
|
+
availableBytes -= needsBytes;
|
|
615
|
+
if (this.maxInflightBatches !== undefined) {
|
|
616
|
+
availableBatches -= needsBatches;
|
|
617
|
+
}
|
|
618
|
+
debugSession("[%s] [CAPACITY] waking waiter (bytes=%d, batches=%d)", this.streamName, needsBytes, needsBatches);
|
|
619
|
+
next.resolve();
|
|
620
|
+
continue;
|
|
621
|
+
}
|
|
622
|
+
// Not enough capacity for the next waiter yet - keep them queued.
|
|
623
|
+
break;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Ensure pump loop is running.
|
|
628
|
+
*/
|
|
629
|
+
ensurePump() {
|
|
630
|
+
if (this.pumpPromise || this.pumpStopped) {
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
this.pumpPromise = this.runPump().catch((e) => {
|
|
634
|
+
debugSession("[%s] pump crashed unexpectedly: %s", this.streamName, e);
|
|
635
|
+
// This should never happen - pump handles all errors internally
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Main pump loop: processes inflight queue, handles acks, retries, and recovery.
|
|
640
|
+
*/
|
|
641
|
+
async runPump() {
|
|
642
|
+
debugSession("[%s] pump started", this.streamName);
|
|
643
|
+
while (true) {
|
|
644
|
+
debugSession("[%s] [PUMP] loop: inflight=%d, queuedBytes=%d, pendingBytes=%d, pendingBatches=%d, closing=%s, pumpStopped=%s", this.streamName, this.inflight.length, this.queuedBytes, this.pendingBytes, this.pendingBatches, this.closing, this.pumpStopped);
|
|
645
|
+
// Check if we should stop
|
|
646
|
+
if (this.pumpStopped) {
|
|
647
|
+
debugSession("[%s] [PUMP] stopped by flag", this.streamName);
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
// If closing and queue is empty, stop
|
|
651
|
+
// BUT: if there are capacity waiters, they might add to inflight, so keep running
|
|
652
|
+
if (this.closing &&
|
|
653
|
+
this.inflight.length === 0 &&
|
|
654
|
+
this.capacityWaiters.length === 0) {
|
|
655
|
+
debugSession("[%s] [PUMP] closing and queue empty, stopping", this.streamName);
|
|
656
|
+
this.pumpStopped = true;
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
// If no entries, sleep and continue
|
|
660
|
+
if (this.inflight.length === 0) {
|
|
661
|
+
debugSession("[%s] [PUMP] no entries, parking until wakeup", this.streamName);
|
|
662
|
+
await new Promise((resolve) => {
|
|
663
|
+
this.pumpWakeup = resolve;
|
|
664
|
+
});
|
|
665
|
+
this.pumpWakeup = undefined;
|
|
666
|
+
continue;
|
|
667
|
+
}
|
|
668
|
+
// Get head entry (we know it exists because we checked length above)
|
|
669
|
+
const head = this.inflight[0];
|
|
670
|
+
debugSession("[%s] [PUMP] processing head: expectedCount=%d, meteredBytes=%d, match_seq_num=%s", this.streamName, head.expectedCount, head.input.meteredBytes, head.input.matchSeqNum ?? "none");
|
|
671
|
+
// Ensure session exists
|
|
672
|
+
debugSession("[%s] [PUMP] ensuring session exists", this.streamName);
|
|
673
|
+
await this.ensureSession();
|
|
674
|
+
if (!this.session) {
|
|
675
|
+
// Session creation failed - will retry
|
|
676
|
+
this.consecutiveFailures++;
|
|
677
|
+
const delay = calculateDelay(this.consecutiveFailures - 1, this.retryConfig.minBaseDelayMillis, this.retryConfig.maxBaseDelayMillis);
|
|
678
|
+
debugSession("[%s] [PUMP] session creation failed, backing off for %dms", this.streamName, delay);
|
|
679
|
+
await sleep(delay);
|
|
680
|
+
continue;
|
|
681
|
+
}
|
|
682
|
+
// Submit ALL entries that need submitting (enables HTTP/2 pipelining for S2S)
|
|
683
|
+
for (const entry of this.inflight) {
|
|
684
|
+
if (!entry.innerPromise || entry.needsSubmit) {
|
|
685
|
+
debugSession("[%s] [PUMP] submitting entry to inner session (%d records, %d bytes, match_seq_num=%s)", this.streamName, entry.expectedCount, entry.input.meteredBytes, entry.input.matchSeqNum ?? "none");
|
|
686
|
+
const attemptStarted = performance.now();
|
|
687
|
+
entry.attemptStartedMonotonicMs = attemptStarted;
|
|
688
|
+
entry.innerPromise = this.session.submit(entry.input);
|
|
689
|
+
delete entry.needsSubmit;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
// Wait for head with timeout
|
|
693
|
+
debugSession("[%s] [PUMP] waiting for head result", this.streamName);
|
|
694
|
+
const result = await this.waitForHead(head);
|
|
695
|
+
debugSession("[%s] [PUMP] got result: kind=%s", this.streamName, result.kind);
|
|
696
|
+
// Convert result to AppendResult (timeout becomes retryable error)
|
|
697
|
+
let appendResult;
|
|
698
|
+
if (result.kind === "timeout") {
|
|
699
|
+
// Ack timeout - convert to retryable error that flows through retry logic
|
|
700
|
+
const attemptElapsed = head.attemptStartedMonotonicMs != null
|
|
701
|
+
? Math.round(performance.now() - head.attemptStartedMonotonicMs)
|
|
702
|
+
: undefined;
|
|
703
|
+
const error = new error_js_1.S2Error({
|
|
704
|
+
message: `Request timeout after ${attemptElapsed ?? "unknown"}ms (${head.expectedCount} records, ${head.input.meteredBytes} bytes)`,
|
|
705
|
+
status: 408,
|
|
706
|
+
code: "REQUEST_TIMEOUT",
|
|
707
|
+
origin: "sdk",
|
|
708
|
+
});
|
|
709
|
+
debugSession("[%s] ack timeout for head entry: %s", this.streamName, error.message);
|
|
710
|
+
appendResult = (0, result_js_1.err)(error);
|
|
711
|
+
}
|
|
712
|
+
else {
|
|
713
|
+
// Promise settled
|
|
714
|
+
appendResult = result.value;
|
|
715
|
+
}
|
|
716
|
+
if (appendResult.ok) {
|
|
717
|
+
// Success!
|
|
718
|
+
const ack = appendResult.value;
|
|
719
|
+
debugSession("[%s] [PUMP] success, got ack: seq_num=%d-%d", this.streamName, ack.start.seqNum, ack.end.seqNum);
|
|
720
|
+
// Invariant check: ack count matches batch count
|
|
721
|
+
const ackCount = ack.end.seqNum - ack.start.seqNum;
|
|
722
|
+
if (ackCount !== head.expectedCount) {
|
|
723
|
+
const error = (0, error_js_1.invariantViolation)(`Ack count mismatch: expected ${head.expectedCount}, got ${ackCount}`);
|
|
724
|
+
debugSession("[%s] invariant violation: %s", this.streamName, error.message);
|
|
725
|
+
await this.abort(error);
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
// Invariant check: sequence numbers must be strictly increasing
|
|
729
|
+
if (this._lastAckedPosition) {
|
|
730
|
+
const prevEnd = this._lastAckedPosition.end.seqNum;
|
|
731
|
+
const currentEnd = ack.end.seqNum;
|
|
732
|
+
if (currentEnd <= prevEnd) {
|
|
733
|
+
const error = (0, error_js_1.invariantViolation)(`Sequence number not strictly increasing: previous=${prevEnd}, current=${currentEnd}`);
|
|
734
|
+
debugSession("[%s] invariant violation: %s", this.streamName, error.message);
|
|
735
|
+
await this.abort(error);
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
// Update last acked position
|
|
740
|
+
this._lastAckedPosition = ack;
|
|
741
|
+
// Resolve submit() caller if present
|
|
742
|
+
if (head.maybeResolve) {
|
|
743
|
+
head.maybeResolve((0, result_js_1.ok)(ack));
|
|
744
|
+
}
|
|
745
|
+
// Emit to readable stream
|
|
746
|
+
try {
|
|
747
|
+
this.acksController?.enqueue(ack);
|
|
748
|
+
}
|
|
749
|
+
catch (e) {
|
|
750
|
+
debugSession("[%s] failed to enqueue ack: %s", this.streamName, e);
|
|
751
|
+
}
|
|
752
|
+
// Remove from inflight and release capacity
|
|
753
|
+
debugSession("[%s] [PUMP] removing head from inflight, releasing %d bytes", this.streamName, head.input.meteredBytes);
|
|
754
|
+
this.inflight.shift();
|
|
755
|
+
this.releaseCapacity(head.input.meteredBytes);
|
|
756
|
+
// Reset consecutive failures on success
|
|
757
|
+
this.consecutiveFailures = 0;
|
|
758
|
+
this.currentAttempt = 0;
|
|
759
|
+
}
|
|
760
|
+
else {
|
|
761
|
+
// Error result
|
|
762
|
+
const error = appendResult.error;
|
|
763
|
+
debugSession("[%s] [PUMP] error: status=%s, message=%s", this.streamName, error.status, error.message);
|
|
764
|
+
// Check if retryable
|
|
765
|
+
if (!isRetryable(error)) {
|
|
766
|
+
debugSession("[%s] error not retryable, aborting", this.streamName);
|
|
767
|
+
await this.abort(error);
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
// Check policy compliance
|
|
771
|
+
if (this.retryConfig.appendRetryPolicy === "noSideEffects" &&
|
|
772
|
+
!this.isIdempotent(head)) {
|
|
773
|
+
debugSession("[%s] error not policy-compliant (noSideEffects), aborting", this.streamName);
|
|
774
|
+
await this.abort(error);
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
// Check max attempts (total attempts include initial; retries = max - 1)
|
|
778
|
+
const effectiveMax = Math.max(1, this.retryConfig.maxAttempts);
|
|
779
|
+
const allowedRetries = effectiveMax - 1;
|
|
780
|
+
if (this.currentAttempt >= allowedRetries) {
|
|
781
|
+
debugSession("[%s] max attempts reached (%d), aborting", this.streamName, effectiveMax);
|
|
782
|
+
const wrappedError = new error_js_1.S2Error({
|
|
783
|
+
message: `Max attempts (${effectiveMax}) exhausted: ${error.message}`,
|
|
784
|
+
status: error.status,
|
|
785
|
+
code: error.code,
|
|
786
|
+
});
|
|
787
|
+
await this.abort(wrappedError);
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
// Perform recovery
|
|
791
|
+
this.consecutiveFailures++;
|
|
792
|
+
this.currentAttempt++;
|
|
793
|
+
debugSession("[%s] performing recovery (retry %d/%d)", this.streamName, this.currentAttempt, allowedRetries);
|
|
794
|
+
await this.recover();
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* Wait for head entry's innerPromise with timeout.
|
|
800
|
+
* Returns either the settled result or a timeout indicator.
|
|
801
|
+
*
|
|
802
|
+
* Per-attempt ack timeout semantics:
|
|
803
|
+
* - The deadline is computed from the current attempt's start time using a
|
|
804
|
+
* monotonic clock (performance.now) to avoid issues with wall clock adjustments.
|
|
805
|
+
* - Each retry gets a fresh timeout window (attemptStartedMonotonicMs is reset
|
|
806
|
+
* during recovery).
|
|
807
|
+
* - If attempt start is missing (for backward compatibility), we measure
|
|
808
|
+
* from "now" with the full timeout window.
|
|
809
|
+
*/
|
|
810
|
+
async waitForHead(head) {
|
|
811
|
+
const attemptStart = head.attemptStartedMonotonicMs ?? performance.now();
|
|
812
|
+
const deadline = attemptStart + this.requestTimeoutMillis;
|
|
813
|
+
const remaining = Math.max(0, deadline - performance.now());
|
|
814
|
+
let timer;
|
|
815
|
+
const timeoutP = new Promise((resolve) => {
|
|
816
|
+
timer = setTimeout(() => resolve({ kind: "timeout" }), remaining);
|
|
817
|
+
});
|
|
818
|
+
const settledP = head.innerPromise.then((result) => ({
|
|
819
|
+
kind: "settled",
|
|
820
|
+
value: result,
|
|
821
|
+
}));
|
|
822
|
+
try {
|
|
823
|
+
return await Promise.race([settledP, timeoutP]);
|
|
824
|
+
}
|
|
825
|
+
finally {
|
|
826
|
+
if (timer)
|
|
827
|
+
clearTimeout(timer);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
/**
|
|
831
|
+
* Recover from transient error: recreate session and resubmit all inflight entries.
|
|
832
|
+
*/
|
|
833
|
+
async recover() {
|
|
834
|
+
debugSession("[%s] starting recovery", this.streamName);
|
|
835
|
+
// Calculate backoff delay
|
|
836
|
+
const delay = calculateDelay(this.consecutiveFailures - 1, this.retryConfig.minBaseDelayMillis, this.retryConfig.maxBaseDelayMillis);
|
|
837
|
+
debugSession("[%s] backing off for %dms", this.streamName, delay);
|
|
838
|
+
await sleep(delay);
|
|
839
|
+
// Teardown old session
|
|
840
|
+
if (this.session) {
|
|
841
|
+
try {
|
|
842
|
+
const closeResult = await this.session.close();
|
|
843
|
+
if (!closeResult.ok) {
|
|
844
|
+
debugSession("[%s] error closing old session during recovery: %s", this.streamName, closeResult.error.message);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
catch (e) {
|
|
848
|
+
debugSession("[%s] exception closing old session: %s", this.streamName, e);
|
|
849
|
+
}
|
|
850
|
+
this.session = undefined;
|
|
851
|
+
}
|
|
852
|
+
// Create new session
|
|
853
|
+
await this.ensureSession();
|
|
854
|
+
if (!this.session) {
|
|
855
|
+
debugSession("[%s] failed to create new session during recovery", this.streamName);
|
|
856
|
+
// Will retry on next pump iteration
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
// Store session in local variable to help TypeScript type narrowing
|
|
860
|
+
const session = this.session;
|
|
861
|
+
// Resubmit all inflight entries (replace their innerPromise and reset attempt start)
|
|
862
|
+
debugSession("[%s] resubmitting %d inflight entries", this.streamName, this.inflight.length);
|
|
863
|
+
for (const entry of this.inflight) {
|
|
864
|
+
// Attach .catch to superseded promise to avoid unhandled rejection
|
|
865
|
+
entry.innerPromise.catch(() => { });
|
|
866
|
+
// Create new promise from new session
|
|
867
|
+
const attemptStarted = performance.now();
|
|
868
|
+
entry.attemptStartedMonotonicMs = attemptStarted;
|
|
869
|
+
entry.innerPromise = session.submit(entry.input);
|
|
870
|
+
debugSession("[%s] resubmitted entry (%d records, %d bytes, match_seq_num=%s)", this.streamName, entry.expectedCount, entry.input.meteredBytes, entry.input.matchSeqNum ?? "none");
|
|
871
|
+
}
|
|
872
|
+
debugSession("[%s] recovery complete", this.streamName);
|
|
873
|
+
}
|
|
874
|
+
/**
|
|
875
|
+
* Check if append can be retried under noSideEffects policy.
|
|
876
|
+
* For appends, idempotency requires match_seq_num.
|
|
877
|
+
*/
|
|
878
|
+
isIdempotent(entry) {
|
|
879
|
+
return entry.input.matchSeqNum !== undefined;
|
|
880
|
+
}
|
|
881
|
+
/**
|
|
882
|
+
* Ensure session exists, creating it if necessary.
|
|
883
|
+
*/
|
|
884
|
+
async ensureSession() {
|
|
885
|
+
if (this.session) {
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
try {
|
|
889
|
+
debugSession("[%s] creating new transport session", this.streamName);
|
|
890
|
+
this.session = await this.generator(this.sessionOptions);
|
|
891
|
+
debugSession("[%s] transport session created", this.streamName);
|
|
892
|
+
}
|
|
893
|
+
catch (e) {
|
|
894
|
+
const error = (0, error_js_1.s2Error)(e);
|
|
895
|
+
debugSession("[%s] failed to create session: %s", this.streamName, error.message);
|
|
896
|
+
// Don't set this.session - will retry later
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
900
|
+
* Abort the session with a fatal error.
|
|
901
|
+
*/
|
|
902
|
+
async abort(error) {
|
|
903
|
+
if (this.pumpStopped) {
|
|
904
|
+
return; // Already aborted
|
|
905
|
+
}
|
|
906
|
+
debugSession("[%s] aborting session: %s", this.streamName, error.message);
|
|
907
|
+
this.fatalError = error;
|
|
908
|
+
this.pumpStopped = true;
|
|
909
|
+
// Resolve all inflight entries with error
|
|
910
|
+
debugSession("[%s] rejecting %d inflight entries", this.streamName, this.inflight.length);
|
|
911
|
+
for (const entry of this.inflight) {
|
|
912
|
+
if (entry.maybeResolve) {
|
|
913
|
+
entry.maybeResolve((0, result_js_1.err)(error));
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
this.inflight.length = 0;
|
|
917
|
+
this.queuedBytes = 0;
|
|
918
|
+
this.pendingBytes = 0;
|
|
919
|
+
this.pendingBatches = 0;
|
|
920
|
+
// Error the readable stream
|
|
921
|
+
try {
|
|
922
|
+
this.acksController?.error(error);
|
|
923
|
+
}
|
|
924
|
+
catch (e) {
|
|
925
|
+
debugSession("[%s] failed to error acks controller: %s", this.streamName, e);
|
|
926
|
+
}
|
|
927
|
+
// Wake all capacity waiters to unblock any pending writers
|
|
928
|
+
for (const waiter of this.capacityWaiters) {
|
|
929
|
+
waiter.resolve();
|
|
930
|
+
}
|
|
931
|
+
this.capacityWaiters = [];
|
|
932
|
+
// Close inner session
|
|
933
|
+
if (this.session) {
|
|
934
|
+
debugSession("[%s] closing inner session", this.streamName);
|
|
935
|
+
try {
|
|
936
|
+
await this.session.close();
|
|
937
|
+
}
|
|
938
|
+
catch (e) {
|
|
939
|
+
debugSession("[%s] error closing session during abort: %s", this.streamName, e);
|
|
940
|
+
}
|
|
941
|
+
this.session = undefined;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
/**
|
|
945
|
+
* Close the append session.
|
|
946
|
+
* Waits for all pending appends to complete before resolving.
|
|
947
|
+
* Does not interrupt recovery - allows it to complete.
|
|
948
|
+
*/
|
|
949
|
+
async close() {
|
|
950
|
+
if (this.closed) {
|
|
951
|
+
if (this.fatalError) {
|
|
952
|
+
throw this.fatalError;
|
|
953
|
+
}
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
debugSession("[%s] close requested", this.streamName);
|
|
957
|
+
this.closing = true;
|
|
958
|
+
// Wake pump if it's sleeping so it can check closing flag
|
|
959
|
+
if (this.pumpWakeup) {
|
|
960
|
+
this.pumpWakeup();
|
|
961
|
+
}
|
|
962
|
+
// Wait for pump to stop (drains inflight queue, including through recovery)
|
|
963
|
+
if (this.pumpPromise) {
|
|
964
|
+
debugSession("[%s] [CLOSE] awaiting pump to drain inflight queue", this.streamName);
|
|
965
|
+
await this.pumpPromise;
|
|
966
|
+
}
|
|
967
|
+
// Close inner session
|
|
968
|
+
if (this.session) {
|
|
969
|
+
try {
|
|
970
|
+
const result = await this.session.close();
|
|
971
|
+
if (!result.ok) {
|
|
972
|
+
debugSession("[%s] error closing inner session: %s", this.streamName, result.error.message);
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
catch (e) {
|
|
976
|
+
debugSession("[%s] exception closing inner session: %s", this.streamName, e);
|
|
977
|
+
}
|
|
978
|
+
this.session = undefined;
|
|
979
|
+
}
|
|
980
|
+
// Close readable stream
|
|
981
|
+
try {
|
|
982
|
+
this.acksController?.close();
|
|
983
|
+
}
|
|
984
|
+
catch (e) {
|
|
985
|
+
debugSession("[%s] error closing acks controller: %s", this.streamName, e);
|
|
986
|
+
}
|
|
987
|
+
this.closed = true;
|
|
988
|
+
// If fatal error occurred, throw it
|
|
989
|
+
if (this.fatalError) {
|
|
990
|
+
throw this.fatalError;
|
|
991
|
+
}
|
|
992
|
+
debugSession("[%s] close complete", this.streamName);
|
|
993
|
+
}
|
|
994
|
+
async [Symbol.asyncDispose]() {
|
|
995
|
+
await this.close();
|
|
996
|
+
}
|
|
997
|
+
/**
|
|
998
|
+
* Get a stream of acknowledgements for appends.
|
|
999
|
+
*/
|
|
1000
|
+
acks() {
|
|
1001
|
+
return this.readable;
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
1004
|
+
* Get the last acknowledged position.
|
|
1005
|
+
*/
|
|
1006
|
+
lastAckedPosition() {
|
|
1007
|
+
return this._lastAckedPosition;
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
exports.RetryAppendSession = RetryAppendSession;
|
|
1011
|
+
//# sourceMappingURL=retry.js.map
|