@supacloud/compiler 0.13.0 → 0.14.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/README.md +74 -2
- package/dist/cli.js +275 -99
- package/dist/config.d.ts +11 -1
- package/dist/graphql-client.d.ts +1 -1
- package/dist/graphql-runtime.d.ts +5 -0
- package/dist/index.js +230 -54
- package/package.json +3 -3
package/dist/config.d.ts
CHANGED
|
@@ -10,10 +10,15 @@ export interface SupaCloudConfig {
|
|
|
10
10
|
generateClient?: boolean;
|
|
11
11
|
generatePermissions?: boolean;
|
|
12
12
|
moduleBoundaryPreset?: ModuleBoundaryPresetName;
|
|
13
|
+
moduleBoundaries?: NonNullable<CompileOptions["moduleBoundaries"]>;
|
|
14
|
+
typeSafety?: NonNullable<CompileOptions["typeSafety"]>;
|
|
15
|
+
allowRouteCommandBindings?: boolean;
|
|
16
|
+
disallowControllerDirectDb?: boolean;
|
|
17
|
+
detectOrphanModules?: boolean;
|
|
13
18
|
commandCapabilities?: CommandExecutionCapabilities;
|
|
14
19
|
treeShakeUnusedProviders?: boolean;
|
|
15
20
|
}
|
|
16
|
-
export declare const DEFAULT_SUPACLOUD_CONFIG: Required<Omit<SupaCloudConfig, "include" | "moduleBoundaryPreset" | "commandCapabilities">> & {
|
|
21
|
+
export declare const DEFAULT_SUPACLOUD_CONFIG: Required<Omit<SupaCloudConfig, "include" | "moduleBoundaryPreset" | "commandCapabilities" | "moduleBoundaries" | "typeSafety" | "allowRouteCommandBindings" | "disallowControllerDirectDb" | "detectOrphanModules">> & {
|
|
17
22
|
include: string[];
|
|
18
23
|
moduleBoundaryPreset: ModuleBoundaryPresetName;
|
|
19
24
|
};
|
|
@@ -28,6 +33,11 @@ export declare function resolveSupacloudConfig(config?: SupaCloudConfig, cwd?: s
|
|
|
28
33
|
generatePermissions: boolean;
|
|
29
34
|
moduleBoundaryPreset: ModuleBoundaryPresetName;
|
|
30
35
|
commandCapabilities?: CommandExecutionCapabilities;
|
|
36
|
+
moduleBoundaries?: NonNullable<CompileOptions["moduleBoundaries"]>;
|
|
37
|
+
typeSafety?: NonNullable<CompileOptions["typeSafety"]>;
|
|
38
|
+
allowRouteCommandBindings?: boolean;
|
|
39
|
+
disallowControllerDirectDb?: boolean;
|
|
40
|
+
detectOrphanModules?: boolean;
|
|
31
41
|
treeShakeUnusedProviders: boolean;
|
|
32
42
|
graphql?: GraphqlOptions;
|
|
33
43
|
};
|
package/dist/graphql-client.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Appended to the generated SDK; browser consumers need only the platform fetch API. */
|
|
2
|
-
export declare const GRAPHQL_CLIENT_SOURCE = "\nexport interface GraphqlClientOptions {\n /** Project base URL, not the management API URL. HTTPS is required outside loopback. */\n url: string;\n /** Public project key only. Never put a service-role or management key in a browser. */\n publishableKey?: string;\n /** Resolved on every request so session refresh and logout are observed. */\n getAccessToken?: () => string | null | undefined | Promise<string | null | undefined>;\n fetch?: (url: string, init: RequestInit) => Promise<Response>;\n}\n\nexport interface GraphqlRequestOptions {\n signal?: AbortSignal;\n}\n\nexport class GraphqlRequestError extends Error {\n constructor(\n public readonly code: \"http\" | \"graphql\" | \"invalid-response\",\n message: string,\n public readonly status?: number,\n public readonly errors?: readonly unknown[],\n ) {\n super(message);\n this.name = \"GraphqlRequestError\";\n }\n}\n\nexport function createGraphqlClient(options: GraphqlClientOptions) {\n const endpoint = new URL(options.url);\n const loopback = [\"localhost\", \"127.0.0.1\", \"[::1]\"].includes(endpoint.hostname);\n if (endpoint.protocol !== \"https:\" && !(endpoint.protocol === \"http:\" && loopback)) {\n throw new Error(\"GraphQL requires HTTPS outside loopback development.\");\n }\n if (endpoint.username || endpoint.password || endpoint.search || endpoint.hash) {\n throw new Error(\"GraphQL project URLs must not contain credentials, query parameters or fragments.\");\n }\n endpoint.pathname = endpoint.pathname.replace(/\\/$/, \"\") + \"/graphql/v1\";\n const fetcher = options.fetch ?? globalThis.fetch.bind(globalThis);\n return getSdk<GraphqlRequestOptions>(async
|
|
2
|
+
export declare const GRAPHQL_CLIENT_SOURCE = "\nexport interface GraphqlClientOptions {\n /** Project base URL, not the management API URL. HTTPS is required outside loopback. */\n url: string;\n /** Public project key only. Never put a service-role or management key in a browser. */\n publishableKey?: string;\n /** Resolved on every request so session refresh and logout are observed. */\n getAccessToken?: () => string | null | undefined | Promise<string | null | undefined>;\n fetch?: (url: string, init: RequestInit) => Promise<Response>;\n}\n\nexport interface GraphqlRequestOptions {\n signal?: AbortSignal;\n}\n\nexport class GraphqlRequestError extends Error {\n constructor(\n public readonly code: \"http\" | \"graphql\" | \"invalid-response\",\n message: string,\n public readonly status?: number,\n public readonly errors?: readonly unknown[],\n ) {\n super(message);\n this.name = \"GraphqlRequestError\";\n }\n}\n\nexport function createGraphqlClient(options: GraphqlClientOptions) {\n const endpoint = new URL(options.url);\n const loopback = [\"localhost\", \"127.0.0.1\", \"[::1]\"].includes(endpoint.hostname);\n if (endpoint.protocol !== \"https:\" && !(endpoint.protocol === \"http:\" && loopback)) {\n throw new Error(\"GraphQL requires HTTPS outside loopback development.\");\n }\n if (endpoint.username || endpoint.password || endpoint.search || endpoint.hash) {\n throw new Error(\"GraphQL project URLs must not contain credentials, query parameters or fragments.\");\n }\n endpoint.pathname = endpoint.pathname.replace(/\\/$/, \"\") + \"/graphql/v1\";\n const fetcher = options.fetch ?? globalThis.fetch.bind(globalThis);\n return getSdk<GraphqlRequestOptions>(async (\n query: string,\n variables?: unknown,\n request?: GraphqlRequestOptions,\n ): Promise<unknown> => {\n const headers = new Headers({ \"Content-Type\": \"application/json\", Accept: \"application/json\" });\n if (options.publishableKey) headers.set(\"apikey\", options.publishableKey);\n const token = await options.getAccessToken?.();\n if (token) headers.set(\"Authorization\", \"Bearer \" + token);\n const response = await fetcher(endpoint.toString(), {\n method: \"POST\",\n headers,\n body: JSON.stringify({ query, variables }),\n ...(request?.signal ? { signal: request.signal } : {}),\n redirect: \"error\",\n });\n if (!response.ok) {\n throw new GraphqlRequestError(\"http\", \"GraphQL HTTP request failed (\" + response.status + \").\", response.status);\n }\n let envelope: unknown;\n try {\n envelope = await response.json();\n } catch {\n throw new GraphqlRequestError(\"invalid-response\", \"GraphQL returned invalid JSON.\", response.status);\n }\n if (!envelope || typeof envelope !== \"object\" || Array.isArray(envelope)) {\n throw new GraphqlRequestError(\"invalid-response\", \"GraphQL returned an invalid envelope.\", response.status);\n }\n if (\"errors\" in envelope) {\n if (!Array.isArray(envelope.errors)) {\n throw new GraphqlRequestError(\"invalid-response\", \"GraphQL returned invalid errors.\", response.status);\n }\n if (envelope.errors.length > 0) {\n throw new GraphqlRequestError(\"graphql\", \"GraphQL query failed.\", response.status, envelope.errors);\n }\n }\n if (!(\"data\" in envelope) || !envelope.data || typeof envelope.data !== \"object\" || Array.isArray(envelope.data)) {\n throw new GraphqlRequestError(\"invalid-response\", \"GraphQL returned no result object.\", response.status);\n }\n // The operation-specific parser in getSdk validates selected field values.\n return envelope.data;\n });\n}\n";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project the standard Codegen result types, rather than independently interpreting
|
|
3
|
+
* selections, fragments and conditional fields. Unsupported wire types fail closed.
|
|
4
|
+
*/
|
|
5
|
+
export declare function renderGraphqlValidators(source: string, operationNames: readonly string[]): string;
|
package/dist/index.js
CHANGED
|
@@ -1212,11 +1212,11 @@ export function createGraphqlClient(options: GraphqlClientOptions) {
|
|
|
1212
1212
|
}
|
|
1213
1213
|
endpoint.pathname = endpoint.pathname.replace(/\\/$/, "") + "/graphql/v1";
|
|
1214
1214
|
const fetcher = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
1215
|
-
return getSdk<GraphqlRequestOptions>(async
|
|
1215
|
+
return getSdk<GraphqlRequestOptions>(async (
|
|
1216
1216
|
query: string,
|
|
1217
|
-
variables?:
|
|
1217
|
+
variables?: unknown,
|
|
1218
1218
|
request?: GraphqlRequestOptions,
|
|
1219
|
-
): Promise<
|
|
1219
|
+
): Promise<unknown> => {
|
|
1220
1220
|
const headers = new Headers({ "Content-Type": "application/json", Accept: "application/json" });
|
|
1221
1221
|
if (options.publishableKey) headers.set("apikey", options.publishableKey);
|
|
1222
1222
|
const token = await options.getAccessToken?.();
|
|
@@ -1225,7 +1225,7 @@ export function createGraphqlClient(options: GraphqlClientOptions) {
|
|
|
1225
1225
|
method: "POST",
|
|
1226
1226
|
headers,
|
|
1227
1227
|
body: JSON.stringify({ query, variables }),
|
|
1228
|
-
signal: request
|
|
1228
|
+
...(request?.signal ? { signal: request.signal } : {}),
|
|
1229
1229
|
redirect: "error",
|
|
1230
1230
|
});
|
|
1231
1231
|
if (!response.ok) {
|
|
@@ -1251,8 +1251,8 @@ export function createGraphqlClient(options: GraphqlClientOptions) {
|
|
|
1251
1251
|
if (!("data" in envelope) || !envelope.data || typeof envelope.data !== "object" || Array.isArray(envelope.data)) {
|
|
1252
1252
|
throw new GraphqlRequestError("invalid-response", "GraphQL returned no result object.", response.status);
|
|
1253
1253
|
}
|
|
1254
|
-
//
|
|
1255
|
-
return envelope.data
|
|
1254
|
+
// The operation-specific parser in getSdk validates selected field values.
|
|
1255
|
+
return envelope.data;
|
|
1256
1256
|
});
|
|
1257
1257
|
}
|
|
1258
1258
|
`;
|
|
@@ -1326,6 +1326,165 @@ var init_graphql_inputs = __esm(() => {
|
|
|
1326
1326
|
init_graphql_options();
|
|
1327
1327
|
});
|
|
1328
1328
|
|
|
1329
|
+
// src/graphql-runtime.ts
|
|
1330
|
+
import * as ts7 from "@typescript/typescript6";
|
|
1331
|
+
import { resolve as resolve5 } from "node:path";
|
|
1332
|
+
function renderGraphqlValidators(source, operationNames) {
|
|
1333
|
+
const fileName = resolve5("/__supacloud_graphql__/contracts.ts");
|
|
1334
|
+
const options = {
|
|
1335
|
+
strict: true,
|
|
1336
|
+
noUncheckedIndexedAccess: true,
|
|
1337
|
+
exactOptionalPropertyTypes: true,
|
|
1338
|
+
noImplicitOverride: true,
|
|
1339
|
+
noPropertyAccessFromIndexSignature: true,
|
|
1340
|
+
noFallthroughCasesInSwitch: true,
|
|
1341
|
+
skipLibCheck: false,
|
|
1342
|
+
target: ts7.ScriptTarget.ES2022,
|
|
1343
|
+
lib: ["lib.es2022.d.ts"],
|
|
1344
|
+
types: [],
|
|
1345
|
+
noEmit: true
|
|
1346
|
+
};
|
|
1347
|
+
const host = ts7.createCompilerHost(options);
|
|
1348
|
+
const getSourceFile = host.getSourceFile.bind(host);
|
|
1349
|
+
host.getSourceFile = (path, languageVersion, onError, shouldCreateNewSourceFile) => path === fileName ? ts7.createSourceFile(path, source, languageVersion, true) : getSourceFile(path, languageVersion, onError, shouldCreateNewSourceFile);
|
|
1350
|
+
const program = ts7.createProgram([fileName], options, host);
|
|
1351
|
+
const diagnostics = ts7.getPreEmitDiagnostics(program);
|
|
1352
|
+
if (diagnostics.length) {
|
|
1353
|
+
throw new Error(`Generated GraphQL types cannot be validated: ${diagnostics.map((item) => ts7.flattenDiagnosticMessageText(item.messageText, `
|
|
1354
|
+
`)).join("; ")}`);
|
|
1355
|
+
}
|
|
1356
|
+
const checker = program.getTypeChecker();
|
|
1357
|
+
const entry = program.getSourceFile(fileName);
|
|
1358
|
+
const module = entry && checker.getSymbolAtLocation(entry);
|
|
1359
|
+
if (!module)
|
|
1360
|
+
throw new Error("Generated GraphQL types have no module");
|
|
1361
|
+
const exports = new Map(checker.getExportsOfModule(module).map((symbol) => [symbol.name, symbol]));
|
|
1362
|
+
if (exports.has("GraphqlQueryResults")) {
|
|
1363
|
+
throw new Error("GraphQL type GraphqlQueryResults conflicts with the generated result registry.");
|
|
1364
|
+
}
|
|
1365
|
+
const names = new Map;
|
|
1366
|
+
const definitions = new Map;
|
|
1367
|
+
function unsupported(type) {
|
|
1368
|
+
throw new Error(`Unsupported GraphQL result wire type: ${checker.typeToString(type)}. Map custom scalars to JSON wire types or unknown.`);
|
|
1369
|
+
}
|
|
1370
|
+
function reference(type) {
|
|
1371
|
+
const existing = names.get(type);
|
|
1372
|
+
if (existing)
|
|
1373
|
+
return existing;
|
|
1374
|
+
const name = `checkGraphqlValue${names.size}`;
|
|
1375
|
+
names.set(type, name);
|
|
1376
|
+
definitions.set(name, "");
|
|
1377
|
+
definitions.set(name, `function ${name}(value: unknown): boolean {
|
|
1378
|
+
return ${expression(type)};
|
|
1379
|
+
}`);
|
|
1380
|
+
return name;
|
|
1381
|
+
}
|
|
1382
|
+
function expression(type) {
|
|
1383
|
+
if (type.flags & ts7.TypeFlags.Any)
|
|
1384
|
+
return unsupported(type);
|
|
1385
|
+
if (type.flags & ts7.TypeFlags.Unknown)
|
|
1386
|
+
return "true";
|
|
1387
|
+
if (type.flags & ts7.TypeFlags.Never)
|
|
1388
|
+
return "false";
|
|
1389
|
+
if (type.flags & ts7.TypeFlags.Null)
|
|
1390
|
+
return "value === null";
|
|
1391
|
+
if (type.flags & ts7.TypeFlags.Undefined)
|
|
1392
|
+
return "value === undefined";
|
|
1393
|
+
if (type.isStringLiteral() || type.isNumberLiteral())
|
|
1394
|
+
return `value === ${JSON.stringify(type.value)}`;
|
|
1395
|
+
if (type.flags & ts7.TypeFlags.BooleanLiteral)
|
|
1396
|
+
return `value === ${checker.typeToString(type)}`;
|
|
1397
|
+
if (type.flags & ts7.TypeFlags.String)
|
|
1398
|
+
return 'typeof value === "string"';
|
|
1399
|
+
if (type.flags & ts7.TypeFlags.Number)
|
|
1400
|
+
return 'typeof value === "number" && Number.isFinite(value)';
|
|
1401
|
+
if (type.flags & ts7.TypeFlags.Boolean)
|
|
1402
|
+
return 'typeof value === "boolean"';
|
|
1403
|
+
if (type.isUnion())
|
|
1404
|
+
return type.types.map((part) => `${reference(part)}(value)`).join(" || ");
|
|
1405
|
+
if (type.isIntersection())
|
|
1406
|
+
return type.types.map((part) => `${reference(part)}(value)`).join(" && ");
|
|
1407
|
+
if (checker.isTupleType(type))
|
|
1408
|
+
return unsupported(type);
|
|
1409
|
+
if (checker.isArrayType(type)) {
|
|
1410
|
+
const item = checker.getIndexTypeOfType(type, ts7.IndexKind.Number);
|
|
1411
|
+
if (!item)
|
|
1412
|
+
return unsupported(type);
|
|
1413
|
+
return `isGraphqlArray(value) && Array.from(value).every(${reference(item)})`;
|
|
1414
|
+
}
|
|
1415
|
+
if (type.flags & ts7.TypeFlags.Object) {
|
|
1416
|
+
if (type.getCallSignatures().length || type.getConstructSignatures().length)
|
|
1417
|
+
return unsupported(type);
|
|
1418
|
+
const indexes = checker.getIndexInfosOfType(type);
|
|
1419
|
+
if (indexes.some((index) => !(index.keyType.flags & ts7.TypeFlags.String)))
|
|
1420
|
+
return unsupported(type);
|
|
1421
|
+
const properties = checker.getPropertiesOfType(type).map((property2) => {
|
|
1422
|
+
const declaration = property2.valueDeclaration ?? property2.declarations?.[0];
|
|
1423
|
+
if (!declaration)
|
|
1424
|
+
return unsupported(type);
|
|
1425
|
+
const check = reference(checker.getTypeOfSymbolAtLocation(property2, declaration));
|
|
1426
|
+
const key = JSON.stringify(property2.name);
|
|
1427
|
+
const present = `Object.prototype.hasOwnProperty.call(value, ${key})`;
|
|
1428
|
+
return property2.flags & ts7.SymbolFlags.Optional ? `(!${present} || ${check}(value[${key}]))` : `(${present} && ${check}(value[${key}]))`;
|
|
1429
|
+
});
|
|
1430
|
+
const indexedValues = indexes.map((index) => `Object.values(value).every(${reference(index.type)})`);
|
|
1431
|
+
return ["isGraphqlRecord(value)", ...properties, ...indexedValues].join(" && ");
|
|
1432
|
+
}
|
|
1433
|
+
return unsupported(type);
|
|
1434
|
+
}
|
|
1435
|
+
const operations = [...operationNames].sort();
|
|
1436
|
+
const parsers = operations.map((name) => {
|
|
1437
|
+
const typeName = `${name}Query`;
|
|
1438
|
+
const symbol = exports.get(typeName);
|
|
1439
|
+
if (!symbol)
|
|
1440
|
+
throw new Error(`Missing generated operation type: ${typeName}`);
|
|
1441
|
+
const check = reference(checker.getDeclaredTypeOfSymbol(symbol));
|
|
1442
|
+
return `export function is${typeName}(value: unknown): value is ${typeName} {
|
|
1443
|
+
return ${check}(value);
|
|
1444
|
+
}
|
|
1445
|
+
export function parse${typeName}(value: unknown): ${typeName} {
|
|
1446
|
+
if (!is${typeName}(value)) {
|
|
1447
|
+
throw new GraphqlRequestError("invalid-response", ${JSON.stringify(`GraphQL result does not match ${name}.`)});
|
|
1448
|
+
}
|
|
1449
|
+
return value;
|
|
1450
|
+
}`;
|
|
1451
|
+
});
|
|
1452
|
+
return `
|
|
1453
|
+
function isGraphqlRecord(value: unknown): value is Record<string, unknown> {
|
|
1454
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1455
|
+
}
|
|
1456
|
+
function isGraphqlArray(value: unknown): value is unknown[] {
|
|
1457
|
+
return Array.isArray(value);
|
|
1458
|
+
}
|
|
1459
|
+
${[...definitions.values()].join(`
|
|
1460
|
+
`)}
|
|
1461
|
+
${parsers.join(`
|
|
1462
|
+
`)}
|
|
1463
|
+
export interface GraphqlQueryResults {
|
|
1464
|
+
${operations.map((name) => ` ${JSON.stringify(name)}: ${name}Query;`).join(`
|
|
1465
|
+
`)}
|
|
1466
|
+
}
|
|
1467
|
+
export function isGraphqlResult<Name extends keyof GraphqlQueryResults>(
|
|
1468
|
+
name: Name, value: unknown,
|
|
1469
|
+
): value is GraphqlQueryResults[Name] {
|
|
1470
|
+
switch (name) {
|
|
1471
|
+
${operations.map((name) => ` case ${JSON.stringify(name)}: return is${name}Query(value);`).join(`
|
|
1472
|
+
`)}
|
|
1473
|
+
default: return false;
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
export function parseGraphqlResult<Name extends keyof GraphqlQueryResults>(
|
|
1477
|
+
name: Name, value: unknown,
|
|
1478
|
+
): GraphqlQueryResults[Name] {
|
|
1479
|
+
if (!isGraphqlResult(name, value)) {
|
|
1480
|
+
throw new GraphqlRequestError("invalid-response", "GraphQL result does not match operation " + name + ".");
|
|
1481
|
+
}
|
|
1482
|
+
return value;
|
|
1483
|
+
}
|
|
1484
|
+
`;
|
|
1485
|
+
}
|
|
1486
|
+
var init_graphql_runtime = () => {};
|
|
1487
|
+
|
|
1329
1488
|
// src/graphql.ts
|
|
1330
1489
|
var exports_graphql = {};
|
|
1331
1490
|
__export(exports_graphql, {
|
|
@@ -1349,7 +1508,6 @@ import {
|
|
|
1349
1508
|
validateSchema
|
|
1350
1509
|
} from "graphql";
|
|
1351
1510
|
import { codegen } from "@graphql-codegen/core";
|
|
1352
|
-
import * as typescript from "@graphql-codegen/typescript";
|
|
1353
1511
|
import * as operations from "@graphql-codegen/typescript-operations";
|
|
1354
1512
|
async function renderGraphql(options) {
|
|
1355
1513
|
const result = { diagnostics: [], files: {} };
|
|
@@ -1455,9 +1613,8 @@ async function renderGraphql(options) {
|
|
|
1455
1613
|
try {
|
|
1456
1614
|
const config = {
|
|
1457
1615
|
useTypeImports: true,
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
onlyOperationTypes: true,
|
|
1616
|
+
nonOptionalTypename: false,
|
|
1617
|
+
enumType: "string-literal",
|
|
1461
1618
|
namingConvention: "keep",
|
|
1462
1619
|
dedupeOperationSuffix: false,
|
|
1463
1620
|
omitOperationSuffix: false,
|
|
@@ -1470,21 +1627,21 @@ async function renderGraphql(options) {
|
|
|
1470
1627
|
schemaAst: schema,
|
|
1471
1628
|
documents,
|
|
1472
1629
|
config,
|
|
1473
|
-
plugins: [{
|
|
1474
|
-
pluginMap: {
|
|
1630
|
+
plugins: [{ operations: {} }],
|
|
1631
|
+
pluginMap: { operations }
|
|
1475
1632
|
});
|
|
1476
1633
|
const separated = separateOperations(combined);
|
|
1477
1634
|
const methods = Object.entries(separated).sort(([a], [b]) => a.localeCompare(b)).map(([name, document]) => {
|
|
1478
1635
|
const operation = document.definitions.find((node) => node.kind === Kind.OPERATION_DEFINITION);
|
|
1479
|
-
if (operation.kind !== Kind.OPERATION_DEFINITION)
|
|
1636
|
+
if (!operation || operation.kind !== Kind.OPERATION_DEFINITION)
|
|
1480
1637
|
throw new Error("Missing query operation");
|
|
1481
1638
|
const required = operation.variableDefinitions?.some((variable) => variable.type.kind === Kind.NON_NULL_TYPE && !variable.defaultValue);
|
|
1482
|
-
return ` ${JSON.stringify(name)}(variables${required ? "" : "?"}: ${name}QueryVariables, options?: C): Promise<${name}Query> {
|
|
1483
|
-
return
|
|
1639
|
+
return ` async ${JSON.stringify(name)}(variables${required ? "" : "?"}: ${name}QueryVariables, options?: C): Promise<${name}Query> {
|
|
1640
|
+
return parse${name}Query(await requester(${JSON.stringify(print(document))}, variables, options));
|
|
1484
1641
|
}`;
|
|
1485
1642
|
});
|
|
1486
1643
|
const facade = `
|
|
1487
|
-
export type Requester<C> =
|
|
1644
|
+
export type Requester<C> = (query: string, variables?: unknown, options?: C) => Promise<unknown>;
|
|
1488
1645
|
export function getSdk<C>(requester: Requester<C>) {
|
|
1489
1646
|
return {
|
|
1490
1647
|
${methods.join(`,
|
|
@@ -1492,8 +1649,9 @@ ${methods.join(`,
|
|
|
1492
1649
|
};
|
|
1493
1650
|
}
|
|
1494
1651
|
`;
|
|
1652
|
+
const validators = renderGraphqlValidators(generated, queryEntries.map((entry) => entry.name));
|
|
1495
1653
|
result.files["graphql.ts"] = `// GENERATED BY @supacloud/compiler. DO NOT EDIT.
|
|
1496
|
-
` + generated + facade + GRAPHQL_CLIENT_SOURCE;
|
|
1654
|
+
` + generated + validators + facade + GRAPHQL_CLIENT_SOURCE;
|
|
1497
1655
|
if (options.graphql.typedDocuments) {
|
|
1498
1656
|
const typedDocuments = await import("@graphql-codegen/typed-document-node");
|
|
1499
1657
|
result.files["graphql.documents.ts"] = `// GENERATED BY @supacloud/compiler. DO NOT EDIT.
|
|
@@ -1503,8 +1661,8 @@ ${methods.join(`,
|
|
|
1503
1661
|
schemaAst: schema,
|
|
1504
1662
|
documents,
|
|
1505
1663
|
config,
|
|
1506
|
-
plugins: [{
|
|
1507
|
-
pluginMap: {
|
|
1664
|
+
plugins: [{ operations: {} }, { typedDocuments: {} }],
|
|
1665
|
+
pluginMap: { operations, typedDocuments }
|
|
1508
1666
|
});
|
|
1509
1667
|
}
|
|
1510
1668
|
result.files["graphql.manifest.json"] = JSON.stringify({
|
|
@@ -1522,6 +1680,7 @@ ${methods.join(`,
|
|
|
1522
1680
|
var init_graphql = __esm(() => {
|
|
1523
1681
|
init_graphql_inputs();
|
|
1524
1682
|
init_graphql_options();
|
|
1683
|
+
init_graphql_runtime();
|
|
1525
1684
|
});
|
|
1526
1685
|
|
|
1527
1686
|
// src/graphql-schema.ts
|
|
@@ -1531,7 +1690,7 @@ __export(exports_graphql_schema, {
|
|
|
1531
1690
|
});
|
|
1532
1691
|
import { mkdir as mkdir2, readFile as readFile4 } from "node:fs/promises";
|
|
1533
1692
|
import { createHash as createHash7 } from "node:crypto";
|
|
1534
|
-
import { dirname as dirname7, resolve as
|
|
1693
|
+
import { dirname as dirname7, resolve as resolve9 } from "node:path";
|
|
1535
1694
|
async function pullGraphqlSchema(options) {
|
|
1536
1695
|
assertGraphqlOptions({ schema: options.output });
|
|
1537
1696
|
const endpoint = new URL(options.url);
|
|
@@ -1571,7 +1730,7 @@ async function pullGraphqlSchema(options) {
|
|
|
1571
1730
|
throw new Error("GraphQL schema export failed. Verify caller grants and enable introspection only in the intended development environment.");
|
|
1572
1731
|
}
|
|
1573
1732
|
const schema = lexicographicSortSchema(buildClientSchema2(data));
|
|
1574
|
-
const path =
|
|
1733
|
+
const path = resolve9(options.output);
|
|
1575
1734
|
const content = path.endsWith(".json") ? JSON.stringify(introspectionFromSchema(schema), null, 2) + `
|
|
1576
1735
|
` : `# GENERATED BY supacloud-compiler graphql-schema. DO NOT EDIT.
|
|
1577
1736
|
# Database First: change database declarations, apply migrations, then re-export for the intended role.
|
|
@@ -5626,12 +5785,12 @@ function resolveTypeSafety(options) {
|
|
|
5626
5785
|
}
|
|
5627
5786
|
// src/watch.ts
|
|
5628
5787
|
import { existsSync as existsSync4, watch } from "node:fs";
|
|
5629
|
-
import { dirname as dirname5, relative as relative7, resolve as
|
|
5788
|
+
import { dirname as dirname5, relative as relative7, resolve as resolve7, sep as sep7 } from "node:path";
|
|
5630
5789
|
|
|
5631
5790
|
// src/incremental.ts
|
|
5632
5791
|
import { createHash as createHash6 } from "node:crypto";
|
|
5633
5792
|
import { access as access2, readdir, readFile as readFile3 } from "node:fs/promises";
|
|
5634
|
-
import { isAbsolute as isAbsolute2, relative as relative6, resolve as
|
|
5793
|
+
import { isAbsolute as isAbsolute2, relative as relative6, resolve as resolve6, sep as sep6 } from "node:path";
|
|
5635
5794
|
init_graphql_inputs();
|
|
5636
5795
|
function createDependencyGraphCache() {
|
|
5637
5796
|
return {
|
|
@@ -5706,11 +5865,11 @@ function createIncrementalCompiler() {
|
|
|
5706
5865
|
};
|
|
5707
5866
|
}
|
|
5708
5867
|
async function updateSnapshot(previous, options, changedPaths) {
|
|
5709
|
-
const rootDir =
|
|
5710
|
-
const outDir =
|
|
5868
|
+
const rootDir = resolve6(options.rootDir);
|
|
5869
|
+
const outDir = resolve6(options.outDir);
|
|
5711
5870
|
const files = { ...previous.files };
|
|
5712
5871
|
for (const changedPath of changedPaths) {
|
|
5713
|
-
const absolutePath = isAbsolute2(changedPath) ?
|
|
5872
|
+
const absolutePath = isAbsolute2(changedPath) ? resolve6(changedPath) : resolve6(rootDir, changedPath);
|
|
5714
5873
|
const relativeChangedPath = relative6(rootDir, absolutePath);
|
|
5715
5874
|
if (relativeChangedPath === ".." || relativeChangedPath.startsWith(`..${sep6}`))
|
|
5716
5875
|
continue;
|
|
@@ -5728,8 +5887,8 @@ async function updateSnapshot(previous, options, changedPaths) {
|
|
|
5728
5887
|
return { files, optionsKey: optionsKeyOf(options) };
|
|
5729
5888
|
}
|
|
5730
5889
|
async function createSnapshot(options) {
|
|
5731
|
-
const rootDir =
|
|
5732
|
-
const outDir =
|
|
5890
|
+
const rootDir = resolve6(options.rootDir);
|
|
5891
|
+
const outDir = resolve6(options.outDir);
|
|
5733
5892
|
const paths = await listSourceFiles(rootDir, outDir);
|
|
5734
5893
|
const files = {};
|
|
5735
5894
|
for (const path of paths) {
|
|
@@ -5748,8 +5907,8 @@ async function createSnapshot(options) {
|
|
|
5748
5907
|
}
|
|
5749
5908
|
function optionsKeyOf(options) {
|
|
5750
5909
|
return JSON.stringify({
|
|
5751
|
-
rootDir:
|
|
5752
|
-
outDir:
|
|
5910
|
+
rootDir: resolve6(options.rootDir),
|
|
5911
|
+
outDir: resolve6(options.outDir),
|
|
5753
5912
|
include: options.include,
|
|
5754
5913
|
strict: options.strict,
|
|
5755
5914
|
writeOnError: options.writeOnError,
|
|
@@ -5771,7 +5930,7 @@ async function listSourceFiles(rootDir, outDir) {
|
|
|
5771
5930
|
const result = [];
|
|
5772
5931
|
const visit = async (directory) => {
|
|
5773
5932
|
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
|
5774
|
-
const path =
|
|
5933
|
+
const path = resolve6(directory, entry.name);
|
|
5775
5934
|
if (entry.isDirectory()) {
|
|
5776
5935
|
if (entry.name === "node_modules" || entry.name === ".git" || path === outDir)
|
|
5777
5936
|
continue;
|
|
@@ -5895,8 +6054,8 @@ function findAffectedModules(previous, current, changedFiles) {
|
|
|
5895
6054
|
// src/watch.ts
|
|
5896
6055
|
var DEFAULT_DEBOUNCE_MS = 100;
|
|
5897
6056
|
function watchProject(options) {
|
|
5898
|
-
const rootDir =
|
|
5899
|
-
const outDir =
|
|
6057
|
+
const rootDir = resolve7(options.rootDir);
|
|
6058
|
+
const outDir = resolve7(options.outDir);
|
|
5900
6059
|
const debounceMs = options.debounceMs ?? DEFAULT_DEBOUNCE_MS;
|
|
5901
6060
|
let timer;
|
|
5902
6061
|
let closed = false;
|
|
@@ -5905,7 +6064,7 @@ function watchProject(options) {
|
|
|
5905
6064
|
const pendingPaths = new Set;
|
|
5906
6065
|
let watcher;
|
|
5907
6066
|
let schemaWatcher;
|
|
5908
|
-
const schemaPath = options.graphql ?
|
|
6067
|
+
const schemaPath = options.graphql ? resolve7(rootDir, options.graphql.schema) : undefined;
|
|
5909
6068
|
const incremental = createIncrementalCompiler();
|
|
5910
6069
|
let initialEvent;
|
|
5911
6070
|
let resolveReady = () => {
|
|
@@ -5982,7 +6141,7 @@ function watchProject(options) {
|
|
|
5982
6141
|
watcher = watch(rootDir, { recursive: true }, (_eventType, filename) => {
|
|
5983
6142
|
if (!filename)
|
|
5984
6143
|
return schedule();
|
|
5985
|
-
const changedPath =
|
|
6144
|
+
const changedPath = resolve7(rootDir, filename.toString());
|
|
5986
6145
|
const relativePath = relative7(outDir, changedPath);
|
|
5987
6146
|
if (!relativePath.startsWith("..") && relativePath !== "")
|
|
5988
6147
|
return;
|
|
@@ -5995,7 +6154,7 @@ function watchProject(options) {
|
|
|
5995
6154
|
while (!existsSync4(directory) && dirname5(directory) !== directory)
|
|
5996
6155
|
directory = dirname5(directory);
|
|
5997
6156
|
schemaWatcher = watch(directory, { recursive: true }, (_eventType, filename) => {
|
|
5998
|
-
if (!filename ||
|
|
6157
|
+
if (!filename || resolve7(directory, filename.toString()) === schemaPath)
|
|
5999
6158
|
schedule(schemaPath);
|
|
6000
6159
|
});
|
|
6001
6160
|
}
|
|
@@ -6293,7 +6452,7 @@ init_generate();
|
|
|
6293
6452
|
// src/config.ts
|
|
6294
6453
|
init_graphql_options();
|
|
6295
6454
|
import { existsSync as existsSync6 } from "node:fs";
|
|
6296
|
-
import { join as join6, resolve as
|
|
6455
|
+
import { join as join6, resolve as resolve8 } from "node:path";
|
|
6297
6456
|
import { pathToFileURL } from "node:url";
|
|
6298
6457
|
var DEFAULT_SUPACLOUD_CONFIG = {
|
|
6299
6458
|
graphql: false,
|
|
@@ -6310,6 +6469,7 @@ var DEFAULT_SUPACLOUD_CONFIG = {
|
|
|
6310
6469
|
function defineSupacloudConfig(config = {}) {
|
|
6311
6470
|
if (config.graphql !== undefined && config.graphql !== false)
|
|
6312
6471
|
assertGraphqlOptions(config.graphql);
|
|
6472
|
+
validateGovernanceConfig(config);
|
|
6313
6473
|
return {
|
|
6314
6474
|
...DEFAULT_SUPACLOUD_CONFIG,
|
|
6315
6475
|
...config,
|
|
@@ -6317,11 +6477,35 @@ function defineSupacloudConfig(config = {}) {
|
|
|
6317
6477
|
graphql: config.graphql ?? DEFAULT_SUPACLOUD_CONFIG.graphql
|
|
6318
6478
|
};
|
|
6319
6479
|
}
|
|
6480
|
+
function validateGovernanceConfig(config) {
|
|
6481
|
+
const isRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
6482
|
+
const isStrings = (value) => Array.isArray(value) && Array.from(value).every((item) => typeof item === "string" && item.trim().length > 0);
|
|
6483
|
+
if (config.moduleBoundaries !== undefined) {
|
|
6484
|
+
if (!Array.isArray(config.moduleBoundaries))
|
|
6485
|
+
throw new Error("moduleBoundaries must be an array of module tag rules.");
|
|
6486
|
+
const rules = config.moduleBoundaries;
|
|
6487
|
+
for (const rule of rules) {
|
|
6488
|
+
if (!isRecord(rule) || typeof rule["sourceTag"] !== "string" || !rule["sourceTag"].trim() || Object.keys(rule).some((key) => !["sourceTag", "onlyDependOnLibsWithTags", "bannedDependenciesWithTags"].includes(key)) || rule["onlyDependOnLibsWithTags"] !== undefined && !isStrings(rule["onlyDependOnLibsWithTags"]) || rule["bannedDependenciesWithTags"] !== undefined && !isStrings(rule["bannedDependenciesWithTags"])) {
|
|
6489
|
+
throw new Error("moduleBoundaries rules require sourceTag and optional string arrays onlyDependOnLibsWithTags/bannedDependenciesWithTags.");
|
|
6490
|
+
}
|
|
6491
|
+
}
|
|
6492
|
+
}
|
|
6493
|
+
if (config.typeSafety !== undefined) {
|
|
6494
|
+
const rules = config.typeSafety;
|
|
6495
|
+
if (!isRecord(rules) || Object.keys(rules).some((key) => !["scanProductionSource", "noAnyInGenerated", "exclude"].includes(key)) || rules["scanProductionSource"] !== undefined && typeof rules["scanProductionSource"] !== "boolean" || rules["noAnyInGenerated"] !== undefined && typeof rules["noAnyInGenerated"] !== "boolean" || rules["exclude"] !== undefined && !isStrings(rules["exclude"])) {
|
|
6496
|
+
throw new Error("typeSafety accepts boolean scanProductionSource/noAnyInGenerated and a string array exclude.");
|
|
6497
|
+
}
|
|
6498
|
+
}
|
|
6499
|
+
for (const key of ["allowRouteCommandBindings", "disallowControllerDirectDb", "detectOrphanModules"]) {
|
|
6500
|
+
if (config[key] !== undefined && typeof config[key] !== "boolean")
|
|
6501
|
+
throw new Error(`${key} must be a boolean.`);
|
|
6502
|
+
}
|
|
6503
|
+
}
|
|
6320
6504
|
function resolveSupacloudConfig(config = {}, cwd = process.cwd()) {
|
|
6321
6505
|
const resolved = defineSupacloudConfig(config);
|
|
6322
6506
|
return {
|
|
6323
|
-
rootDir:
|
|
6324
|
-
outDir:
|
|
6507
|
+
rootDir: resolve8(cwd, resolved.root ?? DEFAULT_SUPACLOUD_CONFIG.root),
|
|
6508
|
+
outDir: resolve8(cwd, resolved.outDir ?? DEFAULT_SUPACLOUD_CONFIG.outDir),
|
|
6325
6509
|
include: resolved.include ?? [...DEFAULT_SUPACLOUD_CONFIG.include],
|
|
6326
6510
|
strict: resolved.strict ?? DEFAULT_SUPACLOUD_CONFIG.strict,
|
|
6327
6511
|
requireRouteContracts: resolved.requireRouteContracts ?? DEFAULT_SUPACLOUD_CONFIG.requireRouteContracts,
|
|
@@ -6329,10 +6513,15 @@ function resolveSupacloudConfig(config = {}, cwd = process.cwd()) {
|
|
|
6329
6513
|
generatePermissions: resolved.generatePermissions ?? DEFAULT_SUPACLOUD_CONFIG.generatePermissions,
|
|
6330
6514
|
moduleBoundaryPreset: resolved.moduleBoundaryPreset ?? DEFAULT_SUPACLOUD_CONFIG.moduleBoundaryPreset,
|
|
6331
6515
|
commandCapabilities: resolved.commandCapabilities,
|
|
6516
|
+
...resolved.moduleBoundaries ? { moduleBoundaries: resolved.moduleBoundaries } : {},
|
|
6517
|
+
...resolved.typeSafety ? { typeSafety: resolved.typeSafety } : {},
|
|
6518
|
+
...resolved.allowRouteCommandBindings === undefined ? {} : { allowRouteCommandBindings: resolved.allowRouteCommandBindings },
|
|
6519
|
+
...resolved.disallowControllerDirectDb === undefined ? {} : { disallowControllerDirectDb: resolved.disallowControllerDirectDb },
|
|
6520
|
+
...resolved.detectOrphanModules === undefined ? {} : { detectOrphanModules: resolved.detectOrphanModules },
|
|
6332
6521
|
treeShakeUnusedProviders: resolved.treeShakeUnusedProviders ?? DEFAULT_SUPACLOUD_CONFIG.treeShakeUnusedProviders,
|
|
6333
6522
|
graphql: resolved.graphql ? {
|
|
6334
6523
|
...resolved.graphql,
|
|
6335
|
-
schema:
|
|
6524
|
+
schema: resolve8(cwd, resolved.graphql.schema)
|
|
6336
6525
|
} : undefined
|
|
6337
6526
|
};
|
|
6338
6527
|
}
|
|
@@ -6350,20 +6539,7 @@ async function loadSupacloudConfig(cwd = process.cwd()) {
|
|
|
6350
6539
|
return defineSupacloudConfig(imported.default ?? {});
|
|
6351
6540
|
}
|
|
6352
6541
|
function compileOptionsFromConfig(config, cwd = process.cwd()) {
|
|
6353
|
-
|
|
6354
|
-
return {
|
|
6355
|
-
rootDir: resolved.rootDir,
|
|
6356
|
-
outDir: resolved.outDir,
|
|
6357
|
-
include: resolved.include,
|
|
6358
|
-
strict: resolved.strict,
|
|
6359
|
-
requireRouteContracts: resolved.requireRouteContracts,
|
|
6360
|
-
generateClient: resolved.generateClient,
|
|
6361
|
-
generatePermissions: resolved.generatePermissions,
|
|
6362
|
-
moduleBoundaryPreset: resolved.moduleBoundaryPreset,
|
|
6363
|
-
commandCapabilities: resolved.commandCapabilities,
|
|
6364
|
-
treeShakeUnusedProviders: resolved.treeShakeUnusedProviders,
|
|
6365
|
-
graphql: resolved.graphql
|
|
6366
|
-
};
|
|
6542
|
+
return resolveSupacloudConfig(config, cwd);
|
|
6367
6543
|
}
|
|
6368
6544
|
|
|
6369
6545
|
// src/index.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supacloud/compiler",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Static compiler for @supacloud/app metadata: builds the application graph from AST, validates it, and generates reflection-free factory code",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -25,8 +25,9 @@
|
|
|
25
25
|
"build:js": "bun build src/index.ts src/cli.ts --outdir dist --target node --external @typescript/typescript6 --external graphql --external '@graphql-codegen/*'",
|
|
26
26
|
"build:types": "tsc -p tsconfig.json --emitDeclarationOnly",
|
|
27
27
|
"clean": "rm -rf dist",
|
|
28
|
-
"prepublishOnly": "bun run build",
|
|
28
|
+
"prepublishOnly": "bun run build && bun run test:package",
|
|
29
29
|
"test": "bun test",
|
|
30
|
+
"test:package": "SUPACLOUD_COMPILER_TEST_CLI=\"$PWD/dist/cli.js\" bun test src/graphql-package.test.ts",
|
|
30
31
|
"benchmark": "bun run src/benchmark.ts",
|
|
31
32
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
32
33
|
"typecheck:test": "tsc -p tsconfig.test.json --noEmit"
|
|
@@ -47,7 +48,6 @@
|
|
|
47
48
|
"dependencies": {
|
|
48
49
|
"@graphql-codegen/core": "^6.2.0",
|
|
49
50
|
"@graphql-codegen/typed-document-node": "^7.1.0",
|
|
50
|
-
"@graphql-codegen/typescript": "^6.1.0",
|
|
51
51
|
"@graphql-codegen/typescript-operations": "^6.1.6",
|
|
52
52
|
"@graphql-typed-document-node/core": "^3.2.0",
|
|
53
53
|
"@typescript/typescript6": "^6.0.2",
|