@friehub/blueprint 0.1.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.
Files changed (138) hide show
  1. package/.github/workflows/ci.yml +122 -0
  2. package/.github/workflows/publish.yml +24 -0
  3. package/README.md +266 -0
  4. package/adapters/analytics/amplitude.yaml +44 -0
  5. package/adapters/analytics/mixpanel.yaml +47 -0
  6. package/adapters/analytics/segment.yaml +40 -0
  7. package/adapters/auth/auth0.yaml +56 -0
  8. package/adapters/auth/clerk.yaml +53 -0
  9. package/adapters/auth/supertokens.yaml +55 -0
  10. package/adapters/billing/paddle.yaml +57 -0
  11. package/adapters/billing/stripe.yaml +49 -0
  12. package/adapters/caching/memcached.yaml +28 -0
  13. package/adapters/caching/redis.yaml +37 -0
  14. package/adapters/chargebacks/chargebacks911.yaml +45 -0
  15. package/adapters/chargebacks/stripe.yaml +45 -0
  16. package/adapters/crm_leads/hubspot.yaml +43 -0
  17. package/adapters/crm_leads/salesforce.yaml +60 -0
  18. package/adapters/customer_support/intercom.yaml +44 -0
  19. package/adapters/customer_support/zendesk.yaml +51 -0
  20. package/adapters/donations/paypal.yaml +47 -0
  21. package/adapters/donations/stripe.yaml +47 -0
  22. package/adapters/emails/mailgun.yaml +47 -0
  23. package/adapters/emails/resend.yaml +43 -0
  24. package/adapters/emails/sendgrid.yaml +43 -0
  25. package/adapters/error_tracking/bugsnag.yaml +52 -0
  26. package/adapters/error_tracking/sentry.yaml +58 -0
  27. package/adapters/feature_flags/flagsmith.yaml +41 -0
  28. package/adapters/feature_flags/launchdarkly.yaml +41 -0
  29. package/adapters/feature_flags/unleash.yaml +41 -0
  30. package/adapters/fraud_detection/riskified.yaml +41 -0
  31. package/adapters/fraud_detection/sift.yaml +40 -0
  32. package/adapters/fulfillment/easyship.yaml +51 -0
  33. package/adapters/fulfillment/shipengine.yaml +51 -0
  34. package/adapters/incident_management/opsgenie.yaml +49 -0
  35. package/adapters/incident_management/pagerduty.yaml +48 -0
  36. package/adapters/invoicing/freshbooks.yaml +54 -0
  37. package/adapters/invoicing/stripe.yaml +47 -0
  38. package/adapters/ip_intelligence/ipinfo.yaml +37 -0
  39. package/adapters/ip_intelligence/maxmind.yaml +39 -0
  40. package/adapters/jobs/bullmq.yaml +54 -0
  41. package/adapters/jobs/temporal.yaml +53 -0
  42. package/adapters/kyc/jumio.yaml +54 -0
  43. package/adapters/kyc/onfido.yaml +53 -0
  44. package/adapters/media/cloudinary.yaml +48 -0
  45. package/adapters/media/imgix.yaml +47 -0
  46. package/adapters/notifications/firebase.yaml +45 -0
  47. package/adapters/notifications/novu.yaml +46 -0
  48. package/adapters/notifications/onesignal.yaml +45 -0
  49. package/adapters/payments/adyen.yaml +46 -0
  50. package/adapters/payments/paystack.yaml +45 -0
  51. package/adapters/payments/stripe.yaml +49 -0
  52. package/adapters/payouts/paypal.yaml +49 -0
  53. package/adapters/payouts/stripe.yaml +49 -0
  54. package/adapters/projects/asana.yaml +49 -0
  55. package/adapters/projects/jira.yaml +58 -0
  56. package/adapters/projects/linear.yaml +49 -0
  57. package/adapters/queues/bullmq.yaml +47 -0
  58. package/adapters/queues/rabbitmq.yaml +51 -0
  59. package/adapters/queues/sqs.yaml +45 -0
  60. package/adapters/rate_limiting/cloudflare.yaml +37 -0
  61. package/adapters/rate_limiting/upstash.yaml +35 -0
  62. package/adapters/search/algolia.yaml +39 -0
  63. package/adapters/search/meilisearch.yaml +39 -0
  64. package/adapters/search/typesense.yaml +42 -0
  65. package/adapters/shipping/easyship.yaml +45 -0
  66. package/adapters/shipping/shipengine.yaml +45 -0
  67. package/adapters/sms/twilio.yaml +41 -0
  68. package/adapters/sms/vonage.yaml +41 -0
  69. package/adapters/storage/azure-blob.yaml +42 -0
  70. package/adapters/storage/gcs.yaml +41 -0
  71. package/adapters/storage/s3.yaml +49 -0
  72. package/adapters/subscriptions/chargebee.yaml +32 -0
  73. package/adapters/subscriptions/stripe.yaml +37 -0
  74. package/adapters/tasks/asana.yaml +50 -0
  75. package/adapters/tasks/jira.yaml +59 -0
  76. package/adapters/tasks/linear.yaml +50 -0
  77. package/adapters/taxation/avalara.yaml +52 -0
  78. package/adapters/taxation/taxjar.yaml +48 -0
  79. package/adapters/trace_query/datadog.yaml +49 -0
  80. package/adapters/trace_query/honeycomb.yaml +42 -0
  81. package/adapters/trace_query/jaeger.yaml +42 -0
  82. package/adapters/web_analytics/google-analytics.yaml +42 -0
  83. package/adapters/web_analytics/plausible.yaml +34 -0
  84. package/adapters/web_analytics/posthog.yaml +34 -0
  85. package/adapters/webhooks/relay.yaml +35 -0
  86. package/adapters/webhooks/svix.yaml +41 -0
  87. package/blueprint.json +5 -0
  88. package/blueprinter_system_design.svg +139 -0
  89. package/catalog.json +37943 -0
  90. package/dist/cli/commands.js +362 -0
  91. package/dist/cli/help.js +211 -0
  92. package/dist/cli/render.js +109 -0
  93. package/dist/cli.js +69 -0
  94. package/dist/core/adapters/adapter-audit.test.js +85 -0
  95. package/dist/core/adapters/adapter.test.js +66 -0
  96. package/dist/core/adapters/index.js +4 -0
  97. package/dist/core/adapters/load.js +131 -0
  98. package/dist/core/adapters/resolve.js +78 -0
  99. package/dist/core/adapters/select.js +80 -0
  100. package/dist/core/adapters/types.js +1 -0
  101. package/dist/core/adapters/validate.js +121 -0
  102. package/dist/core/catalog.js +3 -0
  103. package/dist/core/collectors.js +126 -0
  104. package/dist/core/discovery.js +57 -0
  105. package/dist/core/edge-cases.test.js +147 -0
  106. package/dist/core/envelope.js +1 -0
  107. package/dist/core/graph.js +123 -0
  108. package/dist/core/graph.test.js +62 -0
  109. package/dist/core/implement.js +48 -0
  110. package/dist/core/index.js +9 -0
  111. package/dist/core/load-catalog.js +24 -0
  112. package/dist/core/parse-document.js +114 -0
  113. package/dist/core/parse-document.test.js +104 -0
  114. package/dist/core/parser.js +6 -0
  115. package/dist/core/parser.test.js +134 -0
  116. package/dist/core/resolve.js +119 -0
  117. package/dist/core/resolve.test.js +108 -0
  118. package/dist/core/scanner.js +163 -0
  119. package/dist/core/search.js +34 -0
  120. package/dist/core/search.test.js +43 -0
  121. package/dist/core/section-body.js +258 -0
  122. package/dist/core/sections.js +53 -0
  123. package/dist/core/verify-implement.test.js +123 -0
  124. package/dist/core/verify.js +156 -0
  125. package/dist/generators/engine.js +86 -0
  126. package/dist/generators/generator.test.js +112 -0
  127. package/dist/generators/index.js +3 -0
  128. package/dist/generators/prototype/index.js +242 -0
  129. package/dist/generators/render.js +146 -0
  130. package/dist/generators/types.js +124 -0
  131. package/dist/generators/typescript/helpers.js +125 -0
  132. package/dist/generators/typescript/index.js +206 -0
  133. package/dist/index.js +8 -0
  134. package/dist/mcp/server.js +202 -0
  135. package/dist/mcp/server.test.js +136 -0
  136. package/dist/utils/args.js +142 -0
  137. package/package.json +38 -0
  138. package/tsconfig.json +16 -0
