bonescript-compiler 0.8.0 → 0.11.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/dist/cli.js +134 -14
- package/dist/cli.js.map +1 -1
- package/dist/emit_colyseus.d.ts +26 -0
- package/dist/emit_colyseus.js +812 -0
- package/dist/emit_colyseus.js.map +1 -0
- package/dist/emit_graphql.js +19 -7
- package/dist/emit_graphql.js.map +1 -1
- package/dist/emit_notify.js +36 -1
- package/dist/emit_notify.js.map +1 -1
- package/dist/emit_prisma.js +10 -2
- package/dist/emit_prisma.js.map +1 -1
- package/dist/emit_sqlite.d.ts +42 -1
- package/dist/emit_sqlite.js +375 -51
- package/dist/emit_sqlite.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/lowering.js +104 -8
- package/dist/lowering.js.map +1 -1
- package/package.json +11 -2
- package/src/cli.ts +142 -15
- package/src/emit_colyseus.ts +867 -0
- package/src/emit_graphql.ts +18 -7
- package/src/emit_notify.ts +36 -1
- package/src/emit_prisma.ts +10 -2
- package/src/emit_sqlite.ts +388 -52
- package/src/index.ts +1 -0
- package/src/lowering.ts +107 -9
- package/dist/test_notify.d.ts +0 -11
- package/dist/test_notify.js +0 -220
- package/dist/test_notify.js.map +0 -1
- package/dist/test_react.d.ts +0 -10
- package/dist/test_react.js +0 -177
- package/dist/test_react.js.map +0 -1
- package/dist/test_sqlite.d.ts +0 -13
- package/dist/test_sqlite.js +0 -262
- package/dist/test_sqlite.js.map +0 -1
package/src/emit_graphql.ts
CHANGED
|
@@ -142,20 +142,31 @@ export function emitGraphQLSchema(system: IR.IRSystem): string {
|
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
// ─── type Query ───
|
|
145
|
+
// GraphQL spec requires every schema to define a non-empty Query type.
|
|
146
|
+
// For systems with no API modules (event-only or channel-only), emit a
|
|
147
|
+
// placeholder _service field so the schema parses.
|
|
145
148
|
lines.push(`type Query {`);
|
|
146
|
-
|
|
147
|
-
lines.push(
|
|
149
|
+
if (queryFields.length === 0) {
|
|
150
|
+
lines.push(` _service: String!`);
|
|
151
|
+
} else {
|
|
152
|
+
for (const f of queryFields) {
|
|
153
|
+
lines.push(f);
|
|
154
|
+
}
|
|
148
155
|
}
|
|
149
156
|
lines.push(`}`);
|
|
150
157
|
lines.push(``);
|
|
151
158
|
|
|
152
159
|
// ─── type Mutation ───
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
160
|
+
// Mutation is technically optional in GraphQL, but emitting an empty body
|
|
161
|
+
// is a syntax error. Skip the type entirely if there's nothing to mutate.
|
|
162
|
+
if (mutationFields.length > 0) {
|
|
163
|
+
lines.push(`type Mutation {`);
|
|
164
|
+
for (const f of mutationFields) {
|
|
165
|
+
lines.push(f);
|
|
166
|
+
}
|
|
167
|
+
lines.push(`}`);
|
|
168
|
+
lines.push(``);
|
|
156
169
|
}
|
|
157
|
-
lines.push(`}`);
|
|
158
|
-
lines.push(``);
|
|
159
170
|
|
|
160
171
|
return lines.join("\n");
|
|
161
172
|
}
|
package/src/emit_notify.ts
CHANGED
|
@@ -97,6 +97,33 @@ export function emitNotifyService(system: IR.IRSystem): string {
|
|
|
97
97
|
lines.push(` return createHmac("sha256", WEBHOOK_SECRET).update(body).digest("hex");`);
|
|
98
98
|
lines.push(`}`);
|
|
99
99
|
lines.push(``);
|
|
100
|
+
// Private-host detection. Catches loopback (127/8, ::1, 0.0.0.0), RFC1918
|
|
101
|
+
// (10/8, 172.16/12, 192.168/16), link-local (169.254/16, fe80::/10) and
|
|
102
|
+
// unique-local IPv6 (fc00::/7). DNS hostnames that resolve to private
|
|
103
|
+
// addresses are not blocked here — that requires a DNS lookup and a
|
|
104
|
+
// dual-stack check; we leave that to the deployer's network policy.
|
|
105
|
+
lines.push(`function isPrivateHost(host: string): boolean {`);
|
|
106
|
+
lines.push(` const h = host.toLowerCase();`);
|
|
107
|
+
lines.push(` if (h === "localhost" || h === "0.0.0.0" || h === "::" || h === "::1") return true;`);
|
|
108
|
+
lines.push(` // IPv4 dotted quad`);
|
|
109
|
+
lines.push(` const m4 = h.match(/^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$/);`);
|
|
110
|
+
lines.push(` if (m4) {`);
|
|
111
|
+
lines.push(` const a = Number(m4[1]), b = Number(m4[2]);`);
|
|
112
|
+
lines.push(` if (a === 127) return true; // loopback`);
|
|
113
|
+
lines.push(` if (a === 10) return true; // RFC1918`);
|
|
114
|
+
lines.push(` if (a === 172 && b >= 16 && b <= 31) return true; // RFC1918`);
|
|
115
|
+
lines.push(` if (a === 192 && b === 168) return true; // RFC1918`);
|
|
116
|
+
lines.push(` if (a === 169 && b === 254) return true; // link-local / cloud metadata`);
|
|
117
|
+
lines.push(` if (a === 0) return true;`);
|
|
118
|
+
lines.push(` return false;`);
|
|
119
|
+
lines.push(` }`);
|
|
120
|
+
lines.push(` // IPv6 — strip optional brackets and zone suffix.`);
|
|
121
|
+
lines.push(` const v6 = h.replace(/^\\[|\\]$/g, "").split("%")[0];`);
|
|
122
|
+
lines.push(` if (/^fe[89ab][0-9a-f]?:/i.test(v6)) return true; // link-local fe80::/10`);
|
|
123
|
+
lines.push(` if (/^f[cd][0-9a-f]?:/i.test(v6)) return true; // unique-local fc00::/7`);
|
|
124
|
+
lines.push(` return false;`);
|
|
125
|
+
lines.push(`}`);
|
|
126
|
+
lines.push(``);
|
|
100
127
|
lines.push(`/**`);
|
|
101
128
|
lines.push(` * Send a JSON payload to NOTIFY_WEBHOOK_URL.`);
|
|
102
129
|
lines.push(` *`);
|
|
@@ -113,13 +140,21 @@ export function emitNotifyService(system: IR.IRSystem): string {
|
|
|
113
140
|
lines.push(` if (!WEBHOOK_URL) {`);
|
|
114
141
|
lines.push(` throw new Error("NOTIFY_WEBHOOK_URL is not configured");`);
|
|
115
142
|
lines.push(` }`);
|
|
116
|
-
lines.push(` // Validate URL — only http(s) and reject
|
|
143
|
+
lines.push(` // Validate URL — only http(s), and reject loopback / RFC1918 /`);
|
|
144
|
+
lines.push(` // link-local hosts to make this server unusable as an SSRF probe.`);
|
|
145
|
+
lines.push(` // Set NOTIFY_WEBHOOK_ALLOW_PRIVATE=1 to opt out (e.g. for internal CI`);
|
|
146
|
+
lines.push(` // setups where the webhook receiver is on the same network).`);
|
|
117
147
|
lines.push(` let url: URL;`);
|
|
118
148
|
lines.push(` try { url = new URL(WEBHOOK_URL); }`);
|
|
119
149
|
lines.push(` catch { throw new Error("Invalid NOTIFY_WEBHOOK_URL"); }`);
|
|
120
150
|
lines.push(` if (url.protocol !== "https:" && url.protocol !== "http:") {`);
|
|
121
151
|
lines.push(` throw new Error(\`Webhook URL protocol must be http(s), got \${url.protocol}\`);`);
|
|
122
152
|
lines.push(` }`);
|
|
153
|
+
lines.push(` if (process.env.NOTIFY_WEBHOOK_ALLOW_PRIVATE !== "1") {`);
|
|
154
|
+
lines.push(` if (isPrivateHost(url.hostname)) {`);
|
|
155
|
+
lines.push(` throw new Error(\`Webhook URL host is loopback / private / link-local: \${url.hostname}. Set NOTIFY_WEBHOOK_ALLOW_PRIVATE=1 to allow.\`);`);
|
|
156
|
+
lines.push(` }`);
|
|
157
|
+
lines.push(` }`);
|
|
123
158
|
lines.push(` const body = JSON.stringify(payload);`);
|
|
124
159
|
lines.push(` const headers: Record<string, string> = { "Content-Type": "application/json" };`);
|
|
125
160
|
lines.push(` const sig = signPayload(body);`);
|
package/src/emit_prisma.ts
CHANGED
|
@@ -50,8 +50,13 @@ function toPrismaNativeType(irType: string): string | null {
|
|
|
50
50
|
case "bytes": return null;
|
|
51
51
|
case "timestamp": return "@db.Timestamptz";
|
|
52
52
|
case "float": return "@db.DoublePrecision";
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
// BoneScript `uint` and `int` map to Prisma `Int`. Prisma rejects
|
|
54
|
+
// `Int @db.BigInt` (BigInt requires the Prisma `BigInt` type), so we use
|
|
55
|
+
// no native type and let Prisma map to the default Postgres `INTEGER`.
|
|
56
|
+
// If a caller needs 64-bit ints they should use the `int` IR type with a
|
|
57
|
+
// future `@db.bigint` annotation — TBD.
|
|
58
|
+
case "uint": return null;
|
|
59
|
+
case "int": return null;
|
|
55
60
|
default: return null;
|
|
56
61
|
}
|
|
57
62
|
}
|
|
@@ -253,6 +258,9 @@ export class PrismaEmitter {
|
|
|
253
258
|
} else if (field.name === "created_at" || (field.type === "timestamp" && field.name.includes("created"))) {
|
|
254
259
|
attrs.push("@default(now())");
|
|
255
260
|
} else if (field.name === "updated_at") {
|
|
261
|
+
// @updatedAt only fires on update — we also need a default for create
|
|
262
|
+
// or the NOT NULL column has no value at INSERT time.
|
|
263
|
+
attrs.push("@default(now())");
|
|
256
264
|
attrs.push("@updatedAt");
|
|
257
265
|
} else if (field.default_value) {
|
|
258
266
|
const dv = this.mapDefaultValue(field.default_value, field.type);
|