@sonordev/contracts 2.18.0 → 2.20.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.
@@ -0,0 +1,260 @@
1
+ // src/contracts/wire-schema.ts
2
+ function createWireSchema(fail2, isValidationError) {
3
+ const own = (v, k) => Object.prototype.hasOwnProperty.call(v, k);
4
+ const field2 = (schema, read) => ({ schema, read });
5
+ const text2 = (max = 200, pattern, min = 1) => field2({ type: "string", minLength: min, maxLength: max, ...pattern ? { pattern: pattern.source } : {} }, (v, p) => typeof v === "string" && v.length >= min && v.length <= max && (!pattern || pattern.test(v)) ? v : fail2(p, "invalid string"));
6
+ const choice2 = (...values) => field2({ type: "string", enum: values }, (v, p) => typeof v === "string" && values.includes(v) ? v : fail2(p, "unsupported value"));
7
+ const literal2 = (value) => field2({ const: value }, (v, p) => v === value ? value : fail2(p, "unexpected value"));
8
+ const integer2 = (min = 0, max = Number.MAX_SAFE_INTEGER) => field2({ type: "integer", minimum: min, maximum: max }, (v, p) => typeof v === "number" && Number.isSafeInteger(v) && v >= min && v <= max ? v : fail2(p, "invalid integer"));
9
+ const bool = field2({ type: "boolean" }, (v, p) => typeof v === "boolean" ? v : fail2(p, "must be boolean"));
10
+ const optional2 = (f) => ({ ...f, optional: true });
11
+ const nullable2 = (f) => field2({ anyOf: [f.schema, { type: "null" }] }, (v, p) => v === null ? null : f.read(v, p));
12
+ const list2 = (f, max = 100, min = 0) => field2({ type: "array", items: f.schema, minItems: min, maxItems: max }, (v, p) => {
13
+ if (!Array.isArray(v) || v.length < min || v.length > max) fail2(p, "invalid array");
14
+ return v.map((x, i) => f.read(x, `${p}[${i}]`));
15
+ });
16
+ const object2 = (fields2) => field2({ type: "object", additionalProperties: false, properties: Object.fromEntries(Object.entries(fields2).map(([k, f]) => [k, f.schema])), required: Object.keys(fields2).filter((k) => !("optional" in fields2[k])) }, (v, p) => {
17
+ if (!v || typeof v !== "object" || Array.isArray(v) || ![Object.prototype, null].includes(Object.getPrototypeOf(v))) fail2(p, "must be a plain object");
18
+ const source = v, result = {};
19
+ for (const key of Reflect.ownKeys(source)) if (typeof key !== "string" || !own(fields2, key)) fail2(p, "unknown field");
20
+ for (const [key, f] of Object.entries(fields2)) {
21
+ if (!own(source, key)) {
22
+ if ("optional" in f) continue;
23
+ fail2(`${p}.${key}`, "required");
24
+ }
25
+ const descriptor = Object.getOwnPropertyDescriptor(source, key);
26
+ if (!own(descriptor, "value")) fail2(`${p}.${key}`, "accessors are not wire data");
27
+ result[key] = f.read(descriptor.value, `${p}.${key}`);
28
+ }
29
+ return result;
30
+ });
31
+ const union2 = (...fields2) => field2({ oneOf: fields2.map((f) => f.schema) }, (v, p) => {
32
+ const found = [];
33
+ for (const f of fields2) {
34
+ try {
35
+ found.push(f.read(v, p));
36
+ } catch (e) {
37
+ if (!isValidationError(e)) throw e;
38
+ }
39
+ }
40
+ return found.length === 1 ? found[0] : fail2(p, "must match exactly one supported shape");
41
+ });
42
+ const refine2 = (f, check) => field2(f.schema, (v, p) => {
43
+ const result = f.read(v, p);
44
+ check(result, p);
45
+ return result;
46
+ });
47
+ return { field: field2, text: text2, choice: choice2, literal: literal2, integer: integer2, bool, optional: optional2, nullable: nullable2, list: list2, object: object2, union: union2, refine: refine2 };
48
+ }
49
+
50
+ // src/contracts/sites-normalize.ts
51
+ function normalizeSiteHost(site) {
52
+ if (!site || typeof site !== "string") return null;
53
+ let s = site.trim().toLowerCase();
54
+ if (!s) return null;
55
+ s = s.replace(/^https?:\/\//, "");
56
+ const slash = s.indexOf("/");
57
+ if (slash >= 0) s = s.slice(0, slash);
58
+ const colon = s.indexOf(":");
59
+ if (colon >= 0) s = s.slice(0, colon);
60
+ s = s.replace(/\.+$/, "");
61
+ return s || null;
62
+ }
63
+
64
+ // src/contracts/sites.ts
65
+ function isNormalizedSiteHost(value) {
66
+ return typeof value === "string" && value.length > 0 && normalizeSiteHost(value) === value;
67
+ }
68
+
69
+ // src/contracts/public-url-host.ts
70
+ function isPrivateIpv4(a, b) {
71
+ return a === 127 || a === 10 || a === 0 || a === 192 && b === 168 || a === 172 && b >= 16 && b <= 31 || a === 169 && b === 254 || a === 100 && b >= 64 && b <= 127 || a >= 224;
72
+ }
73
+ function ipv6Words(host) {
74
+ if (!host.includes(":") || !/^[a-f\d:.]+$/.test(host)) return null;
75
+ let canonical;
76
+ try {
77
+ canonical = new URL(`http://[${host}]/`).hostname.slice(1, -1);
78
+ } catch {
79
+ return null;
80
+ }
81
+ const sides = canonical.split("::");
82
+ const left = sides[0] ? sides[0].split(":") : [];
83
+ const right = sides.length > 1 && sides[1] ? sides[1].split(":") : [];
84
+ return [...left, ...Array(sides.length > 1 ? 8 - left.length - right.length : 0).fill("0"), ...right].map((word) => parseInt(word, 16));
85
+ }
86
+ function isBlockedPublicUrlHost(rawHost) {
87
+ const host = rawHost.toLowerCase().replace(/^\[|\]$/g, "");
88
+ const blockedName = host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local") || host.endsWith(".internal");
89
+ if (host.includes(":") && host.includes("%")) return true;
90
+ const ipv4 = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(host);
91
+ const blockedIp = !!ipv4 && isPrivateIpv4(Number(ipv4[1]), Number(ipv4[2]));
92
+ const words = ipv6Words(host);
93
+ const blockedIpv6 = !!words && // Unspecified/loopback, unique-local, link-local, deprecated site-local
94
+ // and multicast destinations aren't public logo/webhook endpoints.
95
+ (words.every((word) => word === 0) || words.slice(0, 7).every((word) => word === 0) && words[7] === 1 || (words[0] & 65024) === 64512 || (words[0] & 65472) === 65152 || (words[0] & 65472) === 65216 || (words[0] & 65280) === 65280 || // IPv4-mapped and older IPv4-compatible forms carry an IPv4 destination.
96
+ words.slice(0, 5).every((word) => word === 0) && [0, 65535].includes(words[5]) && isPrivateIpv4(words[6] >> 8, words[6] & 255));
97
+ return blockedName || blockedIp || blockedIpv6;
98
+ }
99
+
100
+ // src/contracts/signal-experiments.ts
101
+ var SignalExperimentValidationError = class extends Error {
102
+ constructor(path, reason) {
103
+ super(`${path}: ${reason}`);
104
+ this.path = path;
105
+ this.name = "SignalExperimentValidationError";
106
+ }
107
+ };
108
+ var fail = (path, reason) => {
109
+ throw new SignalExperimentValidationError(path, reason);
110
+ };
111
+ var { field, text, choice, literal, integer, optional, nullable, list, object, union, refine } = createWireSchema(fail, (error) => error instanceof SignalExperimentValidationError);
112
+ var percent = field({ type: "number", minimum: 0, maximum: 100 }, (v, p) => typeof v === "number" && Number.isFinite(v) && v >= 0 && v <= 100 ? v : fail(p, "must be a finite percentage from 0 to 100"));
113
+ var page = refine(text(2048, /^\/(?!\/)[^?#\s\\]*$/), (v, p) => {
114
+ const parsed = new URL(v, "https://example.com");
115
+ if (parsed.pathname !== v) fail(p, "must be a canonical page path");
116
+ });
117
+ var variant = object({ key: text(80), name: text(160), weight: percent, description: optional(text(1e3, void 0, 0)) });
118
+ var variants = refine(list(variant, 50, 2), (v, p) => {
119
+ if (new Set(v.map((x) => x.key)).size !== v.length) fail(p, "variant keys must be unique");
120
+ if (Math.abs(v.reduce((sum, x) => sum + x.weight, 0) - 100) > 0.01) fail(p, "variant weights must sum to 100");
121
+ });
122
+ var targeting = object({ pages: optional(list(page, 100)), segments: optional(list(text(160), 100)), devices: optional(list(choice("desktop", "mobile", "tablet"), 3)), geo: optional(list(text(160), 100)) });
123
+ var fields = { name: text(160), description: text(1e4, void 0, 0), variants, traffic_allocation: percent, targeting_rules: targeting };
124
+ var create = object({ ...fields, description: optional(fields.description), traffic_allocation: optional(fields.traffic_allocation), targeting_rules: optional(fields.targeting_rules) });
125
+ var update = object({ name: optional(fields.name), description: optional(fields.description), variants: optional(fields.variants), traffic_allocation: optional(fields.traffic_allocation), targeting_rules: optional(fields.targeting_rules) });
126
+ var parseSignalExperimentCreate = (v) => create.read(v, "experimentCreate");
127
+ var parseSignalExperimentUpdate = (v) => update.read(v, "experimentUpdate");
128
+ var SIGNAL_EXPERIMENT_CREATE_JSON_SCHEMA = create.schema;
129
+ var SIGNAL_EXPERIMENT_UPDATE_JSON_SCHEMA = update.schema;
130
+ var measurementUuid = text(36, /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i);
131
+ var measurementRevision = text(128, /^[a-zA-Z0-9._:-]+$/);
132
+ var measurementInstant = refine(text(30, /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/), (v, p) => {
133
+ if (!Number.isFinite(Date.parse(v)) || new Date(v).toISOString().replace(".000Z", "Z") !== v.replace(".000Z", "Z")) fail(p, "invalid UTC instant");
134
+ });
135
+ var measurementSite = refine(text(253), (v, p) => {
136
+ if (!isNormalizedSiteHost(v) || isBlockedPublicUrlHost(v)) fail(p, "must be a normalized public site host");
137
+ });
138
+ var measurementCount = text(30, /^(?:0|[1-9][0-9]*)$/);
139
+ var measurementScope = object({
140
+ projectId: measurementUuid,
141
+ experimentId: measurementUuid,
142
+ site: measurementSite,
143
+ experimentRevision: integer(1),
144
+ goalRevision: measurementRevision,
145
+ campaignId: measurementUuid,
146
+ campaignVersionId: measurementUuid,
147
+ assignedFrom: measurementInstant,
148
+ assignedTo: measurementInstant
149
+ });
150
+ var assignmentCounts = {
151
+ variantKey: text(80),
152
+ assignedUnits: measurementCount,
153
+ exposureEvents: measurementCount,
154
+ exposedUnits: measurementCount,
155
+ matureAssignedUnits: measurementCount,
156
+ immatureAssignedUnits: measurementCount,
157
+ assignmentsWithoutExposure: measurementCount
158
+ };
159
+ var availableVariant = object({ ...assignmentCounts, acceptedFormEvents: measurementCount, acceptedFormUnits: measurementCount, matureAcceptedFormUnits: measurementCount });
160
+ var unsupportedVariant = object({ ...assignmentCounts, acceptedFormEvents: literal(null), acceptedFormUnits: literal(null), matureAcceptedFormUnits: literal(null) });
161
+ var businessMetrics = object({
162
+ uniqueQualifiedContacts: object({ state: literal("unavailable"), reasonCode: literal("original_qualified_lineage_not_bound") }),
163
+ wonValue: object({ state: literal("unavailable"), reasonCode: literal("original_deal_lineage_not_bound") }),
164
+ paidRevenue: object({ state: literal("unavailable"), reasonCode: literal("original_paid_source_lineage_not_bound") })
165
+ });
166
+ var goalCountingUnit = choice("unique_source", "unique_qualified_contact", "net_order");
167
+ var originalGoalFields = { sourceId: measurementUuid, sourceRevision: measurementRevision, countingUnit: goalCountingUnit };
168
+ var formSourceKind = choice("accepted_form", "qualified_lead");
169
+ var unsupportedSourceKind = choice("completed_booking", "net_paid_order", "approved_application");
170
+ var observedFields = {
171
+ schemaVersion: literal(1),
172
+ mode: literal("trusted_source"),
173
+ state: literal("observed"),
174
+ observedAt: measurementInstant,
175
+ scope: measurementScope,
176
+ countingUnit: literal("consent_subject"),
177
+ coverage: literal("assigned_population_only"),
178
+ semanticRole: literal("accepted_form_diagnostic"),
179
+ businessMetrics
180
+ };
181
+ var observedMeasurement = refine(union(
182
+ object({
183
+ ...observedFields,
184
+ originalGoal: object({ ...originalGoalFields, sourceKind: formSourceKind }),
185
+ diagnosticType: literal("accepted_form"),
186
+ diagnosticState: literal("available"),
187
+ variants: list(availableVariant, 50)
188
+ }),
189
+ object({
190
+ ...observedFields,
191
+ originalGoal: object({ ...originalGoalFields, sourceKind: unsupportedSourceKind }),
192
+ diagnosticType: literal(null),
193
+ diagnosticState: literal("unsupported_original_source"),
194
+ variants: list(unsupportedVariant, 50)
195
+ })
196
+ ), (value, p) => {
197
+ if (Date.parse(value.scope.assignedFrom) >= Date.parse(value.scope.assignedTo) || Date.parse(value.scope.assignedTo) > Date.parse(value.observedAt))
198
+ fail(p + ".scope", "requires an increasing assignment cohort ending by the native observation");
199
+ if (new Set(value.variants.map((v) => v.variantKey)).size !== value.variants.length) fail(p + ".variants", "variant keys must be unique");
200
+ for (const [index, v] of value.variants.entries()) {
201
+ const assigned = BigInt(v.assignedUnits), exposed = BigInt(v.exposedUnits), mature = BigInt(v.matureAssignedUnits);
202
+ const at = `${p}.variants[${index}]`;
203
+ if (exposed > assigned || BigInt(v.exposureEvents) < exposed || BigInt(v.assignmentsWithoutExposure) !== assigned - exposed || mature + BigInt(v.immatureAssignedUnits) !== assigned)
204
+ fail(at, "assignment, exposure and maturity counts are inconsistent");
205
+ if (v.acceptedFormUnits !== null && v.acceptedFormEvents !== null && v.matureAcceptedFormUnits !== null) {
206
+ const accepted = BigInt(v.acceptedFormUnits), matureAccepted = BigInt(v.matureAcceptedFormUnits);
207
+ if (accepted > exposed || accepted > BigInt(v.acceptedFormEvents) || matureAccepted > mature || matureAccepted > accepted)
208
+ fail(at, "accepted form units must remain within their exposed, event and mature cohorts");
209
+ }
210
+ }
211
+ });
212
+ var measurement = union(
213
+ object({ schemaVersion: literal(1), mode: literal("legacy"), state: literal("unavailable"), reasonCode: literal("legacy_assignment_outcome_events_only") }),
214
+ object({ schemaVersion: literal(1), mode: literal("trusted_source"), state: literal("unavailable"), reasonCode: literal("measurement_scope_required") }),
215
+ observedMeasurement
216
+ );
217
+ var parseSignalExperimentMeasurement = (v) => measurement.read(v, "experimentMeasurement");
218
+ var SIGNAL_EXPERIMENT_MEASUREMENT_JSON_SCHEMA = measurement.schema;
219
+ var scopeOption = object({
220
+ campaignId: measurementUuid,
221
+ campaignVersionId: measurementUuid,
222
+ campaignDefinitionRevision: integer(1),
223
+ acceptedAt: measurementInstant,
224
+ site: measurementSite,
225
+ experimentId: measurementUuid,
226
+ experimentRevision: integer(1),
227
+ goalRevision: measurementRevision,
228
+ goalLabel: nullable(text(200)),
229
+ assignmentExtent: nullable(object({ firstAssignedAt: measurementInstant, lastAssignedAt: measurementInstant })),
230
+ suggestedCohort: nullable(object({ assignedFrom: measurementInstant, assignedTo: measurementInstant }))
231
+ });
232
+ var scopeOptions = refine(object({ observedAt: measurementInstant, items: list(scopeOption, 100), nextCursor: nullable(text(4096)) }), (value, p) => {
233
+ const observed = Date.parse(value.observedAt), tuples = /* @__PURE__ */ new Set();
234
+ for (const [index, item] of value.items.entries()) {
235
+ const at = `${p}.items[${index}]`, accepted = Date.parse(item.acceptedAt);
236
+ const tuple = JSON.stringify([
237
+ item.campaignId.toLowerCase(),
238
+ item.campaignVersionId.toLowerCase(),
239
+ item.site,
240
+ item.experimentId.toLowerCase(),
241
+ item.experimentRevision,
242
+ item.goalRevision
243
+ ]);
244
+ if (tuples.has(tuple)) fail(at, "original scope tuples must be unique");
245
+ tuples.add(tuple);
246
+ if (accepted > observed) fail(at + ".acceptedAt", "must not follow the native observation");
247
+ if (item.assignmentExtent) {
248
+ const first = Date.parse(item.assignmentExtent.firstAssignedAt), last = Date.parse(item.assignmentExtent.lastAssignedAt);
249
+ if (first > last || last > observed) fail(at + ".assignmentExtent", "requires first <= last <= native observation");
250
+ }
251
+ if (item.suggestedCohort) {
252
+ const from = Date.parse(item.suggestedCohort.assignedFrom), to = Date.parse(item.suggestedCohort.assignedTo);
253
+ if (from !== accepted || from >= to || to > observed) fail(at + ".suggestedCohort", "requires original acceptance start and increasing cohort ending by native observation");
254
+ }
255
+ }
256
+ });
257
+ var parseSignalExperimentScopeOptions = (v) => scopeOptions.read(v, "experimentScopeOptions");
258
+ var SIGNAL_EXPERIMENT_SCOPE_OPTIONS_JSON_SCHEMA = scopeOptions.schema;
259
+
260
+ export { SIGNAL_EXPERIMENT_CREATE_JSON_SCHEMA, SIGNAL_EXPERIMENT_MEASUREMENT_JSON_SCHEMA, SIGNAL_EXPERIMENT_SCOPE_OPTIONS_JSON_SCHEMA, SIGNAL_EXPERIMENT_UPDATE_JSON_SCHEMA, SignalExperimentValidationError, parseSignalExperimentCreate, parseSignalExperimentMeasurement, parseSignalExperimentScopeOptions, parseSignalExperimentUpdate };
@@ -0,0 +1,36 @@
1
+ /** Internal shared wire-schema primitives. Parser and JSON Schema share one field definition. */
2
+ export type JsonSchema = {
3
+ [key: string]: unknown;
4
+ };
5
+ export interface Field<T> {
6
+ schema: JsonSchema;
7
+ read(value: unknown, path: string): T;
8
+ }
9
+ export type Value<F> = F extends Field<infer T> ? T : never;
10
+ type Fields = Record<string, Field<unknown>>;
11
+ type ObjectValue<S extends Fields> = {
12
+ [K in keyof S as S[K] extends {
13
+ optional: true;
14
+ } ? never : K]: Value<S[K]>;
15
+ } & {
16
+ [K in keyof S as S[K] extends {
17
+ optional: true;
18
+ } ? K : never]?: Value<S[K]>;
19
+ };
20
+ export declare function createWireSchema(fail: (path: string, reason: string) => never, isValidationError: (error: unknown) => boolean): {
21
+ field: <T>(schema: JsonSchema, read: Field<T>['read']) => Field<T>;
22
+ text: (max?: number, pattern?: RegExp, min?: number) => Field<string>;
23
+ choice: <const T extends readonly string[]>(...values: T) => Field<T[number]>;
24
+ literal: <T extends string | number | boolean | null>(value: T) => Field<T>;
25
+ integer: (min?: number, max?: number) => Field<number>;
26
+ bool: Field<boolean>;
27
+ optional: <T>(f: Field<T>) => Field<T> & {
28
+ optional: true;
29
+ };
30
+ nullable: <T>(f: Field<T>) => Field<T | null>;
31
+ list: <T>(f: Field<T>, max?: number, min?: number) => Field<T[]>;
32
+ object: <S extends Fields>(fields: S) => Field<ObjectValue<S>>;
33
+ union: <const F extends readonly Field<unknown>[]>(...fields: F) => Field<Value<F[number]>>;
34
+ refine: <T>(f: Field<T>, check: (v: T, p: string) => void) => Field<T>;
35
+ };
36
+ export {};
package/docs.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "title": "contracts",
4
+ "summary": "Shared validation rules, wire types and JSON schemas for Sonor applications.",
5
+ "groups": [
6
+ {
7
+ "title": "Start here",
8
+ "pages": [
9
+ {
10
+ "slug": "",
11
+ "title": "Overview and contracts",
12
+ "file": "README.md"
13
+ }
14
+ ]
15
+ }
16
+ ],
17
+ "changelog": {
18
+ "file": "CHANGELOG.md",
19
+ "since": "2.18.0"
20
+ }
21
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sonordev/contracts",
3
- "version": "2.18.0",
4
- "description": "The rules Sonor's sites, APIs and dashboard share: popup blocks and site design tokens, site hosts, SEO page identity, title quality, llms.txt sanitizers, the forms honeypot, fleet heartbeats, slot content and signing, site cache tags, portfolio metrics, the house voice's contraction rule, how a proposal's site plan counts its pages, what separates an error page from a page's content, which stored JSON-LD is a template's unfilled placeholder, where prose splits into sentences, which Sonor records Signal reads into a project's knowledge and the address each one is stored under, what outbound email and text may claim about a business and whether a sent email arrived intact, what a segment of outreach is and who may turn one on, a page's own name taken from its title or its slug, which Broadcast card layouts and review compositions are drawn and what a retired one draws as, what a publication's branding may hold, which literal hosts are refused as public fetch destinations, which inbox cards are decisions a bulk action leaves alone, what a rep's call outcome (positive, negative or left voicemail) changes on a prospect, and which buying role a job title says (owner, decision maker, influencer or gatekeeper). Pure functions, no dependencies.",
3
+ "version": "2.20.0",
4
+ "description": "The rules Sonor's sites, APIs and dashboard share: popup blocks and site design tokens, site hosts, SEO page identity, title quality, llms.txt sanitizers, shared form validation, fleet heartbeats, slot content and signing, site cache tags, portfolio metrics, the house voice's contraction rule, how a proposal's site plan counts its pages, what separates an error page from a page's content, which stored JSON-LD is a template's unfilled placeholder, where prose splits into sentences, which Sonor records Signal reads into a project's knowledge and the address each one is stored under, what outbound email and text may claim about a business and whether a sent email arrived intact, what a segment of outreach is and who may turn one on, a page's own name taken from its title or its slug, which Broadcast card layouts and review compositions are drawn and what a retired one draws as, what a publication's branding may hold, which literal hosts are refused as public fetch destinations, which inbox cards are decisions a bulk action leaves alone, what a rep's call outcome (positive, negative or left voicemail) changes on a prospect, and which buying role a job title says (owner, decision maker, influencer or gatekeeper). Pure functions, no dependencies.",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -165,6 +165,21 @@
165
165
  "types": "./dist/decision-roles.d.ts",
166
166
  "import": "./dist/decision-roles.mjs",
167
167
  "require": "./dist/decision-roles.js"
168
+ },
169
+ "./resonance": {
170
+ "types": "./dist/resonance.d.ts",
171
+ "import": "./dist/resonance.mjs",
172
+ "require": "./dist/resonance.js"
173
+ },
174
+ "./resonance-constants": {
175
+ "types": "./dist/resonance-constants.d.ts",
176
+ "import": "./dist/resonance-constants.mjs",
177
+ "require": "./dist/resonance-constants.js"
178
+ },
179
+ "./signal-experiments": {
180
+ "types": "./dist/signal-experiments.d.ts",
181
+ "import": "./dist/signal-experiments.mjs",
182
+ "require": "./dist/signal-experiments.js"
168
183
  }
169
184
  },
170
185
  "typesVersions": {
@@ -177,7 +192,8 @@
177
192
  "files": [
178
193
  "dist",
179
194
  "README.md",
180
- "CHANGELOG.md"
195
+ "CHANGELOG.md",
196
+ "docs.json"
181
197
  ],
182
198
  "engines": {
183
199
  "node": ">=20"