@stone-js/resources 0.8.18 → 0.8.19

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.
Files changed (2) hide show
  1. package/dist/index.js +105 -105
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -1,6 +1,111 @@
1
1
  import { RuntimeError, hasMetadata, getMetadata, classDecoratorLegacyWrapper, setMetadata, SERVICE_KEY, addBlueprint, methodDecoratorLegacyWrapper, addMetadata } from '@stone-js/core';
2
2
  import { cloneValue } from '@stone-js/config';
3
3
 
4
+ /**
5
+ * Returns a copy of `object` without any `undefined` values (so conditional fields simply vanish).
6
+ *
7
+ * @param object - The object to clean.
8
+ * @returns A copy without undefined values.
9
+ */
10
+ function stripUndefined(object) {
11
+ const out = {};
12
+ for (const key of Object.keys(object)) {
13
+ if (object[key] !== undefined) {
14
+ out[key] = object[key];
15
+ }
16
+ }
17
+ return out;
18
+ }
19
+ /**
20
+ * Keeps only the given keys of an object (ignoring keys that are absent).
21
+ *
22
+ * @param object - The source object.
23
+ * @param keys - The keys to keep.
24
+ * @returns A new object with only those keys.
25
+ */
26
+ function only(object, keys) {
27
+ const set = new Set(keys);
28
+ const out = {};
29
+ for (const key of Object.keys(object)) {
30
+ if (set.has(key)) {
31
+ out[key] = object[key];
32
+ }
33
+ }
34
+ return out;
35
+ }
36
+ /**
37
+ * Returns a copy of an object without the given keys.
38
+ *
39
+ * @param object - The source object.
40
+ * @param keys - The keys to drop.
41
+ * @returns A new object without those keys.
42
+ */
43
+ function except(object, keys) {
44
+ const set = new Set(keys);
45
+ const out = {};
46
+ for (const key of Object.keys(object)) {
47
+ if (!set.has(key)) {
48
+ out[key] = object[key];
49
+ }
50
+ }
51
+ return out;
52
+ }
53
+ /**
54
+ * Applies a sparse fieldset to an output: strips undefined, then narrows to the requested fields
55
+ * (when any were requested).
56
+ *
57
+ * @param output - The transformed output.
58
+ * @param fields - The requested fields (optional).
59
+ * @returns The filtered output.
60
+ */
61
+ function applyFields(output, fields) {
62
+ const clean = stripUndefined(output);
63
+ return fields !== undefined && fields.length > 0 ? only(clean, fields) : clean;
64
+ }
65
+ /**
66
+ * Build a {@link ResourceContext} from an incoming event.
67
+ *
68
+ * The parameter names are configuration, not convention: an API that already answers `?view=` or
69
+ * `?only=` keeps its own vocabulary instead of gaining a second one. Defaults are `fields`, `include`
70
+ * and `view`.
71
+ *
72
+ * The authenticated principal is read too, because deciding what a caller may see is the most common
73
+ * reason two callers get different shapes — and a resource that cannot see who is asking has to be
74
+ * told by the handler, which is exactly the plumbing this module exists to remove.
75
+ *
76
+ * Agnostic: the event only needs `get(key)`.
77
+ *
78
+ * @param event - Anything with `get(key)` (an `IncomingHttpEvent`, a URL search wrapper, …).
79
+ * @param blueprint - The blueprint carrying the parameter names, when there is one.
80
+ * @param extra - Extra context to merge in.
81
+ * @returns The resource context.
82
+ */
83
+ function contextFromEvent(event, blueprint, extra = {}) {
84
+ const names = blueprint?.get('stone.resources.params', {}) ?? {};
85
+ const fragment = event.get(names.fragment ?? 'view', '');
86
+ return {
87
+ ...extra,
88
+ event,
89
+ // `getUser()` and not `get('user')`: the principal is set through a resolver, not as metadata, so
90
+ // the generic accessor never reaches it. Duck-typed, because the kernel is agnostic and an event
91
+ // without a user simply has no such method.
92
+ principal: event.getUser?.(),
93
+ fields: splitCsv(event.get(names.fields ?? 'fields', '')),
94
+ include: splitCsv(event.get(names.include ?? 'include', '')),
95
+ fragment: fragment.length > 0 ? fragment : undefined
96
+ };
97
+ }
98
+ /**
99
+ * Split a comma-separated string into a trimmed, non-empty list (or `undefined` when empty).
100
+ *
101
+ * @param value - The CSV string.
102
+ * @returns The list, or `undefined`.
103
+ */
104
+ function splitCsv(value) {
105
+ const parts = String(value).split(',').map((part) => part.trim()).filter((part) => part.length > 0);
106
+ return parts.length > 0 ? parts : undefined;
107
+ }
108
+
4
109
  /**
5
110
  * Runs a schema and reports what it said.
6
111
  *
@@ -142,111 +247,6 @@ class ContractChecker {
142
247
  }
143
248
  }
144
249
 
145
- /**
146
- * Returns a copy of `object` without any `undefined` values (so conditional fields simply vanish).
147
- *
148
- * @param object - The object to clean.
149
- * @returns A copy without undefined values.
150
- */
151
- function stripUndefined(object) {
152
- const out = {};
153
- for (const key of Object.keys(object)) {
154
- if (object[key] !== undefined) {
155
- out[key] = object[key];
156
- }
157
- }
158
- return out;
159
- }
160
- /**
161
- * Keeps only the given keys of an object (ignoring keys that are absent).
162
- *
163
- * @param object - The source object.
164
- * @param keys - The keys to keep.
165
- * @returns A new object with only those keys.
166
- */
167
- function only(object, keys) {
168
- const set = new Set(keys);
169
- const out = {};
170
- for (const key of Object.keys(object)) {
171
- if (set.has(key)) {
172
- out[key] = object[key];
173
- }
174
- }
175
- return out;
176
- }
177
- /**
178
- * Returns a copy of an object without the given keys.
179
- *
180
- * @param object - The source object.
181
- * @param keys - The keys to drop.
182
- * @returns A new object without those keys.
183
- */
184
- function except(object, keys) {
185
- const set = new Set(keys);
186
- const out = {};
187
- for (const key of Object.keys(object)) {
188
- if (!set.has(key)) {
189
- out[key] = object[key];
190
- }
191
- }
192
- return out;
193
- }
194
- /**
195
- * Applies a sparse fieldset to an output: strips undefined, then narrows to the requested fields
196
- * (when any were requested).
197
- *
198
- * @param output - The transformed output.
199
- * @param fields - The requested fields (optional).
200
- * @returns The filtered output.
201
- */
202
- function applyFields(output, fields) {
203
- const clean = stripUndefined(output);
204
- return fields !== undefined && fields.length > 0 ? only(clean, fields) : clean;
205
- }
206
- /**
207
- * Build a {@link ResourceContext} from an incoming event.
208
- *
209
- * The parameter names are configuration, not convention: an API that already answers `?view=` or
210
- * `?only=` keeps its own vocabulary instead of gaining a second one. Defaults are `fields`, `include`
211
- * and `view`.
212
- *
213
- * The authenticated principal is read too, because deciding what a caller may see is the most common
214
- * reason two callers get different shapes — and a resource that cannot see who is asking has to be
215
- * told by the handler, which is exactly the plumbing this module exists to remove.
216
- *
217
- * Agnostic: the event only needs `get(key)`.
218
- *
219
- * @param event - Anything with `get(key)` (an `IncomingHttpEvent`, a URL search wrapper, …).
220
- * @param blueprint - The blueprint carrying the parameter names, when there is one.
221
- * @param extra - Extra context to merge in.
222
- * @returns The resource context.
223
- */
224
- function contextFromEvent(event, blueprint, extra = {}) {
225
- const names = blueprint?.get('stone.resources.params', {}) ?? {};
226
- const fragment = event.get(names.fragment ?? 'view', '');
227
- return {
228
- ...extra,
229
- event,
230
- // `getUser()` and not `get('user')`: the principal is set through a resolver, not as metadata, so
231
- // the generic accessor never reaches it. Duck-typed, because the kernel is agnostic and an event
232
- // without a user simply has no such method.
233
- principal: event.getUser?.(),
234
- fields: splitCsv(event.get(names.fields ?? 'fields', '')),
235
- include: splitCsv(event.get(names.include ?? 'include', '')),
236
- fragment: fragment.length > 0 ? fragment : undefined
237
- };
238
- }
239
- /**
240
- * Split a comma-separated string into a trimmed, non-empty list (or `undefined` when empty).
241
- *
242
- * @param value - The CSV string.
243
- * @returns The list, or `undefined`.
244
- */
245
- function splitCsv(value) {
246
- const parts = String(value).split(',').map((part) => part.trim()).filter((part) => part.length > 0);
247
- return parts.length > 0 ? parts : undefined;
248
- }
249
-
250
250
  /**
251
251
  * Raised when what a handler produced does not match the schema its resource published.
252
252
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stone-js/resources",
3
- "version": "0.8.18",
3
+ "version": "0.8.19",
4
4
  "description": "Framework-agnostic API resources for Stone.js. Shape what your domain exposes — sparse fieldsets, conditional fields, includes and envelopes — decoupled from controllers, the same on backend and frontend.",
5
5
  "author": "Mr. Stone <evensstone@gmail.com>",
6
6
  "license": "MIT",
@@ -58,7 +58,7 @@
58
58
  "typescript": "^5.6.3",
59
59
  "vitest": "^3.2.4",
60
60
  "zod": "^3.25.76",
61
- "@stone-js/service-container": "0.8.18"
61
+ "@stone-js/service-container": "0.8.19"
62
62
  },
63
63
  "ts-standard": {
64
64
  "globals": [
@@ -71,10 +71,10 @@
71
71
  ]
72
72
  },
73
73
  "dependencies": {
74
- "@stone-js/config": "0.8.18"
74
+ "@stone-js/config": "0.8.19"
75
75
  },
76
76
  "peerDependencies": {
77
- "@stone-js/core": "0.8.18"
77
+ "@stone-js/core": "0.8.19"
78
78
  },
79
79
  "scripts": {
80
80
  "lint": "ts-standard src",