in Clients/AmbrosiaJS/Ambrosia-Node/src/Meta.ts [3244:3450]
function codeGen(section: CodeGenSection, fileOptions: FileGenOptions, sourceFileName?: string): string
{
const NL: string = Utils.NEW_LINE; // Just for short-hand
const tab: string = " ".repeat(assertDefined(fileOptions.tabIndent));
let lines: string[] = [];
let moduleAlias: string = sourceFileName ? SOURCE_MODULE_ALIAS + "." : "";
/** [Local function] Returns the TypeScript namespace (if any) of the supplied published type (if it exists), including the trailing ".". */
function makeParamTypePrefix(publishedType?: Type): string
{
if (publishedType)
{
if (publishedType.codeGenOptions?.nsPath)
{
return (moduleAlias + publishedType.codeGenOptions.nsPath + ".");
}
else
{
return (moduleAlias);
}
}
else
{
// No prefix required for a non-published type (eg. "string")
return ("");
}
}
// Skip this section if requested
if ((section & assertDefined(fileOptions.publisherSectionsToSkip)) === section)
{
return ("");
}
switch (section)
{
case CodeGenSection.Header:
lines.push(...getHeaderCommentLines(GeneratedFileKind.Publisher, fileOptions));
if (sourceFileName)
{
// Add an 'import' for the developer-provided source file that contains the implementations of published types and methods
// Note: We don't just want to use an absolute path to the source file (even though that would be simpler for us) because
// we want to retain any relative path so that it's easier for the user to move their code-base around.
// The reference to the source file must be relative to location of the generated file. For example, if the generated
// file is in ./src and the input source file is ./test/Foo.ts, then the import file reference should be '../test/Foo'
const relativeSourceFileName: string = Path.relative(assertDefined(fileOptions.outputPath), sourceFileName);
let filePath: string = Path.dirname(relativeSourceFileName.replace(/\\/g, "/"));
if ((filePath !== ".") && !filePath.startsWith("../") && !filePath.startsWith("./"))
{
filePath = "./" + filePath;
}
let fileReference: string = filePath + "/" + Path.basename(relativeSourceFileName).replace(Path.extname(relativeSourceFileName), "");
lines.push(`import * as ${SOURCE_MODULE_ALIAS} from "${fileReference}"; // ${SOURCE_MODULE_ALIAS} = "Published Types and Methods", but this file can also include app-state and app-event handlers`);
}
break;
case CodeGenSection.AppState:
if (sourceFileName)
{
if (AST.appStateVar() !== "")
{
lines.push(`// ${CODEGEN_COMMENT_PREFIX}: '${CodeGenSection[section]}' section skipped (using provided state variable '${SOURCE_MODULE_ALIAS}.${AST.appStateVar()}' and class '${AST.appStateVarClassName()}' instead)`);
break;
}
else
{
// Note: The _appState variable MUST be in an exported namespace (or module) so that it becomes a [mutable] property of an exported object [the namespace],
// thus allowing it to be set [by checkpointConsumer()] at runtime (see https://stackoverflow.com/questions/53617972/exported-variables-are-read-only).
// If it's exported from the root-level of the source file, the generated PublisherFramework.g.ts code will contain this error [in checkpointConsumer()]:
// Cannot assign to '_myAppState' because it is a read-only property. (ts:2540)
lines.push(`// TODO: It's recommended that you move this namespace to your input file (${makeRelativePath(assertDefined(fileOptions.outputPath), sourceFileName)}) then re-run code-gen`);
}
}
lines.push("export namespace State");
lines.push("{");
lines.push(tab + "export class AppState extends Ambrosia.AmbrosiaAppState" + NL + tab + "{");
lines.push(tab.repeat(2) + "// TODO: Define your application state here" + NL);
lines.push(tab.repeat(2) + "/**");
lines.push(tab.repeat(2) + " * @param restoredAppState Supplied only when loading (restoring) a checkpoint, or (for a \"VNext\" AppState) when upgrading from the prior AppState.\\");
lines.push(tab.repeat(2) + " * **WARNING:** When loading a checkpoint, restoredAppState will be an object literal, so you must use this to reinstantiate any members that are (or contain) class references.");
lines.push(tab.repeat(2) + " */");
lines.push(tab.repeat(2) + "constructor(restoredAppState?: AppState)");
lines.push(tab.repeat(2) + "{");
lines.push(tab.repeat(3) + "super(restoredAppState);" + NL);
lines.push(tab.repeat(3) + "if (restoredAppState)");
lines.push(tab.repeat(3) + "{");
lines.push(tab.repeat(4) + "// TODO: Re-initialize your application state from restoredAppState here");
lines.push(tab.repeat(4) + "// WARNING: You MUST reinstantiate all members that are (or contain) class references because restoredAppState is data-only");
lines.push(tab.repeat(3) + "}");
lines.push(tab.repeat(3) + "else");
lines.push(tab.repeat(3) + "{");
lines.push(tab.repeat(4) + "// TODO: Initialize your application state here");
lines.push(tab.repeat(3) + "}");
lines.push(tab.repeat(2) + "}" + NL + tab + "}" + NL);
lines.push(tab + "/**");
lines.push(tab + " * Only assign this using the return value of IC.start(), the return value of the upgrade() method of your AmbrosiaAppState");
lines.push(tab + " * instance, and [if not using the generated checkpointConsumer()] in the 'onFinished' callback of an IncomingCheckpoint object.");
lines.push(tab + " */");
lines.push(tab + "export let _appState: AppState;");
lines.push("}")
break;
case CodeGenSection.PostMethodHandlers:
for (const name in _publishedMethods)
{
for (const version in _publishedMethods[name])
{
let method: Method = _publishedMethods[name][version];
let variableNames: string[] = method.parameterNames.map(name => name.endsWith("?") ? name.slice(0, -1) : name);
let nsPathForMethod: string = method.codeGenOptions?.nsPath ? (method.codeGenOptions.nsPath + ".") : "";
if (method.isPost)
{
let caseTab: string = tab;
lines.push(`case "${method.name}":`);
if (variableNames.length > 0)
{
// To prevent variable name collisions in the switch statement (eg. if 2 methods use the same parameter name), if needed, we create a new block scope for each case statement
lines.push(`${tab}{`);
caseTab = tab + tab;
}
for (let i = 0; i < variableNames.length; i++)
{
let prefix: string = makeParamTypePrefix(_publishedTypes[Type.removeArraySuffix(method.parameterTypes[i])]);
lines.push(`${caseTab}const ${Method.trimRest(variableNames[i])}: ${prefix}${method.parameterTypes[i]} = IC.getPostMethodArg(rpc, "${Method.trimRest(method.parameterNames[i])}");`);
}
let prefix: string = makeParamTypePrefix(_publishedTypes[Type.removeArraySuffix(method.returnType)]);
lines.push(`${caseTab}IC.postResult<${prefix}${method.returnType}>(rpc, ${moduleAlias + nsPathForMethod}${method.name}(${variableNames.join(", ")}));`);
if (variableNames.length > 0)
{
lines.push(`${tab}}`);
}
lines.push(`${tab}break;${NL}`);
}
}
}
break;
case CodeGenSection.NonPostMethodHandlers:
for (const name in _publishedMethods)
{
for (const version in _publishedMethods[name])
{
let method: Method = _publishedMethods[name][version];
let variableNames: string[] = method.parameterNames.map(name => name.endsWith("?") ? name.slice(0, -1) : name);
let nsPathForMethod: string = method.codeGenOptions?.nsPath ? (method.codeGenOptions.nsPath + ".") : "";
if (!method.isPost)
{
let caseTab: string = tab;
lines.push(`case ${method.id}:`);
if (variableNames.length > 0)
{
// To prevent variable name collisions in the switch statement (eg. if 2 methods use the same parameter name), if needed, we create a new block scope for each case statement
lines.push(`${tab}{`);
caseTab = tab + tab;
}
for (let i = 0; i < variableNames.length; i++)
{
let prefix: string = makeParamTypePrefix(_publishedTypes[Type.removeArraySuffix(method.parameterTypes[i])]);
if (method.takesRawParams)
{
lines.push(`${caseTab}const ${variableNames[i]}: ${prefix}${method.parameterTypes[i]} = rpc.getRawParams();`);
}
else
{
const isOptionalParam: boolean = method.parameterNames[i].endsWith("?");
const paramName: string = isOptionalParam ? method.parameterNames[i].slice(0, -1) : method.parameterNames[i];
lines.push(`${caseTab}const ${Method.trimRest(variableNames[i])}: ${prefix}${method.parameterTypes[i]} = rpc.getJsonParam("${Method.trimRest(paramName)}");${isOptionalParam ? " // Optional parameter" : ""}`);
}
}
lines.push(`${caseTab}${moduleAlias + nsPathForMethod}${method.name}(${variableNames.join(", ")});`);
if (variableNames.length > 0)
{
lines.push(`${tab}}`);
}
lines.push(`${tab}break;${NL}`);
}
}
}
break;
case CodeGenSection.PublishTypes:
for (const typeName in _publishedTypes)
{
let type: Type = _publishedTypes[typeName];
lines.push(`Meta.publishType("${type.name}", "${type.definition.replace(/"/g, "\\\"")}");`);
}
break;
case CodeGenSection.PublishMethods:
for (const name in _publishedMethods)
{
for (const version in _publishedMethods[name])
{
let method: Method = _publishedMethods[name][version];
let paramList: string[] = [];
paramList.push(...method.parameterNames.map((name, index) => `"${name}: ${method.parameterTypes[index].replace(/"/g, "\\\"")}"`));
let methodParams: string = `[${paramList.join(", ")}]`;
if (method.isPost)
{
lines.push(`Meta.publishPostMethod("${method.name}", ${method.version}, ${methodParams}, "${method.returnType.replace(/"/g, "\\\"")}"${method.isTypeChecked ? "" : ", false"});`);
}