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: findRecord and query request builders #8687

Merged
merged 17 commits into from
Jul 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ packages/model/addon
packages/json-api/addon
packages/graph/addon
packages/legacy-compat/addon
packages/request-utils/addon
packages/rest/addon
packages/active-record/addon

# dependencies
bower_components
Expand Down
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ detailing how to become involved to best ensure your contributions are successfu
- [Requesting Features or Deprecations](./contributing/rfc-process.md)
- [Submitting Pull Requests](./contributing/submitting-prs.md)
- [Linking the project to your application locally](./contributing/linking-to-applications.md)
- [Key Concepts](./contributing/key-concepts.md)

You may also want to review the [roadmap](./ROADMAP.md) for ideas of how you may want to get
involved.
36 changes: 36 additions & 0 deletions contributing/key-concepts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Key Concepts

- [Identity](#-identity)

-------------

## 🔸 Identity

### Working with Identifiers and TypeScript

Identifying information can be encountered in several different manners depending on which APIs are being worked with and whether the code is "internal" or "public facing".

* The "ResourceIdentifier" type is used when the identifying information is end-user-supplied and not guaranteed to have "lid".

Example: `findRecord({ type: 'user', id: '1' })`

Most commonly this is the case when a record has not been encountered yet or when processing a payload received from the API.

* The "RecordIdentifier" type is used when identifying information MUST have "lid" but may not be the "stable" identifier object instance.

Example: `saveRecord({ type: 'user', id: null, lid: 'user:1' })`

Most commonly this is the case when the user might manually construct an identifier. Often this is the result of having previously serialized record state and later attempting to restore it.

* The "StableRecordIdentifier" type is used when identifying information MUST have "lid" AND MUST be the "stable" identifier object
instance produced and managed by the `IdentifierCache` associated to a
specific `Store` instance.

Example:

```ts
const identifier = recordIdentifierFor(record);
unloadRecord(identifier);
```

Any identifier supplied by an EmberData API will always be the stable variant. APIs which are operating based on identity and which can reasonably presume that the data exists expect stable identifiers and should error if an unknown identifier is encountered to prevent potential system-correctness errors.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"lint:js": "eslint --quiet --cache --cache-strategy=content --ext=js,ts .",
"preinstall": "npx only-allow pnpm",
"problems": "tsc -p tsconfig.json --noEmit --pretty false",
"test": "pnpm --filter main-test-app --filter graph-test-app --filter json-api-test-app --filter request-test-app run test",
"test": "pnpm --filter main-test-app --filter graph-test-app --filter json-api-test-app --filter request-test-app --filter builders-test-app run test",
"test:production": "pnpm --filter main-test-app --filter graph-test-app --filter json-api-test-app run test -e production",
"test:try-one": "pnpm --filter main-test-app run test:try-one",
"test:docs": "pnpm build:docs && pnpm --filter docs-tests test",
Expand Down
11 changes: 11 additions & 0 deletions packages/active-record/LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
The MIT License (MIT)

Copyright (C) 2017-2023 Ember.js contributors
Portions Copyright (C) 2011-2017 Tilde, Inc. and contributors.
Portions Copyright (C) 2011 LivingSocial Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
13 changes: 13 additions & 0 deletions packages/active-record/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
@ember-data/active-record
============================================================================

ActiveRecord Format Support for EmberData

> Note: This is a V2 Addon, but we have intentionally configured it to act and report as a V1 Addon due
to bugs with ember-auto-import.
>
> We can remove the V1 tag if ember-auto-import will no longer attempt
to load V2 addons or if it is fixed to work with V1 addons with custom addon trees and also dedupes modules for test apps.
>
> You can still consume this as a normal library.
> In other projects.
19 changes: 19 additions & 0 deletions packages/active-record/addon-main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
module.exports = {
name: require('./package.json').name,

treeForVendor() {
return;
},
treeForPublic() {
return;
},
treeForStyles() {
return;
},
treeForAddonStyles() {
return;
},
treeForApp() {
return;
},
};
8 changes: 8 additions & 0 deletions packages/active-record/babel.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"plugins": [
"@babel/plugin-transform-runtime",
["@babel/plugin-transform-typescript", { "allowDeclareFields": true }],
["@babel/plugin-proposal-decorators", { "legacy": true }],
"@babel/plugin-proposal-class-properties"
]
}
70 changes: 70 additions & 0 deletions packages/active-record/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
{
"name": "@ember-data/active-record",
"description": "ActiveRecord Format Support for EmberData",
"version": "5.3.0-alpha.3",
"private": false,
"license": "MIT",
"author": "Chris Thoburn <runspired@users.noreply.github.com>",
"repository": {
"type": "git",
"url": "git+ssh://git@github.com:emberjs/data.git",
"directory": "packages/active-record"
},
"homepage": "https://github.com/emberjs/data",
"bugs": "https://github.com/emberjs/data/issues",
"engines": {
"node": "16.* || >= 18"
},
"keywords": [
"ember-addon"
],
"volta": {
"extends": "../../package.json"
},
"dependencies": {
"ember-cli-babel": "^7.26.11"
},
"peerDependencies": {
"ember-inflector": "^4.0.2"
},
"files": [
"addon-main.js",
"addon",
"README.md",
"LICENSE.md",
"ember-data-logo-dark.svg",
"ember-data-logo-light.svg"
],
"scripts": {
"build": "rollup --config && babel ./addon --out-dir addon --plugins=../private-build-infra/src/transforms/babel-plugin-transform-ext.js",
"start": "rollup --config --watch",
"prepack": "pnpm build",
"prepare": "pnpm build"
},
"ember-addon": {
"main": "addon-main.js",
"type": "addon",
"version": 1
},
"devDependencies": {
"@babel/core": "^7.21.8",
"@babel/cli": "^7.21.5",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-proposal-decorators": "^7.21.0",
"@babel/plugin-transform-runtime": "^7.21.4",
"@babel/plugin-transform-typescript": "^7.21.3",
"@babel/preset-env": "^7.21.5",
"@babel/preset-typescript": "^7.21.5",
"@babel/runtime": "^7.21.5",
"@embroider/addon-dev": "^3.0.0",
"@rollup/plugin-babel": "^6.0.3",
"@rollup/plugin-node-resolve": "^15.0.2",
"rollup": "^3.21.7",
"tslib": "^2.5.0",
"typescript": "^5.0.4",
"walk-sync": "^3.0.0"
},
"ember": {
"edition": "octane"
}
}
31 changes: 31 additions & 0 deletions packages/active-record/rollup.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Addon } from '@embroider/addon-dev/rollup';
import babel from '@rollup/plugin-babel';
import { nodeResolve } from '@rollup/plugin-node-resolve';

