From b2bd48e641605009360ddf918a339f23c09bd7b9 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 2 Aug 2026 12:03:31 +0000 Subject: [PATCH] fix(spec): anchor the Schema-suffix strip when deriving published schema names (#4592) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit build-schemas.ts and build-docs.ts derived the published JSON Schema name via key.replace('Schema', '') — a string pattern, which replaces the FIRST occurrence. Consts with 'Schema' in prefix/middle position (SchemaModeSchema, SchemaChangeSchema, SchemaLevelIsolationStrategySchema, DocumentSchemaValidationSchema) lost an inner segment instead of the suffix, publishing $id URLs, manifest keys and docs sections under type names that exist nowhere in the export surface. - extract the mapping into scripts/lib/schema-name.ts (schemaNameFromExportKey, /Schema$/ anchored) shared by both generators, with a pin test covering the four mangled names - swap the 4 stale json-schema.manifest.json keys for the corrected ones (deliberate retirement per #2978 — the old names never named a real type) - rename the 14 affected authorable-surface.json key prefixes (rename, not a key retirement: every authorable key survives under the corrected name) - shrink docs-import-surface.baseline.json 152 -> 144 (the 4 'no schema const export' / 'no type export' pairs are gaps that no longer exist) - regenerate the 4 reference-doc pages: sections renamed, real import examples restored Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_012C2cd7tL8QDoZ2QKN3djJ5 --- .changeset/schema-name-suffix-strip.md | 31 +++++++++++ content/docs/references/data/datasource.mdx | 6 +-- content/docs/references/data/driver-nosql.mdx | 6 +-- .../docs/references/system/deploy-bundle.mdx | 40 +++++++------- content/docs/references/system/tenant.mdx | 32 ++++++------ packages/spec/authorable-surface.json | 28 +++++----- .../spec/docs-import-surface.baseline.json | 8 --- packages/spec/json-schema.manifest.json | 10 ++-- packages/spec/scripts/build-docs.ts | 4 +- packages/spec/scripts/build-schemas.ts | 4 +- packages/spec/scripts/lib/schema-name.ts | 22 ++++++++ packages/spec/scripts/schema-name.test.ts | 52 +++++++++++++++++++ 12 files changed, 172 insertions(+), 71 deletions(-) create mode 100644 .changeset/schema-name-suffix-strip.md create mode 100644 packages/spec/scripts/lib/schema-name.ts create mode 100644 packages/spec/scripts/schema-name.test.ts diff --git a/.changeset/schema-name-suffix-strip.md b/.changeset/schema-name-suffix-strip.md new file mode 100644 index 0000000000..7be691fb4c --- /dev/null +++ b/.changeset/schema-name-suffix-strip.md @@ -0,0 +1,31 @@ +--- +"@objectstack/spec": patch +--- + +fix(spec): strip the `Schema` suffix by anchored regex when deriving published JSON Schema names (#4592) + +`build-schemas.ts` / `build-docs.ts` turned an exported const name into its +published schema name with `key.replace('Schema', '')` — a **string** pattern, +which replaces the FIRST occurrence. Every const whose name also contains +`Schema` in prefix/middle position lost that inner segment instead of its +suffix, so four schemas were published — `$id` URL, `json-schema.manifest.json` +key, docs page section, and import example — under type names that exist +nowhere in the export surface. Both generators now share one anchored helper +(`schemaNameFromExportKey`, `key.replace(/Schema$/, '')`). + +Corrected names, FROM → TO (the fix if you referenced an old `$id` under +`https://schema.objectstack.io/v17/...` is to swap in the new name — the TS +exports themselves never changed): + +| exported const | old (wrong) schema name | new schema name | +|:---|:---|:---| +| `SchemaModeSchema` | `data/ModeSchema` | `data/SchemaMode` | +| `SchemaChangeSchema` | `system/ChangeSchema` | `system/SchemaChange` | +| `SchemaLevelIsolationStrategySchema` | `system/LevelIsolationStrategySchema` | `system/SchemaLevelIsolationStrategy` | +| `DocumentSchemaValidationSchema` | `data/DocumentValidationSchema` | `data/DocumentSchemaValidation` | + +The four old manifest keys are removed as a deliberate retirement per the +#2978 rule (they never named a real exported type), and the four +`no schema const export` / `no type export` pairs they caused in +`docs-import-surface.baseline.json` are deleted (152 → 144 accepted gaps) — +the four reference-doc pages regain real, compilable import examples. diff --git a/content/docs/references/data/datasource.mdx b/content/docs/references/data/datasource.mdx index 93dbc1eb24..9375ad4784 100644 --- a/content/docs/references/data/datasource.mdx +++ b/content/docs/references/data/datasource.mdx @@ -16,8 +16,8 @@ Can be a built-in driver or a plugin-contributed driver (e.g., "com.vendor.snowf ## TypeScript Usage ```typescript -import { DatasourceSchema, DriverDefinitionSchema, DriverType, ExternalDatasourceSettingsSchema } from '@objectstack/spec/data'; -import type { Datasource, DriverDefinition, ExternalDatasourceSettings } from '@objectstack/spec/data'; +import { DatasourceSchema, DriverDefinitionSchema, DriverType, ExternalDatasourceSettingsSchema, SchemaModeSchema } from '@objectstack/spec/data'; +import type { Datasource, DriverDefinition, ExternalDatasourceSettings, SchemaMode } from '@objectstack/spec/data'; // Validate data const result = DatasourceSchema.parse(data); @@ -93,7 +93,7 @@ External datasource federation settings (schemaMode != "managed") --- -## ModeSchema +## SchemaMode Schema ownership mode diff --git a/content/docs/references/data/driver-nosql.mdx b/content/docs/references/data/driver-nosql.mdx index 827091e57c..081c4beaad 100644 --- a/content/docs/references/data/driver-nosql.mdx +++ b/content/docs/references/data/driver-nosql.mdx @@ -16,8 +16,8 @@ Supported NoSQL database types ## TypeScript Usage ```typescript -import { AggregationPipelineSchema, AggregationStageSchema, ConsistencyLevelSchema, NoSQLDataTypeMappingSchema, NoSQLDatabaseTypeSchema, NoSQLDriverConfigSchema, NoSQLIndexSchema, NoSQLIndexTypeSchema, NoSQLOperationTypeSchema, NoSQLQueryOptionsSchema, NoSQLTransactionOptionsSchema, ReplicationConfigSchema, ShardingConfigSchema } from '@objectstack/spec/data'; -import type { AggregationPipeline, AggregationStage, ConsistencyLevel, NoSQLDataTypeMapping, NoSQLDatabaseType, NoSQLDriverConfig, NoSQLIndex, NoSQLIndexType, NoSQLOperationType, NoSQLQueryOptions, NoSQLTransactionOptions, ReplicationConfig, ShardingConfig } from '@objectstack/spec/data'; +import { AggregationPipelineSchema, AggregationStageSchema, ConsistencyLevelSchema, DocumentSchemaValidationSchema, NoSQLDataTypeMappingSchema, NoSQLDatabaseTypeSchema, NoSQLDriverConfigSchema, NoSQLIndexSchema, NoSQLIndexTypeSchema, NoSQLOperationTypeSchema, NoSQLQueryOptionsSchema, NoSQLTransactionOptionsSchema, ReplicationConfigSchema, ShardingConfigSchema } from '@objectstack/spec/data'; +import type { AggregationPipeline, AggregationStage, ConsistencyLevel, DocumentSchemaValidation, NoSQLDataTypeMapping, NoSQLDatabaseType, NoSQLDriverConfig, NoSQLIndex, NoSQLIndexType, NoSQLOperationType, NoSQLQueryOptions, NoSQLTransactionOptions, ReplicationConfig, ShardingConfig } from '@objectstack/spec/data'; // Validate data const result = AggregationPipelineSchema.parse(data); @@ -64,7 +64,7 @@ const result = AggregationPipelineSchema.parse(data); --- -## DocumentValidationSchema +## DocumentSchemaValidation ### Properties diff --git a/content/docs/references/system/deploy-bundle.mdx b/content/docs/references/system/deploy-bundle.mdx index c8ce1c0df6..09c7b3cc33 100644 --- a/content/docs/references/system/deploy-bundle.mdx +++ b/content/docs/references/system/deploy-bundle.mdx @@ -28,31 +28,13 @@ Target: 2-5 second deploys vs. 2-15 minute traditional Docker/CI/CD. ## TypeScript Usage ```typescript -import { DeployBundleSchema, DeployDiffSchema, DeployManifestSchema, DeployStatusEnum, DeployValidationIssueSchema, DeployValidationResultSchema, MigrationPlanSchema, MigrationStatementSchema } from '@objectstack/spec/system'; -import type { DeployBundle, DeployDiff, DeployManifest, DeployValidationIssue, DeployValidationResult, MigrationPlan, MigrationStatement } from '@objectstack/spec/system'; +import { DeployBundleSchema, DeployDiffSchema, DeployManifestSchema, DeployStatusEnum, DeployValidationIssueSchema, DeployValidationResultSchema, MigrationPlanSchema, MigrationStatementSchema, SchemaChangeSchema } from '@objectstack/spec/system'; +import type { DeployBundle, DeployDiff, DeployManifest, DeployValidationIssue, DeployValidationResult, MigrationPlan, MigrationStatement, SchemaChange } from '@objectstack/spec/system'; // Validate data const result = DeployBundleSchema.parse(data); ``` ---- - -## ChangeSchema - -Individual schema change - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **entityType** | `Enum<'object' \| 'field' \| 'index' \| 'view' \| 'flow' \| 'permission'>` | ✅ | Entity type | -| **entityName** | `string` | ✅ | Entity name | -| **parentEntity** | `string` | optional | Parent entity name | -| **changeType** | `Enum<'added' \| 'modified' \| 'removed'>` | ✅ | Change type | -| **oldValue** | `any` | optional | Previous value | -| **newValue** | `any` | optional | New value | - - --- ## DeployBundle @@ -188,3 +170,21 @@ Single DDL migration statement --- +## SchemaChange + +Individual schema change + +### Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **entityType** | `Enum<'object' \| 'field' \| 'index' \| 'view' \| 'flow' \| 'permission'>` | ✅ | Entity type | +| **entityName** | `string` | ✅ | Entity name | +| **parentEntity** | `string` | optional | Parent entity name | +| **changeType** | `Enum<'added' \| 'modified' \| 'removed'>` | ✅ | Change type | +| **oldValue** | `any` | optional | Previous value | +| **newValue** | `any` | optional | New value | + + +--- + diff --git a/content/docs/references/system/tenant.mdx b/content/docs/references/system/tenant.mdx index f17f38c662..8c94ea9aac 100644 --- a/content/docs/references/system/tenant.mdx +++ b/content/docs/references/system/tenant.mdx @@ -28,8 +28,8 @@ Isolation Levels: ## TypeScript Usage ```typescript -import { DatabaseLevelIsolationStrategySchema, DatabaseProviderSchema, QuotaEnforcementResultSchema, RowLevelIsolationStrategySchema, TenantSchema, TenantConnectionConfigSchema, TenantIsolationConfigSchema, TenantIsolationLevel, TenantQuotaSchema, TenantSecurityPolicySchema, TenantUsageSchema } from '@objectstack/spec/system'; -import type { DatabaseLevelIsolationStrategy, DatabaseProvider, QuotaEnforcementResult, RowLevelIsolationStrategy, Tenant, TenantConnectionConfig, TenantIsolationConfig, TenantIsolationLevel, TenantQuota, TenantSecurityPolicy, TenantUsage } from '@objectstack/spec/system'; +import { DatabaseLevelIsolationStrategySchema, DatabaseProviderSchema, QuotaEnforcementResultSchema, RowLevelIsolationStrategySchema, SchemaLevelIsolationStrategySchema, TenantSchema, TenantConnectionConfigSchema, TenantIsolationConfigSchema, TenantIsolationLevel, TenantQuotaSchema, TenantSecurityPolicySchema, TenantUsageSchema } from '@objectstack/spec/system'; +import type { DatabaseLevelIsolationStrategy, DatabaseProvider, QuotaEnforcementResult, RowLevelIsolationStrategy, SchemaLevelIsolationStrategy, Tenant, TenantConnectionConfig, TenantIsolationConfig, TenantIsolationLevel, TenantQuota, TenantSecurityPolicy, TenantUsage } from '@objectstack/spec/system'; // Validate data const result = DatabaseLevelIsolationStrategySchema.parse(data); @@ -63,20 +63,6 @@ Database provider for tenant data * `memory` ---- - -## LevelIsolationStrategySchema - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **strategy** | `'isolated_schema'` | ✅ | Schema-level isolation strategy | -| **schema** | `{ namingPattern: string; includePublicSchema: boolean; sharedSchema: string; autoCreateSchema: boolean }` | optional | Schema configuration | -| **migrations** | `{ strategy: Enum<'parallel' \| 'sequential' \| 'on_demand'>; maxConcurrent: integer; rollbackOnError: boolean }` | optional | Migration configuration | -| **performance** | `{ poolPerSchema: boolean; schemaCacheTTL: integer }` | optional | Performance settings | - - --- ## QuotaEnforcementResult @@ -107,6 +93,20 @@ Quota enforcement check result | **performance** | `{ usePartialIndexes: boolean; usePartitioning: boolean; poolSizePerTenant?: integer }` | optional | Performance settings | +--- + +## SchemaLevelIsolationStrategy + +### Properties + +| Property | Type | Required | Description | +| :--- | :--- | :--- | :--- | +| **strategy** | `'isolated_schema'` | ✅ | Schema-level isolation strategy | +| **schema** | `{ namingPattern: string; includePublicSchema: boolean; sharedSchema: string; autoCreateSchema: boolean }` | optional | Schema configuration | +| **migrations** | `{ strategy: Enum<'parallel' \| 'sequential' \| 'on_demand'>; maxConcurrent: integer; rollbackOnError: boolean }` | optional | Migration configuration | +| **performance** | `{ poolPerSchema: boolean; schemaCacheTTL: integer }` | optional | Performance settings | + + --- ## Tenant diff --git a/packages/spec/authorable-surface.json b/packages/spec/authorable-surface.json index 4dda8f322a..d215d7a698 100644 --- a/packages/spec/authorable-surface.json +++ b/packages/spec/authorable-surface.json @@ -3264,16 +3264,16 @@ "data/Document:tags", "data/Document:template", "data/Document:versioning", + "data/DocumentSchemaValidation:enabled", + "data/DocumentSchemaValidation:jsonSchema", + "data/DocumentSchemaValidation:validationAction", + "data/DocumentSchemaValidation:validationLevel", "data/DocumentTemplate:description", "data/DocumentTemplate:fileType", "data/DocumentTemplate:fileUrl", "data/DocumentTemplate:id", "data/DocumentTemplate:name", "data/DocumentTemplate:placeholders", - "data/DocumentValidationSchema:enabled", - "data/DocumentValidationSchema:jsonSchema", - "data/DocumentValidationSchema:validationAction", - "data/DocumentValidationSchema:validationLevel", "data/DocumentVersion:checksum", "data/DocumentVersion:createdAt", "data/DocumentVersion:createdBy", @@ -5798,12 +5798,6 @@ "system/ChangeRequest:status", "system/ChangeRequest:title", "system/ChangeRequest:type", - "system/ChangeSchema:changeType", - "system/ChangeSchema:entityName", - "system/ChangeSchema:entityType", - "system/ChangeSchema:newValue", - "system/ChangeSchema:oldValue", - "system/ChangeSchema:parentEntity", "system/ChangeSet:author", "system/ChangeSet:createdAt", "system/ChangeSet:dependencies", @@ -6206,10 +6200,6 @@ "system/LWWRegister:type", "system/LWWRegister:value", "system/LWWRegister:vectorClock", - "system/LevelIsolationStrategySchema:migrations", - "system/LevelIsolationStrategySchema:performance", - "system/LevelIsolationStrategySchema:schema", - "system/LevelIsolationStrategySchema:strategy", "system/License:customFeatures", "system/License:customLimits", "system/License:expiresAt", @@ -6664,6 +6654,16 @@ "system/SMSTemplate:maxLength", "system/SMSTemplate:message", "system/SMSTemplate:variables", + "system/SchemaChange:changeType", + "system/SchemaChange:entityName", + "system/SchemaChange:entityType", + "system/SchemaChange:newValue", + "system/SchemaChange:oldValue", + "system/SchemaChange:parentEntity", + "system/SchemaLevelIsolationStrategy:migrations", + "system/SchemaLevelIsolationStrategy:performance", + "system/SchemaLevelIsolationStrategy:schema", + "system/SchemaLevelIsolationStrategy:strategy", "system/SearchConfig:analyzers", "system/SearchConfig:facets", "system/SearchConfig:indexes", diff --git a/packages/spec/docs-import-surface.baseline.json b/packages/spec/docs-import-surface.baseline.json index a3bff73dd8..d49939a787 100644 --- a/packages/spec/docs-import-surface.baseline.json +++ b/packages/spec/docs-import-surface.baseline.json @@ -45,8 +45,6 @@ "data/DateGranularity — no type export", "data/DateMacroPlaceholder — no type export", "data/DimensionType — no type export", - "data/DocumentValidationSchema — no schema const export", - "data/DocumentValidationSchema — no type export", "data/DriverSslToggle — no type export", "data/DriverType — no type export", "data/EqualityOperator — no type export", @@ -57,8 +55,6 @@ "data/Index — no type export", "data/InstantValue — no type export", "data/LocationValue — no type export", - "data/ModeSchema — no schema const export", - "data/ModeSchema — no type export", "data/Object — no type export", "data/ObjectOwnershipEnum — no type export", "data/Query — no type export", @@ -95,8 +91,6 @@ "studio/ActivationEvent — no type export", "studio/PanelLocation — no type export", "system/AddFieldOperation — no type export", - "system/ChangeSchema — no schema const export", - "system/ChangeSchema — no type export", "system/CreateObjectOperation — no type export", "system/DeleteObjectOperation — no type export", "system/DeployStatusEnum — no type export", @@ -104,8 +98,6 @@ "system/EnvironmentArtifactHashAlgorithmEnum — no type export", "system/ExecuteSqlOperation — no type export", "system/KernelServiceMap — no type export", - "system/LevelIsolationStrategySchema — no schema const export", - "system/LevelIsolationStrategySchema — no type export", "system/Locale — no type export", "system/MetadataState — no type export", "system/MigrationDependency — no type export", diff --git a/packages/spec/json-schema.manifest.json b/packages/spec/json-schema.manifest.json index 47ef846db3..cb2b064712 100644 --- a/packages/spec/json-schema.manifest.json +++ b/packages/spec/json-schema.manifest.json @@ -1,5 +1,5 @@ { - "description": "Ratchet manifest of every JSON Schema emitted by scripts/build-schemas.ts. Auto-appended when new schemas are added (commit the change). A listed schema that a build no longer emits fails gen:schema \u2014 remove a key ONLY for a deliberate retirement. See #2978.", + "description": "Ratchet manifest of every JSON Schema emitted by scripts/build-schemas.ts. Auto-appended when new schemas are added (commit the change). A listed schema that a build no longer emits fails gen:schema — remove a key ONLY for a deliberate retirement. See #2978.", "schemas": [ "ai/AIModelConfig", "ai/AIUsageRecord", @@ -724,8 +724,8 @@ "data/Dimension", "data/DimensionType", "data/Document", + "data/DocumentSchemaValidation", "data/DocumentTemplate", - "data/DocumentValidationSchema", "data/DocumentVersion", "data/DriverCapabilities", "data/DriverConfig", @@ -778,7 +778,6 @@ "data/LocationValue", "data/Mapping", "data/Metric", - "data/ModeSchema", "data/MongoConfig", "data/MysqlConfig", "data/NoSQLDataTypeMapping", @@ -813,6 +812,7 @@ "data/SQLDialect", "data/SQLDriverConfig", "data/SSLConfig", + "data/SchemaMode", "data/ScriptBody", "data/ScriptValidation", "data/Seed", @@ -1229,7 +1229,6 @@ "system/ChangeImpact", "system/ChangePriority", "system/ChangeRequest", - "system/ChangeSchema", "system/ChangeSet", "system/ChangeStatus", "system/ChangeType", @@ -1319,7 +1318,6 @@ "system/KeyManagementProvider", "system/KeyRotationPolicy", "system/LWWRegister", - "system/LevelIsolationStrategySchema", "system/License", "system/LicenseMetricType", "system/LifecycleAction", @@ -1415,6 +1413,8 @@ "system/SamplingDecision", "system/SamplingStrategyType", "system/Schedule", + "system/SchemaChange", + "system/SchemaLevelIsolationStrategy", "system/SearchConfig", "system/SearchIndexConfig", "system/SearchProvider", diff --git a/packages/spec/scripts/build-docs.ts b/packages/spec/scripts/build-docs.ts index 2b4b6a0da9..35437582aa 100644 --- a/packages/spec/scripts/build-docs.ts +++ b/packages/spec/scripts/build-docs.ts @@ -27,6 +27,7 @@ import { type CategorySurface, } from './lib/docs-import-surface'; import { createSink } from './lib/generated-output'; +import { schemaNameFromExportKey } from './lib/schema-name'; const SCHEMA_DIR = path.resolve(__dirname, '../json-schema'); const SRC_DIR = path.resolve(__dirname, '../src'); @@ -130,7 +131,8 @@ function scanCategories() { let match; while ((match = regex.exec(content)) !== null) { const rawName = match[1]; - const finalName = rawName.endsWith('Schema') ? rawName.replace('Schema', '') : rawName; + // Suffix-only strip — shared with build-schemas.ts; see lib/schema-name.ts (#4592). + const finalName = schemaNameFromExportKey(rawName); schemaCategoryMap.set(finalName, category); schemaZodFileMap.set(finalName, slug); } diff --git a/packages/spec/scripts/build-schemas.ts b/packages/spec/scripts/build-schemas.ts index 486972adb2..82871782c8 100644 --- a/packages/spec/scripts/build-schemas.ts +++ b/packages/spec/scripts/build-schemas.ts @@ -8,6 +8,7 @@ process.env.OS_EAGER_SCHEMAS = '1'; import fs from 'fs'; import path from 'path'; import { z } from 'zod'; +import { schemaNameFromExportKey } from './lib/schema-name'; import { CONVERSIONS_BY_MAJOR } from '../src/conversions/registry'; import { MIGRATIONS_BY_MAJOR } from '../src/migrations/registry'; import * as AI from '../src/ai'; @@ -197,7 +198,8 @@ for (const [namespaceName, namespaceExports] of Object.entries(Protocol)) { for (const [key, value] of Object.entries(namespaceExports)) { // Check if it looks like a Zod Schema if (value instanceof z.ZodType) { - const schemaName = key.endsWith('Schema') ? key.replace('Schema', '') : key; + // Suffix-only strip — shared with build-docs.ts; see lib/schema-name.ts (#4592). + const schemaName = schemaNameFromExportKey(key); try { // Convert to JSON Schema using Zod v4's built-in toJSONSchema(). diff --git a/packages/spec/scripts/lib/schema-name.ts b/packages/spec/scripts/lib/schema-name.ts new file mode 100644 index 0000000000..3fbace8394 --- /dev/null +++ b/packages/spec/scripts/lib/schema-name.ts @@ -0,0 +1,22 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Published JSON Schema name for an exported schema const. + * + * The convention is `Schema` (const) → `` (schema/doc/type name), + * and the strip must be anchored to the END of the identifier: a string-pattern + * `String.prototype.replace('Schema', '')` removes the FIRST occurrence, which + * mangles every name that also contains `Schema` in prefix/middle position — + * `SchemaModeSchema` became `ModeSchema` instead of `SchemaMode`, publishing a + * `$id`, a manifest key and a docs section under a type name that does not + * exist (#4592). + * + * Single source of truth for `build-schemas.ts` (the `$id` / manifest / + * authorable-surface key) and `build-docs.ts` (the docs section + import + * example), so the two generators cannot drift apart again. The inverse + * lookup (`lib/docs-import-surface.ts`) appends the suffix and is already + * anchored by construction. + */ +export function schemaNameFromExportKey(key: string): string { + return key.replace(/Schema$/, ''); +} diff --git a/packages/spec/scripts/schema-name.test.ts b/packages/spec/scripts/schema-name.test.ts new file mode 100644 index 0000000000..f7576b2aa2 --- /dev/null +++ b/packages/spec/scripts/schema-name.test.ts @@ -0,0 +1,52 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Pin for the export-const → JSON Schema name mapping (#4592). + * + * The original inline `key.replace('Schema', '')` used a STRING pattern, and + * `String.prototype.replace(string, …)` replaces only the FIRST occurrence — + * so any const with `Schema` in prefix/middle position lost that inner + * segment instead of its suffix. Four schemas published under names that + * exist nowhere in the export surface (`data/ModeSchema` for + * `SchemaModeSchema`, …), which poisoned all three public projections of the + * name: the `$id` URL, the json-schema.manifest.json ratchet key, and the + * docs section + import example (the docs then dropped the import entirely + * because the mangled name resolved to no real export — #4570). + * + * MEASURED: reverting `schemaNameFromExportKey` to the string-pattern replace + * fails the four prefix/middle cases below; the suffix-only cases keep + * passing, which is exactly why the bug survived — every name without a + * second `Schema` mapped correctly. + */ + +import { describe, expect, it } from 'vitest'; + +import { schemaNameFromExportKey } from './lib/schema-name'; + +describe('schemaNameFromExportKey', () => { + it('strips only the trailing Schema suffix on prefix-Schema names (the #4592 quartet)', () => { + expect(schemaNameFromExportKey('SchemaModeSchema')).toBe('SchemaMode'); + expect(schemaNameFromExportKey('SchemaChangeSchema')).toBe('SchemaChange'); + expect(schemaNameFromExportKey('SchemaLevelIsolationStrategySchema')).toBe( + 'SchemaLevelIsolationStrategy', + ); + expect(schemaNameFromExportKey('DocumentSchemaValidationSchema')).toBe( + 'DocumentSchemaValidation', + ); + }); + + it('strips a plain trailing suffix', () => { + expect(schemaNameFromExportKey('FieldSchema')).toBe('Field'); + expect(schemaNameFromExportKey('DatasourceSchema')).toBe('Datasource'); + }); + + it('leaves non-Schema-suffixed names untouched', () => { + expect(schemaNameFromExportKey('DriverType')).toBe('DriverType'); + expect(schemaNameFromExportKey('SchemaMode')).toBe('SchemaMode'); + }); + + it('strips exactly one suffix occurrence', () => { + // `XSchemaSchema` names the schema OF `XSchema` — only the outer suffix goes. + expect(schemaNameFromExportKey('JsonSchemaSchema')).toBe('JsonSchema'); + }); +});