Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Runnable new-and-enhanced JS samples directory for v3 #438

Merged
merged 12 commits into from
Feb 3, 2023
7 changes: 7 additions & 0 deletions samples-js/.funcignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
*.js.map
*.ts
.git*
.vscode
local.settings.json
test
tsconfig.json
99 changes: 99 additions & 0 deletions samples-js/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# TypeScript v1 declaration files
typings/

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env
.env.test

# parcel-bundler cache (https://parceljs.org/)
.cache

# next.js build output
.next

# nuxt.js build output
.nuxt

# vuepress build output
.vuepress/dist

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# TypeScript output
dist
out

# Azure Functions artifacts
bin
obj
appsettings.json
local.settings.json

# Azurite artifacts
__blobstorage__
__queuestorage__
__azurite_db*__.json
19 changes: 19 additions & 0 deletions samples-js/extensions.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@

<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<WarningsAsErrors></WarningsAsErrors>
<DefaultItemExcludes>**</DefaultItemExcludes>
</PropertyGroup>
<ItemGroup>
<None Include="host.json" />
<None Include="package.json" />
<None Include="local.settings.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.DurableTask" Version="2.9.0" />
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Storage" Version="5.0.1" />
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Twilio" Version="3.0.2" />
<PackageReference Include="Microsoft.Azure.WebJobs.Script.ExtensionsMetadataGenerator" Version="4.0.1" />
</ItemGroup>
</Project>
76 changes: 76 additions & 0 deletions samples-js/functions/backupSiteContent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
const df = require("durable-functions");
const fs = require("fs/promises");
const readdirp = require("readdirp");
const path = require("path");
const { output } = require("@azure/functions");

const getFileListActivityName = "getFileList";
const copyFileToBlobActivityName = "copyFileToBlob";

if (process.env.StorageConnString) {
df.app.orchestration("backupSiteContent", function* (context) {
const rootDir = context.df.getInput();
if (!rootDir) {
throw new Error("A directory path is required as an input.");
}

const rootDirAbs = path.resolve(rootDir);
const files = yield context.df.callActivity(getFileListActivityName, rootDirAbs);

// Backup Files and save Promises into array
const tasks = [];
for (const file of files) {
const input = {
backupPath: path.relative(rootDirAbs, file).replace("\\", "/"),
filePath: file,
};
tasks.push(context.df.callActivity(copyFileToBlobActivityName, input));
}

// wait for all the Backup Files Activities to complete, sum total bytes
const results = yield context.df.Task.all(tasks);
const totalBytes = results ? results.reduce((prev, curr) => prev + curr, 0) : 0;

// return results;
return totalBytes;
});

df.app.activity(getFileListActivityName, {
handler: async function (rootDirectory, context) {
context.log(`Searching for files under '${rootDirectory}'...`);

const allFilePaths = [];
for await (const entry of readdirp(rootDirectory, { type: "files" })) {
allFilePaths.push(entry.fullPath);
}
context.log(`Found ${allFilePaths.length} under ${rootDirectory}.`);
return allFilePaths;
},
});

const blobOutput = output.storageBlob({
path: "backups/{backupPath}",
connection: "StorageConnString",
});

df.app.activity(copyFileToBlobActivityName, {
extraOutputs: [blobOutput],
handler: async function ({ backupPath, filePath }, context) {
const outputLocation = `backups/${backupPath}`;
const stats = await fs.stat(filePath);
context.log(
`Copying '${filePath}' to '${outputLocation}'. Total bytes = ${stats.size}.`
);

const fileContents = await fs.readFile(filePath);

context.extraOutputs.set(blobOutput, fileContents);

return stats.size;
},
});
} else {
console.warn(
"Missing storage connection string, skipping registering functions for backupSiteContent orchestration..."
);
}
21 changes: 21 additions & 0 deletions samples-js/functions/callActivityWithRetry.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const df = require("durable-functions");

df.app.orchestration("callActivityWithRetry", function* (context) {
const retryOptions = new df.RetryOptions(1000, 2);
let returnValue;

try {
returnValue = yield context.df.callActivityWithRetry("flakyFunction", retryOptions);
} catch (e) {
context.log("Orchestrator caught exception. Flaky function is extremely flaky.");
}

return returnValue;
});

df.app.activity("flakyFunction", {
handler: function (_input, context) {
context.log("Flaky Function Flaking!");
throw Error("FlakyFunction flaked");
},
});
27 changes: 27 additions & 0 deletions samples-js/functions/callSubOrchestratorWithRetry.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const df = require("durable-functions");

const subOrchestratorName = "throwsErrorInLine";

