yayson 4.1.0 → 4.3.0

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 (39) hide show
  1. package/README.md +43 -0
  2. package/build/legacy.cjs +27 -23
  3. package/build/legacy.d.cts +35 -7
  4. package/build/legacy.d.cts.map +1 -1
  5. package/build/legacy.d.mts +35 -7
  6. package/build/legacy.d.mts.map +1 -1
  7. package/build/legacy.mjs +27 -23
  8. package/build/legacy.mjs.map +1 -1
  9. package/build/{symbols-nFs99aEX.cjs → symbols-B--FS78o.cjs} +3 -0
  10. package/build/{symbols-DSjKJ8vh.mjs → symbols-BfU4k1el.mjs} +4 -1
  11. package/build/symbols-BfU4k1el.mjs.map +1 -0
  12. package/build/{types-NiKV-lj-.d.cts → types-KZiF6x7A.d.mts} +14 -2
  13. package/build/types-KZiF6x7A.d.mts.map +1 -0
  14. package/build/{types-Do2flKZX.d.mts → types-iC38_iCI.d.cts} +14 -2
  15. package/build/types-iC38_iCI.d.cts.map +1 -0
  16. package/build/utils.cjs +1 -1
  17. package/build/utils.d.cts +1 -1
  18. package/build/utils.d.mts +1 -1
  19. package/build/utils.mjs +1 -1
  20. package/build/{yayson-l2JKseMH.cjs → yayson-B51EJfRP.cjs} +97 -27
  21. package/build/{yayson-3UYKq2H5.d.cts → yayson-BvwMr4Ad.d.mts} +6 -4
  22. package/build/yayson-BvwMr4Ad.d.mts.map +1 -0
  23. package/build/{yayson-Ce5uGpgU.d.mts → yayson-DTMLeA5k.d.cts} +6 -4
  24. package/build/yayson-DTMLeA5k.d.cts.map +1 -0
  25. package/build/{yayson-CwZg5FNt.mjs → yayson-RzT9zsdx.mjs} +81 -29
  26. package/build/yayson-RzT9zsdx.mjs.map +1 -0
  27. package/build/yayson.cjs +1 -1
  28. package/build/yayson.d.cts +2 -2
  29. package/build/yayson.d.mts +2 -2
  30. package/build/yayson.mjs +1 -1
  31. package/package.json +1 -1
  32. package/skill/yayson/SKILL.md +20 -0
  33. package/skill/yayson/references/api.md +27 -2
  34. package/build/symbols-DSjKJ8vh.mjs.map +0 -1
  35. package/build/types-Do2flKZX.d.mts.map +0 -1
  36. package/build/types-NiKV-lj-.d.cts.map +0 -1
  37. package/build/yayson-3UYKq2H5.d.cts.map +0 -1
  38. package/build/yayson-Ce5uGpgU.d.mts.map +0 -1
  39. package/build/yayson-CwZg5FNt.mjs.map +0 -1
@@ -10,6 +10,9 @@ var Adapter = class {
10
10
  if (id == null) return;
11
11
  return `${id}`;
12
12
  }
13
+ static has(model, key) {
14
+ return key in model;
15
+ }
13
16
  };
14
17
  var adapter_default = Adapter;
15
18
 
@@ -23,4 +26,4 @@ const REL_META = Symbol("yayson.rel-meta");
23
26
 
24
27
  //#endregion
25
28
  export { TYPE as a, REL_META as i, META as n, adapter_default as o, REL_LINKS as r, LINKS as t };
26
- //# sourceMappingURL=symbols-DSjKJ8vh.mjs.map
29
+ //# sourceMappingURL=symbols-BfU4k1el.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"symbols-BfU4k1el.mjs","names":[],"sources":["../src/yayson/adapter.ts","../src/yayson/symbols.ts"],"sourcesContent":["export type ModelLike = object\n\nclass Adapter {\n static get(model: ModelLike): Record<string, unknown>\n static get(model: ModelLike, key: string): unknown\n static get(model: ModelLike, key?: string): unknown {\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- ModelLike is object for flexibility, cast for dynamic access\n const obj = model as Record<string, unknown>\n if (key) {\n return obj[key]\n }\n return obj\n }\n\n static id(model: ModelLike): string | undefined {\n const id = this.get(model, 'id')\n if (id == null) {\n return undefined\n }\n return `${id}`\n }\n\n static has(model: ModelLike, key: string): boolean {\n return key in model\n }\n}\n\nexport default Adapter\n","export const TYPE: unique symbol = Symbol('yayson.type')\nexport const LINKS: unique symbol = Symbol('yayson.links')\nexport const META: unique symbol = Symbol('yayson.meta')\nexport const REL_LINKS: unique symbol = Symbol('yayson.rel-links')\nexport const REL_META: unique symbol = Symbol('yayson.rel-meta')\n"],"mappings":";AAEA,IAAM,UAAN,MAAc;CAGZ,OAAO,IAAI,OAAkB,KAAuB;EAElD,MAAM,MAAM;AACZ,MAAI,IACF,QAAO,IAAI;AAEb,SAAO;;CAGT,OAAO,GAAG,OAAsC;EAC9C,MAAM,KAAK,KAAK,IAAI,OAAO,KAAK;AAChC,MAAI,MAAM,KACR;AAEF,SAAO,GAAG;;CAGZ,OAAO,IAAI,OAAkB,KAAsB;AACjD,SAAO,OAAO;;;AAIlB,sBAAe;;;;AC3Bf,MAAa,OAAsB,OAAO,cAAc;AACxD,MAAa,QAAuB,OAAO,eAAe;AAC1D,MAAa,OAAsB,OAAO,cAAc;AACxD,MAAa,YAA2B,OAAO,mBAAmB;AAClE,MAAa,WAA0B,OAAO,kBAAkB"}
@@ -4,6 +4,7 @@ declare class Adapter {
4
4
  static get(model: ModelLike): Record<string, unknown>;
5
5
  static get(model: ModelLike, key: string): unknown;
6
6
  static id(model: ModelLike): string | undefined;
7
+ static has(model: ModelLike, key: string): boolean;
7
8
  }
8
9
  //#endregion
9
10
  //#region src/yayson/symbols.d.ts
@@ -46,6 +47,17 @@ interface JsonApiRelationship {
46
47
  links?: JsonApiLink;
47
48
  meta?: Record<string, unknown>;
48
49
  }
50
+ /**
51
+ * Extended relationship descriptor for `relationships()` return values.
52
+ * `hasMany: true` renders empty/missing as `data: []` (spec-compliant to-many).
53
+ * `optional: true` omits the relationship when its key is absent on the instance
54
+ * (or renders `links` only); explicit `null`/`[]` still render normally.
55
+ */
56
+ interface RelationshipConfig<P> {
57
+ presenter: P;
58
+ hasMany?: boolean;
59
+ optional?: boolean;
60
+ }
49
61
  interface JsonApiRelationships {
50
62
  [key: string]: JsonApiRelationship;
51
63
  }
