@alchemy_cms/json_api 2.0.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,11 +13,13 @@ gem 'alchemy-json_api'
13
13
  ```
14
14
 
15
15
  And then execute:
16
+
16
17
  ```bash
17
18
  $ bundle
18
19
  ```
19
20
 
20
21
  Or install it yourself as:
22
+
21
23
  ```bash
22
24
  $ gem install alchemy-json_api
23
25
  ```
@@ -27,9 +29,11 @@ $ gem install alchemy-json_api
27
29
  Run this in your application:
28
30
 
29
31
  ```
30
- yarn add "@alchemy_cms/json_api"
32
+ npm install @alchemy_cms/json_api --save
31
33
  ```
32
34
 
35
+ or with the package manager of your choice
36
+
33
37
  ## Usage
34
38
 
35
39
  ### In your Rails app
@@ -57,18 +61,46 @@ const pages = deserializePages(data)
57
61
  console.log(pages[0].name) // => Homepage
58
62
  ```
59
63
 
64
+ ## HTTP Caching
65
+
66
+ Alchemy::JsonApi allows for caching API responses. It respects the caching configuration of your Rails app and of your Alchemy configuration and settings in the pages page layout configuration. Restricted pages are never cached.
67
+
68
+ By default it sets the `max-age` `Cache-Control` header to 10 minutes (`600` seconds). You can change this by configuring the `Alchemy::JsonApi.page_cache_max_age` setting. It is recommended to set this via an environment variable like this:
69
+
70
+ ```rb
71
+ # config/initializers/alchemy_json_api.rb
72
+ Alchemy::JsonApi.page_cache_max_age = ENV.fetch("ALCHEMY_JSON_API_CACHE_DURATION", 600).to_i
73
+ ```
74
+
75
+ ### Edge Caching
76
+
77
+ Alchemy sets the `must-revalidate` directive if caching is enabled. If your CDN supports it, you can change that to use the much more efficient `stale-while-revalidate` directive by changing the `Alchemy::JsonApi.page_caching_options` setting to any integer value.
78
+
79
+ ```rb
80
+ # config/initializers/alchemy_json_api.rb
81
+ Alchemy::JsonApi.page_caching_options = {
82
+ stale_while_revalidate: ENV.fetch("ALCHEMY_JSON_API_CACHE_STALE_WHILE_REVALIDATE", 60).to_i
83
+ }
84
+ ```
85
+
86
+ > [!TIP]
87
+ > You can set any caching option that [`ActionController::ConditionalGet#expires_in` supports](https://api.rubyonrails.org/classes/ActionController/ConditionalGet.html#method-i-expires_in), like `stale_if_error`, `public` or `immutable`.
88
+
60
89
  ## Key transforms
61
90
 
62
91
  If you ever want to change how Alchemy serializes attributes you can set
63
92
 
64
93
  ```rb
94
+ # config/initializers/alchemy_json_api.rb
65
95
  Alchemy::JsonApi.key_transform = :camel_lower
66
96
  ```
67
97
 
68
98
  It defaults to `:underscore`.
69
99
 
70
100
  ## Contributing
101
+
71
102
  Contribution directions go here.
72
103
 
73
104
  ## License
105
+
74
106
  The gem is available as open source under the terms of the [BSD-3-Clause license](https://opensource.org/licenses/BSD-3-Clause).
@@ -1,132 +1,24 @@
1
- import structuredClone from '@ungap/structured-clone';
2
-
3
- function deserialize(originalResponse, options = {}) {
4
- const response = structuredClone(originalResponse);
5
- if (!options) {
6
- options = {};
7
- }
8
-
9
- const included = response.included || [];
10
-
11
- if (Array.isArray(response.data)) {
12
- return response.data.map((data) => {
13
- return parseJsonApiSimpleResourceData(data, included, false)
14
- })
15
- } else {
16
- return parseJsonApiSimpleResourceData(
17
- response.data,
18
- included,
19
- false)
20
- }
1
+ import { deserialize as r } from "./deserialize.js";
2
+ function n(t) {
3
+ const s = [];
4
+ return t.forEach((e) => {
5
+ e.nested_elements?.length > 0 && (e.nested_elements = n(
6
+ e.nested_elements
7
+ )), e.nestedElements?.length > 0 && (e.nestedElements = n(e.nestedElements)), e.essences?.length > 0 && (e.essences = e.essences.filter((i) => !i.deprecated)), e.deprecated || s.push(e);
8
+ }), s;
21
9
  }
22
-
23
- function parseJsonApiSimpleResourceData(data, included, useCache, options) {
24
- if (!included.cached) {
25
- included.cached = {};
26
- }
27
-
28
- if (!(data.type in included.cached)) {
29
- included.cached[data.type] = {};
30
- }
31
-
32
- if (useCache && data.id in included.cached[data.type]) {
33
- return included.cached[data.type][data.id]
34
- }
35
-
36
- const attributes = data.attributes || {};
37
-
38
- const resource = attributes;
39
- resource.id = data.id;
40
-
41
- included.cached[data.type][data.id] = resource;
42
-
43
- if (data.relationships) {
44
- for (const relationName of Object.keys(data.relationships)) {
45
- const relationRef = data.relationships[relationName];
46
-
47
- if (Array.isArray(relationRef.data)) {
48
- const items = [];
49
-
50
- relationRef.data.forEach((relationData) => {
51
- const item = findJsonApiIncluded(
52
- included,
53
- relationData.type,
54
- relationData.id);
55
-
56
- items.push(item);
57
- });
58
-
59
- resource[relationName] = items;
60
- } else if (relationRef && relationRef.data) {
61
- resource[relationName] = findJsonApiIncluded(
62
- included,
63
- relationRef.data.type,
64
- relationRef.data.id);
65
- } else {
66
- resource[relationName] = null;
67
- }
68
- }
69
- }
70
-
71
- return resource
10
+ function d(t) {
11
+ const s = r(t);
12
+ return s.elements = n(s.elements), s;
72
13
  }
73
-
74
- function findJsonApiIncluded(included, type, id, options) {
75
- let found = null;
76
-
77
- included.forEach((item) => {
78
- if (item.type === type && item.id === id) {
79
- found = parseJsonApiSimpleResourceData(item, included, true);
80
- }
81
- });
82
-
83
- if (!found) {
84
- found = { id };
85
- }
86
-
87
- return found
14
+ function f(t) {
15
+ const s = r(t);
16
+ return s.forEach((e) => {
17
+ e.elements = n(e.elements);
18
+ }), s;
88
19
  }
89
-
90
- // Recursively filters all deprecated elements and essences from collection
91
- function filterDeprecatedElements(elements) {
92
- const els = [];
93
-
94
- elements.forEach((element) => {
95
- if (element.nested_elements?.length > 0) {
96
- element.nested_elements = filterDeprecatedElements(
97
- element.nested_elements
98
- );
99
- }
100
- if (element.nestedElements?.length > 0) {
101
- element.nestedElements = filterDeprecatedElements(element.nestedElements);
102
- }
103
- if (element.essences?.length > 0) {
104
- element.essences = element.essences.filter((essence) => {
105
- return !essence.deprecated
106
- });
107
- }
108
- if (!element.deprecated) {
109
- els.push(element);
110
- }
111
- });
112
-
113
- return els
114
- }
115
-
116
- // Returns deserialized page without deprecated content
117
- function deserializePage(pageData) {
118
- const page = deserialize(pageData);
119
- page.elements = filterDeprecatedElements(page.elements);
120
- return page
121
- }
122
-
123
- // Returns deserialized pages without deprecated content
124
- function deserializePages(pagesData) {
125
- const pages = deserialize(pagesData);
126
- pages.forEach((page) => {
127
- page.elements = filterDeprecatedElements(page.elements);
128
- });
129
- return pages
130
- }
131
-
132
- export { deserialize, deserializePage, deserializePages };
20
+ export {
21
+ r as deserialize,
22
+ d as deserializePage,
23
+ f as deserializePages
24
+ };
@@ -1,90 +1,44 @@
1
- import structuredClone from '@ungap/structured-clone';
2
-
3
- function deserialize(originalResponse, options = {}) {
4
- const response = structuredClone(originalResponse);
5
- if (!options) {
6
- options = {};
7
- }
8
-
9
- const included = response.included || [];
10
-
11
- if (Array.isArray(response.data)) {
12
- return response.data.map((data) => {
13
- return parseJsonApiSimpleResourceData(data, included, false)
14
- })
15
- } else {
16
- return parseJsonApiSimpleResourceData(
17
- response.data,
18
- included,
19
- false)
20
- }
1
+ function y(e, t = {}) {
2
+ const s = structuredClone(e);
3
+ t || (t = {});
4
+ const o = s.included || [];
5
+ return Array.isArray(s.data) ? s.data.map((i) => f(i, o, !1)) : f(
6
+ s.data,
7
+ o,
8
+ !1
9
+ );
21
10
  }
22
-
23
- function parseJsonApiSimpleResourceData(data, included, useCache, options) {
24
- if (!included.cached) {
25
- included.cached = {};
26
- }
27
-
28
- if (!(data.type in included.cached)) {
29
- included.cached[data.type] = {};
30
- }
31
-
32
- if (useCache && data.id in included.cached[data.type]) {
33
- return included.cached[data.type][data.id]
34
- }
35
-
36
- const attributes = data.attributes || {};
37
-
38
- const resource = attributes;
39
- resource.id = data.id;
40
-
41
- included.cached[data.type][data.id] = resource;
42
-
43
- if (data.relationships) {
44
- for (const relationName of Object.keys(data.relationships)) {
45
- const relationRef = data.relationships[relationName];
46
-
47
- if (Array.isArray(relationRef.data)) {
48
- const items = [];
49
-
50
- relationRef.data.forEach((relationData) => {
51
- const item = findJsonApiIncluded(
52
- included,
53
- relationData.type,
54
- relationData.id);
55
-
56
- items.push(item);
57
- });
58
-
59
- resource[relationName] = items;
60
- } else if (relationRef && relationRef.data) {
61
- resource[relationName] = findJsonApiIncluded(
62
- included,
63
- relationRef.data.type,
64
- relationRef.data.id);
65
- } else {
66
- resource[relationName] = null;
67
- }
11
+ function f(e, t, s, o) {
12
+ if (t.cached || (t.cached = {}), e.type in t.cached || (t.cached[e.type] = {}), s && e.id in t.cached[e.type])
13
+ return t.cached[e.type][e.id];
14
+ const r = e.attributes || {};
15
+ if (r.id = e.id, t.cached[e.type][e.id] = r, e.relationships)
16
+ for (const c of Object.keys(e.relationships)) {
17
+ const n = e.relationships[c];
18
+ if (Array.isArray(n.data)) {
19
+ const p = [];
20
+ n.data.forEach((a) => {
21
+ const h = u(
22
+ t,
23
+ a.type,
24
+ a.id
25
+ );
26
+ p.push(h);
27
+ }), r[c] = p;
28
+ } else n && n.data ? r[c] = u(
29
+ t,
30
+ n.data.type,
31
+ n.data.id
32
+ ) : r[c] = null;
68
33
  }
69
- }
70
-
71
- return resource
34
+ return r;
72
35
  }
73
-
74
- function findJsonApiIncluded(included, type, id, options) {
75
- let found = null;
76
-
77
- included.forEach((item) => {
78
- if (item.type === type && item.id === id) {
79
- found = parseJsonApiSimpleResourceData(item, included, true);
80
- }
81
- });
82
-
83
- if (!found) {
84
- found = { id };
85
- }
86
-
87
- return found
36
+ function u(e, t, s, o) {
37
+ let i = null;
38
+ return e.forEach((r) => {
39
+ r.type === t && r.id === s && (i = f(r, e, !0));
40
+ }), i || (i = { id: s }), i;
88
41
  }
89
-
90
- export { deserialize };
42
+ export {
43
+ y as deserialize
44
+ };
package/package.json CHANGED
@@ -1,12 +1,15 @@
1
1
  {
2
2
  "name": "@alchemy_cms/json_api",
3
- "version": "2.0.0",
3
+ "version": "3.0.1",
4
4
  "description": "JSON API deserializer for AlchemyCMS",
5
5
  "license": "BSD-3-Clause",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "git+https://github.com/AlchemyCMS/alchemy-json_api.git"
9
9
  },
10
+ "engines" : {
11
+ "node" : ">=20.19.0"
12
+ },
10
13
  "keywords": [
11
14
  "AlchemyCMS",
12
15
  "json:api",
@@ -14,6 +17,7 @@
14
17
  "headless"
15
18
  ],
16
19
  "authors": [
20
+ "Lasse Diercks",
17
21
  "Martin Meyerhoff",
18
22
  "Thomas von Deyen"
19
23
  ],
@@ -22,22 +26,15 @@
22
26
  "files": [
23
27
  "dist"
24
28
  ],
25
- "dependencies": {
26
- "@ungap/structured-clone": "^1.0.1"
27
- },
28
29
  "devDependencies": {
29
- "@babel/core": "^7.19.0",
30
- "@babel/preset-env": "^7.19.0",
31
- "@rollup/plugin-babel": "^6.0.4",
32
- "babel-jest": "^29.0.2",
33
- "jest": "^29.0.2",
34
30
  "prettier": "^3.3.3",
35
- "rollup": "^4.24.4"
31
+ "vite": "^7.0.4",
32
+ "vitest": "^3.2.4"
36
33
  },
37
34
  "scripts": {
38
- "build": "rollup --config",
35
+ "build": "vite build",
39
36
  "lint": "prettier --check 'src/**/*.js'",
40
- "test": "jest"
37
+ "test": "vitest"
41
38
  },
42
- "browserslist": "> 0.25%, not dead"
39
+ "packageManager": "pnpm@10.12.1"
43
40
  }