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

feat(@aws-amplify/datastore): Support SSR #5450

Closed
wants to merge 21 commits into from
Closed
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
b6e2d72
Add jest config to aws-amplify
ericclemmons Apr 15, 2020
1158231
Add exports-test.ts
ericclemmons Apr 15, 2020
a07e0ac
aws-amplify has named export DataStore
ericclemmons Apr 15, 2020
7dd9dde
Move __tests__ so build script ignores them
ericclemmons Apr 15, 2020
aaa31f5
Add Predicates from DataStore
ericclemmons Apr 15, 2020
9ce2624
Mark datastore as having sideEffects (initSchema)
ericclemmons Apr 16, 2020
a733a4e
Node is always "online"
ericclemmons Apr 16, 2020
b1e41c8
getDefaultAdapter uses fake-indexeddb for Node
ericclemmons Apr 16, 2020
fea8126
Node support for WebSocket (via isomorphic-ws)
ericclemmons Apr 17, 2020
8dd5fe2
Warn when schema has been initialized rather than throw
ericclemmons Apr 17, 2020
0baf796
Add model.fromJSON([] | {}) for creating existing instances from JSON
ericclemmons Apr 17, 2020
10a8141
Revert "Mark datastore as having sideEffects (initSchema)"
ericclemmons Apr 17, 2020
4733b5c
Remove `isomorphic-ws` from Predictions until formally supported & te…
ericclemmons Apr 17, 2020
644a5f1
Use modelInstanceCreator for fromJSON
ericclemmons Apr 17, 2020
a267d05
Add DataStore.toJSON()
ericclemmons Apr 17, 2020
280e685
Merge branch 'master' of github.com:aws-amplify/amplify-js into next-…
ericclemmons Apr 20, 2020
6f92a34
Simplify with Observerable.from
ericclemmons May 20, 2020
b476969
Merge branch 'master' of github.com:aws-amplify/amplify-js into next-…
ericclemmons Jun 16, 2020
fb25ade
Fix TypeScript error with Observable.from({ online: true })
ericclemmons Jun 16, 2020
bf5d5d5
DataStore awaits SYNC_ENGINE_SYNC_QUERIES_READY when in Node
ericclemmons Jun 16, 2020
a34bd42
Correct `Observable.from` usage in 6f92a34
ericclemmons Jun 16, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions packages/core/src/Util/Reachability.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { JS } from '@aws-amplify/core';
import Observable, { ZenObservable } from 'zen-observable-ts';

