@stripe/extensibility-custom-objects-tools 0.41.0 → 0.42.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/dist/internal.js CHANGED
@@ -8117,6 +8117,7 @@ function buildCustomObjectPackage(options, dependencies = {}) {
8117
8117
  appendTransformPlan(transformPlans, target.modulePath, analysis.transformPlan);
8118
8118
  }
8119
8119
  if (analysis.buildResult) {
8120
+ diagnostics.push(...validateCustomObjectBuildResult(analysis.buildResult));
8120
8121
  objects[toObjectKey(target)] = analysis.buildResult;
8121
8122
  }
8122
8123
  }
@@ -8157,6 +8158,377 @@ function buildCustomObjectPackage(options, dependencies = {}) {
8157
8158
  function toObjectKey(target) {
8158
8159
  return `${target.modulePath}#${target.exportName}`;
8159
8160
  }
8161
+ function validateCustomObjectBuildResult(buildResult) {
8162
+ const diagnostics = [];
8163
+ const fieldsSchema = resolveTopLevelSchema(buildResult.fields.jsonSchema);
8164
+ validateTopLevelObjectShape(fieldsSchema, buildResult, "fields_payload", diagnostics);
8165
+ validatePropertyMap(fieldsSchema, buildResult, diagnostics, "field");
8166
+ for (const [actionApiName, action] of Object.entries(buildResult.actions)) {
8167
+ if (action.input?.jsonSchema) {
8168
+ const inputSchema = resolveTopLevelSchema(action.input.jsonSchema);
8169
+ validateTopLevelObjectShape(
8170
+ inputSchema,
8171
+ buildResult,
8172
+ "action_input_payload",
8173
+ diagnostics,
8174
+ actionApiName
8175
+ );
8176
+ validatePropertyMap(
8177
+ inputSchema,
8178
+ buildResult,
8179
+ diagnostics,
8180
+ "action_input_field",
8181
+ actionApiName
8182
+ );
8183
+ }
8184
+ const outputSchema = resolveTopLevelSchema(action.output.jsonSchema);
8185
+ validateTopLevelObjectShape(
8186
+ outputSchema,
8187
+ buildResult,
8188
+ "action_output_payload",
8189
+ diagnostics,
8190
+ actionApiName
8191
+ );
8192
+ validatePropertyMap(
8193
+ outputSchema,
8194
+ buildResult,
8195
+ diagnostics,
8196
+ "action_output_field",
8197
+ actionApiName
8198
+ );
8199
+ }
8200
+ return diagnostics;
8201
+ }
8202
+ function validatePropertyMap(schema, buildResult, diagnostics, kind, actionApiName) {
8203
+ if (!schema?.properties) {
8204
+ return;
8205
+ }
8206
+ const defs = schema.$defs ?? {};
8207
+ for (const [propertyName, propSchema] of Object.entries(schema.properties)) {
8208
+ const resolved = resolveSchemaRef(propSchema, defs);
8209
+ validatePropertySchema(
8210
+ resolved,
8211
+ buildResult,
8212
+ diagnostics,
8213
+ buildPropertyScope(kind, propertyName, actionApiName)
8214
+ );
8215
+ }
8216
+ }
8217
+ function buildPropertyScope(kind, propertyName, actionApiName) {
8218
+ switch (kind) {
8219
+ case "field":
8220
+ return { kind, propertyName };
8221
+ case "action_input_field":
8222
+ case "action_output_field":
8223
+ return {
8224
+ kind,
8225
+ actionApiName: actionApiName ?? "(unknown action)",
8226
+ propertyName
8227
+ };
8228
+ }
8229
+ }
8230
+ function validateTopLevelObjectShape(schema, buildResult, kind, diagnostics, actionApiName) {
8231
+ const hasProperties = schema.properties !== void 0 && Object.keys(schema.properties).length > 0;
8232
+ const looksObjectLike = schema.type === "object" || hasProperties;
8233
+ if (schema.additionalProperties !== void 0 && schema.additionalProperties !== false) {
8234
+ diagnostics.push(
8235
+ createUnsupportedSchemaDiagnostic(
8236
+ kind,
8237
+ buildResult,
8238
+ buildPayloadScope(kind, actionApiName),
8239
+ schema,
8240
+ getTopLevelMapShapeDetail(kind)
8241
+ )
8242
+ );
8243
+ return;
8244
+ }
8245
+ if (looksObjectLike) {
8246
+ return;
8247
+ }
8248
+ diagnostics.push(
8249
+ createUnsupportedSchemaDiagnostic(
8250
+ kind,
8251
+ buildResult,
8252
+ buildPayloadScope(kind, actionApiName),
8253
+ schema,
8254
+ getTopLevelObjectShapeDetail(kind)
8255
+ )
8256
+ );
8257
+ }
8258
+ function buildPayloadScope(kind, actionApiName) {
8259
+ switch (kind) {
8260
+ case "fields_payload":
8261
+ return { kind };
8262
+ case "action_input_payload":
8263
+ case "action_output_payload":
8264
+ return { kind, actionApiName: actionApiName ?? "(unknown action)" };
8265
+ }
8266
+ }
8267
+ function getTopLevelObjectShapeDetail(kind) {
8268
+ switch (kind) {
8269
+ case "fields_payload":
8270
+ return "Custom object fields must be object-shaped.";
8271
+ case "action_input_payload":
8272
+ case "action_output_payload":
8273
+ return "Action inputs and outputs must be object-shaped for custom objects.";
8274
+ }
8275
+ }
8276
+ function getTopLevelMapShapeDetail(kind) {
8277
+ switch (kind) {
8278
+ case "fields_payload":
8279
+ return "Custom object fields must declare named fields. Record<string, T> and other map-shaped field payloads are not supported for custom objects.";
8280
+ case "action_input_payload":
8281
+ case "action_output_payload":
8282
+ return "Action inputs and outputs must declare named fields. Record<string, T> and other map-shaped payloads are not supported for custom objects.";
8283
+ }
8284
+ }
8285
+ function validatePropertySchema(schema, buildResult, diagnostics, scope) {
8286
+ if (hasNonStringEnumValues(schema)) {
8287
+ diagnostics.push(
8288
+ createUnsupportedSchemaDiagnostic(
8289
+ scope.kind,
8290
+ buildResult,
8291
+ scope,
8292
+ schema,
8293
+ "Only string enums are supported for custom objects."
8294
+ )
8295
+ );
8296
+ return;
8297
+ }
8298
+ if (schema.format === "decimal") {
8299
+ diagnostics.push(
8300
+ createUnsupportedSchemaDiagnostic(
8301
+ scope.kind,
8302
+ buildResult,
8303
+ scope,
8304
+ schema,
8305
+ "Decimal fields are not supported for custom objects."
8306
+ )
8307
+ );
8308
+ return;
8309
+ }
8310
+ if (schema.type === "number") {
8311
+ diagnostics.push(
8312
+ createUnsupportedSchemaDiagnostic(
8313
+ scope.kind,
8314
+ buildResult,
8315
+ scope,
8316
+ schema,
8317
+ "Number fields are not supported for custom objects. Use Integer for whole numbers."
8318
+ )
8319
+ );
8320
+ return;
8321
+ }
8322
+ if (schema.type === "array") {
8323
+ diagnostics.push(
8324
+ createUnsupportedSchemaDiagnostic(
8325
+ scope.kind,
8326
+ buildResult,
8327
+ scope,
8328
+ schema,
8329
+ "Array fields are not supported for custom objects."
8330
+ )
8331
+ );
8332
+ return;
8333
+ }
8334
+ const refTarget = schema.type === "object" ? getRefTargetType(schema) : null;
8335
+ if (schema.type === "object" && refTarget === null) {
8336
+ diagnostics.push(
8337
+ createUnsupportedSchemaDiagnostic(
8338
+ scope.kind,
8339
+ buildResult,
8340
+ scope,
8341
+ schema,
8342
+ "Nested object fields are not supported for custom objects. Use Ref<T> for object references."
8343
+ )
8344
+ );
8345
+ return;
8346
+ }
8347
+ validateNumericBoundary(schema, buildResult, diagnostics, scope);
8348
+ validateDefaultValue(schema, buildResult, diagnostics, scope);
8349
+ if (!isSupportedCustomObjectSchema(schema, refTarget)) {
8350
+ diagnostics.push(
8351
+ createUnsupportedSchemaDiagnostic(
8352
+ scope.kind,
8353
+ buildResult,
8354
+ scope,
8355
+ schema,
8356
+ "This schema shape is not supported for custom objects."
8357
+ )
8358
+ );
8359
+ }
8360
+ }
8361
+ function validateNumericBoundary(schema, buildResult, diagnostics, scope) {
8362
+ const boundaries = [
8363
+ ["minimum", schema.minimum],
8364
+ ["exclusiveMinimum", schema.exclusiveMinimum],
8365
+ ["maximum", schema.maximum],
8366
+ ["exclusiveMaximum", schema.exclusiveMaximum]
8367
+ ];
8368
+ for (const [name, value] of boundaries) {
8369
+ if (value === void 0 || Number.isInteger(value)) {
8370
+ continue;
8371
+ }
8372
+ diagnostics.push(
8373
+ createUnsupportedSchemaDiagnostic(
8374
+ scope.kind,
8375
+ buildResult,
8376
+ scope,
8377
+ schema,
8378
+ `${name}=${String(value)} is not representable for custom objects; only integer numeric boundaries are supported.`
8379
+ )
8380
+ );
8381
+ }
8382
+ }
8383
+ function validateDefaultValue(schema, buildResult, diagnostics, scope) {
8384
+ if (schema.type === "integer" && schema.default !== void 0 && (typeof schema.default !== "number" || !Number.isInteger(schema.default))) {
8385
+ diagnostics.push(
8386
+ createUnsupportedSchemaDiagnostic(
8387
+ scope.kind,
8388
+ buildResult,
8389
+ scope,
8390
+ schema,
8391
+ `default=${JSON.stringify(schema.default)} is not representable for custom objects; integer fields require integer default values.`
8392
+ )
8393
+ );
8394
+ }
8395
+ }
8396
+ function isSupportedCustomObjectSchema(schema, refTarget) {
8397
+ if (extractEnumValues(schema) !== null) {
8398
+ return true;
8399
+ }
8400
+ if (refTarget !== null) {
8401
+ return true;
8402
+ }
8403
+ switch (schema.type) {
8404
+ case "string":
8405
+ case "integer":
8406
+ case "boolean":
8407
+ return true;
8408
+ default:
8409
+ return false;
8410
+ }
8411
+ }
8412
+ function createUnsupportedSchemaDiagnostic(kind, buildResult, scope, schema, detail) {
8413
+ const { modulePath, className } = buildResult.platformMetadata;
8414
+ return {
8415
+ severity: "error",
8416
+ code: getUnsupportedSchemaDiagnosticCode(kind),
8417
+ message: `${describeValidationScope(scope)} on "${className}" in "${modulePath}" is not supported for custom objects (${describeSchema(schema)}). ${detail}`,
8418
+ modulePath,
8419
+ exportName: className
8420
+ };
8421
+ }
8422
+ function getUnsupportedSchemaDiagnosticCode(kind) {
8423
+ switch (kind) {
8424
+ case "fields_payload":
8425
+ case "field":
8426
+ return "UNSUPPORTED_FIELD_SCHEMA";
8427
+ case "action_input_field":
8428
+ case "action_input_payload":
8429
+ return "UNSUPPORTED_ACTION_INPUT_SCHEMA";
8430
+ case "action_output_field":
8431
+ case "action_output_payload":
8432
+ return "UNSUPPORTED_ACTION_OUTPUT_SCHEMA";
8433
+ }
8434
+ }
8435
+ function describeValidationScope(scope) {
8436
+ switch (scope.kind) {
8437
+ case "fields_payload":
8438
+ return "Custom object fields schema";
8439
+ case "field":
8440
+ return `Field "${scope.propertyName}"`;
8441
+ case "action_input_field":
8442
+ return `Action "${scope.actionApiName}" input field "${scope.propertyName}"`;
8443
+ case "action_output_field":
8444
+ return `Action "${scope.actionApiName}" output field "${scope.propertyName}"`;
8445
+ case "action_input_payload":
8446
+ return `Action "${scope.actionApiName}" input payload`;
8447
+ case "action_output_payload":
8448
+ return `Action "${scope.actionApiName}" output payload`;
8449
+ }
8450
+ }
8451
+ function describeSchema(schema) {
8452
+ if (Array.isArray(schema.type)) {
8453
+ return `type: [${schema.type.join(", ")}]`;
8454
+ }
8455
+ if (schema.type === "string" && typeof schema.format === "string") {
8456
+ return `type: string, format: ${schema.format}`;
8457
+ }
8458
+ if (schema.type === "object") {
8459
+ return "type: object";
8460
+ }
8461
+ if (schema.type !== void 0) {
8462
+ return `type: ${schema.type}`;
8463
+ }
8464
+ if (Array.isArray(schema.anyOf) && schema.anyOf.length > 0) {
8465
+ return "anyOf union";
8466
+ }
8467
+ if (Array.isArray(schema.oneOf) && schema.oneOf.length > 0) {
8468
+ return "oneOf union";
8469
+ }
8470
+ return "untyped schema";
8471
+ }
8472
+ function resolveSchemaRef(schema, defs) {
8473
+ const ref = schema.$ref;
8474
+ if (!ref) return schema;
8475
+ if (!ref.startsWith("#/$defs/")) return schema;
8476
+ const defName = ref.slice("#/$defs/".length);
8477
+ const defSchema = defs[defName];
8478
+ if (!defSchema) return schema;
8479
+ const { $ref: _, ...rest } = schema;
8480
+ return { ...defSchema, ...rest };
8481
+ }
8482
+ function resolveTopLevelSchema(schema) {
8483
+ return resolveSchemaRef(schema, schema.$defs ?? {});
8484
+ }
8485
+ function getRefTargetType(schema) {
8486
+ if (!schema.properties) return null;
8487
+ const properties = schema.properties;
8488
+ const realProps = Object.keys(properties).filter((key) => !key.startsWith("__"));
8489
+ const allowedProps = /* @__PURE__ */ new Set(["id", "type", "url"]);
8490
+ if (realProps.length === 0 || realProps.some((key) => !allowedProps.has(key))) {
8491
+ return null;
8492
+ }
8493
+ const idProp = properties.id;
8494
+ const typeProp = properties.type;
8495
+ if (!idProp || !typeProp) return null;
8496
+ if (idProp.type !== "string") return null;
8497
+ const requiredSet = new Set(schema.required ?? []);
8498
+ if (!requiredSet.has("id") || !requiredSet.has("type")) return null;
8499
+ return extractSingleLiteral(typeProp);
8500
+ }
8501
+ function extractSingleLiteral(schema) {
8502
+ if (Array.isArray(schema.enum) && schema.enum.length === 1 && typeof schema.enum[0] === "string") {
8503
+ return schema.enum[0];
8504
+ }
8505
+ if (Array.isArray(schema.oneOf) && schema.oneOf.length === 1 && schema.oneOf.every(
8506
+ (item) => "const" in item && typeof item.const === "string"
8507
+ )) {
8508
+ const values = schema.oneOf.map(
8509
+ (item) => String(item.const)
8510
+ );
8511
+ return values[0] ?? null;
8512
+ }
8513
+ return null;
8514
+ }
8515
+ function extractEnumValues(schema) {
8516
+ if (Array.isArray(schema.enum) && schema.enum.every((value) => typeof value === "string")) {
8517
+ return schema.enum.map(String);
8518
+ }
8519
+ if (Array.isArray(schema.oneOf) && schema.oneOf.length > 0 && schema.oneOf.every(
8520
+ (item) => "const" in item && typeof item.const === "string"
8521
+ )) {
8522
+ return schema.oneOf.map((item) => String(item.const));
8523
+ }
8524
+ return null;
8525
+ }
8526
+ function hasNonStringEnumValues(schema) {
8527
+ if (Array.isArray(schema.enum) && schema.enum.length > 0) {
8528
+ return schema.enum.some((value) => typeof value !== "string");
8529
+ }
8530
+ return Array.isArray(schema.oneOf) && schema.oneOf.length > 0 && schema.oneOf.every((item) => "const" in item) && schema.oneOf.some((item) => typeof item.const !== "string");
8531
+ }
8160
8532
  var PRIMARY_FIELD_TAG_RE = /@primaryField\s*(?:$|\*)/m;
8161
8533
  var SECONDARY_FIELD_TAG_RE = /@secondaryField\s*(?:$|\*)/m;
8162
8534
  var API_NAME_TAG_RE = /@apiName\s+(\S+)/;