df.app.orchestration("callSubOrchestratorWithRetry", function* (context) {
const retryOptions = new df.RetryOptions(10000, 2);
const childId = `${context.df.instanceId}:0`;

let returnValue;

try {
returnValue = yield context.df.callSubOrchestratorWithRetry(
subOrchestratorName,
retryOptions,
"Matter",
childId
);
} catch (e) {
context.log("Orchestrator caught exception. Sub-orchestrator failed.");
}

return returnValue;
});

df.app.orchestration(subOrchestratorName, function* () {
throw Error(`${subOrchestratorName} does what it says on the tin.`);
});
15 changes: 15 additions & 0 deletions samples-js/functions/cancelTimer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const df = require("durable-functions");
const { DateTime } = require("luxon");

df.app.orchestration("cancelTimer", function* (context) {
const expiration = DateTime.fromJSDate(context.df.currentUtcDateTime).plus({ minutes: 2 });
const timeoutTask = context.df.createTimer(expiration.toJSDate());

const hello = yield context.df.callActivity("sayHello", "from the other side");

if (!timeoutTask.isCompleted) {
timeoutTask.cancel();
}

return hello;
});
18 changes: 18 additions & 0 deletions samples-js/functions/continueAsNewCounter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const df = require("durable-functions");
const { DateTime } = require("luxon");

df.app.orchestration("continueAsNewCounter", function* (context) {
let currentValue = context.df.getInput() || 0;
context.log(`Value is ${currentValue}`);
currentValue++;

const wait = DateTime.fromJSDate(context.df.currentUtcDateTime).plus({ seconds: 30 });
context.log("Counting up at" + wait.toString());
yield context.df.createTimer(wait.toJSDate());

if (currentValue < 10) {
context.df.continueAsNew(currentValue);
}

return currentValue;
});
32 changes: 32 additions & 0 deletions samples-js/functions/counter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const df = require("durable-functions");

const counterEntityName = "counterEntity";

df.app.entity(counterEntityName, async function (context) {
await Promise.resolve();
let currentValue = context.df.getState(() => 0);

switch (context.df.operationName) {
case "add":
const amount = context.df.getInput();
currentValue += amount;
break;
case "reset":
currentValue = 0;
break;
case "get":
context.df.return(currentValue);
break;
}

context.df.setState(currentValue);
});

df.app.orchestration("counterOrchestration", function* (context) {
const entityId = new df.EntityId(counterEntityName, "myCounter");

currentValue = yield context.df.callEntity(entityId, "get");
if (currentValue < 10) {
yield context.df.callEntity(entityId, "add", 1);
}
});
18 changes: 18 additions & 0 deletions samples-js/functions/httpStart.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const df = require("durable-functions");
const { app } = require("@azure/functions");

const clientInput = df.input.durableClient();

app.http("httpStart", {
route: "orchestrators/{orchestratorName}",
extraInputs: [clientInput],
handler: async (request, context) => {
const client = df.getClient(context, clientInput);
const body = await request.json();
const instanceId = await client.startNew(request.params.orchestratorName, undefined, body);

context.log(`Started orchestration with ID = '${instanceId}'.`);

return client.createCheckStatusResponse(request, instanceId);
},
});
39 changes: 39 additions & 0 deletions samples-js/functions/httpSyncStart.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
const { app } = require("@azure/functions");
const df = require("durable-functions");

const timeout = "timeout";
const retryInterval = "retryInterval";

const clientInput = df.input.durableClient();

app.http("httpSyncStart", {
methods: ["POST"],
route: "orchestrators/wait/{orchestratorName}",
authLevel: "anonymous",
extraInputs: [clientInput],
handler: async function (request, context) {
const client = df.getClient(context, clientInput);
const body = await request.json();
const instanceId = await client.startNew(request.params.orchestratorName, undefined, body);

context.log(`Started orchestration with ID = '${instanceId}'.`);

const timeoutInMilliseconds = getTimeInSeconds(request, timeout) || 30000;
const retryIntervalInMilliseconds = getTimeInSeconds(request, retryInterval) || 1000;

const response = client.waitForCompletionOrCreateCheckStatusResponse(
request,
instanceId,
timeoutInMilliseconds,
retryIntervalInMilliseconds
);
return response;
},
});

function getTimeInSeconds(req, queryParameterName) {
const queryValue = req.query.get(queryParameterName);
return queryValue
? queryValue * 1000 // expected to be in seconds
: undefined;
}
12 changes: 12 additions & 0 deletions samples-js/functions/listAzureSubscriptions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const df = require("durable-functions");

df.app.orchestration("listAzureSubscriptions", function* (context) {
// More information on the use of managed identities in the callHttp API:
// https://docs.microsoft.com/azure/azure-functions/durable/durable-functions-http-features#managed-identities
const res = yield context.df.callHttp({
method: "GET",
url: "https://management.azure.com/subscriptions?api-version=2019-06-01",
tokenSource: new df.ManagedIdentityTokenSource("https://management.core.windows.net"),
});
return res;
});
Loading