@@ -0,0 +1,146 @@
1
+ import Handlebars from "handlebars";
2
+ Handlebars.registerHelper("pascalCase", (str) => {
3
+ return str
4
+ .split(/[_\-.\s]+/)
5
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
6
+ .join("");
7
+ });
8
+ Handlebars.registerHelper("camelCase", (str) => {
9
+ const pascal = str
10
+ .split(/[_\-.\s]+/)
11
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
12
+ .join("");
13
+ return pascal.charAt(0).toLowerCase() + pascal.slice(1);
14
+ });
15
+ Handlebars.registerHelper("snakeCase", (str) => {
16
+ return str
17
+ .replace(/([A-Z])/g, "_$1")
18
+ .toLowerCase()
19
+ .replace(/^_/, "")
20
+ .replace(/[_\-.\s]+/g, "_");
21
+ });
22
+ Handlebars.registerHelper("kebabCase", (str) => {
23
+ return str
24
+ .replace(/([A-Z])/g, "-$1")
25
+ .toLowerCase()
26
+ .replace(/^-/, "")
27
+ .replace(/[_\-.\s]+/g, "-");
28
+ });
29
+ Handlebars.registerHelper("lowerCase", (str) => str.toLowerCase());
30
+ Handlebars.registerHelper("upperCase", (str) => str.toUpperCase());
31
+ Handlebars.registerHelper("mapType", (type, language) => {
32
+ const TYPE_MAPPINGS = {
33
+ string: { typescript: "string", rust: "String", python: "str", go: "string" },
34
+ number: { typescript: "number", rust: "f64", python: "float", go: "float64" },
35
+ boolean: { typescript: "boolean", rust: "bool", python: "bool", go: "bool" },
36
+ null: { typescript: "null", rust: "Option::None", python: "None", go: "nil" },
37
+ };
38
+ const mapping = TYPE_MAPPINGS[type]?.[language];
39
+ if (mapping)
40
+ return mapping;
41
+ if (type.endsWith("[]")) {
42
+ const inner = type.slice(0, -2);
43
+ const mappedInner = mapTypeHelper(inner, language);
44
+ switch (language) {
45
+ case "typescript":
46
+ return `${mappedInner}[]`;
47
+ case "rust":
48
+ return `Vec<${mappedInner}>`;
49
+ case "python":
50
+ return `list[${mappedInner}]`;
51
+ case "go":
52
+ return `[]${mappedInner}`;
53
+ }
54
+ }
55
+ if (type.endsWith("?")) {
56
+ const inner = type.slice(0, -1);
57
+ const mappedInner = mapTypeHelper(inner, language);
58
+ switch (language) {
59
+ case "typescript":
60
+ return `${mappedInner} | undefined`;
61
+ case "rust":
62
+ return `Option<${mappedInner}>`;
63
+ case "python":
64
+ return `Optional[${mappedInner}]`;
65
+ case "go":
66
+ return `*${mappedInner}`;
67
+ }
68
+ }
69
+ return type;
70
+ });
71
+ function mapTypeHelper(type, language) {
72
+ const TYPE_MAPPINGS = {
73
+ string: { typescript: "string", rust: "String", python: "str", go: "string" },
74
+ number: { typescript: "number", rust: "f64", python: "float", go: "float64" },
75
+ boolean: { typescript: "boolean", rust: "bool", python: "bool", go: "bool" },
76
+ null: { typescript: "null", rust: "Option::None", python: "None", go: "nil" },
77
+ };
78
+ const mapping = TYPE_MAPPINGS[type]?.[language];
79
+ if (mapping)
80
+ return mapping;
81
+ if (type.endsWith("[]")) {
82
+ const inner = type.slice(0, -2);
83
+ const mappedInner = mapTypeHelper(inner, language);
84
+ switch (language) {
85
+ case "typescript":
86
+ return `${mappedInner}[]`;
87
+ case "rust":
88
+ return `Vec<${mappedInner}>`;
89
+ case "python":
90
+ return `list[${mappedInner}]`;
91
+ case "go":
92
+ return `[]${mappedInner}`;
93
+ }
94
+ }
95
+ if (type.endsWith("?")) {
96
+ const inner = type.slice(0, -1);
97
+ const mappedInner = mapTypeHelper(inner, language);
98
+ switch (language) {
99
+ case "typescript":
100
+ return `${mappedInner} | undefined`;
101
+ case "rust":
102
+ return `Option<${mappedInner}>`;
103
+ case "python":
104
+ return `Optional[${mappedInner}]`;
105
+ case "go":
106
+ return `*${mappedInner}`;
107
+ }
108
+ }
109
+ return type;
110
+ }
111
+ Handlebars.registerHelper("inferType", (fieldName, language) => {
112
+ const TYPE_INFERENCE_RULES = [
113
+ { pattern: /^id$/i, typescript: "string", rust: "String", python: "str", go: "string" },
114
+ { pattern: /_id$/i, typescript: "string", rust: "String", python: "str", go: "string" },
115
+ { pattern: /_at$/i, typescript: "Timestamp", rust: "DateTime<Utc>", python: "str", go: "time.Time" },
116
+ { pattern: /_count$/i, typescript: "number", rust: "i64", python: "int", go: "int" },
117
+ { pattern: /_amount$/i, typescript: "number", rust: "f64", python: "float", go: "float64" },
118
+ { pattern: /_price$/i, typescript: "number", rust: "f64", python: "float", go: "float64" },
119
+ { pattern: /_total$/i, typescript: "number", rust: "f64", python: "float", go: "float64" },
120
+ { pattern: /is_/i, typescript: "boolean", rust: "bool", python: "bool", go: "bool" },
121
+ { pattern: /has_/i, typescript: "boolean", rust: "bool", python: "bool", go: "bool" },
122
+ { pattern: /_status$/i, typescript: "string", rust: "String", python: "str", go: "string" },
123
+ { pattern: /_type$/i, typescript: "string", rust: "String", python: "str", go: "string" },
124
+ { pattern: /_name$/i, typescript: "string", rust: "String", python: "str", go: "string" },
125
+ { pattern: /_url$/i, typescript: "string", rust: "String", python: "str", go: "string" },
126
+ { pattern: /_email$/i, typescript: "string", rust: "String", python: "str", go: "string" },
127
+ { pattern: /_key$/i, typescript: "string", rust: "String", python: "str", go: "string" },
128
+ { pattern: /_token$/i, typescript: "string", rust: "String", python: "str", go: "string" },
129
+ { pattern: /_data$/i, typescript: "Record<string, unknown>", rust: "HashMap<String, Value>", python: "dict[str, Any]", go: "map[string]interface{}" },
130
+ { pattern: /_metadata$/i, typescript: "Record<string, unknown>", rust: "HashMap<String, Value>", python: "dict[str, Any]", go: "map[string]interface{}" },
131
+ { pattern: /_options$/i, typescript: "Record<string, unknown>", rust: "HashMap<String, Value>", python: "dict[str, Any]", go: "map[string]interface{}" },
132
+ ];
133
+ const rule = TYPE_INFERENCE_RULES.find((r) => r.pattern.test(fieldName));
134
+ if (rule) {
135
+ return rule[language] ?? "unknown";
136
+ }
137
+ return "unknown";
138
+ });
139
+ export function renderTemplate(template, context) {
140
+ const compiled = Handlebars.compile(template);
141
+ return compiled(context);
142
+ }
143
+ export function renderTemplateFile(templatePath, context) {
144
+ const template = Handlebars.compile(templatePath);
145
+ return template(context);
146
+ }
@@ -0,0 +1,124 @@
1
+ export const TYPE_MAPPINGS = [
2
+ { contractType: "string", typescript: "string", rust: "String", python: "str", go: "string" },
3
+ { contractType: "number", typescript: "number", rust: "f64", python: "float", go: "float64" },
4
+ { contractType: "boolean", typescript: "boolean", rust: "bool", python: "bool", go: "bool" },
5
+ { contractType: "null", typescript: "null", rust: "Option::None", python: "None", go: "nil" },
6
+ ];
7
+ export const TYPE_INFERENCE_RULES = [
8
+ { pattern: /^id$/i, typescript: "string", rust: "String", python: "str", go: "string" },
9
+ { pattern: /_id$/i, typescript: "string", rust: "String", python: "str", go: "string" },
10
+ { pattern: /_at$/i, typescript: "Timestamp", rust: "DateTime<Utc>", python: "str", go: "time.Time" },
11
+ { pattern: /_count$/i, typescript: "number", rust: "i64", python: "int", go: "int" },
12
+ { pattern: /_amount$/i, typescript: "number", rust: "f64", python: "float", go: "float64" },
13
+ { pattern: /_price$/i, typescript: "number", rust: "f64", python: "float", go: "float64" },
14
+ { pattern: /_total$/i, typescript: "number", rust: "f64", python: "float", go: "float64" },
15
+ { pattern: /is_/i, typescript: "boolean", rust: "bool", python: "bool", go: "bool" },
16
+ { pattern: /has_/i, typescript: "boolean", rust: "bool", python: "bool", go: "bool" },
17
+ { pattern: /_status$/i, typescript: "string", rust: "String", python: "str", go: "string" },
18
+ { pattern: /_type$/i, typescript: "string", rust: "String", python: "str", go: "string" },
19
+ { pattern: /_name$/i, typescript: "string", rust: "String", python: "str", go: "string" },
20
+ { pattern: /_url$/i, typescript: "string", rust: "String", python: "str", go: "string" },
21
+ { pattern: /_email$/i, typescript: "string", rust: "String", python: "str", go: "string" },
22
+ { pattern: /_key$/i, typescript: "string", rust: "String", python: "str", go: "string" },
23
+ { pattern: /_token$/i, typescript: "string", rust: "String", python: "str", go: "string" },
24
+ { pattern: /_data$/i, typescript: "Record<string, unknown>", rust: "HashMap<String, Value>", python: "dict[str, Any]", go: "map[string]interface{}" },
25
+ { pattern: /_metadata$/i, typescript: "Record<string, unknown>", rust: "HashMap<String, Value>", python: "dict[str, Any]", go: "map[string]interface{}" },
26
+ { pattern: /_options$/i, typescript: "Record<string, unknown>", rust: "HashMap<String, Value>", python: "dict[str, Any]", go: "map[string]interface{}" },
27
+ { pattern: /^input$/i, typescript: "unknown", rust: "Value", python: "Any", go: "interface{}" },
28
+ { pattern: /^data$/i, typescript: "unknown", rust: "Value", python: "Any", go: "interface{}" },
29
+ { pattern: /^context$/i, typescript: "Record<string, unknown>", rust: "HashMap<String, Value>", python: "dict[str, Any]", go: "map[string]interface{}" },
30
+ { pattern: /^reason$/i, typescript: "string", rust: "String", python: "str", go: "string" },
31
+ { pattern: /^currency$/i, typescript: "string", rust: "String", python: "str", go: "string" },
32
+ { pattern: /^period$/i, typescript: "string", rust: "String", python: "str", go: "string" },
33
+ { pattern: /^filters$/i, typescript: "Record<string, unknown>", rust: "HashMap<String, Value>", python: "dict[str, Any]", go: "map[string]interface{}" },
34
+ { pattern: /^code$/i, typescript: "string", rust: "String", python: "str", go: "string" },
35
+ { pattern: /^message$/i, typescript: "string", rust: "String", python: "str", go: "string" },
36
+ { pattern: /^content$/i, typescript: "string", rust: "String", python: "str", go: "string" },
37
+ { pattern: /^status$/i, typescript: "string", rust: "String", python: "str", go: "string" },
38
+ { pattern: /^method$/i, typescript: "string", rust: "String", python: "str", go: "string" },
39
+ { pattern: /^reference$/i, typescript: "string", rust: "String", python: "str", go: "string" },
40
+ { pattern: /^amount$/i, typescript: "number", rust: "f64", python: "float", go: "float64" },
41
+ { pattern: /balance/i, typescript: "number", rust: "f64", python: "float", go: "float64" },
42
+ { pattern: /[Rr]eference$/i, typescript: "string", rust: "String", python: "str", go: "string" },
43
+ { pattern: /^options$/i, typescript: "Record<string, unknown>", rust: "HashMap<String, Value>", python: "dict[str, Any]", go: "map[string]interface{}" },
44
+ ];
45
+ export function inferType(fieldName, language) {
46
+ const rule = TYPE_INFERENCE_RULES.find((r) => r.pattern.test(fieldName));
47
+ if (rule) {
48
+ return rule[language];
49
+ }
50
+ return "unknown";
51
+ }
52
+ export function mapType(type, language) {
53
+ const mapping = TYPE_MAPPINGS.find((m) => m.contractType === type);
54
+ if (mapping) {
55
+ return mapping[language];
56
+ }
57
+ if (type.endsWith("[]")) {
58
+ const inner = type.slice(0, -2);
59
+ const mappedInner = mapType(inner, language);
60
+ switch (language) {
61
+ case "typescript":
62
+ return `${mappedInner}[]`;
63
+ case "rust":
64
+ return `Vec<${mappedInner}>`;
65
+ case "python":
66
+ return `list[${mappedInner}]`;
67
+ case "go":
68
+ return `[]${mappedInner}`;
69
+ }
70
+ }
71
+ if (type.endsWith("?")) {
72
+ const inner = type.slice(0, -1);
73
+ const mappedInner = mapType(inner, language);
74
+ switch (language) {
75
+ case "typescript":
76
+ return `${mappedInner} | undefined`;
77
+ case "rust":
78
+ return `Option<${mappedInner}>`;
79
+ case "python":
80
+ return `Optional[${mappedInner}]`;
81
+ case "go":
82
+ return `*${mappedInner}`;
83
+ }
84
+ }
85
+ return type;
86
+ }
87
+ export function pascalCase(str) {
88
+ return str
89
+ .split(/[_\-.\s]+/)
90
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
91
+ .join("");
92
+ }
93
+ export function camelCase(str) {
94
+ if (str.includes("_") || str.includes("-") || str.includes(" ")) {
95
+ const pascal = pascalCase(str);
96
+ return pascal.charAt(0).toLowerCase() + pascal.slice(1);
97
+ }
98
+ return str;
99
+ }
100
+ export function snakeCase(str) {
101
+ return str
102
+ .replace(/([A-Z])/g, "_$1")
103
+ .toLowerCase()
104
+ .replace(/^_/, "")
105
+ .replace(/[_\-.\s]+/g, "_");
106
+ }
107
+ export function kebabCase(str) {
108
+ return str
109
+ .replace(/([A-Z])/g, "-$1")
110
+ .toLowerCase()
111
+ .replace(/^-/, "")
112
+ .replace(/[_\-.\s]+/g, "-");
113
+ }
114
+ export function createTemplateData(module) {
115
+ return {
116
+ module,
117
+ pascalCase,
118
+ camelCase,
119
+ snakeCase,
120
+ kebabCase,
121
+ lowerCase: (str) => str.toLowerCase(),
122
+ upperCase: (str) => str.toUpperCase(),
123
+ };
124
+ }
@@ -0,0 +1,125 @@
1
+ import { pascalCase, camelCase, mapType, inferType } from "../types.js";
2
+ export function generateTypeDefinition(type) {
3
+ const name = pascalCase(type.name);
4
+ const raw = type.raw;
5
+ if (raw.startsWith("type ")) {
6
+ return `export ${raw}`;
7
+ }
8
+ if (raw.includes("{")) {
9
+ const fields = parseTypeFields(raw);
10
+ const fieldsStr = fields.map((f) => {
11
+ const type = f.type ? `: ${mapType(f.type, "typescript")}` : `: ${inferType(f.name, "typescript")}`;
12
+ return ` ${camelCase(f.name)}${f.optional ? "?" : ""}${type};`;
13
+ }).join("\n");
14
+ return `export interface ${name} {\n${fieldsStr}\n}`;
15
+ }
16
+ const fieldsMatch = raw.match(/^\w+\s*\{\s*(.+)\s*\}$/);
17
+ if (fieldsMatch) {
18
+ const fields = fieldsMatch[1].split(",").map((f) => f.trim());
19
+ const fieldsStr = fields.map((f) => {
20
+ const optional = f.endsWith("?");
21
+ const fieldName = f.replace(/\?$/, "");
22
+ return ` ${camelCase(fieldName)}${optional ? "?" : ""}: ${inferType(fieldName, "typescript")};`;
23
+ }).join("\n");
24
+ return `export interface ${name} {\n${fieldsStr}\n}`;
25
+ }
26
+ return `export type ${name} = ${raw};`;
27
+ }
28
+ function parseTypeFields(raw) {
29
+ const fields = [];
30
+ const match = raw.match(/\{([^}]+)\}/s);
31
+ if (!match)
32
+ return fields;
33
+ const content = match[1] ?? "";
34
+ for (const line of content.split(/[,\n]/).filter((l) => l.trim())) {
35
+ const trimmed = line.trim().replace(/,\s*$/, "").replace(/\/\/.*$/, "").trim();
36
+ if (!trimmed)
37
+ continue;
38
+ const typeMatch = trimmed.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*:\s*(.+)$/);
39
+ if (typeMatch) {
40
+ fields.push({ name: typeMatch[1] ?? "", type: typeMatch[2]?.trim() ?? null, optional: false });
41
+ continue;
42
+ }
43
+ const optionalMatch = trimmed.match(/^([A-Za-z_][A-Za-z0-9_]*)\?\s*:\s*(.+)$/);
44
+ if (optionalMatch) {
45
+ fields.push({ name: optionalMatch[1] ?? "", type: optionalMatch[2]?.trim() ?? null, optional: true });
46
+ continue;
47
+ }
48
+ const simpleMatch = trimmed.match(/^([A-Za-z_][A-Za-z0-9_]*)$/);
49
+ if (simpleMatch) {
50
+ fields.push({ name: simpleMatch[1] ?? "", type: null, optional: trimmed.endsWith("?") });
51
+ }
52
+ }
53
+ return fields;
54
+ }
55
+ export function generateFunctionSignature(fn) {
56
+ const params = fn.params.map((p) => {
57
+ const type = p.type ? `: ${mapType(p.type, "typescript")}` : `: ${inferType(p.name, "typescript")}`;
58
+ return `${camelCase(p.name)}${p.optional ? "?" : ""}${type}`;
59
+ }).join(", ");
60
+ return ` ${camelCase(fn.name)}(${params}): Promise<${mapType(fn.returns, "typescript")}>;`;
61
+ }
62
+ export function generateParamsList(fn) {
63
+ return fn.params.map((p) => {
64
+ const type = p.type ? `: ${mapType(p.type, "typescript")}` : `: ${inferType(p.name, "typescript")}`;
65
+ return `${camelCase(p.name)}${p.optional ? "?" : ""}${type}`;
66
+ }).join(", ");
67
+ }
68
+ const SHARED_TYPES = `// Shared types used across all contracts
69
+ export type Timestamp = string;
70
+ export type UserId = string;
71
+ export type EntityId = string;
72
+
73
+ export interface PaginatedResult<T> {
74
+ data: T[];
75
+ cursor: string | null;
76
+ hasMore: boolean;
77
+ total?: number;
78
+ }
79
+ `;
80
+ export function generateSharedTypes() {
81
+ return SHARED_TYPES;
82
+ }
83
+ export function generateIndex(moduleNames) {
84
+ const lines = [
85
+ "// Auto-generated module index",
86
+ "",
87
+ "export * from './shared.js';",
88
+ "",
89
+ ];
90
+ for (const name of moduleNames.sort()) {
91
+ lines.push(`export * from './${name}.js';`);
92
+ }
93
+ return lines.join("\n");
94
+ }
95
+ export function getSdkHint(adapterName, functionName) {
96
+ const hints = {
97
+ stripe: {
98
+ initiatePayment: "const paymentIntent = await this.stripe.paymentIntents.create({\n amount: Math.round(amount * 100),\n currency: currency.toLowerCase(),\n payment_method: method,\n metadata: { orderId },\n});\nreturn this.toPayment(paymentIntent);",
99
+ verifyPayment: "const paymentIntent = await this.stripe.paymentIntents.retrieve(paymentId);\nreturn this.toPayment(paymentIntent);",
100
+ getPaymentByOrder: "const paymentIntents = await this.stripe.paymentIntents.list({\n limit: 1,\n query: `metadata['orderId']:'${orderId}'`,\n});\nif (paymentIntents.data.length === 0) return undefined;\nreturn this.toPayment(paymentIntents.data[0]);",
101
+ initiateRefund: "const refund = await this.stripe.refunds.create({\n payment_intent: paymentId,\n amount: amount ? Math.round(amount * 100) : undefined,\n reason: reason as any,\n});\nreturn { id: refund.id, paymentId, amount: refund.amount / 100, status: refund.status, reason: reason || '', createdAt: new Date(refund.created * 1000).toISOString() };",
102
+ getRefundByOrder: "const payment = await this.getPaymentByOrder(orderId);\nif (!payment) return undefined;\nreturn this.getRefund(payment.id);",
103
+ getRefund: "const refund = await this.stripe.refunds.retrieve(refundId);\nreturn { id: refund.id, paymentId: refund.payment_intent as string, amount: refund.amount / 100, status: refund.status, reason: refund.reason || '', createdAt: new Date(refund.created * 1000).toISOString() };",
104
+ },
105
+ redis: {
106
+ get: "const result = await this.redis.get(key);\nif (!result) return null;\nreturn JSON.parse(result);",
107
+ set: "const serialized = JSON.stringify(value);\nif (ttl) {\n await this.redis.set(key, serialized, 'EX', ttl);\n} else {\n await this.redis.set(key, serialized);\n}",
108
+ delete: "await this.redis.del(key);",
109
+ exists: "const result = await this.redis.exists(key);\nreturn result === 1;",
110
+ expire: "await this.redis.expire(key, seconds);",
111
+ ttl: "return await this.redis.ttl(key);",
112
+ },
113
+ bullmq: {
114
+ enqueue: "return await this.queue.add(name, data, { attempts: 3, backoff: { type: 'exponential', delay: 1000 } });",
115
+ dequeue: "const job = await this.queue.getNext();\nif (!job) return null;\nreturn job.data;",
116
+ peek: "const job = await this.queue.getJob(jobId);\nif (!job) return null;\nreturn job.data;",
117
+ size: "return await this.queue.getWaitingCount();",
118
+ remove: "await this.queue.remove(jobId);",
119
+ },
120
+ sendgrid: { sendEmail: "await this.sg.send({ to, from, subject, html: body, text: body });" },
121
+ resend: { sendEmail: "const result = await this.resend.emails.send({ from, to, subject, html: body });\nreturn { messageId: result.id, status: 'sent' };" },
122
+ twilio: { sendSMS: "const message = await this.twilio.messages.create({ to, from, body });\nreturn { messageId: message.sid, status: message.status };" },
123
+ };
124
+ return hints[adapterName]?.[functionName] ?? null;
125
+ }
@@ -0,0 +1,206 @@
1
+ import { pascalCase, camelCase, mapType } from "../types.js";
2
+ import { generateTypeDefinition, generateFunctionSignature, generateParamsList, generateIndex, getSdkHint, generateSharedTypes } from "./helpers.js";
3
+ const SDK_IMPORTS = {
4
+ stripe: `import Stripe from 'stripe';`,
5
+ redis: `import { createClient } from 'redis';`,
6
+ bullmq: `import { Queue, Worker, Job } from 'bullmq';`,
7
+ sendgrid: `import sgMail from '@sendgrid/mail';`,
8
+ resend: `import { Resend } from 'resend';`,
9
+ twilio: `import twilio from 'twilio';`,
10
+ memcached: `import Memcached from 'memcached';`,
11
+ sqs: `import { SQSClient, SendMessageCommand } from '@aws-sdk/client-sqs';`,
12
+ sentry: `import * as Sentry from '@sentry/node';`,
13
+ clerk: `import { createClerkClient } from '@clerk/clerk-sdk-node';`,
14
+ sift: `import { SiftClient } from 'sift';`,
15
+ cloudinary: `import { v2 as cloudinary } from 'cloudinary';`,
16
+ algolia: `import { algoliasearch } from 'algoliasearch';`,
17
+ };
18
+ function getSdkImport(adapterName) {
19
+ return SDK_IMPORTS[adapterName] || `// TODO: import ${adapterName} SDK`;
20
+ }
21
+ export class TypeScriptGenerator {
22
+ language = "typescript";
23
+ name = "TypeScript Generator";
24
+ generateInterfaces(context) {
25
+ const files = [];
26
+ const errors = [];
27
+ let modules = this.resolveModules(context);
28
+ files.push({ path: "interfaces/shared.ts", content: generateSharedTypes() });
29
+ for (const mod of modules) {
30
+ try {
31
+ files.push({ path: `interfaces/${mod.name}.ts`, content: this.generateModuleInterface(mod) });
32
+ }
33
+ catch (error) {
34
+ errors.push(`Failed to generate interface for ${mod.name}: ${error instanceof Error ? error.message : error}`);
35
+ }
36
+ }
37
+ files.push({ path: "interfaces/index.ts", content: generateIndex(modules.map((m) => m.name)) });
38
+ return { files, errors };
39
+ }
40
+ generateAdapter(context) {
41
+ const files = [];
42
+ const errors = [];
43
+ const adapters = context.adapters.filter((a) => {
44
+ if (context.module && a.module !== context.module)
45
+ return false;
46
+ if (context.provider && a.name !== context.provider)
47
+ return false;
48
+ return true;
49
+ });
50
+ for (const adapter of adapters) {
51
+ try {
52
+ const mod = context.catalog.modules.find((m) => m.name === adapter.module);
53
+ if (!mod) {
54
+ errors.push(`Module ${adapter.module} not found for adapter ${adapter.name}`);
55
+ continue;
56
+ }
57
+ files.push({ path: `adapters/${adapter.module}/${adapter.name}.ts`, content: this.generateAdapterClass(adapter, mod) });
58
+ }
59
+ catch (error) {
60
+ errors.push(`Failed to generate adapter ${adapter.name}: ${error instanceof Error ? error.message : error}`);
61
+ }
62
+ }
63
+ return { files, errors };
64
+ }
65
+ generateTests(context) {
66
+ const files = [];
67
+ const errors = [];
68
+ const adapters = context.adapters.filter((a) => {
69
+ if (context.module && a.module !== context.module)
70
+ return false;
71
+ if (context.provider && a.name !== context.provider)
72
+ return false;
73
+ return true;
74
+ });
75
+ for (const adapter of adapters) {
76
+ try {
77
+ const mod = context.catalog.modules.find((m) => m.name === adapter.module);
78
+ if (!mod) {
79
+ errors.push(`Module ${adapter.module} not found for adapter ${adapter.name}`);
80
+ continue;
81
+ }
82
+ files.push({ path: `__tests__/${adapter.module}/${adapter.name}.test.ts`, content: this.generateConformanceTest(adapter, mod) });
83
+ }
84
+ catch (error) {
85
+ errors.push(`Failed to generate test for ${adapter.name}: ${error instanceof Error ? error.message : error}`);
86
+ }
87
+ }
88
+ return { files, errors };
89
+ }
90
+ resolveModules(context) {
91
+ let modules = context.module
92
+ ? context.catalog.modules.filter((m) => m.name === context.module)
93
+ : context.catalog.modules;
94
+ if (context.module) {
95
+ const targetMod = context.catalog.modules.find((m) => m.name === context.module);
96
+ if (targetMod) {
97
+ for (const dep of targetMod.hardDeps) {
98
+ const depMod = context.catalog.modules.find((m) => m.name === dep);
99
+ if (depMod && !modules.some((m) => m.name === dep))
100
+ modules = [...modules, depMod];
101
+ }
102
+ }
103
+ }
104
+ return modules;
105
+ }
106
+ generateModuleInterface(mod) {
107
+ const versionNote = mod.version ? `v${mod.version}` : "version not specified";
108
+ const lines = [
109
+ `// ${mod.name}.ts — ${versionNote} — contracts/${mod.name}.md`,
110
+ `// Auto-generated from contracts/${mod.name}.md`,
111
+ `// Types are inferred from naming conventions. Review before production use.`,
112
+ "",
113
+ ];
114
+ for (const type of mod.types) {
115
+ lines.push(generateTypeDefinition(type));
116
+ lines.push("");
117
+ }
118
+ const interfaceName = `${pascalCase(mod.name)}Contract`;
119
+ lines.push(`export interface ${interfaceName} {`);
120
+ for (const fn of mod.functions)
121
+ lines.push(generateFunctionSignature(fn));
122
+ lines.push("}");
123
+ return lines.join("\n");
124
+ }
125
+ generateAdapterClass(adapter, mod) {
126
+ const lines = [
127
+ `// ${adapter.name}.ts`,
128
+ `// Auto-generated adapter for ${adapter.name} → ${mod.name}`,
129
+ `// Types are inferred from naming conventions. Review before production use.`,
130
+ "",
131
+ getSdkImport(adapter.name),
132
+ `import type { ${pascalCase(mod.name)}Contract } from '../interfaces/${mod.name}';`,
133
+ "",
134
+ ];
135
+ const className = `${pascalCase(adapter.name)}Adapter`;
136
+ const configFields = adapter.config.required.map((f) => ` ${f.name}: ${mapType(f.type, "typescript")};`).join("\n");
137
+ lines.push(`export class ${className} implements ${pascalCase(mod.name)}Contract {`);
138
+ lines.push(` constructor(private config: {`);
139
+ lines.push(configFields);
140
+ lines.push(` }) {}`);
141
+ lines.push("");
142
+ for (const fn of mod.functions) {
143
+ if (adapter.implements.includes(fn.name)) {
144
+ lines.push(this.generateAdapterMethod(fn, adapter.name));
145
+ }
146
+ else {
147
+ const notSupportedMessage = adapter.does_not_implement?.includes(fn.name)
148
+ ? `Not supported by ${adapter.name}: ${fn.name}`
149
+ : `Not yet implemented: ${fn.name}`;
150
+ lines.push(this.generateUnimplementedMethod(fn, notSupportedMessage));
151
+ }
152
+ }
153
+ lines.push("}");
154
+ return lines.join("\n");
155
+ }
156
+ generateAdapterMethod(fn, adapterName) {
157
+ const lines = [];
158
+ lines.push(` async ${camelCase(fn.name)}(${generateParamsList(fn)}): Promise<${mapType(fn.returns, "typescript")}> {`);
159
+ const hint = getSdkHint(adapterName, fn.name);
160
+ if (hint) {
161
+ const hintLines = hint.split("\n");
162
+ for (const hintLine of hintLines) {
163
+ lines.push(` ${hintLine}`);
164
+ }
165
+ }
166
+ else {
167
+ lines.push(` // TODO: Implement ${fn.name}`);
168
+ if (fn.returns !== "void") {
169
+ lines.push(` throw new Error('Not implemented: ${fn.name}');`);
170
+ }
171
+ }
172
+ lines.push(` }`);
173
+ return lines.join("\n");
174
+ }
175
+ generateUnimplementedMethod(fn, message) {
176
+ return ` async ${camelCase(fn.name)}(${generateParamsList(fn)}): Promise<${mapType(fn.returns, "typescript")}> {\n throw new Error('${message}');\n }`;
177
+ }
178
+ generateConformanceTest(adapter, mod) {
179
+ const className = `${pascalCase(adapter.name)}Adapter`;
180
+ const interfaceName = `${pascalCase(mod.name)}Contract`;
181
+ const lines = [
182
+ `// ${adapter.name}.test.ts`,
183
+ `// Auto-generated conformance test for ${adapter.name} → ${mod.name}`,
184
+ "",
185
+ `import { ${className} } from '../adapters/${mod.name}/${adapter.name}';`,
186
+ `import type { ${interfaceName} } from '../interfaces/${mod.name}';`,
187
+ "",
188
+ `describe('${className} implements ${interfaceName}', () => {`,
189
+ ` const adapter: ${interfaceName} = new ${className}({`,
190
+ ];
191
+ const testConfigs = adapter.config.required.map((f) => {
192
+ const val = f.type === "number" ? 0 : "false";
193
+ return ` ${f.name}: 'test'`;
194
+ }).join(",\n");
195
+ lines.push(testConfigs);
196
+ lines.push(` });`);
197
+ lines.push("");
198
+ for (const fn of mod.functions) {
199
+ lines.push(` it('has ${fn.name} method', () => {`);
200
+ lines.push(` expect(typeof adapter.${camelCase(fn.name)}).toBe('function');`);
201
+ lines.push(` });`);
202
+ }
203
+ lines.push(`});`);
204
+ return lines.join("\n");
205
+ }
206
+ }
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ // Engineering Blueprinter - Public API
2
+ // Import this library as: import { loadCatalog } from '@friehub/blueprint'
3
+ export { loadCatalogFromRoot } from "./core/load-catalog.js";
4
+ export { loadAdapters, loadAdapter } from "./core/adapters/load.js";
5
+ export { resolve as resolveDeps, detectCycles } from "./core/resolve.js";
6
+ export { searchModules } from "./core/search.js";
7
+ export { buildGraph } from "./core/graph.js";
8
+ export { implicitCores } from "./core/catalog.js";