@highstate/common 0.9.16 → 0.9.19

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 (48) hide show
  1. package/dist/{chunk-HZBJ6LLS.js → chunk-WDYIUWYZ.js} +659 -267
  2. package/dist/chunk-WDYIUWYZ.js.map +1 -0
  3. package/dist/highstate.manifest.json +12 -8
  4. package/dist/index.js +1 -1
  5. package/dist/units/access-point/index.js +16 -0
  6. package/dist/units/access-point/index.js.map +1 -0
  7. package/dist/units/databases/existing-mariadb/index.js +17 -0
  8. package/dist/units/databases/existing-mariadb/index.js.map +1 -0
  9. package/dist/units/databases/existing-mongodb/index.js +17 -0
  10. package/dist/units/databases/existing-mongodb/index.js.map +1 -0
  11. package/dist/units/databases/existing-postgresql/index.js +17 -0
  12. package/dist/units/databases/existing-postgresql/index.js.map +1 -0
  13. package/dist/units/dns/record-set/index.js +22 -11
  14. package/dist/units/dns/record-set/index.js.map +1 -1
  15. package/dist/units/existing-server/index.js +12 -12
  16. package/dist/units/existing-server/index.js.map +1 -1
  17. package/dist/units/network/l3-endpoint/index.js +1 -1
  18. package/dist/units/network/l3-endpoint/index.js.map +1 -1
  19. package/dist/units/network/l4-endpoint/index.js +1 -1
  20. package/dist/units/network/l4-endpoint/index.js.map +1 -1
  21. package/dist/units/script/index.js +1 -1
  22. package/dist/units/script/index.js.map +1 -1
  23. package/dist/units/server-dns/index.js +1 -1
  24. package/dist/units/server-dns/index.js.map +1 -1
  25. package/dist/units/server-patch/index.js +1 -1
  26. package/dist/units/server-patch/index.js.map +1 -1
  27. package/dist/units/ssh/key-pair/index.js +6 -6
  28. package/dist/units/ssh/key-pair/index.js.map +1 -1
  29. package/package.json +61 -8
  30. package/src/shared/access-point.ts +110 -0
  31. package/src/shared/command.ts +310 -69
  32. package/src/shared/dns.ts +150 -90
  33. package/src/shared/files.ts +34 -34
  34. package/src/shared/gateway.ts +117 -0
  35. package/src/shared/impl-ref.ts +123 -0
  36. package/src/shared/index.ts +4 -0
  37. package/src/shared/network.ts +41 -27
  38. package/src/shared/passwords.ts +38 -2
  39. package/src/shared/ssh.ts +261 -126
  40. package/src/shared/tls.ts +123 -0
  41. package/src/units/access-point/index.ts +12 -0
  42. package/src/units/databases/existing-mariadb/index.ts +14 -0
  43. package/src/units/databases/existing-mongodb/index.ts +14 -0
  44. package/src/units/databases/existing-postgresql/index.ts +14 -0
  45. package/src/units/dns/record-set/index.ts +21 -11
  46. package/src/units/existing-server/index.ts +12 -17
  47. package/src/units/ssh/key-pair/index.ts +6 -6
  48. package/dist/chunk-HZBJ6LLS.js.map +0 -1
