cloudcruise 1.2.1 → 1.4.0

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.
@@ -5,6 +5,8 @@ import { WorkflowsClient } from './workflows/WorkflowsClient.js';
5
5
  import { RunsClient } from './runs/RunsClient.js';
6
6
  import { WebhookClient } from './webhook/WebhookClient.js';
7
7
  import { ConnectionManager } from './utils/connectionManager.js';
8
+ import { inputValidationErrorFromResponse } from './workflows/validation.js';
9
+ import { clientIdentityHeaders } from './clientIdentity.js';
8
10
  const DEFAULT_BASE_URL = 'https://api.cloudcruise.com';
9
11
  const STAGING_BASE_URL = 'https://staging-api.cloudcruise.app';
10
12
  const ALLOWED_BASE_URLS = new Set([DEFAULT_BASE_URL, STAGING_BASE_URL]);
@@ -75,6 +77,7 @@ export class CloudCruise {
75
77
  async makeRequest(method, path, body) {
76
78
  const url = `${this.baseUrl}${path}`;
77
79
  const headers = {
80
+ ...clientIdentityHeaders(),
78
81
  'Content-Type': 'application/json',
79
82
  'cc-key': this.apiKey
80
83
  };
@@ -87,13 +90,25 @@ export class CloudCruise {
87
90
  if (!response.ok) {
88
91
  const errorText = await response.text();
89
92
  let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
93
+ let errorJson;
90
94
  try {
91
- const errorJson = JSON.parse(errorText);
92
- errorMessage = errorJson.message || errorJson.error || errorMessage;
95
+ errorJson = JSON.parse(errorText);
93
96
  }
94
97
  catch {
95
98
  // Use HTTP status if we can't parse error response
96
99
  }
100
+ if (typeof errorJson === 'object' &&
101
+ errorJson !== null &&
102
+ !Array.isArray(errorJson)) {
103
+ const errorBody = errorJson;
104
+ const validationError = inputValidationErrorFromResponse(errorBody, body);
105
+ if (validationError)
106
+ throw validationError;
107
+ errorMessage =
108
+ (typeof errorBody.message === 'string' && errorBody.message) ||
109
+ (typeof errorBody.error === 'string' && errorBody.error) ||
110
+ errorMessage;
111
+ }
97
112
  throw new Error(errorMessage);
98
113
  }
99
114
  const contentType = response.headers.get('content-type');
@@ -0,0 +1 @@
1
+ export declare function clientIdentityHeaders(): Record<string, string>;
@@ -0,0 +1,9 @@
1
+ import { detectAgent } from 'std-env';
2
+ import { SDK_VERSION } from './version.js';
3
+ export function clientIdentityHeaders() {
4
+ const agent = detectAgent().name;
5
+ return {
6
+ 'X-CloudCruise-Client': `sdk-js/${SDK_VERSION}`,
7
+ ...(agent ? { 'X-CloudCruise-Agent': agent } : {})
8
+ };
9
+ }
package/dist/index.d.ts CHANGED
@@ -10,7 +10,7 @@ export { WorkflowsClient } from './workflows/WorkflowsClient.js';
10
10
  export { RunsClient } from './runs/RunsClient.js';
11
11
  export { WebhookClient } from './webhook/WebhookClient.js';
12
12
  export type { VaultEntry, GetVaultEntriesFilters, ProxyConfig, VaultPostPutHeadersInBody, SecretProvider, SecretProviderItem, SecretProviderType } from './vault/types.js';
13
- export type { Workflow, WorkflowInputSchema, WorkflowMetadata } from './workflows/types.js';
13
+ export type { Workflow, WorkflowInputSchema, WorkflowVaultCredentialSchema, WorkflowVaultSchema, WorkflowMetadata, InvalidTypeDetail, SchemaErrorDetail } from './workflows/types.js';
14
14
  export type { DryRun, Metadata, RunSpecificWebhook, PayloadWebhook, StartRunRequest, StartRunResponse, UserInteractionData, VideoUrl, SignedFileUrl, SignedScreenshotUrl, RunError, WorkflowError, RunResult, GetRunResult, WebhookEvent, WebhookReplayResponse, RunHandle, RunStreamOptions, SseEventName, SseMessage, RunEventEnvelope, RunHandleEventMap, LiveViewConnection } from './runs/types.js';
15
15
  export { EventType } from './events/types.js';
16
16
  export type { WebhookMessage, RunEventMessage, AvailableAction, PopupRetry, PopupContext, InputRequiredReason, ExecutionInputRequiredPayload, AgentErrorAnalysisPayload } from './events/types.js';
@@ -2,6 +2,7 @@ import { openSSE } from './sse.js';
2
2
  import { SimpleEventEmitter } from './events.js';
3
3
  import { AsyncEventQueue } from './asyncQueue.js';
4
4
  import { EventType } from '../events/types.js';
5
+ import { clientIdentityHeaders } from '../clientIdentity.js';
5
6
  function isFinalEvent(eventType) {
6
7
  return (eventType === EventType.ExecutionSuccess ||
7
8
  eventType === EventType.ExecutionFailed ||
@@ -121,6 +122,7 @@ export class ConnectionManager {
121
122
  await this.ensureClientId();
122
123
  this.connecting = true;
123
124
  const headers = {
125
+ ...clientIdentityHeaders(),
124
126
  'cc-key': this.apiKey
125
127
  };
126
128
  const url = `${this.baseUrl}/run/clients/${this.clientId}/events`;
@@ -0,0 +1 @@
1
+ export declare const SDK_VERSION = "1.4.0";
@@ -0,0 +1 @@
1
+ export const SDK_VERSION = '1.4.0';
@@ -1,4 +1,4 @@
1
- import { InputValidationError } from './types.js';
1
+ import { validateInputSchema } from './validation.js';
2
2
  export class WorkflowsClient {
3
3
  makeRequest;
4
4
  constructor(makeRequest) {
@@ -16,7 +16,14 @@ export class WorkflowsClient {
16
16
  */
17
17
  async getWorkflowMetadata(workflowId) {
18
18
  const path = `/workflows/${workflowId}/metadata`;
19
- return await this.makeRequest('GET', path);
19
+ const response = await this.makeRequest('GET', path);
20
+ if (response &&
21
+ typeof response === 'object' &&
22
+ !('input_schema' in response) &&
23
+ response.metadata) {
24
+ return response.metadata;
25
+ }
26
+ return response;
20
27
  }
21
28
  /**
22
29
  * Validates a payload against a workflow's input schema.
@@ -24,74 +31,6 @@ export class WorkflowsClient {
24
31
  */
25
32
  async validateWorkflowInput(workflowId, payload) {
26
33
  const { input_schema } = await this.getWorkflowMetadata(workflowId);
27
- const schema = input_schema ?? {};
28
- const properties = schema.properties ?? {};
29
- const required = schema.required ?? [];
30
- const disallowExtras = schema.additionalProperties === false;
31
- // Check only required keys for presence and type
32
- const missingRequired = required.filter((key) => payload[key] === undefined);
33
- const invalidTypes = [];
34
- const detectType = (v) => {
35
- if (v === null)
36
- return 'null';
37
- if (Array.isArray(v))
38
- return 'array';
39
- if (typeof v === 'number')
40
- return Number.isInteger(v) ? 'integer' : 'number';
41
- return typeof v;
42
- };
43
- const allowedTypes = new Set(['array', 'boolean', 'integer', 'number', 'object', 'string', 'null']);
44
- const expectedTypesOf = (def) => {
45
- if (!def)
46
- return [];
47
- // Normalize to array-of-strings from either string | string[] | { type: string | string[] }
48
- const raw = (typeof def === 'object' && !Array.isArray(def)) ? def.type : def;
49
- if (!raw)
50
- return [];
51
- const arr = Array.isArray(raw) ? raw : [raw];
52
- return arr
53
- .map((t) => String(t).toLowerCase())
54
- .filter((t) => allowedTypes.has(t));
55
- };
56
- const matches = (expected, actual) => {
57
- if (expected.length === 0)
58
- return true; // unknown => don't enforce
59
- if (expected.includes(actual))
60
- return true;
61
- if (actual === 'integer' && expected.includes('number'))
62
- return true;
63
- return false;
64
- };
65
- // Validate types for:
66
- // - all required keys that are present
67
- // - optional keys if they exist in the payload
68
- for (const [key, schemaDef] of Object.entries(properties)) {
69
- if (payload[key] === undefined)
70
- continue; // optional and not provided
71
- const expected = expectedTypesOf(schemaDef);
72
- const actual = detectType(payload[key]);
73
- if (!matches(expected, actual)) {
74
- const exp = expected.length ? expected : ['any'];
75
- invalidTypes.push({ field: key, expected_display: exp.join(' | '), actual });
76
- }
77
- }
78
- // If additionalProperties is false, collect unknown keys present in payload
79
- const unknownKeys = disallowExtras
80
- ? Object.keys(payload).filter((k) => !(k in properties))
81
- : [];
82
- if (missingRequired.length || invalidTypes.length || unknownKeys.length) {
83
- const parts = [];
84
- if (missingRequired.length)
85
- parts.push(`missing required: ${missingRequired.join(', ')}`);
86
- if (invalidTypes.length) {
87
- parts.push(invalidTypes
88
- .map((e) => `${e.field}: expected ${e.expected_display}, got ${e.actual}`)
89
- .join('; '));
90
- }
91
- if (unknownKeys.length)
92
- parts.push(`unknown keys: ${unknownKeys.join(', ')}`);
93
- const message = `Workflow input validation failed: ${parts.join(' | ')}`;
94
- throw new InputValidationError(message, missingRequired, invalidTypes, unknownKeys);
95
- }
34
+ validateInputSchema(input_schema ?? {}, payload);
96
35
  }
97
36
  }
@@ -15,27 +15,43 @@ export interface Workflow {
15
15
  enable_service_unavailable_recovery: boolean;
16
16
  enable_action_timing_recovery: boolean;
17
17
  }
18
- export type WorkflowPropertySchema = string | string[] | {
18
+ export type WorkflowPropertySchema = boolean | string | string[] | {
19
19
  type?: string | string[];
20
20
  [key: string]: unknown;
21
21
  };
22
22
  export interface WorkflowInputSchema {
23
- type?: 'object';
23
+ type?: string | string[];
24
24
  properties?: Record<string, WorkflowPropertySchema>;
25
25
  required?: string[];
26
- additionalProperties?: boolean;
26
+ additionalProperties?: WorkflowPropertySchema;
27
+ [key: string]: unknown;
27
28
  }
29
+ export interface WorkflowVaultCredentialSchema {
30
+ type: 'credential';
31
+ domain: string;
32
+ example?: string;
33
+ }
34
+ export type WorkflowVaultSchema = Record<string, WorkflowVaultCredentialSchema>;
28
35
  export interface WorkflowMetadata {
29
36
  input_schema: WorkflowInputSchema;
37
+ workspace_id?: string;
38
+ vault_schema?: WorkflowVaultSchema;
30
39
  }
31
40
  export interface InvalidTypeDetail {
32
41
  field: string;
33
42
  expected_display: string;
34
43
  actual: string;
35
44
  }
45
+ export interface SchemaErrorDetail {
46
+ instancePath: string;
47
+ schemaPath: string;
48
+ keyword: string;
49
+ message: string;
50
+ }
36
51
  export declare class InputValidationError extends Error {
37
52
  readonly missingRequired: string[];
38
53
  readonly invalidTypes: InvalidTypeDetail[];
39
54
  readonly unknownKeys: string[];
40
- constructor(message?: string, missingRequired?: string[], invalidTypes?: InvalidTypeDetail[], unknownKeys?: string[]);
55
+ readonly schemaErrors: SchemaErrorDetail[];
56
+ constructor(message?: string, missingRequired?: string[], invalidTypes?: InvalidTypeDetail[], unknownKeys?: string[], schemaErrors?: SchemaErrorDetail[]);
41
57
  }
@@ -5,11 +5,13 @@ export class InputValidationError extends Error {
5
5
  missingRequired;
6
6
  invalidTypes;
7
7
  unknownKeys;
8
- constructor(message = 'Input validation failed', missingRequired = [], invalidTypes = [], unknownKeys = []) {
8
+ schemaErrors;
9
+ constructor(message = 'Input validation failed', missingRequired = [], invalidTypes = [], unknownKeys = [], schemaErrors = []) {
9
10
  super(message);
10
11
  this.name = 'InputValidationError';
11
12
  this.missingRequired = missingRequired;
12
13
  this.invalidTypes = invalidTypes;
13
14
  this.unknownKeys = unknownKeys;
15
+ this.schemaErrors = schemaErrors;
14
16
  }
15
17
  }
@@ -0,0 +1,3 @@
1
+ import { InputValidationError, type WorkflowInputSchema } from './types.js';
2
+ export declare function validateInputSchema(schema: WorkflowInputSchema, payload: Record<string, unknown>): void;
3
+ export declare function inputValidationErrorFromResponse(response: Record<string, unknown>, requestBody?: unknown): InputValidationError | undefined;
@@ -0,0 +1,354 @@
1
+ import Ajv from 'ajv';
2
+ import { InputValidationError } from './types.js';
3
+ const ajv = new Ajv({
4
+ allErrors: true,
5
+ strict: true,
6
+ validateFormats: false,
7
+ addUsedSchema: false,
8
+ removeAdditional: false,
9
+ strictSchema: false,
10
+ keywords: ['example']
11
+ });
12
+ const singleSchemaKeywords = new Set([
13
+ 'additionalItems',
14
+ 'additionalProperties',
15
+ 'contains',
16
+ 'else',
17
+ 'if',
18
+ 'not',
19
+ 'propertyNames',
20
+ 'then'
21
+ ]);
22
+ const arraySchemaKeywords = new Set(['allOf', 'anyOf', 'oneOf']);
23
+ const mappingSchemaKeywords = new Set([
24
+ 'definitions',
25
+ 'patternProperties',
26
+ 'properties'
27
+ ]);
28
+ export function validateInputSchema(schema, payload) {
29
+ const externalRef = firstExternalRef(schema);
30
+ if (externalRef) {
31
+ const detail = {
32
+ instancePath: '',
33
+ schemaPath: externalRef.path,
34
+ keyword: '$ref',
35
+ message: `external reference is not allowed: ${externalRef.ref}`
36
+ };
37
+ throw new InputValidationError(`Workflow input schema is invalid: ${detail.message}`, [], [], [], [detail]);
38
+ }
39
+ let validate;
40
+ try {
41
+ validate = ajv.compile(schema);
42
+ }
43
+ catch (error) {
44
+ const message = error instanceof Error ? error.message : String(error);
45
+ const detail = {
46
+ instancePath: '',
47
+ schemaPath: '#',
48
+ keyword: 'schema',
49
+ message
50
+ };
51
+ throw new InputValidationError(`Workflow input schema is invalid: ${message}`, [], [], [], [detail]);
52
+ }
53
+ if (validate(payload))
54
+ return;
55
+ const errors = [...(validate.errors ?? [])].sort(compareErrors);
56
+ const schemaErrors = errors.map(toSchemaError);
57
+ const missingRequired = missingRequiredFields(errors);
58
+ const invalidTypes = invalidTypeDetails(errors, payload);
59
+ const unknownKeys = unknownPropertyNames(errors);
60
+ throw new InputValidationError(validationMessage(schemaErrors, missingRequired, invalidTypes, unknownKeys), missingRequired, invalidTypes, unknownKeys, schemaErrors);
61
+ }
62
+ export function inputValidationErrorFromResponse(response, requestBody) {
63
+ const rawErrors = response.run_input_variables_errors;
64
+ if (!Array.isArray(rawErrors))
65
+ return undefined;
66
+ const schema = isObject(response.input_schema) ? response.input_schema : {};
67
+ const payload = isObject(requestBody) && isObject(requestBody.run_input_variables)
68
+ ? requestBody.run_input_variables
69
+ : {};
70
+ const schemaErrors = [];
71
+ const missingRequired = [];
72
+ const invalidTypes = [];
73
+ const unknownKeys = [];
74
+ for (const rawError of rawErrors) {
75
+ if (!isObject(rawError))
76
+ continue;
77
+ const field = String(rawError.field ?? '');
78
+ const keyword = String(rawError.keyword ?? 'validation');
79
+ const message = String(rawError.message ?? 'Invalid value');
80
+ const expected = isObject(rawError.expected) ? rawError.expected : {};
81
+ const instancePath = field.startsWith('/') ? field : '';
82
+ const schemaPath = field.startsWith('#')
83
+ ? field
84
+ : schemaPathForInstance(schema, instancePath, keyword);
85
+ schemaErrors.push({ instancePath, schemaPath, keyword, message });
86
+ if (keyword === 'required') {
87
+ const missingProperty = expected.missingProperty;
88
+ if (typeof missingProperty === 'string') {
89
+ const missingField = instancePath
90
+ ? `${instancePath}/${escapePointer(missingProperty)}`
91
+ : missingProperty;
92
+ pushUnique(missingRequired, missingField);
93
+ }
94
+ }
95
+ else if (keyword === 'type') {
96
+ const expectedType = expected.type;
97
+ const expectedTypes = Array.isArray(expectedType)
98
+ ? expectedType.map(String)
99
+ : String(expectedType ?? 'any').split(',');
100
+ invalidTypes.push({
101
+ field: displayField(instancePath),
102
+ expected_display: expectedTypes.join(' | '),
103
+ actual: detectType(valueAtPointer(payload, instancePath))
104
+ });
105
+ }
106
+ else if (keyword === 'additionalProperties') {
107
+ const additionalProperty = expected.additionalProperty;
108
+ if (typeof additionalProperty === 'string') {
109
+ const unknownField = instancePath
110
+ ? `${instancePath}/${escapePointer(additionalProperty)}`
111
+ : additionalProperty;
112
+ pushUnique(unknownKeys, unknownField);
113
+ }
114
+ }
115
+ }
116
+ if (!schemaErrors.length) {
117
+ schemaErrors.push({
118
+ instancePath: '',
119
+ schemaPath: '',
120
+ keyword: 'validation',
121
+ message: String(response.message ?? 'Validation failed for run_input_variables')
122
+ });
123
+ }
124
+ return new InputValidationError(validationMessage(schemaErrors, missingRequired, invalidTypes, unknownKeys), missingRequired, invalidTypes, unknownKeys, schemaErrors);
125
+ }
126
+ function firstExternalRef(schema, path = []) {
127
+ if (!isObject(schema))
128
+ return undefined;
129
+ if (typeof schema.$ref === 'string' && !schema.$ref.startsWith('#')) {
130
+ return {
131
+ path: jsonPointer([...path, '$ref'], '#'),
132
+ ref: schema.$ref
133
+ };
134
+ }
135
+ for (const keyword of singleSchemaKeywords) {
136
+ const found = firstExternalRef(schema[keyword], [...path, keyword]);
137
+ if (found)
138
+ return found;
139
+ }
140
+ const items = schema.items;
141
+ if (Array.isArray(items)) {
142
+ for (const [index, child] of items.entries()) {
143
+ const found = firstExternalRef(child, [...path, 'items', index]);
144
+ if (found)
145
+ return found;
146
+ }
147
+ }
148
+ else {
149
+ const found = firstExternalRef(items, [...path, 'items']);
150
+ if (found)
151
+ return found;
152
+ }
153
+ for (const keyword of arraySchemaKeywords) {
154
+ const children = schema[keyword];
155
+ if (!Array.isArray(children))
156
+ continue;
157
+ for (const [index, child] of children.entries()) {
158
+ const found = firstExternalRef(child, [...path, keyword, index]);
159
+ if (found)
160
+ return found;
161
+ }
162
+ }
163
+ for (const keyword of mappingSchemaKeywords) {
164
+ const children = schema[keyword];
165
+ if (!isObject(children))
166
+ continue;
167
+ for (const [name, child] of Object.entries(children)) {
168
+ const found = firstExternalRef(child, [...path, keyword, name]);
169
+ if (found)
170
+ return found;
171
+ }
172
+ }
173
+ const dependencies = schema.dependencies;
174
+ if (isObject(dependencies)) {
175
+ for (const [name, child] of Object.entries(dependencies)) {
176
+ if (!isObject(child))
177
+ continue;
178
+ const found = firstExternalRef(child, [...path, 'dependencies', name]);
179
+ if (found)
180
+ return found;
181
+ }
182
+ }
183
+ return undefined;
184
+ }
185
+ function compareErrors(left, right) {
186
+ return (left.instancePath.localeCompare(right.instancePath) ||
187
+ left.schemaPath.localeCompare(right.schemaPath) ||
188
+ left.keyword.localeCompare(right.keyword) ||
189
+ (left.message ?? '').localeCompare(right.message ?? ''));
190
+ }
191
+ function toSchemaError(error) {
192
+ return {
193
+ instancePath: error.instancePath,
194
+ schemaPath: error.schemaPath,
195
+ keyword: error.keyword,
196
+ message: error.message ?? 'validation failed'
197
+ };
198
+ }
199
+ function missingRequiredFields(errors) {
200
+ const fields = [];
201
+ for (const error of errors) {
202
+ if (error.keyword !== 'required')
203
+ continue;
204
+ const missingProperty = String(error.params.missingProperty);
205
+ const field = error.instancePath
206
+ ? `${error.instancePath}/${escapePointer(missingProperty)}`
207
+ : missingProperty;
208
+ pushUnique(fields, field);
209
+ }
210
+ return fields;
211
+ }
212
+ function invalidTypeDetails(errors, payload) {
213
+ const details = [];
214
+ for (const error of errors) {
215
+ if (error.keyword !== 'type')
216
+ continue;
217
+ const expected = String(error.params.type).split(',').join(' | ');
218
+ const value = valueAtPointer(payload, error.instancePath);
219
+ details.push({
220
+ field: displayField(error.instancePath),
221
+ expected_display: expected,
222
+ actual: detectType(value)
223
+ });
224
+ }
225
+ return details;
226
+ }
227
+ function unknownPropertyNames(errors) {
228
+ const fields = [];
229
+ for (const error of errors) {
230
+ if (error.keyword !== 'additionalProperties')
231
+ continue;
232
+ const property = String(error.params.additionalProperty);
233
+ const field = error.instancePath
234
+ ? `${error.instancePath}/${escapePointer(property)}`
235
+ : property;
236
+ pushUnique(fields, field);
237
+ }
238
+ return fields;
239
+ }
240
+ function validationMessage(schemaErrors, missingRequired, invalidTypes, unknownKeys) {
241
+ const parts = [];
242
+ if (missingRequired.length) {
243
+ parts.push(`missing required: ${missingRequired.join(', ')}`);
244
+ }
245
+ if (invalidTypes.length) {
246
+ parts.push(invalidTypes
247
+ .map((error) => `${error.field}: expected ${error.expected_display}, got ${error.actual}`)
248
+ .join('; '));
249
+ }
250
+ if (unknownKeys.length) {
251
+ parts.push(`unknown keys: ${unknownKeys.join(', ')}`);
252
+ }
253
+ const extraErrors = schemaErrors.filter((error) => !['required', 'type', 'additionalProperties'].includes(error.keyword));
254
+ if (extraErrors.length) {
255
+ parts.push(extraErrors
256
+ .map((error) => `${error.instancePath || '/'}: ${error.message}`)
257
+ .join('; '));
258
+ }
259
+ return `Workflow input validation failed: ${parts.join(' | ')}`;
260
+ }
261
+ function valueAtPointer(value, pointer) {
262
+ if (!pointer)
263
+ return value;
264
+ return pointer
265
+ .slice(1)
266
+ .split('/')
267
+ .map((part) => part.replace(/~1/g, '/').replace(/~0/g, '~'))
268
+ .reduce((current, part) => {
269
+ if (Array.isArray(current))
270
+ return current[Number(part)];
271
+ if (isObject(current))
272
+ return current[part];
273
+ return undefined;
274
+ }, value);
275
+ }
276
+ function displayField(instancePath) {
277
+ if (!instancePath)
278
+ return '$';
279
+ const withoutPrefix = instancePath.slice(1);
280
+ return withoutPrefix.includes('/') ? instancePath : withoutPrefix;
281
+ }
282
+ function schemaPathForInstance(schema, instancePath, keyword) {
283
+ let current = schema;
284
+ const schemaPath = [];
285
+ const parts = instancePath
286
+ ? instancePath
287
+ .slice(1)
288
+ .split('/')
289
+ .map((part) => part.replace(/~1/g, '/').replace(/~0/g, '~'))
290
+ : [];
291
+ for (const part of parts) {
292
+ if (!isObject(current))
293
+ return '';
294
+ const properties = current.properties;
295
+ if (isObject(properties) && part in properties) {
296
+ current = properties[part];
297
+ schemaPath.push('properties', part);
298
+ continue;
299
+ }
300
+ const items = current.items;
301
+ if (isObject(items) && /^\d+$/.test(part)) {
302
+ current = items;
303
+ schemaPath.push('items');
304
+ continue;
305
+ }
306
+ const patternProperties = current.patternProperties;
307
+ if (isObject(patternProperties)) {
308
+ const match = Object.entries(patternProperties).find(([pattern]) => {
309
+ try {
310
+ return new RegExp(pattern).test(part);
311
+ }
312
+ catch {
313
+ return false;
314
+ }
315
+ });
316
+ if (match) {
317
+ current = match[1];
318
+ schemaPath.push('patternProperties', match[0]);
319
+ continue;
320
+ }
321
+ }
322
+ return '';
323
+ }
324
+ if (isObject(current) && keyword in current) {
325
+ schemaPath.push(keyword);
326
+ return jsonPointer(schemaPath, '#');
327
+ }
328
+ return '';
329
+ }
330
+ function detectType(value) {
331
+ if (value === null)
332
+ return 'null';
333
+ if (Array.isArray(value))
334
+ return 'array';
335
+ if (typeof value === 'number') {
336
+ return Number.isInteger(value) ? 'integer' : 'number';
337
+ }
338
+ return typeof value;
339
+ }
340
+ function jsonPointer(path, prefix = '') {
341
+ if (!path.length)
342
+ return prefix;
343
+ return `${prefix}/${path.map((part) => escapePointer(String(part))).join('/')}`;
344
+ }
345
+ function escapePointer(value) {
346
+ return value.replace(/~/g, '~0').replace(/\//g, '~1');
347
+ }
348
+ function pushUnique(values, value) {
349
+ if (!values.includes(value))
350
+ values.push(value);
351
+ }
352
+ function isObject(value) {
353
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
354
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudcruise",
3
- "version": "1.2.1",
3
+ "version": "1.4.0",
4
4
  "description": "The official CloudCruise JS/TS client.",
5
5
  "homepage": "https://github.com/CloudCruise/cloudcruise-js#readme",
6
6
  "bugs": {
@@ -19,8 +19,8 @@
19
19
  "dist"
20
20
  ],
21
21
  "scripts": {
22
- "build": "tsc",
23
- "dev": "tsc --watch",
22
+ "build": "node scripts/sync-version.mjs && tsc",
23
+ "dev": "node scripts/sync-version.mjs && tsc --watch",
24
24
  "prepublishOnly": "npm run build",
25
25
  "test": "pnpm build && node --test test/*.test.js"
26
26
  },
@@ -30,5 +30,14 @@
30
30
  },
31
31
  "engines": {
32
32
  "node": ">=18.0.0"
33
+ },
34
+ "dependencies": {
35
+ "ajv": "^8.20.0",
36
+ "std-env": "^4.3.0"
37
+ },
38
+ "pnpm": {
39
+ "overrides": {
40
+ "fast-uri": "3.1.8"
41
+ }
33
42
  }
34
43
  }