Skip to content

Commit cff5137

Browse files
committed
Port PHP SDK route generation from nextlove to Metalsmith
Replace the @seamapi/nextlove-sdk-generator based generation with the metalsmith + handlebars + @seamapi/blueprint architecture used by seamapi/javascript-http and seamapi/python. The generated output under src/ (SeamClient.php and Objects/*.php) is byte-identical. The blueprint drives the route, endpoint, and namespace iteration order. The raw OpenAPI spec is still consulted wherever the nextlove generator derived output from data the blueprint normalizes differently (integer vs number types, resource schema set and order, parameter and oneOf/allOf flattening, deeply nested namespaces); each of those spots carries a TODO to migrate to the blueprint once output is allowed to change, and the supporting code lives in files marked TEMPORARY. - Add codegen/ with the Metalsmith pipeline, plugin, context builders, and Handlebars layouts and partials - Remove generate-routes.js - Pin @seamapi/types at 1.827.0 for output parity and use @seamapi/blueprint 0.55.0 with @seamapi/smith - Add tsconfig.json, a one-line eslint.config.ts, and typecheck, lint, postlint, and preformat scripts matching seamapi/docs - Ignore *.hbs and CODEGEN.md in a new .prettierignore since the glimmer parser mangles PHP-flavored templates, and format .ts with the js rules - Ignore the generated CODEGEN.md seed and *.tsbuildinfo in .gitignore Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UrUX4ghim2PDnBjXayuLDj
1 parent de79cb9 commit cff5137

32 files changed

Lines changed: 6996 additions & 928 deletions

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1+
# Codegen
2+
CODEGEN.md
3+
14
vendor
25
node_modules
36
.env
47
.vscode
8+
9+
# TypeScript cache
10+
*.tsbuildinfo

.prettierignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Prettier exceptions
2+
3+
*.hbs
4+
CODEGEN.md

.prettierrc.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
"overrides": [
66
{
7-
"files": "**/*.{js,json,yml,md}",
7+
"files": "**/*.{js,ts,json,yml,md}",
88
"options": {
99
"semi": false,
1010
"singleQuote": true,

codegen/content/CODEGEN.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Codegen

codegen/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default null

codegen/layouts/default.hbs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{{contents}}

codegen/layouts/object.hbs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
namespace Seam\Objects;
4+
5+
class {{className}}
6+
{
7+
public static function from_json(mixed $json): {{className}}|null
8+
{
9+
if (!$json) {
10+
return null;
11+
}
12+
return new self(
13+
{{#each fromJsonProps}}
14+
{{{this}}}
15+
{{/each}}
16+
);
17+
}
18+
19+
public function __construct(
20+
{{#each constructorParams}}
21+
{{{this}}}
22+
{{/each}}
23+
) {
24+
}
25+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
class {{clientName}}Client
2+
{
3+
private SeamClient $seam;
4+
{{#if hasChildClients}}
5+
{{#each childClients}}
6+
public {{clientName}}Client ${{namespace}};
7+
{{/each}}
8+
{{else}}
9+
10+
{{/if}}
11+
public function __construct(SeamClient $seam)
12+
{
13+
$this->seam = $seam;
14+
{{#each childClients}}
15+
$this->{{namespace}} = new {{clientName}}Client($seam);
16+
{{/each}}
17+
}
18+
{{#each methods}}
19+
20+
{{> route-method}}
21+
{{/each}}
22+
{{#if isActionAttempts}}
23+
{{> poll-until-ready}}
24+
{{/if}}
25+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
public function poll_until_ready(string $action_attempt_id, float $timeout = 20.0): ActionAttempt
2+
{
3+
$seam = $this->seam;
4+
$time_waiting = 0.0;
5+
$polling_interval = 0.4;
6+
$action_attempt = $seam->action_attempts->get($action_attempt_id);
7+
8+
while ($action_attempt->status == 'pending') {
9+
$action_attempt = $seam->action_attempts->get(
10+
$action_attempt->action_attempt_id
11+
);
12+
if ($time_waiting > $timeout) {
13+
throw new ActionAttemptTimeoutError($action_attempt, $timeout);
14+
}
15+
$time_waiting += $polling_interval;
16+
usleep($polling_interval * 1000000);
17+
}
18+
19+
if ($action_attempt->status == 'error') {
20+
throw new ActionAttemptFailedError($action_attempt);
21+
}
22+
23+
return $action_attempt;
24+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
public function {{methodName}}({{{signatureParams}}}): {{returnType}} {
2+
{{#if hasParams}}
3+
$request_payload = [];
4+
5+
{{#each paramNames}}
6+
if (${{this}} !== null) {
7+
$request_payload["{{this}}"] = ${{this}};
8+
}
9+
{{/each}}
10+
11+
{{/if}}
12+
{{#unless returnsVoid}}$res = {{/unless}}$this->seam->request(
13+
"POST",
14+
"{{path}}",
15+
{{#if hasParams}}
16+
json: (object) $request_payload,
17+
{{/if}}
18+
);
19+
{{#if usesActionAttempt}}
20+
21+
if (!$wait_for_action_attempt) {
22+
return {{returnResource}}::from_json($res->{{returnPath}});
23+
}
24+
25+
$action_attempt = $this->seam->action_attempts->poll_until_ready($res->action_attempt->action_attempt_id);
26+
27+
return $action_attempt;
28+
{{else}}
29+
{{#if usesOnResponse}}
30+
31+
if ($on_response !== null) {
32+
$on_response($res);
33+
}
34+
{{/if}}
35+
{{#unless returnsVoid}}
36+
37+
{{#if isArrayResponse}}
38+
return array_map(fn ($r) => {{returnResource}}::from_json($r), $res->{{returnPath}});
39+
{{else}}
40+
return {{returnResource}}::from_json($res->{{returnPath}});
41+
{{/if}}
42+
{{/unless}}
43+
{{/if}}
44+
}

0 commit comments

Comments
 (0)