@reform-society/agera-core 0.1.0 → 0.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.
package/dist/index.d.ts CHANGED
@@ -204,6 +204,8 @@ declare function calculateSectionResults(questions: QuizQuestionInput[], questio
204
204
  declare function scoreQuiz(questions: QuizQuestionInput[], config?: QuizConfig): QuizResults;
205
205
  //#endregion
206
206
  //#region src/basin-client.d.ts
207
+ type JwtPayload = Record<string, unknown>;
208
+ declare function decodeJwtPayload(jwt: string): JwtPayload | null;
207
209
  /** Check if JWT is expired (with 60s buffer) */
208
210
  declare function isJwtExpired(jwt: string): boolean;
209
211
  type StorageAccessor = {
@@ -211,7 +213,10 @@ type StorageAccessor = {
211
213
  setItem(key: string, value: string): void;
212
214
  removeItem(key: string): void;
213
215
  };
214
- declare function fetchBasinJWT(endpoint: string, storage: StorageAccessor, storageKey: string): Promise<string>;
216
+ type FetchBasinJWTOptions = {
217
+ isCachedJwtValid?: (jwt: string) => boolean;
218
+ };
219
+ declare function fetchBasinJWT(endpoint: string, storage: StorageAccessor, storageKey: string, options?: FetchBasinJWTOptions): Promise<string>;
215
220
  //#endregion
216
221
  //#region src/crm-types.d.ts
217
222
  type CounterUpdateConfig = {
@@ -262,11 +267,55 @@ type CrmV2Request = {
262
267
  type CrmV2PatchRequest = CrmV2Request & {
263
268
  response: Record<string, unknown>;
264
269
  };
270
+ type CrmV2Payload = CrmV2Request & {
271
+ response?: Record<string, unknown>;
272
+ };
265
273
  type CrmFetchResult = {
266
274
  ok: boolean;
267
275
  status: number;
268
276
  data: Record<string, unknown> | null;
269
277
  };
278
+ type CrmCommand = {
279
+ client: string;
280
+ payload: CrmV2Payload;
281
+ method?: "POST" | "PUT" | "PATCH";
282
+ delivery?: "wait" | "background";
283
+ };
284
+ type CrmDispatch = {
285
+ client: string;
286
+ method: "POST" | "PUT" | "PATCH";
287
+ delivery: "wait" | "background";
288
+ payload: CrmV2Payload;
289
+ signal?: AbortSignal;
290
+ };
291
+ type CrmGatewayResult = {
292
+ kind: "response";
293
+ response: CrmFetchResult;
294
+ } | {
295
+ kind: "accepted";
296
+ };
297
+ type CrmGateway = (dispatch: CrmDispatch) => Promise<CrmGatewayResult>;
298
+ type CrmOutcome = {
299
+ status: "succeeded";
300
+ httpStatus: number;
301
+ response: Record<string, unknown> | null;
302
+ } | {
303
+ status: "accepted";
304
+ } | {
305
+ status: "rejected";
306
+ httpStatus: number;
307
+ response: Record<string, unknown> | null;
308
+ } | {
309
+ status: "failed";
310
+ cause: unknown;
311
+ } | {
312
+ status: "aborted";
313
+ };
314
+ //#endregion
315
+ //#region src/crm-submission.d.ts
316
+ declare function executeCrm(gateway: CrmGateway, command: CrmCommand, options?: {
317
+ signal?: AbortSignal;
318
+ }): Promise<CrmOutcome>;
270
319
  //#endregion
271
320
  //#region src/utm.d.ts
272
321
  declare const UTM_KEYS: readonly ["source", "campaign", "medium", "content", "term", "wec"];
@@ -347,6 +396,19 @@ type DeviceInput = {
347
396
  };
348
397
  declare function detectDeviceType(input: DeviceInput): DeviceType;
349
398
  //#endregion
399
+ //#region src/visitor.d.ts
400
+ declare const VISITOR_ID_STORAGE_KEY = "agera:vid";
401
+ /**
402
+ * Read the semi-persistent pseudonymous visitor id from storage, generating
403
+ * and persisting a new one when absent. Used only to correlate requests from
404
+ * the same browser in crm-api logs — never stored server-side.
405
+ *
406
+ * Returns null when storage can't be read or written (private mode, quota,
407
+ * blocked storage): an unpersisted id would look like a new visitor on every
408
+ * page load, so callers omit the header instead.
409
+ */
410
+ declare function getOrCreateVisitorId(storage: StorageAccessor): string | null;
411
+ //#endregion
350
412
  //#region src/crm-fetch.d.ts
351
413
  type CrmFetchOptions = {
352
414
  waitForResponse?: boolean;
@@ -356,6 +418,22 @@ type CrmFetchOptions = {
356
418
  };
357
419
  declare function crmFetch(url: string, payload: Record<string, unknown> | object, options?: CrmFetchOptions): Promise<CrmFetchResult>;
358
420
  //#endregion
421
+ //#region src/crm-fields.d.ts
422
+ declare const CRM_CONTACT_KEYS: readonly ["given-name", "family-name", "email", "tel", "street-address", "address-level2", "postal-code", "country"];
423
+ type CrmContactKey = (typeof CRM_CONTACT_KEYS)[number];
424
+ declare function isCrmContactKey(key: string): key is CrmContactKey;
425
+ type JsonValue = null | boolean | number | string | readonly JsonValue[] | {
426
+ readonly [key: string]: JsonValue;
427
+ };
428
+ type CrmGroupedAnswer = {
429
+ group: string;
430
+ name: string;
431
+ value: JsonValue;
432
+ };
433
+ type CrmJsonGroups = Record<string, Record<string, JsonValue>>;
434
+ declare function groupCrmAnswers(answers: readonly CrmGroupedAnswer[]): CrmJsonGroups;
435
+ declare function serializeCrmJsonGroups(groups: CrmJsonGroups): string | undefined;
436
+ //#endregion
359
437
  //#region src/crm-request.d.ts
360
438
  type AssembleCrmRequestParams = {
361
439
  body: Record<string, unknown>;
@@ -365,7 +443,7 @@ type AssembleCrmRequestParams = {
365
443
  queryParams: Record<string, string>;
366
444
  sourceUrl: string;
367
445
  counters?: CounterUpdateConfig[];
368
- jsonGroups?: Record<string, Record<string, string>>;
446
+ jsonGroups?: CrmJsonGroups;
369
447
  };
370
448
  declare function assembleCrmRequest({
371
449
  body: rawBody,
@@ -381,5 +459,5 @@ declare function assembleCrmRequest({
381
459
  //#region src/polling.d.ts
382
460
  declare function matchesFormat(example: unknown, value: unknown): boolean;
383
461
  //#endregion
384
- export { type AssembleCrmRequestParams, type BandQuizResults, type CompositePredicate, type CounterDisplayConfig, type CounterPayload, type CounterResponse, type CounterUpdateConfig, type CrmConfig, type CrmFetchOptions, type CrmFetchResult, type CrmFrontendKey, type CrmV2PatchRequest, type CrmV2Request, type DeviceInput, type DeviceType, type ErrorMessages, type InsertDirective, type MemoryCache, type MemoryCacheOptions, type MemoryCacheSetOptions, type PassQuizResults, type PlacesMessageKey, type PlacesMessages, type Predicate, type PredicateOp, type ProgressInfo, type ProgressModeInfo, type QuestionMetrics, type QuizBandDefinition, type QuizConfig, type QuizOutcomeModel, type QuizPassFailConfig, type QuizQuestionInput, type QuizQuestionModel, type QuizQuestionResult, type QuizQuestionState, type QuizResults, type QuizScoringModel, type QuizSectionConfig, type QuizSectionResults, type ResolveValueSources, type ScoreQuizResults, type SessionState, type ShareConfig, type SimplePredicate, type SplitConfig, type StepState, type StorageAccessor, type SurveyOptions, type Utms, type ValidationRule, type Validators, type WeightedQuizResults, aggregateWeightedScores, answersChanged, assembleCrmRequest, buildContext, buildCounterPayload, buildFcrmUtms, calculateScoreResults, calculateSectionResults, calculateWinner, computeAutoTarget, createMemoryCache, createSessionStore, createStepsStore, crmFetch, deriveBandResults, derivePassResults, deriveQuestionState, detectDeviceType, errorMessages, evaluateNextMap, evaluatePredicate, evaluateShowIf, extractUtms, fetchBasinJWT, fetchCounter, flattenObject, formatCounterValue, getErrorMessagesForLanguage, getPhoneExample, getPlacesMessagesForLanguage, getPostalExample, inferCounterName, isCounterResponse, isJwtExpired, isSafeRedirectUrl, matchesFormat, mergeAnswers, parseCounterUpdateAttr, parseInsertDirective, parseInsertDirectives, resolveValue, scoreExactQuestion, scorePartialQuestion, scoreQuiz, scoreQuizQuestions, updateUrlWithParams, validators };
462
+ export { type AssembleCrmRequestParams, type BandQuizResults, CRM_CONTACT_KEYS, type CompositePredicate, type CounterDisplayConfig, type CounterPayload, type CounterResponse, type CounterUpdateConfig, type CrmCommand, type CrmConfig, type CrmContactKey, type CrmDispatch, type CrmFetchOptions, type CrmFetchResult, type CrmFrontendKey, type CrmGateway, type CrmGatewayResult, type CrmGroupedAnswer, type CrmJsonGroups, type CrmOutcome, type CrmV2PatchRequest, type CrmV2Payload, type CrmV2Request, type DeviceInput, type DeviceType, type ErrorMessages, type FetchBasinJWTOptions, type InsertDirective, type JsonValue, type JwtPayload, type MemoryCache, type MemoryCacheOptions, type MemoryCacheSetOptions, type PassQuizResults, type PlacesMessageKey, type PlacesMessages, type Predicate, type PredicateOp, type ProgressInfo, type ProgressModeInfo, type QuestionMetrics, type QuizBandDefinition, type QuizConfig, type QuizOutcomeModel, type QuizPassFailConfig, type QuizQuestionInput, type QuizQuestionModel, type QuizQuestionResult, type QuizQuestionState, type QuizResults, type QuizScoringModel, type QuizSectionConfig, type QuizSectionResults, type ResolveValueSources, type ScoreQuizResults, type SessionState, type ShareConfig, type SimplePredicate, type SplitConfig, type StepState, type StorageAccessor, type SurveyOptions, type Utms, VISITOR_ID_STORAGE_KEY, type ValidationRule, type Validators, type WeightedQuizResults, aggregateWeightedScores, answersChanged, assembleCrmRequest, buildContext, buildCounterPayload, buildFcrmUtms, calculateScoreResults, calculateSectionResults, calculateWinner, computeAutoTarget, createMemoryCache, createSessionStore, createStepsStore, crmFetch, decodeJwtPayload, deriveBandResults, derivePassResults, deriveQuestionState, detectDeviceType, errorMessages, evaluateNextMap, evaluatePredicate, evaluateShowIf, executeCrm, extractUtms, fetchBasinJWT, fetchCounter, flattenObject, formatCounterValue, getErrorMessagesForLanguage, getOrCreateVisitorId, getPhoneExample, getPlacesMessagesForLanguage, getPostalExample, groupCrmAnswers, inferCounterName, isCounterResponse, isCrmContactKey, isJwtExpired, isSafeRedirectUrl, matchesFormat, mergeAnswers, parseCounterUpdateAttr, parseInsertDirective, parseInsertDirectives, resolveValue, scoreExactQuestion, scorePartialQuestion, scoreQuiz, scoreQuizQuestions, serializeCrmJsonGroups, updateUrlWithParams, validators };
385
463
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -111,9 +111,26 @@ function evaluatePredicate(predicate, answers, sources) {
111
111
  }
112
112
  return false;
113
113
  }
114
+ /**
115
+ * Parse predicate JSON, tolerating single-quoted JSON.
116
+ *
117
+ * Webflow's Designer API (setCustomAttribute) forbids double quotes in
118
+ * attribute values, so predicates authored via the chat/Logic tab use single
119
+ * quotes — e.g. {'field':'plan','op':'eq','value':'pro'}. Strict JSON is tried
120
+ * first (fully backwards compatible); single-quote normalization is a fallback.
121
+ */
122
+ function parsePredicateJson(json) {
123
+ try {
124
+ return JSON.parse(json);
125
+ } catch {
126
+ const sentinel = String.fromCharCode(0);
127
+ const normalized = json.replace(/\\'/g, sentinel).replace(/'/g, "\"").split(sentinel).join("'");
128
+ return JSON.parse(normalized);
129
+ }
130
+ }
114
131
  function evaluateShowIf(predicateJson, answers, sources) {
115
132
  try {
116
- return evaluatePredicate(JSON.parse(predicateJson), answers, sources);
133
+ return evaluatePredicate(parsePredicateJson(predicateJson), answers, sources);
117
134
  } catch (error) {
118
135
  log.error("[conditions] Invalid predicate JSON:", error);
119
136
  return false;
@@ -121,7 +138,7 @@ function evaluateShowIf(predicateJson, answers, sources) {
121
138
  }
122
139
  function evaluateNextMap(nextMapJson, answers, sources) {
123
140
  try {
124
- const nextMap = JSON.parse(nextMapJson);
141
+ const nextMap = parsePredicateJson(nextMapJson);
125
142
  for (const rule of nextMap) if (evaluatePredicate(rule.if, answers, sources)) return rule.then;
126
143
  return null;
127
144
  } catch (error) {
@@ -664,21 +681,55 @@ function scoreQuiz(questions, config = {}) {
664
681
  }
665
682
  //#endregion
666
683
  //#region src/basin-client.ts
667
- /** Check if JWT is expired (with 60s buffer) */
668
- function isJwtExpired(jwt) {
684
+ function decodeBase64Url(value) {
685
+ const base64 = value.replace(/-/g, "+").replace(/_/g, "/");
686
+ const padded = base64.padEnd(base64.length + (4 - base64.length % 4) % 4, "=");
687
+ return atob(padded);
688
+ }
689
+ function decodeJwtPayload(jwt) {
669
690
  try {
670
691
  const payload = jwt.split(".")[1];
671
- if (!payload) return true;
672
- const { exp } = JSON.parse(atob(payload.replace(/-/g, "+").replace(/_/g, "/")));
673
- return typeof exp === "number" && exp <= Date.now() / 1e3 + 60;
692
+ if (!payload) return null;
693
+ const parsed = JSON.parse(decodeBase64Url(payload));
694
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
695
+ return parsed;
674
696
  } catch {
675
- return true;
697
+ return null;
698
+ }
699
+ }
700
+ /** Check if JWT is expired (with 60s buffer) */
701
+ function isJwtExpired(jwt) {
702
+ const payload = decodeJwtPayload(jwt);
703
+ if (!payload) return true;
704
+ if (typeof payload.exp !== "number") return true;
705
+ return payload.exp <= Date.now() / 1e3 + 60;
706
+ }
707
+ function getStoredJwt(storage, storageKey) {
708
+ try {
709
+ return storage.getItem(storageKey);
710
+ } catch (error) {
711
+ log.debug("[basin] Failed to read cached JWT:", error);
712
+ return null;
713
+ }
714
+ }
715
+ function removeStoredJwt(storage, storageKey) {
716
+ try {
717
+ storage.removeItem(storageKey);
718
+ } catch (error) {
719
+ log.debug("[basin] Failed to remove cached JWT:", error);
720
+ }
721
+ }
722
+ function setStoredJwt(storage, storageKey, jwt) {
723
+ try {
724
+ storage.setItem(storageKey, jwt);
725
+ } catch (error) {
726
+ log.debug("[basin] Failed to cache JWT:", error);
676
727
  }
677
728
  }
678
- async function fetchBasinJWT(endpoint, storage, storageKey) {
679
- const cached = storage.getItem(storageKey);
680
- if (cached && !isJwtExpired(cached)) return cached;
681
- if (cached) storage.removeItem(storageKey);
729
+ async function fetchBasinJWT(endpoint, storage, storageKey, options = {}) {
730
+ const cached = getStoredJwt(storage, storageKey);
731
+ if (cached && !isJwtExpired(cached) && (options.isCachedJwtValid?.(cached) ?? true)) return cached;
732
+ if (cached) removeStoredJwt(storage, storageKey);
682
733
  try {
683
734
  const res = await fetch(`${endpoint}/generate_jwt`, {
684
735
  method: "GET",
@@ -693,7 +744,7 @@ async function fetchBasinJWT(endpoint, storage, storageKey) {
693
744
  }
694
745
  const data = await res.json();
695
746
  if (data.jwt) {
696
- storage.setItem(storageKey, data.jwt);
747
+ setStoredJwt(storage, storageKey, data.jwt);
697
748
  return data.jwt;
698
749
  }
699
750
  return "";
@@ -703,6 +754,41 @@ async function fetchBasinJWT(endpoint, storage, storageKey) {
703
754
  }
704
755
  }
705
756
  //#endregion
757
+ //#region src/crm-submission.ts
758
+ function isAbortError(error) {
759
+ return typeof error === "object" && error !== null && "name" in error && error.name === "AbortError";
760
+ }
761
+ async function executeCrm(gateway, command, options = {}) {
762
+ const { signal } = options;
763
+ if (signal?.aborted) return { status: "aborted" };
764
+ try {
765
+ const result = await gateway({
766
+ client: command.client,
767
+ method: command.method ?? "POST",
768
+ delivery: command.delivery ?? "wait",
769
+ payload: command.payload,
770
+ signal
771
+ });
772
+ if (result.kind === "accepted") return { status: "accepted" };
773
+ const { ok, status, data } = result.response;
774
+ return ok ? {
775
+ status: "succeeded",
776
+ httpStatus: status,
777
+ response: data
778
+ } : {
779
+ status: "rejected",
780
+ httpStatus: status,
781
+ response: data
782
+ };
783
+ } catch (error) {
784
+ if (signal?.aborted || isAbortError(error)) return { status: "aborted" };
785
+ return {
786
+ status: "failed",
787
+ cause: error
788
+ };
789
+ }
790
+ }
791
+ //#endregion
706
792
  //#region src/utm.ts
707
793
  const UTM_KEYS = [
708
794
  "source",
@@ -1029,6 +1115,32 @@ function detectDeviceType(input) {
1029
1115
  return "desktop";
1030
1116
  }
1031
1117
  //#endregion
1118
+ //#region src/visitor.ts
1119
+ const VISITOR_ID_STORAGE_KEY = "agera:vid";
1120
+ function generateVisitorId() {
1121
+ return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
1122
+ }
1123
+ /**
1124
+ * Read the semi-persistent pseudonymous visitor id from storage, generating
1125
+ * and persisting a new one when absent. Used only to correlate requests from
1126
+ * the same browser in crm-api logs — never stored server-side.
1127
+ *
1128
+ * Returns null when storage can't be read or written (private mode, quota,
1129
+ * blocked storage): an unpersisted id would look like a new visitor on every
1130
+ * page load, so callers omit the header instead.
1131
+ */
1132
+ function getOrCreateVisitorId(storage) {
1133
+ try {
1134
+ const existing = storage.getItem(VISITOR_ID_STORAGE_KEY);
1135
+ if (existing) return existing;
1136
+ const id = generateVisitorId();
1137
+ storage.setItem(VISITOR_ID_STORAGE_KEY, id);
1138
+ return id;
1139
+ } catch {
1140
+ return null;
1141
+ }
1142
+ }
1143
+ //#endregion
1032
1144
  //#region src/crm-fetch.ts
1033
1145
  const noop = () => {};
1034
1146
  async function crmFetch(url, payload, options = {}) {
@@ -1076,14 +1188,45 @@ async function crmFetch(url, payload, options = {}) {
1076
1188
  }
1077
1189
  }
1078
1190
  //#endregion
1191
+ //#region src/crm-fields.ts
1192
+ const CRM_CONTACT_KEYS = [
1193
+ "given-name",
1194
+ "family-name",
1195
+ "email",
1196
+ "tel",
1197
+ "street-address",
1198
+ "address-level2",
1199
+ "postal-code",
1200
+ "country"
1201
+ ];
1202
+ const crmContactKeySet = new Set(CRM_CONTACT_KEYS);
1203
+ function isCrmContactKey(key) {
1204
+ return crmContactKeySet.has(key);
1205
+ }
1206
+ function groupCrmAnswers(answers) {
1207
+ const groups = /* @__PURE__ */ new Map();
1208
+ for (const { group, name, value } of answers) {
1209
+ if (group === "" || name === "") continue;
1210
+ const fields = groups.get(group) ?? /* @__PURE__ */ new Map();
1211
+ fields.set(name, value);
1212
+ groups.set(group, fields);
1213
+ }
1214
+ return Object.fromEntries([...groups].map(([group, fields]) => [group, Object.fromEntries(fields)]));
1215
+ }
1216
+ function serializeCrmJsonGroups(groups) {
1217
+ const groupEntries = Object.entries(groups);
1218
+ if (groupEntries.length === 0) return void 0;
1219
+ return JSON.stringify(groupEntries.map(([key, fields]) => ({ [key]: fields })));
1220
+ }
1221
+ //#endregion
1079
1222
  //#region src/crm-request.ts
1080
1223
  function assembleCrmRequest({ body: rawBody, config, frontendKeys, utms, queryParams, sourceUrl, counters, jsonGroups }) {
1081
1224
  const configuredCounters = Array.isArray(config.counter_update) ? config.counter_update : void 0;
1082
1225
  const resolvedCounters = counters ?? configuredCounters;
1083
1226
  const body = { ...rawBody };
1084
1227
  if (jsonGroups) {
1085
- const groupEntries = Object.entries(jsonGroups);
1086
- if (groupEntries.length > 0) body["json-groups"] = JSON.stringify(groupEntries.map(([key, fields]) => ({ [key]: fields })));
1228
+ const serialized = serializeCrmJsonGroups(jsonGroups);
1229
+ if (serialized !== void 0) body["json-groups"] = serialized;
1087
1230
  }
1088
1231
  return {
1089
1232
  body,
@@ -1117,6 +1260,6 @@ function matchesFormat(example, value) {
1117
1260
  return example === value;
1118
1261
  }
1119
1262
  //#endregion
1120
- export { aggregateWeightedScores, answersChanged, assembleCrmRequest, buildContext, buildCounterPayload, buildFcrmUtms, calculateScoreResults, calculateSectionResults, calculateWinner, computeAutoTarget, createMemoryCache, createSessionStore, createStepsStore, crmFetch, deriveBandResults, derivePassResults, deriveQuestionState, detectDeviceType, errorMessages, evaluateNextMap, evaluatePredicate, evaluateShowIf, extractUtms, fetchBasinJWT, fetchCounter, flattenObject, formatCounterValue, getErrorMessagesForLanguage, getPhoneExample, getPlacesMessagesForLanguage, getPostalExample, inferCounterName, isCounterResponse, isJwtExpired, isSafeRedirectUrl, matchesFormat, mergeAnswers, parseCounterUpdateAttr, parseInsertDirective, parseInsertDirectives, resolveValue, scoreExactQuestion, scorePartialQuestion, scoreQuiz, scoreQuizQuestions, updateUrlWithParams, validators };
1263
+ export { CRM_CONTACT_KEYS, VISITOR_ID_STORAGE_KEY, aggregateWeightedScores, answersChanged, assembleCrmRequest, buildContext, buildCounterPayload, buildFcrmUtms, calculateScoreResults, calculateSectionResults, calculateWinner, computeAutoTarget, createMemoryCache, createSessionStore, createStepsStore, crmFetch, decodeJwtPayload, deriveBandResults, derivePassResults, deriveQuestionState, detectDeviceType, errorMessages, evaluateNextMap, evaluatePredicate, evaluateShowIf, executeCrm, extractUtms, fetchBasinJWT, fetchCounter, flattenObject, formatCounterValue, getErrorMessagesForLanguage, getOrCreateVisitorId, getPhoneExample, getPlacesMessagesForLanguage, getPostalExample, groupCrmAnswers, inferCounterName, isCounterResponse, isCrmContactKey, isJwtExpired, isSafeRedirectUrl, matchesFormat, mergeAnswers, parseCounterUpdateAttr, parseInsertDirective, parseInsertDirectives, resolveValue, scoreExactQuestion, scorePartialQuestion, scoreQuiz, scoreQuizQuestions, serializeCrmJsonGroups, updateUrlWithParams, validators };
1121
1264
 
1122
1265
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reform-society/agera-core",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "DOM-free core logic for Agera",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -17,7 +17,7 @@
17
17
  ],
18
18
  "repository": {
19
19
  "type": "git",
20
- "url": "git+https://github.com/reform-society/agera-survey-app.git",
20
+ "url": "git+https://github.com/reform-society/agera.git",
21
21
  "directory": "packages/agera-core"
22
22
  },
23
23
  "author": "Reform Society",
@@ -34,12 +34,14 @@
34
34
  "test": "vitest run"
35
35
  },
36
36
  "devDependencies": {
37
- "tsdown": "^0.21.8",
38
- "vite": "^7.3.2",
39
- "vitest": "^4.1.3"
37
+ "@typescript/native": "npm:typescript@^7.0.2",
38
+ "tsdown": "^0.21.10",
39
+ "typescript": "npm:@typescript/typescript6@^6.0.2",
40
+ "vite": "7.3.5",
41
+ "vitest": "^4.1.11"
40
42
  },
41
43
  "dependencies": {
42
44
  "loglevel": "^1.9.2",
43
- "nanostores": "^1.1.0"
45
+ "nanostores": "^1.5.2"
44
46
  }
45
47
  }