const addon = new Addon({
srcDir: 'src',
destDir: 'addon',
});

export default {
// This provides defaults that work well alongside `publicEntrypoints` below.
// You can augment this if you need to.
output: addon.output(),

external: [],

plugins: [
// These are the modules that users should be able to import from your
// addon. Anything not listed here may get optimized away.
addon.publicEntrypoints(['request.js']),

nodeResolve({ extensions: ['.ts'] }),
babel({
extensions: ['.ts'],
babelHelpers: 'runtime', // we should consider "external",
}),

// Remove leftover build artifacts when starting a new build.
addon.clean(),
],
};
39 changes: 39 additions & 0 deletions packages/active-record/src/-private/builders/-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { QueryParamsSerializationOptions } from '@ember-data/request-utils';
import type { ResourceIdentifierObject } from '@ember-data/types/q/ember-data-json-api';

export type CacheOptions = {
key?: string;
reload?: boolean;
backgroundReload?: boolean;
};
export type FindRecordRequestOptions = {
url: string;
method: 'GET';
headers: Headers;
cacheOptions: CacheOptions;
op: 'findRecord';
records: [ResourceIdentifierObject];
};

export type QueryRequestOptions = {
url: string;
method: 'GET';
headers: Headers;
cacheOptions: CacheOptions;
op: 'query';
};

export type RemotelyAccessibleIdentifier = {
id: string;
type: string;
lid?: string;
};

export type ConstrainedRequestOptions = {
reload?: boolean;
backgroundReload?: boolean;
host?: string;
namespace?: string;
resourcePath?: string;
urlParamsSettings?: QueryParamsSerializationOptions;
};
26 changes: 26 additions & 0 deletions packages/active-record/src/-private/builders/-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { type UrlOptions } from '@ember-data/request-utils';

import type { CacheOptions, ConstrainedRequestOptions } from './-types';

export function copyForwardUrlOptions(urlOptions: UrlOptions, options: ConstrainedRequestOptions): void {
if ('host' in options) {
urlOptions.host = options.host;
}
if ('namespace' in options) {
urlOptions.namespace = options.namespace;
}
if ('resourcePath' in options) {
urlOptions.resourcePath = options.resourcePath;
}
}

