@paakd/config 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/dist/src/index.js +20 -0
  2. package/gen/go/addresses/Address.pkl.go +10 -0
  3. package/gen/go/addresses/Addresses.pkl.go +43 -0
  4. package/gen/go/addresses/BillingAddress.pkl.go +8 -0
  5. package/gen/go/addresses/Field.pkl.go +43 -0
  6. package/gen/go/addresses/addressgroup/AddressGroup.pkl.go +43 -0
  7. package/gen/go/addresses/fieldtype/FieldType.pkl.go +61 -0
  8. package/gen/go/addresses/init.pkl.go +11 -0
  9. package/gen/go/cloudflare/Cloudflare.pkl.go +37 -0
  10. package/gen/go/cloudflare/R2Bucket.pkl.go +22 -0
  11. package/gen/go/cloudflare/TerraformBackend.pkl.go +16 -0
  12. package/gen/go/cloudflare/Turnstile.pkl.go +8 -0
  13. package/gen/go/cloudflare/init.pkl.go +11 -0
  14. package/gen/go/db/DB.pkl.go +43 -0
  15. package/gen/go/db/DBConnection.pkl.go +12 -0
  16. package/gen/go/db/init.pkl.go +9 -0
  17. package/gen/go/dune/Dune.pkl.go +47 -0
  18. package/gen/go/dune/init.pkl.go +8 -0
  19. package/gen/go/enterprise/Enterprise.pkl.go +118 -0
  20. package/gen/go/enterprise/init.pkl.go +8 -0
  21. package/gen/go/enterprise/taxonomyversion/TaxonomyVersion.pkl.go +37 -0
  22. package/gen/go/redis/Redis.pkl.go +41 -0
  23. package/gen/go/redis/init.pkl.go +8 -0
  24. package/gen/go/shared/Build.pkl.go +46 -0
  25. package/gen/go/shared/Shared.pkl.go +35 -0
  26. package/gen/go/shared/init.pkl.go +9 -0
  27. package/gen/go/worker/Worker.pkl.go +55 -0
  28. package/gen/go/worker/WorkerConnect.pkl.go +6 -0
  29. package/gen/go/worker/init.pkl.go +9 -0
  30. package/gen/js/development/basket_eco_packages_config_addresses.pkl.ts +106 -0
  31. package/gen/js/development/basket_eco_packages_config_checkout.pkl.ts +73 -0
  32. package/gen/js/development/basket_eco_packages_config_cloudflare.pkl.ts +69 -0
  33. package/gen/js/development/basket_eco_packages_config_cms.pkl.ts +119 -0
  34. package/gen/js/development/basket_eco_packages_config_db.pkl.ts +47 -0
  35. package/gen/js/development/basket_eco_packages_config_dune.pkl.ts +36 -0
  36. package/gen/js/development/basket_eco_packages_config_enterprise.pkl.ts +290 -0
  37. package/gen/js/development/basket_eco_packages_config_feature.pkl.ts +58 -0
  38. package/gen/js/development/basket_eco_packages_config_redis.pkl.ts +30 -0
  39. package/gen/js/development/basket_eco_packages_config_renderer_development_cms.pkl.ts +28 -0
  40. package/gen/js/development/basket_eco_packages_config_shared.pkl.ts +73 -0
  41. package/gen/js/development/basket_eco_packages_config_worker.pkl.ts +53 -0
  42. package/gen/js/production/basket_eco_packages_config_addresses.pkl.ts +106 -0
  43. package/gen/js/production/basket_eco_packages_config_checkout.pkl.ts +73 -0
  44. package/gen/js/production/basket_eco_packages_config_cloudflare.pkl.ts +69 -0
  45. package/gen/js/production/basket_eco_packages_config_cms.pkl.ts +119 -0
  46. package/gen/js/production/basket_eco_packages_config_db.pkl.ts +47 -0
  47. package/gen/js/production/basket_eco_packages_config_dune.pkl.ts +36 -0
  48. package/gen/js/production/basket_eco_packages_config_enterprise.pkl.ts +290 -0
  49. package/gen/js/production/basket_eco_packages_config_feature.pkl.ts +58 -0
  50. package/gen/js/production/basket_eco_packages_config_kiosk_host.pkl.ts +72 -0
  51. package/gen/js/production/basket_eco_packages_config_redis.pkl.ts +30 -0
  52. package/gen/js/production/basket_eco_packages_config_renderer_production_cms.pkl.ts +28 -0
  53. package/gen/js/production/basket_eco_packages_config_renderer_production_dune_config.pkl.ts +29 -0
  54. package/gen/js/production/basket_eco_packages_config_renderer_production_workflow_config.pkl.ts +29 -0
  55. package/gen/js/production/basket_eco_packages_config_renderer_staging_dune_config.pkl.ts +29 -0
  56. package/gen/js/production/basket_eco_packages_config_shared.pkl.ts +73 -0
  57. package/gen/js/production/basket_eco_packages_config_worker.pkl.ts +49 -0
  58. package/gen/js/staging/basket_eco_packages_config_addresses.pkl.ts +106 -0
  59. package/gen/js/staging/basket_eco_packages_config_checkout.pkl.ts +77 -0
  60. package/gen/js/staging/basket_eco_packages_config_cloudflare.pkl.ts +74 -0
  61. package/gen/js/staging/basket_eco_packages_config_cms.pkl.ts +119 -0
  62. package/gen/js/staging/basket_eco_packages_config_db.pkl.ts +47 -0
  63. package/gen/js/staging/basket_eco_packages_config_dune.pkl.ts +40 -0
  64. package/gen/js/staging/basket_eco_packages_config_enterprise.pkl.ts +290 -0
  65. package/gen/js/staging/basket_eco_packages_config_feature.pkl.ts +58 -0
  66. package/gen/js/staging/basket_eco_packages_config_redis.pkl.ts +30 -0
  67. package/gen/js/staging/basket_eco_packages_config_renderer_staging_cms.pkl.ts +28 -0
  68. package/gen/js/staging/basket_eco_packages_config_renderer_staging_dune_config.pkl.ts +29 -0
  69. package/gen/js/staging/basket_eco_packages_config_renderer_staging_workflow_config.pkl.ts +29 -0
  70. package/gen/js/staging/basket_eco_packages_config_shared.pkl.ts +69 -0
  71. package/gen/js/staging/basket_eco_packages_config_worker.pkl.ts +53 -0
  72. package/package.json +66 -0
  73. package/src/addresses.ts +90 -0
  74. package/src/authorization/policy.conf +15 -0
  75. package/src/authorization/policy.csv +26 -0
  76. package/src/chk.spec.ts +61 -0
  77. package/src/chk.ts +88 -0
  78. package/src/cms.test.ts +52 -0
  79. package/src/cms.ts +65 -0
  80. package/src/index.ts +4 -0
  81. package/src/pkl/PklProject +21 -0
  82. package/src/pkl/development/Addresses.pkl +2114 -0
  83. package/src/pkl/development/CMS.pkl +37 -0
  84. package/src/pkl/development/Checkout.pkl +32 -0
  85. package/src/pkl/development/Cloudflare.pkl +20 -0
  86. package/src/pkl/development/Db.pkl +14 -0
  87. package/src/pkl/development/Dune.pkl +12 -0
  88. package/src/pkl/development/Enterprise.pkl +158 -0
  89. package/src/pkl/development/Feature.pkl +4 -0
  90. package/src/pkl/development/Redis.pkl +6 -0
  91. package/src/pkl/development/Shared.pkl +16 -0
  92. package/src/pkl/development/Worker.pkl +43 -0
  93. package/src/pkl/development/renderer/CMS.pkl +59 -0
  94. package/src/pkl/production/Addresses.pkl +2114 -0
  95. package/src/pkl/production/CMS.pkl +34 -0
  96. package/src/pkl/production/Checkout.pkl +32 -0
  97. package/src/pkl/production/Cloudflare.pkl +20 -0
  98. package/src/pkl/production/Db.pkl +14 -0
  99. package/src/pkl/production/Dune.pkl +12 -0
  100. package/src/pkl/production/Enterprise.pkl +158 -0
  101. package/src/pkl/production/Feature.pkl +8 -0
  102. package/src/pkl/production/KioskHost.pkl +5 -0
  103. package/src/pkl/production/Redis.pkl +6 -0
  104. package/src/pkl/production/Shared.pkl +15 -0
  105. package/src/pkl/production/Worker.pkl +43 -0
  106. package/src/pkl/production/renderer/CMS.pkl +59 -0
  107. package/src/pkl/production/renderer/Dune.pkl +52 -0
  108. package/src/pkl/production/renderer/Feature.pkl +51 -0
  109. package/src/pkl/production/renderer/Workflow.pkl +51 -0
  110. package/src/pkl/staging/Addresses.pkl +2114 -0
  111. package/src/pkl/staging/CMS.pkl +35 -0
  112. package/src/pkl/staging/Checkout.pkl +32 -0
  113. package/src/pkl/staging/Cloudflare.pkl +20 -0
  114. package/src/pkl/staging/Db.pkl +14 -0
  115. package/src/pkl/staging/Dune.pkl +12 -0
  116. package/src/pkl/staging/Enterprise.pkl +158 -0
  117. package/src/pkl/staging/Feature.pkl +8 -0
  118. package/src/pkl/staging/Redis.pkl +6 -0
  119. package/src/pkl/staging/Shared.pkl +16 -0
  120. package/src/pkl/staging/Worker.pkl +43 -0
  121. package/src/pkl/staging/renderer/CMS.pkl +59 -0
  122. package/src/pkl/staging/renderer/Dune.pkl +52 -0
  123. package/src/pkl/staging/renderer/Feature.pkl +51 -0
  124. package/src/pkl/staging/renderer/Workflow.pkl +51 -0
  125. package/src/pkl/tmpl/AddressesTmpl.pkl +44 -0
  126. package/src/pkl/tmpl/CMSTmpl.pkl +64 -0
  127. package/src/pkl/tmpl/CheckoutTmpl.pkl +34 -0
  128. package/src/pkl/tmpl/CloudflareTmpl.pkl +33 -0
  129. package/src/pkl/tmpl/DbTmpl.pkl +18 -0
  130. package/src/pkl/tmpl/DuneTmpl.pkl +124 -0
  131. package/src/pkl/tmpl/EnterpriseTmpl.pkl +105 -0
  132. package/src/pkl/tmpl/FeatureTmpl.pkl +90 -0
  133. package/src/pkl/tmpl/KioskHostTmpl.pkl +194 -0
  134. package/src/pkl/tmpl/RedisTmpl.pkl +9 -0
  135. package/src/pkl/tmpl/SharedTmpl.pkl +40 -0
  136. package/src/pkl/tmpl/WorkerTmpl.pkl +27 -0
  137. package/src/pkl-reader/main.go +167 -0
  138. package/src/shared.ts +65 -0
  139. package/src/vault.spec.ts +62 -0
  140. package/src/vault.ts +68 -0
