Skip to content

Commit

Permalink
feat(sdk)!: expose simulator via HTTP server (#4105)
Browse files Browse the repository at this point in the history
This PR evolves the Wing simulator so that every simulation spins up an HTTP server. This HTTP server accepts requests to call inflight methods on resources, making it possible for simulated resources to interact across multiple processes on the same machine (such as from containers running on your system).

Since it's not possible to serialize all forms of data as HTTP, there's a small regression where the `Redis` class's `rawClient` API no longer worked. For the time being, I've removed the method to work around the issue. We might be able to recover the functionality by writing a dedicated implementation of `toInflight` for the `sim` target's implementation of `Redis`, but it's a bit beyond the scope of this PR.

BREAKING CHANGE: The `rawClient()` method on `ex.Redis` has been removed. This API was infrequently used and was provided without any type information. If you have a use case for this API, let us know!

## Checklist

- [x] Title matches [Winglang's style guide](https://www.winglang.io/contributing/start-here/pull_requests#how-are-pull-request-titles-formatted)
- [x] Description explains motivation and solution
- [ ] Tests added (always)
- [ ] Docs updated (only required for features)
- [ ] Added `pr/e2e-full` label if this feature requires end-to-end testing

*By submitting this pull request, I confirm that my contribution is made under the terms of the [Wing Cloud Contribution License](https://github.com/winglang/wing/blob/main/CONTRIBUTION_LICENSE.md)*.
  • Loading branch information
Chriscbr authored Oct 13, 2023
1 parent e2bfee5 commit 3a136d4
Show file tree
Hide file tree
Showing 45 changed files with 543 additions and 316 deletions.
22 changes: 11 additions & 11 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 28 additions & 0 deletions docs/docs/02-concepts/05-simulator.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,34 @@ const response = await fn.invoke("hello!");
console.log(response);
```

### Interacting with the simulation across processes

The simulator also exposes an HTTP server that can be used to interact with the
simulated application from other processes. To get the URL of the simulator's
HTTP server, access `sim.url` after the simulator has started.

```typescript
declare const sim: testing.Simulator;

// ...after the simulator has started

const url = sim.url;
console.log(url);

const handleId = sim.getResourceConfig("root/Default/cloud.Bucket").attrs.handle;
console.log(handleId);
```

In another process, a client can be generated to interact with the simulator. Internally it will behave the same as the resource client, but it will use HTTP requests to communicate with the simulator.

```typescript
import { cloud, testing } from '@winglang/sdk';

const bucket = testing.makeSimulatorClient("WING_SIMULATOR_URL", "HANDLE_ID") as cloud.IBucketClient;
const response = await bucket.get("my-key");
console.log(response);
```

### Debugging

Finally, when you want to understand how Wing resources are working, you may
Expand Down
18 changes: 0 additions & 18 deletions docs/docs/04-standard-library/02-ex/redis.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ new ex.Redis();
| <code><a href="#@winglang/sdk.ex.IRedisClient.get">get</a></code> | Get value at given key. |
| <code><a href="#@winglang/sdk.ex.IRedisClient.hget">hget</a></code> | Returns the value associated with field in the hash stored at key. |
| <code><a href="#@winglang/sdk.ex.IRedisClient.hset">hset</a></code> | Sets the specified field to respective value in the hash stored at key. |
| <code><a href="#@winglang/sdk.ex.IRedisClient.rawClient">rawClient</a></code> | Get raw redis client (currently IoRedis). |
| <code><a href="#@winglang/sdk.ex.IRedisClient.sadd">sadd</a></code> | Add the specified members to the set stored at key. |
| <code><a href="#@winglang/sdk.ex.IRedisClient.set">set</a></code> | Set key value pair. |
| <code><a href="#@winglang/sdk.ex.IRedisClient.smembers">smembers</a></code> | Returns all the members of the set value stored at key. |
Expand Down Expand Up @@ -132,14 +131,6 @@ value to set at field in key.

---

##### `rawClient` <a name="rawClient" id="@winglang/sdk.ex.IRedisClient.rawClient"></a>

```wing
inflight rawClient(): any
```

Get raw redis client (currently IoRedis).

##### `sadd` <a name="sadd" id="@winglang/sdk.ex.IRedisClient.sadd"></a>

```wing
Expand Down Expand Up @@ -263,7 +254,6 @@ new ex.RedisClientBase();
| <code><a href="#@winglang/sdk.ex.RedisClientBase.get">get</a></code> | Get value at given key. |
| <code><a href="#@winglang/sdk.ex.RedisClientBase.hget">hget</a></code> | Returns the value associated with field in the hash stored at key. |
| <code><a href="#@winglang/sdk.ex.RedisClientBase.hset">hset</a></code> | Sets the specified field to respective value in the hash stored at key. |
| <code><a href="#@winglang/sdk.ex.RedisClientBase.rawClient">rawClient</a></code> | Get raw redis client (currently IoRedis). |
| <code><a href="#@winglang/sdk.ex.RedisClientBase.sadd">sadd</a></code> | Add the specified members to the set stored at key. |
| <code><a href="#@winglang/sdk.ex.RedisClientBase.set">set</a></code> | Set key value pair. |
| <code><a href="#@winglang/sdk.ex.RedisClientBase.smembers">smembers</a></code> | Returns all the members of the set value stored at key. |
Expand Down Expand Up @@ -345,14 +335,6 @@ Sets the specified field to respective value in the hash stored at key.

---

##### `rawClient` <a name="rawClient" id="@winglang/sdk.ex.RedisClientBase.rawClient"></a>

```wing
rawClient(): any
```

Get raw redis client (currently IoRedis).

##### `sadd` <a name="sadd" id="@winglang/sdk.ex.RedisClientBase.sadd"></a>

```wing
Expand Down
4 changes: 2 additions & 2 deletions examples/tests/sdk_tests/bucket/delete.test.w
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ test "delete" {
try {
block();
} catch actual {
assert(actual == expected);
assert(actual.contains(expected));
error = true;
}
assert(error);
Expand All @@ -34,4 +34,4 @@ test "delete" {
b.delete("file2.txt");

assert(!b.exists("file2.txt"));
}
}
9 changes: 5 additions & 4 deletions examples/tests/sdk_tests/bucket/metadata.test.w
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ let b = new cloud.Bucket();
test "metadata" {
b.put("test1.txt", "Foo");

assert(b.metadata("test1.txt").size == 3);
assert(b.metadata("test1.txt").contentType == "application/octet-stream");
assert(b.metadata("test1.txt").lastModified.year >= 2023);
let metadata = b.metadata("test1.txt");
assert(metadata.size == 3);
assert(metadata.contentType == "application/octet-stream");
assert(metadata.lastModified.year >= 2023);

try {
b.metadata("no-such-file.txt").lastModified;
} catch e {
assert(e == "Object does not exist (key=no-such-file.txt).");
assert(e.contains("Object does not exist (key=no-such-file.txt)."));
}
}
4 changes: 2 additions & 2 deletions examples/tests/sdk_tests/bucket/public_url.test.w
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ test "publicUrl" {
try {
block();
} catch actual {
assert(actual == expected);
assert(actual.contains(expected));
error = true;
}
assert(error);
Expand All @@ -33,4 +33,4 @@ test "publicUrl" {
assertThrows(BUCKET_NOT_PUBLIC_ERROR, () => {
privateBucket.publicUrl("file2.txt");
});
}
}
6 changes: 3 additions & 3 deletions examples/tests/sdk_tests/queue/push.test.w
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ new std.Test(inflight () => {
q.push("");
assert(false);
} catch e {
assert(e == "Empty messages are not allowed");
assert(e.contains("Empty messages are not allowed"));
}

try {
q.push("Foo", "");
assert(false);
} catch e {
assert(e == "Empty messages are not allowed");
assert(e.contains("Empty messages are not allowed"));
}

q.push("Foo");
Expand All @@ -47,4 +47,4 @@ new std.Test(inflight () => {
assert(util.waitUntil((): bool => {
return q.approxSize() == 3;
}));
}, { timeout: 3m }) as "push";
}, { timeout: 3m }) as "push";
26 changes: 11 additions & 15 deletions examples/tests/sdk_tests/schedule/on_tick.test.w
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,23 @@ let from_rate = new cloud.Schedule( rate: 1m ) as "from_rate";
let c1 = new cloud.Counter() as "c1";
let c2 = new cloud.Counter() as "c2";


from_cron.onTick(inflight () => {
c1.inc();
c1.inc();
});

from_rate.onTick(inflight () => {
c2.inc();
c2.inc();
});


// std.Test is used setting the timeout property
new std.Test(inflight () => {
// counters start at zero
assert(c1.peek() == 0);
assert(c2.peek() == 0);

// wait at least one minute
util.sleep(1.1m);
// counters may have been incremented before the test starts
let c1val = c1.peek();
let c2val = c2.peek();

// check that both counters have been incremented
assert(c1.peek() >= 1);
assert(c2.peek() >= 1);
// wait at least one minute
util.sleep(1.1m);

}, std.TestProps { timeout: 2m }) as "on tick is called both for rate and cron schedules";
// check that both counters have been incremented
assert(c1.peek() >= c1val + 1);
assert(c2.peek() >= c2val + 1);
},timeout: 2m) as "on tick is called both for rate and cron schedules";
2 changes: 1 addition & 1 deletion examples/tests/sdk_tests/table/get.test.w
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ test "get" {
try {
block();
} catch actual {
assert(actual == expected);
assert(actual.contains(expected));
error = true;
}
assert(error);
Expand Down
6 changes: 0 additions & 6 deletions examples/tests/valid/redis.test.w
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,6 @@ queue.setConsumer(inflight (message: str) => {
}, timeout: 3s);

test "testing Redis" {
// Using raw client
let connection = r.rawClient();
connection.set("wing", "does redis");
let value = connection.get("wing");
assert(value == "does redis");

// Using API
r2.set("wing", "does redis again");
let value2 = r2.get("wing");
Expand Down
4 changes: 4 additions & 0 deletions libs/wingsdk/.projen/deps.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion libs/wingsdk/.projen/tasks.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions libs/wingsdk/.projenrc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ const project = new cdk.JsiiProject({
// simulator dependencies
"express",
"uuid",
"undici",
// using version 3 because starting from version 4, it no longer works with CommonJS.
"nanoid@^3.3.6",
"cron-parser",
Expand Down
2 changes: 2 additions & 0 deletions libs/wingsdk/package.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 4 additions & 10 deletions libs/wingsdk/src/ex/redis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ export abstract class Redis extends Resource {
/** @internal */
public _getInflightOps(): string[] {
return [
RedisInflightMethods.RAW_CLIENT,
RedisInflightMethods.URL,
RedisInflightMethods.SET,
RedisInflightMethods.GET,
Expand All @@ -49,12 +48,6 @@ export abstract class Redis extends Resource {
* Inflight interface for `Redis`.
*/
export interface IRedisClient {
/**
* Get raw redis client (currently IoRedis).
* @inflight
*/
rawClient(): Promise<any>;

/**
* Get url of redis server.
* @inflight
Expand Down Expand Up @@ -126,8 +119,6 @@ export interface IRedisClient {
* @internal
*/
export enum RedisInflightMethods {
/** `Redis.rawClient` */
RAW_CLIENT = "rawClient",
/** `Redis.url` */
URL = "url",
/** `Redis.set` */
Expand All @@ -150,7 +141,10 @@ export enum RedisInflightMethods {
* Base class for `Redis` Client.
*/
export abstract class RedisClientBase implements IRedisClient {
public abstract rawClient(): Promise<any>;
/**
* Get raw redis client (currently IoRedis).
*/
protected abstract rawClient(): Promise<any>;
public abstract url(): Promise<string>;

public async set(key: string, value: string): Promise<void> {
Expand Down
5 changes: 5 additions & 0 deletions libs/wingsdk/src/shared/bundling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export interface Bundle {
hash: string;
}

const SDK_PATH = normalPath(join(__dirname, "..", ".."));

/**
* Bundles a javascript entrypoint into a single file.
* @param entrypoint The javascript entrypoint
Expand All @@ -29,6 +31,9 @@ export function createBundle(entrypoint: string, outputDir?: string): Bundle {
nodePaths: process.env.WING_NODE_MODULES
? [normalPath(process.env.WING_NODE_MODULES as string)]
: undefined,
alias: {
"@winglang/sdk": SDK_PATH,
},
minify: false,
platform: "node",
target: "node18",
Expand Down
Loading

0 comments on commit 3a136d4

Please sign in to comment.