@jskit-ai/http-runtime 0.1.4

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 (38) hide show
  1. package/package.descriptor.mjs +83 -0
  2. package/package.json +24 -0
  3. package/src/client/index.js +14 -0
  4. package/src/client/providers/HttpClientRuntimeClientProvider.js +21 -0
  5. package/src/client/providers/HttpValidatorsClientProvider.js +14 -0
  6. package/src/client/validationErrors.js +23 -0
  7. package/src/server/providers/HttpClientRuntimeServiceProvider.js +21 -0
  8. package/src/server/providers/HttpValidatorsServiceProvider.js +14 -0
  9. package/src/shared/clientRuntime/client.js +632 -0
  10. package/src/shared/clientRuntime/errors.js +35 -0
  11. package/src/shared/clientRuntime/headers.js +28 -0
  12. package/src/shared/clientRuntime/index.js +4 -0
  13. package/src/shared/clientRuntime/retry.js +38 -0
  14. package/src/shared/index.js +30 -0
  15. package/src/shared/providers/singletonApiProvider.js +27 -0
  16. package/src/shared/support/fieldErrors.js +31 -0
  17. package/src/shared/validators/command.js +34 -0
  18. package/src/shared/validators/errorResponses.js +108 -0
  19. package/src/shared/validators/httpValidatorsApi.js +58 -0
  20. package/src/shared/validators/operationMessages.js +149 -0
  21. package/src/shared/validators/operationValidation.js +126 -0
  22. package/src/shared/validators/paginationQuery.js +32 -0
  23. package/src/shared/validators/resource.js +43 -0
  24. package/src/shared/validators/schemaUtils.js +9 -0
  25. package/src/shared/validators/typeboxFormats.js +43 -0
  26. package/test/client.test.js +246 -0
  27. package/test/command.test.js +49 -0
  28. package/test/entrypoints.boundary.test.js +36 -0
  29. package/test/errorResponses.test.js +84 -0
  30. package/test/operationMessages.test.js +93 -0
  31. package/test/operationValidation.test.js +137 -0
  32. package/test/paginationQuery.test.js +32 -0
  33. package/test/providerRuntime.httpClient.test.js +35 -0
  34. package/test/providerRuntime.validators.test.js +39 -0
  35. package/test/resource.test.js +94 -0
  36. package/test/retry.test.js +41 -0
  37. package/test/typeboxFormats.test.js +42 -0
  38. package/test/validationErrors.test.js +100 -0