@@ -0,0 +1,123 @@
1
+ import type { ImplementationReference } from "@highstate/library"
2
+ import type { z } from "@highstate/contract"
3
+ import { type Output, output, toPromise, type Input, type Unwrap } from "@highstate/pulumi"
4
+
5
+ /**
6
+ * The ImplementationMediator is used as a contract between the calling code and the implementation.
7
+ *
8
+ * From the calling code perspective, it provides a way to define the input and output schemas for the implementation
9
+ * and call the implementation with the provided input.
10
+ *
11
+ * From the implementation perspective, it provides a way to get zod function with automatic type inference and validation.
12
+ */
13
+ export class ImplementationMediator<
14
+ TInputSchema extends z.ZodType,
15
+ TOutputSchema extends z.ZodType,
16
+ > {
17
+ constructor(
18
+ readonly path: string,
19
+ private readonly inputSchema: TInputSchema,
20
+ private readonly outputSchema: TOutputSchema,
21
+ ) {}
22
+
23
+ implement<TDataSchema extends z.ZodType>(
24
+ dataSchema: TDataSchema,
25
+ func: (
26
+ input: z.infer<TInputSchema>,
27
+ data: z.infer<TDataSchema>,
28
+ ) => z.infer<TOutputSchema> | Promise<z.infer<TOutputSchema>>,
29
+ ) {
30
+ return async (
31
+ input: z.infer<TInputSchema>,
32
+ data: z.infer<TDataSchema>,
33
+ ): Promise<z.infer<TOutputSchema>> => {
34
+ const parsedInput = this.inputSchema.safeParse(input)
35
+ if (!parsedInput.success) {
36
+ throw new Error(
37
+ `Invalid input for implementation "${this.path}": ${parsedInput.error.message}`,
38
+ )
39
+ }
40
+
41
+ const parsedData = dataSchema.safeParse(data)
42
+ if (!parsedData.success) {
43
+ throw new Error(
44
+ `Invalid data for implementation "${this.path}": ${parsedData.error.message}`,
45
+ )
46
+ }
47
+
48
+ const result = await func(parsedInput.data, parsedData.data)
49
+ const parsedResult = this.outputSchema.safeParse(result)
50
+
51
+ if (!parsedResult.success) {
52
+ throw new Error(
53
+ `Invalid output from implementation "${this.path}": ${parsedResult.error.message}`,
54
+ )
55
+ }
56
+
57
+ return parsedResult.data
58
+ }
59
+ }
60
+
61
+ async call(
62
+ implRef: Input<ImplementationReference>,
63
+ input: Input<z.infer<TInputSchema>>,
64
+ ): Promise<z.infer<TOutputSchema>> {
65
+ const resolvedImplRef = await toPromise(implRef)
66
+ const resolvedInput = await toPromise(input)
67
+
68
+ const importPath = `${resolvedImplRef.package}/impl/${this.path}`
69
+
70
+ let impl: Record<string, unknown>
71
+ try {
72
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
73
+ impl = await import(importPath)
74
+ } catch (error) {
75
+ throw new Error(`Failed to import module "${importPath}" required by implementation.`, {
76
+ cause: error,
77
+ })
78
+ }
79
+
80
+ const funcs = Object.entries(impl).filter(value => typeof value[1] === "function") as [
81
+ string,
82
+ Function,
83
+ ][]
84
+
85
+ if (funcs.length === 0) {
86
+ throw new Error(`No implementation functions found in module "${importPath}".`)
87
+ }
88
+
89
+ if (funcs.length > 1) {
90
+ throw new Error(
91
+ `Multiple implementation functions found in module "${importPath}": ${funcs.map(func => func[0]).join(", ")}. ` +
92
+ "Ensure only one function is exported.",
93
+ )
94
+ }
95
+
96
+ const [funcName, implFunc] = funcs[0]
97
+
98
+ let result: unknown
99
+ try {
100
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
101
+ result = await implFunc(resolvedInput, resolvedImplRef.data)
102
+ } catch (error) {
103
+ console.error(`Error in implementation function "${funcName}":`, error)
104
+ throw new Error(`Implementation function "${funcName}" failed`)
105
+ }
106
+
107
+ const parsedResult = this.outputSchema.safeParse(result)
108
+ if (!parsedResult.success) {
109
+ throw new Error(
110
+ `Implementation function "${funcName}" returned invalid result: ${parsedResult.error.message}`,
111
+ )
112
+ }
113
+
114
+ return parsedResult.data
115
+ }
116
+
117
+ callOutput(
118
+ implRef: Input<ImplementationReference>,
119
+ input: Input<z.infer<TInputSchema>>,
120
+ ): Output<Unwrap<z.infer<TOutputSchema>>> {
121
+ return output(this.call(implRef, input))
122
+ }
123
+ }
@@ -4,3 +4,7 @@ export * from "./passwords"
4
4
  export * from "./ssh"
