@voxgig/sdkgen-langpack 0.0.1
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/.sdk/model/target/dart.aon +63 -0
- package/.sdk/model/target/haskell.aon +72 -0
- package/.sdk/model/target/lean.aon +91 -0
- package/.sdk/src/cmp/dart/Config_dart.ts +248 -0
- package/.sdk/src/cmp/dart/EntityBase_dart.ts +34 -0
- package/.sdk/src/cmp/dart/EntityOperation_dart.ts +43 -0
- package/.sdk/src/cmp/dart/EntityTypes_dart.ts +207 -0
- package/.sdk/src/cmp/dart/Entity_dart.ts +87 -0
- package/.sdk/src/cmp/dart/Gitignore_dart.ts +34 -0
- package/.sdk/src/cmp/dart/MainEntity_dart.ts +33 -0
- package/.sdk/src/cmp/dart/Main_dart.ts +178 -0
- package/.sdk/src/cmp/dart/Package_dart.ts +82 -0
- package/.sdk/src/cmp/dart/ReadmeEntity_dart.ts +177 -0
- package/.sdk/src/cmp/dart/ReadmeExamplesTest_dart.ts +238 -0
- package/.sdk/src/cmp/dart/ReadmeExplanation_dart.ts +53 -0
- package/.sdk/src/cmp/dart/ReadmeHowto_dart.ts +152 -0
- package/.sdk/src/cmp/dart/ReadmeInstall_dart.ts +59 -0
- package/.sdk/src/cmp/dart/ReadmeIntro_dart.ts +65 -0
- package/.sdk/src/cmp/dart/ReadmeModel_dart.ts +151 -0
- package/.sdk/src/cmp/dart/ReadmeOptions_dart.ts +63 -0
- package/.sdk/src/cmp/dart/ReadmeQuick_dart.ts +225 -0
- package/.sdk/src/cmp/dart/ReadmeRef_dart.ts +397 -0
- package/.sdk/src/cmp/dart/ReadmeTopHowto_dart.ts +24 -0
- package/.sdk/src/cmp/dart/ReadmeTopQuick_dart.ts +106 -0
- package/.sdk/src/cmp/dart/ReadmeTopTest_dart.ts +78 -0
- package/.sdk/src/cmp/dart/SdkError_dart.ts +42 -0
- package/.sdk/src/cmp/dart/TestDirect_dart.ts +516 -0
- package/.sdk/src/cmp/dart/TestEntity_dart.ts +628 -0
- package/.sdk/src/cmp/dart/Test_dart.ts +138 -0
- package/.sdk/src/cmp/dart/fragment/Config.data.fragment.dart +98 -0
- package/.sdk/src/cmp/dart/fragment/Config.fragment.dart +70 -0
- package/.sdk/src/cmp/dart/fragment/Direct.test.fragment.dart +25 -0
- package/.sdk/src/cmp/dart/fragment/Entity.fragment.dart +29 -0
- package/.sdk/src/cmp/dart/fragment/Entity.test.fragment.dart +28 -0
- package/.sdk/src/cmp/dart/fragment/EntityBase.fragment.dart +302 -0
- package/.sdk/src/cmp/dart/fragment/EntityCreateOp.fragment.dart +98 -0
- package/.sdk/src/cmp/dart/fragment/EntityListOp.fragment.dart +90 -0
- package/.sdk/src/cmp/dart/fragment/EntityLoadOp.fragment.dart +102 -0
- package/.sdk/src/cmp/dart/fragment/EntityRemoveOp.fragment.dart +108 -0
- package/.sdk/src/cmp/dart/fragment/EntityUpdateOp.fragment.dart +102 -0
- package/.sdk/src/cmp/dart/fragment/Main.fragment.dart +339 -0
- package/.sdk/src/cmp/dart/fragment/SdkError.fragment.dart +26 -0
- package/.sdk/src/cmp/dart/tsconfig.json +15 -0
- package/.sdk/src/cmp/dart/utility_dart.ts +155 -0
- package/.sdk/src/cmp/haskell/Config_haskell.ts +98 -0
- package/.sdk/src/cmp/haskell/Entity_haskell.ts +13 -0
- package/.sdk/src/cmp/haskell/Gitignore_haskell.ts +27 -0
- package/.sdk/src/cmp/haskell/Main_haskell.ts +123 -0
- package/.sdk/src/cmp/haskell/Package_haskell.ts +60 -0
- package/.sdk/src/cmp/haskell/ReadmeEntity_haskell.ts +186 -0
- package/.sdk/src/cmp/haskell/ReadmeExamplesTest_haskell.ts +135 -0
- package/.sdk/src/cmp/haskell/ReadmeExplanation_haskell.ts +48 -0
- package/.sdk/src/cmp/haskell/ReadmeHowto_haskell.ts +168 -0
- package/.sdk/src/cmp/haskell/ReadmeInstall_haskell.ts +51 -0
- package/.sdk/src/cmp/haskell/ReadmeIntro_haskell.ts +67 -0
- package/.sdk/src/cmp/haskell/ReadmeModel_haskell.ts +162 -0
- package/.sdk/src/cmp/haskell/ReadmeOptions_haskell.ts +74 -0
- package/.sdk/src/cmp/haskell/ReadmeQuick_haskell.ts +241 -0
- package/.sdk/src/cmp/haskell/ReadmeRef_haskell.ts +408 -0
- package/.sdk/src/cmp/haskell/ReadmeTopHowto_haskell.ts +31 -0
- package/.sdk/src/cmp/haskell/ReadmeTopQuick_haskell.ts +104 -0
- package/.sdk/src/cmp/haskell/ReadmeTopTest_haskell.ts +80 -0
- package/.sdk/src/cmp/haskell/Test_haskell.ts +297 -0
- package/.sdk/src/cmp/haskell/tsconfig.json +15 -0
- package/.sdk/src/cmp/haskell/utility_haskell.ts +166 -0
- package/.sdk/src/cmp/lean/Config_lean.ts +93 -0
- package/.sdk/src/cmp/lean/Entity_lean.ts +9 -0
- package/.sdk/src/cmp/lean/Gitignore_lean.ts +18 -0
- package/.sdk/src/cmp/lean/Main_lean.ts +120 -0
- package/.sdk/src/cmp/lean/Package_lean.ts +93 -0
- package/.sdk/src/cmp/lean/ReadmeEntity_lean.ts +29 -0
- package/.sdk/src/cmp/lean/ReadmeExplanation_lean.ts +19 -0
- package/.sdk/src/cmp/lean/ReadmeHowto_lean.ts +25 -0
- package/.sdk/src/cmp/lean/ReadmeInstall_lean.ts +39 -0
- package/.sdk/src/cmp/lean/ReadmeIntro_lean.ts +25 -0
- package/.sdk/src/cmp/lean/ReadmeModel_lean.ts +24 -0
- package/.sdk/src/cmp/lean/ReadmeOptions_lean.ts +22 -0
- package/.sdk/src/cmp/lean/ReadmeQuick_lean.ts +50 -0
- package/.sdk/src/cmp/lean/ReadmeRef_lean.ts +40 -0
- package/.sdk/src/cmp/lean/ReadmeTopHowto_lean.ts +16 -0
- package/.sdk/src/cmp/lean/ReadmeTopQuick_lean.ts +23 -0
- package/.sdk/src/cmp/lean/ReadmeTopTest_lean.ts +19 -0
- package/.sdk/src/cmp/lean/Test_lean.ts +261 -0
- package/.sdk/src/cmp/lean/utility_lean.ts +129 -0
- package/.sdk/tm/dart/LICENSE +22 -0
- package/.sdk/tm/dart/Makefile +50 -0
- package/.sdk/tm/dart/lib/Context.dart +150 -0
- package/.sdk/tm/dart/lib/Control.dart +15 -0
- package/.sdk/tm/dart/lib/Operation.dart +31 -0
- package/.sdk/tm/dart/lib/Point.dart +51 -0
- package/.sdk/tm/dart/lib/Response.dart +32 -0
- package/.sdk/tm/dart/lib/Result.dart +39 -0
- package/.sdk/tm/dart/lib/Spec.dart +49 -0
- package/.sdk/tm/dart/lib/feature/audit/AuditFeature.dart +103 -0
- package/.sdk/tm/dart/lib/feature/base/BaseFeature.dart +76 -0
- package/.sdk/tm/dart/lib/feature/cache/CacheFeature.dart +152 -0
- package/.sdk/tm/dart/lib/feature/clienttrack/ClienttrackFeature.dart +104 -0
- package/.sdk/tm/dart/lib/feature/cost/CostFeature.dart +446 -0
- package/.sdk/tm/dart/lib/feature/debug/DebugFeature.dart +153 -0
- package/.sdk/tm/dart/lib/feature/idempotency/IdempotencyFeature.dart +92 -0
- package/.sdk/tm/dart/lib/feature/log/LogFeature.dart +85 -0
- package/.sdk/tm/dart/lib/feature/metrics/MetricsFeature.dart +103 -0
- package/.sdk/tm/dart/lib/feature/netsim/NetsimFeature.dart +186 -0
- package/.sdk/tm/dart/lib/feature/paging/PagingFeature.dart +240 -0
- package/.sdk/tm/dart/lib/feature/proxy/ProxyFeature.dart +118 -0
- package/.sdk/tm/dart/lib/feature/ratelimit/RatelimitFeature.dart +103 -0
- package/.sdk/tm/dart/lib/feature/rbac/RbacFeature.dart +121 -0
- package/.sdk/tm/dart/lib/feature/retry/RetryFeature.dart +155 -0
- package/.sdk/tm/dart/lib/feature/secrets/SecretsFeature.dart +649 -0
- package/.sdk/tm/dart/lib/feature/secrets/plugin/capability.dart +105 -0
- package/.sdk/tm/dart/lib/feature/secrets/plugin/catalog.dart +46 -0
- package/.sdk/tm/dart/lib/feature/secrets/plugin/config.dart +297 -0
- package/.sdk/tm/dart/lib/feature/secrets/plugin/depend.dart +198 -0
- package/.sdk/tm/dart/lib/feature/secrets/plugin/env.dart +144 -0
- package/.sdk/tm/dart/lib/feature/secrets/plugin/export.dart +58 -0
- package/.sdk/tm/dart/lib/feature/secrets/plugin/graph.dart +160 -0
- package/.sdk/tm/dart/lib/feature/secrets/plugin/host.dart +1067 -0
- package/.sdk/tm/dart/lib/feature/secrets/plugin/order.dart +172 -0
- package/.sdk/tm/dart/lib/feature/secrets/plugin/plugin.dart +23 -0
- package/.sdk/tm/dart/lib/feature/secrets/plugin/point.dart +117 -0
- package/.sdk/tm/dart/lib/feature/secrets/plugin/ref.dart +105 -0
- package/.sdk/tm/dart/lib/feature/secrets/plugin/resolve.dart +58 -0
- package/.sdk/tm/dart/lib/feature/secrets/plugin/types.dart +201 -0
- package/.sdk/tm/dart/lib/feature/secrets/plugin/version.dart +116 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/plugins/aws.dart +285 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/plugins/azuresecrets.dart +178 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/plugins/boru.dart +145 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/plugins/crypto.dart +194 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/plugins/doppler.dart +101 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/plugins/gcpsecrets.dart +143 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/plugins/hashicorp.dart +190 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/plugins/httpjson.dart +363 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/plugins/infisical.dart +149 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/plugins/onepassword.dart +146 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/plugins/secretspec.dart +116 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/plugins/sigv4.dart +237 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/src/addr.dart +138 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/src/json.dart +276 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/src/provider.dart +31 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/src/providers.dart +243 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/src/sekreto.dart +730 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/src/spec.dart +200 -0
- package/.sdk/tm/dart/lib/feature/secrets/sekreto/src/support.dart +217 -0
- package/.sdk/tm/dart/lib/feature/streaming/StreamingFeature.dart +97 -0
- package/.sdk/tm/dart/lib/feature/telemetry/TelemetryFeature.dart +136 -0
- package/.sdk/tm/dart/lib/feature/test/TestFeature.dart +328 -0
- package/.sdk/tm/dart/lib/feature/timeout/TimeoutFeature.dart +104 -0
- package/.sdk/tm/dart/lib/utility/CleanUtility.dart +12 -0
- package/.sdk/tm/dart/lib/utility/DoneUtility.dart +18 -0
- package/.sdk/tm/dart/lib/utility/ErrUtility.dart +46 -0
- package/.sdk/tm/dart/lib/utility/FeatureAddUtility.dart +35 -0
- package/.sdk/tm/dart/lib/utility/FeatureHookUtility.dart +20 -0
- package/.sdk/tm/dart/lib/utility/FeatureInitUtility.dart +9 -0
- package/.sdk/tm/dart/lib/utility/FetcherUtility.dart +104 -0
- package/.sdk/tm/dart/lib/utility/GraphqlUtility.dart +162 -0
- package/.sdk/tm/dart/lib/utility/MakeContextUtility.dart +6 -0
- package/.sdk/tm/dart/lib/utility/MakeErrorUtility.dart +71 -0
- package/.sdk/tm/dart/lib/utility/MakeFetchDefUtility.dart +42 -0
- package/.sdk/tm/dart/lib/utility/MakeOptionsUtility.dart +233 -0
- package/.sdk/tm/dart/lib/utility/MakePointUtility.dart +130 -0
- package/.sdk/tm/dart/lib/utility/MakeRequestUtility.dart +66 -0
- package/.sdk/tm/dart/lib/utility/MakeResponseUtility.dart +59 -0
- package/.sdk/tm/dart/lib/utility/MakeResultUtility.dart +49 -0
- package/.sdk/tm/dart/lib/utility/MakeSpecUtility.dart +77 -0
- package/.sdk/tm/dart/lib/utility/MakeUrlUtility.dart +51 -0
- package/.sdk/tm/dart/lib/utility/ParamUtility.dart +56 -0
- package/.sdk/tm/dart/lib/utility/PrepareAuthUtility.dart +46 -0
- package/.sdk/tm/dart/lib/utility/PrepareBodyUtility.dart +19 -0
- package/.sdk/tm/dart/lib/utility/PrepareHeadersUtility.dart +11 -0
- package/.sdk/tm/dart/lib/utility/PrepareMethodUtility.dart +30 -0
- package/.sdk/tm/dart/lib/utility/PrepareParamsUtility.dart +23 -0
- package/.sdk/tm/dart/lib/utility/PreparePathUtility.dart +9 -0
- package/.sdk/tm/dart/lib/utility/PrepareQueryUtility.dart +18 -0
- package/.sdk/tm/dart/lib/utility/ResultBasicUtility.dart +29 -0
- package/.sdk/tm/dart/lib/utility/ResultBodyUtility.dart +15 -0
- package/.sdk/tm/dart/lib/utility/ResultHeadersUtility.dart +18 -0
- package/.sdk/tm/dart/lib/utility/StructUtility.dart +127 -0
- package/.sdk/tm/dart/lib/utility/TransformRequestUtility.dart +28 -0
- package/.sdk/tm/dart/lib/utility/TransformResponseUtility.dart +44 -0
- package/.sdk/tm/dart/lib/utility/Utility.dart +239 -0
- package/.sdk/tm/dart/lib/utility/voxgig_struct.dart +2462 -0
- package/.sdk/tm/dart/src/feature/README.md +3 -0
- package/.sdk/tm/dart/src/feature/audit/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/base/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/cache/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/clienttrack/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/cost/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/debug/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/idempotency/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/log/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/metrics/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/netsim/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/paging/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/proxy/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/ratelimit/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/rbac/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/retry/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/secrets/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/streaming/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/telemetry/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/test/.gitkeep +0 -0
- package/.sdk/tm/dart/src/feature/timeout/.gitkeep +0 -0
- package/.sdk/tm/dart/test/custom_test.dart +65 -0
- package/.sdk/tm/dart/test/exists_test.dart +12 -0
- package/.sdk/tm/dart/test/feature/harness.dart +347 -0
- package/.sdk/tm/dart/test/feature/secrets/secrets_test.dart +1107 -0
- package/.sdk/tm/dart/test/feature_test.dart +1193 -0
- package/.sdk/tm/dart/test/harness.dart +153 -0
- package/.sdk/tm/dart/test/netsim_test.dart +48 -0
- package/.sdk/tm/dart/test/omni.dart +520 -0
- package/.sdk/tm/dart/test/omni_smoke_test.dart +143 -0
- package/.sdk/tm/dart/test/pipeline_test.dart +608 -0
- package/.sdk/tm/dart/test/primary_test.dart +476 -0
- package/.sdk/tm/dart/test/sdk-test-control.json +19 -0
- package/.sdk/tm/dart/test/struct_test.dart +468 -0
- package/.sdk/tm/dart/test/utility.dart +198 -0
- package/.sdk/tm/dart/test/vendor/omni/omni.dart +7 -0
- package/.sdk/tm/dart/test/vendor/omni/runner.dart +653 -0
- package/.sdk/tm/dart/test/vendor/omni/util.dart +232 -0
- package/.sdk/tm/haskell/LICENSE +21 -0
- package/.sdk/tm/haskell/Makefile +22 -0
- package/.sdk/tm/haskell/VERSION +1 -0
- package/.sdk/tm/haskell/src/SdkFeatures.hs +1440 -0
- package/.sdk/tm/haskell/src/SdkHelpers.hs +337 -0
- package/.sdk/tm/haskell/src/SdkJson.hs +111 -0
- package/.sdk/tm/haskell/src/SdkRuntime.hs +1279 -0
- package/.sdk/tm/haskell/src/SdkTypes.hs +195 -0
- package/.sdk/tm/haskell/src/VoxgigStruct.hs +2299 -0
- package/.sdk/tm/haskell/src/Vregex.hs +237 -0
- package/.sdk/tm/haskell/src/feature/README.md +5 -0
- package/.sdk/tm/haskell/src/feature/audit/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/base/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/cache/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/clienttrack/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/debug/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/idempotency/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/log/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/metrics/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/netsim/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/paging/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/proxy/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/ratelimit/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/rbac/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/retry/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/streaming/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/telemetry/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/test/.gitkeep +0 -0
- package/.sdk/tm/haskell/src/feature/timeout/.gitkeep +0 -0
- package/.sdk/tm/haskell/test/Harness.hs +281 -0
- package/.sdk/tm/haskell/test/OmniResolver.hs +252 -0
- package/.sdk/tm/haskell/test/Runner.hs +49 -0
- package/.sdk/tm/haskell/test/StructCorpus.hs +449 -0
- package/.sdk/tm/haskell/test/TCustomUtility.hs +38 -0
- package/.sdk/tm/haskell/test/TFeature.hs +794 -0
- package/.sdk/tm/haskell/test/TNetsim.hs +50 -0
- package/.sdk/tm/haskell/test/TPipeline.hs +334 -0
- package/.sdk/tm/haskell/test/TPrimaryCorpus.hs +263 -0
- package/.sdk/tm/haskell/test/TPrimaryUtility.hs +386 -0
- package/.sdk/tm/haskell/test/Testutil.hs +59 -0
- package/.sdk/tm/haskell/test/vendor/omni/Omni.hs +1147 -0
- package/.sdk/tm/lean/LICENSE +21 -0
- package/.sdk/tm/lean/Makefile +32 -0
- package/.sdk/tm/lean/VERSION +1 -0
- package/.sdk/tm/lean/src/SdkFeature.lean +171 -0
- package/.sdk/tm/lean/src/SdkFeatures.lean +787 -0
- package/.sdk/tm/lean/src/SdkJson.lean +134 -0
- package/.sdk/tm/lean/src/SdkRuntime.lean +374 -0
- package/.sdk/tm/lean/src/SdkUtility.lean +809 -0
- package/.sdk/tm/lean/src/VoxgigStruct.lean +2880 -0
- package/.sdk/tm/lean/src/Vregex.lean +412 -0
- package/.sdk/tm/lean/src/feature/README.md +5 -0
- package/.sdk/tm/lean/src/feature/audit/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/base/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/cache/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/clienttrack/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/cost/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/debug/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/idempotency/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/log/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/metrics/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/netsim/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/paging/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/proxy/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/ratelimit/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/rbac/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/retry/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/streaming/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/telemetry/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/test/.gitkeep +0 -0
- package/.sdk/tm/lean/src/feature/timeout/.gitkeep +0 -0
- package/.sdk/tm/lean/test/OmniResolver.lean +358 -0
- package/.sdk/tm/lean/test/OmniSmoke.lean +174 -0
- package/.sdk/tm/lean/test/StructCorpus.lean +368 -0
- package/.sdk/tm/lean/test/TFeature.lean +281 -0
- package/.sdk/tm/lean/test/TPrimaryUtility.lean +227 -0
- package/.sdk/tm/lean/test/vendor/omni/Omni.lean +997 -0
- package/LICENSE +21 -0
- package/README.md +104 -0
- package/package.json +78 -0
- package/sdkgen-package.json +22 -0
|
@@ -0,0 +1,649 @@
|
|
|
1
|
+
// ignore_for_file: non_constant_identifier_names
|
|
2
|
+
|
|
3
|
+
import 'dart:async';
|
|
4
|
+
import 'dart:convert';
|
|
5
|
+
|
|
6
|
+
import '../base/BaseFeature.dart';
|
|
7
|
+
|
|
8
|
+
// The plugin DEFINITIONS the model selected for this feature, emitted by
|
|
9
|
+
// Config generically from the catalogue's active `plugin.def` entries.
|
|
10
|
+
// Upstream sekreto's contract since the registry was retired: a kind not
|
|
11
|
+
// passed in `plugins` is unknown to that Sekreto, so the model's choice of
|
|
12
|
+
// plugin groups IS the SDK's provider vocabulary.
|
|
13
|
+
//
|
|
14
|
+
// Config.dart imports this file (the generated #ImportFeatures block) and
|
|
15
|
+
// this file imports Config.dart back, which is a CYCLE - legal in Dart, and
|
|
16
|
+
// safe here because `FEATURE_PLUGINS` is a lazily-initialised top-level
|
|
17
|
+
// final that is first read in `init`, long after both libraries have
|
|
18
|
+
// loaded. Config emits the map unconditionally (empty when no group is
|
|
19
|
+
// active), so this import resolves in an SDK that carries the feature
|
|
20
|
+
// source without selecting a single plugin group.
|
|
21
|
+
import '../../Config.dart' show FEATURE_PLUGINS;
|
|
22
|
+
|
|
23
|
+
import 'sekreto/src/provider.dart';
|
|
24
|
+
import 'sekreto/src/sekreto.dart';
|
|
25
|
+
import 'sekreto/src/spec.dart';
|
|
26
|
+
import 'sekreto/src/support.dart';
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
// Secret access via a vendored @voxgig/sekreto provider chain, and the
|
|
30
|
+
// access-token exchange some APIs require on top of it. The dart port of
|
|
31
|
+
// tm/ts/src/feature/secrets/SecretsFeature.ts - same contract, dart idiom,
|
|
32
|
+
// and structurally the GO port rather than the ts one (see below).
|
|
33
|
+
//
|
|
34
|
+
// The SDK's `apikey` option keeps exactly its old meaning: an explicit
|
|
35
|
+
// credential given in code. This feature makes it ONE SOURCE among several
|
|
36
|
+
// rather than the only one: when active, the apikey is resolved through a
|
|
37
|
+
// sekreto chain in which the explicit option (when set) is the FIRST
|
|
38
|
+
// provider - a `memory` store named `options` - so an explicit value always
|
|
39
|
+
// wins, by sekreto's own first-hit rule rather than by special-case logic.
|
|
40
|
+
// When the option is unset, the remaining providers (env, dotenv, a vault)
|
|
41
|
+
// are asked in order, and moving a credential from code to a vault becomes
|
|
42
|
+
// a configuration change.
|
|
43
|
+
//
|
|
44
|
+
// WHY THE TRANSPORT SEAM, AND NOT PreSpec. Dart's feature hooks ARE
|
|
45
|
+
// awaited (EntityBase's `fres = featureHook(ctx, 'PreSpec'); if (fres is
|
|
46
|
+
// Future) await fres;`), so a PreSpec resolve COULD fail an entity op the
|
|
47
|
+
// ts way. It would not be enough. `ProjectNameSDK.prepare()` runs no
|
|
48
|
+
// feature hooks at all, and `direct()` and `graphql()` reach the wire
|
|
49
|
+
// through it - so a feature that only resolved in PreSpec would leave both
|
|
50
|
+
// RAW paths uncredentialed and, worse, fail OPEN: a broken vault would
|
|
51
|
+
// still send the request, unauthenticated. Every wire path instead crosses
|
|
52
|
+
// one mutable field, `ctx.utility.fetcher`, so this feature wraps that -
|
|
53
|
+
// the same seam the netsim/retry/cache features wrap - and resolves there.
|
|
54
|
+
// One gate, every path, entity and raw alike.
|
|
55
|
+
//
|
|
56
|
+
// THE CREDENTIAL LIVES IN FEATURE STATE, never in the shared options map.
|
|
57
|
+
// The ts reference writes `client._options.apikey` so the synchronous
|
|
58
|
+
// prepareAuth picks it up; here the transport wrapper rewrites the header
|
|
59
|
+
// itself, from the same `options.auth.prefix` prepareAuth uses, so the two
|
|
60
|
+
// cannot drift and the options map stays exactly as makeOptions built it.
|
|
61
|
+
// (go documents the same choice for a stronger reason - a shared map read
|
|
62
|
+
// by concurrent goroutines. Dart is single-isolate, so this is not a data
|
|
63
|
+
// race here; it is what makes the raw paths work and keeps the seam the
|
|
64
|
+
// single writer of the credential.)
|
|
65
|
+
//
|
|
66
|
+
// MISS vs ERROR (sekreto's invariant): a provider MISS falls through - the
|
|
67
|
+
// op proceeds, unauthenticated if nothing else supplies a credential. A
|
|
68
|
+
// provider ERROR (unreachable vault, bad creds) must FAIL the op: a broken
|
|
69
|
+
// vault never degrades into an unauthenticated request. The wrapper
|
|
70
|
+
// refuses to call the inner transport at all in that case, and answers
|
|
71
|
+
// with the provider's OWN message.
|
|
72
|
+
//
|
|
73
|
+
// EXCHANGE: some APIs will not take a long-lived credential at all. What
|
|
74
|
+
// the chain resolves is then a REFRESH token, which buys a short-lived
|
|
75
|
+
// ACCESS token from a token endpoint (`exchange.path`, relative to
|
|
76
|
+
// options.base); the access token is what every request carries, and when
|
|
77
|
+
// a response status in `exchange.statuses` (401) says it is spent the
|
|
78
|
+
// wrapper buys another and retries the same request once. Concurrent
|
|
79
|
+
// purchases share the one in-flight exchange; test mode buys nothing and
|
|
80
|
+
// answers with a deterministic fake token.
|
|
81
|
+
class SecretsFeature extends BaseFeature {
|
|
82
|
+
dynamic _client;
|
|
83
|
+
|
|
84
|
+
// The LIVE options map (root ctx options), read for `auth`, `apikey`,
|
|
85
|
+
// `base` and `system.fetch`. NEVER written: see the note above.
|
|
86
|
+
dynamic _liveopts;
|
|
87
|
+
|
|
88
|
+
Sekreto? _sekreto;
|
|
89
|
+
String _secretname = 'apikey';
|
|
90
|
+
bool _cache = true;
|
|
91
|
+
|
|
92
|
+
// A chain that would not BUILD (an unknown provider kind, a spec a
|
|
93
|
+
// definition refused). init cannot fail construction the way ts's
|
|
94
|
+
// throwing init does, so the failure is held here and the transport gate
|
|
95
|
+
// refuses to send - a misconfigured chain is fail-closed, not silently
|
|
96
|
+
// unauthenticated.
|
|
97
|
+
Object? _initerr;
|
|
98
|
+
|
|
99
|
+
// The RESOLVED credential, held in feature state and injected into each
|
|
100
|
+
// request at the transport seam.
|
|
101
|
+
String _cred = '';
|
|
102
|
+
|
|
103
|
+
// The one in-flight resolution concurrent operations share. Dart is
|
|
104
|
+
// single-isolate, so a plain nullable Future is all go's mutex and
|
|
105
|
+
// channel machinery collapses to.
|
|
106
|
+
Future<void>? _resolving;
|
|
107
|
+
|
|
108
|
+
// Exchange state: null config when off; the refresh credential the chain
|
|
109
|
+
// resolved; the single in-flight purchase.
|
|
110
|
+
_Exchange? _exchange;
|
|
111
|
+
String _refresh = '';
|
|
112
|
+
Future<String>? _buying;
|
|
113
|
+
|
|
114
|
+
SecretsFeature() {
|
|
115
|
+
version = '0.1.0';
|
|
116
|
+
name = 'secrets';
|
|
117
|
+
active = true;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Sync by feature contract (init cannot await): build the chain only,
|
|
121
|
+
// never look anything up here.
|
|
122
|
+
@override
|
|
123
|
+
dynamic init(dynamic ctx, dynamic opts) {
|
|
124
|
+
_client = ctx.client;
|
|
125
|
+
_liveopts = ctx.options;
|
|
126
|
+
options = opts is Map ? Map<String, dynamic>.from(opts) : {};
|
|
127
|
+
active = true == options['active'];
|
|
128
|
+
|
|
129
|
+
if (!active) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
final rawname = options['name'];
|
|
134
|
+
_secretname =
|
|
135
|
+
(rawname is String && rawname.isNotEmpty) ? rawname : 'apikey';
|
|
136
|
+
_cache = false != options['cache'];
|
|
137
|
+
|
|
138
|
+
// Exchange config, normalised once. Null when off, so every later
|
|
139
|
+
// decision is a null check rather than a repeated `true == ...active`.
|
|
140
|
+
final xopts = options['exchange'];
|
|
141
|
+
if (xopts is Map && true == xopts['active']) {
|
|
142
|
+
_exchange = _Exchange(
|
|
143
|
+
path: _str(xopts['path'], 'auth/token'),
|
|
144
|
+
method: _str(xopts['method'], 'POST'),
|
|
145
|
+
request: _str(xopts['request'], 'refresh_token'),
|
|
146
|
+
response: _str(xopts['response'], 'access_token'),
|
|
147
|
+
statuses: _ints(xopts['statuses'], const [401]),
|
|
148
|
+
retries: xopts['retries'] is num ? (xopts['retries'] as num).toInt() : 1,
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// The explicit credential, when set, is the first store in the chain.
|
|
153
|
+
//
|
|
154
|
+
// WHICH option that is depends on the exchange. Without one, the secret
|
|
155
|
+
// being resolved IS the credential the transport sends, so `apikey` is
|
|
156
|
+
// it. With one, the secret is a REFRESH token and `apikey` means the
|
|
157
|
+
// opposite thing - an access token the caller already holds - so the
|
|
158
|
+
// explicit seat belongs to `exchange.refresh`, and apikey is left alone
|
|
159
|
+
// to serve as the starting access token (see _resolveonce).
|
|
160
|
+
final explicit = null == _exchange
|
|
161
|
+
? _optstr('apikey')
|
|
162
|
+
: (xopts is Map ? _str(xopts['refresh'], '') : '');
|
|
163
|
+
|
|
164
|
+
final providers = <Object?>[];
|
|
165
|
+
|
|
166
|
+
if (explicit.isNotEmpty) {
|
|
167
|
+
// `envkey` VALIDATES the name, and a bad one (`Api.Token`, `api..token`)
|
|
168
|
+
// raises. Held rather than thrown: init runs inside the SDK
|
|
169
|
+
// constructor, where a throw is a stack trace from `ProjectNameSDK(...)`
|
|
170
|
+
// naming nothing the caller can act on. The transport gate reports it
|
|
171
|
+
// instead - with sekreto's own message - and refuses to send, which is
|
|
172
|
+
// the same fail-closed answer a broken store gets.
|
|
173
|
+
try {
|
|
174
|
+
providers.add(ProviderSpec(
|
|
175
|
+
kind: 'memory',
|
|
176
|
+
name: 'options',
|
|
177
|
+
values: <String, String>{envkey(_secretname): explicit},
|
|
178
|
+
));
|
|
179
|
+
} catch (err) {
|
|
180
|
+
_initerr = err;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
final given = options['providers'];
|
|
185
|
+
if (given is List) {
|
|
186
|
+
for (final p in given) {
|
|
187
|
+
// A live Provider or a built ProviderSpec joins the chain as it is.
|
|
188
|
+
// A declarative MAP - `{ kind: 'env', prefix: 'X' }`, the shape a
|
|
189
|
+
// config file and the docs use - goes through sekreto's own
|
|
190
|
+
// converter, because the Sekreto constructor accepts nothing else
|
|
191
|
+
// ("sekreto: not a provider or a provider spec").
|
|
192
|
+
if (p is ProviderSpec || p is Provider) {
|
|
193
|
+
providers.add(p);
|
|
194
|
+
} else if (p is Map) {
|
|
195
|
+
try {
|
|
196
|
+
providers.add(specof(Map<String, dynamic>.from(
|
|
197
|
+
p.map((k, v) => MapEntry('$k', v)))));
|
|
198
|
+
} catch (err) {
|
|
199
|
+
_initerr = err;
|
|
200
|
+
}
|
|
201
|
+
} else {
|
|
202
|
+
_initerr = SekretoError(
|
|
203
|
+
'secrets: not a provider or a provider spec: ${p ?? ''}');
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
_sekreto = Sekreto(
|
|
210
|
+
providers: providers,
|
|
211
|
+
plugins: FEATURE_PLUGINS[name] ?? const <Object?>[],
|
|
212
|
+
cache: _cache,
|
|
213
|
+
);
|
|
214
|
+
} catch (err) {
|
|
215
|
+
_initerr = err;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// NO `client._secrets = this` HERE, and the omission is load-bearing.
|
|
219
|
+
// The ts and js ports plant that field for their `secrets()` accessor
|
|
220
|
+
// to read; in Dart a leading underscore is LIBRARY-private, and this
|
|
221
|
+
// file is a different library from ProjectNameSDK.dart - so a dynamic
|
|
222
|
+
// `_client._secrets = this` would create/seek a distinct private symbol
|
|
223
|
+
// and throw NoSuchMethodError at construction. The generated accessor
|
|
224
|
+
// finds this feature by name in `client.features` instead, which is
|
|
225
|
+
// public and needs no seam at all.
|
|
226
|
+
|
|
227
|
+
// Wrap the transport. Unlike the ts reference - which wraps only when
|
|
228
|
+
// exchanging, because its PreSpec hook covers the ordinary case - the
|
|
229
|
+
// wrapper is the fail-closed gate AND the credential injector here, so
|
|
230
|
+
// it is installed whenever the feature is active.
|
|
231
|
+
final self = this;
|
|
232
|
+
final utility = ctx.utility;
|
|
233
|
+
final inner = utility.fetcher;
|
|
234
|
+
|
|
235
|
+
utility.fetcher = (dynamic ctx2, dynamic url, dynamic fetchdef) async {
|
|
236
|
+
return self._transport(ctx2, url, fetchdef, inner);
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// The LIVE Sekreto instance, for the SDK's secrets() accessor and for
|
|
243
|
+
// callers who want arbitrary secrets or redaction:
|
|
244
|
+
//
|
|
245
|
+
// await sdk.secrets().get('db.password')
|
|
246
|
+
// sdk.secrets().redact(logline)
|
|
247
|
+
//
|
|
248
|
+
// Never a clone: sekreto holds provider state (caches, vault leases) that
|
|
249
|
+
// has to stay live to be worth anything.
|
|
250
|
+
Sekreto? sekreto() => _sekreto;
|
|
251
|
+
|
|
252
|
+
// The resolved credential (empty when none) - the state the transport
|
|
253
|
+
// injects. The dart peer of go's Credential(): tests and callers read it
|
|
254
|
+
// here rather than from the options map, which this feature never writes.
|
|
255
|
+
String get credential => _cred;
|
|
256
|
+
|
|
257
|
+
// Resolve the secret. Concurrent operations share the one IN-FLIGHT
|
|
258
|
+
// future; a settled HIT is kept only when caching is on (`cache: false`
|
|
259
|
+
// means every resolve asks the chain again). A FAILURE is always cleared,
|
|
260
|
+
// so a transient vault outage never poisons the client permanently - the
|
|
261
|
+
// next operation asks the chain again.
|
|
262
|
+
//
|
|
263
|
+
// A MISS is cleared too, however caching is set. That rule is sekreto's,
|
|
264
|
+
// not this feature's: `A miss is never cached: the next read asks again`,
|
|
265
|
+
// in sekreto's own source (sekreto/src/sekreto.dart). Keeping a settled
|
|
266
|
+
// miss here would override that from the layer above, and a secret
|
|
267
|
+
// provisioned after startup - a mounted file, a policy granted a minute
|
|
268
|
+
// late - would never be picked up for the life of the client. `cache` is
|
|
269
|
+
// about caching a HIT; it was never a promise to keep saying no.
|
|
270
|
+
Future<void> resolve() {
|
|
271
|
+
final err = _initerr;
|
|
272
|
+
if (null != err) {
|
|
273
|
+
return Future<void>.error(err);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
final current = _resolving;
|
|
277
|
+
if (null != current) {
|
|
278
|
+
return current;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
final inflight = _resolveonce().then((bool hit) {
|
|
282
|
+
if (!_cache || !hit) {
|
|
283
|
+
_resolving = null;
|
|
284
|
+
}
|
|
285
|
+
}, onError: (Object e) {
|
|
286
|
+
_resolving = null;
|
|
287
|
+
throw e;
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
_resolving = inflight;
|
|
291
|
+
|
|
292
|
+
return inflight;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Resolve once, reporting whether a credential came out of it. That bool
|
|
296
|
+
// is the whole of what resolve() needs to tell a cacheable HIT from a miss
|
|
297
|
+
// it must not keep.
|
|
298
|
+
Future<bool> _resolveonce() async {
|
|
299
|
+
final sek = _sekreto;
|
|
300
|
+
if (null == sek) {
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// `tryget` is FutureOr: a chain of purely local stores answers without
|
|
305
|
+
// yielding. `await` reads either, and this function being async is what
|
|
306
|
+
// turns a SYNCHRONOUS provider throw into a failed future rather than
|
|
307
|
+
// an exception escaping resolve()'s caller.
|
|
308
|
+
final found = await sek.tryget(_secretname);
|
|
309
|
+
|
|
310
|
+
if (null == _exchange) {
|
|
311
|
+
// An UNCACHED miss after an earlier hit is a revocation: the chain
|
|
312
|
+
// now says no provider has the secret, so the resolved value must not
|
|
313
|
+
// keep going out on the wire. (An explicit apikey OPTION is never
|
|
314
|
+
// lost here - it seats FIRST in the chain as a memory provider, so
|
|
315
|
+
// the chain HITS while one is set and this branch writes it back.)
|
|
316
|
+
_cred = found ?? '';
|
|
317
|
+
return null != found;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Exchanging: what the chain resolved is the REFRESH token, kept for
|
|
321
|
+
// every later purchase. A miss is not fatal here - an explicit `apikey`
|
|
322
|
+
// may already hold a usable access token, and the API is what gets to
|
|
323
|
+
// say whether it does.
|
|
324
|
+
_refresh = found ?? '';
|
|
325
|
+
|
|
326
|
+
if (_cred.isEmpty) {
|
|
327
|
+
// A starting access token supplied as the OPTION.
|
|
328
|
+
_cred = _optstr('apikey');
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (_cred.isNotEmpty) {
|
|
332
|
+
// Spend it: if it is stale the API answers with an expiry status and
|
|
333
|
+
// the transport wrapper buys another, which is the same path expiry
|
|
334
|
+
// takes anyway.
|
|
335
|
+
return true;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// `auth: null` is the documented way to send NO credential, and a
|
|
339
|
+
// purchase is a credential-bearing call: the refresh token goes to the
|
|
340
|
+
// token endpoint in the request body. _withRefresh honours suppression
|
|
341
|
+
// for the RETRY, but it runs after this - by then the refresh token has
|
|
342
|
+
// already left the process, and no later check can call it back. The
|
|
343
|
+
// suppression has to be honoured here, before the first purchase, or it
|
|
344
|
+
// only ever half-held.
|
|
345
|
+
if (null == _rawauth()) {
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
_cred = await _buy();
|
|
350
|
+
|
|
351
|
+
return true;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// The transport wrapper: the ONE seam every wire path crosses.
|
|
355
|
+
//
|
|
356
|
+
// Entity ops, prepare()/direct()/graphql() and the exchange retries all
|
|
357
|
+
// come through here, so resolving HERE is what gives the raw paths -
|
|
358
|
+
// which run no feature hooks at all - the same credential the entity
|
|
359
|
+
// pipeline gets. resolve() is shared and cached: concurrent callers join
|
|
360
|
+
// the in-flight attempt, a cached success is free, and with `cache:
|
|
361
|
+
// false` the chain is asked once per REQUEST, which is that option's
|
|
362
|
+
// meaning.
|
|
363
|
+
Future<dynamic> _transport(
|
|
364
|
+
dynamic ctx, dynamic url, dynamic fetchdef, dynamic inner) async {
|
|
365
|
+
// FAIL CLOSED. A provider ERROR refuses the request, carrying the
|
|
366
|
+
// provider's own message - never an unauthenticated send. Returned as
|
|
367
|
+
// an error VALUE rather than thrown, which is this pipeline's shape for
|
|
368
|
+
// a transport failure (netsim does the same): _rawRequest reports it as
|
|
369
|
+
// `{ok: false, err: ...}` and an entity op fails on it.
|
|
370
|
+
try {
|
|
371
|
+
await resolve();
|
|
372
|
+
} catch (err) {
|
|
373
|
+
return ctx.error('secrets_provider', _why(err));
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Inject the resolved credential into THIS request's header. The header
|
|
377
|
+
// was built by prepareAuth from the options apikey; the chain-resolved
|
|
378
|
+
// value lives in feature state instead, so the wrapper writes it here -
|
|
379
|
+
// same construction, same suppression rules.
|
|
380
|
+
if (_cred.isNotEmpty) {
|
|
381
|
+
_reauth(fetchdef, _cred);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (null == _exchange) {
|
|
385
|
+
return Future.value(inner(ctx, url, fetchdef));
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return _withRefresh(ctx, url, fetchdef, inner);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Buy a token and try the request again when the API says the current one
|
|
392
|
+
// is spent.
|
|
393
|
+
//
|
|
394
|
+
// The retry rewrites the authorization header IN PLACE on the fetchdef,
|
|
395
|
+
// because the header was built by the synchronous prepareAuth before this
|
|
396
|
+
// request left, and it carries the token that just failed. Rebuilt the
|
|
397
|
+
// way prepareAuth builds it, from the same options auth.prefix, so the
|
|
398
|
+
// two cannot drift.
|
|
399
|
+
Future<dynamic> _withRefresh(
|
|
400
|
+
dynamic ctx, dynamic url, dynamic fetchdef, dynamic inner) async {
|
|
401
|
+
// `auth: null` is the documented way to send NO credential, and
|
|
402
|
+
// prepareAuth honours it by removing the header. A refusal of a
|
|
403
|
+
// deliberately unauthenticated request is not an expired token and
|
|
404
|
+
// cannot be fixed by buying one - retrying would transmit exactly the
|
|
405
|
+
// credential the caller suppressed.
|
|
406
|
+
if (null == _rawauth()) {
|
|
407
|
+
return Future.value(inner(ctx, url, fetchdef));
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
final max = _exchange!.retries;
|
|
411
|
+
var attempt = 0;
|
|
412
|
+
|
|
413
|
+
for (;;) {
|
|
414
|
+
// The credential THIS attempt goes out with, captured before it
|
|
415
|
+
// leaves: it is what tells a stale refusal apart from a fresh one.
|
|
416
|
+
final used = _cred;
|
|
417
|
+
|
|
418
|
+
final res = await Future.value(inner(ctx, url, fetchdef));
|
|
419
|
+
|
|
420
|
+
if (attempt >= max || !_spent(res)) {
|
|
421
|
+
return res;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Another request may have bought a token while this one was in
|
|
425
|
+
// flight. Concurrent expiries share the in-flight purchase, but
|
|
426
|
+
// STAGGERED ones do not - so spend what is current before buying: a
|
|
427
|
+
// second exchange for a token that is already fresh is wasted, and on
|
|
428
|
+
// a provider that invalidates the previous credential on issuance it
|
|
429
|
+
// breaks the first request's own retry.
|
|
430
|
+
final current = _cred;
|
|
431
|
+
String token;
|
|
432
|
+
|
|
433
|
+
if (current.isNotEmpty && current != used) {
|
|
434
|
+
token = current;
|
|
435
|
+
} else {
|
|
436
|
+
try {
|
|
437
|
+
token = await _buy();
|
|
438
|
+
} catch (_err) {
|
|
439
|
+
// The purchase failed: answer with the API's own refusal rather
|
|
440
|
+
// than this one. The caller asked for data, and the refusal is
|
|
441
|
+
// the more useful of the two - the exchange error is a symptom.
|
|
442
|
+
return res;
|
|
443
|
+
}
|
|
444
|
+
_cred = token;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
_reauth(fetchdef, token);
|
|
448
|
+
|
|
449
|
+
attempt++;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
bool _spent(dynamic res) {
|
|
454
|
+
if (null == res || res is Error || res is Exception || res is! Map) {
|
|
455
|
+
return false;
|
|
456
|
+
}
|
|
457
|
+
final status = res['status'];
|
|
458
|
+
if (status is! num) {
|
|
459
|
+
return false;
|
|
460
|
+
}
|
|
461
|
+
return _exchange!.statuses.contains(status.toInt());
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Buy an access token with the refresh token. Concurrent callers share
|
|
465
|
+
// the ONE in-flight purchase: a client running four operations at once
|
|
466
|
+
// must not open four token requests. The slot is cleared once settled, so
|
|
467
|
+
// the next expiry buys a fresh token rather than replaying this result.
|
|
468
|
+
Future<String> _buy() {
|
|
469
|
+
// TEST MODE BUYS NOTHING. The test feature replaces the transport so no
|
|
470
|
+
// request leaves the process; an exchange here would be the one HTTP
|
|
471
|
+
// call it could not stop, and it would need a live token endpoint for a
|
|
472
|
+
// suite whose whole point is not needing one. A deterministic,
|
|
473
|
+
// obviously-fake token instead - the same answer makeOptions gives a
|
|
474
|
+
// required server variable, for the same reason.
|
|
475
|
+
if ('live' != _client.mode) {
|
|
476
|
+
return Future.value('test-' + _exchange!.response);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
final current = _buying;
|
|
480
|
+
if (null != current) {
|
|
481
|
+
return current;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
final buying = _buyonce().then((String token) {
|
|
485
|
+
_buying = null;
|
|
486
|
+
return token;
|
|
487
|
+
}, onError: (Object err) {
|
|
488
|
+
_buying = null;
|
|
489
|
+
throw err;
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
_buying = buying;
|
|
493
|
+
|
|
494
|
+
return buying;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
Future<String> _buyonce() async {
|
|
498
|
+
final x = _exchange!;
|
|
499
|
+
|
|
500
|
+
if (_refresh.isEmpty) {
|
|
501
|
+
throw SekretoError(
|
|
502
|
+
"secrets: no refresh token: the provider chain has no '"
|
|
503
|
+
"$_secretname', and feature.secrets.exchange.refresh is unset");
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// The token endpoint is RELATIVE to the base, which already carries
|
|
507
|
+
// whatever account or tenant segment the server URL declares.
|
|
508
|
+
var base = _optstr('base');
|
|
509
|
+
while (base.endsWith('/')) {
|
|
510
|
+
base = base.substring(0, base.length - 1);
|
|
511
|
+
}
|
|
512
|
+
var path = x.path;
|
|
513
|
+
while (path.startsWith('/')) {
|
|
514
|
+
path = path.substring(1);
|
|
515
|
+
}
|
|
516
|
+
final url = '$base/$path';
|
|
517
|
+
|
|
518
|
+
// From the LIVE options captured at init, NEVER from client.options():
|
|
519
|
+
// that returns `struct.clone(_options)`, and makeOptions specially
|
|
520
|
+
// preserves the function-valued system.fetch across merge/validate
|
|
521
|
+
// exactly because a clone is where a function is lost. Reading the
|
|
522
|
+
// clone risks a real token purchase where a test injected a transport.
|
|
523
|
+
final system = _liveopts is Map ? _liveopts['system'] : null;
|
|
524
|
+
final fetch = system is Map ? system['fetch'] : null;
|
|
525
|
+
|
|
526
|
+
if (fetch is! Function) {
|
|
527
|
+
throw SekretoError(
|
|
528
|
+
'secrets: no fetch implementation for the token exchange');
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// The body is MARSHALLED, never concatenated: a refresh token (or a
|
|
532
|
+
// configured request-field name) carrying a quote, backslash or newline
|
|
533
|
+
// must arrive as that literal value, not as malformed JSON.
|
|
534
|
+
final body = jsonEncode(<String, String>{x.request: _refresh});
|
|
535
|
+
|
|
536
|
+
// Deliberately NOT the SDK transport. The transport is what this
|
|
537
|
+
// feature wraps, and sending the token request back through it would
|
|
538
|
+
// recurse on the first expiry - and would route the exchange through
|
|
539
|
+
// the test mock, which knows nothing about it.
|
|
540
|
+
final res = await Future.value(fetch(url, <String, dynamic>{
|
|
541
|
+
'method': x.method,
|
|
542
|
+
'headers': <String, dynamic>{'content-type': 'application/json'},
|
|
543
|
+
'body': body,
|
|
544
|
+
}));
|
|
545
|
+
|
|
546
|
+
final rawstatus = (res is Map) ? res['status'] : null;
|
|
547
|
+
final status = rawstatus is num ? rawstatus.toInt() : 0;
|
|
548
|
+
|
|
549
|
+
if (200 > status || 300 <= status) {
|
|
550
|
+
throw SekretoError(
|
|
551
|
+
'secrets: token exchange failed: $status from $url');
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
dynamic decoded;
|
|
555
|
+
if (res is Map) {
|
|
556
|
+
final jsonfn = res['json'];
|
|
557
|
+
decoded = jsonfn is Function ? await Future.value(jsonfn()) : res['body'];
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
final token = decoded is Map ? decoded[x.response] : null;
|
|
561
|
+
|
|
562
|
+
if (token is! String || token.isEmpty) {
|
|
563
|
+
throw SekretoError("secrets: token exchange returned no '"
|
|
564
|
+
"${x.response}' field from $url");
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
return token;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// The raw `auth` option: a Map when auth is configured, null when the
|
|
571
|
+
// caller suppressed it with `auth: null`.
|
|
572
|
+
dynamic _rawauth() => _liveopts is Map ? _liveopts['auth'] : null;
|
|
573
|
+
|
|
574
|
+
void _reauth(dynamic fetchdef, String token) {
|
|
575
|
+
if (fetchdef is! Map) {
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
final headers = fetchdef['headers'];
|
|
579
|
+
if (headers is! Map) {
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// Suppressed auth means NO header, the same answer prepareAuth gives.
|
|
584
|
+
// Reached defensively for the exchange path - _withRefresh does not
|
|
585
|
+
// retry at all when auth is null - but this is the function that writes
|
|
586
|
+
// the credential, so it is where the rule has to hold.
|
|
587
|
+
final auth = _rawauth();
|
|
588
|
+
if (auth is! Map) {
|
|
589
|
+
headers.remove('authorization');
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
final prefix = auth['prefix'];
|
|
594
|
+
headers['authorization'] = (prefix is String && prefix.isNotEmpty)
|
|
595
|
+
? '$prefix $token'
|
|
596
|
+
: token;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
String _optstr(String key) {
|
|
600
|
+
final value = _liveopts is Map ? _liveopts[key] : null;
|
|
601
|
+
return value is String ? value : '';
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
static String _str(dynamic value, String dflt) =>
|
|
605
|
+
(value is String && value.isNotEmpty) ? value : dflt;
|
|
606
|
+
|
|
607
|
+
static List<int> _ints(dynamic value, List<int> dflt) {
|
|
608
|
+
if (value is! List) {
|
|
609
|
+
return dflt;
|
|
610
|
+
}
|
|
611
|
+
final out = <int>[];
|
|
612
|
+
for (final one in value) {
|
|
613
|
+
if (one is num) {
|
|
614
|
+
out.add(one.toInt());
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
return out.isEmpty ? dflt : out;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// What a failure has to say for itself. A SekretoError's whole contract
|
|
621
|
+
// is its message (the shared spec pins every one of them byte for byte),
|
|
622
|
+
// so it is passed through rather than decorated - a test that asserts on
|
|
623
|
+
// the PROVIDER's own words is asserting on what the operator will read.
|
|
624
|
+
static String _why(Object err) {
|
|
625
|
+
if (err is SekretoError) {
|
|
626
|
+
return err.message;
|
|
627
|
+
}
|
|
628
|
+
return '$err';
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
class _Exchange {
|
|
634
|
+
final String path;
|
|
635
|
+
final String method;
|
|
636
|
+
final String request;
|
|
637
|
+
final String response;
|
|
638
|
+
final List<int> statuses;
|
|
639
|
+
final int retries;
|
|
640
|
+
|
|
641
|
+
_Exchange({
|
|
642
|
+
required this.path,
|
|
643
|
+
required this.method,
|
|
644
|
+
required this.request,
|
|
645
|
+
required this.response,
|
|
646
|
+
required this.statuses,
|
|
647
|
+
required this.retries,
|
|
648
|
+
});
|
|
649
|
+
}
|