Skip to content

Commit

Permalink
more updates
Browse files Browse the repository at this point in the history
  • Loading branch information
runspired committed Jul 11, 2024
1 parent 8f88a91 commit b0529a2
Show file tree
Hide file tree
Showing 9 changed files with 478 additions and 126 deletions.
3 changes: 2 additions & 1 deletion packages/experiments/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,6 @@ pnpm add @warp-drive/experiments

## Current Experiments

- [PersistedCache](./src/data-worker/README.md)
- [DocumentStorage](./src/document-storage/README.md)
- [PersistedCache](./src/persisted-cache/README.md)

8 changes: 8 additions & 0 deletions packages/experiments/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@
"./document-storage": {
"types": "./unstable-preview-types/document-storage.d.ts",
"default": "./dist/document-storage.js"
},
"./data-worker": {
"types": "./unstable-preview-types/data-worker.d.ts",
"default": "./dist/data-worker.js"
},
"./worker-fetch": {
"types": "./unstable-preview-types/worker-fetch.d.ts",
"default": "./dist/worker-fetch.js"
}
},
"files": [
Expand Down
177 changes: 177 additions & 0 deletions packages/experiments/src/data-worker/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
<p align="center">
<img
class="project-logo"
src="../../NCC-1701-a-blue.svg#gh-light-mode-only"
alt="WarpDrive"
width="120px"
title="WarpDrive" />
<img
class="project-logo"
src="../../NCC-1701-a.svg#gh-dark-mode-only"
alt="WarpDrive"
width="120px"
title="WarpDrive" />
</p>

<h3 align="center">DataWorker</h3>

- 🏡 Basic LocalFirst features
- 🔋 Run Fetch/PersistedCache logic in a Worker
- ♻️ Dedupe Requests across Tabs and Windows

## Install

```cli
pnpm add @warp-drive/experiments
```

Or use favorite your javascript package manager.

## About

DataWorker enables offloading network related work to a SharedWorker.

In addition to freeing up a bit of CPU time on the main thread, this enables
intelligent deduping of requests made by your application across multiple
tabs and windows keeping overall network resource usage on the device lower.

## Known Limitations

Permanent (we do not plan to address this limitation)
- Your ember application must be built with embroider
- DataWorker is only available for applications that have removed their usage
of Adapters and Serializers.
- Service injection is not available inside the worker, any services you were injecting
into the store or into handlers need to be provided as standalone instances.
You can use the constructor or class fields to assign these.

Temporary (we plan to address this limitation)
- Requests fulfilled by the DataWorker do not (yet) support streaming responses into the client
- (intentionally) not active in SSR/Fastboot Environments
- dirty state is never sent to the worker, and thus does not sync cross-tab/restore on refresh
- because dirty state is never sent to the worker, in-flight states do not exist either, which
means that save operations which persist local changes will not work as expected if the API
request does not also return the new state.


## Configure

Configuring your app to use a DataWorker happens in two steps:

1. Creating and configuring the Worker instance
2. Updating your App's store to use the Worker

> [!TIP]
> The DataWorker works best with PersistedCache but can be used without it.
### Step 1. Create The Worker

A DataWorker is a Store instance that runs in a Worker wrapped in a lightweight
shell to handle communication with your application.

The store should have nearly the same configuration as the store used by your app,
with a few exceptions:

- Instead of using the `CacheHandler` from `@ember-data/store` we use the one provided by
`@warp-drive/experiments/data-worker`
- Since this store will never directly instantiate records, you should omit the configuration
of the `instantiateRecord` and `teardownRecord` hooks.

Below is an example worker using a `JSONAPI` cache and the basic `Fetch` handler.

```ts
import Store from '@ember-data/store';
import RequestManager from '@ember-data/request';
import Fetch from '@ember-data/request/fetch';
import JSONAPICache from '@ember-data/json-api';

import { DataWorker, CacheHandler } from '@warp-drive/experiments/data-worker';
import type { CacheCapabilitiesManager } from '@ember-data/store/types';
import { CachePolicy } from '@ember-data/request-utils';
import { SchemaService } from '@warp-drive/schema-record/schema';

const requestManager = new RequestManager();
requestManager.use([Fetch]);
requestManager.useCache(CacheHandler);

class WorkerStore extends Store {
requestManager = requestManager;

lifetimes = new CachePolicy({
apiCacheHardExpires: 600_000,
apiCacheSoftExpires: 300_000,
});

createCache(capabilities: CacheCapabilitiesManager) {
return new JSONAPICache(capabilities);
}

createSchemaService() {
return new SchemaService();
}
}

new DataWorker(WorkerStore);
```

### Step 2. Modify Your Application

In the configuration for your application's `RequestManager`, drop all handlers
and replace them with `WorkerFetch`.

```ts
import { WorkerFetch } from '@warp-drive/experiments/worker-fetch';

// this approach to constructing the worker instance will work with both embroider/webpack
// and embroider/vite
const worker = new SharedWorker(new URL('./basic-worker.ts', import.meta.url));

manager.use([new WorkerFetch(worker)]);
```

> [!TIP]
> SharedWorker and Worker are both supported; however, SharedWorker is preferred
> Worker is sometimes the better choice for test environments.

#### Usage in SSR

In SSR, WorkerFetch will deactivate itself and pass through all requests on the handler chain.
This means that to support fetch in SSR all you need to do is keep your original handler chain
present in your configuration.

For example:

```ts
manager.use([new WorkerFetch(worker), MyHandler, Fetch]);
```

Likely you want to prevent creating the worker in SSR. When in SSR mode, the worker argument
is allowed to be `null` to support guarding its creation.

```ts
const worker = isFastBoot ? null : new SharedWorker(new URL('./basic-worker.ts', import.meta.url));

manager.use([new WorkerFetch(worker), MyHandler, Fetch]);
```

#### Usage in Tests

In tests, its often best to use a `Worker` or the main thread instead of a `SharedWorker`.

Main Thread Example:

```ts
const worker = macroCondition(isTesting()) ? null : new SharedWorker(new URL('./basic-worker.ts', import.meta.url));
const handlers = worker ? [new WorkerFetch(worker)] : [MyHandler, Fetch];

manager.use(handlers);
```

Worker Constructor Example:

```ts
const worker = macroCondition(isTesting()) ? new Worker(new URL('./basic-worker.ts', import.meta.url)) : new SharedWorker(new URL('./basic-worker.ts', import.meta.url));

manager.use([new WorkerFetch(worker)]);
```
Loading

0 comments on commit b0529a2

Please sign in to comment.