@polyprism/php-shared 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Travis Fitzgerald
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # @polyprism/php-shared
2
+
3
+ PHP rendering primitives shared by every `php-*` pattern in [PolyPrism](https://github.com/TravFitz/polyprism) — a Prisma 6 & 7 generator that emits typed code from your `schema.prisma` in whichever shape fits the layer you're writing.
4
+
5
+ **Pure ESM, Prisma 7-native, zero third-party runtime dependencies on any published `@polyprism/*` package.**
6
+
7
+ ## You're probably looking for a pattern package
8
+
9
+ You don't install `@polyprism/php-shared` directly — each `php-*` pattern package pulls it in transitively.
10
+
11
+ | Install | What it emits |
12
+ |---|---|
13
+ | [`@polyprism/php-class`](https://www.npmjs.com/package/@polyprism/php-class) | `final class User { ... }` — PHP 8.1+, mutable, public typed properties via constructor property promotion |
14
+ | [`@polyprism/php-readonly`](https://www.npmjs.com/package/@polyprism/php-readonly) | `final readonly class User { ... }` — PHP 8.2+, immutable value objects |
15
+
16
+ ## What lives here
17
+
18
+ The PHP-specific layer between [`@polyprism/core`](https://www.npmjs.com/package/@polyprism/core)'s language-agnostic IR and the per-pattern emitters:
19
+
20
+ - **`emitPhpModels(ctx, opts)`** — the top-level pipeline. Walks the IR, renders enums and models, surfaces any emit-time diagnostics, and writes everything to `<outputDir>/Enums/*.php` + `<outputDir>/Models/*.php`.
21
+ - **`renderPhpModel(opts)`** — emits one model file as a PHP class. Two declaration styles (`"class"` and `"readonly"`) share the same field-by-field rendering loop; the keyword block at the top of the class is the only differentiator.
22
+ - **`renderPhpEnum(opts)`** — emits a PHP 8.1+ backed enum (`enum Role: string { case ADMIN = 'ADMIN'; }`).
23
+ - **`mapFieldPhpType`** — IR field → PHP type expression. Handles scalars, enums, relations, nullability, lists (with PHPDoc `@var array<int, T>` hints), and resolves `@json` references to generated value classes.
24
+ - **`renderPhpJsonType`** — Parses a TS-shaped inline `@json` expression (a small supported subset) and emits a `final readonly class` for it. Nested objects collapse to PHPDoc `array{...}` shapes rather than spawning sub-classes.
25
+ - **`UseCollector`** — deduped, sorted `use`-statement builder. Skips same-namespace references automatically.
26
+ - **`renderPhpDoc`** — PHPDoc emission for `///` docs, `@deprecated` tags, native-type metadata, and list-element hints.
27
+
28
+ ## What's NOT in here (v0 scope)
29
+
30
+ - `@coerce` / `@normalise` / `@noCoerce` annotations are recognised but ignored. They're domain-class concepts that need PHP 8.4 property hooks and a Composer-published runtime helper — that'll ship as a future `@polyprism/php-domain-class`.
31
+ - TS unions / generics / identifier references inside an inline `@json` shape — fall back to `mixed` with a warning. Use `@type("\\App\\YourType")` to point at a hand-written PHP class for richer typing.
32
+ - Bare and with-path `@json` forms (e.g. `@json(SomeType)`, `@json(SomeType from "./path")`) — these rely on TS module imports that don't translate to PHP autoloading. Warn + fall back to `mixed`.
33
+ - Source-position line numbers on diagnostics — DMMF doesn't expose them, so issues carry `Model.field` context strings instead.
34
+
35
+ ## Why this is split out from `@polyprism/core`
36
+
37
+ `@polyprism/core` is deliberately language-agnostic — IR, Prisma schema reader, annotation parser, naming resolver. `@polyprism/php-shared` is where PHP-specific concerns live. Both `php-class` and `php-readonly` share one PHP rendering layer, so they agree on type mapping, use-statement handling, and PHPDoc by construction — not by convention.
38
+
39
+ ## Links
40
+
41
+ - [PolyPrism on GitHub](https://github.com/TravFitz/polyprism) — full feature list, annotation reference, side-by-side pattern examples
42
+ - [Issue tracker](https://github.com/TravFitz/polyprism/issues)
43
+
44
+ ## License
45
+
46
+ [MIT](https://github.com/TravFitz/polyprism/blob/main/LICENSE) © Travis Fitzgerald
@@ -0,0 +1,16 @@
1
+ interface Diagnostic {
2
+ readonly severity: "error" | "warning";
3
+ /**
4
+ * Where the issue applies. Conventional shapes:
5
+ * - `"User"` — model
6
+ * - `"User.email"` — model field
7
+ * - `"Role"` — enum
8
+ * - `"Role.ADMIN"` — enum value
9
+ */
10
+ readonly context: string;
11
+ readonly message: string;
12
+ }
13
+ /** Default reporter: writes warnings and errors to stderr with a prefix tag. */
14
+ declare function defaultReportDiagnostic(d: Diagnostic): void;
15
+
16
+ export { type Diagnostic, defaultReportDiagnostic };
@@ -0,0 +1,8 @@
1
+ function defaultReportDiagnostic(d) {
2
+ const prefix = d.severity === "error" ? "[error]" : "[warn] ";
3
+ console.error(`PolyPrism ${prefix} ${d.context}: ${d.message}`);
4
+ }
5
+ export {
6
+ defaultReportDiagnostic
7
+ };
8
+ //# sourceMappingURL=diagnostics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/diagnostics.ts"],"sourcesContent":["// Diagnostic surface for PHP emit-time issues.\n//\n// Mirror of the ts-shared/src/diagnostics.ts shape. Kept local to php-shared\n// for v0 — both copies are tiny and the duplication keeps the PHP package\n// independent of the TS family at install time. A future cleanup will hoist\n// the type into @polyprism/core once a third family arrives (Go, Rust, ...)\n// and the duplication is no longer hypothetical.\n//\n// Two kinds of issues funnel through here:\n// 1. AnnotationSet.parseIssues recorded by the core annotation parser.\n// 2. PHP-emitter validations (e.g. unsupported @json forms, unhandled\n// default-value shapes) that this package raises while rendering.\n\nexport interface Diagnostic {\n readonly severity: \"error\" | \"warning\";\n /**\n * Where the issue applies. Conventional shapes:\n * - `\"User\"` — model\n * - `\"User.email\"` — model field\n * - `\"Role\"` — enum\n * - `\"Role.ADMIN\"` — enum value\n */\n readonly context: string;\n readonly message: string;\n}\n\n/** Default reporter: writes warnings and errors to stderr with a prefix tag. */\nexport function defaultReportDiagnostic(d: Diagnostic): void {\n const prefix = d.severity === \"error\" ? \"[error]\" : \"[warn] \";\n // Prisma's JSON-RPC for generators is also on stderr, but its frames are\n // pure JSON; ours start with \"PolyPrism \" and contain a colon, so the\n // two stream types don't tangle when the Prisma CLI surfaces them.\n console.error(`PolyPrism ${prefix} ${d.context}: ${d.message}`);\n}\n"],"mappings":"AA2BO,SAAS,wBAAwB,GAAqB;AAC3D,QAAM,SAAS,EAAE,aAAa,UAAU,YAAY;AAIpD,UAAQ,MAAM,aAAa,MAAM,IAAI,EAAE,OAAO,KAAK,EAAE,OAAO,EAAE;AAChE;","names":[]}
@@ -0,0 +1,23 @@
1
+ import { GeneratorContext } from '@polyprism/core';
2
+ import { Diagnostic } from './diagnostics.js';
3
+ import { PhpDeclarationStyle } from './render-model.js';
4
+
5
+ interface EmitPhpModelsOptions {
6
+ readonly declarationStyle: PhpDeclarationStyle;
7
+ /**
8
+ * Root namespace for model classes. Default: `"Generated\\Models"`.
9
+ * Use a single backslash in source; this is a real PHP namespace string
10
+ * (no escape doubling required at runtime — the doubling shown here is
11
+ * just because backslash is the JS escape character).
12
+ */
13
+ readonly modelsNamespace?: string;
14
+ /** Root namespace for enum classes. Default: `"Generated\\Enums"`. */
15
+ readonly enumsNamespace?: string;
16
+ /** Root namespace for generated JSON value classes. Default: `"Generated\\JsonTypes"`. */
17
+ readonly jsonTypesNamespace?: string;
18
+ /** Optional diagnostic sink. Defaults to stderr. */
19
+ readonly onDiagnostic?: (diagnostic: Diagnostic) => void;
20
+ }
21
+ declare function emitPhpModels(ctx: GeneratorContext, opts: EmitPhpModelsOptions): Promise<void>;
22
+
23
+ export { type EmitPhpModelsOptions, emitPhpModels };
@@ -0,0 +1,136 @@
1
+ import { autoNameInlineJson, resolveTypeIdent } from "@polyprism/core";
2
+ import { defaultReportDiagnostic } from "./diagnostics.js";
3
+ import { renderPhpEnum } from "./render-enum.js";
4
+ import { renderPhpJsonType } from "./render-json-type.js";
5
+ import { renderPhpModel } from "./render-model.js";
6
+ const DEFAULT_MODELS_NAMESPACE = "Generated\\Models";
7
+ const DEFAULT_ENUMS_NAMESPACE = "Generated\\Enums";
8
+ const DEFAULT_JSON_TYPES_NAMESPACE = "Generated\\JsonTypes";
9
+ async function emitPhpModels(ctx, opts) {
10
+ const report = opts.onDiagnostic ?? defaultReportDiagnostic;
11
+ const modelsNamespace = opts.modelsNamespace ?? DEFAULT_MODELS_NAMESPACE;
12
+ const enumsNamespace = opts.enumsNamespace ?? DEFAULT_ENUMS_NAMESPACE;
13
+ const jsonTypesNamespace = opts.jsonTypesNamespace ?? DEFAULT_JSON_TYPES_NAMESPACE;
14
+ let errorCount = 0;
15
+ const emit = (d) => {
16
+ if (d.severity === "error") errorCount += 1;
17
+ report(d);
18
+ };
19
+ for (const diag of collectParseDiagnostics(ctx.ir)) emit(diag);
20
+ for (const enumDef of ctx.ir.enums) {
21
+ if (enumDef.annotations.hide) continue;
22
+ const filename = resolveTypeIdent({
23
+ schemaName: enumDef.name,
24
+ override: enumDef.annotations.name,
25
+ convention: ctx.config.naming.typeNaming
26
+ });
27
+ const source = renderPhpEnum({
28
+ enumDef,
29
+ naming: ctx.config.naming,
30
+ namespace: enumsNamespace
31
+ });
32
+ await ctx.writer.write(`Enums/${filename}.php`, source);
33
+ }
34
+ const jsonTypesEmitted = /* @__PURE__ */ new Map();
35
+ const jsonTypeOrigin = /* @__PURE__ */ new Map();
36
+ for (const model of ctx.ir.models) {
37
+ if (model.annotations.hide) continue;
38
+ for (const field of model.fields) {
39
+ if (field.annotations.hide) continue;
40
+ const json = field.annotations.json;
41
+ if (!json) continue;
42
+ let typeName = null;
43
+ let typeExpression = null;
44
+ if (json.kind === "inline-anonymous") {
45
+ typeName = autoNameInlineJson(model.name, field.name);
46
+ typeExpression = json.typeExpression;
47
+ } else if (json.kind === "inline-named") {
48
+ typeName = json.typeName;
49
+ typeExpression = json.typeExpression;
50
+ }
51
+ if (!typeName || typeExpression === null) continue;
52
+ const origin = `${model.name}.${field.name}`;
53
+ const existing = jsonTypesEmitted.get(typeName);
54
+ if (existing !== void 0 && existing !== typeExpression) {
55
+ emit({
56
+ severity: "warning",
57
+ context: origin,
58
+ message: `@json auto-naming collision: ${origin} produced JsonType class "${typeName}" but a different shape from ${jsonTypeOrigin.get(typeName)} already registered the same name. The later shape wins; the earlier field's runtime class will not match its schema-declared shape. Disambiguate with \`@json(<UniqueName> = { ... })\`.`
59
+ });
60
+ }
61
+ jsonTypesEmitted.set(typeName, typeExpression);
62
+ if (!jsonTypeOrigin.has(typeName)) jsonTypeOrigin.set(typeName, origin);
63
+ }
64
+ }
65
+ const successfullyEmitted = /* @__PURE__ */ new Set();
66
+ for (const [typeName, expression] of jsonTypesEmitted) {
67
+ const { source, issues } = renderPhpJsonType({
68
+ typeName,
69
+ typeExpression: expression,
70
+ namespace: jsonTypesNamespace,
71
+ // Best-effort context: there's no single source field for a named
72
+ // shape that's referenced from multiple places, so the JSON type
73
+ // class itself is the locus.
74
+ diagnosticContext: `JsonTypes.${typeName}`,
75
+ // JsonType readonly syntax follows the parent's declaration style —
76
+ // per-property `readonly` for `php-class` (PHP 8.1 floor) so we don't
77
+ // silently emit 8.2-only `final readonly class` syntax from an 8.1-
78
+ // documented package. Same semantics either way.
79
+ declarationStyle: opts.declarationStyle
80
+ });
81
+ for (const issue of issues) emit(issue);
82
+ if (source.length === 0) {
83
+ continue;
84
+ }
85
+ await ctx.writer.write(`JsonTypes/${typeName}.php`, source);
86
+ successfullyEmitted.add(typeName);
87
+ }
88
+ for (const model of ctx.ir.models) {
89
+ if (model.annotations.hide) continue;
90
+ const filename = resolveTypeIdent({
91
+ schemaName: model.name,
92
+ override: model.annotations.name,
93
+ convention: ctx.config.naming.typeNaming
94
+ });
95
+ const { source, issues } = renderPhpModel({
96
+ model,
97
+ ir: ctx.ir,
98
+ config: ctx.config,
99
+ declarationStyle: opts.declarationStyle,
100
+ modelsNamespace,
101
+ enumsNamespace,
102
+ jsonTypesNamespace,
103
+ jsonTypeClassNames: successfullyEmitted
104
+ });
105
+ for (const issue of issues) emit(issue);
106
+ await ctx.writer.write(`Models/${filename}.php`, source);
107
+ }
108
+ if (errorCount > 0) {
109
+ throw new Error(
110
+ `PolyPrism: PHP emit failed with ${errorCount} error-severity diagnostic${errorCount === 1 ? "" : "s"}. See the messages above for details.`
111
+ );
112
+ }
113
+ }
114
+ function* collectParseDiagnostics(ir) {
115
+ for (const model of ir.models) {
116
+ yield* parseIssuesFor(model.annotations, model.name);
117
+ for (const field of model.fields) {
118
+ yield* parseIssuesFor(field.annotations, `${model.name}.${field.name}`);
119
+ }
120
+ }
121
+ for (const enumDef of ir.enums) {
122
+ yield* parseIssuesFor(enumDef.annotations, enumDef.name);
123
+ for (const value of enumDef.values) {
124
+ yield* parseIssuesFor(value.annotations, `${enumDef.name}.${value.name}`);
125
+ }
126
+ }
127
+ }
128
+ function* parseIssuesFor(annotations, context) {
129
+ for (const issue of annotations.parseIssues) {
130
+ yield { severity: issue.severity, context, message: issue.message };
131
+ }
132
+ }
133
+ export {
134
+ emitPhpModels
135
+ };
136
+ //# sourceMappingURL=emit-models.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/emit-models.ts"],"sourcesContent":["// Top-level emit pipeline for the PHP family (php-class, php-readonly).\n//\n// Layout produced on disk (relative to the generator's outputDir):\n//\n// <outputDir>/\n// Models/\n// User.php\n// Order.php\n// Enums/\n// Role.php\n//\n// Each file declares its PSR-4 namespace as the first non-php-tag line,\n// so users can wire the generated directory into composer.json autoload\n// with a single mapping:\n//\n// \"autoload\": {\n// \"psr-4\": {\n// \"Generated\\\\\": \"src/Generated/\"\n// }\n// }\n//\n// Annotations honoured (v0):\n// - @hide — field is omitted from the class body\n// - @deprecated — emits a PHPDoc @deprecated tag\n// - @name — overrides the class / field identifier verbatim\n// - @type — overrides the field's PHP type expression verbatim\n// - @json(...) — inline forms (anonymous + named) generate readonly\n// value classes under JsonTypes/; the Json field's type resolves to the\n// generated class name. Bare and with-path forms emit a warning and\n// fall back to `mixed` (PHP has no equivalent of TS module imports).\n//\n// Annotations recognised but ignored (intentional v0 scope):\n// - @coerce / @normalise / @noCoerce — domain-class concepts that need\n// PHP 8.4 property hooks + a runtime helper. Will land as php-domain-class.\n//\n// Errors propagate the same way the ts-shared pipeline does: per-issue\n// onDiagnostic callback (defaults to stderr), accumulate error-severity\n// count, throw at the end if non-zero.\n\nimport type { AnnotationSet, EnumDef, GeneratorContext, ModelDef } from \"@polyprism/core\";\nimport { autoNameInlineJson, resolveTypeIdent } from \"@polyprism/core\";\n\nimport { type Diagnostic, defaultReportDiagnostic } from \"./diagnostics.js\";\nimport { renderPhpEnum } from \"./render-enum.js\";\nimport { renderPhpJsonType } from \"./render-json-type.js\";\nimport { type PhpDeclarationStyle, renderPhpModel } from \"./render-model.js\";\n\nexport interface EmitPhpModelsOptions {\n readonly declarationStyle: PhpDeclarationStyle;\n /**\n * Root namespace for model classes. Default: `\"Generated\\\\Models\"`.\n * Use a single backslash in source; this is a real PHP namespace string\n * (no escape doubling required at runtime — the doubling shown here is\n * just because backslash is the JS escape character).\n */\n readonly modelsNamespace?: string;\n /** Root namespace for enum classes. Default: `\"Generated\\\\Enums\"`. */\n readonly enumsNamespace?: string;\n /** Root namespace for generated JSON value classes. Default: `\"Generated\\\\JsonTypes\"`. */\n readonly jsonTypesNamespace?: string;\n /** Optional diagnostic sink. Defaults to stderr. */\n readonly onDiagnostic?: (diagnostic: Diagnostic) => void;\n}\n\nconst DEFAULT_MODELS_NAMESPACE = \"Generated\\\\Models\";\nconst DEFAULT_ENUMS_NAMESPACE = \"Generated\\\\Enums\";\nconst DEFAULT_JSON_TYPES_NAMESPACE = \"Generated\\\\JsonTypes\";\n\nexport async function emitPhpModels(\n ctx: GeneratorContext,\n opts: EmitPhpModelsOptions,\n): Promise<void> {\n const report = opts.onDiagnostic ?? defaultReportDiagnostic;\n const modelsNamespace = opts.modelsNamespace ?? DEFAULT_MODELS_NAMESPACE;\n const enumsNamespace = opts.enumsNamespace ?? DEFAULT_ENUMS_NAMESPACE;\n const jsonTypesNamespace = opts.jsonTypesNamespace ?? DEFAULT_JSON_TYPES_NAMESPACE;\n let errorCount = 0;\n\n const emit = (d: Diagnostic): void => {\n if (d.severity === \"error\") errorCount += 1;\n report(d);\n };\n\n // (1) Parser issues — recorded across models, fields, enums, enum values.\n for (const diag of collectParseDiagnostics(ctx.ir)) emit(diag);\n\n // (2) Enums — one file per visible enum, under <outputDir>/Enums/<Name>.php\n for (const enumDef of ctx.ir.enums) {\n if (enumDef.annotations.hide) continue;\n const filename = resolveTypeIdent({\n schemaName: enumDef.name,\n override: enumDef.annotations.name,\n convention: ctx.config.naming.typeNaming,\n });\n const source = renderPhpEnum({\n enumDef,\n naming: ctx.config.naming,\n namespace: enumsNamespace,\n });\n await ctx.writer.write(`Enums/${filename}.php`, source);\n }\n\n // (3) JSON value classes — one file per inline @json shape (forms 3 + 4),\n // under <outputDir>/JsonTypes/<Name>.php. Bare and with-path forms\n // reference user-supplied types and are warned about in the type\n // mapper rather than generating files here.\n const jsonTypesEmitted = new Map<string, string>();\n // Track which field first registered each JsonType name so collision\n // warnings can point at both sides.\n const jsonTypeOrigin = new Map<string, string>();\n for (const model of ctx.ir.models) {\n if (model.annotations.hide) continue;\n for (const field of model.fields) {\n if (field.annotations.hide) continue;\n const json = field.annotations.json;\n if (!json) continue;\n let typeName: string | null = null;\n let typeExpression: string | null = null;\n if (json.kind === \"inline-anonymous\") {\n typeName = autoNameInlineJson(model.name, field.name);\n typeExpression = json.typeExpression;\n } else if (json.kind === \"inline-named\") {\n typeName = json.typeName;\n typeExpression = json.typeExpression;\n }\n if (!typeName || typeExpression === null) continue;\n\n const origin = `${model.name}.${field.name}`;\n const existing = jsonTypesEmitted.get(typeName);\n if (existing !== undefined && existing !== typeExpression) {\n // Two different inline @json shapes resolved to the same class\n // name. Common ways this happens: two inline-anonymous fields\n // whose Model+Field PascalCase to the same identifier, or two\n // inline-named declarations sharing a name with different shapes.\n // Last-write-wins matches the core TS pipeline behaviour, but\n // PHP's nominal typing makes a silent shape mismatch nastier\n // than TS's structural one (the class is what it is at runtime,\n // not what the consumer expected). Warn loudly so the user can\n // disambiguate with @json(<UniqueName> = { ... }).\n emit({\n severity: \"warning\",\n context: origin,\n message:\n `@json auto-naming collision: ${origin} produced JsonType class ` +\n `\"${typeName}\" but a different shape from ${jsonTypeOrigin.get(typeName)} ` +\n \"already registered the same name. The later shape wins; the earlier \" +\n \"field's runtime class will not match its schema-declared shape. \" +\n \"Disambiguate with `@json(<UniqueName> = { ... })`.\",\n });\n }\n jsonTypesEmitted.set(typeName, typeExpression);\n if (!jsonTypeOrigin.has(typeName)) jsonTypeOrigin.set(typeName, origin);\n }\n }\n // Drive the type-mapper off the SUCCESSFUL set (built below), not the\n // intent set above. Otherwise an unparseable expression would still\n // register a `use` for a class that was never written.\n const successfullyEmitted = new Set<string>();\n for (const [typeName, expression] of jsonTypesEmitted) {\n const { source, issues } = renderPhpJsonType({\n typeName,\n typeExpression: expression,\n namespace: jsonTypesNamespace,\n // Best-effort context: there's no single source field for a named\n // shape that's referenced from multiple places, so the JSON type\n // class itself is the locus.\n diagnosticContext: `JsonTypes.${typeName}`,\n // JsonType readonly syntax follows the parent's declaration style —\n // per-property `readonly` for `php-class` (PHP 8.1 floor) so we don't\n // silently emit 8.2-only `final readonly class` syntax from an 8.1-\n // documented package. Same semantics either way.\n declarationStyle: opts.declarationStyle,\n });\n for (const issue of issues) emit(issue);\n if (source.length === 0) {\n // Renderer rejected the expression as unparseable. The warning has\n // already been emitted; the field will fall back to `mixed` when\n // the type-mapper checks `successfullyEmitted`.\n continue;\n }\n await ctx.writer.write(`JsonTypes/${typeName}.php`, source);\n successfullyEmitted.add(typeName);\n }\n\n // (4) Models — one file per visible model, under <outputDir>/Models/<Name>.php\n for (const model of ctx.ir.models) {\n if (model.annotations.hide) continue;\n const filename = resolveTypeIdent({\n schemaName: model.name,\n override: model.annotations.name,\n convention: ctx.config.naming.typeNaming,\n });\n const { source, issues } = renderPhpModel({\n model,\n ir: ctx.ir,\n config: ctx.config,\n declarationStyle: opts.declarationStyle,\n modelsNamespace,\n enumsNamespace,\n jsonTypesNamespace,\n jsonTypeClassNames: successfullyEmitted,\n });\n for (const issue of issues) emit(issue);\n await ctx.writer.write(`Models/${filename}.php`, source);\n }\n\n if (errorCount > 0) {\n throw new Error(\n `PolyPrism: PHP emit failed with ${errorCount} error-severity ` +\n `diagnostic${errorCount === 1 ? \"\" : \"s\"}. See the messages above for details.`,\n );\n }\n}\n\nfunction* collectParseDiagnostics(ir: {\n readonly models: readonly ModelDef[];\n readonly enums: readonly EnumDef[];\n}): Generator<Diagnostic> {\n for (const model of ir.models) {\n yield* parseIssuesFor(model.annotations, model.name);\n for (const field of model.fields) {\n yield* parseIssuesFor(field.annotations, `${model.name}.${field.name}`);\n }\n }\n for (const enumDef of ir.enums) {\n yield* parseIssuesFor(enumDef.annotations, enumDef.name);\n for (const value of enumDef.values) {\n yield* parseIssuesFor(value.annotations, `${enumDef.name}.${value.name}`);\n }\n }\n}\n\nfunction* parseIssuesFor(annotations: AnnotationSet, context: string): Generator<Diagnostic> {\n for (const issue of annotations.parseIssues) {\n yield { severity: issue.severity, context, message: issue.message };\n }\n}\n"],"mappings":"AAwCA,SAAS,oBAAoB,wBAAwB;AAErD,SAA0B,+BAA+B;AACzD,SAAS,qBAAqB;AAC9B,SAAS,yBAAyB;AAClC,SAAmC,sBAAsB;AAmBzD,MAAM,2BAA2B;AACjC,MAAM,0BAA0B;AAChC,MAAM,+BAA+B;AAErC,eAAsB,cACpB,KACA,MACe;AACf,QAAM,SAAS,KAAK,gBAAgB;AACpC,QAAM,kBAAkB,KAAK,mBAAmB;AAChD,QAAM,iBAAiB,KAAK,kBAAkB;AAC9C,QAAM,qBAAqB,KAAK,sBAAsB;AACtD,MAAI,aAAa;AAEjB,QAAM,OAAO,CAAC,MAAwB;AACpC,QAAI,EAAE,aAAa,QAAS,eAAc;AAC1C,WAAO,CAAC;AAAA,EACV;AAGA,aAAW,QAAQ,wBAAwB,IAAI,EAAE,EAAG,MAAK,IAAI;AAG7D,aAAW,WAAW,IAAI,GAAG,OAAO;AAClC,QAAI,QAAQ,YAAY,KAAM;AAC9B,UAAM,WAAW,iBAAiB;AAAA,MAChC,YAAY,QAAQ;AAAA,MACpB,UAAU,QAAQ,YAAY;AAAA,MAC9B,YAAY,IAAI,OAAO,OAAO;AAAA,IAChC,CAAC;AACD,UAAM,SAAS,cAAc;AAAA,MAC3B;AAAA,MACA,QAAQ,IAAI,OAAO;AAAA,MACnB,WAAW;AAAA,IACb,CAAC;AACD,UAAM,IAAI,OAAO,MAAM,SAAS,QAAQ,QAAQ,MAAM;AAAA,EACxD;AAMA,QAAM,mBAAmB,oBAAI,IAAoB;AAGjD,QAAM,iBAAiB,oBAAI,IAAoB;AAC/C,aAAW,SAAS,IAAI,GAAG,QAAQ;AACjC,QAAI,MAAM,YAAY,KAAM;AAC5B,eAAW,SAAS,MAAM,QAAQ;AAChC,UAAI,MAAM,YAAY,KAAM;AAC5B,YAAM,OAAO,MAAM,YAAY;AAC/B,UAAI,CAAC,KAAM;AACX,UAAI,WAA0B;AAC9B,UAAI,iBAAgC;AACpC,UAAI,KAAK,SAAS,oBAAoB;AACpC,mBAAW,mBAAmB,MAAM,MAAM,MAAM,IAAI;AACpD,yBAAiB,KAAK;AAAA,MACxB,WAAW,KAAK,SAAS,gBAAgB;AACvC,mBAAW,KAAK;AAChB,yBAAiB,KAAK;AAAA,MACxB;AACA,UAAI,CAAC,YAAY,mBAAmB,KAAM;AAE1C,YAAM,SAAS,GAAG,MAAM,IAAI,IAAI,MAAM,IAAI;AAC1C,YAAM,WAAW,iBAAiB,IAAI,QAAQ;AAC9C,UAAI,aAAa,UAAa,aAAa,gBAAgB;AAUzD,aAAK;AAAA,UACH,UAAU;AAAA,UACV,SAAS;AAAA,UACT,SACE,gCAAgC,MAAM,6BAClC,QAAQ,gCAAgC,eAAe,IAAI,QAAQ,CAAC;AAAA,QAI5E,CAAC;AAAA,MACH;AACA,uBAAiB,IAAI,UAAU,cAAc;AAC7C,UAAI,CAAC,eAAe,IAAI,QAAQ,EAAG,gBAAe,IAAI,UAAU,MAAM;AAAA,IACxE;AAAA,EACF;AAIA,QAAM,sBAAsB,oBAAI,IAAY;AAC5C,aAAW,CAAC,UAAU,UAAU,KAAK,kBAAkB;AACrD,UAAM,EAAE,QAAQ,OAAO,IAAI,kBAAkB;AAAA,MAC3C;AAAA,MACA,gBAAgB;AAAA,MAChB,WAAW;AAAA;AAAA;AAAA;AAAA,MAIX,mBAAmB,aAAa,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA,MAKxC,kBAAkB,KAAK;AAAA,IACzB,CAAC;AACD,eAAW,SAAS,OAAQ,MAAK,KAAK;AACtC,QAAI,OAAO,WAAW,GAAG;AAIvB;AAAA,IACF;AACA,UAAM,IAAI,OAAO,MAAM,aAAa,QAAQ,QAAQ,MAAM;AAC1D,wBAAoB,IAAI,QAAQ;AAAA,EAClC;AAGA,aAAW,SAAS,IAAI,GAAG,QAAQ;AACjC,QAAI,MAAM,YAAY,KAAM;AAC5B,UAAM,WAAW,iBAAiB;AAAA,MAChC,YAAY,MAAM;AAAA,MAClB,UAAU,MAAM,YAAY;AAAA,MAC5B,YAAY,IAAI,OAAO,OAAO;AAAA,IAChC,CAAC;AACD,UAAM,EAAE,QAAQ,OAAO,IAAI,eAAe;AAAA,MACxC;AAAA,MACA,IAAI,IAAI;AAAA,MACR,QAAQ,IAAI;AAAA,MACZ,kBAAkB,KAAK;AAAA,MACvB;AAAA,MACA;AAAA,MACA;AAAA,MACA,oBAAoB;AAAA,IACtB,CAAC;AACD,eAAW,SAAS,OAAQ,MAAK,KAAK;AACtC,UAAM,IAAI,OAAO,MAAM,UAAU,QAAQ,QAAQ,MAAM;AAAA,EACzD;AAEA,MAAI,aAAa,GAAG;AAClB,UAAM,IAAI;AAAA,MACR,mCAAmC,UAAU,6BAC9B,eAAe,IAAI,KAAK,GAAG;AAAA,IAC5C;AAAA,EACF;AACF;AAEA,UAAU,wBAAwB,IAGR;AACxB,aAAW,SAAS,GAAG,QAAQ;AAC7B,WAAO,eAAe,MAAM,aAAa,MAAM,IAAI;AACnD,eAAW,SAAS,MAAM,QAAQ;AAChC,aAAO,eAAe,MAAM,aAAa,GAAG,MAAM,IAAI,IAAI,MAAM,IAAI,EAAE;AAAA,IACxE;AAAA,EACF;AACA,aAAW,WAAW,GAAG,OAAO;AAC9B,WAAO,eAAe,QAAQ,aAAa,QAAQ,IAAI;AACvD,eAAW,SAAS,QAAQ,QAAQ;AAClC,aAAO,eAAe,MAAM,aAAa,GAAG,QAAQ,IAAI,IAAI,MAAM,IAAI,EAAE;AAAA,IAC1E;AAAA,EACF;AACF;AAEA,UAAU,eAAe,aAA4B,SAAwC;AAC3F,aAAW,SAAS,YAAY,aAAa;AAC3C,UAAM,EAAE,UAAU,MAAM,UAAU,SAAS,SAAS,MAAM,QAAQ;AAAA,EACpE;AACF;","names":[]}
@@ -0,0 +1,9 @@
1
+ export { Diagnostic, defaultReportDiagnostic } from './diagnostics.js';
2
+ export { EmitPhpModelsOptions, emitPhpModels } from './emit-models.js';
3
+ export { RenderPhpDocOptions, buildNativeTypeTag, renderPhpDoc } from './phpdoc.js';
4
+ export { RenderEnumOptions, renderPhpEnum } from './render-enum.js';
5
+ export { RenderJsonTypeOptions, RenderJsonTypeResult, renderPhpJsonType } from './render-json-type.js';
6
+ export { PhpDeclarationStyle, RenderPhpModelOptions, RenderPhpModelResult, renderPhpModel } from './render-model.js';
7
+ export { PhpTypeMapperOptions, PhpTypeMapping, mapFieldPhpType } from './type-mapper.js';
8
+ export { UseCollector } from './use-collector.js';
9
+ import '@polyprism/core';
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ export * from "./diagnostics.js";
2
+ export * from "./emit-models.js";
3
+ export * from "./phpdoc.js";
4
+ export * from "./render-enum.js";
5
+ export * from "./render-json-type.js";
6
+ export * from "./render-model.js";
7
+ export * from "./type-mapper.js";
8
+ export * from "./use-collector.js";
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export * from \"./diagnostics.js\";\nexport * from \"./emit-models.js\";\nexport * from \"./phpdoc.js\";\nexport * from \"./render-enum.js\";\nexport * from \"./render-json-type.js\";\nexport * from \"./render-model.js\";\nexport * from \"./type-mapper.js\";\nexport * from \"./use-collector.js\";\n"],"mappings":"AAAA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;","names":[]}
@@ -0,0 +1,16 @@
1
+ import { NativeType, AnnotationSet } from '@polyprism/core';
2
+
3
+ interface RenderPhpDocOptions {
4
+ /** Number of leading spaces before each `*` line. 0 for top-level, 4 for class members. */
5
+ readonly indent: number;
6
+ /** Optional extra tag lines, each as `@tag content` without the leading `* `. */
7
+ readonly extraTags?: readonly string[];
8
+ }
9
+ declare function renderPhpDoc(annotations: AnnotationSet, opts: RenderPhpDocOptions): string;
10
+ /**
11
+ * Build the @db.X native-type tag line if the field has Prisma native-type
12
+ * metadata. Returns null if none — caller spreads into extraTags.
13
+ */
14
+ declare function buildNativeTypeTag(nativeType: NativeType | null): string | null;
15
+
16
+ export { type RenderPhpDocOptions, buildNativeTypeTag, renderPhpDoc };
package/dist/phpdoc.js ADDED
@@ -0,0 +1,32 @@
1
+ function renderPhpDoc(annotations, opts) {
2
+ const lines = [];
3
+ if (annotations.documentation) {
4
+ for (const docLine of annotations.documentation.split("\n")) {
5
+ lines.push(docLine);
6
+ }
7
+ }
8
+ if (annotations.deprecated) {
9
+ const reason = annotations.deprecated.reason;
10
+ lines.push(reason ? `@deprecated ${reason}` : "@deprecated");
11
+ }
12
+ for (const tag of opts.extraTags ?? []) {
13
+ lines.push(tag);
14
+ }
15
+ if (lines.length === 0) return "";
16
+ const pad = " ".repeat(opts.indent);
17
+ const body = lines.map((line) => `${pad} * ${line}`).join("\n");
18
+ return `${pad}/**
19
+ ${body}
20
+ ${pad} */
21
+ `;
22
+ }
23
+ function buildNativeTypeTag(nativeType) {
24
+ if (!nativeType) return null;
25
+ const args = nativeType.args.join(", ");
26
+ return args ? `@db.${nativeType.name}(${args})` : `@db.${nativeType.name}`;
27
+ }
28
+ export {
29
+ buildNativeTypeTag,
30
+ renderPhpDoc
31
+ };
32
+ //# sourceMappingURL=phpdoc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/phpdoc.ts"],"sourcesContent":["// PHPDoc block emission for models, fields, and enums.\n//\n// PHPDoc is the de-facto comment format read by IDEs (PhpStorm, VS Code via\n// Intelephense), static analysers (PHPStan, Psalm), and `phpdoc` itself.\n// We emit it for:\n// - `///` documentation lines from the Prisma schema (preserved verbatim\n// as the doc body)\n// - `@deprecated` tags (with optional reason from `@deprecated(\"reason\")`)\n// - `@db.X(precision, scale)` native-type metadata (kept as a tag because\n// PHP has no native Decimal precision generics, same reasoning as the\n// TS family — precision is schema-level info worth preserving)\n// - PHPDoc `@var` hints for list types — PHP's native `array` doesn't\n// carry an element type, so we annotate with `@var array<int, Type>`\n// so static analysers see the intended shape.\n//\n// The output is always either:\n// - empty string (no doc, no tags, no extras → no block)\n// - a multi-line `/** ... */` block ending with a newline\n\nimport type { AnnotationSet, NativeType } from \"@polyprism/core\";\n\nexport interface RenderPhpDocOptions {\n /** Number of leading spaces before each `*` line. 0 for top-level, 4 for class members. */\n readonly indent: number;\n /** Optional extra tag lines, each as `@tag content` without the leading `* `. */\n readonly extraTags?: readonly string[];\n}\n\nexport function renderPhpDoc(annotations: AnnotationSet, opts: RenderPhpDocOptions): string {\n const lines: string[] = [];\n\n if (annotations.documentation) {\n for (const docLine of annotations.documentation.split(\"\\n\")) {\n lines.push(docLine);\n }\n }\n\n if (annotations.deprecated) {\n const reason = annotations.deprecated.reason;\n // PHPDoc `@deprecated` follows the same shape as JSDoc/Javadoc.\n lines.push(reason ? `@deprecated ${reason}` : \"@deprecated\");\n }\n\n for (const tag of opts.extraTags ?? []) {\n lines.push(tag);\n }\n\n if (lines.length === 0) return \"\";\n\n const pad = \" \".repeat(opts.indent);\n const body = lines.map((line) => `${pad} * ${line}`).join(\"\\n\");\n return `${pad}/**\\n${body}\\n${pad} */\\n`;\n}\n\n/**\n * Build the @db.X native-type tag line if the field has Prisma native-type\n * metadata. Returns null if none — caller spreads into extraTags.\n */\nexport function buildNativeTypeTag(nativeType: NativeType | null): string | null {\n if (!nativeType) return null;\n const args = nativeType.args.join(\", \");\n return args ? `@db.${nativeType.name}(${args})` : `@db.${nativeType.name}`;\n}\n"],"mappings":"AA4BO,SAAS,aAAa,aAA4B,MAAmC;AAC1F,QAAM,QAAkB,CAAC;AAEzB,MAAI,YAAY,eAAe;AAC7B,eAAW,WAAW,YAAY,cAAc,MAAM,IAAI,GAAG;AAC3D,YAAM,KAAK,OAAO;AAAA,IACpB;AAAA,EACF;AAEA,MAAI,YAAY,YAAY;AAC1B,UAAM,SAAS,YAAY,WAAW;AAEtC,UAAM,KAAK,SAAS,eAAe,MAAM,KAAK,aAAa;AAAA,EAC7D;AAEA,aAAW,OAAO,KAAK,aAAa,CAAC,GAAG;AACtC,UAAM,KAAK,GAAG;AAAA,EAChB;AAEA,MAAI,MAAM,WAAW,EAAG,QAAO;AAE/B,QAAM,MAAM,IAAI,OAAO,KAAK,MAAM;AAClC,QAAM,OAAO,MAAM,IAAI,CAAC,SAAS,GAAG,GAAG,MAAM,IAAI,EAAE,EAAE,KAAK,IAAI;AAC9D,SAAO,GAAG,GAAG;AAAA,EAAQ,IAAI;AAAA,EAAK,GAAG;AAAA;AACnC;AAMO,SAAS,mBAAmB,YAA8C;AAC/E,MAAI,CAAC,WAAY,QAAO;AACxB,QAAM,OAAO,WAAW,KAAK,KAAK,IAAI;AACtC,SAAO,OAAO,OAAO,WAAW,IAAI,IAAI,IAAI,MAAM,OAAO,WAAW,IAAI;AAC1E;","names":[]}
@@ -0,0 +1,10 @@
1
+ import { EnumDef, NamingConfig } from '@polyprism/core';
2
+
3
+ interface RenderEnumOptions {
4
+ readonly enumDef: EnumDef;
5
+ readonly naming: NamingConfig;
6
+ readonly namespace: string;
7
+ }
8
+ declare function renderPhpEnum(opts: RenderEnumOptions): string;
9
+
10
+ export { type RenderEnumOptions, renderPhpEnum };
@@ -0,0 +1,36 @@
1
+ import { resolveTypeIdent } from "@polyprism/core";
2
+ import { renderPhpDoc } from "./phpdoc.js";
3
+ function renderPhpEnum(opts) {
4
+ const { enumDef, naming, namespace } = opts;
5
+ const ident = resolveTypeIdent({
6
+ schemaName: enumDef.name,
7
+ override: enumDef.annotations.name,
8
+ convention: naming.typeNaming
9
+ });
10
+ const visibleValues = enumDef.values.filter((v) => !v.annotations.hide);
11
+ const caseLines = visibleValues.map(renderEnumCase).join("\n");
12
+ const headerDoc = renderPhpDoc(enumDef.annotations, { indent: 0 });
13
+ return [
14
+ "<?php",
15
+ "",
16
+ "declare(strict_types=1);",
17
+ "",
18
+ `namespace ${namespace};`,
19
+ "",
20
+ `${headerDoc}enum ${ident}: string
21
+ {
22
+ ${caseLines}
23
+ }`,
24
+ ""
25
+ ].join("\n");
26
+ }
27
+ function renderEnumCase(value) {
28
+ const dbValue = value.dbName ?? value.name;
29
+ const escapedValue = dbValue.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
30
+ const docBlock = renderPhpDoc(value.annotations, { indent: 4 });
31
+ return `${docBlock} case ${value.name} = '${escapedValue}';`;
32
+ }
33
+ export {
34
+ renderPhpEnum
35
+ };
36
+ //# sourceMappingURL=render-enum.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/render-enum.ts"],"sourcesContent":["// Renders one Prisma enum as a PHP 8.1+ backed enum (string-typed).\n//\n// Output shape:\n//\n// <?php\n//\n// declare(strict_types=1);\n//\n// namespace Generated\\Enums;\n//\n// /**\n// * Doc lines from `///` comments above the schema enum, if any.\n// * @deprecated If the enum has @deprecated\n// */\n// enum Role: string\n// {\n// case ADMIN = 'ADMIN';\n// case MEMBER = 'MEMBER';\n// }\n//\n// Backed enums (`enum X: string`) were chosen over pure enums because:\n// - Prisma stores enums in the database as strings; round-tripping through\n// `Role::from($dbValue)` and `$enum->value` is the natural mapping.\n// - PHPStan / Psalm / PhpStorm all narrow `enum X: string` cases on\n// equality with string literals, so callers can do\n// `if ($role->value === 'ADMIN')` without losing autocompletion.\n\nimport type { EnumDef, EnumValueDef, NamingConfig } from \"@polyprism/core\";\nimport { resolveTypeIdent } from \"@polyprism/core\";\n\nimport { renderPhpDoc } from \"./phpdoc.js\";\n\nexport interface RenderEnumOptions {\n readonly enumDef: EnumDef;\n readonly naming: NamingConfig;\n readonly namespace: string;\n}\n\nexport function renderPhpEnum(opts: RenderEnumOptions): string {\n const { enumDef, naming, namespace } = opts;\n const ident = resolveTypeIdent({\n schemaName: enumDef.name,\n override: enumDef.annotations.name,\n convention: naming.typeNaming,\n });\n\n const visibleValues = enumDef.values.filter((v) => !v.annotations.hide);\n const caseLines = visibleValues.map(renderEnumCase).join(\"\\n\");\n const headerDoc = renderPhpDoc(enumDef.annotations, { indent: 0 });\n\n return [\n \"<?php\",\n \"\",\n \"declare(strict_types=1);\",\n \"\",\n `namespace ${namespace};`,\n \"\",\n `${headerDoc}enum ${ident}: string\\n{\\n${caseLines}\\n}`,\n \"\",\n ].join(\"\\n\");\n}\n\nfunction renderEnumCase(value: EnumValueDef): string {\n // Prisma allows `@map(\"DB_VALUE\")` on enum values via `dbName`. Honour\n // that as the case's backing-string value so consumers can match against\n // what the database actually stores.\n const dbValue = value.dbName ?? value.name;\n // Single-quoted PHP strings don't process escapes for anything except\n // `\\\\` and `\\'`. JSON.stringify gives us double-quoted; flip the quotes\n // and escape any apostrophes in the (very rare) case the value contains\n // one. Realistic Prisma enum values are uppercase ASCII identifiers, so\n // the escape branch is defensive.\n const escapedValue = dbValue.replace(/\\\\/g, \"\\\\\\\\\").replace(/'/g, \"\\\\'\");\n const docBlock = renderPhpDoc(value.annotations, { indent: 4 });\n return `${docBlock} case ${value.name} = '${escapedValue}';`;\n}\n"],"mappings":"AA4BA,SAAS,wBAAwB;AAEjC,SAAS,oBAAoB;AAQtB,SAAS,cAAc,MAAiC;AAC7D,QAAM,EAAE,SAAS,QAAQ,UAAU,IAAI;AACvC,QAAM,QAAQ,iBAAiB;AAAA,IAC7B,YAAY,QAAQ;AAAA,IACpB,UAAU,QAAQ,YAAY;AAAA,IAC9B,YAAY,OAAO;AAAA,EACrB,CAAC;AAED,QAAM,gBAAgB,QAAQ,OAAO,OAAO,CAAC,MAAM,CAAC,EAAE,YAAY,IAAI;AACtE,QAAM,YAAY,cAAc,IAAI,cAAc,EAAE,KAAK,IAAI;AAC7D,QAAM,YAAY,aAAa,QAAQ,aAAa,EAAE,QAAQ,EAAE,CAAC;AAEjE,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,aAAa,SAAS;AAAA,IACtB;AAAA,IACA,GAAG,SAAS,QAAQ,KAAK;AAAA;AAAA,EAAgB,SAAS;AAAA;AAAA,IAClD;AAAA,EACF,EAAE,KAAK,IAAI;AACb;AAEA,SAAS,eAAe,OAA6B;AAInD,QAAM,UAAU,MAAM,UAAU,MAAM;AAMtC,QAAM,eAAe,QAAQ,QAAQ,OAAO,MAAM,EAAE,QAAQ,MAAM,KAAK;AACvE,QAAM,WAAW,aAAa,MAAM,aAAa,EAAE,QAAQ,EAAE,CAAC;AAC9D,SAAO,GAAG,QAAQ,YAAY,MAAM,IAAI,OAAO,YAAY;AAC7D;","names":[]}
@@ -0,0 +1,32 @@
1
+ import { Diagnostic } from './diagnostics.js';
2
+
3
+ interface RenderJsonTypeOptions {
4
+ /** Top-level class name, e.g. "BillingAddress". */
5
+ readonly typeName: string;
6
+ /** Raw TS expression from the @json annotation, e.g. `{ street: string, city: string }`. */
7
+ readonly typeExpression: string;
8
+ /** PHP namespace the generated class lives in, e.g. `"Generated\\JsonTypes"`. */
9
+ readonly namespace: string;
10
+ /**
11
+ * Context string for diagnostics — typically `"Model.field"` so users can
12
+ * find the schema source of an unsupported-shape warning.
13
+ */
14
+ readonly diagnosticContext: string;
15
+ /**
16
+ * Determines the readonly syntax used:
17
+ * - `"class"`: emit `final class Foo` with per-property `public readonly`
18
+ * (works on PHP 8.1). Matches the floor of the `php-class` generator.
19
+ * - `"readonly"`: emit `final readonly class Foo` (class-level modifier,
20
+ * PHP 8.2+). Matches the floor of the `php-readonly` generator.
21
+ * Semantically both produce a value object whose properties can't be
22
+ * reassigned after construction; only the syntax differs.
23
+ */
24
+ readonly declarationStyle: "class" | "readonly";
25
+ }
26
+ interface RenderJsonTypeResult {
27
+ readonly source: string;
28
+ readonly issues: readonly Diagnostic[];
29
+ }
30
+ declare function renderPhpJsonType(opts: RenderJsonTypeOptions): RenderJsonTypeResult;
31
+
32
+ export { type RenderJsonTypeOptions, type RenderJsonTypeResult, renderPhpJsonType };