@@ -125,5 +137,5 @@ interface LegacyStoreOptions<S extends SchemaRegistry = SchemaRegistry> {
125
137
  strict?: boolean;
126
138
  }
127
139
  //#endregion
128
- export { REL_META as C, ModelLike as E, REL_LINKS as S, Adapter as T, StoreResult as _, JsonApiRelationship as a, LINKS as b, LegacyPresenterOptions as c, SchemaRegistry as d, StoreModel as f, StoreRecord as g, StoreOptions as h, JsonApiLinks as i, LegacyStoreOptions as l, StoreModels as m, JsonApiDocument as n, JsonApiRelationships as o, StoreModelWithOptionalId as p, JsonApiLink as r, JsonApiResource as s, InferModelType as t, PresenterOptions as u, ValidationError as v, TYPE as w, META as x, ZodLikeSchema as y };
129
- //# sourceMappingURL=types-NiKV-lj-.d.cts.map
140
+ export { REL_LINKS as C, ModelLike as D, Adapter as E, META as S, TYPE as T, StoreRecord as _, JsonApiRelationship as a, ZodLikeSchema as b, LegacyPresenterOptions as c, RelationshipConfig as d, SchemaRegistry as f, StoreOptions as g, StoreModels as h, JsonApiLinks as i, LegacyStoreOptions as l, StoreModelWithOptionalId as m, JsonApiDocument as n, JsonApiRelationships as o, StoreModel as p, JsonApiLink as r, JsonApiResource as s, InferModelType as t, PresenterOptions as u, StoreResult as v, REL_META as w, LINKS as x, ValidationError as y };
141
+ //# sourceMappingURL=types-KZiF6x7A.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types-KZiF6x7A.d.mts","names":[],"sources":["../../../../../../yayson/adapter.ts","../../../../../../yayson/symbols.ts","../../../../../../yayson/schema.ts","../../../../../../yayson/types.ts"],"mappings":"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"}
@@ -4,6 +4,7 @@ declare class Adapter {
4
4
  static get(model: ModelLike): Record<string, unknown>;
5
5
  static get(model: ModelLike, key: string): unknown;
6
6
  static id(model: ModelLike): string | undefined;
7
+ static has(model: ModelLike, key: string): boolean;
7
8
  }
8
9
  //#endregion
9
10
  //#region src/yayson/symbols.d.ts
@@ -46,6 +47,17 @@ interface JsonApiRelationship {
46
47
  links?: JsonApiLink;
47
48
  meta?: Record<string, unknown>;
48
49
  }
50
+ /**
51
+ * Extended relationship descriptor for `relationships()` return values.
52
+ * `hasMany: true` renders empty/missing as `data: []` (spec-compliant to-many).
53
+ * `optional: true` omits the relationship when its key is absent on the instance
54
+ * (or renders `links` only); explicit `null`/`[]` still render normally.
55
+ */
56
+ interface RelationshipConfig<P> {
57
+ presenter: P;
58
+ hasMany?: boolean;
59
+ optional?: boolean;
60
+ }
49
61
  interface JsonApiRelationships {
50
62
  [key: string]: JsonApiRelationship;
51
63
  }
@@ -125,5 +137,5 @@ interface LegacyStoreOptions<S extends SchemaRegistry = SchemaRegistry> {
125
137
  strict?: boolean;
126
138
  }
127
139
  //#endregion
128
- export { REL_META as C, ModelLike as E, REL_LINKS as S, Adapter as T, StoreResult as _, JsonApiRelationship as a, LINKS as b, LegacyPresenterOptions as c, SchemaRegistry as d, StoreModel as f, StoreRecord as g, StoreOptions as h, JsonApiLinks as i, LegacyStoreOptions as l, StoreModels as m, JsonApiDocument as n, JsonApiRelationships as o, StoreModelWithOptionalId as p, JsonApiLink as r, JsonApiResource as s, InferModelType as t, PresenterOptions as u, ValidationError as v, TYPE as w, META as x, ZodLikeSchema as y };
129
- //# sourceMappingURL=types-Do2flKZX.d.mts.map
140
+ export { REL_LINKS as C, ModelLike as D, Adapter as E, META as S, TYPE as T, StoreRecord as _, JsonApiRelationship as a, ZodLikeSchema as b, LegacyPresenterOptions as c, RelationshipConfig as d, SchemaRegistry as f, StoreOptions as g, StoreModels as h, JsonApiLinks as i, LegacyStoreOptions as l, StoreModelWithOptionalId as m, JsonApiDocument as n, JsonApiRelationships as o, StoreModel as p, JsonApiLink as r, JsonApiResource as s, InferModelType as t, PresenterOptions as u, StoreResult as v, REL_META as w, LINKS as x, ValidationError as y };
141
+ //# sourceMappingURL=types-iC38_iCI.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types-iC38_iCI.d.cts","names":[],"sources":["../../../../../../yayson/adapter.ts","../../../../../../yayson/symbols.ts","../../../../../../yayson/schema.ts","../../../../../../yayson/types.ts"],"mappings":";KAAY,SAAA;AAAA,cAEN,OAAA;EAAA,OACG,GAAA,CAAI,KAAA,EAAO,SAAA,GAAY,MAAA;EAAA,OACvB,GAAA,CAAI,KAAA,EAAO,SAAA,EAAW,GAAA;EAAA,OAUtB,EAAA,CAAG,KAAA,EAAO,SAAA;EAAA,OAQV,GAAA,CAAI,KAAA,EAAO,SAAA,EAAW,GAAA;AAAA;;;cCtBlB,IAAA;AAAA,cACA,KAAA;AAAA,cACA,IAAA;AAAA,cACA,SAAA;AAAA,cACA,QAAA;;;;ADJb;;;UEIiB,aAAA;EACf,KAAA,GAAQ,IAAA;EACR,SAAA,GAAY,IAAA;IAAoB,OAAA;IAAe,IAAA;EAAA;IAAoB,OAAA;IAAgB,KAAA;EAAA;AAAA;;;UCHpE,WAAA,SAAoB,MAAA;EACnC,IAAA;EACA,OAAA;AAAA;AAAA,UAGe,YAAA;EAAA,CACd,GAAA,WAAc,WAAA;AAAA;AAAA,UAGA,yBAAA;EACf,EAAA;EACA,IAAA;AAAA;AAAA,UAGe,mBAAA;EACf,IAAA,GAAO,yBAAA,GAA4B,yBAAA;EACnC,KAAA,GAAQ,WAAA;EACR,IAAA,GAAO,MAAA;AAAA;;;;;;;UASQ,kBAAA;EACf,SAAA,EAAW,CAAA;EACX,OAAA;EACA,QAAA;AAAA;AAAA,UAGe,oBAAA;EAAA,CACd,GAAA,WAAc,mBAAA;AAAA;AAAA,UAGA,eAAA;EACf,EAAA;EACA,IAAA;EACA,UAAA,GAAa,MAAA;EACb,aAAA,GAAgB,oBAAA;EAChB,KAAA,GAAQ,WAAA;EACR,IAAA,GAAO,MAAA;AAAA;AAAA,UAGQ,eAAA;EACf,IAAA,EAAM,eAAA,GAAkB,eAAA;EACxB,QAAA,GAAW,eAAA;EACX,KAAA,GAAQ,YAAA;EACR,IAAA,GAAO,MAAA;AAAA;AAAA,UAGQ,gBAAA;EACf,IAAA,GAAO,MAAA;EACP,KAAA,GAAQ,YAAA;EACR,OAAA;AAAA;AAAA,UAGe,sBAAA,SAA+B,gBAAA;EAC9C,aAAA;AAAA;AAAA,UAGe,WAAA;EACf,EAAA;EACA,IAAA;EACA,UAAA,GAAa,MAAA;EACb,aAAA,GAAgB,oBAAA;EAChB,KAAA,GAAQ,WAAA;EACR,IAAA,GAAO,MAAA;AAAA;AAAA,UAGQ,UAAA,SAAmB,MAAA;EAClC,EAAA;EAAA,CACC,IAAA;EAAA,CACA,KAAA,IAAS,WAAA;EAAA,CACT,IAAA,IAAQ,MAAA;EAAA,CACR,SAAA,IAAa,WAAA;EAAA,CACb,QAAA,IAAY,MAAA;AAAA;;UAIE,wBAAA,SAAiC,MAAA;EAChD,EAAA;EAAA,CACC,IAAA;EAAA,CACA,KAAA,IAAS,WAAA;EAAA,CACT,IAAA,IAAQ,MAAA;AAAA;AAAA,UAGM,WAAA;EAAA,CACd,IAAA;IAAA,CACE,EAAA,WAAa,UAAA;EAAA;AAAA;AAAA,UAID,cAAA;EAAA,CACd,IAAA,WAAe,aAAA;AAAA;AAAA,KAIN,eAAA,WAA0B,MAAA;EACpC,KAAA,GAAQ,IAAA;EACR,SAAA,GAAY,IAAA;AAAA,IAEV,UAAA;AAAA,KAGQ,cAAA,sCAAoD,QAAA,SAAiB,cAAA,GAC7E,QAAA,eAAuB,QAAA,GACrB,eAAA,CAAgB,QAAA,CAAS,QAAA,KACzB,UAAA,GACF,UAAA;AAAA,UAEa,WAAA,KAAgB,UAAA,UAAoB,KAAA,CAAM,CAAA;EAAA,CACxD,IAAA,IAAQ,MAAA;AAAA;AAAA,UAGM,YAAA,WAAuB,cAAA,GAAiB,cAAA;EACvD,OAAA,GAAU,CAAA;EACV,MAAA;AAAA;AAAA,UAGe,eAAA;EACf,IAAA;EACA,EAAA;EACA,KAAA;AAAA;AAAA,UAGe,kBAAA,WAA6B,cAAA,GAAiB,cAAA;EAC7D,KAAA,GAAQ,MAAA;EACR,OAAA,GAAU,CAAA;EACV,MAAA;AAAA"}
package/build/utils.cjs CHANGED
@@ -1,4 +1,4 @@
1
- const require_symbols = require('./symbols-nFs99aEX.cjs');
1
+ const require_symbols = require('./symbols-B--FS78o.cjs');
2
2
 