type NetworkStatus = {
Expand All @@ -10,6 +11,12 @@ export default class ReachabilityNavigator implements Reachability {
> = [];

networkMonitor(netInfo?: any): Observable<NetworkStatus> {
if (JS.browserOrNode().isNode) {
return new Observable(observer => {
observer.next({ online: true });
});
}
ericclemmons marked this conversation as resolved.
Show resolved Hide resolved

return new Observable(observer => {
observer.next({ online: window.navigator.onLine });

Expand Down
6 changes: 4 additions & 2 deletions packages/datastore/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,19 @@
"@react-native-community/netinfo": "4.7.0",
"@types/uuid": "3.4.5",
"dexie": "3.0.1",
"dexie-export-import": "1.0.0-rc.2",
"fake-indexeddb": "3.0.0"
"dexie-export-import": "1.0.0-rc.2"
},
"dependencies": {
"@aws-amplify/api": "^3.1.16",
"@aws-amplify/core": "^3.3.3",
"@aws-amplify/pubsub": "^3.0.17",
"fake-indexeddb": "3.0.0",
"idb": "5.0.2",
"immer": "6.0.1",
"isomorphic-ws": "^4.0.1",
"ulid": "2.3.0",
"uuid": "3.3.2",
"ws": "^7.2.3",
"zen-observable-ts": "0.8.19",
"zen-push": "0.2.1"
},
Expand Down
28 changes: 25 additions & 3 deletions packages/datastore/src/datastore/datastore.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Amplify, ConsoleLogger as Logger, Hub } from '@aws-amplify/core';
import { Amplify, ConsoleLogger as Logger, Hub, JS } from '@aws-amplify/core';
import { Draft, immerable, produce, setAutoFreeze } from 'immer';
import { v4 as uuid4 } from 'uuid';
import Observable, { ZenObservable } from 'zen-observable-ts';
Expand Down Expand Up @@ -52,6 +52,7 @@ setAutoFreeze(true);
const logger = new Logger('DataStore');

const ulid = monotonicUlidFactory(Date.now());
const { isNode } = JS.browserOrNode();

declare class Setting {
constructor(init: ModelInit<Setting>);
Expand Down Expand Up @@ -100,7 +101,9 @@ let storageClasses: TypeConstructorMap;

const initSchema = (userSchema: Schema) => {
if (schema !== undefined) {
throw new Error('The schema has already been initialized');
console.warn('The schema has already been initialized');

return userClasses;
manueliglesias marked this conversation as resolved.
Show resolved Hide resolved
}

logger.log('validating schema', { schema: userSchema });
Expand Down Expand Up @@ -325,6 +328,14 @@ const createModelClass = <T extends PersistentModel>(
draft.id = source.id;
});
}

static fromJSON(json: T | T[]) {
if (Array.isArray(json)) {
return json.map(init => this.fromJSON(init));
}

return modelInstanceCreator(clazz, json);
}
});

clazz[immerable] = true;
Expand Down Expand Up @@ -861,7 +872,13 @@ async function start(): Promise<void> {
.start({ fullSyncInterval: fullSyncIntervalInMilliseconds })
.subscribe({
next: ({ type, data }) => {
if (type === ControlMessage.SYNC_ENGINE_STORAGE_SUBSCRIBED) {
// In Node, we need to wait for queries to be synced to prevent returning empty arrays.
// In the Browser, we can begin returning data once subscriptions are in place.
const readyType = isNode
? ControlMessage.SYNC_ENGINE_SYNC_QUERIES_READY
: ControlMessage.SYNC_ENGINE_STORAGE_SUBSCRIBED;

if (type === readyType) {
Comment on lines +875 to +881
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change fixes expected behavior in Node (particularly lambdas).

Notice here where I'm listening to Hub.listen('datastore') and finally logging out { posts }:

// 👇 Hub.listen('datastore', ...)
datastore {
  channel: 'datastore',
  payload: { event: 'storageSubscribed', data: undefined },
  source: '',
  patternInfo: []
}
datastore {
  channel: 'datastore',
  payload: { event: 'networkStatus', data: { active: true } },
  source: '',
  patternInfo: []
}
datastore {
  channel: 'datastore',
  payload: { event: 'outboxStatus', data: { isEmpty: true } },
  source: '',
  patternInfo: []
}
datastore {
  channel: 'datastore',
  payload: { event: 'subscriptionsEstablished', data: undefined },
  source: '',
  patternInfo: []
}
datastore {
  channel: 'datastore',
  payload: { event: 'syncQueriesStarted', data: { models: [Array] } },
  source: '',
  patternInfo: []
}
datastore {
  channel: 'datastore',
  payload: {
    event: 'modelSynced',
    data: {
      model: [Function],
      isFullSync: true,
      isDeltaSync: false,
      counts: [Object]
    }
  },
  source: '',
  patternInfo: []
}
datastore {
  channel: 'datastore',
  payload: {
    event: 'modelSynced',
    data: {
      model: [Function],
      isFullSync: true,
      isDeltaSync: false,
      counts: [Object]
    }
  },
  source: '',
  patternInfo: []
}
datastore {
  channel: 'datastore',
  payload: { event: 'syncQueriesReady', data: undefined },
  source: '',
  patternInfo: []
}
datastore {
  channel: 'datastore',
  payload: { event: 'ready', data: undefined },
  source: '',
  patternInfo: []
}
// 👇 { posts }
{
  posts: [
    Post {
      id: 'd4ad30d3-bb95-4cb2-9f55-b5917605e797',
      title: 'New Post (4/16/2020, 4:58:42 PM)',
      rating: 1,
      status: 'ACTIVE',
      _version: 2,
      _lastChangedAt: 1587081528053,
      _deleted: null
    },
    Post {
      id: '7b14789a-40a9-4b3a-8a23-849e3866f77a',
      title: 'New Post (4/17/2020, 3:17:13 PM)',
      rating: 4,
      status: 'INACTIVE',
      _version: 1,
      _lastChangedAt: 1587161833864,
      _deleted: null
    },
    Post {
      id: '2e5a6422-47e8-4693-b141-a126e8aaacfc',
      title: 'New Post (4/16/2020, 4:28:16 PM)',
      rating: 3,
      status: 'ACTIVE',
      _version: 2,
      _lastChangedAt: 1587081526956,
      _deleted: null
    },
    Post {
      id: 'f05918fc-2209-4013-a185-d6f9f8d9c468',
      title: 'New Post (4/20/2020, 2:04:21 PM)',
      rating: 2,
      status: 'INACTIVE',
      _version: 1,
      _lastChangedAt: 1587416662244,
      _deleted: null
    },
    Post {
      id: 'a94a6cc5-8a0b-441a-9da8-5dfc391df18d',
      title: 'New Post (4/16/2020, 4:58:43 PM)',
      rating: 3,
      status: 'INACTIVE',
      _version: 1,
      _lastChangedAt: 1587081523206,
      _deleted: null
    }
  ]
}
{
  posts: [
    Post {
      id: 'd4ad30d3-bb95-4cb2-9f55-b5917605e797',
      title: 'New Post (4/16/2020, 4:58:42 PM)',
      rating: 1,
      status: 'ACTIVE',
      _version: 2,
      _lastChangedAt: 1587081528053,
      _deleted: null
    },
    Post {
      id: '7b14789a-40a9-4b3a-8a23-849e3866f77a',
      title: 'New Post (4/17/2020, 3:17:13 PM)',
      rating: 4,
      status: 'INACTIVE',
      _version: 1,
      _lastChangedAt: 1587161833864,
      _deleted: null
    },
    Post {
      id: '2e5a6422-47e8-4693-b141-a126e8aaacfc',
      title: 'New Post (4/16/2020, 4:28:16 PM)',
      rating: 3,
      status: 'ACTIVE',
      _version: 2,
      _lastChangedAt: 1587081526956,
      _deleted: null
    },
    Post {
      id: 'f05918fc-2209-4013-a185-d6f9f8d9c468',
      title: 'New Post (4/20/2020, 2:04:21 PM)',
      rating: 2,
      status: 'INACTIVE',
      _version: 1,
      _lastChangedAt: 1587416662244,
      _deleted: null
    },
    Post {
      id: 'a94a6cc5-8a0b-441a-9da8-5dfc391df18d',
      title: 'New Post (4/16/2020, 4:58:43 PM)',
      rating: 3,
      status: 'INACTIVE',
      _version: 1,
      _lastChangedAt: 1587081523206,
      _deleted: null
    }
  ]
}

initResolve();
}

Expand Down Expand Up @@ -940,6 +957,10 @@ function getNamespace(): SchemaNamespace {
return namespace;
}

const toJSON = <T extends PersistentModel>(model: T | T[]): JSON => {
return JSON.parse(JSON.stringify(model));
};

class DataStore {
constructor() {
Amplify.register(this);
Expand All @@ -954,6 +975,7 @@ class DataStore {
observe = observe;
configure = configure;
clear = clear;
toJSON = toJSON;
}

const instance = new DataStore();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { JS } from '@aws-amplify/core';
import { Adapter } from '..';

const getDefaultAdapter: () => Adapter = () => {
if (window.indexedDB) {
const { isBrowser, isNode } = JS.browserOrNode();

if (isNode) {
require('fake-indexeddb/auto');
return require('../indexeddb').default;
}
if (process && process.env) {
throw new Error('Node is not supported');

if (isBrowser && window.indexedDB) {
return require('../indexeddb').default;
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
MessageHeaderValue,
} from '@aws-sdk/eventstream-marshaller';
import { fromUtf8, toUtf8 } from '@aws-sdk/util-utf8-node';

const logger = new Logger('AmazonAIConvertPredictionsProvider');
const eventBuilder = new EventStreamMarshaller(toUtf8, fromUtf8);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
} from '@aws-amplify/core';
import Cache from '@aws-amplify/cache';
import Auth from '@aws-amplify/auth';
import WebSocket from 'isomorphic-ws';
ericclemmons marked this conversation as resolved.
Show resolved Hide resolved
import { AbstractPubSubProvider } from './PubSubProvider';
import { CONTROL_MSG } from '../index';

Expand Down