@@ -0,0 +1,38 @@
1
+ const DEFAULT_RETRYABLE_CSRF_ERROR_CODES = Object.freeze(["FST_CSRF_INVALID_TOKEN", "FST_CSRF_MISSING_SECRET"]);
2
+
3
+ function toUpperStringSet(values, fallback = []) {
4
+ const source = Array.isArray(values) ? values : fallback;
5
+ return new Set(
6
+ source
7
+ .map((value) =>
8
+ String(value || "")
9
+ .trim()
10
+ .toUpperCase()
11
+ )
12
+ .filter(Boolean)
13
+ );
14
+ }
15
+
16
+ function shouldRetryForCsrfFailure({
17
+ response,
18
+ method,
19
+ state,
20
+ data,
21
+ unsafeMethods,
22
+ retryableErrorCodes = DEFAULT_RETRYABLE_CSRF_ERROR_CODES
23
+ }) {
24
+ const methodValue = String(method || "")
25
+ .trim()
26
+ .toUpperCase();
27
+ if (Number(response?.status) !== 403 || !unsafeMethods?.has(methodValue) || state?.csrfRetried) {
28
+ return false;
29
+ }
30
+
31
+ const code = String(data?.details?.code || "")
32
+ .trim()
33
+ .toUpperCase();
34
+ const retryableCodes = toUpperStringSet(retryableErrorCodes, DEFAULT_RETRYABLE_CSRF_ERROR_CODES);
35
+ return retryableCodes.has(code);
36
+ }
37
+
38
+ export { DEFAULT_RETRYABLE_CSRF_ERROR_CODES, shouldRetryForCsrfFailure };
@@ -0,0 +1,30 @@
1
+ export { createPaginationQuerySchema } from "./validators/paginationQuery.js";
2
+ export { registerTypeBoxFormats, __testables } from "./validators/typeboxFormats.js";
3
+ export {
4
+ fieldErrorsSchema,
5
+ apiErrorDetailsSchema,
6
+ apiErrorResponseSchema,
7
+ apiValidationErrorResponseSchema,
8
+ fastifyDefaultErrorResponseSchema,
9
+ STANDARD_ERROR_STATUS_CODES,
10
+ passthroughErrorResponses,
11
+ withStandardErrorResponses,
12
+ enumSchema
13
+ } from "./validators/errorResponses.js";
14
+ export {
15
+ createCursorPagedListResponseSchema,
16
+ createResource
17
+ } from "./validators/resource.js";
18
+ export { createCommand } from "./validators/command.js";
19
+ export {
20
+ resolveSchemaMessages,
21
+ resolveFieldSchema,
22
+ resolveIssueField,
23
+ resolveMissingRequiredFields,
24
+ resolveIssueMessageFromSchema,
25
+ mapOperationIssues
26
+ } from "./validators/operationMessages.js";
27
+ export {
28
+ validateOperationSection,
29
+ validateOperationInput
30
+ } from "./validators/operationValidation.js";
@@ -0,0 +1,27 @@
1
+ class SingletonApiProvider {
2
+ static bindingToken = "";
3
+
4
+ static api = null;
5
+
6
+ static providerName = "SingletonApiProvider";
7
+
8
+ register(app) {
9
+ if (!app || typeof app.singleton !== "function") {
10
+ throw new Error(`${this.constructor.providerName} requires application singleton().`);
11
+ }
12
+
13
+ const bindingToken = String(this.constructor.bindingToken || "").trim();
14
+ if (!bindingToken) {
15
+ throw new Error(`${this.constructor.providerName} requires static bindingToken.`);
16
+ }
17
+ if (!this.constructor.api || typeof this.constructor.api !== "object") {
18
+ throw new Error(`${this.constructor.providerName} requires static api object.`);
19
+ }
20
+
21
+ app.singleton(bindingToken, () => this.constructor.api);
22
+ }
23
+
24
+ boot() {}
25
+ }
26
+
27
+ export { SingletonApiProvider };
@@ -0,0 +1,31 @@
1
+ import { isRecord } from "@jskit-ai/kernel/shared/support/normalize";
2
+
3
+ function normalizeFieldErrors(value) {
4
+ const source = isRecord(value) ? value : {};
5
+ const normalized = {};
6
+
7
+ for (const [field, message] of Object.entries(source)) {
8
+ const normalizedField = String(field || "").trim();
9
+ if (!normalizedField) {
10
+ continue;
11
+ }
12
+ normalized[normalizedField] = String(message || "");
13
+ }
14
+
15
+ return normalized;
16
+ }
17
+
18
+ function resolveFieldErrors(value = null) {
19
+ const source = isRecord(value) ? value : {};
20
+ if (isRecord(source.fieldErrors)) {
21
+ return normalizeFieldErrors(source.fieldErrors);
22
+ }
23
+
24
+ if (isRecord(source.details?.fieldErrors)) {
25
+ return normalizeFieldErrors(source.details.fieldErrors);
26
+ }
27
+
28
+ return {};
29
+ }
30
+
31
+ export { isRecord, normalizeFieldErrors, resolveFieldErrors };
@@ -0,0 +1,34 @@
1
+ import { asSchema } from "./schemaUtils.js";
2
+
3
+ function normalizeInvalidates(value) {
4
+ if (!Array.isArray(value)) {
5
+ return Object.freeze([]);
6
+ }
7
+
8
+ const normalized = value
9
+ .map((entry) => String(entry || "").trim())
10
+ .filter(Boolean);
11
+
12
+ return Object.freeze(Array.from(new Set(normalized)));
13
+ }
14
+
15
+ function createCommand({
16
+ input,
17
+ output,
18
+ idempotent = null,
19
+ invalidates = []
20
+ } = {}) {
21
+ const command = {
22
+ input: asSchema(input, "input"),
23
+ output: asSchema(output, "output"),
24
+ invalidates: normalizeInvalidates(invalidates)
25
+ };
26
+
27
+ if (typeof idempotent === "boolean") {
28
+ command.idempotent = idempotent;
29
+ }
30
+
31
+ return Object.freeze(command);
32
+ }
33
+
34
+ export { createCommand };
@@ -0,0 +1,108 @@
1
+ import { Type } from "@fastify/type-provider-typebox";
2
+
3
+ const fieldErrorsSchema = Type.Record(Type.String(), Type.String());
4
+
5
+ const apiErrorDetailsSchema = Type.Object(
6
+ {
7
+ fieldErrors: Type.Optional(fieldErrorsSchema)
8
+ },
9
+ {
10
+ additionalProperties: true
11
+ }
12
+ );
13
+
14
+ const apiErrorResponseSchema = Type.Object(
15
+ {
16
+ error: Type.String({ minLength: 1 }),
17
+ code: Type.Optional(Type.String({ minLength: 1 })),
18
+ details: Type.Optional(apiErrorDetailsSchema),
19
+ fieldErrors: Type.Optional(fieldErrorsSchema)
20
+ },
21
+ {
22
+ additionalProperties: false
23
+ }
24
+ );
25
+
26
+ const apiValidationErrorResponseSchema = Type.Object(
27
+ {
28
+ error: Type.String({ minLength: 1 }),
29
+ code: Type.Optional(Type.String({ minLength: 1 })),
30
+ fieldErrors: fieldErrorsSchema,
31
+ details: Type.Object(
32
+ {
33
+ fieldErrors: fieldErrorsSchema
34
+ },
35
+ {
36
+ additionalProperties: true
37
+ }
38
+ )
39
+ },
40
+ {
41
+ additionalProperties: false
42
+ }
43
+ );
44
+
45
+ const fastifyDefaultErrorResponseSchema = Type.Object(
46
+ {
47
+ statusCode: Type.Integer({ minimum: 400, maximum: 599 }),
48
+ error: Type.String({ minLength: 1 }),
49
+ message: Type.String({ minLength: 1 }),
50
+ code: Type.Optional(Type.String({ minLength: 1 })),
51
+ details: Type.Optional(Type.Unknown()),
52
+ fieldErrors: Type.Optional(fieldErrorsSchema)
53
+ },
54
+ {
55
+ additionalProperties: true
56
+ }
57
+ );
58
+
59
+ const STANDARD_ERROR_STATUS_CODES = [400, 401, 403, 404, 409, 422, 429, 500, 503];
60
+
61
+ function passthroughErrorResponses(successResponses) {
62
+ return successResponses;
63
+ }
64
+
65
+ function withStandardErrorResponses(successResponses, { includeValidation400 = false } = {}) {
66
+ const responses = {
67
+ ...successResponses
68
+ };
69
+
70
+ for (const statusCode of STANDARD_ERROR_STATUS_CODES) {
71
+ if (responses[statusCode]) {
72
+ continue;
73
+ }
74
+
75
+ if (statusCode === 400 && includeValidation400) {
76
+ responses[statusCode] = {
77
+ schema: Type.Union([
78
+ apiValidationErrorResponseSchema,
79
+ apiErrorResponseSchema,
80
+ fastifyDefaultErrorResponseSchema
81
+ ])
82
+ };
83
+ continue;
84
+ }
85
+
86
+ responses[statusCode] = {
87
+ schema: Type.Union([apiErrorResponseSchema, fastifyDefaultErrorResponseSchema])
88
+ };
89
+ }
90
+
91
+ return responses;
92
+ }
93
+
94
+ function enumSchema(values) {
95
+ return Type.Union(values.map((value) => Type.Literal(value)));
96
+ }
97
+
98
+ export {
99
+ fieldErrorsSchema,
100
+ apiErrorDetailsSchema,
101
+ apiErrorResponseSchema,
102
+ apiValidationErrorResponseSchema,
103
+ fastifyDefaultErrorResponseSchema,
104
+ STANDARD_ERROR_STATUS_CODES,
105
+ passthroughErrorResponses,
106
+ withStandardErrorResponses,
107
+ enumSchema
108
+ };
@@ -0,0 +1,58 @@
1
+ import { createPaginationQuerySchema } from "./paginationQuery.js";
2
+ import { registerTypeBoxFormats, __testables } from "./typeboxFormats.js";
3
+ import {
4
+ fieldErrorsSchema,
5
+ apiErrorDetailsSchema,
6
+ apiErrorResponseSchema,
7
+ apiValidationErrorResponseSchema,
8
+ fastifyDefaultErrorResponseSchema,
9
+ STANDARD_ERROR_STATUS_CODES,
10
+ passthroughErrorResponses,
11
+ withStandardErrorResponses,
12
+ enumSchema
13
+ } from "./errorResponses.js";
14
+ import {
15
+ createCursorPagedListResponseSchema,
16
+ createResource
17
+ } from "./resource.js";
18
+ import { createCommand } from "./command.js";
19
+ import {
20
+ resolveSchemaMessages,
21
+ resolveFieldSchema,
22
+ resolveIssueField,
23
+ resolveMissingRequiredFields,
24
+ resolveIssueMessageFromSchema,
25
+ mapOperationIssues
26
+ } from "./operationMessages.js";
27
+ import {
28
+ validateOperationSection,
29
+ validateOperationInput
30
+ } from "./operationValidation.js";
31
+
32
+ const HTTP_VALIDATORS_API = Object.freeze({
33
+ createPaginationQuerySchema,
34
+ registerTypeBoxFormats,
35
+ __testables,
36
+ fieldErrorsSchema,
37
+ apiErrorDetailsSchema,
38
+ apiErrorResponseSchema,
39
+ apiValidationErrorResponseSchema,
40
+ fastifyDefaultErrorResponseSchema,
41
+ STANDARD_ERROR_STATUS_CODES,
42
+ passthroughErrorResponses,
43
+ withStandardErrorResponses,
44
+ enumSchema,
45
+ createCursorPagedListResponseSchema,
46
+ createResource,
47
+ createCommand,
48
+ resolveSchemaMessages,
49
+ resolveFieldSchema,
50
+ resolveIssueField,
51
+ resolveMissingRequiredFields,
52
+ resolveIssueMessageFromSchema,
53
+ mapOperationIssues,
54
+ validateOperationSection,
55
+ validateOperationInput
56
+ });
57
+
58
+ export { HTTP_VALIDATORS_API };
@@ -0,0 +1,149 @@
1
+ import { isRecord, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
2
+
3
+ function resolveIssueField(issue = {}) {
4
+ const instancePath = normalizeText(issue.instancePath);
5
+ if (instancePath) {
6
+ const segments = instancePath
7
+ .replace(/^\//, "")
8
+ .split("/")
9
+ .map((entry) => normalizeText(entry))
10
+ .filter(Boolean);
11
+
12
+ if (segments.length > 0) {
13
+ return segments[0];
14
+ }
15
+ }
16
+
17
+ const params = isRecord(issue.params) ? issue.params : {};
18
+ const missingProperty = normalizeText(params.missingProperty);
19
+ if (missingProperty) {
20
+ return missingProperty;
21
+ }
22
+
23
+ const requiredProperties = Array.isArray(params.requiredProperties)
24
+ ? params.requiredProperties
25
+ : [];
26
+ if (requiredProperties.length > 0) {
27
+ return normalizeText(requiredProperties[0]);
28
+ }
29
+
30
+ const additionalProperties = Array.isArray(params.additionalProperties)
31
+ ? params.additionalProperties
32
+ : [];
33
+ if (additionalProperties.length > 0) {
34
+ return normalizeText(additionalProperties[0]);
35
+ }
36
+
37
+ const additionalProperty = normalizeText(params.additionalProperty);
38
+ if (additionalProperty) {
39
+ return additionalProperty;
40
+ }
41
+
42
+ return "";
43
+ }
44
+
45
+ function resolveMissingRequiredFields(issue = {}) {
46
+ const params = isRecord(issue.params) ? issue.params : {};
47
+ const requiredProperties = Array.isArray(params.requiredProperties)
48
+ ? params.requiredProperties
49
+ : [];
50
+
51
+ return requiredProperties
52
+ .map((entry) => normalizeText(entry))
53
+ .filter(Boolean);
54
+ }
55
+
56
+ function resolveSchemaMessages(schema = {}) {
57
+ const source = isRecord(schema) ? schema : {};
58
+ const messages = source.messages;
59
+ return isRecord(messages) ? messages : {};
60
+ }
61
+
62
+ function resolveFieldSchema(schema = {}, field = "") {
63
+ const source = isRecord(schema) ? schema : {};
64
+ const properties = isRecord(source.properties) ? source.properties : {};
65
+ const fieldSchema = properties[field];
66
+ return isRecord(fieldSchema) ? fieldSchema : null;
67
+ }
68
+
69
+ function resolveIssueMessageFromSchema(field, issue, schema = {}) {
70
+ const keyword = normalizeText(issue?.keyword);
71
+
72
+ if (field) {
73
+ const fieldSchema = resolveFieldSchema(schema, field);
74
+ const fieldMessages = resolveSchemaMessages(fieldSchema);
75
+
76
+ const fieldKeywordMessage = normalizeText(fieldMessages[keyword]);
77
+ if (fieldKeywordMessage) {
78
+ return fieldKeywordMessage;
79
+ }
80
+
81
+ const fieldDefaultMessage = normalizeText(fieldMessages.default);
82
+ if (fieldDefaultMessage) {
83
+ return fieldDefaultMessage;
84
+ }
85
+ }
86
+
87
+ const schemaMessages = resolveSchemaMessages(schema);
88
+ const schemaKeywordMessage = normalizeText(schemaMessages[keyword]);
89
+ if (schemaKeywordMessage) {
90
+ return schemaKeywordMessage;
91
+ }
92
+
93
+ const schemaDefaultMessage = normalizeText(schemaMessages.default);
94
+ if (schemaDefaultMessage) {
95
+ return schemaDefaultMessage;
96
+ }
97
+
98
+ const issueMessage = normalizeText(issue?.message);
99
+ if (issueMessage) {
100
+ return issueMessage;
101
+ }
102
+
103
+ return "Invalid value.";
104
+ }
105
+
106
+ function mapOperationIssues(issues = [], schema = {}) {
107
+ const source = Array.isArray(issues) ? issues : [];
108
+ const fieldErrors = {};
109
+ const globalErrors = [];
110
+
111
+ for (const issue of source) {
112
+ const missingRequiredFields = resolveMissingRequiredFields(issue);
113
+ if (missingRequiredFields.length > 0) {
114
+ for (const field of missingRequiredFields) {
115
+ if (Object.hasOwn(fieldErrors, field)) {
116
+ continue;
117
+ }
118
+
119
+ fieldErrors[field] = resolveIssueMessageFromSchema(field, issue, schema);
120
+ }
121
+
122
+ continue;
123
+ }
124
+
125
+ const field = resolveIssueField(issue);
126
+ if (field) {
127
+ if (!Object.hasOwn(fieldErrors, field)) {
128
+ fieldErrors[field] = resolveIssueMessageFromSchema(field, issue, schema);
129
+ }
130
+ continue;
131
+ }
132
+
133
+ globalErrors.push(resolveIssueMessageFromSchema("", issue, schema));
134
+ }
135
+
136
+ return {
137
+ fieldErrors,
138
+ globalErrors
139
+ };
140
+ }
141
+
142
+ export {
143
+ resolveSchemaMessages,
144
+ resolveFieldSchema,
145
+ resolveIssueField,
146
+ resolveMissingRequiredFields,
147
+ resolveIssueMessageFromSchema,
148
+ mapOperationIssues
149
+ };
@@ -0,0 +1,126 @@
1
+ import { Check, Errors } from "typebox/value";
2
+ import { mapOperationIssues } from "./operationMessages.js";
3
+ import { isRecord } from "@jskit-ai/kernel/shared/support/normalize";
4
+
5
+ function defaultNormalize(value) {
6
+ if (!isRecord(value)) {
7
+ return {};
8
+ }
9
+
10
+ return {
11
+ ...value
12
+ };
13
+ }
14
+
15
+ function resolveOperationSection(operation = {}, section = "bodyValidator") {
16
+ const source = isRecord(operation) ? operation : {};
17
+ const value = source[section];
18
+ if (!isRecord(value)) {
19
+ return null;
20
+ }
21
+
22
+ return value;
23
+ }
24
+
25
+ function validateOperationSection({
26
+ operation = {},
27
+ section = "bodyValidator",
28
+ value,
29
+ context = {}
30
+ } = {}) {
31
+ const sectionDefinition = resolveOperationSection(operation, section);
32
+ if (!sectionDefinition) {
33
+ return {
34
+ ok: true,
35
+ value,
36
+ normalized: value,
37
+ fieldErrors: {},
38
+ globalErrors: [],
39
+ issues: []
40
+ };
41
+ }
42
+
43
+ const schema = sectionDefinition.schema;
44
+ if (!isRecord(schema)) {
45
+ throw new TypeError(`Operation section \"${section}\" requires a schema object.`);
46
+ }
47
+
48
+ const normalize = typeof sectionDefinition.normalize === "function"
49
+ ? sectionDefinition.normalize
50
+ : defaultNormalize;
51
+
52
+ const normalized = normalize(value, context);
53
+ const issues = Check(schema, normalized) ? [] : [...Errors(schema, normalized)];
54
+ const mapped = mapOperationIssues(issues, schema);
55
+
56
+ return {
57
+ ok: issues.length < 1,
58
+ value: issues.length < 1 ? normalized : null,
59
+ normalized,
60
+ fieldErrors: mapped.fieldErrors,
61
+ globalErrors: mapped.globalErrors,
62
+ issues
63
+ };
64
+ }
65
+
66
+ function validateOperationInput({
67
+ operation = {},
68
+ input = {},
69
+ context = {}
70
+ } = {}) {
71
+ const source = isRecord(input) ? input : {};
72
+ const sectionResults = {
73
+ paramsValidator: validateOperationSection({
74
+ operation,
75
+ section: "paramsValidator",
76
+ value: source.params,
77
+ context
78
+ }),
79
+ queryValidator: validateOperationSection({
80
+ operation,
81
+ section: "queryValidator",
82
+ value: source.query,
83
+ context
84
+ }),
85
+ bodyValidator: validateOperationSection({
86
+ operation,
87
+ section: "bodyValidator",
88
+ value: source.body,
89
+ context
90
+ })
91
+ };
92
+
93
+ const fieldErrors = {
94
+ ...sectionResults.paramsValidator.fieldErrors,
95
+ ...sectionResults.queryValidator.fieldErrors,
96
+ ...sectionResults.bodyValidator.fieldErrors
97
+ };
98
+
99
+ const globalErrors = [
100
+ ...sectionResults.paramsValidator.globalErrors,
101
+ ...sectionResults.queryValidator.globalErrors,
102
+ ...sectionResults.bodyValidator.globalErrors
103
+ ];
104
+
105
+ return {
106
+ ok: sectionResults.paramsValidator.ok && sectionResults.queryValidator.ok && sectionResults.bodyValidator.ok,
107
+ value: {
108
+ params: sectionResults.paramsValidator.value,
109
+ query: sectionResults.queryValidator.value,
110
+ body: sectionResults.bodyValidator.value
111
+ },
112
+ normalized: {
113
+ params: sectionResults.paramsValidator.normalized,
114
+ query: sectionResults.queryValidator.normalized,
115
+ body: sectionResults.bodyValidator.normalized
116
+ },
117
+ fieldErrors,
118
+ globalErrors,
119
+ sections: sectionResults
120
+ };
121
+ }
122
+
123
+ export {
124
+ validateOperationSection,
125
+ validateOperationInput
126
+ };
@@ -0,0 +1,32 @@
1
+ import { Type } from "@fastify/type-provider-typebox";
2
+
3
+ function createPaginationQuerySchema({
4
+ defaultPage = 1,
5
+ defaultPageSize = 10,
6
+ minPage = 1,
7
+ minPageSize = 1,
8
+ maxPageSize = 100
9
+ } = {}) {
10
+ return Type.Object(
11
+ {
12
+ page: Type.Optional(
13
+ Type.Integer({
14
+ minimum: minPage,
15
+ default: defaultPage
16
+ })
17
+ ),
18
+ pageSize: Type.Optional(
19
+ Type.Integer({
20
+ minimum: minPageSize,
21
+ maximum: maxPageSize,
22
+ default: defaultPageSize
23
+ })
24
+ )
25
+ },
26
+ {
27
+ additionalProperties: false
28
+ }
29
+ );
30
+ }
31
+
32
+ export { createPaginationQuerySchema };
@@ -0,0 +1,43 @@
1
+ import { Type } from "@fastify/type-provider-typebox";
2
+ import { asSchema } from "./schemaUtils.js";
3
+
4
+ function createCursorPagedListResponseSchema(itemSchema) {
5
+ const normalizedItemSchema = asSchema(itemSchema, "itemSchema");
6
+ return Type.Object(
7
+ {
8
+ items: Type.Array(normalizedItemSchema),
9
+ nextCursor: Type.Union([Type.String({ minLength: 1 }), Type.Null()])
10
+ },
11
+ { additionalProperties: false }
12
+ );
13
+ }
14
+
15
+ function createResource({
16
+ record,
17
+ create,
18
+ replace,
19
+ patch,
20
+ list = null,
21
+ listItem = null
22
+ } = {}) {
23
+ const normalizedRecordSchema = asSchema(record, "record");
24
+ const normalizedCreateSchema = asSchema(create, "create");
25
+ const normalizedReplaceSchema = asSchema(replace, "replace");
26
+ const normalizedPatchSchema = asSchema(patch, "patch");
27
+ const normalizedListItemSchema = listItem ? asSchema(listItem, "listItem") : normalizedRecordSchema;
28
+ const normalizedListSchema = list ? asSchema(list, "list") : createCursorPagedListResponseSchema(normalizedListItemSchema);
29
+
30
+ return Object.freeze({
31
+ record: normalizedRecordSchema,
32
+ create: normalizedCreateSchema,
33
+ replace: normalizedReplaceSchema,
34
+ patch: normalizedPatchSchema,
35
+ listItem: normalizedListItemSchema,
36
+ list: normalizedListSchema
37
+ });
38
+ }
39
+
40
+ export {
41
+ createCursorPagedListResponseSchema,
42
+ createResource
43
+ };
@@ -0,0 +1,9 @@
1
+ function asSchema(value, label) {
2
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
3
+ throw new TypeError(`${label} must be a TypeBox schema object.`);
4
+ }
5
+
6
+ return value;
7
+ }
8
+
9
+ export { asSchema };