3
3
  //#region src/utils.ts
4
4
  function getType(model) {
package/build/utils.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { C as REL_META, E as ModelLike, S as REL_LINKS, T as Adapter, b as LINKS, f as StoreModel, r as JsonApiLink, w as TYPE, x as META } from "./types-NiKV-lj-.cjs";
1
+ import { C as REL_LINKS, D as ModelLike, E as Adapter, S as META, T as TYPE, p as StoreModel, r as JsonApiLink, w as REL_META, x as LINKS } from "./types-iC38_iCI.cjs";
2
2
 
3
3
  //#region src/utils.d.ts
4
4
  declare function getType(model: StoreModel): string | undefined;
package/build/utils.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { C as REL_META, E as ModelLike, S as REL_LINKS, T as Adapter, b as LINKS, f as StoreModel, r as JsonApiLink, w as TYPE, x as META } from "./types-Do2flKZX.mjs";
1
+ import { C as REL_LINKS, D as ModelLike, E as Adapter, S as META, T as TYPE, p as StoreModel, r as JsonApiLink, w as REL_META, x as LINKS } from "./types-KZiF6x7A.mjs";
2
2
 
3
3
  //#region src/utils.d.ts
4
4
  declare function getType(model: StoreModel): string | undefined;
package/build/utils.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { a as TYPE, i as REL_META, n as META, o as adapter_default, r as REL_LINKS, t as LINKS } from "./symbols-DSjKJ8vh.mjs";
1
+ import { a as TYPE, i as REL_META, n as META, o as adapter_default, r as REL_LINKS, t as LINKS } from "./symbols-BfU4k1el.mjs";
2
2
 
3
3
  //#region src/utils.ts
4
4
  function getType(model) {
@@ -1,4 +1,4 @@
1
- const require_symbols = require('./symbols-nFs99aEX.cjs');
1
+ const require_symbols = require('./symbols-B--FS78o.cjs');
2
2
 
3
3
  //#region src/yayson/adapters/sequelize.ts
4
4
  function isSequelizeModel(model) {
@@ -8,6 +8,10 @@ var SequelizeAdapter = class extends require_symbols.adapter_default {
8
8
  static get(model, key) {
9
9
  if (isSequelizeModel(model)) return model.get(key);
10
10
  }
11
+ static has(model, key) {
12
+ if (isSequelizeModel(model)) return model.get(key) !== void 0;
13
+ return false;
14
+ }
11
15
  static id(model) {
12
16
  const pkFields = model.constructor && "primaryKeys" in model.constructor ? Object.keys(model.constructor.primaryKeys) : ["id"];
13
17
  if (pkFields.length > 1) throw new Error("YAYSON does not support Sequelize models with composite primary keys. You can only use one column for your primary key. Currently using: " + pkFields.join(","));
@@ -69,23 +73,32 @@ function createPresenter(adapter) {
69
73
  const result = [];
70
74
  if (!relationships) return result;
71
75
  for (const key in relationships) {
72
- const factory = relationships[key];
73
- if (!factory) throw new Error(`Presenter for ${key} in ${this.constructor.type} is not defined`);
74
- const presenter = new factory(scope);
76
+ const entry = relationships[key];
77
+ if (!entry) throw new Error(`Presenter for ${key} in ${this.constructor.type} is not defined`);
78
+ const presenter = new (typeof entry === "function" ? entry : entry.presenter)(scope);
75
79
  const data = this.constructor.adapter.get(instance, key);
76
80
  result.push(presenter.toJSON(data, { include: true }));
77
81
  }
78
82
  return result;
79
83
  }
80
- buildRelationships(instance) {
84
+ buildRelationships(instance, options = {}) {
81
85
  if (instance == null) return null;
82
86
  const rels = this.relationships();
83
87
  const links = this.links(instance) || {};
88
+ const isPayload = options.payload === true;
84
89
  let relationships = null;
85
90
  if (!rels) return null;
86
91
  for (const key in rels) {
92
+ const entry = rels[key];
93
+ if (!entry) continue;
94
+ const isConfig = typeof entry !== "function";
95
+ const presenter = isConfig ? entry.presenter : entry;
96
+ const hasMany = isConfig ? entry.hasMany : void 0;
97
+ const optional = isConfig ? entry.optional ?? false : false;
98
+ const linkValue = links[key];
99
+ const hasLinks = linkValue != null;
87
100
  const data = this.constructor.adapter.get(instance, key);
88
- const presenter = rels[key];
101
+ const keyPresent = this.constructor.adapter.has(instance, key);
89
102
  const buildData = (d) => {
90
103
  const id = this.constructor.adapter.id(d);
91
104
  if (!id) throw new Error(`Model of type ${presenter.type} is missing an id (relationship '${key}' of ${this.constructor.type})`);
@@ -94,19 +107,21 @@ function createPresenter(adapter) {
94
107
  type: presenter.type
95
108
  };
96
109
  };
97
- const build = (d) => {
98
- const rel = {};
99
- if (d != null) rel.data = buildData(d);
100
- if (links[key] != null) rel.links = buildLinks(links[key]);
101
- else if (d == null) rel.data = null;
102
- return rel;
103
- };
110
+ if (optional && !keyPresent && !isPayload) {
111
+ if (hasLinks) {
112
+ if (!relationships) relationships = {};
113
+ relationships[key] = { links: buildLinks(linkValue) };
114
+ }
115
+ continue;
116
+ }
104
117
  if (!relationships) relationships = {};
105
- if (!relationships[key]) relationships[key] = {};
106
- if (Array.isArray(data)) {
107
- relationships[key].data = data.map(buildData);
108
- if (links[key] != null) relationships[key].links = buildLinks(links[key]);
109
- } else relationships[key] = build(data);
118
+ const rel = {};
119
+ if (Array.isArray(data)) rel.data = data.map(buildData);
120
+ else if (data != null) rel.data = buildData(data);
121
+ else if (hasMany === true) rel.data = [];
122
+ else if (!hasLinks) rel.data = null;
123
+ if (hasLinks) rel.links = buildLinks(linkValue);
124
+ relationships[key] = rel;
110
125
  }
111
126
  return relationships;
112
127
  }
@@ -159,7 +174,7 @@ function createPresenter(adapter) {
159
174
  };
160
175
  const id = this.id(instance);
161
176
  if (id != null) model.id = id;
162
- const relationships = this.buildRelationships(instance);
177
+ const relationships = this.buildRelationships(instance, { payload: true });
163
178
  if (relationships != null) model.relationships = relationships;
164
179
  const result = { data: model };
165
180
  const opts = options ?? {};
@@ -208,6 +223,42 @@ function validate(schema, data, strict) {
208
223
  }
209
224
  }
210
225
 
226
+ //#endregion
227
+ //#region src/yayson/safe.ts
228
+ /**
229
+ * Defenses against prototype pollution, since yayson keys lookup tables and
230
+ * model objects by strings from untrusted documents (`type`, `id`, member
231
+ * names). Per MDN's guidance
232
+ * (https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/Prototype_pollution):
233
+ * caches use null-prototype objects, and "__proto__"/"constructor"/"prototype"
234
+ * are rejected as member names.
235
+ */
236
+ /** Null-prototype lookup table (MDN's `Object.create(null)`, never `obj.__proto__`). */
237
+ function safeObject() {
238
+ return Object.create(null);
239
+ }
240
+ /**
241
+ * Normalize a caller-supplied cache to a null-prototype object, so a public
242
+ * `models` argument that happens to be a plain `{}` can't let type="__proto__"
243
+ * resolve through Object.prototype. Already-null-prototype caches pass through
244
+ * unchanged (preserving identity/memoization).
245
+ */
246
+ function safeCache(cache) {
247
+ if (cache === void 0) return safeObject();
248
+ if (Object.getPrototypeOf(cache) === null) return cache;
249
+ const safe = safeObject();
250
+ Object.assign(safe, cache);
251
+ return safe;
252
+ }
253
+ const UNSAFE_KEYS = new Set([
254
+ "__proto__",
255
+ "constructor",
256
+ "prototype"
257
+ ]);
258
+ function isUnsafeKey(key) {
259
+ return UNSAFE_KEYS.has(key);
260
+ }
261
+
211
262
  //#endregion
212
263
  //#region src/yayson/store.ts
213
264
  function hasId(model) {
@@ -249,7 +300,7 @@ var Store = class Store {
249
300
  return stub;
250
301
  }
251
302
  #createModel(resource, options) {
252
- const models = options?.models ?? {};
303
+ const models = options?.models ?? safeObject();
253
304
  const model = { ...resource.attributes || {} };
254
305
  if (resource.id != null) model.id = resource.id;
255
306
  const type = resource.type;
@@ -258,7 +309,7 @@ var Store = class Store {
258
309
  if (resource.links != null) model[require_symbols.LINKS] = resource.links;
259
310
  if (hasId(model)) {
260
311
  const idStr = String(model.id);
261
- if (!models[type]) models[type] = {};
312
+ if (!models[type]) models[type] = safeObject();
262
313
  if (!models[type][idStr]) models[type][idStr] = model;
263
314
  }
264
315
  if (resource.relationships != null) {
@@ -271,7 +322,8 @@ var Store = class Store {
271
322
  }
272
323
  #resolveRelationships(model, relationships, resolver, options) {
273
324
  const includeRelMeta = options?.includeRelMeta ?? true;
274
- for (const key in relationships) {
325
+ for (const key of Object.keys(relationships)) {
326
+ if (isUnsafeKey(key)) continue;
275
327
  const { data, links, meta } = relationships[key];
276
328
  model[key] = null;
277
329
  if (data == null && links == null) continue;
@@ -293,7 +345,7 @@ var Store = class Store {
293
345
  }
294
346
  }
295
347
  toModel(rec, type, models) {
296
- const model = this.#createModel(rec, { models });
348
+ const model = this.#createModel(rec, { models: safeCache(models) });
297
349
  if (this.schemas && this.schemas[rec.type]) {
298
350
  const schema = this.schemas[rec.type];
299
351
  const result = validate(schema, model, this.strict);
@@ -326,14 +378,14 @@ var Store = class Store {
326
378
  return this.toModel(rec, type, models);
327
379
  }
328
380
  find(type, id, models) {
329
- return this.#findModel(type, id, models ?? {});
381
+ return this.#findModel(type, id, safeCache(models));
330
382
  }
331
383
  findAll(type, models) {
332
- const modelsObj = models ?? {};
384
+ const modelsObj = safeCache(models);
333
385
  const recs = this.findRecords(type);
334
386
  if (recs == null) return [];
335
387
  recs.forEach((rec) => {
336
- if (!modelsObj[type]) modelsObj[type] = {};
388
+ if (!modelsObj[type]) modelsObj[type] = safeObject();
337
389
  return this.toModel(rec, type, modelsObj);
338
390
  });
339
391
  return Object.values(modelsObj[type] || {});
@@ -382,7 +434,7 @@ var Store = class Store {
382
434
  try {
383
435
  syncData(body.included);
384
436
  const recs = syncData(body.data);
385
- const models = {};
437
+ const models = safeObject();
386
438
  const result = recs.map((rec) => this.toModel(rec, rec.type, models));
387
439
  if (body.meta != null) result[require_symbols.META] = body.meta;
388
440
  return result;
@@ -454,6 +506,24 @@ Object.defineProperty(exports, 'filterByFields', {
454
506
  return filterByFields;
455
507
  }
456
508
  });
509
+ Object.defineProperty(exports, 'isUnsafeKey', {
510
+ enumerable: true,
511
+ get: function () {
512
+ return isUnsafeKey;
513
+ }
514
+ });
515
+ Object.defineProperty(exports, 'safeCache', {
516
+ enumerable: true,
517
+ get: function () {
518
+ return safeCache;
519
+ }
520
+ });
521
+ Object.defineProperty(exports, 'safeObject', {
522
+ enumerable: true,
523
+ get: function () {
524
+ return safeObject;
525
+ }
526
+ });
457
527
  Object.defineProperty(exports, 'validate', {
458
528
  enumerable: true,
459
529
  get: function () {
@@ -1,4 +1,4 @@
1
- import { E as ModelLike, T as Adapter, _ as StoreResult, d as SchemaRegistry, f as StoreModel, g as StoreRecord$1, h as StoreOptions, i as JsonApiLinks, m as StoreModels, n as JsonApiDocument, o as JsonApiRelationships, p as StoreModelWithOptionalId, r as JsonApiLink, t as InferModelType, u as PresenterOptions, v as ValidationError } from "./types-NiKV-lj-.cjs";
1
+ import { D as ModelLike, E as Adapter, _ as StoreRecord$1, d as RelationshipConfig, f as SchemaRegistry, g as StoreOptions, h as StoreModels, i as JsonApiLinks, m as StoreModelWithOptionalId, n as JsonApiDocument, o as JsonApiRelationships, p as StoreModel, r as JsonApiLink, t as InferModelType, u as PresenterOptions, v as StoreResult, y as ValidationError } from "./types-KZiF6x7A.mjs";
2
2
 
3
3
  //#region src/yayson/presenter.d.ts
4
4
  declare function createPresenter(adapter: typeof Adapter): {
@@ -8,10 +8,12 @@ declare function createPresenter(adapter: typeof Adapter): {
8
8
  id(instance: ModelLike): string | undefined;
9
9
  selfLinks(_instance: ModelLike): JsonApiLink | string | undefined;
10
10
  links(_instance?: ModelLike): JsonApiLinks | undefined;
11
- relationships(): Record<string, /*elided*/any>;
11
+ relationships(): Record<string, /*elided*/any | RelationshipConfig< /*elided*/any>>;
12
12
  attributes(instance: ModelLike | null): Record<string, unknown>;
13
13
  includeRelationships(scope: JsonApiDocument, instance: ModelLike): unknown[];
14
- buildRelationships(instance: ModelLike | null): JsonApiRelationships | null;
14
+ buildRelationships(instance: ModelLike | null, options?: {
15
+ payload?: boolean;
16
+ }): JsonApiRelationships | null;
15
17
  buildSelfLink(instance: ModelLike): JsonApiLink | undefined;
16
18
  toJSON(instanceOrCollection: ModelLike | ModelLike[] | null | undefined, options?: PresenterOptions): JsonApiDocument;
17
19
  payload(instance: ModelLike, options?: PresenterOptions): JsonApiDocument;
@@ -78,4 +80,4 @@ interface YaysonResult {
78
80
  declare function yayson(options?: YaysonOptions): YaysonResult;
79
81
  //#endregion
80
82
  export { Presenter as i, YaysonResult as n, yayson as r, YaysonOptions as t };
81
- //# sourceMappingURL=yayson-3UYKq2H5.d.cts.map
83
+ //# sourceMappingURL=yayson-BvwMr4Ad.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"yayson-BvwMr4Ad.d.mts","names":[],"sources":["../../../../../../yayson/presenter.ts","../../../../../../yayson/store.ts","../../../../../../yayson.ts"],"mappings":";;;iBA0BwB,eAAA,CAAgB,OAAA,SAAgB,OAAA;EAAA,aAUhC,eAAA;iBAVe;WAQ5B,eAAA;iBAMM,SAAA;yBAIQ,SAAA,GAAY,WAAA;sBAIf,SAAA,GAAY,YAAA;qBAIb,MAAA,SAZJ,gBAYsC,kBAAA,EAZtC;yBAgBQ,SAAA,UAAmB,MAAA;gCAiBZ,eAAA,EAAe,QAAA,EAAY,SAAA;iCAqB1B,SAAA,SAAgB,OAAA;MAAa,OAAA;IAAA,IAA2B,oBAAA;4BAuE7D,SAAA,GAAY,WAAA;iCAKZ,SAAA,GAAY,SAAA,uBAA8B,OAAA,GACtD,gBAAA,GACT,eAAA;sBA8Ee,SAAA,EAAS,OAAA,GAAY,gBAAA,GAAmB,eAAA;iCA0B7B,SAAA,GAAY,SAAA,WAAkB,OAAA,GAAY,gBAAA,GAAmB,eAAA;EAAA;;;;+BAItD,SAAA,GAAY,SAAA,WAAkB,OAAA,GAAY,gBAAA,GAAmB,eAAA;+BAI7D,SAAA,GAAY,SAAA,WAAkB,OAAA,GAAY,gBAAA,GAAmB,eAAA;oBAIxE,SAAA,EAAS,OAAA,GAAY,gBAAA,GAAmB,eAAA;AAAA;AAAA,KAQzD,SAAA,GAAY,UAAA,QAAkB,eAAA;;;cCjRpC,WAAA,YAAuB,aAAA;EAC3B,EAAA;EACA,IAAA;EACA,UAAA,GAAa,MAAA;EACb,aAAA,GAAgB,oBAAA;EAChB,KAAA,GAAQ,WAAA;EACR,IAAA,GAAO,MAAA;cAEK,OAAA,EAAS,aAAA;AAAA;AAAA,cAUF,KAAA,WAAgB,cAAA,GAAiB,cAAA;EAAA;EACpD,OAAA,EAAS,WAAA;EACT,OAAA,GAAU,CAAA;EACV,MAAA;EACA,gBAAA,EAAkB,eAAA;cAEN,OAAA,GAAU,YAAA,CAAa,CAAA;EAMnC,KAAA,CAAA;EAuGA,OAAA,CAAQ,GAAA,EAAK,WAAA,EAAa,IAAA,UAAc,MAAA,EAAQ,WAAA,GAAc,UAAA;EA+B9D,UAAA,CAAW,IAAA,UAAc,EAAA,oBAAsB,WAAA;EAK/C,WAAA,CAAY,IAAA,WAAe,WAAA;EAiB3B,IAAA,kBAAA,CAAuB,IAAA,EAAM,CAAA,EAAG,EAAA,mBAAqB,MAAA,GAAS,WAAA,GAAc,cAAA,CAAe,CAAA,EAAG,CAAA;EAM9F,OAAA,kBAAA,CAA0B,IAAA,EAAM,CAAA,EAAG,MAAA,GAAS,WAAA,GAAc,cAAA,CAAe,CAAA,EAAG,CAAA;EAgB5E,MAAA,CAAO,IAAA,UAAc,EAAA;EAkBrB,OAAA,CAAQ,IAAA,EAAM,eAAA,GAAkB,WAAA;EA8DhC,IAAA,CAAK,IAAA,EAAM,eAAA,GAAkB,UAAA,GAAa,WAAA;ED5I5B;;;;;;;EAAA,OC+JP,KAAA,CAAM,IAAA,EAAM,eAAA,GAAkB,wBAAA;EAIrC,KAAA,CAAM,IAAA,EAAM,eAAA,GAAkB,wBAAA;EAQ9B,QAAA,kBAAA,CAA2B,IAAA,EAAM,CAAA,EAAG,IAAA,EAAM,eAAA,GAAkB,cAAA,CAAe,CAAA,EAAG,CAAA;EAW9E,WAAA,kBAAA,CAA8B,IAAA,EAAM,CAAA,EAAG,IAAA,EAAM,eAAA,GAAkB,WAAA,CAAY,cAAA,CAAe,CAAA,EAAG,CAAA;AAAA;;;KC9U1F,aAAA,mBAAgC,OAAA;AAAA,UAE3B,aAAA;EACR,OAAA,GAAU,aAAA;AAAA;AAAA,UAGF,YAAA;EACR,KAAA,SAAc,KAAA;EACd,SAAA,EAAW,SAAA;EACX,OAAA,SAAgB,OAAA;AAAA;AAAA,iBAgBT,MAAA,CAAO,OAAA,GAAU,aAAA,GAAgB,YAAA"}
@@ -1,4 +1,4 @@
1
- import { E as ModelLike, T as Adapter, _ as StoreResult, d as SchemaRegistry, f as StoreModel, g as StoreRecord$1, h as StoreOptions, i as JsonApiLinks, m as StoreModels, n as JsonApiDocument, o as JsonApiRelationships, p as StoreModelWithOptionalId, r as JsonApiLink, t as InferModelType, u as PresenterOptions, v as ValidationError } from "./types-Do2flKZX.mjs";
1
+ import { D as ModelLike, E as Adapter, _ as StoreRecord$1, d as RelationshipConfig, f as SchemaRegistry, g as StoreOptions, h as StoreModels, i as JsonApiLinks, m as StoreModelWithOptionalId, n as JsonApiDocument, o as JsonApiRelationships, p as StoreModel, r as JsonApiLink, t as InferModelType, u as PresenterOptions, v as StoreResult, y as ValidationError } from "./types-iC38_iCI.cjs";
2
2
 
3
3
  //#region src/yayson/presenter.d.ts
4
4
  declare function createPresenter(adapter: typeof Adapter): {
@@ -8,10 +8,12 @@ declare function createPresenter(adapter: typeof Adapter): {
8
8
  id(instance: ModelLike): string | undefined;
9
9
  selfLinks(_instance: ModelLike): JsonApiLink | string | undefined;
10
10
  links(_instance?: ModelLike): JsonApiLinks | undefined;
11
- relationships(): Record<string, /*elided*/any>;
11
+ relationships(): Record<string, /*elided*/any | RelationshipConfig< /*elided*/any>>;
12
12
  attributes(instance: ModelLike | null): Record<string, unknown>;
13
13
  includeRelationships(scope: JsonApiDocument, instance: ModelLike): unknown[];
14
- buildRelationships(instance: ModelLike | null): JsonApiRelationships | null;
14
+ buildRelationships(instance: ModelLike | null, options?: {
15
+ payload?: boolean;
16
+ }): JsonApiRelationships | null;
15
17
  buildSelfLink(instance: ModelLike): JsonApiLink | undefined;
16
18
  toJSON(instanceOrCollection: ModelLike | ModelLike[] | null | undefined, options?: PresenterOptions): JsonApiDocument;
17
19
  payload(instance: ModelLike, options?: PresenterOptions): JsonApiDocument;
@@ -78,4 +80,4 @@ interface YaysonResult {
78
80
  declare function yayson(options?: YaysonOptions): YaysonResult;
79
81
  //#endregion
80
82
  export { Presenter as i, YaysonResult as n, yayson as r, YaysonOptions as t };
81
- //# sourceMappingURL=yayson-Ce5uGpgU.d.mts.map
83
+ //# sourceMappingURL=yayson-DTMLeA5k.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"yayson-DTMLeA5k.d.cts","names":[],"sources":["../../../../../../yayson/presenter.ts","../../../../../../yayson/store.ts","../../../../../../yayson.ts"],"mappings":";;;iBA0BwB,eAAA,CAAgB,OAAA,SAAgB,OAAA;EAAA,aAUhC,eAAA;iBAVe;WAQ5B,eAAA;iBAMM,SAAA;yBAIQ,SAAA,GAAY,WAAA;sBAIf,SAAA,GAAY,YAAA;qBAIb,MAAA,SAZJ,gBAYsC,kBAAA,EAZtC;yBAgBQ,SAAA,UAAmB,MAAA;gCAiBZ,eAAA,EAAe,QAAA,EAAY,SAAA;iCAqB1B,SAAA,SAAgB,OAAA;MAAa,OAAA;IAAA,IAA2B,oBAAA;4BAuE7D,SAAA,GAAY,WAAA;iCAKZ,SAAA,GAAY,SAAA,uBAA8B,OAAA,GACtD,gBAAA,GACT,eAAA;sBA8Ee,SAAA,EAAS,OAAA,GAAY,gBAAA,GAAmB,eAAA;iCA0B7B,SAAA,GAAY,SAAA,WAAkB,OAAA,GAAY,gBAAA,GAAmB,eAAA;EAAA;;;;+BAItD,SAAA,GAAY,SAAA,WAAkB,OAAA,GAAY,gBAAA,GAAmB,eAAA;+BAI7D,SAAA,GAAY,SAAA,WAAkB,OAAA,GAAY,gBAAA,GAAmB,eAAA;oBAIxE,SAAA,EAAS,OAAA,GAAY,gBAAA,GAAmB,eAAA;AAAA;AAAA,KAQzD,SAAA,GAAY,UAAA,QAAkB,eAAA;;;cCjRpC,WAAA,YAAuB,aAAA;EAC3B,EAAA;EACA,IAAA;EACA,UAAA,GAAa,MAAA;EACb,aAAA,GAAgB,oBAAA;EAChB,KAAA,GAAQ,WAAA;EACR,IAAA,GAAO,MAAA;cAEK,OAAA,EAAS,aAAA;AAAA;AAAA,cAUF,KAAA,WAAgB,cAAA,GAAiB,cAAA;EAAA;EACpD,OAAA,EAAS,WAAA;EACT,OAAA,GAAU,CAAA;EACV,MAAA;EACA,gBAAA,EAAkB,eAAA;cAEN,OAAA,GAAU,YAAA,CAAa,CAAA;EAMnC,KAAA,CAAA;EAuGA,OAAA,CAAQ,GAAA,EAAK,WAAA,EAAa,IAAA,UAAc,MAAA,EAAQ,WAAA,GAAc,UAAA;EA+B9D,UAAA,CAAW,IAAA,UAAc,EAAA,oBAAsB,WAAA;EAK/C,WAAA,CAAY,IAAA,WAAe,WAAA;EAiB3B,IAAA,kBAAA,CAAuB,IAAA,EAAM,CAAA,EAAG,EAAA,mBAAqB,MAAA,GAAS,WAAA,GAAc,cAAA,CAAe,CAAA,EAAG,CAAA;EAM9F,OAAA,kBAAA,CAA0B,IAAA,EAAM,CAAA,EAAG,MAAA,GAAS,WAAA,GAAc,cAAA,CAAe,CAAA,EAAG,CAAA;EAgB5E,MAAA,CAAO,IAAA,UAAc,EAAA;EAkBrB,OAAA,CAAQ,IAAA,EAAM,eAAA,GAAkB,WAAA;EA8DhC,IAAA,CAAK,IAAA,EAAM,eAAA,GAAkB,UAAA,GAAa,WAAA;ED5I5B;;;;;;;EAAA,OC+JP,KAAA,CAAM,IAAA,EAAM,eAAA,GAAkB,wBAAA;EAIrC,KAAA,CAAM,IAAA,EAAM,eAAA,GAAkB,wBAAA;EAQ9B,QAAA,kBAAA,CAA2B,IAAA,EAAM,CAAA,EAAG,IAAA,EAAM,eAAA,GAAkB,cAAA,CAAe,CAAA,EAAG,CAAA;EAW9E,WAAA,kBAAA,CAA8B,IAAA,EAAM,CAAA,EAAG,IAAA,EAAM,eAAA,GAAkB,WAAA,CAAY,cAAA,CAAe,CAAA,EAAG,CAAA;AAAA;;;KC9U1F,aAAA,mBAAgC,OAAA;AAAA,UAE3B,aAAA;EACR,OAAA,GAAU,aAAA;AAAA;AAAA,UAGF,YAAA;EACR,KAAA,SAAc,KAAA;EACd,SAAA,EAAW,SAAA;EACX,OAAA,SAAgB,OAAA;AAAA;AAAA,iBAgBT,MAAA,CAAO,OAAA,GAAU,aAAA,GAAgB,YAAA"}
@@ -1,4 +1,4 @@
1
- import { a as TYPE, i as REL_META, n as META, o as adapter_default, r as REL_LINKS, t as LINKS } from "./symbols-DSjKJ8vh.mjs";
1
+ import { a as TYPE, i as REL_META, n as META, o as adapter_default, r as REL_LINKS, t as LINKS } from "./symbols-BfU4k1el.mjs";
2
2
 
3
3
  //#region src/yayson/adapters/sequelize.ts
4
4
  function isSequelizeModel(model) {
@@ -8,6 +8,10 @@ var SequelizeAdapter = class extends adapter_default {
8
8
  static get(model, key) {
9
9
  if (isSequelizeModel(model)) return model.get(key);
10
10
  }
11
+ static has(model, key) {
12
+ if (isSequelizeModel(model)) return model.get(key) !== void 0;
13
+ return false;
14
+ }
11
15
  static id(model) {
12
16
  const pkFields = model.constructor && "primaryKeys" in model.constructor ? Object.keys(model.constructor.primaryKeys) : ["id"];
13
17
  if (pkFields.length > 1) throw new Error("YAYSON does not support Sequelize models with composite primary keys. You can only use one column for your primary key. Currently using: " + pkFields.join(","));
@@ -69,23 +73,32 @@ function createPresenter(adapter) {
69
73
  const result = [];
70
74
  if (!relationships) return result;
71
75
  for (const key in relationships) {
72
- const factory = relationships[key];
73
- if (!factory) throw new Error(`Presenter for ${key} in ${this.constructor.type} is not defined`);
74
- const presenter = new factory(scope);
76
+ const entry = relationships[key];
77
+ if (!entry) throw new Error(`Presenter for ${key} in ${this.constructor.type} is not defined`);
78
+ const presenter = new (typeof entry === "function" ? entry : entry.presenter)(scope);
75
79
  const data = this.constructor.adapter.get(instance, key);
76
80
  result.push(presenter.toJSON(data, { include: true }));
77
81
  }
78
82
  return result;
79
83
  }
80
- buildRelationships(instance) {
84
+ buildRelationships(instance, options = {}) {
81
85
  if (instance == null) return null;
82
86
  const rels = this.relationships();
83
87
  const links = this.links(instance) || {};
88
+ const isPayload = options.payload === true;
84
89
  let relationships = null;
85
90
  if (!rels) return null;
86
91
  for (const key in rels) {
92
+ const entry = rels[key];
93
+ if (!entry) continue;
94
+ const isConfig = typeof entry !== "function";
95
+ const presenter = isConfig ? entry.presenter : entry;
96
+ const hasMany = isConfig ? entry.hasMany : void 0;
97
+ const optional = isConfig ? entry.optional ?? false : false;
98
+ const linkValue = links[key];
99
+ const hasLinks = linkValue != null;
87
100
  const data = this.constructor.adapter.get(instance, key);
88
- const presenter = rels[key];
101
+ const keyPresent = this.constructor.adapter.has(instance, key);
89
102
  const buildData = (d) => {
90
103
  const id = this.constructor.adapter.id(d);
91
104
  if (!id) throw new Error(`Model of type ${presenter.type} is missing an id (relationship '${key}' of ${this.constructor.type})`);
@@ -94,19 +107,21 @@ function createPresenter(adapter) {
94
107
  type: presenter.type
95
108
  };
96
109
  };
97
- const build = (d) => {
98
- const rel = {};
99
- if (d != null) rel.data = buildData(d);
100
- if (links[key] != null) rel.links = buildLinks(links[key]);
101
- else if (d == null) rel.data = null;
102
- return rel;
103
- };
110
+ if (optional && !keyPresent && !isPayload) {
111
+ if (hasLinks) {
112
+ if (!relationships) relationships = {};
113
+ relationships[key] = { links: buildLinks(linkValue) };
114
+ }
115
+ continue;
116
+ }
104
117
  if (!relationships) relationships = {};
105
- if (!relationships[key]) relationships[key] = {};
106
- if (Array.isArray(data)) {
107
- relationships[key].data = data.map(buildData);
108
- if (links[key] != null) relationships[key].links = buildLinks(links[key]);
109
- } else relationships[key] = build(data);
118
+ const rel = {};
119
+ if (Array.isArray(data)) rel.data = data.map(buildData);
120
+ else if (data != null) rel.data = buildData(data);
121
+ else if (hasMany === true) rel.data = [];
122
+ else if (!hasLinks) rel.data = null;
123
+ if (hasLinks) rel.links = buildLinks(linkValue);
124
+ relationships[key] = rel;
110
125
  }
111
126
  return relationships;
112
127
  }
@@ -159,7 +174,7 @@ function createPresenter(adapter) {
159
174
  };
160
175
  const id = this.id(instance);
161
176
  if (id != null) model.id = id;
162
- const relationships = this.buildRelationships(instance);
177
+ const relationships = this.buildRelationships(instance, { payload: true });
163
178
  if (relationships != null) model.relationships = relationships;
164
179
  const result = { data: model };
165
180
  const opts = options ?? {};
@@ -208,6 +223,42 @@ function validate(schema, data, strict) {
208
223
  }
209
224
  }
210
225
 
226
+ //#endregion
227
+ //#region src/yayson/safe.ts
228
+ /**
229
+ * Defenses against prototype pollution, since yayson keys lookup tables and
230
+ * model objects by strings from untrusted documents (`type`, `id`, member
231
+ * names). Per MDN's guidance
232
+ * (https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/Prototype_pollution):
233
+ * caches use null-prototype objects, and "__proto__"/"constructor"/"prototype"
234
+ * are rejected as member names.
235
+ */
236
+ /** Null-prototype lookup table (MDN's `Object.create(null)`, never `obj.__proto__`). */
237
+ function safeObject() {
238
+ return Object.create(null);
239
+ }
240
+ /**
241
+ * Normalize a caller-supplied cache to a null-prototype object, so a public
242
+ * `models` argument that happens to be a plain `{}` can't let type="__proto__"
243
+ * resolve through Object.prototype. Already-null-prototype caches pass through
244
+ * unchanged (preserving identity/memoization).
245
+ */
246
+ function safeCache(cache) {
247
+ if (cache === void 0) return safeObject();
248
+ if (Object.getPrototypeOf(cache) === null) return cache;
249
+ const safe = safeObject();
250
+ Object.assign(safe, cache);
251
+ return safe;
252
+ }
253
+ const UNSAFE_KEYS = new Set([
254
+ "__proto__",
255
+ "constructor",
256
+ "prototype"
257
+ ]);
258
+ function isUnsafeKey(key) {
259
+ return UNSAFE_KEYS.has(key);
260
+ }
261
+
211
262
  //#endregion
212
263
  //#region src/yayson/store.ts
213
264
  function hasId(model) {
@@ -249,7 +300,7 @@ var Store = class Store {
249
300
  return stub;
250
301
  }
251
302
  #createModel(resource, options) {
252
- const models = options?.models ?? {};
303
+ const models = options?.models ?? safeObject();
253
304
  const model = { ...resource.attributes || {} };
254
305
  if (resource.id != null) model.id = resource.id;
255
306
  const type = resource.type;
@@ -258,7 +309,7 @@ var Store = class Store {
258
309
  if (resource.links != null) model[LINKS] = resource.links;
259
310
  if (hasId(model)) {
260
311
  const idStr = String(model.id);
261
- if (!models[type]) models[type] = {};
312
+ if (!models[type]) models[type] = safeObject();
262
313
  if (!models[type][idStr]) models[type][idStr] = model;
263
314
  }
264
315
  if (resource.relationships != null) {
@@ -271,7 +322,8 @@ var Store = class Store {
271
322
  }
272
323
  #resolveRelationships(model, relationships, resolver, options) {
273
324
  const includeRelMeta = options?.includeRelMeta ?? true;
274
- for (const key in relationships) {
325
+ for (const key of Object.keys(relationships)) {
326
+ if (isUnsafeKey(key)) continue;
275
327
  const { data, links, meta } = relationships[key];
276
328
  model[key] = null;
277
329
  if (data == null && links == null) continue;
@@ -293,7 +345,7 @@ var Store = class Store {
293
345
  }
294
346
  }
295
347
  toModel(rec, type, models) {
296
- const model = this.#createModel(rec, { models });
348
+ const model = this.#createModel(rec, { models: safeCache(models) });
297
349
  if (this.schemas && this.schemas[rec.type]) {
298
350
  const schema = this.schemas[rec.type];
299
351
  const result = validate(schema, model, this.strict);
@@ -326,14 +378,14 @@ var Store = class Store {
326
378
  return this.toModel(rec, type, models);
327
379
  }
328
380
  find(type, id, models) {
329
- return this.#findModel(type, id, models ?? {});
381
+ return this.#findModel(type, id, safeCache(models));
330
382
  }
331
383
  findAll(type, models) {
332
- const modelsObj = models ?? {};
384
+ const modelsObj = safeCache(models);
333
385
  const recs = this.findRecords(type);
334
386
  if (recs == null) return [];
335
387
  recs.forEach((rec) => {
336
- if (!modelsObj[type]) modelsObj[type] = {};
388
+ if (!modelsObj[type]) modelsObj[type] = safeObject();
337
389
  return this.toModel(rec, type, modelsObj);
338
390
  });
339
391
  return Object.values(modelsObj[type] || {});
@@ -382,7 +434,7 @@ var Store = class Store {
382
434
  try {
383
435
  syncData(body.included);
384
436
  const recs = syncData(body.data);
385
- const models = {};
437
+ const models = safeObject();
386
438
  const result = recs.map((rec) => this.toModel(rec, rec.type, models));
387
439
  if (body.meta != null) result[META] = body.meta;
388
440
  return result;
@@ -448,5 +500,5 @@ function yayson(options) {
448
500
  var yayson_default = yayson;
449
501
 
450
502
  //#endregion
451
- export { validate as n, filterByFields as r, yayson_default as t };
452
- //# sourceMappingURL=yayson-CwZg5FNt.mjs.map
503
+ export { validate as a, safeObject as i, isUnsafeKey as n, filterByFields as o, safeCache as r, yayson_default as t };
504
+ //# sourceMappingURL=yayson-RzT9zsdx.mjs.map