5
5
  export * from "./network"
6
6
  export * from "./files"
7
+ export * from "./impl-ref"
8
+ export * from "./gateway"
9
+ export * from "./tls"
10
+ export * from "./access-point"
@@ -1,5 +1,5 @@
1
1
  import type { ArrayPatchMode, network } from "@highstate/library"
2
- import { toPromise, type Input } from "@highstate/pulumi"
2
+ import { type Input, toPromise } from "@highstate/pulumi"
3
3
  import { uniqueBy } from "remeda"
4
4
 
5
5
  /**
@@ -248,14 +248,14 @@ export async function requireInputL4Endpoint(
248
248
  }
249
249
 
250
250
  /**
251
- * Convers L3 endpoint to L4 endpoint by adding a port and protocol.
251
+ * Converts L3 endpoint to L4 endpoint by adding a port and protocol.
252
252
  *
253
253
  * @param l3Endpoint The L3 endpoint to convert.
254
254
  * @param port The port to add to the L3 endpoint.
255
255
  * @param protocol The protocol to add to the L3 endpoint. Defaults to "tcp".
256
256
  * @returns The L4 endpoint with the port and protocol added.
257
257
  */
258
- export function l3ToL4Endpoint(
258
+ export function l3EndpointToL4(
259
259
  l3Endpoint: InputL3Endpoint,
260
260
  port: number,
261
261
  protocol: network.L4Protocol = "tcp",
@@ -276,11 +276,14 @@ export function l3ToL4Endpoint(
276
276
  *
277
277
  * @returns The filtered list of endpoints.
278
278
  */
279
- export function filterEndpoints<TEndpoint extends network.L34Endpoint>(
279
+ export function filterEndpoints<
280
+ TEndpoint extends network.L34Endpoint,
281
+ TType extends network.L34Endpoint["type"],
282
+ >(
280
283
  endpoints: TEndpoint[],
281
284
  filter?: network.EndpointFilter,
282
- types?: network.L34Endpoint["type"][],
283
- ): TEndpoint[] {
285
+ types?: TType[],
286
+ ): (TEndpoint & { type: TType })[] {
284
287
  if (filter?.length) {
285
288
  endpoints = endpoints.filter(endpoint => filter.includes(endpoint.visibility))
286
289
  } else if (endpoints.some(endpoint => endpoint.visibility === "public")) {
@@ -289,11 +292,11 @@ export function filterEndpoints<TEndpoint extends network.L34Endpoint>(
289
292
  endpoints = endpoints.filter(endpoint => endpoint.visibility === "external")
290
293
  }
291
294
 
292
- if (types && types.length) {
293
- endpoints = endpoints.filter(endpoint => types.includes(endpoint.type))
295
+ if (types?.length) {
296
+ endpoints = endpoints.filter(endpoint => types.includes(endpoint.type as TType))
294
297
  }
295
298
 
296
- return endpoints
299
+ return endpoints as (TEndpoint & { type: TType })[]
297
300
  }
298
301
 
299
302
  /**
@@ -369,31 +372,42 @@ export function parseL7Endpoint(l7Endpoint: InputL7Endpoint): network.L7Endpoint
369
372
  * @param endpoints The new endpoints to add in string format.
370
373
  * @param inputEndpoints The input endpoints to add in object format.
371
374
  * @param mode The mode to use when updating the endpoints. Can be "replace" or "prepend". Defaults to "prepend".
372
- *
373
- * @returns The updated list of endpoints.
375
+ * @returns The updated list of endpoints with duplicates removed.
374
376
  */
375
- export async function updateEndpoints<TEdnpoints extends network.L34Endpoint>(
376
- currentEndpoints: Input<TEdnpoints[]>,
377
- endpoints: string[],
378
- inputEndpoints: Input<TEdnpoints[]>,
377
+ export async function updateEndpoints<TEndpoints extends network.L34Endpoint>(
378
+ currentEndpoints: Input<TEndpoints[]>,
379
+ endpoints: string[] | undefined,
380
+ inputEndpoints: Input<TEndpoints[]> | undefined,
379
381
  mode: ArrayPatchMode = "prepend",
380
- ): Promise<TEdnpoints[]> {
382
+ ): Promise<TEndpoints[]> {
381
383
  const resolvedCurrentEndpoints = await toPromise(currentEndpoints)
382
- const resolvedInputEndpoints = await toPromise(inputEndpoints)
383
-
384
- const newEndpoints = uniqueBy(
385
- //
386
- [...endpoints.map(parseL34Endpoint), ...resolvedInputEndpoints],
387
- endpoint => l34EndpointToString(endpoint),
388
- )
384
+ const newEndpoints = await parseEndpoints(endpoints, inputEndpoints)
389
385
 
390
386
  if (mode === "replace") {
391
- return newEndpoints as TEdnpoints[]
387
+ return newEndpoints as TEndpoints[]
392
388
  }
393
389
 
394
390
  return uniqueBy(
395
- //
396
391
  [...newEndpoints, ...resolvedCurrentEndpoints],
397
- endpoint => l34EndpointToString(endpoint),
398
- ) as TEdnpoints[]
392
+ l34EndpointToString,
393
+ ) as TEndpoints[]
394
+ }
395
+
396
+ /**
397
+ * Parses a list of endpoints from strings and input objects.
398
+ *
399
+ * @param endpoints The list of endpoint strings to parse.
400
+ * @param inputEndpoints The list of input endpoint objects to use.
401
+ * @returns The parsed list of endpoint objects with duplicates removed.
402
+ */
403
+ export async function parseEndpoints<TEndpoints extends network.L34Endpoint>(
404
+ endpoints: string[] | undefined,
405
+ inputEndpoints: Input<TEndpoints[]> | undefined,
406
+ ): Promise<TEndpoints[]> {
407
+ const resolvedInputEndpoints = await toPromise(inputEndpoints)
408
+
409
+ return uniqueBy(
410
+ [...(endpoints?.map(parseL34Endpoint) ?? []), ...(resolvedInputEndpoints ?? [])],
411
+ l34EndpointToString,
412
+ ) as TEndpoints[]
399
413
  }
@@ -1,6 +1,42 @@
1
- import { randomBytes } from "@noble/hashes/utils"
1
+ import { bytesToHex, randomBytes } from "@noble/hashes/utils"
2
2
  import { secureMask } from "micro-key-producer/password.js"
3
3
 
4
- export function generatePassword() {
4
+ /**
5
+ * Generates a secure random password strong enough for online use.
6
+ *
7
+ * It uses "Safari Keychain Secure Password" format.
8
+ *
9
+ * The approximate entropy is [71 bits](https://support.apple.com/guide/security/automatic-strong-passwords-secc84c811c4/web).
10
+ */
11
+ export function generatePassword(): string {
5
12
  return secureMask.apply(randomBytes(32)).password
6
13
  }
14
+
15
+ type KeyFormatMap = {
16
+ raw: Uint8Array
17
+ hex: string
18
+ base64: string
19
+ }
20
+
21
+ /**
22
+ * Generates a secure random key strong enough for offline use such as encryption.
23
+ *
24
+ * The strong entropy is 256 bits.
25
+ *
26
+ * @param format The format of the generated key. By default, it is "hex".
27
+ */
28
+ export function generateKey<TFormat extends keyof KeyFormatMap = "hex">(
29
+ format: TFormat = "hex" as TFormat,
30
+ ): KeyFormatMap[TFormat] {
31
+ const bytes = randomBytes(32)
32
+
33
+ if (format === "raw") {
34
+ return bytes as KeyFormatMap[TFormat]
35
+ }
36
+
37
+ if (format === "base64") {
38
+ return Buffer.from(bytes).toString("base64") as KeyFormatMap[TFormat]
39
+ }
40
+
41
+ return bytesToHex(bytes) as KeyFormatMap[TFormat]
42
+ }