export function extractCacheOptions(options: ConstrainedRequestOptions) {
const cacheOptions: CacheOptions = {};
if ('reload' in options) {
cacheOptions.reload = options.reload;
}
if ('backgroundReload' in options) {
cacheOptions.backgroundReload = options.backgroundReload;
}
return cacheOptions;
}
49 changes: 49 additions & 0 deletions packages/active-record/src/-private/builders/find-record.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { underscore } from '@ember/string';

import { pluralize } from 'ember-inflector';

import { buildBaseURL, buildQueryParams, type FindRecordUrlOptions } from '@ember-data/request-utils';

import type { ConstrainedRequestOptions, FindRecordRequestOptions, RemotelyAccessibleIdentifier } from './-types';
import { copyForwardUrlOptions, extractCacheOptions } from './-utils';

type FindRecordOptions = ConstrainedRequestOptions & {
include?: string | string[];
};

export function findRecord(
identifier: RemotelyAccessibleIdentifier,
options?: FindRecordOptions
): FindRecordRequestOptions;
export function findRecord(type: string, id: string, options?: FindRecordOptions): FindRecordRequestOptions;
export function findRecord(
arg1: string | RemotelyAccessibleIdentifier,
arg2: string | FindRecordOptions | undefined,
arg3?: FindRecordOptions
): FindRecordRequestOptions {
const identifier: RemotelyAccessibleIdentifier = typeof arg1 === 'string' ? { type: arg1, id: arg2 as string } : arg1;
const options = ((typeof arg1 === 'string' ? arg3 : arg2) || {}) as FindRecordOptions;
const cacheOptions = extractCacheOptions(options);
const urlOptions: FindRecordUrlOptions = {
identifier,
op: 'findRecord',
resourcePath: pluralize(underscore(identifier.type)),
};

copyForwardUrlOptions(urlOptions, options);

const url = buildBaseURL(urlOptions);
const headers = new Headers();
headers.append('Content-Type', 'application/json; charset=utf-8');

return {
url: options.include?.length
? `${url}?${buildQueryParams({ include: options.include }, options.urlParamsSettings)}`
: url,
method: 'GET',
headers,
cacheOptions,
op: 'findRecord',
records: [identifier],
};
}
35 changes: 35 additions & 0 deletions packages/active-record/src/-private/builders/query.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { underscore } from '@ember/string';

import { pluralize } from 'ember-inflector';

import { buildBaseURL, buildQueryParams, QueryParamsSource, type QueryUrlOptions } from '@ember-data/request-utils';

import type { ConstrainedRequestOptions, QueryRequestOptions } from './-types';
import { copyForwardUrlOptions, extractCacheOptions } from './-utils';

export function query(
type: string,
query: QueryParamsSource = {},
options: ConstrainedRequestOptions = {}
): QueryRequestOptions {
const cacheOptions = extractCacheOptions(options);
const urlOptions: QueryUrlOptions = {
identifier: { type },
op: 'query',
resourcePath: pluralize(underscore(type)),
};

copyForwardUrlOptions(urlOptions, options);

const url = buildBaseURL(urlOptions);
const headers = new Headers();
headers.append('Content-Type', 'application/json; charset=utf-8');

return {
url: `${url}?${buildQueryParams(query, options.urlParamsSettings)}`,
method: 'GET',
headers,
cacheOptions,
op: 'query',
};
}
Empty file.
2 changes: 2 additions & 0 deletions packages/active-record/src/request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { findRecord } from './-private/builders/find-record';
export { query } from './-private/builders/query';
3 changes: 2 additions & 1 deletion packages/json-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@
],
"peerDependencies": {
"@ember-data/store": "workspace:5.3.0-alpha.3",
"@ember-data/graph": "workspace:5.3.0-alpha.3"
"@ember-data/graph": "workspace:5.3.0-alpha.3",
"ember-inflector": "^4.0.2"
},
"dependenciesMeta": {
"@ember-data/private-build-infra": {
Expand Down
2 changes: 1 addition & 1 deletion packages/json-api/rollup.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export default {
plugins: [
// These are the modules that users should be able to import from your
// addon. Anything not listed here may get optimized away.
addon.publicEntrypoints(['index.js']),
addon.publicEntrypoints(['index.js', 'request.js']),

nodeResolve({ extensions: ['.ts', '.js'] }),
babel({
Expand Down
Loading
Loading