@pikku/core 0.12.96 → 0.12.97
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/dist/middleware/index.d.ts +1 -0
- package/dist/middleware/index.js +1 -0
- package/dist/middleware/require-origin.d.ts +23 -0
- package/dist/middleware/require-origin.js +57 -0
- package/dist/services/secret-service.d.ts +5 -0
- package/dist/services/typed-variables-service.d.ts +34 -0
- package/dist/services/typed-variables-service.js +69 -3
- package/package.json +1 -1
- package/src/middleware/index.ts +1 -0
- package/src/middleware/require-origin.test.ts +115 -0
- package/src/middleware/require-origin.ts +79 -0
- package/src/public-surface.json +4 -1
- package/src/services/secret-service.ts +5 -0
- package/src/services/typed-variables-service.test.ts +93 -0
- package/src/services/typed-variables-service.ts +94 -3
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { StandardSchemaV1 } from '@standard-schema/spec'
|
|
1
2
|
import type { VariablesService } from './variables-service.js'
|
|
2
3
|
|
|
3
4
|
export interface VariableStatus {
|
|
@@ -5,13 +6,35 @@ export interface VariableStatus {
|
|
|
5
6
|
name: string
|
|
6
7
|
displayName: string
|
|
7
8
|
isConfigured: boolean
|
|
9
|
+
/** Whether the declaration answers for itself when the host sets nothing. */
|
|
10
|
+
hasDefault: boolean
|
|
8
11
|
}
|
|
9
12
|
|
|
10
13
|
export type VariableMeta = {
|
|
11
14
|
name: string
|
|
12
15
|
displayName: string
|
|
16
|
+
/**
|
|
17
|
+
* The shape the variable was declared with. It is the schema itself rather
|
|
18
|
+
* than a description of it, because a default is only knowable by running it:
|
|
19
|
+
* `undefined` goes in and, if the declaration carries one, the default comes
|
|
20
|
+
* back out.
|
|
21
|
+
*
|
|
22
|
+
* A thunk is accepted, and is what code generation emits. The generated file
|
|
23
|
+
* and the file declaring the schema import each other, so reading the schema
|
|
24
|
+
* while the modules are still initializing throws — deferring the read until
|
|
25
|
+
* a variable is actually asked for is what keeps the cycle harmless.
|
|
26
|
+
*/
|
|
27
|
+
schema?: StandardSchemaV1 | (() => StandardSchemaV1)
|
|
13
28
|
}
|
|
14
29
|
|
|
30
|
+
const isPromise = (value: unknown): value is Promise<unknown> =>
|
|
31
|
+
typeof (value as Promise<unknown> | undefined)?.then === 'function'
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* A declared default is the answer to a variable nobody set, so it is resolved
|
|
35
|
+
* here rather than in `VariablesService`: the store knows what a host has put
|
|
36
|
+
* in it, and only this layer knows what was declared.
|
|
37
|
+
*/
|
|
15
38
|
export class TypedVariablesService<
|
|
16
39
|
TMap = Record<string, unknown>,
|
|
17
40
|
> implements VariablesService {
|
|
@@ -25,13 +48,23 @@ export class TypedVariablesService<
|
|
|
25
48
|
): Promise<TMap[K] | undefined> | TMap[K] | undefined
|
|
26
49
|
get<T = string>(name: string): Promise<T | undefined> | T | undefined
|
|
27
50
|
get(name: string): Promise<unknown> | unknown {
|
|
28
|
-
|
|
51
|
+
const stored = this.variables.get(name)
|
|
52
|
+
if (isPromise(stored)) {
|
|
53
|
+
return stored.then((value) =>
|
|
54
|
+
value === undefined ? this.resolveDefault(name) : value
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
return stored === undefined ? this.resolveDefault(name) : stored
|
|
29
58
|
}
|
|
30
59
|
|
|
31
60
|
getVariables<T extends Record<string, unknown> = Record<string, unknown>>(
|
|
32
61
|
names: (keyof T & string)[]
|
|
33
62
|
): Promise<Partial<T>> | Partial<T> {
|
|
34
|
-
|
|
63
|
+
const stored = this.variables.getVariables<T>(names)
|
|
64
|
+
if (isPromise(stored)) {
|
|
65
|
+
return stored.then((values) => this.withDefaults(names, values))
|
|
66
|
+
}
|
|
67
|
+
return this.withDefaults(names, stored)
|
|
35
68
|
}
|
|
36
69
|
|
|
37
70
|
getAll():
|
|
@@ -62,14 +95,72 @@ export class TypedVariablesService<
|
|
|
62
95
|
name: meta.name,
|
|
63
96
|
displayName: meta.displayName,
|
|
64
97
|
isConfigured: all[variableId] !== undefined,
|
|
98
|
+
hasDefault: (await this.resolveDefault(variableId)) !== undefined,
|
|
65
99
|
})
|
|
66
100
|
}
|
|
67
101
|
|
|
68
102
|
return results
|
|
69
103
|
}
|
|
70
104
|
|
|
105
|
+
/**
|
|
106
|
+
* What a deployment still has to be told. A variable that defaults is not on
|
|
107
|
+
* this list — it has a value, just not one anybody has to supply.
|
|
108
|
+
*/
|
|
71
109
|
async getMissing(): Promise<VariableStatus[]> {
|
|
72
110
|
const all = await this.getAllStatus()
|
|
73
|
-
return all.filter((v) => !v.isConfigured)
|
|
111
|
+
return all.filter((v) => !v.isConfigured && !v.hasDefault)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* The value the declaration answers with when the host set nothing, or
|
|
116
|
+
* `undefined` when it does not answer for itself.
|
|
117
|
+
*/
|
|
118
|
+
private resolveDefault(name: string): Promise<unknown> | unknown {
|
|
119
|
+
const declared = this.variablesMeta[name]?.schema
|
|
120
|
+
if (!declared) {
|
|
121
|
+
return undefined
|
|
122
|
+
}
|
|
123
|
+
const schema = typeof declared === 'function' ? declared() : declared
|
|
124
|
+
const result = schema['~standard'].validate(undefined)
|
|
125
|
+
if (isPromise(result)) {
|
|
126
|
+
return result.then(unwrapDefault)
|
|
127
|
+
}
|
|
128
|
+
return unwrapDefault(result)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Kept synchronous when the defaults resolve synchronously, so a caller that
|
|
133
|
+
* did not await `getVariables` before does not have to start.
|
|
134
|
+
*/
|
|
135
|
+
private withDefaults<T extends Record<string, unknown>>(
|
|
136
|
+
names: (keyof T & string)[],
|
|
137
|
+
values: Partial<T>
|
|
138
|
+
): Promise<Partial<T>> | Partial<T> {
|
|
139
|
+
const out: Record<string, unknown> = { ...values }
|
|
140
|
+
const pending: Promise<void>[] = []
|
|
141
|
+
for (const name of names) {
|
|
142
|
+
if (out[name] !== undefined) continue
|
|
143
|
+
const fallback = this.resolveDefault(name)
|
|
144
|
+
if (isPromise(fallback)) {
|
|
145
|
+
pending.push(
|
|
146
|
+
fallback.then((value) => {
|
|
147
|
+
if (value !== undefined) out[name] = value
|
|
148
|
+
})
|
|
149
|
+
)
|
|
150
|
+
} else if (fallback !== undefined) {
|
|
151
|
+
out[name] = fallback
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (pending.length > 0) {
|
|
155
|
+
return Promise.all(pending).then(() => out as Partial<T>)
|
|
156
|
+
}
|
|
157
|
+
return out as Partial<T>
|
|
74
158
|
}
|
|
75
159
|
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* A schema with no default rejects `undefined`, which is not a failure here —
|
|
163
|
+
* it is the answer that there is nothing to fall back to.
|
|
164
|
+
*/
|
|
165
|
+
const unwrapDefault = (result: StandardSchemaV1.Result<unknown>) =>
|
|
166
|
+
result.issues ? undefined : result.value
|