@withstudiocms/sdk 0.0.0-beta.0 → 0.1.0-beta.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/LICENSE +21 -0
- package/README.md +21 -0
- package/dist/cache.d.ts +109 -0
- package/dist/cache.js +94 -0
- package/dist/consts.d.ts +28 -0
- package/dist/consts.js +26 -0
- package/dist/context.d.ts +188 -0
- package/dist/context.js +33 -0
- package/dist/index.d.ts +1136 -0
- package/dist/index.js +24 -0
- package/dist/lib/diff.d.ts +39 -0
- package/dist/lib/diff.js +29 -0
- package/dist/lib/logger.d.ts +31 -0
- package/dist/lib/logger.js +131 -0
- package/dist/lib/pluginUtils.d.ts +221 -0
- package/dist/lib/pluginUtils.js +80 -0
- package/dist/modules/auth/index.d.ts +463 -0
- package/dist/modules/auth/index.js +412 -0
- package/dist/modules/clear/index.d.ts +72 -0
- package/dist/modules/clear/index.js +52 -0
- package/dist/modules/config/consts.d.ts +32 -0
- package/dist/modules/config/consts.js +18 -0
- package/dist/modules/config/index.d.ts +100 -0
- package/dist/modules/config/index.js +205 -0
- package/dist/modules/config/templates/mailer.d.ts +36 -0
- package/dist/modules/config/templates/mailer.js +218 -0
- package/dist/modules/config/type-utils.d.ts +13 -0
- package/dist/modules/config/type-utils.js +11 -0
- package/dist/modules/delete/index.d.ts +140 -0
- package/dist/modules/delete/index.js +274 -0
- package/dist/modules/diffTracking/index.d.ts +188 -0
- package/dist/modules/diffTracking/index.js +276 -0
- package/dist/modules/get/index.d.ts +272 -0
- package/dist/modules/get/index.js +466 -0
- package/dist/modules/index.d.ts +1003 -0
- package/dist/modules/index.js +37 -0
- package/dist/modules/init/index.d.ts +60 -0
- package/dist/modules/init/index.js +38 -0
- package/dist/modules/middleware/index.d.ts +56 -0
- package/dist/modules/middleware/index.js +50 -0
- package/dist/modules/notificationSettings/index.d.ts +57 -0
- package/dist/modules/notificationSettings/index.js +39 -0
- package/dist/modules/plugins/index.d.ts +166 -0
- package/dist/modules/plugins/index.js +261 -0
- package/dist/modules/post/index.d.ts +305 -0
- package/dist/modules/post/index.js +305 -0
- package/dist/modules/resetTokenBucket/index.d.ts +91 -0
- package/dist/modules/resetTokenBucket/index.js +93 -0
- package/dist/modules/rest_api/index.d.ts +92 -0
- package/dist/modules/rest_api/index.js +113 -0
- package/dist/modules/update/index.d.ts +184 -0
- package/dist/modules/update/index.js +174 -0
- package/dist/modules/util/collectors.d.ts +261 -0
- package/dist/modules/util/collectors.js +141 -0
- package/dist/modules/util/folderTree.d.ts +100 -0
- package/dist/modules/util/folderTree.js +176 -0
- package/dist/modules/util/generators.d.ts +83 -0
- package/dist/modules/util/generators.js +106 -0
- package/dist/modules/util/getFromNPM.d.ts +191 -0
- package/dist/modules/util/getFromNPM.js +100 -0
- package/dist/modules/util/index.d.ts +236 -0
- package/dist/modules/util/index.js +20 -0
- package/dist/modules/util/parsers.d.ts +60 -0
- package/dist/modules/util/parsers.js +43 -0
- package/dist/modules/util/slugify.d.ts +22 -0
- package/dist/modules/util/slugify.js +19 -0
- package/dist/modules/util/users.d.ts +99 -0
- package/dist/modules/util/users.js +78 -0
- package/dist/types.d.ts +360 -0
- package/dist/types.js +10 -0
- package/package.json +55 -7
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Effect } from '@withstudiocms/effect';
|
|
2
|
+
import type { JwtVerificationResult } from '../../types.js';
|
|
3
|
+
declare const GeneratorError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => import("effect/Cause").YieldableError & {
|
|
4
|
+
readonly _tag: "GeneratorError";
|
|
5
|
+
} & Readonly<A>;
|
|
6
|
+
/**
|
|
7
|
+
* Represents errors that occur during generator operations.
|
|
8
|
+
*/
|
|
9
|
+
export declare class GeneratorError extends GeneratorError_base<{
|
|
10
|
+
cause: unknown;
|
|
11
|
+
}> {
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A helper function to wrap generator operations in an Effect with error handling.
|
|
15
|
+
*
|
|
16
|
+
* @param _try - A function that performs the generator operation.
|
|
17
|
+
* @returns An Effect that either yields the result of the operation or fails with a GeneratorError.
|
|
18
|
+
*/
|
|
19
|
+
export declare const useGeneratorError: <A>(_try: () => A) => Effect.Effect<A, GeneratorError, never>;
|
|
20
|
+
/**
|
|
21
|
+
* SDKGenerators
|
|
22
|
+
*
|
|
23
|
+
* Effect generator that builds a collection of utility functions used across the SDK.
|
|
24
|
+
*
|
|
25
|
+
* This generator:
|
|
26
|
+
* - Reads the CMS encryption key from configuration (CMS_ENCRYPTION_KEY). The key's value is kept redacted for
|
|
27
|
+
* safety in logs and debug output; the generator uses the redacted value for cryptographic operations where needed.
|
|
28
|
+
* - Exposes utilities for creating random IDs, secure passwords, issuing JWTs for users, and verifying JWTs.
|
|
29
|
+
*
|
|
30
|
+
* Exposed utilities:
|
|
31
|
+
* - generateRandomIDNumber(length: number): number
|
|
32
|
+
* - Generates a numeric identifier of the specified length.
|
|
33
|
+
* - Uses Math.random() and therefore is suitable for non-cryptographic identifiers.
|
|
34
|
+
*
|
|
35
|
+
* - generateRandomPassword(length: number): string
|
|
36
|
+
* - Produces a password of the requested length using the character set
|
|
37
|
+
* "A-Z a-z 0-9".
|
|
38
|
+
* - Uses crypto.getRandomValues and rejection sampling to avoid modulo bias, providing cryptographically
|
|
39
|
+
* strong random selection from the character set.
|
|
40
|
+
*
|
|
41
|
+
* - generateToken(userId: string, noExpire?: boolean): string
|
|
42
|
+
* - Creates a signed JSON Web Token for the supplied userId.
|
|
43
|
+
* - By default the token is time-limited (expires in a short timeframe — implementation uses a 3 hour default).
|
|
44
|
+
* Passing `noExpire = true` will produce a token without the automatic expiry.
|
|
45
|
+
* - Token signing uses the CMS encryption key (redacted) via the SDK's internal JWT generation routine.
|
|
46
|
+
*
|
|
47
|
+
* - testToken(token: string): Effect.Effect<JwtVerificationResult, GeneratorError, never>
|
|
48
|
+
* - Validates the provided JWT using the CMS encryption key.
|
|
49
|
+
* - Verification performs:
|
|
50
|
+
* - header algorithm check (expects HS256),
|
|
51
|
+
* - expiration check (tokens with exp in the past are rejected),
|
|
52
|
+
* - signature verification (HMAC SHA-256 using the configured secret).
|
|
53
|
+
* - Returns a JwtVerificationResult indicating whether the token is valid and, if so, the userId extracted from the token.
|
|
54
|
+
*
|
|
55
|
+
* Error handling and effects:
|
|
56
|
+
* - Many operations are wrapped with the generator's error handling helpers and may fail with GeneratorError when
|
|
57
|
+
* JSON parsing, cryptographic operations, or other wrapped computations error.
|
|
58
|
+
* - Sensitive values (CMS encryption key) are handled in redacted form to avoid accidental exposure.
|
|
59
|
+
*
|
|
60
|
+
* Security notes:
|
|
61
|
+
* - generateRandomPassword uses cryptographically secure randomness; generateRandomIDNumber does not and should not
|
|
62
|
+
* be used where cryptographic unpredictability is required.
|
|
63
|
+
* - Tokens are signed and validated using HMAC-SHA256 (HS256). Ensure the configured CMS_ENCRYPTION_KEY is strong and
|
|
64
|
+
* kept secret.
|
|
65
|
+
*
|
|
66
|
+
* Returns:
|
|
67
|
+
* - An object containing the four utilities: { generateRandomIDNumber, generateRandomPassword, generateToken, testToken }.
|
|
68
|
+
*
|
|
69
|
+
* Example:
|
|
70
|
+
* @example
|
|
71
|
+
* const sdk = yield* SDKGenerators;
|
|
72
|
+
* const token = yield* sdk.generateToken('user-123');
|
|
73
|
+
* const result = yield* sdk.testToken(token);
|
|
74
|
+
*
|
|
75
|
+
* @throws {GeneratorError} if any underlying generator-wrapped operation (parsing, crypto, config retrieval) fails.
|
|
76
|
+
*/
|
|
77
|
+
export declare const SDKGenerators: Effect.Effect<{
|
|
78
|
+
generateRandomIDNumber: (length: number) => Effect.Effect<number, GeneratorError, never>;
|
|
79
|
+
generateRandomPassword: (length: number) => Effect.Effect<string, GeneratorError, never>;
|
|
80
|
+
generateToken: (userId: string, noExpire?: boolean | undefined) => Effect.Effect<string, GeneratorError, never>;
|
|
81
|
+
testToken: (token: string) => Effect.Effect<JwtVerificationResult, GeneratorError, never>;
|
|
82
|
+
}, import("effect/ConfigError").ConfigError, never>;
|
|
83
|
+
export {};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import * as crypto from "node:crypto";
|
|
2
|
+
import { Config, Data, Effect, Redacted } from "@withstudiocms/effect";
|
|
3
|
+
class GeneratorError extends Data.TaggedError("GeneratorError") {
|
|
4
|
+
}
|
|
5
|
+
const useGeneratorError = (_try) => Effect.try({
|
|
6
|
+
try: _try,
|
|
7
|
+
catch: (error) => new GeneratorError({ cause: error })
|
|
8
|
+
});
|
|
9
|
+
const SDKGenerators = Effect.gen(function* () {
|
|
10
|
+
const redactedCMSEncryptionKey = yield* Config.redacted("CMS_ENCRYPTION_KEY");
|
|
11
|
+
const cmsEncryptionKey = Redacted.value(redactedCMSEncryptionKey);
|
|
12
|
+
const base64UrlEncode = (input) => Buffer.from(input).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
13
|
+
const base64UrlDecode = (input) => {
|
|
14
|
+
let newInput = input.replace(/-/g, "+").replace(/_/g, "/");
|
|
15
|
+
while (newInput.length % 4 !== 0) {
|
|
16
|
+
newInput += "=";
|
|
17
|
+
}
|
|
18
|
+
return Buffer.from(newInput, "base64").toString();
|
|
19
|
+
};
|
|
20
|
+
const generateJwt = Effect.fn(
|
|
21
|
+
(secret, payload, noExpire) => useGeneratorError(() => {
|
|
22
|
+
const header = { alg: "HS256", typ: "JWT" };
|
|
23
|
+
const currentDate = /* @__PURE__ */ new Date();
|
|
24
|
+
const thirtyYearsFromToday = Math.floor(
|
|
25
|
+
currentDate.setFullYear(currentDate.getFullYear() + 30) / 1e3
|
|
26
|
+
);
|
|
27
|
+
const exp = noExpire ? thirtyYearsFromToday : Math.floor(Date.now() / 1e3) + 86400;
|
|
28
|
+
const payloadObj = {
|
|
29
|
+
...payload,
|
|
30
|
+
iat: Math.floor(Date.now() / 1e3),
|
|
31
|
+
// Corrected iat
|
|
32
|
+
exp
|
|
33
|
+
};
|
|
34
|
+
const encodedHeader = base64UrlEncode(JSON.stringify(header));
|
|
35
|
+
const encodedPayload = base64UrlEncode(JSON.stringify(payloadObj));
|
|
36
|
+
const signatureInput = `${encodedHeader}.${encodedPayload}`;
|
|
37
|
+
const signature = Buffer.from(
|
|
38
|
+
crypto.createHmac("sha256", secret + secret).update(signatureInput).digest()
|
|
39
|
+
).toString("base64url");
|
|
40
|
+
return `${encodedHeader}.${encodedPayload}.${signature}`;
|
|
41
|
+
})
|
|
42
|
+
);
|
|
43
|
+
const verifyJwt = (token, secret) => Effect.gen(function* () {
|
|
44
|
+
const [encodedHeader, encodedPayload, encodedSignature] = token.split(".");
|
|
45
|
+
if (!encodedHeader || !encodedPayload || !encodedSignature) {
|
|
46
|
+
yield* Effect.logDebug("Invalid token format");
|
|
47
|
+
return { isValid: false };
|
|
48
|
+
}
|
|
49
|
+
const [header, payload] = yield* Effect.all([
|
|
50
|
+
useGeneratorError(() => JSON.parse(base64UrlDecode(encodedHeader))),
|
|
51
|
+
useGeneratorError(() => JSON.parse(base64UrlDecode(encodedPayload)))
|
|
52
|
+
]);
|
|
53
|
+
if (header.alg !== "HS256") {
|
|
54
|
+
yield* Effect.logDebug("Invalid algorithm");
|
|
55
|
+
return { isValid: false };
|
|
56
|
+
}
|
|
57
|
+
const currentTime = Math.floor(Date.now() / 1e3);
|
|
58
|
+
if (payload.exp && currentTime > payload.exp) {
|
|
59
|
+
yield* Effect.logDebug("Token has expired");
|
|
60
|
+
return { isValid: false };
|
|
61
|
+
}
|
|
62
|
+
const signatureInput = `${encodedHeader}.${encodedPayload}`;
|
|
63
|
+
const generatedSignature = yield* useGeneratorError(() => {
|
|
64
|
+
return Buffer.from(
|
|
65
|
+
crypto.createHmac("sha256", secret + secret).update(signatureInput).digest()
|
|
66
|
+
).toString("base64url");
|
|
67
|
+
});
|
|
68
|
+
if (generatedSignature !== encodedSignature) {
|
|
69
|
+
yield* Effect.logDebug("Invalid signature");
|
|
70
|
+
return { isValid: false };
|
|
71
|
+
}
|
|
72
|
+
return { isValid: true, userId: payload.userId };
|
|
73
|
+
});
|
|
74
|
+
const generateRandomIDNumber = Effect.fn(
|
|
75
|
+
(length) => useGeneratorError(() => Math.floor(Math.random() * 10 ** length))
|
|
76
|
+
);
|
|
77
|
+
const generateRandomPassword = Effect.fn(
|
|
78
|
+
(length) => useGeneratorError(() => {
|
|
79
|
+
const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
80
|
+
let password = "";
|
|
81
|
+
const maxValidValue = Math.floor((2 ** 32 - 1) / characters.length) * characters.length;
|
|
82
|
+
while (password.length < length) {
|
|
83
|
+
const n = crypto.getRandomValues(new Uint32Array(1))[0];
|
|
84
|
+
if (n < maxValidValue) {
|
|
85
|
+
password += characters[n % characters.length];
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return password;
|
|
89
|
+
})
|
|
90
|
+
);
|
|
91
|
+
const generateToken = Effect.fn(
|
|
92
|
+
(userId, noExpire) => generateJwt(cmsEncryptionKey, { userId }, noExpire)
|
|
93
|
+
);
|
|
94
|
+
const testToken = Effect.fn((token) => verifyJwt(token, cmsEncryptionKey));
|
|
95
|
+
return {
|
|
96
|
+
generateRandomIDNumber,
|
|
97
|
+
generateRandomPassword,
|
|
98
|
+
generateToken,
|
|
99
|
+
testToken
|
|
100
|
+
};
|
|
101
|
+
});
|
|
102
|
+
export {
|
|
103
|
+
GeneratorError,
|
|
104
|
+
SDKGenerators,
|
|
105
|
+
useGeneratorError
|
|
106
|
+
};
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { Effect, Schema } from '@withstudiocms/effect';
|
|
2
|
+
import { CacheService } from '../../cache.js';
|
|
3
|
+
declare const NpmRegistryResponseSchema_base: Schema.Class<NpmRegistryResponseSchema, {
|
|
4
|
+
_id: typeof Schema.String;
|
|
5
|
+
_integrity: typeof Schema.String;
|
|
6
|
+
_npmUser: Schema.Struct<{
|
|
7
|
+
name: typeof Schema.String;
|
|
8
|
+
email: typeof Schema.String;
|
|
9
|
+
trustedPublisher: Schema.Struct<{
|
|
10
|
+
id: typeof Schema.String;
|
|
11
|
+
oidcConfigId: typeof Schema.String;
|
|
12
|
+
}>;
|
|
13
|
+
}>;
|
|
14
|
+
maintainers: Schema.Array$<Schema.Struct<{
|
|
15
|
+
name: typeof Schema.String;
|
|
16
|
+
email: typeof Schema.String;
|
|
17
|
+
}>>;
|
|
18
|
+
name: typeof Schema.String;
|
|
19
|
+
version: typeof Schema.String;
|
|
20
|
+
description: typeof Schema.String;
|
|
21
|
+
license: typeof Schema.String;
|
|
22
|
+
author: Schema.optional<Schema.Struct<{
|
|
23
|
+
name: typeof Schema.String;
|
|
24
|
+
url: typeof Schema.String;
|
|
25
|
+
}>>;
|
|
26
|
+
repository: Schema.optional<Schema.Struct<{
|
|
27
|
+
type: typeof Schema.String;
|
|
28
|
+
url: typeof Schema.String;
|
|
29
|
+
directory: Schema.optional<typeof Schema.String>;
|
|
30
|
+
}>>;
|
|
31
|
+
contributors: Schema.optional<Schema.Array$<Schema.Struct<{
|
|
32
|
+
name: typeof Schema.String;
|
|
33
|
+
url: Schema.optional<typeof Schema.String>;
|
|
34
|
+
}>>>;
|
|
35
|
+
keywords: Schema.Array$<typeof Schema.String>;
|
|
36
|
+
homepage: Schema.optional<typeof Schema.String>;
|
|
37
|
+
publishConfig: Schema.optional<Schema.Struct<{
|
|
38
|
+
access: typeof Schema.String;
|
|
39
|
+
provenance: typeof Schema.Boolean;
|
|
40
|
+
}>>;
|
|
41
|
+
sideEffects: Schema.optional<typeof Schema.Boolean>;
|
|
42
|
+
}, Schema.Struct.Encoded<{
|
|
43
|
+
_id: typeof Schema.String;
|
|
44
|
+
_integrity: typeof Schema.String;
|
|
45
|
+
_npmUser: Schema.Struct<{
|
|
46
|
+
name: typeof Schema.String;
|
|
47
|
+
email: typeof Schema.String;
|
|
48
|
+
trustedPublisher: Schema.Struct<{
|
|
49
|
+
id: typeof Schema.String;
|
|
50
|
+
oidcConfigId: typeof Schema.String;
|
|
51
|
+
}>;
|
|
52
|
+
}>;
|
|
53
|
+
maintainers: Schema.Array$<Schema.Struct<{
|
|
54
|
+
name: typeof Schema.String;
|
|
55
|
+
email: typeof Schema.String;
|
|
56
|
+
}>>;
|
|
57
|
+
name: typeof Schema.String;
|
|
58
|
+
version: typeof Schema.String;
|
|
59
|
+
description: typeof Schema.String;
|
|
60
|
+
license: typeof Schema.String;
|
|
61
|
+
author: Schema.optional<Schema.Struct<{
|
|
62
|
+
name: typeof Schema.String;
|
|
63
|
+
url: typeof Schema.String;
|
|
64
|
+
}>>;
|
|
65
|
+
repository: Schema.optional<Schema.Struct<{
|
|
66
|
+
type: typeof Schema.String;
|
|
67
|
+
url: typeof Schema.String;
|
|
68
|
+
directory: Schema.optional<typeof Schema.String>;
|
|
69
|
+
}>>;
|
|
70
|
+
contributors: Schema.optional<Schema.Array$<Schema.Struct<{
|
|
71
|
+
name: typeof Schema.String;
|
|
72
|
+
url: Schema.optional<typeof Schema.String>;
|
|
73
|
+
}>>>;
|
|
74
|
+
keywords: Schema.Array$<typeof Schema.String>;
|
|
75
|
+
homepage: Schema.optional<typeof Schema.String>;
|
|
76
|
+
publishConfig: Schema.optional<Schema.Struct<{
|
|
77
|
+
access: typeof Schema.String;
|
|
78
|
+
provenance: typeof Schema.Boolean;
|
|
79
|
+
}>>;
|
|
80
|
+
sideEffects: Schema.optional<typeof Schema.Boolean>;
|
|
81
|
+
}>, never, {
|
|
82
|
+
readonly name: string;
|
|
83
|
+
} & {
|
|
84
|
+
readonly description: string;
|
|
85
|
+
} & {
|
|
86
|
+
readonly _id: string;
|
|
87
|
+
} & {
|
|
88
|
+
readonly _integrity: string;
|
|
89
|
+
} & {
|
|
90
|
+
readonly version: string;
|
|
91
|
+
} & {
|
|
92
|
+
readonly license: string;
|
|
93
|
+
} & {
|
|
94
|
+
readonly _npmUser: {
|
|
95
|
+
readonly name: string;
|
|
96
|
+
readonly email: string;
|
|
97
|
+
readonly trustedPublisher: {
|
|
98
|
+
readonly id: string;
|
|
99
|
+
readonly oidcConfigId: string;
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
} & {
|
|
103
|
+
readonly maintainers: readonly {
|
|
104
|
+
readonly name: string;
|
|
105
|
+
readonly email: string;
|
|
106
|
+
}[];
|
|
107
|
+
} & {
|
|
108
|
+
readonly author?: {
|
|
109
|
+
readonly name: string;
|
|
110
|
+
readonly url: string;
|
|
111
|
+
} | undefined;
|
|
112
|
+
} & {
|
|
113
|
+
readonly repository?: {
|
|
114
|
+
readonly type: string;
|
|
115
|
+
readonly url: string;
|
|
116
|
+
readonly directory?: string | undefined;
|
|
117
|
+
} | undefined;
|
|
118
|
+
} & {
|
|
119
|
+
readonly contributors?: readonly {
|
|
120
|
+
readonly name: string;
|
|
121
|
+
readonly url?: string | undefined;
|
|
122
|
+
}[] | undefined;
|
|
123
|
+
} & {
|
|
124
|
+
readonly keywords: readonly string[];
|
|
125
|
+
} & {
|
|
126
|
+
readonly homepage?: string | undefined;
|
|
127
|
+
} & {
|
|
128
|
+
readonly publishConfig?: {
|
|
129
|
+
readonly access: string;
|
|
130
|
+
readonly provenance: boolean;
|
|
131
|
+
} | undefined;
|
|
132
|
+
} & {
|
|
133
|
+
readonly sideEffects?: boolean | undefined;
|
|
134
|
+
}, {}, {}>;
|
|
135
|
+
/**
|
|
136
|
+
* Represents the shape of an npm registry document for a specific package version.
|
|
137
|
+
*
|
|
138
|
+
* This class models the JSON structure returned by the npm registry for a published
|
|
139
|
+
* package version. It includes metadata about the package (name, version, description,
|
|
140
|
+
* license), publisher/maintainer information, repository and publish configuration,
|
|
141
|
+
* and other auxiliary fields commonly present on the npm registry.
|
|
142
|
+
*
|
|
143
|
+
* @remarks
|
|
144
|
+
* The shape mirrors common fields returned by the npm registry and is suitable for
|
|
145
|
+
* validating or typing responses when fetching package metadata (for example via
|
|
146
|
+
* the registry REST API). Optional fields reflect that not all packages supply every
|
|
147
|
+
* piece of metadata.
|
|
148
|
+
*
|
|
149
|
+
* @public
|
|
150
|
+
*/
|
|
151
|
+
export declare class NpmRegistryResponseSchema extends NpmRegistryResponseSchema_base {
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Helper to process and validate the HTTP response from the NPM registry.
|
|
155
|
+
*/
|
|
156
|
+
export declare const parseNpmRegistryResponse: (response: Response) => Effect.Effect<NpmRegistryResponseSchema, import("effect/Cause").UnknownException | import("effect/ParseResult").ParseError, never>;
|
|
157
|
+
/**
|
|
158
|
+
* Cache key for NPM package data.
|
|
159
|
+
*/
|
|
160
|
+
export declare const cacheKey: (name: string, version: string) => string;
|
|
161
|
+
/**
|
|
162
|
+
* Cache options for NPM package data.
|
|
163
|
+
*/
|
|
164
|
+
export declare const cacheOpts: {
|
|
165
|
+
tags: string[];
|
|
166
|
+
};
|
|
167
|
+
/**
|
|
168
|
+
* An Effect service for retrieving the version of an NPM package from the NPM registry.
|
|
169
|
+
*
|
|
170
|
+
* @remarks
|
|
171
|
+
* This service uses an HTTP client with retry logic to fetch the version information
|
|
172
|
+
* for a given package and version (defaulting to 'latest') from the NPM registry.
|
|
173
|
+
*
|
|
174
|
+
* @example
|
|
175
|
+
* ```typescript
|
|
176
|
+
* const version = await GetVersionFromNPM.get('react');
|
|
177
|
+
* ```
|
|
178
|
+
*
|
|
179
|
+
* @method get
|
|
180
|
+
* @param pkg - The name of the NPM package.
|
|
181
|
+
* @param ver - The version tag or number (defaults to 'latest').
|
|
182
|
+
* @returns An Effect that resolves to the version string of the specified package.
|
|
183
|
+
*/
|
|
184
|
+
export declare const GetFromNPM: Effect.Effect<{
|
|
185
|
+
getVersion: (pkg: string, ver?: string | undefined) => Effect.Effect<{
|
|
186
|
+
version: string;
|
|
187
|
+
lastCacheUpdate: Date;
|
|
188
|
+
}, import("effect/Cause").UnknownException | Error | import("effect/ParseResult").ParseError, never>;
|
|
189
|
+
getDataFromNPM: (pkg: string, ver?: string | undefined) => Effect.Effect<NpmRegistryResponseSchema, import("effect/Cause").UnknownException | Error | import("effect/ParseResult").ParseError, never>;
|
|
190
|
+
}, never, CacheService>;
|
|
191
|
+
export {};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { Effect, HTTPClient, Schema } from "@withstudiocms/effect";
|
|
2
|
+
import { CacheService } from "../../cache.js";
|
|
3
|
+
import { cacheKeyGetters, cacheTags } from "../../consts.js";
|
|
4
|
+
class NpmRegistryResponseSchema extends Schema.Class(
|
|
5
|
+
"NpmRegistryResponseSchema"
|
|
6
|
+
)({
|
|
7
|
+
_id: Schema.String,
|
|
8
|
+
_integrity: Schema.String,
|
|
9
|
+
_npmUser: Schema.Struct({
|
|
10
|
+
name: Schema.String,
|
|
11
|
+
email: Schema.String,
|
|
12
|
+
trustedPublisher: Schema.Struct({
|
|
13
|
+
id: Schema.String,
|
|
14
|
+
oidcConfigId: Schema.String
|
|
15
|
+
})
|
|
16
|
+
}),
|
|
17
|
+
maintainers: Schema.Array(
|
|
18
|
+
Schema.Struct({
|
|
19
|
+
name: Schema.String,
|
|
20
|
+
email: Schema.String
|
|
21
|
+
})
|
|
22
|
+
),
|
|
23
|
+
name: Schema.String,
|
|
24
|
+
version: Schema.String,
|
|
25
|
+
description: Schema.String,
|
|
26
|
+
license: Schema.String,
|
|
27
|
+
author: Schema.optional(
|
|
28
|
+
Schema.Struct({
|
|
29
|
+
name: Schema.String,
|
|
30
|
+
url: Schema.String
|
|
31
|
+
})
|
|
32
|
+
),
|
|
33
|
+
repository: Schema.optional(
|
|
34
|
+
Schema.Struct({
|
|
35
|
+
type: Schema.String,
|
|
36
|
+
url: Schema.String,
|
|
37
|
+
directory: Schema.optional(Schema.String)
|
|
38
|
+
})
|
|
39
|
+
),
|
|
40
|
+
contributors: Schema.optional(
|
|
41
|
+
Schema.Array(
|
|
42
|
+
Schema.Struct({
|
|
43
|
+
name: Schema.String,
|
|
44
|
+
url: Schema.optional(Schema.String)
|
|
45
|
+
})
|
|
46
|
+
)
|
|
47
|
+
),
|
|
48
|
+
keywords: Schema.Array(Schema.String),
|
|
49
|
+
homepage: Schema.optional(Schema.String),
|
|
50
|
+
publishConfig: Schema.optional(
|
|
51
|
+
Schema.Struct({
|
|
52
|
+
access: Schema.String,
|
|
53
|
+
provenance: Schema.Boolean
|
|
54
|
+
})
|
|
55
|
+
),
|
|
56
|
+
sideEffects: Schema.optional(Schema.Boolean)
|
|
57
|
+
}) {
|
|
58
|
+
}
|
|
59
|
+
const parseNpmRegistryResponse = Effect.fn(function* (response) {
|
|
60
|
+
const data = yield* Effect.tryPromise(() => response.json());
|
|
61
|
+
return yield* Schema.decodeUnknown(NpmRegistryResponseSchema)(data);
|
|
62
|
+
});
|
|
63
|
+
const cacheKey = cacheKeyGetters.npmPackage;
|
|
64
|
+
const cacheOpts = { tags: cacheTags.npmPackage };
|
|
65
|
+
const GetFromNPM = Effect.gen(function* () {
|
|
66
|
+
const [{ memoize, getCacheStatus }] = yield* Effect.all([CacheService]);
|
|
67
|
+
const effectFetch = Effect.fn(
|
|
68
|
+
(url) => Effect.tryPromise({
|
|
69
|
+
try: () => fetch(url),
|
|
70
|
+
catch: (error) => new Error(`Failed to fetch NPM package data: ${String(error)}`)
|
|
71
|
+
})
|
|
72
|
+
);
|
|
73
|
+
const _remapCacheStatusData = (status) => status ? status.lastUpdatedAt : /* @__PURE__ */ new Date();
|
|
74
|
+
const _remapData = (pkg, srcVer) => ({ version }) => Effect.all({
|
|
75
|
+
version: Effect.succeed(version),
|
|
76
|
+
lastCacheUpdate: getCacheStatus(cacheKey(pkg, srcVer || "latest")).pipe(
|
|
77
|
+
Effect.map(_remapCacheStatusData)
|
|
78
|
+
)
|
|
79
|
+
});
|
|
80
|
+
const getDataFromNPM = Effect.fn(
|
|
81
|
+
(pkg, ver) => memoize(
|
|
82
|
+
cacheKey(pkg, ver || "latest"),
|
|
83
|
+
effectFetch(`https://registry.npmjs.org/${pkg}/${ver || "latest"}`).pipe(
|
|
84
|
+
Effect.flatMap(parseNpmRegistryResponse)
|
|
85
|
+
),
|
|
86
|
+
cacheOpts
|
|
87
|
+
)
|
|
88
|
+
);
|
|
89
|
+
const getVersion = Effect.fn(
|
|
90
|
+
(pkg, ver) => getDataFromNPM(pkg, ver).pipe(Effect.flatMap(_remapData(pkg, ver)))
|
|
91
|
+
);
|
|
92
|
+
return { getVersion, getDataFromNPM };
|
|
93
|
+
}).pipe(Effect.provide(HTTPClient.Default));
|
|
94
|
+
export {
|
|
95
|
+
GetFromNPM,
|
|
96
|
+
NpmRegistryResponseSchema,
|
|
97
|
+
cacheKey,
|
|
98
|
+
cacheOpts,
|
|
99
|
+
parseNpmRegistryResponse
|
|
100
|
+
};
|