@curless/shopify-storefront 0.1.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 (129) hide show
  1. package/dist/__tests__/card-contract.test.d.ts +2 -0
  2. package/dist/__tests__/card-contract.test.d.ts.map +1 -0
  3. package/dist/__tests__/card-contract.test.js +22 -0
  4. package/dist/__tests__/card-contract.test.js.map +1 -0
  5. package/dist/__tests__/catalog-registry.test.d.ts +2 -0
  6. package/dist/__tests__/catalog-registry.test.d.ts.map +1 -0
  7. package/dist/__tests__/catalog-registry.test.js +109 -0
  8. package/dist/__tests__/catalog-registry.test.js.map +1 -0
  9. package/dist/__tests__/challenge.test.d.ts +2 -0
  10. package/dist/__tests__/challenge.test.d.ts.map +1 -0
  11. package/dist/__tests__/challenge.test.js +46 -0
  12. package/dist/__tests__/challenge.test.js.map +1 -0
  13. package/dist/__tests__/config.test.d.ts +2 -0
  14. package/dist/__tests__/config.test.d.ts.map +1 -0
  15. package/dist/__tests__/config.test.js +123 -0
  16. package/dist/__tests__/config.test.js.map +1 -0
  17. package/dist/__tests__/connection-state.test.d.ts +2 -0
  18. package/dist/__tests__/connection-state.test.d.ts.map +1 -0
  19. package/dist/__tests__/connection-state.test.js +40 -0
  20. package/dist/__tests__/connection-state.test.js.map +1 -0
  21. package/dist/__tests__/multi-token-store.test.d.ts +2 -0
  22. package/dist/__tests__/multi-token-store.test.d.ts.map +1 -0
  23. package/dist/__tests__/multi-token-store.test.js +89 -0
  24. package/dist/__tests__/multi-token-store.test.js.map +1 -0
  25. package/dist/__tests__/oauth.test.d.ts +2 -0
  26. package/dist/__tests__/oauth.test.d.ts.map +1 -0
  27. package/dist/__tests__/oauth.test.js +112 -0
  28. package/dist/__tests__/oauth.test.js.map +1 -0
  29. package/dist/__tests__/output-schema.test.d.ts +2 -0
  30. package/dist/__tests__/output-schema.test.d.ts.map +1 -0
  31. package/dist/__tests__/output-schema.test.js +112 -0
  32. package/dist/__tests__/output-schema.test.js.map +1 -0
  33. package/dist/__tests__/ping.test.d.ts +2 -0
  34. package/dist/__tests__/ping.test.d.ts.map +1 -0
  35. package/dist/__tests__/ping.test.js +22 -0
  36. package/dist/__tests__/ping.test.js.map +1 -0
  37. package/dist/__tests__/rate-limit.test.d.ts +2 -0
  38. package/dist/__tests__/rate-limit.test.d.ts.map +1 -0
  39. package/dist/__tests__/rate-limit.test.js +46 -0
  40. package/dist/__tests__/rate-limit.test.js.map +1 -0
  41. package/dist/__tests__/stock.test.d.ts +2 -0
  42. package/dist/__tests__/stock.test.d.ts.map +1 -0
  43. package/dist/__tests__/stock.test.js +109 -0
  44. package/dist/__tests__/stock.test.js.map +1 -0
  45. package/dist/__tests__/tenant.test.d.ts +2 -0
  46. package/dist/__tests__/tenant.test.d.ts.map +1 -0
  47. package/dist/__tests__/tenant.test.js +105 -0
  48. package/dist/__tests__/tenant.test.js.map +1 -0
  49. package/dist/__tests__/tools.test.d.ts +2 -0
  50. package/dist/__tests__/tools.test.d.ts.map +1 -0
  51. package/dist/__tests__/tools.test.js +543 -0
  52. package/dist/__tests__/tools.test.js.map +1 -0
  53. package/dist/__tests__/upstream-cap.test.d.ts +2 -0
  54. package/dist/__tests__/upstream-cap.test.d.ts.map +1 -0
  55. package/dist/__tests__/upstream-cap.test.js +78 -0
  56. package/dist/__tests__/upstream-cap.test.js.map +1 -0
  57. package/dist/__tests__/webhooks.test.d.ts +2 -0
  58. package/dist/__tests__/webhooks.test.d.ts.map +1 -0
  59. package/dist/__tests__/webhooks.test.js +88 -0
  60. package/dist/__tests__/webhooks.test.js.map +1 -0
  61. package/dist/card.generated.d.ts +2 -0
  62. package/dist/card.generated.d.ts.map +1 -0
  63. package/dist/card.generated.js +6 -0
  64. package/dist/card.generated.js.map +1 -0
  65. package/dist/catalog-registry.d.ts +44 -0
  66. package/dist/catalog-registry.d.ts.map +1 -0
  67. package/dist/catalog-registry.js +105 -0
  68. package/dist/catalog-registry.js.map +1 -0
  69. package/dist/catalog.d.ts +51 -0
  70. package/dist/catalog.d.ts.map +1 -0
  71. package/dist/catalog.js +189 -0
  72. package/dist/catalog.js.map +1 -0
  73. package/dist/challenge.d.ts +11 -0
  74. package/dist/challenge.d.ts.map +1 -0
  75. package/dist/challenge.js +36 -0
  76. package/dist/challenge.js.map +1 -0
  77. package/dist/config.d.ts +141 -0
  78. package/dist/config.d.ts.map +1 -0
  79. package/dist/config.js +65 -0
  80. package/dist/config.js.map +1 -0
  81. package/dist/connection.d.ts +10 -0
  82. package/dist/connection.d.ts.map +1 -0
  83. package/dist/connection.js +21 -0
  84. package/dist/connection.js.map +1 -0
  85. package/dist/index.d.ts +3 -0
  86. package/dist/index.d.ts.map +1 -0
  87. package/dist/index.js +974 -0
  88. package/dist/index.js.map +1 -0
  89. package/dist/legal.d.ts +52 -0
  90. package/dist/legal.d.ts.map +1 -0
  91. package/dist/legal.js +139 -0
  92. package/dist/legal.js.map +1 -0
  93. package/dist/load-config.d.ts +11 -0
  94. package/dist/load-config.d.ts.map +1 -0
  95. package/dist/load-config.js +23 -0
  96. package/dist/load-config.js.map +1 -0
  97. package/dist/multi-token-store.d.ts +18 -0
  98. package/dist/multi-token-store.d.ts.map +1 -0
  99. package/dist/multi-token-store.js +95 -0
  100. package/dist/multi-token-store.js.map +1 -0
  101. package/dist/oauth.d.ts +20 -0
  102. package/dist/oauth.d.ts.map +1 -0
  103. package/dist/oauth.js +123 -0
  104. package/dist/oauth.js.map +1 -0
  105. package/dist/rate-limit.d.ts +15 -0
  106. package/dist/rate-limit.d.ts.map +1 -0
  107. package/dist/rate-limit.js +56 -0
  108. package/dist/rate-limit.js.map +1 -0
  109. package/dist/stock.d.ts +39 -0
  110. package/dist/stock.d.ts.map +1 -0
  111. package/dist/stock.js +77 -0
  112. package/dist/stock.js.map +1 -0
  113. package/dist/tenant.d.ts +69 -0
  114. package/dist/tenant.d.ts.map +1 -0
  115. package/dist/tenant.js +68 -0
  116. package/dist/tenant.js.map +1 -0
  117. package/dist/token-store.d.ts +16 -0
  118. package/dist/token-store.d.ts.map +1 -0
  119. package/dist/token-store.js +47 -0
  120. package/dist/token-store.js.map +1 -0
  121. package/dist/tools.d.ts +201 -0
  122. package/dist/tools.d.ts.map +1 -0
  123. package/dist/tools.js +779 -0
  124. package/dist/tools.js.map +1 -0
  125. package/dist/webhooks.d.ts +30 -0
  126. package/dist/webhooks.d.ts.map +1 -0
  127. package/dist/webhooks.js +69 -0
  128. package/dist/webhooks.js.map +1 -0
  129. package/package.json +53 -0
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=card-contract.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"card-contract.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/card-contract.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,22 @@
1
+ // Two places in the card have to agree with each other, and nothing in the type
2
+ // system makes them. Both were found by a buyer, not by a test.
3
+ import { readFileSync } from 'node:fs';
4
+ import { describe, expect, test } from 'vitest';
5
+ const CARD = readFileSync(new URL('../../widget/src/card.ts', import.meta.url), 'utf8');
6
+ describe('the picker box every view must provide', () => {
7
+ // `optionsBox()` finds the box by id. The product view emitted class="opts"
8
+ // and a data attribute but NO id, so the lookup returned null, drawOptions
9
+ // returned on its second line, and a buyer who simply named a lens got a
10
+ // page with no prescriptions and no Buy. Nothing threw and nothing logged —
11
+ // the box was there, and empty.
12
+ test('every rendered `.opts` box carries id="opts"', () => {
13
+ const boxes = CARD.match(/<div class="opts"[^>]*>/g) ?? [];
14
+ expect(boxes.length, 'the card must still draw a picker box somewhere').toBeGreaterThan(0);
15
+ for (const box of boxes)
16
+ expect(box, box).toContain('id="opts"');
17
+ });
18
+ test('the lookup still goes by that id — if this changes, the assertion above must too', () => {
19
+ expect(CARD).toContain("document.getElementById('opts')");
20
+ });
21
+ });
22
+ //# sourceMappingURL=card-contract.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"card-contract.test.js","sourceRoot":"","sources":["../../src/__tests__/card-contract.test.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,gEAAgE;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAEhD,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,GAAG,CAAC,0BAA0B,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC;AAExF,QAAQ,CAAC,wCAAwC,EAAE,GAAG,EAAE;IACtD,4EAA4E;IAC5E,2EAA2E;IAC3E,yEAAyE;IACzE,4EAA4E;IAC5E,gCAAgC;IAChC,IAAI,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACxD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,0BAA0B,CAAC,IAAI,EAAE,CAAC;QAC3D,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,iDAAiD,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QAC3F,KAAK,MAAM,GAAG,IAAI,KAAK;YAAE,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;IACnE,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,kFAAkF,EAAE,GAAG,EAAE;QAC5F,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,iCAAiC,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=catalog-registry.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-registry.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/catalog-registry.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,109 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import { CatalogRegistry } from '../catalog-registry.js';
3
+ // No shop is ever installed here: `resolveConfig` returning null is the honest
4
+ // "not connected" state, and it keeps these tests about the REGISTRY rather
5
+ // than about Shopify.
6
+ const registry = (opts = {}) => {
7
+ let clock = 1_000_000;
8
+ const r = new CatalogRegistry(async () => null, {
9
+ refreshMs: 60_000,
10
+ now: () => clock,
11
+ ...opts,
12
+ });
13
+ const tick = (ms) => {
14
+ clock += ms;
15
+ };
16
+ return { r, tick };
17
+ };
18
+ describe('one catalogue per shop', () => {
19
+ test('the same shop gets the same instance, a different shop does not', () => {
20
+ const { r } = registry();
21
+ const a = r.for('a-shop.myshopify.com');
22
+ expect(r.for('a-shop.myshopify.com')).toBe(a);
23
+ expect(r.for('b-shop.myshopify.com')).not.toBe(a);
24
+ expect(r.size).toBe(2);
25
+ });
26
+ test('shop names differing only in case are one shop, not two', () => {
27
+ const { r } = registry();
28
+ expect(r.for('A-Shop.myshopify.com')).toBe(r.for('a-shop.myshopify.com'));
29
+ expect(r.size).toBe(1);
30
+ });
31
+ });
32
+ describe('a shop that leaves stops being polled', () => {
33
+ // The failure this prevents: an uninstalled merchant whose refresh timer
34
+ // keeps calling their Shopify with a token they revoked.
35
+ test('forget stops the catalogue and drops it', () => {
36
+ const { r } = registry();
37
+ const c = r.for('a-shop.myshopify.com');
38
+ let stopped = false;
39
+ const realStop = c.stop.bind(c);
40
+ c.stop = () => {
41
+ stopped = true;
42
+ realStop();
43
+ };
44
+ r.forget('a-shop.myshopify.com');
45
+ expect(stopped).toBe(true);
46
+ expect(r.size).toBe(0);
47
+ });
48
+ test('forgetting a shop that was never here is a no-op', () => {
49
+ const { r } = registry();
50
+ expect(() => r.forget('never.myshopify.com')).not.toThrow();
51
+ expect(r.size).toBe(0);
52
+ });
53
+ test('a shop asked for again after forget is built fresh', () => {
54
+ const { r } = registry();
55
+ const first = r.for('a-shop.myshopify.com');
56
+ r.forget('a-shop.myshopify.com');
57
+ expect(r.for('a-shop.myshopify.com')).not.toBe(first);
58
+ });
59
+ });
60
+ describe('memory does not grow without bound', () => {
61
+ test('an idle shop is swept, and its timer stopped with it', () => {
62
+ const { r, tick } = registry({ idleMs: 1000 });
63
+ const c = r.for('a-shop.myshopify.com');
64
+ let stopped = false;
65
+ c.stop = () => {
66
+ stopped = true;
67
+ };
68
+ tick(1001);
69
+ // The sweep runs on access, so touching another shop is what triggers it —
70
+ // a registry nobody uses should not wake up to notice it is idle.
71
+ r.for('b-shop.myshopify.com');
72
+ expect(stopped).toBe(true);
73
+ expect(r.size).toBe(1);
74
+ });
75
+ test('a shop still in use is not swept out from under its traffic', () => {
76
+ const { r, tick } = registry({ idleMs: 1000 });
77
+ const a = r.for('a-shop.myshopify.com');
78
+ tick(600);
79
+ expect(r.for('a-shop.myshopify.com')).toBe(a); // refreshes lastUsed
80
+ tick(600);
81
+ r.for('b-shop.myshopify.com');
82
+ expect(r.for('a-shop.myshopify.com')).toBe(a);
83
+ });
84
+ test('past the ceiling, the least recently used shop goes first', () => {
85
+ const { r, tick } = registry({ maxShops: 2 });
86
+ r.for('a-shop.myshopify.com');
87
+ tick(10);
88
+ const b = r.for('b-shop.myshopify.com');
89
+ tick(10);
90
+ r.for('c-shop.myshopify.com');
91
+ expect(r.size).toBe(2);
92
+ // a was the oldest, so a is gone — b survived and is still the same object.
93
+ expect(r.for('b-shop.myshopify.com')).toBe(b);
94
+ });
95
+ });
96
+ describe('shutdown', () => {
97
+ test('stopAll stops every resident catalogue', () => {
98
+ const { r } = registry();
99
+ const stopped = [];
100
+ for (const s of ['a', 'b', 'c']) {
101
+ const c = r.for(`${s}-shop.myshopify.com`);
102
+ c.stop = () => stopped.push(s);
103
+ }
104
+ r.stopAll();
105
+ expect(stopped.sort()).toEqual(['a', 'b', 'c']);
106
+ expect(r.size).toBe(0);
107
+ });
108
+ });
109
+ //# sourceMappingURL=catalog-registry.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-registry.test.js","sourceRoot":"","sources":["../../src/__tests__/catalog-registry.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=challenge.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"challenge.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/challenge.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,46 @@
1
+ // One token per ORIGIN, because that is how OpenAI issues them.
2
+ //
3
+ // Served from a single variable, verifying a SECOND hostname makes the first
4
+ // start returning the second's token, and the app already verified there fails
5
+ // its next re-check — with a symptom ("verification dropped") that points
6
+ // nowhere near the change that caused it. A domain migration needs both hosts
7
+ // verified at once, so this is the normal case, not the corner.
8
+ import { describe, expect, test } from 'vitest';
9
+ import { challengeFor, parseChallengeTokens } from '../challenge.js';
10
+ const PER_HOST = 'mcp.moodylenses.com=NEWTOKEN,mcp.moodycosmetics.com=OLDTOKEN';
11
+ describe('challengeFor', () => {
12
+ test('each host gets its own token', () => {
13
+ expect(challengeFor('mcp.moodylenses.com', PER_HOST, undefined)).toBe('NEWTOKEN');
14
+ expect(challengeFor('mcp.moodycosmetics.com', PER_HOST, undefined)).toBe('OLDTOKEN');
15
+ });
16
+ test('the Host header is matched case-insensitively', () => {
17
+ expect(challengeFor('MCP.MoodyLenses.com', PER_HOST, undefined)).toBe('NEWTOKEN');
18
+ });
19
+ // Every deployment that has only ever set the single variable must keep
20
+ // working with no config change at all.
21
+ test('an unlisted host falls back to the single-value variable', () => {
22
+ expect(challengeFor('other.example', PER_HOST, 'SINGLE')).toBe('SINGLE');
23
+ expect(challengeFor('anything', undefined, 'SINGLE')).toBe('SINGLE');
24
+ });
25
+ test('nothing configured serves nothing — the endpoint 404s', () => {
26
+ expect(challengeFor('mcp.moodylenses.com', undefined, undefined)).toBeUndefined();
27
+ expect(challengeFor(undefined, undefined, undefined)).toBeUndefined();
28
+ });
29
+ });
30
+ describe('parseChallengeTokens', () => {
31
+ // A token is base64url-ish and may legitimately contain '='. Splitting on
32
+ // every '=' would truncate it, and the endpoint would serve a token that is
33
+ // ALMOST right — which verifies as a mismatch and reads like a typo upstream.
34
+ test('only the first separator divides host from token', () => {
35
+ expect(parseChallengeTokens('h.example=abc=def==').get('h.example')).toBe('abc=def==');
36
+ });
37
+ test('whitespace around a pair is tolerated', () => {
38
+ expect(parseChallengeTokens(' a.example = T1 , b.example=T2 ').get('a.example')).toBe('T1');
39
+ });
40
+ test('a malformed pair is skipped, not fatal — one typo must not blank the rest', () => {
41
+ const m = parseChallengeTokens('broken,=nohost,a.example=T1');
42
+ expect(m.get('a.example')).toBe('T1');
43
+ expect(m.size).toBe(1);
44
+ });
45
+ });
46
+ //# sourceMappingURL=challenge.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"challenge.test.js","sourceRoot":"","sources":["../../src/__tests__/challenge.test.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,EAAE;AACF,6EAA6E;AAC7E,+EAA+E;AAC/E,0EAA0E;AAC1E,8EAA8E;AAC9E,gEAAgE;AAChE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAErE,MAAM,QAAQ,GAAG,8DAA8D,CAAC;AAEhF,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,IAAI,CAAC,8BAA8B,EAAE,GAAG,EAAE;QACxC,MAAM,CAAC,YAAY,CAAC,qBAAqB,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAClF,MAAM,CAAC,YAAY,CAAC,wBAAwB,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACvF,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACzD,MAAM,CAAC,YAAY,CAAC,qBAAqB,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACpF,CAAC,CAAC,CAAC;IAEH,wEAAwE;IACxE,wCAAwC;IACxC,IAAI,CAAC,0DAA0D,EAAE,GAAG,EAAE;QACpE,MAAM,CAAC,YAAY,CAAC,eAAe,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACzE,MAAM,CAAC,YAAY,CAAC,UAAU,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACvE,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,uDAAuD,EAAE,GAAG,EAAE;QACjE,MAAM,CAAC,YAAY,CAAC,qBAAqB,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;QAClF,MAAM,CAAC,YAAY,CAAC,SAAS,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;IACxE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;IACpC,0EAA0E;IAC1E,4EAA4E;IAC5E,8EAA8E;IAC9E,IAAI,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC5D,MAAM,CAAC,oBAAoB,CAAC,qBAAqB,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IACzF,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,uCAAuC,EAAE,GAAG,EAAE;QACjD,MAAM,CAAC,oBAAoB,CAAC,iCAAiC,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC9F,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,2EAA2E,EAAE,GAAG,EAAE;QACrF,MAAM,CAAC,GAAG,oBAAoB,CAAC,6BAA6B,CAAC,CAAC;QAC9D,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACzB,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=config.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/config.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,123 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import { resolveStorefront, toolNames } from '../config.js';
3
+ const base = (over = {}) => ({
4
+ id: 'x',
5
+ server: { name: 'x', title: 'X' },
6
+ shopify: { mode: 'env', shopDomain: 'x.myshopify.com' },
7
+ urls: { appUrl: 'https://shopify.curless.ai/x' },
8
+ legal: {},
9
+ ...over,
10
+ });
11
+ describe('tool naming', () => {
12
+ /**
13
+ * The parity target. docs/baselines/moody-mcp-surface.json is what the live
14
+ * single-tenant connector answers today; if config-driven naming cannot
15
+ * reproduce those four names exactly, cutting moody over would silently
16
+ * rename its tools — and a host that has already learned them would stop
17
+ * finding them.
18
+ */
19
+ test('reproduces the live moody tool names exactly', () => {
20
+ const names = toolNames(base({ naming: { prefix: 'moody', noun: 'lens', nounPlural: 'lenses' } }));
21
+ expect(names).toEqual({
22
+ list: 'list_moody_lenses',
23
+ get: 'get_moody_lens',
24
+ checkout: 'create_moody_checkout',
25
+ lookupOrder: 'lookup_moody_order',
26
+ });
27
+ });
28
+ test('a prefix with no noun still reads as that merchant', () => {
29
+ expect(toolNames(base({ naming: { prefix: 'elspet' } }))).toEqual({
30
+ list: 'list_elspet_products',
31
+ get: 'get_elspet_product',
32
+ checkout: 'create_elspet_checkout',
33
+ lookupOrder: 'lookup_elspet_order',
34
+ });
35
+ });
36
+ /** The listed app: one install, one shop, and a capability list fixed at review time. */
37
+ test('no naming at all gives the generic public-app names', () => {
38
+ expect(toolNames(base())).toEqual({
39
+ list: 'list_products',
40
+ get: 'get_product',
41
+ checkout: 'create_checkout',
42
+ lookupOrder: 'lookup_order',
43
+ });
44
+ });
45
+ /**
46
+ * A merchant called "Els-Pet" must not fail to boot over a hyphen, and must
47
+ * not emit `list_els-pet_products` either — hosts reject tool names outside
48
+ * [a-z0-9_].
49
+ */
50
+ test('punctuation in a brand name is folded, not rejected', () => {
51
+ const names = toolNames(base({ naming: { prefix: 'Els-Pet', noun: 'Dog Bed', nounPlural: 'Dog Beds' } }));
52
+ expect(names.list).toBe('list_els_pet_dog_beds');
53
+ expect(names.get).toBe('get_els_pet_dog_bed');
54
+ for (const n of Object.values(names))
55
+ expect(n).toMatch(/^[a-z0-9_]+$/);
56
+ });
57
+ });
58
+ describe('resolution', () => {
59
+ test('the redirect uri is derived, never configured', () => {
60
+ // It is registered with Shopify by hand; a config that could disagree with
61
+ // what was registered would fail at install time, on a merchant's screen.
62
+ const r = resolveStorefront(base({ urls: { appUrl: 'https://shopify.curless.ai/elspet/' } }));
63
+ expect(r.redirectUri).toBe('https://shopify.curless.ai/elspet/auth/callback');
64
+ expect(r.appUrl).toBe('https://shopify.curless.ai/elspet');
65
+ });
66
+ test('publicUrl falls back to appUrl, and is kept when the merchant fronts it', () => {
67
+ expect(resolveStorefront(base()).publicUrl).toBe('https://shopify.curless.ai/x');
68
+ const fronted = resolveStorefront(base({
69
+ urls: {
70
+ appUrl: 'https://shopify.curless.ai/moody',
71
+ publicUrl: 'https://mcp.moodylenses.com/',
72
+ },
73
+ }));
74
+ expect(fronted.publicUrl).toBe('https://mcp.moodylenses.com');
75
+ // The redirect still belongs to the app URL — that is the one Shopify knows.
76
+ expect(fronted.redirectUri).toBe('https://shopify.curless.ai/moody/auth/callback');
77
+ });
78
+ test('token path and card uri are namespaced by id, so two tenants cannot collide', () => {
79
+ const a = resolveStorefront(base({ id: 'moody' }));
80
+ const b = resolveStorefront(base({ id: 'elspet' }));
81
+ expect(a.tokenPath).not.toBe(b.tokenPath);
82
+ expect(a.cardUriPrefix).toBe('ui://moody/');
83
+ expect(b.cardUriPrefix).toBe('ui://elspet/');
84
+ });
85
+ });
86
+ // Only `options` matters to the assertion below; the rest is the minimum a
87
+ // ShopifyProduct needs to exist.
88
+ const productWith = (title, options) => ({
89
+ id: 'gid://shopify/Product/1',
90
+ title,
91
+ handle: 'h',
92
+ url: 'https://example.com/products/h',
93
+ description: '',
94
+ productType: '',
95
+ options,
96
+ images: [],
97
+ variants: [],
98
+ tags: [],
99
+ currency: 'USD',
100
+ // biome-ignore lint/suspicious/noExplicitAny: minimal fixture for a shape assertion
101
+ });
102
+ describe('product shape does not invent axes the store lacks', () => {
103
+ // Regression: elspet sells litter boxes and every product came back with
104
+ // `eyes: 'unknown'` and `prescriptions: []` — inherited from the lens shop
105
+ // this package grew out of. Empty is not the same as absent: the first tells
106
+ // an assistant the axis exists and it failed to read it.
107
+ test('a store with no prescription option gets neither field', async () => {
108
+ const { productSummaryForTest } = await import('../tools.js');
109
+ const litter = productSummaryForTest(productWith('ELS PET Mixed Cat Litter', [{ name: 'Size', values: ['6L', '12L'] }]));
110
+ expect('eyes' in litter).toBe(false);
111
+ expect('prescriptions' in litter).toBe(false);
112
+ });
113
+ test('a store that does have one still reports both', async () => {
114
+ const { productSummaryForTest } = await import('../tools.js');
115
+ const lens = productSummaryForTest(productWith('Matcha Green', [
116
+ { name: 'Left prescription', values: ['-1.00', '-2.00'] },
117
+ { name: 'Right prescription', values: ['-1.00', '-2.00'] },
118
+ ]));
119
+ expect(lens.eyes).toBe('pair');
120
+ expect(lens.prescriptions).toEqual(['-1.00', '-2.00']);
121
+ });
122
+ });
123
+ //# sourceMappingURL=config.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.test.js","sourceRoot":"","sources":["../../src/__tests__/config.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=connection-state.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection-state.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/connection-state.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,40 @@
1
+ // Three states, not two — and conflating two of them locked a merchant out of
2
+ // fixing the third. See connection.ts for what happened.
3
+ import { describe, expect, test } from 'vitest';
4
+ import { credentialsRejected, needsAuth } from '../connection.js';
5
+ describe('credentialsRejected', () => {
6
+ // 401 revoked or expired, 403 scoped out. Both mean "these credentials are no
7
+ // longer good", which is the merchant's problem to fix, not ours to retry.
8
+ test('a refused token is recognised from what Shopify actually said', () => {
9
+ expect(credentialsRejected('Shopify Admin API: HTTP 401')).toBe(true);
10
+ expect(credentialsRejected('Shopify Admin API: HTTP 403')).toBe(true);
11
+ });
12
+ // A store that is merely unreachable does NOT need reconnecting, and telling a
13
+ // merchant to reauthorise over a 500 sends them to do pointless work while the
14
+ // real fault is ours.
15
+ test('an outage is not a permissions problem', () => {
16
+ expect(credentialsRejected('Shopify Admin API: HTTP 500')).toBe(false);
17
+ expect(credentialsRejected('Shopify Admin API: HTTP 429')).toBe(false);
18
+ expect(credentialsRejected('shopify returned no products')).toBe(false);
19
+ expect(credentialsRejected(undefined)).toBe(false);
20
+ });
21
+ // 4013 is not 401 with a digit after it.
22
+ test('the status is matched as a whole, not as a prefix', () => {
23
+ expect(credentialsRejected('HTTP 4013')).toBe(false);
24
+ });
25
+ });
26
+ describe('needsAuth', () => {
27
+ test('no token at all', () => {
28
+ expect(needsAuth(false, undefined)).toBe(true);
29
+ });
30
+ // THE case. A token is present, so the old check said "connected" and offered
31
+ // the merchant nothing to press.
32
+ test('a token that Shopify refuses still needs the merchant to reconnect', () => {
33
+ expect(needsAuth(true, 'Shopify Admin API: HTTP 401')).toBe(true);
34
+ });
35
+ test('a working connection is left alone', () => {
36
+ expect(needsAuth(true, undefined)).toBe(false);
37
+ expect(needsAuth(true, 'Shopify Admin API: HTTP 500')).toBe(false);
38
+ });
39
+ });
40
+ //# sourceMappingURL=connection-state.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection-state.test.js","sourceRoot":"","sources":["../../src/__tests__/connection-state.test.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,yDAAyD;AACzD,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAChD,OAAO,EAAE,mBAAmB,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAElE,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,8EAA8E;IAC9E,2EAA2E;IAC3E,IAAI,CAAC,+DAA+D,EAAE,GAAG,EAAE;QACzE,MAAM,CAAC,mBAAmB,CAAC,6BAA6B,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtE,MAAM,CAAC,mBAAmB,CAAC,6BAA6B,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxE,CAAC,CAAC,CAAC;IAEH,+EAA+E;IAC/E,+EAA+E;IAC/E,sBAAsB;IACtB,IAAI,CAAC,wCAAwC,EAAE,GAAG,EAAE;QAClD,MAAM,CAAC,mBAAmB,CAAC,6BAA6B,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACvE,MAAM,CAAC,mBAAmB,CAAC,6BAA6B,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACvE,MAAM,CAAC,mBAAmB,CAAC,8BAA8B,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACxE,MAAM,CAAC,mBAAmB,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACrD,CAAC,CAAC,CAAC;IAEH,yCAAyC;IACzC,IAAI,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC7D,MAAM,CAAC,mBAAmB,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACvD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,WAAW,EAAE,GAAG,EAAE;IACzB,IAAI,CAAC,iBAAiB,EAAE,GAAG,EAAE;QAC3B,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,8EAA8E;IAC9E,iCAAiC;IACjC,IAAI,CAAC,oEAAoE,EAAE,GAAG,EAAE;QAC9E,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE,6BAA6B,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpE,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC9C,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC/C,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE,6BAA6B,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACrE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=multi-token-store.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multi-token-store.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/multi-token-store.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,89 @@
1
+ import { mkdtemp, readFile, readdir, stat } from 'node:fs/promises';
2
+ import { tmpdir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { describe, expect, test } from 'vitest';
5
+ import { MultiTokenStore, isStorableShop } from '../multi-token-store.js';
6
+ const store = async () => new MultiTokenStore(await mkdtemp(join(tmpdir(), 'mts-')));
7
+ const token = (shopDomain) => ({
8
+ shopDomain,
9
+ accessToken: `shpat_${shopDomain.split('.')[0]}`,
10
+ scope: 'read_products',
11
+ installedAt: '2026-08-17T00:00:00.000Z',
12
+ });
13
+ describe('shop names that become filenames', () => {
14
+ test('accepts what Shopify actually issues', () => {
15
+ expect(isStorableShop('els-pet-us.myshopify.com')).toBe(true);
16
+ expect(isStorableShop('ELS-PET-US.myshopify.com')).toBe(true);
17
+ });
18
+ // The shop arrives as a request parameter. Everything here would either
19
+ // escape the directory or name a host we have no business holding a token
20
+ // for, and a deny-list would have to anticipate each one.
21
+ test.each([
22
+ '../../etc/passwd',
23
+ '..%2f..%2fetc',
24
+ 'shop.myshopify.com/../other',
25
+ 'evil.example.com',
26
+ 'shop.myshopify.com.evil.com',
27
+ '',
28
+ '.myshopify.com',
29
+ 'a/b.myshopify.com',
30
+ ])('rejects %j', (bad) => {
31
+ expect(isStorableShop(bad)).toBe(false);
32
+ });
33
+ test('a rejected name throws on write rather than landing somewhere', async () => {
34
+ const s = await store();
35
+ await expect(s.set(token('../escape'))).rejects.toThrow(/refusing/);
36
+ });
37
+ test('a rejected name reads as not-installed, never as an error to the buyer', async () => {
38
+ const s = await store();
39
+ await expect(s.get('../escape')).resolves.toBeNull();
40
+ });
41
+ });
42
+ describe('many shops at once', () => {
43
+ test('each shop gets its own token back', async () => {
44
+ const s = await store();
45
+ await s.set(token('a-shop.myshopify.com'));
46
+ await s.set(token('b-shop.myshopify.com'));
47
+ expect((await s.get('a-shop.myshopify.com'))?.accessToken).toBe('shpat_a-shop');
48
+ expect((await s.get('b-shop.myshopify.com'))?.accessToken).toBe('shpat_b-shop');
49
+ expect(await s.get('c-shop.myshopify.com')).toBeNull();
50
+ });
51
+ test('a file whose body names a different shop is not served', async () => {
52
+ // Trust the filename, not the contents: handing back a token under a shop
53
+ // it does not belong to is how one merchant answers for another.
54
+ const dir = await mkdtemp(join(tmpdir(), 'mts-'));
55
+ const s = new MultiTokenStore(dir);
56
+ await s.set(token('a-shop.myshopify.com'));
57
+ const { writeFile } = await import('node:fs/promises');
58
+ await writeFile(join(dir, 'a-shop.myshopify.com.json'), JSON.stringify(token('b-shop.myshopify.com')));
59
+ const fresh = new MultiTokenStore(dir);
60
+ expect(await fresh.get('a-shop.myshopify.com')).toBeNull();
61
+ });
62
+ test('tokens are written 0600 — the file IS the credential', async () => {
63
+ const dir = await mkdtemp(join(tmpdir(), 'mts-'));
64
+ await new MultiTokenStore(dir).set(token('a-shop.myshopify.com'));
65
+ const names = await readdir(dir);
66
+ expect(names).toEqual(['a-shop.myshopify.com.json']);
67
+ expect((await stat(join(dir, names[0]))).mode & 0o777).toBe(0o600);
68
+ });
69
+ test('a half-written temp file never becomes the token', async () => {
70
+ const dir = await mkdtemp(join(tmpdir(), 'mts-'));
71
+ await new MultiTokenStore(dir).set(token('a-shop.myshopify.com'));
72
+ expect(await readdir(dir)).toEqual(['a-shop.myshopify.com.json']);
73
+ expect(JSON.parse(await readFile(join(dir, 'a-shop.myshopify.com.json'), 'utf8')).accessToken).toBe('shpat_a-shop');
74
+ });
75
+ });
76
+ describe('forget, for shop/redact and uninstall', () => {
77
+ test('drops the cache as well as the file, so a revoked token stops answering', async () => {
78
+ const s = await store();
79
+ await s.set(token('a-shop.myshopify.com'));
80
+ expect(await s.get('a-shop.myshopify.com')).not.toBeNull();
81
+ await s.forget('a-shop.myshopify.com');
82
+ expect(await s.get('a-shop.myshopify.com')).toBeNull();
83
+ });
84
+ test('forgetting a shop that was never here is not an error', async () => {
85
+ const s = await store();
86
+ await expect(s.forget('never.myshopify.com')).resolves.toBeUndefined();
87
+ });
88
+ });
89
+ //# sourceMappingURL=multi-token-store.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multi-token-store.test.js","sourceRoot":"","sources":["../../src/__tests__/multi-token-store.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=oauth.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/oauth.test.ts"],"names":[],"mappings":""}