@prisma-next/family-sql 0.16.0-dev.30 → 0.16.0-dev.32
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/control.d.mts.map +1 -1
- package/dist/control.mjs +84 -18
- package/dist/control.mjs.map +1 -1
- package/dist/errors-ZGKTAYsm.mjs +9 -0
- package/dist/errors-ZGKTAYsm.mjs.map +1 -0
- package/dist/ir.d.mts.map +1 -1
- package/dist/ir.mjs +1 -1
- package/dist/{sql-contract-serializer-C75cfMSS.mjs → sql-contract-serializer-othnJUZ_.mjs} +7 -2
- package/dist/sql-contract-serializer-othnJUZ_.mjs.map +1 -0
- package/dist/{verify-C-G0obRm.mjs → verify-DuuJFiTm.mjs} +7 -2
- package/dist/verify-DuuJFiTm.mjs.map +1 -0
- package/dist/verify.d.mts.map +1 -1
- package/dist/verify.mjs +1 -1
- package/package.json +21 -21
- package/src/core/control-instance.ts +43 -16
- package/src/core/errors.ts +22 -0
- package/src/core/ir/sql-contract-serializer-base.ts +8 -1
- package/src/core/migrations/contract-to-schema-ir.ts +56 -8
- package/src/core/verify.ts +10 -1
- package/dist/sql-contract-serializer-C75cfMSS.mjs.map +0 -1
- package/dist/verify-C-G0obRm.mjs.map +0 -1
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { structuredError } from "@prisma-next/utils/structured-error";
|
|
2
|
+
//#region src/core/errors.ts
|
|
3
|
+
function sqlFamilyError(code, message, options) {
|
|
4
|
+
return structuredError(code, message, options);
|
|
5
|
+
}
|
|
6
|
+
//#endregion
|
|
7
|
+
export { sqlFamilyError as t };
|
|
8
|
+
|
|
9
|
+
//# sourceMappingURL=errors-ZGKTAYsm.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors-ZGKTAYsm.mjs","names":[],"sources":["../src/core/errors.ts"],"sourcesContent":["import type { StructuredError, StructuredErrorOptions } from '@prisma-next/utils/structured-error';\nimport { structuredError } from '@prisma-next/utils/structured-error';\n\ntype SqlFamilyErrorCode =\n | 'CONTRACT.ENUM_INVALID'\n | 'CONTRACT.ENUM_UNKNOWN'\n | 'CONTRACT.FOREIGN_KEY_INVALID'\n | 'CONTRACT.INFER_UNSUPPORTED'\n | 'CONTRACT.MARKER_ROW_CORRUPT'\n | 'CONTRACT.NAMESPACE_UNKNOWN'\n | 'CONTRACT.PACK_CONTRIBUTION_INVALID'\n | 'CONTRACT.TABLE_AMBIGUOUS'\n | 'CONTRACT.TYPE_UNKNOWN'\n | 'MIGRATION.MARKER_CAS_FAILURE';\n\nexport function sqlFamilyError(\n code: SqlFamilyErrorCode,\n message: string,\n options?: StructuredErrorOptions,\n): StructuredError {\n return structuredError(code, message, options);\n}\n"],"mappings":";;AAeA,SAAgB,eACd,MACA,SACA,SACiB;CACjB,OAAO,gBAAgB,MAAM,SAAS,OAAO;AAC/C"}
|
package/dist/ir.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ir.d.mts","names":[],"sources":["../src/core/ir/sql-contract-serializer-base.ts","../src/core/ir/sql-contract-serializer.ts","../src/core/ir/sql-schema-verifier-base.ts"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"ir.d.mts","names":[],"sources":["../src/core/ir/sql-contract-serializer-base.ts","../src/core/ir/sql-contract-serializer.ts","../src/core/ir/sql-schema-verifier-base.ts"],"mappings":";;;;;;KAoCY,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;;uBA0BnB,0BAA0B,kBAAkB,SAAS,wBAC9D,mBAAmB;qBAMT,yBAAyB,oBAE1C;mBANa;mBACA;cAGI,0BAAyB,oBAE1C,4BAEF,2BAA0B;EAO5B,oBAAoB,UAAU,YAAY,WAAW,gBAAgB;EAMrE,kBAAkB,UAAU,YAAY;EAIxC,6DAAmB;EAEnB,qDAAW;YAED,0BAA0B,gBAAgB,SAAS;YAOnD,kBAAkB,WAAW,SAAS,cAAc,SAAS;YAuC7D,uBACR,YAAY,SAAS,eAAe,4BACnC,SAAS,eAAe;YAoBjB,yBACR,cACA,KAAK,0BACJ,YAAY;YA+BL,wBAAwB,OAAO,sBAAsB;YAkBrD,wBAAwB,UAAU,SAAS,cAAc;;;;;;;;;;;YAczD,0BACR,SAAS,SAAS,eAAe,SAAS,6BACzC,eAAe,eAAe;UAazB;YAQE,oBAAoB,iBAAiB;UAOvC;;;;;;;;;;;;;;;;;;cChPG,8BAA8B,0BAA0B,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;uBCSxD,sBAAsB,WAAW,oBAC1C,eAAe,WAAW;EAErC,aAAa,SAAS,oBAAoB,WAAW,WAAW;;;;;;;qBAa7C,sBACjB,SAAS,oBAAoB,WAAW,oBAC9B;;;;;;qBAOO,uBACjB,SAAS,oBAAoB,WAAW,oBAC9B"}
|
package/dist/ir.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as SqlContractSerializerBase, t as SqlContractSerializer } from "./sql-contract-serializer-
|
|
1
|
+
import { n as SqlContractSerializerBase, t as SqlContractSerializer } from "./sql-contract-serializer-othnJUZ_.mjs";
|
|
2
2
|
//#region src/core/ir/sql-schema-verifier-base.ts
|
|
3
3
|
/**
|
|
4
4
|
* SQL family `SchemaVerifier` abstract base. Centralises the SQL-shared
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { t as sqlFamilyError } from "./errors-ZGKTAYsm.mjs";
|
|
1
2
|
import { blindCast } from "@prisma-next/utils/casts";
|
|
2
3
|
import { hydrateNamespaceEntities } from "@prisma-next/framework-components/ir";
|
|
3
4
|
import { sqlContractCanonicalizationHooks } from "@prisma-next/sql-contract/canonicalization-hooks";
|
|
@@ -78,7 +79,11 @@ var SqlContractSerializerBase = class {
|
|
|
78
79
|
hydrateSqlNamespaceMap(namespaces) {
|
|
79
80
|
return Object.fromEntries(Object.entries(namespaces).map(([nsId, namespaceEntryRaw]) => {
|
|
80
81
|
const namespaceHydrated = this.hydrateSqlNamespaceEntry(nsId, namespaceEntryRaw);
|
|
81
|
-
if (!isMaterializedSqlNamespace(namespaceHydrated)) throw
|
|
82
|
+
if (!isMaterializedSqlNamespace(namespaceHydrated)) throw sqlFamilyError("CONTRACT.PACK_CONTRIBUTION_INVALID", `Target serializer bug: hydrateSqlNamespaceEntry for namespace "${nsId}" returned a non-NamespaceBase value. Override hydrateSqlNamespaceEntry to produce a target namespace concretion.`, {
|
|
83
|
+
why: "The target contract serializer hydrated a namespace into a value that is not a materialized namespace concretion.",
|
|
84
|
+
fix: "Fix the target package: its hydrateSqlNamespaceEntry override must return a target namespace concretion.",
|
|
85
|
+
meta: { namespaceId: nsId }
|
|
86
|
+
});
|
|
82
87
|
return [nsId, namespaceHydrated];
|
|
83
88
|
}));
|
|
84
89
|
}
|
|
@@ -165,4 +170,4 @@ var SqlContractSerializer = class extends SqlContractSerializerBase {
|
|
|
165
170
|
//#endregion
|
|
166
171
|
export { SqlContractSerializerBase as n, SqlContractSerializer as t };
|
|
167
172
|
|
|
168
|
-
//# sourceMappingURL=sql-contract-serializer-
|
|
173
|
+
//# sourceMappingURL=sql-contract-serializer-othnJUZ_.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sql-contract-serializer-othnJUZ_.mjs","names":["isPlainRecord"],"sources":["../src/core/ir/sql-contract-serializer-base.ts","../src/core/ir/sql-contract-serializer.ts"],"sourcesContent":["import { ContractValidationError } from '@prisma-next/contract/contract-validation-error';\nimport { isPlainRecord } from '@prisma-next/contract/is-plain-record';\nimport type { Contract } from '@prisma-next/contract/types';\nimport type { ContractSerializer } from '@prisma-next/framework-components/control';\nimport {\n type AnyEntityKindDescriptor,\n hydrateNamespaceEntities,\n type Namespace,\n} from '@prisma-next/framework-components/ir';\nimport { sqlContractCanonicalizationHooks } from '@prisma-next/sql-contract/canonicalization-hooks';\nimport { composeSqlEntityKinds } from '@prisma-next/sql-contract/entity-kinds';\nimport {\n isMaterializedSqlNamespace,\n type SqlNamespaceInput,\n SqlStorage,\n type SqlStorageInput,\n type SqlStorageTypeEntry,\n} from '@prisma-next/sql-contract/types';\nimport {\n createSqlContractSchema,\n validateSqlContractFully,\n} from '@prisma-next/sql-contract/validators';\nimport { blindCast } from '@prisma-next/utils/casts';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport type { JsonObject, JsonValue } from '@prisma-next/utils/json';\nimport { type Type, type } from 'arktype';\nimport { sqlFamilyError } from '../errors';\n\nconst NamespaceRawSchema = type({\n id: 'string',\n 'kind?': 'string',\n entries: type({\n '+': 'ignore',\n }),\n});\n\nexport type SqlEntityHydrationFactory = (entry: unknown) => unknown;\n\n/**\n * SQL family `ContractSerializer` abstract base. Carries the SQL-shared\n * deserialization pipeline:\n *\n * 1. `parseSqlContractStructure` validates the on-disk JSON envelope\n * against the SQL contract arktype schema (`validateSqlContractFully`)\n * and returns the validated flat-data shape.\n * 2. `hydrateSqlStorage` walks the validated `storage` subtree and\n * constructs the family-shared SQL Contract IR class hierarchy\n * (`SqlStorage` -> `StorageTable` -> `StorageColumn` / `PrimaryKey`\n * / …). The rest of the contract envelope is JSON-clean primitive\n * data and passes through unchanged.\n * 3. `constructTargetContract` is the target-specific extension hook;\n * defaults to identity. Targets that need to attach target-only\n * fields (e.g. target-specific derived storage fields) override it.\n *\n * Default `serializeContract` is identity over the contract — concrete\n * SQL targets ship JSON-clean class instances, so the contract value\n * can be stringified directly. The non-enumerable family-level `kind`\n * discriminator on `SqlNode` instances stays out of the persisted\n * envelope automatically. Targets that need to canonicalize on the way\n * out (key ordering, dropping computed-only fields) override\n * `serializeContract` directly.\n */\nexport abstract class SqlContractSerializerBase<TContract extends Contract<SqlStorage>>\n implements ContractSerializer<TContract>\n{\n private readonly contractSchema: Type<unknown> | undefined;\n private readonly entryKinds: ReadonlyMap<string, AnyEntityKindDescriptor>;\n\n constructor(\n protected readonly entityHydrationRegistry: ReadonlyMap<\n string,\n SqlEntityHydrationFactory\n > = new Map(),\n packEntityKinds: readonly AnyEntityKindDescriptor[] = [],\n ) {\n this.entryKinds = composeSqlEntityKinds(packEntityKinds);\n this.contractSchema =\n packEntityKinds.length > 0 ? createSqlContractSchema(this.entryKinds) : undefined;\n }\n\n deserializeContract<T extends TContract = TContract>(json: unknown): T {\n const validated = this.parseSqlContractStructure(json);\n const hydrated = this.hydrateSqlStorage(validated);\n return this.constructTargetContract(hydrated) as T;\n }\n\n serializeContract(contract: TContract): JsonObject {\n return contract as unknown as JsonObject;\n }\n\n shouldPreserveEmpty = sqlContractCanonicalizationHooks.shouldPreserveEmpty;\n\n sortStorage = sqlContractCanonicalizationHooks.sortStorage;\n\n protected parseSqlContractStructure(json: unknown): Contract<SqlStorage> {\n return validateSqlContractFully<Contract<SqlStorage>>(\n json,\n this.contractSchema !== undefined ? { contractSchema: this.contractSchema } : undefined,\n );\n }\n\n protected hydrateSqlStorage(validated: Contract<SqlStorage>): Contract<SqlStorage> {\n const types = validated.storage.types;\n const hydratedTypes =\n types !== undefined\n ? Object.fromEntries(\n Object.entries(types).map(([name, entry]) => [\n name,\n this.hydrateStorageTypeEntry(entry),\n ]),\n )\n : undefined;\n\n const rawNamespaces = validated.storage.namespaces;\n if (rawNamespaces === undefined) {\n throw new ContractValidationError(\n 'Contract storage.namespaces is required after structural validation',\n 'structural',\n );\n }\n const hydratedNamespaces = this.hydrateSqlNamespaceMap(\n blindCast<\n Readonly<Record<string, Record<string, unknown>>>,\n 'parseSqlContractStructure validated raw JSON; namespace entries are plain objects, not SqlNamespace instances.'\n >(rawNamespaces),\n );\n\n return {\n ...validated,\n storage: new SqlStorage({\n storageHash: validated.storage.storageHash,\n ...ifDefined('types', hydratedTypes),\n namespaces: blindCast<\n SqlStorageInput['namespaces'],\n 'hydrateSqlNamespaceMap builds each namespace through the target serializer override, so every value is a SqlNamespace; the framework return type only promises the base Namespace.'\n >(hydratedNamespaces),\n }),\n };\n }\n\n protected hydrateSqlNamespaceMap(\n namespaces: Readonly<Record<string, Record<string, unknown>>>,\n ): Readonly<Record<string, Namespace>> {\n return Object.fromEntries(\n Object.entries(namespaces).map(([nsId, namespaceEntryRaw]) => {\n const namespaceHydrated = this.hydrateSqlNamespaceEntry(nsId, namespaceEntryRaw);\n if (!isMaterializedSqlNamespace(namespaceHydrated)) {\n throw sqlFamilyError(\n 'CONTRACT.PACK_CONTRIBUTION_INVALID',\n `Target serializer bug: hydrateSqlNamespaceEntry for namespace \"${nsId}\" returned a non-NamespaceBase value. Override hydrateSqlNamespaceEntry to produce a target namespace concretion.`,\n {\n why: 'The target contract serializer hydrated a namespace into a value that is not a materialized namespace concretion.',\n fix: 'Fix the target package: its hydrateSqlNamespaceEntry override must return a target namespace concretion.',\n meta: { namespaceId: nsId },\n },\n );\n }\n return [nsId, namespaceHydrated];\n }),\n );\n }\n\n protected hydrateSqlNamespaceEntry(\n nsId: string,\n raw: Record<string, unknown>,\n ): Namespace | SqlNamespaceInput {\n const id = typeof raw['id'] === 'string' ? raw['id'] : nsId;\n const parsed = NamespaceRawSchema({ ...raw, id });\n if (parsed instanceof type.errors) {\n const messages = parsed.map((p: { message: string }) => p.message).join('; ');\n throw new ContractValidationError(`Namespace hydration failed: ${messages}`, 'structural');\n }\n const entriesRaw = parsed.entries;\n const rawEntriesMap = isPlainRecord(entriesRaw) ? entriesRaw : {};\n\n const entriesInput: Record<string, Readonly<Record<string, unknown>>> = {};\n for (const [key, innerMap] of Object.entries(rawEntriesMap)) {\n entriesInput[key] = isPlainRecord(innerMap) ? innerMap : Object.freeze({});\n }\n\n const entriesOutput = hydrateNamespaceEntities(entriesInput, this.entryKinds, 'fail', id);\n\n // Always ensure a 'table' key is present (may be empty).\n if (!Object.hasOwn(entriesOutput, 'table')) {\n entriesOutput['table'] = {};\n }\n\n return blindCast<\n SqlNamespaceInput,\n 'entriesOutput holds the hydrated SQL entity-kind maps (table always present); this wraps them as the SqlNamespaceInput the target createNamespace consumes.'\n >({\n id,\n entries: entriesOutput,\n });\n }\n\n protected hydrateStorageTypeEntry(entry: SqlStorageTypeEntry): SqlStorageTypeEntry {\n if (typeof entry !== 'object' || entry === null) {\n return entry;\n }\n const kind = (entry as { kind?: unknown }).kind;\n if (typeof kind !== 'string') {\n return entry;\n }\n const factory = this.entityHydrationRegistry.get(kind);\n if (factory === undefined) {\n return entry;\n }\n return blindCast<\n SqlStorageTypeEntry,\n 'entity registry factory returns SqlStorageTypeEntry for storage.types entries'\n >(factory(entry));\n }\n\n protected constructTargetContract(hydrated: Contract<SqlStorage>): TContract {\n return hydrated as TContract;\n }\n\n /**\n * Serializes a namespace's `entries` dict by walking every enumerable\n * kind — no kind is named here, mirroring the generic hydrate walk in\n * `hydrateSqlNamespaceEntry` above. `table` is the SQL family's one\n * universal base kind (every namespace carries it), so it is always\n * emitted, even when empty; every other kind — target- or\n * pack-contributed — is emitted only when it holds at least one entry.\n * A kind carried non-enumerable on `entries` is excluded here for free,\n * since `Object.entries` honors enumerability.\n */\n protected serializeNamespaceEntries(\n entries: Readonly<Record<string, Readonly<Record<string, unknown>>>>,\n ): Record<string, Record<string, JsonObject>> {\n const out: Record<string, Record<string, JsonObject>> = {\n table: this.serializeEntries(entries['table'] ?? {}),\n };\n for (const [kind, record] of Object.entries(entries)) {\n if (kind === 'table' || record == null || Object.keys(record).length === 0) {\n continue;\n }\n out[kind] = this.serializeEntries(record);\n }\n return out;\n }\n\n private serializeEntries(entries: Readonly<Record<string, unknown>>): Record<string, JsonObject> {\n const out: Record<string, JsonObject> = {};\n for (const [name, entry] of Object.entries(entries)) {\n out[name] = this.serializeJsonObject(entry);\n }\n return out;\n }\n\n protected serializeJsonObject(value: unknown): JsonObject {\n return blindCast<\n JsonObject,\n 'serializeJsonValue round-trips an IR node through JSON, yielding a JsonObject'\n >(this.serializeJsonValue(value));\n }\n\n private serializeJsonValue(value: unknown): JsonValue {\n return blindCast<JsonValue, 'JSON.parse(JSON.stringify(x)) yields a JsonValue'>(\n JSON.parse(JSON.stringify(value)),\n );\n }\n}\n","import type { Contract } from '@prisma-next/contract/types';\nimport type { SqlStorage } from '@prisma-next/sql-contract/types';\nimport { SqlContractSerializerBase } from './sql-contract-serializer-base';\n\n/**\n * Default SQL family `ContractSerializer` concretion. Inherits the\n * full SQL-shared deserialization pipeline (structural validation +\n * IR-class hydration) without pack-registered `storage.types`\n * hydration factories — targets that emit polymorphic JSON outside the\n * codec-typed envelope wire a target-specific subclass with a populated\n * registry (see Postgres). Family-level call sites instantiate this\n * default directly when no target serializer is supplied.\n *\n * Because this serializer has no target concretion, deserialization of\n * contracts that include namespace entries from JSON will throw unless\n * the caller provides pre-hydrated `NamespaceBase` instances. Production\n * paths always supply a target-specific serializer.\n */\nexport class SqlContractSerializer extends SqlContractSerializerBase<Contract<SqlStorage>> {\n constructor() {\n super(new Map());\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { t as sqlFamilyError } from "./errors-ZGKTAYsm.mjs";
|
|
1
2
|
import { type } from "arktype";
|
|
2
3
|
//#region src/core/verify.ts
|
|
3
4
|
const MetaSchema = type({ "[string]": "unknown" });
|
|
@@ -46,7 +47,11 @@ function parseContractMarkerRow(row) {
|
|
|
46
47
|
const result = ContractMarkerRowSchema(row);
|
|
47
48
|
if (result instanceof type.errors) {
|
|
48
49
|
const messages = result.map((p) => p.message).join("; ");
|
|
49
|
-
throw
|
|
50
|
+
throw sqlFamilyError("CONTRACT.MARKER_ROW_CORRUPT", `Invalid contract marker row: ${messages}`, {
|
|
51
|
+
why: "The contract marker row read from the database does not match the expected marker shape.",
|
|
52
|
+
fix: "Re-sign the database with `prisma-next db sign`, or repair the marker table.",
|
|
53
|
+
meta: { issues: messages }
|
|
54
|
+
});
|
|
50
55
|
}
|
|
51
56
|
const updatedAt = result.updated_at ? result.updated_at instanceof Date ? result.updated_at : new Date(result.updated_at) : /* @__PURE__ */ new Date();
|
|
52
57
|
return {
|
|
@@ -78,4 +83,4 @@ function collectSupportedCodecTypeIds(descriptors) {
|
|
|
78
83
|
//#endregion
|
|
79
84
|
export { parseContractMarkerRow as n, collectSupportedCodecTypeIds as t };
|
|
80
85
|
|
|
81
|
-
//# sourceMappingURL=verify-
|
|
86
|
+
//# sourceMappingURL=verify-DuuJFiTm.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify-DuuJFiTm.mjs","names":[],"sources":["../src/core/verify.ts"],"sourcesContent":["import type { ContractMarkerRecord } from '@prisma-next/contract/types';\nimport { type } from 'arktype';\nimport { sqlFamilyError } from './errors';\n\nconst MetaSchema = type({ '[string]': 'unknown' });\n\nfunction parseMeta(meta: unknown): Record<string, unknown> {\n if (meta === null || meta === undefined) {\n return {};\n }\n\n let parsed: unknown;\n if (typeof meta === 'string') {\n try {\n parsed = JSON.parse(meta);\n } catch {\n return {};\n }\n } else {\n parsed = meta;\n }\n\n const result = MetaSchema(parsed);\n if (result instanceof type.errors) {\n return {};\n }\n\n return result as Record<string, unknown>;\n}\n\n/**\n * SQLite stores `contract_json` as TEXT, so the wire shape is a JSON string;\n * Postgres uses `jsonb` and returns an already-parsed value. Normalize both\n * here so `ContractMarkerRecord.contractJson` is always the structured form.\n */\nfunction parseContractJson(value: unknown): unknown {\n if (value === null || value === undefined) return null;\n if (typeof value !== 'string') return value;\n try {\n return JSON.parse(value);\n } catch {\n return null;\n }\n}\n\n/**\n * Wire shape of a `prisma_contract.marker` row as it comes out of a SQL\n * driver. Snake-cased to match the on-disk column names. Shared by every\n * SQL target's `readMarker` so each runner doesn't redeclare it inline.\n */\nexport type ContractMarkerRow = {\n core_hash: string;\n profile_hash: string;\n contract_json: unknown | null;\n canonical_version: number | null;\n updated_at: Date | string;\n app_tag: string | null;\n meta: unknown | null;\n // SQLite stores arrays as JSON-TEXT, so this is `string` on the wire from\n // a SQLite driver and `string[]` from a Postgres driver. Targets normalize\n // before passing to `parseContractMarkerRow`.\n invariants: unknown;\n};\n\nconst ContractMarkerRowSchema = type({\n core_hash: 'string',\n profile_hash: 'string',\n 'contract_json?': 'unknown | null',\n 'canonical_version?': 'number | null',\n 'updated_at?': 'Date | string',\n 'app_tag?': 'string | null',\n 'meta?': 'unknown | null',\n invariants: type('string').array(),\n});\n\n/**\n * Parses a contract marker row from database query result.\n * This is SQL-specific parsing logic (handles SQL row structure with snake_case columns).\n */\nexport function parseContractMarkerRow(row: unknown): ContractMarkerRecord {\n const result = ContractMarkerRowSchema(row);\n if (result instanceof type.errors) {\n const messages = result.map((p: { message: string }) => p.message).join('; ');\n throw sqlFamilyError(\n 'CONTRACT.MARKER_ROW_CORRUPT',\n `Invalid contract marker row: ${messages}`,\n {\n why: 'The contract marker row read from the database does not match the expected marker shape.',\n fix: 'Re-sign the database with `prisma-next db sign`, or repair the marker table.',\n meta: { issues: messages },\n },\n );\n }\n\n const updatedAt = result.updated_at\n ? result.updated_at instanceof Date\n ? result.updated_at\n : new Date(result.updated_at)\n : new Date();\n\n return {\n storageHash: result.core_hash,\n profileHash: result.profile_hash,\n contractJson: parseContractJson(result.contract_json),\n canonicalVersion: result.canonical_version ?? null,\n updatedAt,\n appTag: result.app_tag ?? null,\n meta: parseMeta(result.meta),\n invariants: result.invariants,\n };\n}\n\n/**\n * Collects supported codec type IDs from adapter and extension manifests.\n * Returns a sorted, unique array of type IDs that are declared in the manifests.\n * This enables coverage checks by comparing contract column types against supported types.\n *\n * Note: This extracts type IDs from manifest type imports, not from runtime codec registries.\n * The manifests declare which codec types are available, but the actual type IDs\n * are defined in the codec-types TypeScript modules that are imported.\n *\n * For MVP, we return an empty array since extracting type IDs from TypeScript modules\n * would require runtime evaluation or static analysis. This can be enhanced later.\n */\nexport function collectSupportedCodecTypeIds(\n descriptors: ReadonlyArray<{ readonly id: string }>,\n): readonly string[] {\n // For MVP, return empty array\n // Future enhancement: Extract type IDs from codec-types modules via static analysis\n // or require manifests to explicitly list supported type IDs\n void descriptors;\n return [];\n}\n"],"mappings":";;;AAIA,MAAM,aAAa,KAAK,EAAE,YAAY,UAAU,CAAC;AAEjD,SAAS,UAAU,MAAwC;CACzD,IAAI,SAAS,QAAQ,SAAS,KAAA,GAC5B,OAAO,CAAC;CAGV,IAAI;CACJ,IAAI,OAAO,SAAS,UAClB,IAAI;EACF,SAAS,KAAK,MAAM,IAAI;CAC1B,QAAQ;EACN,OAAO,CAAC;CACV;MAEA,SAAS;CAGX,MAAM,SAAS,WAAW,MAAM;CAChC,IAAI,kBAAkB,KAAK,QACzB,OAAO,CAAC;CAGV,OAAO;AACT;;;;;;AAOA,SAAS,kBAAkB,OAAyB;CAClD,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO;CAClD,IAAI,OAAO,UAAU,UAAU,OAAO;CACtC,IAAI;EACF,OAAO,KAAK,MAAM,KAAK;CACzB,QAAQ;EACN,OAAO;CACT;AACF;AAqBA,MAAM,0BAA0B,KAAK;CACnC,WAAW;CACX,cAAc;CACd,kBAAkB;CAClB,sBAAsB;CACtB,eAAe;CACf,YAAY;CACZ,SAAS;CACT,YAAY,KAAK,QAAQ,CAAC,CAAC,MAAM;AACnC,CAAC;;;;;AAMD,SAAgB,uBAAuB,KAAoC;CACzE,MAAM,SAAS,wBAAwB,GAAG;CAC1C,IAAI,kBAAkB,KAAK,QAAQ;EACjC,MAAM,WAAW,OAAO,KAAK,MAA2B,EAAE,OAAO,CAAC,CAAC,KAAK,IAAI;EAC5E,MAAM,eACJ,+BACA,gCAAgC,YAChC;GACE,KAAK;GACL,KAAK;GACL,MAAM,EAAE,QAAQ,SAAS;EAC3B,CACF;CACF;CAEA,MAAM,YAAY,OAAO,aACrB,OAAO,sBAAsB,OAC3B,OAAO,aACP,IAAI,KAAK,OAAO,UAAU,oBAC5B,IAAI,KAAK;CAEb,OAAO;EACL,aAAa,OAAO;EACpB,aAAa,OAAO;EACpB,cAAc,kBAAkB,OAAO,aAAa;EACpD,kBAAkB,OAAO,qBAAqB;EAC9C;EACA,QAAQ,OAAO,WAAW;EAC1B,MAAM,UAAU,OAAO,IAAI;EAC3B,YAAY,OAAO;CACrB;AACF;;;;;;;;;;;;;AAcA,SAAgB,6BACd,aACmB;CAKnB,OAAO,CAAC;AACV"}
|
package/dist/verify.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verify.d.mts","names":[],"sources":["../src/core/verify.ts"],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"verify.d.mts","names":[],"sources":["../src/core/verify.ts"],"mappings":";;;;;;;KAkDY;EACV;EACA;EACA;EACA;EACA,YAAY;EACZ;EACA;EAIA;;;;;;iBAkBc,uBAAuB,eAAe"}
|
package/dist/verify.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as parseContractMarkerRow } from "./verify-
|
|
1
|
+
import { n as parseContractMarkerRow } from "./verify-DuuJFiTm.mjs";
|
|
2
2
|
export { parseContractMarkerRow };
|
package/package.json
CHANGED
|
@@ -1,35 +1,35 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prisma-next/family-sql",
|
|
3
|
-
"version": "0.16.0-dev.
|
|
3
|
+
"version": "0.16.0-dev.32",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"description": "SQL family descriptor for Prisma Next",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@prisma-next/contract": "0.16.0-dev.
|
|
10
|
-
"@prisma-next/emitter": "0.16.0-dev.
|
|
11
|
-
"@prisma-next/framework-components": "0.16.0-dev.
|
|
12
|
-
"@prisma-next/migration-tools": "0.16.0-dev.
|
|
13
|
-
"@prisma-next/operations": "0.16.0-dev.
|
|
14
|
-
"@prisma-next/sql-contract": "0.16.0-dev.
|
|
15
|
-
"@prisma-next/sql-contract-emitter": "0.16.0-dev.
|
|
16
|
-
"@prisma-next/sql-contract-ts": "0.16.0-dev.
|
|
17
|
-
"@prisma-next/sql-operations": "0.16.0-dev.
|
|
18
|
-
"@prisma-next/sql-relational-core": "0.16.0-dev.
|
|
19
|
-
"@prisma-next/sql-runtime": "0.16.0-dev.
|
|
20
|
-
"@prisma-next/sql-schema-ir": "0.16.0-dev.
|
|
21
|
-
"@prisma-next/utils": "0.16.0-dev.
|
|
9
|
+
"@prisma-next/contract": "0.16.0-dev.32",
|
|
10
|
+
"@prisma-next/emitter": "0.16.0-dev.32",
|
|
11
|
+
"@prisma-next/framework-components": "0.16.0-dev.32",
|
|
12
|
+
"@prisma-next/migration-tools": "0.16.0-dev.32",
|
|
13
|
+
"@prisma-next/operations": "0.16.0-dev.32",
|
|
14
|
+
"@prisma-next/sql-contract": "0.16.0-dev.32",
|
|
15
|
+
"@prisma-next/sql-contract-emitter": "0.16.0-dev.32",
|
|
16
|
+
"@prisma-next/sql-contract-ts": "0.16.0-dev.32",
|
|
17
|
+
"@prisma-next/sql-operations": "0.16.0-dev.32",
|
|
18
|
+
"@prisma-next/sql-relational-core": "0.16.0-dev.32",
|
|
19
|
+
"@prisma-next/sql-runtime": "0.16.0-dev.32",
|
|
20
|
+
"@prisma-next/sql-schema-ir": "0.16.0-dev.32",
|
|
21
|
+
"@prisma-next/utils": "0.16.0-dev.32",
|
|
22
22
|
"arktype": "^2.2.2",
|
|
23
23
|
"pluralize": "^8.0.0"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"@prisma-next/driver-postgres": "0.16.0-dev.
|
|
27
|
-
"@prisma-next/psl-parser": "0.16.0-dev.
|
|
28
|
-
"@prisma-next/psl-printer": "0.16.0-dev.
|
|
29
|
-
"@prisma-next/sql-contract-psl": "0.16.0-dev.
|
|
30
|
-
"@prisma-next/test-utils": "0.16.0-dev.
|
|
31
|
-
"@prisma-next/tsconfig": "0.16.0-dev.
|
|
32
|
-
"@prisma-next/tsdown": "0.16.0-dev.
|
|
26
|
+
"@prisma-next/driver-postgres": "0.16.0-dev.32",
|
|
27
|
+
"@prisma-next/psl-parser": "0.16.0-dev.32",
|
|
28
|
+
"@prisma-next/psl-printer": "0.16.0-dev.32",
|
|
29
|
+
"@prisma-next/sql-contract-psl": "0.16.0-dev.32",
|
|
30
|
+
"@prisma-next/test-utils": "0.16.0-dev.32",
|
|
31
|
+
"@prisma-next/tsconfig": "0.16.0-dev.32",
|
|
32
|
+
"@prisma-next/tsdown": "0.16.0-dev.32",
|
|
33
33
|
"@types/pluralize": "^0.0.33",
|
|
34
34
|
"tsdown": "0.22.8",
|
|
35
35
|
"typescript": "5.9.3",
|
|
@@ -44,6 +44,8 @@ import type {
|
|
|
44
44
|
import type { SqlSchemaIRNode, SqlTableIR } from '@prisma-next/sql-schema-ir/types';
|
|
45
45
|
import { blindCast } from '@prisma-next/utils/casts';
|
|
46
46
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
47
|
+
import { InternalError } from '@prisma-next/utils/internal-error';
|
|
48
|
+
import type { StructuredError } from '@prisma-next/utils/structured-error';
|
|
47
49
|
import type { SqlControlAdapter } from './control-adapter';
|
|
48
50
|
import type {
|
|
49
51
|
SqlControlTargetDescriptor,
|
|
@@ -54,6 +56,7 @@ import {
|
|
|
54
56
|
classifyDiffSubjectGranularity,
|
|
55
57
|
verifySqlSchemaByDiff,
|
|
56
58
|
} from './diff/schema-verify';
|
|
59
|
+
import { sqlFamilyError } from './errors';
|
|
57
60
|
import { SqlContractSerializer } from './ir/sql-contract-serializer';
|
|
58
61
|
import type { SqlSchemaDiffFn } from './migrations/schema-differ';
|
|
59
62
|
import type {
|
|
@@ -63,6 +66,18 @@ import type {
|
|
|
63
66
|
import { sqlOperationsToPreview } from './operation-preview';
|
|
64
67
|
import { collectSupportedCodecTypeIds } from './verify';
|
|
65
68
|
|
|
69
|
+
function missingDescriptorOperationError(targetId: string, operation: string): StructuredError {
|
|
70
|
+
return sqlFamilyError(
|
|
71
|
+
'CONTRACT.PACK_CONTRIBUTION_INVALID',
|
|
72
|
+
`SQL target "${targetId}" is missing the required ${operation} descriptor operation`,
|
|
73
|
+
{
|
|
74
|
+
why: `The target descriptor does not contribute the ${operation} operation the SQL family requires for this call.`,
|
|
75
|
+
fix: `Use a target package whose control descriptor implements ${operation}.`,
|
|
76
|
+
meta: { targetId, operation },
|
|
77
|
+
},
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
66
81
|
function extractCodecTypeIdsFromContract(contract: unknown): readonly string[] {
|
|
67
82
|
const typeIds = new Set<string>();
|
|
68
83
|
|
|
@@ -475,8 +490,14 @@ export function assertNoCrossSpaceFkReverseReferences(
|
|
|
475
490
|
// Check if targetSpaceId depends on ext.id (directly or transitively)
|
|
476
491
|
const targetDeps = dependsOnMap.get(targetSpaceId);
|
|
477
492
|
if (targetDeps?.has(ext.id)) {
|
|
478
|
-
throw
|
|
493
|
+
throw sqlFamilyError(
|
|
494
|
+
'CONTRACT.FOREIGN_KEY_INVALID',
|
|
479
495
|
`Cross-space FK reverse-reference detected: extension "${ext.id}" has a cross-space FK targeting space "${targetSpaceId}", but "${targetSpaceId}" depends on "${ext.id}". Cross-space FKs must follow the dependency direction (a space can only reference spaces it depends on, not spaces that depend on it).`,
|
|
496
|
+
{
|
|
497
|
+
why: 'The foreign key points against the contract-space dependency direction.',
|
|
498
|
+
fix: 'Move the foreign key to the depending space, or restructure the extension dependencies so the referencing space depends on the referenced one.',
|
|
499
|
+
meta: { extensionId: ext.id, targetSpaceId },
|
|
500
|
+
},
|
|
480
501
|
);
|
|
481
502
|
}
|
|
482
503
|
}
|
|
@@ -490,7 +511,7 @@ export function createSqlFamilyInstance<TTargetId extends string>(
|
|
|
490
511
|
stack: ControlStack<'sql', TTargetId>,
|
|
491
512
|
): SqlFamilyInstance {
|
|
492
513
|
if (!stack.adapter) {
|
|
493
|
-
throw new
|
|
514
|
+
throw new InternalError('SQL family requires an adapter descriptor in ControlStack');
|
|
494
515
|
}
|
|
495
516
|
|
|
496
517
|
const target = stack.target as unknown as TargetDescriptor<'sql', TTargetId> &
|
|
@@ -758,14 +779,10 @@ export function createSqlFamilyInstance<TTargetId extends string>(
|
|
|
758
779
|
}): VerifyDatabaseSchemaResult {
|
|
759
780
|
const contract = deserializeWithTargetSerializer(options.contract) as Contract<SqlStorage>;
|
|
760
781
|
if (!diffSchema) {
|
|
761
|
-
throw
|
|
762
|
-
`SQL target "${target.targetId}" is missing the required diffSchema descriptor operation`,
|
|
763
|
-
);
|
|
782
|
+
throw missingDescriptorOperationError(target.targetId, 'diffSchema');
|
|
764
783
|
}
|
|
765
784
|
if (!targetGranularityOf) {
|
|
766
|
-
throw
|
|
767
|
-
`SQL target "${target.targetId}" is missing the required classifySubjectGranularity descriptor operation`,
|
|
768
|
-
);
|
|
785
|
+
throw missingDescriptorOperationError(target.targetId, 'classifySubjectGranularity');
|
|
769
786
|
}
|
|
770
787
|
// THE VERDICT: the target's full-tree node diff, graded by the
|
|
771
788
|
// family's post-diff filters (strict gating + control-policy
|
|
@@ -792,9 +809,7 @@ export function createSqlFamilyInstance<TTargetId extends string>(
|
|
|
792
809
|
*/
|
|
793
810
|
classifySubjectGranularity(issue: SchemaDiffIssue): DiffSubjectGranularity | undefined {
|
|
794
811
|
if (!targetGranularityOf) {
|
|
795
|
-
throw
|
|
796
|
-
`SQL target "${target.targetId}" is missing the required classifySubjectGranularity descriptor operation`,
|
|
797
|
-
);
|
|
812
|
+
throw missingDescriptorOperationError(target.targetId, 'classifySubjectGranularity');
|
|
798
813
|
}
|
|
799
814
|
return classifyDiffSubjectGranularity(issue, targetGranularityOf);
|
|
800
815
|
},
|
|
@@ -807,9 +822,7 @@ export function createSqlFamilyInstance<TTargetId extends string>(
|
|
|
807
822
|
*/
|
|
808
823
|
classifyEntityKind(issue: SchemaDiffIssue): string | undefined {
|
|
809
824
|
if (!targetEntityKindOf) {
|
|
810
|
-
throw
|
|
811
|
-
`SQL target "${target.targetId}" is missing the required classifyEntityKind descriptor operation`,
|
|
812
|
-
);
|
|
825
|
+
throw missingDescriptorOperationError(target.targetId, 'classifyEntityKind');
|
|
813
826
|
}
|
|
814
827
|
return classifyDiffEntityKind(issue, targetEntityKindOf);
|
|
815
828
|
},
|
|
@@ -872,7 +885,15 @@ export function createSqlFamilyInstance<TTargetId extends string>(
|
|
|
872
885
|
},
|
|
873
886
|
);
|
|
874
887
|
if (!updated) {
|
|
875
|
-
throw
|
|
888
|
+
throw sqlFamilyError(
|
|
889
|
+
'MIGRATION.MARKER_CAS_FAILURE',
|
|
890
|
+
'CAS conflict: marker was modified by another process during sign',
|
|
891
|
+
{
|
|
892
|
+
why: 'Another process updated the contract marker between the read and the compare-and-swap write.',
|
|
893
|
+
fix: 'Re-run the sign command; if it keeps failing, make sure only one migration process runs at a time.',
|
|
894
|
+
meta: { space: APP_SPACE_ID },
|
|
895
|
+
},
|
|
896
|
+
);
|
|
876
897
|
}
|
|
877
898
|
markerUpdated = true;
|
|
878
899
|
}
|
|
@@ -983,8 +1004,14 @@ export function createSqlFamilyInstance<TTargetId extends string>(
|
|
|
983
1004
|
|
|
984
1005
|
inferPslContract(schemaIR: SqlSchemaIRNode): PslDocumentAst {
|
|
985
1006
|
if (!targetInferPslContract) {
|
|
986
|
-
throw
|
|
1007
|
+
throw sqlFamilyError(
|
|
1008
|
+
'CONTRACT.INFER_UNSUPPORTED',
|
|
987
1009
|
`Target "${target.targetId}" does not support contract infer (no inferPslContract on its descriptor).`,
|
|
1010
|
+
{
|
|
1011
|
+
why: 'The target descriptor does not provide the inferPslContract hook, so a PSL contract cannot be inferred from the database schema.',
|
|
1012
|
+
fix: 'Use a target package that supports contract infer, or author the contract instead of inferring it.',
|
|
1013
|
+
meta: { targetId: target.targetId },
|
|
1014
|
+
},
|
|
988
1015
|
);
|
|
989
1016
|
}
|
|
990
1017
|
return targetInferPslContract(schemaIR, describedContracts);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { StructuredError, StructuredErrorOptions } from '@prisma-next/utils/structured-error';
|
|
2
|
+
import { structuredError } from '@prisma-next/utils/structured-error';
|
|
3
|
+
|
|
4
|
+
type SqlFamilyErrorCode =
|
|
5
|
+
| 'CONTRACT.ENUM_INVALID'
|
|
6
|
+
| 'CONTRACT.ENUM_UNKNOWN'
|
|
7
|
+
| 'CONTRACT.FOREIGN_KEY_INVALID'
|
|
8
|
+
| 'CONTRACT.INFER_UNSUPPORTED'
|
|
9
|
+
| 'CONTRACT.MARKER_ROW_CORRUPT'
|
|
10
|
+
| 'CONTRACT.NAMESPACE_UNKNOWN'
|
|
11
|
+
| 'CONTRACT.PACK_CONTRIBUTION_INVALID'
|
|
12
|
+
| 'CONTRACT.TABLE_AMBIGUOUS'
|
|
13
|
+
| 'CONTRACT.TYPE_UNKNOWN'
|
|
14
|
+
| 'MIGRATION.MARKER_CAS_FAILURE';
|
|
15
|
+
|
|
16
|
+
export function sqlFamilyError(
|
|
17
|
+
code: SqlFamilyErrorCode,
|
|
18
|
+
message: string,
|
|
19
|
+
options?: StructuredErrorOptions,
|
|
20
|
+
): StructuredError {
|
|
21
|
+
return structuredError(code, message, options);
|
|
22
|
+
}
|
|
@@ -24,6 +24,7 @@ import { blindCast } from '@prisma-next/utils/casts';
|
|
|
24
24
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
25
25
|
import type { JsonObject, JsonValue } from '@prisma-next/utils/json';
|
|
26
26
|
import { type Type, type } from 'arktype';
|
|
27
|
+
import { sqlFamilyError } from '../errors';
|
|
27
28
|
|
|
28
29
|
const NamespaceRawSchema = type({
|
|
29
30
|
id: 'string',
|
|
@@ -144,8 +145,14 @@ export abstract class SqlContractSerializerBase<TContract extends Contract<SqlSt
|
|
|
144
145
|
Object.entries(namespaces).map(([nsId, namespaceEntryRaw]) => {
|
|
145
146
|
const namespaceHydrated = this.hydrateSqlNamespaceEntry(nsId, namespaceEntryRaw);
|
|
146
147
|
if (!isMaterializedSqlNamespace(namespaceHydrated)) {
|
|
147
|
-
throw
|
|
148
|
+
throw sqlFamilyError(
|
|
149
|
+
'CONTRACT.PACK_CONTRIBUTION_INVALID',
|
|
148
150
|
`Target serializer bug: hydrateSqlNamespaceEntry for namespace "${nsId}" returned a non-NamespaceBase value. Override hydrateSqlNamespaceEntry to produce a target namespace concretion.`,
|
|
151
|
+
{
|
|
152
|
+
why: 'The target contract serializer hydrated a namespace into a value that is not a materialized namespace concretion.',
|
|
153
|
+
fix: 'Fix the target package: its hydrateSqlNamespaceEntry override must return a target namespace concretion.',
|
|
154
|
+
meta: { namespaceId: nsId },
|
|
155
|
+
},
|
|
149
156
|
);
|
|
150
157
|
}
|
|
151
158
|
return [nsId, namespaceHydrated];
|
|
@@ -28,6 +28,8 @@ import {
|
|
|
28
28
|
} from '@prisma-next/sql-schema-ir/types';
|
|
29
29
|
import { blindCast } from '@prisma-next/utils/casts';
|
|
30
30
|
import { ifDefined } from '@prisma-next/utils/defined';
|
|
31
|
+
import { InternalError } from '@prisma-next/utils/internal-error';
|
|
32
|
+
import { sqlFamilyError } from '../errors';
|
|
31
33
|
|
|
32
34
|
/**
|
|
33
35
|
* Target-specific callback that expands a column's base `nativeType` and optional
|
|
@@ -187,8 +189,14 @@ function resolveColumnTypeMetadata(
|
|
|
187
189
|
}
|
|
188
190
|
const referenced = storageTypes[column.typeRef];
|
|
189
191
|
if (!referenced) {
|
|
190
|
-
throw
|
|
192
|
+
throw sqlFamilyError(
|
|
193
|
+
'CONTRACT.TYPE_UNKNOWN',
|
|
191
194
|
`Column references storage type "${column.typeRef}" but it is not defined in storage.types.`,
|
|
195
|
+
{
|
|
196
|
+
why: 'The column typeRef does not resolve to any entry in the contract storage.types map.',
|
|
197
|
+
fix: 'Regenerate the contract from its authoring source; do not hand-edit contract JSON.',
|
|
198
|
+
meta: { typeRef: column.typeRef },
|
|
199
|
+
},
|
|
192
200
|
);
|
|
193
201
|
}
|
|
194
202
|
if (isStorageTypeInstance(referenced)) {
|
|
@@ -198,7 +206,7 @@ function resolveColumnTypeMetadata(
|
|
|
198
206
|
typeParams: referenced.typeParams,
|
|
199
207
|
};
|
|
200
208
|
}
|
|
201
|
-
throw new
|
|
209
|
+
throw new InternalError(
|
|
202
210
|
`Storage type "${column.typeRef}" has an unknown polymorphic kind; expected a codec-typed StorageTypeInstance.`,
|
|
203
211
|
);
|
|
204
212
|
}
|
|
@@ -224,14 +232,26 @@ export function resolveValueSetValues(
|
|
|
224
232
|
): readonly string[] {
|
|
225
233
|
const ns = storage.namespaces[ref.namespaceId];
|
|
226
234
|
if (!ns) {
|
|
227
|
-
throw
|
|
235
|
+
throw sqlFamilyError(
|
|
236
|
+
'CONTRACT.NAMESPACE_UNKNOWN',
|
|
228
237
|
`resolveValueSetValues: namespace "${ref.namespaceId}" not found in storage (${contextLabel})`,
|
|
238
|
+
{
|
|
239
|
+
why: 'A value-set reference names a namespace the contract storage does not declare.',
|
|
240
|
+
fix: 'Regenerate the contract from its authoring source; do not hand-edit contract JSON.',
|
|
241
|
+
meta: { namespaceId: ref.namespaceId, context: contextLabel },
|
|
242
|
+
},
|
|
229
243
|
);
|
|
230
244
|
}
|
|
231
245
|
const valueSet = ns.entries.valueSet?.[ref.entityName];
|
|
232
246
|
if (!valueSet) {
|
|
233
|
-
throw
|
|
247
|
+
throw sqlFamilyError(
|
|
248
|
+
'CONTRACT.ENUM_UNKNOWN',
|
|
234
249
|
`resolveValueSetValues: value-set "${ref.entityName}" not found in namespace "${ref.namespaceId}" (${contextLabel})`,
|
|
250
|
+
{
|
|
251
|
+
why: 'A value-set reference names an entry its namespace does not declare.',
|
|
252
|
+
fix: 'Regenerate the contract from its authoring source; do not hand-edit contract JSON.',
|
|
253
|
+
meta: { valueSet: ref.entityName, namespaceId: ref.namespaceId, context: contextLabel },
|
|
254
|
+
},
|
|
235
255
|
);
|
|
236
256
|
}
|
|
237
257
|
// Only TEXT enums ship a CHECK-constraint round-trip in this slice. A
|
|
@@ -239,8 +259,14 @@ export function resolveValueSetValues(
|
|
|
239
259
|
// is future work; fail loudly rather than emit a wrong numeric-as-text check.
|
|
240
260
|
const values = valueSet.values;
|
|
241
261
|
if (!allStrings(values)) {
|
|
242
|
-
throw
|
|
262
|
+
throw sqlFamilyError(
|
|
263
|
+
'CONTRACT.ENUM_INVALID',
|
|
243
264
|
`resolveValueSetValues: value-set "${ref.entityName}" in namespace "${ref.namespaceId}" has a non-string value; numeric-enum CHECK constraints are not yet supported (${contextLabel})`,
|
|
265
|
+
{
|
|
266
|
+
why: 'Only string value-sets can be rendered as CHECK constraints; this value-set carries a non-string value.',
|
|
267
|
+
fix: 'Use string enum values, or drop the CHECK-constrained enum until numeric enums are supported.',
|
|
268
|
+
meta: { valueSet: ref.entityName, namespaceId: ref.namespaceId, context: contextLabel },
|
|
269
|
+
},
|
|
244
270
|
);
|
|
245
271
|
}
|
|
246
272
|
return values;
|
|
@@ -524,7 +550,15 @@ export function contractNamespaceToSchemaIR(
|
|
|
524
550
|
options: ContractToSchemaIROptions,
|
|
525
551
|
): SqlSchemaIR {
|
|
526
552
|
if (options.annotationNamespace.length === 0) {
|
|
527
|
-
throw
|
|
553
|
+
throw sqlFamilyError(
|
|
554
|
+
'CONTRACT.PACK_CONTRIBUTION_INVALID',
|
|
555
|
+
'annotationNamespace must be a non-empty string',
|
|
556
|
+
{
|
|
557
|
+
why: 'The calling target pack passed an empty annotationNamespace to the contract-to-schema-IR projection.',
|
|
558
|
+
fix: 'Fix the target pack to pass its non-empty annotation namespace (e.g. "pg").',
|
|
559
|
+
meta: { option: 'annotationNamespace' },
|
|
560
|
+
},
|
|
561
|
+
);
|
|
528
562
|
}
|
|
529
563
|
const namespace = storage.namespaces[namespaceId];
|
|
530
564
|
if (!namespace) {
|
|
@@ -552,7 +586,15 @@ export function contractToSchemaIR(
|
|
|
552
586
|
options: ContractToSchemaIROptions,
|
|
553
587
|
): SqlSchemaIR {
|
|
554
588
|
if (options.annotationNamespace.length === 0) {
|
|
555
|
-
throw
|
|
589
|
+
throw sqlFamilyError(
|
|
590
|
+
'CONTRACT.PACK_CONTRIBUTION_INVALID',
|
|
591
|
+
'annotationNamespace must be a non-empty string',
|
|
592
|
+
{
|
|
593
|
+
why: 'The calling target pack passed an empty annotationNamespace to the contract-to-schema-IR projection.',
|
|
594
|
+
fix: 'Fix the target pack to pass its non-empty annotation namespace (e.g. "pg").',
|
|
595
|
+
meta: { option: 'annotationNamespace' },
|
|
596
|
+
},
|
|
597
|
+
);
|
|
556
598
|
}
|
|
557
599
|
|
|
558
600
|
if (!contract) {
|
|
@@ -567,8 +609,14 @@ export function contractToSchemaIR(
|
|
|
567
609
|
StorageTable.assert(tableDefRaw, `namespaces.${ns.id}.entries.table.${tableName}`);
|
|
568
610
|
const tableDef = tableDefRaw;
|
|
569
611
|
if (tables[tableName] !== undefined) {
|
|
570
|
-
throw
|
|
612
|
+
throw sqlFamilyError(
|
|
613
|
+
'CONTRACT.TABLE_AMBIGUOUS',
|
|
571
614
|
`contractToSchemaIR: duplicate SQL table name "${tableName}" across namespaces (ambiguous for flat SqlSchemaIR.tables).`,
|
|
615
|
+
{
|
|
616
|
+
why: 'Two namespaces declare a table with the same name, which is ambiguous for the flat schema-IR table map.',
|
|
617
|
+
fix: 'Rename one of the tables so every table name is unique across namespaces.',
|
|
618
|
+
meta: { table: tableName },
|
|
619
|
+
},
|
|
572
620
|
);
|
|
573
621
|
}
|
|
574
622
|
tables[tableName] = convertTable(
|
package/src/core/verify.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ContractMarkerRecord } from '@prisma-next/contract/types';
|
|
2
2
|
import { type } from 'arktype';
|
|
3
|
+
import { sqlFamilyError } from './errors';
|
|
3
4
|
|
|
4
5
|
const MetaSchema = type({ '[string]': 'unknown' });
|
|
5
6
|
|
|
@@ -80,7 +81,15 @@ export function parseContractMarkerRow(row: unknown): ContractMarkerRecord {
|
|
|
80
81
|
const result = ContractMarkerRowSchema(row);
|
|
81
82
|
if (result instanceof type.errors) {
|
|
82
83
|
const messages = result.map((p: { message: string }) => p.message).join('; ');
|
|
83
|
-
throw
|
|
84
|
+
throw sqlFamilyError(
|
|
85
|
+
'CONTRACT.MARKER_ROW_CORRUPT',
|
|
86
|
+
`Invalid contract marker row: ${messages}`,
|
|
87
|
+
{
|
|
88
|
+
why: 'The contract marker row read from the database does not match the expected marker shape.',
|
|
89
|
+
fix: 'Re-sign the database with `prisma-next db sign`, or repair the marker table.',
|
|
90
|
+
meta: { issues: messages },
|
|
91
|
+
},
|
|
92
|
+
);
|
|
84
93
|
}
|
|
85
94
|
|
|
86
95
|
const updatedAt = result.updated_at
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sql-contract-serializer-C75cfMSS.mjs","names":["isPlainRecord"],"sources":["../src/core/ir/sql-contract-serializer-base.ts","../src/core/ir/sql-contract-serializer.ts"],"sourcesContent":["import { ContractValidationError } from '@prisma-next/contract/contract-validation-error';\nimport { isPlainRecord } from '@prisma-next/contract/is-plain-record';\nimport type { Contract } from '@prisma-next/contract/types';\nimport type { ContractSerializer } from '@prisma-next/framework-components/control';\nimport {\n type AnyEntityKindDescriptor,\n hydrateNamespaceEntities,\n type Namespace,\n} from '@prisma-next/framework-components/ir';\nimport { sqlContractCanonicalizationHooks } from '@prisma-next/sql-contract/canonicalization-hooks';\nimport { composeSqlEntityKinds } from '@prisma-next/sql-contract/entity-kinds';\nimport {\n isMaterializedSqlNamespace,\n type SqlNamespaceInput,\n SqlStorage,\n type SqlStorageInput,\n type SqlStorageTypeEntry,\n} from '@prisma-next/sql-contract/types';\nimport {\n createSqlContractSchema,\n validateSqlContractFully,\n} from '@prisma-next/sql-contract/validators';\nimport { blindCast } from '@prisma-next/utils/casts';\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport type { JsonObject, JsonValue } from '@prisma-next/utils/json';\nimport { type Type, type } from 'arktype';\n\nconst NamespaceRawSchema = type({\n id: 'string',\n 'kind?': 'string',\n entries: type({\n '+': 'ignore',\n }),\n});\n\nexport type SqlEntityHydrationFactory = (entry: unknown) => unknown;\n\n/**\n * SQL family `ContractSerializer` abstract base. Carries the SQL-shared\n * deserialization pipeline:\n *\n * 1. `parseSqlContractStructure` validates the on-disk JSON envelope\n * against the SQL contract arktype schema (`validateSqlContractFully`)\n * and returns the validated flat-data shape.\n * 2. `hydrateSqlStorage` walks the validated `storage` subtree and\n * constructs the family-shared SQL Contract IR class hierarchy\n * (`SqlStorage` -> `StorageTable` -> `StorageColumn` / `PrimaryKey`\n * / …). The rest of the contract envelope is JSON-clean primitive\n * data and passes through unchanged.\n * 3. `constructTargetContract` is the target-specific extension hook;\n * defaults to identity. Targets that need to attach target-only\n * fields (e.g. target-specific derived storage fields) override it.\n *\n * Default `serializeContract` is identity over the contract — concrete\n * SQL targets ship JSON-clean class instances, so the contract value\n * can be stringified directly. The non-enumerable family-level `kind`\n * discriminator on `SqlNode` instances stays out of the persisted\n * envelope automatically. Targets that need to canonicalize on the way\n * out (key ordering, dropping computed-only fields) override\n * `serializeContract` directly.\n */\nexport abstract class SqlContractSerializerBase<TContract extends Contract<SqlStorage>>\n implements ContractSerializer<TContract>\n{\n private readonly contractSchema: Type<unknown> | undefined;\n private readonly entryKinds: ReadonlyMap<string, AnyEntityKindDescriptor>;\n\n constructor(\n protected readonly entityHydrationRegistry: ReadonlyMap<\n string,\n SqlEntityHydrationFactory\n > = new Map(),\n packEntityKinds: readonly AnyEntityKindDescriptor[] = [],\n ) {\n this.entryKinds = composeSqlEntityKinds(packEntityKinds);\n this.contractSchema =\n packEntityKinds.length > 0 ? createSqlContractSchema(this.entryKinds) : undefined;\n }\n\n deserializeContract<T extends TContract = TContract>(json: unknown): T {\n const validated = this.parseSqlContractStructure(json);\n const hydrated = this.hydrateSqlStorage(validated);\n return this.constructTargetContract(hydrated) as T;\n }\n\n serializeContract(contract: TContract): JsonObject {\n return contract as unknown as JsonObject;\n }\n\n shouldPreserveEmpty = sqlContractCanonicalizationHooks.shouldPreserveEmpty;\n\n sortStorage = sqlContractCanonicalizationHooks.sortStorage;\n\n protected parseSqlContractStructure(json: unknown): Contract<SqlStorage> {\n return validateSqlContractFully<Contract<SqlStorage>>(\n json,\n this.contractSchema !== undefined ? { contractSchema: this.contractSchema } : undefined,\n );\n }\n\n protected hydrateSqlStorage(validated: Contract<SqlStorage>): Contract<SqlStorage> {\n const types = validated.storage.types;\n const hydratedTypes =\n types !== undefined\n ? Object.fromEntries(\n Object.entries(types).map(([name, entry]) => [\n name,\n this.hydrateStorageTypeEntry(entry),\n ]),\n )\n : undefined;\n\n const rawNamespaces = validated.storage.namespaces;\n if (rawNamespaces === undefined) {\n throw new ContractValidationError(\n 'Contract storage.namespaces is required after structural validation',\n 'structural',\n );\n }\n const hydratedNamespaces = this.hydrateSqlNamespaceMap(\n blindCast<\n Readonly<Record<string, Record<string, unknown>>>,\n 'parseSqlContractStructure validated raw JSON; namespace entries are plain objects, not SqlNamespace instances.'\n >(rawNamespaces),\n );\n\n return {\n ...validated,\n storage: new SqlStorage({\n storageHash: validated.storage.storageHash,\n ...ifDefined('types', hydratedTypes),\n namespaces: blindCast<\n SqlStorageInput['namespaces'],\n 'hydrateSqlNamespaceMap builds each namespace through the target serializer override, so every value is a SqlNamespace; the framework return type only promises the base Namespace.'\n >(hydratedNamespaces),\n }),\n };\n }\n\n protected hydrateSqlNamespaceMap(\n namespaces: Readonly<Record<string, Record<string, unknown>>>,\n ): Readonly<Record<string, Namespace>> {\n return Object.fromEntries(\n Object.entries(namespaces).map(([nsId, namespaceEntryRaw]) => {\n const namespaceHydrated = this.hydrateSqlNamespaceEntry(nsId, namespaceEntryRaw);\n if (!isMaterializedSqlNamespace(namespaceHydrated)) {\n throw new Error(\n `Target serializer bug: hydrateSqlNamespaceEntry for namespace \"${nsId}\" returned a non-NamespaceBase value. Override hydrateSqlNamespaceEntry to produce a target namespace concretion.`,\n );\n }\n return [nsId, namespaceHydrated];\n }),\n );\n }\n\n protected hydrateSqlNamespaceEntry(\n nsId: string,\n raw: Record<string, unknown>,\n ): Namespace | SqlNamespaceInput {\n const id = typeof raw['id'] === 'string' ? raw['id'] : nsId;\n const parsed = NamespaceRawSchema({ ...raw, id });\n if (parsed instanceof type.errors) {\n const messages = parsed.map((p: { message: string }) => p.message).join('; ');\n throw new ContractValidationError(`Namespace hydration failed: ${messages}`, 'structural');\n }\n const entriesRaw = parsed.entries;\n const rawEntriesMap = isPlainRecord(entriesRaw) ? entriesRaw : {};\n\n const entriesInput: Record<string, Readonly<Record<string, unknown>>> = {};\n for (const [key, innerMap] of Object.entries(rawEntriesMap)) {\n entriesInput[key] = isPlainRecord(innerMap) ? innerMap : Object.freeze({});\n }\n\n const entriesOutput = hydrateNamespaceEntities(entriesInput, this.entryKinds, 'fail', id);\n\n // Always ensure a 'table' key is present (may be empty).\n if (!Object.hasOwn(entriesOutput, 'table')) {\n entriesOutput['table'] = {};\n }\n\n return blindCast<\n SqlNamespaceInput,\n 'entriesOutput holds the hydrated SQL entity-kind maps (table always present); this wraps them as the SqlNamespaceInput the target createNamespace consumes.'\n >({\n id,\n entries: entriesOutput,\n });\n }\n\n protected hydrateStorageTypeEntry(entry: SqlStorageTypeEntry): SqlStorageTypeEntry {\n if (typeof entry !== 'object' || entry === null) {\n return entry;\n }\n const kind = (entry as { kind?: unknown }).kind;\n if (typeof kind !== 'string') {\n return entry;\n }\n const factory = this.entityHydrationRegistry.get(kind);\n if (factory === undefined) {\n return entry;\n }\n return blindCast<\n SqlStorageTypeEntry,\n 'entity registry factory returns SqlStorageTypeEntry for storage.types entries'\n >(factory(entry));\n }\n\n protected constructTargetContract(hydrated: Contract<SqlStorage>): TContract {\n return hydrated as TContract;\n }\n\n /**\n * Serializes a namespace's `entries` dict by walking every enumerable\n * kind — no kind is named here, mirroring the generic hydrate walk in\n * `hydrateSqlNamespaceEntry` above. `table` is the SQL family's one\n * universal base kind (every namespace carries it), so it is always\n * emitted, even when empty; every other kind — target- or\n * pack-contributed — is emitted only when it holds at least one entry.\n * A kind carried non-enumerable on `entries` is excluded here for free,\n * since `Object.entries` honors enumerability.\n */\n protected serializeNamespaceEntries(\n entries: Readonly<Record<string, Readonly<Record<string, unknown>>>>,\n ): Record<string, Record<string, JsonObject>> {\n const out: Record<string, Record<string, JsonObject>> = {\n table: this.serializeEntries(entries['table'] ?? {}),\n };\n for (const [kind, record] of Object.entries(entries)) {\n if (kind === 'table' || record == null || Object.keys(record).length === 0) {\n continue;\n }\n out[kind] = this.serializeEntries(record);\n }\n return out;\n }\n\n private serializeEntries(entries: Readonly<Record<string, unknown>>): Record<string, JsonObject> {\n const out: Record<string, JsonObject> = {};\n for (const [name, entry] of Object.entries(entries)) {\n out[name] = this.serializeJsonObject(entry);\n }\n return out;\n }\n\n protected serializeJsonObject(value: unknown): JsonObject {\n return blindCast<\n JsonObject,\n 'serializeJsonValue round-trips an IR node through JSON, yielding a JsonObject'\n >(this.serializeJsonValue(value));\n }\n\n private serializeJsonValue(value: unknown): JsonValue {\n return blindCast<JsonValue, 'JSON.parse(JSON.stringify(x)) yields a JsonValue'>(\n JSON.parse(JSON.stringify(value)),\n );\n }\n}\n","import type { Contract } from '@prisma-next/contract/types';\nimport type { SqlStorage } from '@prisma-next/sql-contract/types';\nimport { SqlContractSerializerBase } from './sql-contract-serializer-base';\n\n/**\n * Default SQL family `ContractSerializer` concretion. Inherits the\n * full SQL-shared deserialization pipeline (structural validation +\n * IR-class hydration) without pack-registered `storage.types`\n * hydration factories — targets that emit polymorphic JSON outside the\n * codec-typed envelope wire a target-specific subclass with a populated\n * registry (see Postgres). Family-level call sites instantiate this\n * default directly when no target serializer is supplied.\n *\n * Because this serializer has no target concretion, deserialization of\n * contracts that include namespace entries from JSON will throw unless\n * the caller provides pre-hydrated `NamespaceBase` instances. Production\n * paths always supply a target-specific serializer.\n */\nexport class SqlContractSerializer extends SqlContractSerializerBase<Contract<SqlStorage>> {\n constructor() {\n super(new Map());\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"verify-C-G0obRm.mjs","names":[],"sources":["../src/core/verify.ts"],"sourcesContent":["import type { ContractMarkerRecord } from '@prisma-next/contract/types';\nimport { type } from 'arktype';\n\nconst MetaSchema = type({ '[string]': 'unknown' });\n\nfunction parseMeta(meta: unknown): Record<string, unknown> {\n if (meta === null || meta === undefined) {\n return {};\n }\n\n let parsed: unknown;\n if (typeof meta === 'string') {\n try {\n parsed = JSON.parse(meta);\n } catch {\n return {};\n }\n } else {\n parsed = meta;\n }\n\n const result = MetaSchema(parsed);\n if (result instanceof type.errors) {\n return {};\n }\n\n return result as Record<string, unknown>;\n}\n\n/**\n * SQLite stores `contract_json` as TEXT, so the wire shape is a JSON string;\n * Postgres uses `jsonb` and returns an already-parsed value. Normalize both\n * here so `ContractMarkerRecord.contractJson` is always the structured form.\n */\nfunction parseContractJson(value: unknown): unknown {\n if (value === null || value === undefined) return null;\n if (typeof value !== 'string') return value;\n try {\n return JSON.parse(value);\n } catch {\n return null;\n }\n}\n\n/**\n * Wire shape of a `prisma_contract.marker` row as it comes out of a SQL\n * driver. Snake-cased to match the on-disk column names. Shared by every\n * SQL target's `readMarker` so each runner doesn't redeclare it inline.\n */\nexport type ContractMarkerRow = {\n core_hash: string;\n profile_hash: string;\n contract_json: unknown | null;\n canonical_version: number | null;\n updated_at: Date | string;\n app_tag: string | null;\n meta: unknown | null;\n // SQLite stores arrays as JSON-TEXT, so this is `string` on the wire from\n // a SQLite driver and `string[]` from a Postgres driver. Targets normalize\n // before passing to `parseContractMarkerRow`.\n invariants: unknown;\n};\n\nconst ContractMarkerRowSchema = type({\n core_hash: 'string',\n profile_hash: 'string',\n 'contract_json?': 'unknown | null',\n 'canonical_version?': 'number | null',\n 'updated_at?': 'Date | string',\n 'app_tag?': 'string | null',\n 'meta?': 'unknown | null',\n invariants: type('string').array(),\n});\n\n/**\n * Parses a contract marker row from database query result.\n * This is SQL-specific parsing logic (handles SQL row structure with snake_case columns).\n */\nexport function parseContractMarkerRow(row: unknown): ContractMarkerRecord {\n const result = ContractMarkerRowSchema(row);\n if (result instanceof type.errors) {\n const messages = result.map((p: { message: string }) => p.message).join('; ');\n throw new Error(`Invalid contract marker row: ${messages}`);\n }\n\n const updatedAt = result.updated_at\n ? result.updated_at instanceof Date\n ? result.updated_at\n : new Date(result.updated_at)\n : new Date();\n\n return {\n storageHash: result.core_hash,\n profileHash: result.profile_hash,\n contractJson: parseContractJson(result.contract_json),\n canonicalVersion: result.canonical_version ?? null,\n updatedAt,\n appTag: result.app_tag ?? null,\n meta: parseMeta(result.meta),\n invariants: result.invariants,\n };\n}\n\n/**\n * Collects supported codec type IDs from adapter and extension manifests.\n * Returns a sorted, unique array of type IDs that are declared in the manifests.\n * This enables coverage checks by comparing contract column types against supported types.\n *\n * Note: This extracts type IDs from manifest type imports, not from runtime codec registries.\n * The manifests declare which codec types are available, but the actual type IDs\n * are defined in the codec-types TypeScript modules that are imported.\n *\n * For MVP, we return an empty array since extracting type IDs from TypeScript modules\n * would require runtime evaluation or static analysis. This can be enhanced later.\n */\nexport function collectSupportedCodecTypeIds(\n descriptors: ReadonlyArray<{ readonly id: string }>,\n): readonly string[] {\n // For MVP, return empty array\n // Future enhancement: Extract type IDs from codec-types modules via static analysis\n // or require manifests to explicitly list supported type IDs\n void descriptors;\n return [];\n}\n"],"mappings":";;AAGA,MAAM,aAAa,KAAK,EAAE,YAAY,UAAU,CAAC;AAEjD,SAAS,UAAU,MAAwC;CACzD,IAAI,SAAS,QAAQ,SAAS,KAAA,GAC5B,OAAO,CAAC;CAGV,IAAI;CACJ,IAAI,OAAO,SAAS,UAClB,IAAI;EACF,SAAS,KAAK,MAAM,IAAI;CAC1B,QAAQ;EACN,OAAO,CAAC;CACV;MAEA,SAAS;CAGX,MAAM,SAAS,WAAW,MAAM;CAChC,IAAI,kBAAkB,KAAK,QACzB,OAAO,CAAC;CAGV,OAAO;AACT;;;;;;AAOA,SAAS,kBAAkB,OAAyB;CAClD,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO;CAClD,IAAI,OAAO,UAAU,UAAU,OAAO;CACtC,IAAI;EACF,OAAO,KAAK,MAAM,KAAK;CACzB,QAAQ;EACN,OAAO;CACT;AACF;AAqBA,MAAM,0BAA0B,KAAK;CACnC,WAAW;CACX,cAAc;CACd,kBAAkB;CAClB,sBAAsB;CACtB,eAAe;CACf,YAAY;CACZ,SAAS;CACT,YAAY,KAAK,QAAQ,CAAC,CAAC,MAAM;AACnC,CAAC;;;;;AAMD,SAAgB,uBAAuB,KAAoC;CACzE,MAAM,SAAS,wBAAwB,GAAG;CAC1C,IAAI,kBAAkB,KAAK,QAAQ;EACjC,MAAM,WAAW,OAAO,KAAK,MAA2B,EAAE,OAAO,CAAC,CAAC,KAAK,IAAI;EAC5E,MAAM,IAAI,MAAM,gCAAgC,UAAU;CAC5D;CAEA,MAAM,YAAY,OAAO,aACrB,OAAO,sBAAsB,OAC3B,OAAO,aACP,IAAI,KAAK,OAAO,UAAU,oBAC5B,IAAI,KAAK;CAEb,OAAO;EACL,aAAa,OAAO;EACpB,aAAa,OAAO;EACpB,cAAc,kBAAkB,OAAO,aAAa;EACpD,kBAAkB,OAAO,qBAAqB;EAC9C;EACA,QAAQ,OAAO,WAAW;EAC1B,MAAM,UAAU,OAAO,IAAI;EAC3B,YAAY,OAAO;CACrB;AACF;;;;;;;;;;;;;AAcA,SAAgB,6BACd,aACmB;CAKnB,OAAO,CAAC;AACV"}
|