@@ -0,0 +1,40 @@
1
+ @go.Package { name = "paakd.com/packages/config/gen/go/shared" }
2
+ module paakd.com.packages.config.Shared
3
+
4
+ import "package://pkg.pkl-lang.org/pkl-go/pkl.golang@0.12.1#/go.pkl"
5
+
6
+ class Build {
7
+ enterpriseEnv: String
8
+ envPath: String
9
+ hidden envFileContent:Mapping = read*(envPath)
10
+ hidden envData = envFileContent[envPath].text.split("\n").map((it) -> it.split("=")).toMap((it) -> it[0].trim(), (it) -> if (it.length == 2) it[1].trim() else "")
11
+ const binUser: String = "owe"
12
+ const binUserGroup: String = "kiosk"
13
+ const binUserId: UInt16 = 10001
14
+ certPath: String
15
+ const serviceDomainName: String = "shofluent.com"
16
+ vaultUrl: String = "https://vault.shofluent.xyz"
17
+ goEnv: String
18
+ nodeEnv: String
19
+ shopId: String = envData.getOrNull("SHOP_ID")
20
+ hidden vaultToken: String = envData.getOrNull("VAULT_TOKEN")
21
+ tenant: String = "\(shopId).\(serviceDomainName)"
22
+ envFilePath: String
23
+ shopBucketName: String = "shop-bucket-\(shopId)"
24
+ const productTaxonomyIndexBucket: String = "taxonomy-index"
25
+ fixed apiVersion: String = "v0.1"
26
+
27
+ envFile: String
28
+ version: String
29
+ storefrontHost: String
30
+ storefrontPort: UInt16
31
+ secureCookiePassword: String
32
+
33
+ function getAssetsDomain(): String = "assets.\(shopId).\(serviceDomainName)"
34
+ function getAssetsURL():String = "https://\(getAssetsDomain())"
35
+ function shopVaultEnvPath(val: String): String = "vault:\(vaultToken)~shops/\(shopId)/env~\(val)"
36
+ function shopVaultThirdpartyPath(val: String): String = "vault:\(vaultToken)~services/third_party~\(val)"
37
+ function shopVaultInternalEnvPath(val: String): String = "vault:\(vaultToken)~shops/\(shopId)/internal/env~\(val)"
38
+ }
39
+
40
+ build: Build
@@ -0,0 +1,27 @@
1
+ @go.Package { name = "paakd.com/packages/config/gen/go/worker" }
2
+ module paakd.com.packages.config.Worker
3
+
4
+ import "package://pkg.pkl-lang.org/pkl-go/pkl.golang@0.12.1#/go.pkl"
5
+
6
+
7
+ class WorkerConnect {
8
+ endpoint: String
9
+ hidden user: String
10
+ hidden password: String
11
+ local baseValue: String = "\(user):\(password)".base64
12
+
13
+ function getBasicAuth(): String = "Basic \(baseValue)"
14
+ }
15
+
16
+ breeze:WorkerConnect
17
+ orchestrator:WorkerConnect
18
+ mesh:WorkerConnect
19
+ tfState:WorkerConnect
20
+ tfStateShopBackend:WorkerConnect
21
+ tfStateServiceBackend:WorkerConnect
22
+ tfStateHostsBackend:WorkerConnect
23
+
24
+ orchestratorAuth: String
25
+ shopBackendAuth: String
26
+ serviceBackendAuth: String
27
+ hostBackendAuth: String
@@ -0,0 +1,167 @@
1
+ package main
2
+
3
+ import (
4
+ "errors"
5
+ "fmt"
6
+ "io"
7
+ "net/http"
8
+ "net/url"
9
+ "os"
10
+ "strings"
11
+ "sync"
12
+ "time"
13
+
14
+ "github.com/apple/pkl-go/pkl"
15
+ json_encoding "github.com/segmentio/encoding/json"
16
+ "go.uber.org/zap"
17
+ )
18
+
19
+ var (
20
+ VAULT_ADDR = os.Getenv("VAULT_ADDR")
21
+ VAULT_TOKEN = os.Getenv("VAULT_TOKEN")
22
+ CACHE_TTL_MIN = 15 * time.Minute
23
+ )
24
+
25
+ type (
26
+ vaultReader struct {
27
+ logger *zap.Logger
28
+ }
29
+
30
+ ResStore struct {
31
+ Data map[string]map[string]interface{}
32
+ CreatedAt time.Time
33
+ sync.Mutex
34
+ }
35
+
36
+ VaultRes struct {
37
+ Data map[string]interface{} `json:"data"`
38
+ }
39
+ )
40
+
41
+ var (
42
+ _ pkl.ResourceReader = &vaultReader{logger: zap.NewNop()}
43
+
44
+ responseCache = ResStore{
45
+ Data: make(map[string]map[string]interface{}),
46
+ }
47
+ )
48
+
49
+ func main() {
50
+ if VAULT_ADDR == "" {
51
+ VAULT_ADDR = "https://vault.shofluent.xyz"
52
+ }
53
+
54
+ logger, _ := zap.NewDevelopment()
55
+ client, err := pkl.NewExternalReaderClient(pkl.WithExternalClientResourceReader(vaultReader{
56
+ logger: logger,
57
+ }))
58
+ if err != nil {
59
+ logger.Fatal("create pkl client", zap.Error(err))
60
+ }
61
+ if err := client.Run(); err != nil {
62
+ logger.Fatal("run pkl client", zap.Error(err))
63
+ }
64
+ }
65
+
66
+ func (r vaultReader) Scheme() string {
67
+ return "vault"
68
+ }
69
+
70
+ func (r vaultReader) HasHierarchicalUris() bool {
71
+ return false
72
+ }
73
+
74
+ func (r vaultReader) IsGlobbable() bool {
75
+ return false
76
+ }
77
+
78
+ func (r vaultReader) ListElements(baseURI url.URL) ([]pkl.PathElement, error) {
79
+ r.logger.Debug("vaultReader.ListElements", zap.String("baseURI", baseURI.String()))
80
+ return nil, nil
81
+ }
82
+
83
+ func (r vaultReader) makeAPICallToVault(vaultToken, path string) (map[string]interface{}, error) {
84
+ if VAULT_TOKEN == "" {
85
+ VAULT_TOKEN = vaultToken
86
+ }
87
+
88
+ var (
89
+ header = map[string]string{
90
+ "X-Vault-Token": VAULT_TOKEN,
91
+ }
92
+ url = VAULT_ADDR + "/v1/secret/data/" + path
93
+ )
94
+
95
+ responseCache.Lock()
96
+ defer responseCache.Unlock()
97
+ if data, found := responseCache.Data[url]; found && time.Since(responseCache.CreatedAt) < CACHE_TTL_MIN {
98
+ return data, nil
99
+ }
100
+
101
+ r.logger.Debug("vaultReader.makeAPICallToVault", zap.String("url", url))
102
+ req, err := http.NewRequest("GET", url, nil)
103
+ if err != nil {
104
+ r.logger.Error("create request", zap.Error(err))
105
+ return nil, err
106
+ }
107
+ for key, value := range header {
108
+ req.Header.Add(key, value)
109
+ }
110
+ client := &http.Client{}
111
+ resp, err := client.Do(req)
112
+ if err != nil {
113
+ r.logger.Error("make request", zap.Error(err))
114
+ return nil, err
115
+ }
116
+ defer func() { _ = resp.Body.Close() }()
117
+ if resp.StatusCode != http.StatusOK {
118
+ // ready body string
119
+ bodyBytes, _ := io.ReadAll(resp.Body)
120
+ r.logger.Error("get data from vault", zap.String("body", string(bodyBytes)))
121
+ return nil, fmt.Errorf("get data from vault %w", err)
122
+ }
123
+ var res VaultRes
124
+ if err := json_encoding.NewDecoder(resp.Body).Decode(&res); err != nil {
125
+ r.logger.Error("decode response", zap.Error(err))
126
+ return nil, err
127
+ }
128
+ if res.Data == nil {
129
+ r.logger.Error("no data found in vault")
130
+ return nil, errors.New("no data found in vault")
131
+ }
132
+ if data, ok := res.Data["data"].(map[string]interface{}); ok {
133
+ responseCache.Data[url] = data
134
+ responseCache.CreatedAt = time.Now()
135
+ return data, nil
136
+ } else {
137
+ return nil, errors.New("invalid data format")
138
+ }
139
+ }
140
+
141
+ func (r vaultReader) Read(uri url.URL) ([]byte, error) {
142
+ path := strings.Split(uri.Opaque, "~")
143
+ if len(path) != 3 {
144
+ r.logger.Error("invalid vault path", zap.String("path", uri.Opaque))
145
+ return nil, errors.New("invalid vault path")
146
+ }
147
+
148
+ if os.Getenv("ENTERPRISE_ENV") == "docker" || os.Getenv("CI") != "" {
149
+ return []byte(path[2]), nil
150
+ }
151
+ data, err := r.makeAPICallToVault(path[0], path[1])
152
+ if err != nil {
153
+ r.logger.Error("read vault secret", zap.Error(err))
154
+ return nil, err
155
+ }
156
+
157
+ switch data[path[2]].(type) {
158
+ case string:
159
+ return []byte(data[path[2]].(string)), nil
160
+ case []byte:
161
+ return data[path[2]].([]byte), nil
162
+ case nil:
163
+ return nil, nil
164
+ default:
165
+ return nil, errors.New("invalid data type")
166
+ }
167
+ }
package/src/shared.ts ADDED
@@ -0,0 +1,65 @@
1
+ import * as pklTypescript from '@pkl-community/pkl-typescript'
2
+ import { load as devLoad } from '../gen/js/development/basket_eco_packages_config_shared.pkl'
3
+ import {
4
+ load as prodLoad,
5
+ type Shared,
6
+ } from '../gen/js/production/basket_eco_packages_config_shared.pkl'
7
+ import { load as stgLoad } from '../gen/js/staging/basket_eco_packages_config_shared.pkl'
8
+ import { vaultReader } from './vault'
9
+
10
+ export const getSharedConfig: () => Promise<Shared> = async () => {
11
+ const evaluator = await pklTypescript.newEvaluator({
12
+ allowedResources: [
13
+ 'http:',
14
+ 'https:',
15
+ 'file:',
16
+ 'env:',
17
+ 'prop:',
18
+ 'modulepath:',
19
+ 'package:',
20
+ 'projectpackage:',
21
+ 'vault:',
22
+ ],
23
+ allowedModules: [
24
+ 'pkl:',
25
+ 'repl:',
26
+ 'file:',
27
+ 'http:',
28
+ 'https:',
29
+ 'modulepath:',
30
+ 'package:',
31
+ 'projectpackage:',
32
+ ],
33
+ projectDir: '../../packages/config/src/pkl/production',
34
+ declaredProjectDependencies: {
35
+ localDependencies: {},
36
+ remoteDependencies: {},
37
+ },
38
+ resourceReaders: [
39
+ {
40
+ scheme: 'vault',
41
+ isGlobbable: false,
42
+ hasHierarchicalUris: false,
43
+ listElements: (uri: URL): any => {
44
+ return []
45
+ },
46
+ read: vaultReader,
47
+ },
48
+ ],
49
+ })
50
+ try {
51
+ let load = devLoad
52
+ let sourcePath = '../../packages/config/src/pkl/development/Shared.pkl'
53
+ if (process.env.BUILD_ENV === 'staging') {
54
+ load = stgLoad
55
+ sourcePath = '../../packages/config/src/pkl/staging/Shared.pkl'
56
+ } else if (process.env.BUILD_ENV === 'production') {
57
+ load = prodLoad
58
+ sourcePath = '../../packages/config/src/pkl/production/Shared.pkl'
59
+ }
60
+ const result = await load(evaluator, pklTypescript.FileSource(sourcePath))
61
+ return result
62
+ } finally {
63
+ evaluator.close()
64
+ }
65
+ }
@@ -0,0 +1,62 @@
1
+ import { vaultReader } from './vault'
2
+
3
+ describe('vaultReader', () => {
4
+ const OLD_ENV = process.env
5
+
6
+ beforeEach(() => {
7
+ process.env = { ...OLD_ENV }
8
+ })
9
+
10
+ afterEach(() => {
11
+ process.env = OLD_ENV
12
+ })
13
+
14
+ it('returns CI_ENVIRONMENT buffer in CI', async () => {
15
+ process.env.CI = '1'
16
+ const olVal = process.env.VITEST
17
+ delete process.env.VITEST // Ensure VITEST is not set
18
+ const uri = new URL('vault://dummy/vaultkey~datapath~field')
19
+ const result = await vaultReader(uri)
20
+ expect(Buffer.from(result).toString()).toBe('CI_ENVIRONMENT')
21
+ process.env.VITEST = olVal // Restore VITEST
22
+ })
23
+
24
+ it('returns empty Uint8Array for invalid path', async () => {
25
+ const uri = new URL('vault://dummy/vaultkey~datapath')
26
+ const result = await vaultReader(uri)
27
+ expect(result).toBeInstanceOf(Uint8Array)
28
+ expect(result.length).toBe(0)
29
+ })
30
+
31
+ it('returns empty Uint8Array for non-ok fetch', async () => {
32
+ global.fetch = vi.fn().mockResolvedValue({ ok: false, statusText: 'fail' })
33
+ const uri = new URL('vault://dummy/vaultkey~datapath~field')
34
+ delete process.env.CI
35
+ const result = await vaultReader(uri)
36
+ expect(result).toBeInstanceOf(Uint8Array)
37
+ expect(result.length).toBe(0)
38
+ })
39
+
40
+ it('returns empty Uint8Array for missing field', async () => {
41
+ global.fetch = vi.fn().mockResolvedValue({
42
+ ok: true,
43
+ json: async () => ({ data: { data: { other: 'value' } } }),
44
+ })
45
+ const uri = new URL('vault://dummy/vaultkey~datapath~field')
46
+ const result = await vaultReader(uri)
47
+ expect(result).toBeInstanceOf(Uint8Array)
48
+ expect(result.length).toBe(0)
49
+ })
50
+
51
+ it('returns decoded buffer for valid field', async () => {
52
+ global.fetch = vi.fn().mockResolvedValue({
53
+ ok: true,
54
+ json: async () => ({
55
+ data: { data: { field2: Buffer.from('hello').toString() } },
56
+ }),
57
+ })
58
+ const uri = new URL('vault://dummy/vaultkey2~datapath2~field2')
59
+ const result = await vaultReader(uri)
60
+ expect(Buffer.from(result).toString()).toBe('hello')
61
+ })
62
+ })
package/src/vault.ts ADDED
@@ -0,0 +1,68 @@
1
+ // Simple in-memory cache using a Map
2
+ export const vaultPathCache = new Map()
3
+ // const vaultPathCache = new Map<string, Uint8Array>();
4
+ export const vaultReader = async (uri: URL): Promise<Uint8Array> => {
5
+ // Remove leading slash if present
6
+ const cleanPath = uri.pathname.startsWith('/')
7
+ ? uri.pathname.slice(1)
8
+ : uri.pathname
9
+ const path = cleanPath.split('~')
10
+ if (path.length !== 3) {
11
+ console.error(`Invalid vault path: ${uri}`)
12
+ return new Uint8Array()
13
+ }
14
+
15
+ if (process.env.CI && !process.env.VITEST) {
16
+ // In CI, we don't want to access the vault, so we return an empty Uint8Array
17
+ // This is useful for testing purposes where we don't want to hit the actual vault
18
+ console.warn('Skipping vault access in CI environment')
19
+ return new Uint8Array(Buffer.from('CI_ENVIRONMENT', 'utf-8'))
20
+ }
21
+
22
+ const [vaultKey, dataPath, fieldName] = path
23
+ let resData = vaultPathCache.get(`${vaultKey}~${dataPath}`)
24
+ if (!resData || process.env.CI) {
25
+ const vaultAddr = process.env.VAULT_PATH || 'https://vault.shofluent.xyz'
26
+ const url = `${vaultAddr}/v1/secret/data/${dataPath}`
27
+ const headers = {
28
+ 'X-Vault-Token': vaultKey,
29
+ 'Content-Type': 'application/json',
30
+ Accept: 'application/json',
31
+ }
32
+ const res = await fetch(url, {
33
+ method: 'GET',
34
+ headers,
35
+ })
36
+ if (!res.ok) {
37
+ console.error(`fetch from vault: ${res.statusText}`)
38
+ return new Uint8Array()
39
+ }
40
+ const { data } = await res.json()
41
+ resData = data?.data
42
+ // Store value with timestamp for manual expiration
43
+ vaultPathCache.set(`${vaultKey}~${dataPath}`, {
44
+ value: resData,
45
+ expires: Date.now() + 15 * 60 * 1000, // 15 minutes
46
+ })
47
+ } else if (resData.expires && resData.expires < Date.now()) {
48
+ // Expired, remove and fetch again
49
+ vaultPathCache.delete(`${vaultKey}~${dataPath}`)
50
+ return vaultReader(uri) // Recursively re-fetch
51
+ } else {
52
+ resData = resData.value
53
+ }
54
+ if (!resData?.[fieldName]) {
55
+ console.error(
56
+ `Invalid data from vault: ${JSON.stringify(resData)}, ${Object.keys(resData)}`
57
+ )
58
+ return new Uint8Array()
59
+ }
60
+
61
+ const fieldData = resData[fieldName]
62
+ if (typeof fieldData !== 'string') {
63
+ console.error(`Invalid field data from vault: ${JSON.stringify(fieldData)}`)
64
+ return new Uint8Array()
65
+ }
66
+ const decodedData = Buffer.from(fieldData)
67
+ return decodedData as Uint8Array
68
+ }