@indigoai-us/hq-cloud 6.18.16 → 6.18.17

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 (75) hide show
  1. package/dist/bin/sync-runner-company.d.ts.map +1 -1
  2. package/dist/bin/sync-runner-company.js +9 -1
  3. package/dist/bin/sync-runner-company.js.map +1 -1
  4. package/dist/bin/sync-runner-company.test.js +184 -3
  5. package/dist/bin/sync-runner-company.test.js.map +1 -1
  6. package/dist/bin/sync-runner.d.ts +8 -0
  7. package/dist/bin/sync-runner.d.ts.map +1 -1
  8. package/dist/bin/sync-runner.js.map +1 -1
  9. package/dist/bin/sync-runner.test.js +57 -2
  10. package/dist/bin/sync-runner.test.js.map +1 -1
  11. package/dist/cli/rescue-core.d.ts.map +1 -1
  12. package/dist/cli/rescue-core.js +21 -6
  13. package/dist/cli/rescue-core.js.map +1 -1
  14. package/dist/cli/rescue-rsync-status.test.js +23 -0
  15. package/dist/cli/rescue-rsync-status.test.js.map +1 -1
  16. package/dist/cli/share-plan-limit-presign.test.d.ts +2 -0
  17. package/dist/cli/share-plan-limit-presign.test.d.ts.map +1 -0
  18. package/dist/cli/share-plan-limit-presign.test.js +271 -0
  19. package/dist/cli/share-plan-limit-presign.test.js.map +1 -0
  20. package/dist/cli/share.d.ts +22 -0
  21. package/dist/cli/share.d.ts.map +1 -1
  22. package/dist/cli/share.js +151 -25
  23. package/dist/cli/share.js.map +1 -1
  24. package/dist/cli/share.test.js +262 -18
  25. package/dist/cli/share.test.js.map +1 -1
  26. package/dist/cli/sync.d.ts +38 -1
  27. package/dist/cli/sync.d.ts.map +1 -1
  28. package/dist/cli/sync.js +2 -1
  29. package/dist/cli/sync.js.map +1 -1
  30. package/dist/index.d.ts +5 -2
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +5 -1
  33. package/dist/index.js.map +1 -1
  34. package/dist/object-io.d.ts +40 -0
  35. package/dist/object-io.d.ts.map +1 -1
  36. package/dist/object-io.js +122 -5
  37. package/dist/object-io.js.map +1 -1
  38. package/dist/object-io.test.js +144 -1
  39. package/dist/object-io.test.js.map +1 -1
  40. package/dist/plan-limit-pause.d.ts +61 -0
  41. package/dist/plan-limit-pause.d.ts.map +1 -0
  42. package/dist/plan-limit-pause.js +252 -0
  43. package/dist/plan-limit-pause.js.map +1 -0
  44. package/dist/plan-limit-pause.test.d.ts +2 -0
  45. package/dist/plan-limit-pause.test.d.ts.map +1 -0
  46. package/dist/plan-limit-pause.test.js +150 -0
  47. package/dist/plan-limit-pause.test.js.map +1 -0
  48. package/dist/plan-limit.d.ts +87 -0
  49. package/dist/plan-limit.d.ts.map +1 -0
  50. package/dist/plan-limit.fixtures.d.ts +34 -0
  51. package/dist/plan-limit.fixtures.d.ts.map +1 -0
  52. package/dist/plan-limit.fixtures.js +96 -0
  53. package/dist/plan-limit.fixtures.js.map +1 -0
  54. package/dist/plan-limit.js +157 -0
  55. package/dist/plan-limit.js.map +1 -0
  56. package/dist/plan-limit.test.d.ts +2 -0
  57. package/dist/plan-limit.test.d.ts.map +1 -0
  58. package/dist/plan-limit.test.js +132 -0
  59. package/dist/plan-limit.test.js.map +1 -0
  60. package/dist/sync/index.d.ts +1 -1
  61. package/dist/sync/index.d.ts.map +1 -1
  62. package/dist/sync/index.js.map +1 -1
  63. package/dist/sync/mutation-client.d.ts +14 -2
  64. package/dist/sync/mutation-client.d.ts.map +1 -1
  65. package/dist/sync/mutation-client.js +34 -2
  66. package/dist/sync/mutation-client.js.map +1 -1
  67. package/dist/sync/mutation-client.test.js +49 -0
  68. package/dist/sync/mutation-client.test.js.map +1 -1
  69. package/dist/vault-client.d.ts +49 -6
  70. package/dist/vault-client.d.ts.map +1 -1
  71. package/dist/vault-client.js +115 -43
  72. package/dist/vault-client.js.map +1 -1
  73. package/dist/vault-client.test.js +86 -10
  74. package/dist/vault-client.test.js.map +1 -1
  75. package/package.json +1 -1
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Plan-limit refusal bodies as the vault service sends them, for tests.
3
+ *
4
+ * `canonicalPlanLimitBody` mirrors the hard-stop 402 body the server builds
5
+ * for a Starter company over storage (`error`, `status`, `blocked`,
6
+ * `resources`, `message`, `fixOptions`, `freeMonth`, `upgradeUrl`) plus the
7
+ * `code: "PLAN_LIMIT_EXCEEDED"` older clients key on. The other bodies are
8
+ * the legacy and per-item shapes clients must keep accepting.
9
+ */
10
+ export const PLAN_LIMIT_UPGRADE_URL = "https://billing.example.test/companies/acme/billing";
11
+ /** Starter storage limit (10 GiB) and a usage over it. */
12
+ const STORAGE_LIMIT = 10_737_418_240;
13
+ const STORAGE_USED = 11_811_160_064;
14
+ /** The canonical HTTP 402 hard-stop body for a company over storage. */
15
+ export function canonicalPlanLimitBody() {
16
+ return {
17
+ error: "plan_limit_reached",
18
+ code: "PLAN_LIMIT_EXCEEDED",
19
+ status: 402,
20
+ blocked: "files.create",
21
+ resources: [
22
+ { resource: "storageBytes", used: STORAGE_USED, limit: STORAGE_LIMIT },
23
+ ],
24
+ message: "New files are paused while Acme is over its Starter limits.",
25
+ fixOptions: { storageBytes: STORAGE_LIMIT },
26
+ freeMonth: {
27
+ eligible: true,
28
+ trialDays: 30,
29
+ url: "https://billing.example.test/companies/acme/billing?offer=free-month",
30
+ },
31
+ upgradeUrl: PLAN_LIMIT_UPGRADE_URL,
32
+ };
33
+ }
34
+ /** The legacy membership 402 (seat limit). */
35
+ export function legacyMembershipPlanLimitBody() {
36
+ return {
37
+ code: "PLAN_LIMIT_EXCEEDED",
38
+ resource: "users",
39
+ used: 5,
40
+ limit: 5,
41
+ requiredPlan: "team",
42
+ upgradeUrl: PLAN_LIMIT_UPGRADE_URL,
43
+ };
44
+ }
45
+ /** A per-item personal-scope presign refusal row. */
46
+ export function personalPresignRefusalRow(key) {
47
+ return {
48
+ key,
49
+ op: "put",
50
+ error: `${key} was not uploaded: your personal vault is over its plan limit.`,
51
+ code: "PLAN_LIMIT_REACHED",
52
+ upgradeUrl: PLAN_LIMIT_UPGRADE_URL,
53
+ };
54
+ }
55
+ /**
56
+ * A per-item company presign refusal row, exactly as the server builds it
57
+ * (`companyPresignRefusalItemError` in the presign handler): the refusal
58
+ * sentence in `error`, the legacy `PLAN_LIMIT_EXCEEDED` code and the upgrade
59
+ * link. The server sends it only for the puts of a MIXED batch; a put-only
60
+ * batch gets the whole-batch 402 body instead.
61
+ */
62
+ export function companyPresignRefusalRow(key) {
63
+ const body = canonicalPlanLimitBody();
64
+ return {
65
+ key,
66
+ op: "put",
67
+ error: body.message,
68
+ code: body.code,
69
+ upgradeUrl: body.upgradeUrl,
70
+ };
71
+ }
72
+ /** The admission check's answer when the company is under its limits. */
73
+ export function admissionAllowedBody() {
74
+ return { allowed: true };
75
+ }
76
+ /**
77
+ * Today's production answer to an admission body on a presign route that
78
+ * predates the admission check: the body names no `company`, so it is a 400.
79
+ */
80
+ export function admissionUnsupportedBody() {
81
+ return { error: "Pass either `personal: true` or `company`" };
82
+ }
83
+ /** Mutation-prepare's HTTP 200 skip body. */
84
+ export function mutationPrepareSkippedBody() {
85
+ const body = canonicalPlanLimitBody();
86
+ return {
87
+ skipped: true,
88
+ code: "PLAN_LIMIT_REACHED",
89
+ message: "notes/a.md was not uploaded: your personal vault is over its plan limit.",
90
+ error: body.error,
91
+ blocked: body.blocked,
92
+ resources: body.resources,
93
+ upgradeUrl: body.upgradeUrl,
94
+ };
95
+ }
96
+ //# sourceMappingURL=plan-limit.fixtures.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan-limit.fixtures.js","sourceRoot":"","sources":["../src/plan-limit.fixtures.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,CAAC,MAAM,sBAAsB,GACjC,qDAAqD,CAAC;AAExD,0DAA0D;AAC1D,MAAM,aAAa,GAAG,cAAc,CAAC;AACrC,MAAM,YAAY,GAAG,cAAc,CAAC;AAEpC,wEAAwE;AACxE,MAAM,UAAU,sBAAsB;IACpC,OAAO;QACL,KAAK,EAAE,oBAAoB;QAC3B,IAAI,EAAE,qBAAqB;QAC3B,MAAM,EAAE,GAAG;QACX,OAAO,EAAE,cAAc;QACvB,SAAS,EAAE;YACT,EAAE,QAAQ,EAAE,cAAc,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,aAAa,EAAE;SACvE;QACD,OAAO,EAAE,6DAA6D;QACtE,UAAU,EAAE,EAAE,YAAY,EAAE,aAAa,EAAE;QAC3C,SAAS,EAAE;YACT,QAAQ,EAAE,IAAI;YACd,SAAS,EAAE,EAAE;YACb,GAAG,EAAE,sEAAsE;SAC5E;QACD,UAAU,EAAE,sBAAsB;KACnC,CAAC;AACJ,CAAC;AAED,8CAA8C;AAC9C,MAAM,UAAU,6BAA6B;IAC3C,OAAO;QACL,IAAI,EAAE,qBAAqB;QAC3B,QAAQ,EAAE,OAAO;QACjB,IAAI,EAAE,CAAC;QACP,KAAK,EAAE,CAAC;QACR,YAAY,EAAE,MAAM;QACpB,UAAU,EAAE,sBAAsB;KACnC,CAAC;AACJ,CAAC;AAED,qDAAqD;AACrD,MAAM,UAAU,yBAAyB,CAAC,GAAW;IACnD,OAAO;QACL,GAAG;QACH,EAAE,EAAE,KAAK;QACT,KAAK,EAAE,GAAG,GAAG,gEAAgE;QAC7E,IAAI,EAAE,oBAAoB;QAC1B,UAAU,EAAE,sBAAsB;KACnC,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,wBAAwB,CAAC,GAAW;IAClD,MAAM,IAAI,GAAG,sBAAsB,EAAE,CAAC;IACtC,OAAO;QACL,GAAG;QACH,EAAE,EAAE,KAAK;QACT,KAAK,EAAE,IAAI,CAAC,OAAO;QACnB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,UAAU,EAAE,IAAI,CAAC,UAAU;KAC5B,CAAC;AACJ,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,oBAAoB;IAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAC3B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,wBAAwB;IACtC,OAAO,EAAE,KAAK,EAAE,2CAA2C,EAAE,CAAC;AAChE,CAAC;AAED,6CAA6C;AAC7C,MAAM,UAAU,0BAA0B;IACxC,MAAM,IAAI,GAAG,sBAAsB,EAAE,CAAC;IACtC,OAAO;QACL,OAAO,EAAE,IAAI;QACb,IAAI,EAAE,oBAAoB;QAC1B,OAAO,EAAE,0EAA0E;QACnF,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,UAAU,EAAE,IAAI,CAAC,UAAU;KAC5B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,157 @@
1
+ /**
2
+ * One parser for every shape the vault service uses to refuse a write because
3
+ * the company (or personal vault) is over its plan limits.
4
+ *
5
+ * Shapes accepted:
6
+ *
7
+ * 1. The canonical hard-stop body, sent with HTTP 402:
8
+ * `{ error: "plan_limit_reached", code: "PLAN_LIMIT_EXCEEDED", status: 402,
9
+ * blocked, resources: [{ resource, used, limit }], message, fixOptions,
10
+ * freeMonth?, upgradeUrl }`
11
+ * 2. The legacy membership 402:
12
+ * `{ code: "PLAN_LIMIT_EXCEEDED", resource, used, limit, requiredPlan, upgradeUrl }`
13
+ * 3. A per-item presign row: `{ key, op, error, code: "PLAN_LIMIT_REACHED" |
14
+ * "PLAN_LIMIT_EXCEEDED", upgradeUrl }`. Here `error` is either the machine
15
+ * value `plan_limit_reached` or a human sentence.
16
+ * 4. The sync mutation-prepare skip, sent with HTTP 200:
17
+ * `{ skipped: true, code, message, error, blocked, resources, upgradeUrl }`
18
+ *
19
+ * A body is a plan-limit refusal only when it carries a plan-limit marker
20
+ * (`error: "plan_limit_reached"`, or `code` of `PLAN_LIMIT_EXCEEDED` /
21
+ * `PLAN_LIMIT_REACHED`) and an `https:` `upgradeUrl`. The HTTP status alone
22
+ * never makes a body a plan-limit refusal: other 402s (billing, payment
23
+ * method) keep their own handling.
24
+ *
25
+ * This module has no imports so the vault client, the presign transport and
26
+ * the mutation client can all share it without a cycle.
27
+ */
28
+ /** Machine `error` value of the canonical hard-stop body. */
29
+ export const PLAN_LIMIT_REACHED_ERROR = "plan_limit_reached";
30
+ /** Machine `code` values that mark a plan-limit refusal (current and legacy). */
31
+ export const PLAN_LIMIT_CODES = new Set([
32
+ "PLAN_LIMIT_EXCEEDED",
33
+ "PLAN_LIMIT_REACHED",
34
+ ]);
35
+ function isRecord(value) {
36
+ return typeof value === "object" && value !== null && !Array.isArray(value);
37
+ }
38
+ function nonEmptyString(value) {
39
+ return typeof value === "string" && value.trim() !== "" ? value : undefined;
40
+ }
41
+ function finiteNumber(value) {
42
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
43
+ }
44
+ /**
45
+ * True for an absolute `https:` URL. Upgrade links are opened in a browser
46
+ * or printed for the user to click, so any other scheme is rejected.
47
+ */
48
+ export function isHttpsUpgradeUrl(value) {
49
+ if (typeof value !== "string" || value.trim() === "")
50
+ return false;
51
+ try {
52
+ return new URL(value).protocol === "https:";
53
+ }
54
+ catch {
55
+ return false;
56
+ }
57
+ }
58
+ /** True when `error` or `code` names a plan-limit refusal. */
59
+ export function hasPlanLimitMarker(fields) {
60
+ return (fields.error === PLAN_LIMIT_REACHED_ERROR ||
61
+ (typeof fields.code === "string" && PLAN_LIMIT_CODES.has(fields.code)));
62
+ }
63
+ function parseResources(body) {
64
+ if (Array.isArray(body.resources)) {
65
+ const rows = [];
66
+ for (const row of body.resources) {
67
+ if (!isRecord(row))
68
+ continue;
69
+ const resource = nonEmptyString(row.resource);
70
+ const used = finiteNumber(row.used);
71
+ const limit = finiteNumber(row.limit);
72
+ if (resource === undefined || used === undefined || limit === undefined)
73
+ continue;
74
+ rows.push({ resource, used, limit });
75
+ }
76
+ return rows.length > 0 ? rows : undefined;
77
+ }
78
+ // Legacy membership 402 carries one resource at the top level.
79
+ const resource = nonEmptyString(body.resource);
80
+ const used = finiteNumber(body.used);
81
+ const limit = finiteNumber(body.limit);
82
+ if (resource !== undefined && used !== undefined && limit !== undefined) {
83
+ return [{ resource, used, limit }];
84
+ }
85
+ return undefined;
86
+ }
87
+ function parseFixOptions(value) {
88
+ if (!isRecord(value))
89
+ return undefined;
90
+ const out = {};
91
+ for (const [key, raw] of Object.entries(value)) {
92
+ const n = finiteNumber(raw);
93
+ if (n !== undefined)
94
+ out[key] = n;
95
+ }
96
+ return Object.keys(out).length > 0 ? out : undefined;
97
+ }
98
+ /**
99
+ * Parse a response body (already JSON-decoded), a per-item presign row, or a
100
+ * stored refusal into a plan-limit refusal. Returns null unless the body has a
101
+ * plan-limit marker and an `https:` upgrade link.
102
+ */
103
+ export function parsePlanLimitRefusal(body) {
104
+ if (!isRecord(body))
105
+ return null;
106
+ if (!hasPlanLimitMarker(body))
107
+ return null;
108
+ const upgradeUrl = body.upgradeUrl;
109
+ if (!isHttpsUpgradeUrl(upgradeUrl))
110
+ return null;
111
+ const refusal = { upgradeUrl };
112
+ const errorField = nonEmptyString(body.error);
113
+ const message = nonEmptyString(body.message) ??
114
+ (errorField !== undefined && errorField !== PLAN_LIMIT_REACHED_ERROR
115
+ ? errorField
116
+ : undefined);
117
+ if (message !== undefined)
118
+ refusal.message = message;
119
+ const code = nonEmptyString(body.code);
120
+ if (code !== undefined)
121
+ refusal.code = code;
122
+ if (errorField === PLAN_LIMIT_REACHED_ERROR)
123
+ refusal.error = errorField;
124
+ const blocked = nonEmptyString(body.blocked);
125
+ if (blocked !== undefined)
126
+ refusal.blocked = blocked;
127
+ const resources = parseResources(body);
128
+ if (resources !== undefined)
129
+ refusal.resources = resources;
130
+ const fixOptions = parseFixOptions(body.fixOptions);
131
+ if (fixOptions !== undefined)
132
+ refusal.fixOptions = fixOptions;
133
+ if (isRecord(body.freeMonth))
134
+ refusal.freeMonth = { ...body.freeMonth };
135
+ return refusal;
136
+ }
137
+ /** Parse a raw response text; non-JSON text is never a plan-limit refusal. */
138
+ export function parsePlanLimitRefusalText(text) {
139
+ try {
140
+ return parsePlanLimitRefusal(JSON.parse(text));
141
+ }
142
+ catch {
143
+ return null;
144
+ }
145
+ }
146
+ /**
147
+ * One console line for a `plan-limit` sync event: how many files were
148
+ * refused, the server's sentence, and the upgrade link.
149
+ */
150
+ export function formatPlanLimitNotice(event) {
151
+ const files = event.count === undefined
152
+ ? "New files are"
153
+ : `${event.count} file${event.count === 1 ? " is" : "s are"}`;
154
+ const reason = event.message ? ` ${event.message}` : "";
155
+ return ` ! ${files} paused by your plan limit.${reason} Upgrade: ${event.upgradeUrl}`;
156
+ }
157
+ //# sourceMappingURL=plan-limit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan-limit.js","sourceRoot":"","sources":["../src/plan-limit.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=plan-limit.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan-limit.test.d.ts","sourceRoot":"","sources":["../src/plan-limit.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,132 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { formatPlanLimitNotice, hasPlanLimitMarker, isHttpsUpgradeUrl, parsePlanLimitRefusal, parsePlanLimitRefusalText, } from "./plan-limit.js";
3
+ import { PLAN_LIMIT_UPGRADE_URL, canonicalPlanLimitBody, companyPresignRefusalRow, legacyMembershipPlanLimitBody, mutationPrepareSkippedBody, personalPresignRefusalRow, } from "./plan-limit.fixtures.js";
4
+ describe("parsePlanLimitRefusal", () => {
5
+ it("reads every field of the canonical 402 hard-stop body", () => {
6
+ expect(parsePlanLimitRefusal(canonicalPlanLimitBody())).toEqual({
7
+ upgradeUrl: PLAN_LIMIT_UPGRADE_URL,
8
+ message: "New files are paused while Acme is over its Starter limits.",
9
+ code: "PLAN_LIMIT_EXCEEDED",
10
+ error: "plan_limit_reached",
11
+ blocked: "files.create",
12
+ resources: [
13
+ { resource: "storageBytes", used: 11_811_160_064, limit: 10_737_418_240 },
14
+ ],
15
+ fixOptions: { storageBytes: 10_737_418_240 },
16
+ freeMonth: {
17
+ eligible: true,
18
+ trialDays: 30,
19
+ url: "https://billing.example.test/companies/acme/billing?offer=free-month",
20
+ },
21
+ });
22
+ });
23
+ it("recognises the canonical body by its error field even without code or status", () => {
24
+ const { code: _code, status: _status, ...body } = canonicalPlanLimitBody();
25
+ expect(parsePlanLimitRefusal(body)).toMatchObject({
26
+ error: "plan_limit_reached",
27
+ upgradeUrl: PLAN_LIMIT_UPGRADE_URL,
28
+ });
29
+ });
30
+ it("reads the legacy membership 402 and lifts its top-level resource", () => {
31
+ expect(parsePlanLimitRefusal(legacyMembershipPlanLimitBody())).toEqual({
32
+ upgradeUrl: PLAN_LIMIT_UPGRADE_URL,
33
+ code: "PLAN_LIMIT_EXCEEDED",
34
+ resources: [{ resource: "users", used: 5, limit: 5 }],
35
+ });
36
+ });
37
+ it("reads a per-item personal presign row and keeps its sentence as the message", () => {
38
+ expect(parsePlanLimitRefusal(personalPresignRefusalRow("notes/a.md"))).toEqual({
39
+ upgradeUrl: PLAN_LIMIT_UPGRADE_URL,
40
+ code: "PLAN_LIMIT_REACHED",
41
+ message: "notes/a.md was not uploaded: your personal vault is over its plan limit.",
42
+ });
43
+ });
44
+ it("reads the server's exact per-item company presign row", () => {
45
+ expect(companyPresignRefusalRow("docs/a.md")).toEqual({
46
+ key: "docs/a.md",
47
+ op: "put",
48
+ error: "New files are paused while Acme is over its Starter limits.",
49
+ code: "PLAN_LIMIT_EXCEEDED",
50
+ upgradeUrl: PLAN_LIMIT_UPGRADE_URL,
51
+ });
52
+ expect(parsePlanLimitRefusal(companyPresignRefusalRow("docs/a.md"))).toEqual({
53
+ upgradeUrl: PLAN_LIMIT_UPGRADE_URL,
54
+ code: "PLAN_LIMIT_EXCEEDED",
55
+ message: "New files are paused while Acme is over its Starter limits.",
56
+ });
57
+ });
58
+ it("reads mutation-prepare's 200 skipped body", () => {
59
+ expect(parsePlanLimitRefusal(mutationPrepareSkippedBody())).toMatchObject({
60
+ upgradeUrl: PLAN_LIMIT_UPGRADE_URL,
61
+ code: "PLAN_LIMIT_REACHED",
62
+ error: "plan_limit_reached",
63
+ blocked: "files.create",
64
+ });
65
+ });
66
+ it("does not treat a body with an upgrade link but no plan-limit marker as a refusal", () => {
67
+ // A billing 402 (no card on file, trial ended) is not a plan limit.
68
+ expect(parsePlanLimitRefusal({ message: "Upgrade to continue", upgradeUrl: PLAN_LIMIT_UPGRADE_URL })).toBeNull();
69
+ expect(parsePlanLimitRefusal({ error: "billing_required", upgradeUrl: PLAN_LIMIT_UPGRADE_URL })).toBeNull();
70
+ });
71
+ it("accepts only https upgrade links", () => {
72
+ for (const upgradeUrl of [
73
+ "http://billing.example.test/companies/acme/billing",
74
+ "javascript:alert(1)",
75
+ "file:///etc/passwd",
76
+ "/companies/acme/billing",
77
+ "not a url",
78
+ ]) {
79
+ expect(parsePlanLimitRefusal({ ...canonicalPlanLimitBody(), upgradeUrl })).toBeNull();
80
+ }
81
+ expect(isHttpsUpgradeUrl(PLAN_LIMIT_UPGRADE_URL)).toBe(true);
82
+ expect(isHttpsUpgradeUrl(undefined)).toBe(false);
83
+ });
84
+ it("rejects bodies that are not plan-limit refusals", () => {
85
+ // No upgrade link: nothing to show the customer.
86
+ const { upgradeUrl: _u, ...noLink } = canonicalPlanLimitBody();
87
+ expect(parsePlanLimitRefusal(noLink)).toBeNull();
88
+ expect(parsePlanLimitRefusal({ message: "Forbidden", upgradeUrl: PLAN_LIMIT_UPGRADE_URL })).toBeNull();
89
+ // Another per-key denial.
90
+ expect(parsePlanLimitRefusal({ key: "a", op: "put", error: "denied", code: "FILES_PRESIGN_FORBIDDEN" })).toBeNull();
91
+ expect(parsePlanLimitRefusal(null)).toBeNull();
92
+ expect(parsePlanLimitRefusal([canonicalPlanLimitBody()])).toBeNull();
93
+ });
94
+ it("drops malformed resources and fix options instead of failing", () => {
95
+ const body = {
96
+ ...canonicalPlanLimitBody(),
97
+ resources: [{ resource: "storageBytes", used: "lots", limit: 1 }, "x"],
98
+ fixOptions: { storageBytes: "ten" },
99
+ freeMonth: "yes",
100
+ };
101
+ const refusal = parsePlanLimitRefusal(body);
102
+ expect(refusal).not.toBeNull();
103
+ expect(refusal).not.toHaveProperty("resources");
104
+ expect(refusal).not.toHaveProperty("fixOptions");
105
+ expect(refusal).not.toHaveProperty("freeMonth");
106
+ });
107
+ it("parses raw text and ignores non-JSON", () => {
108
+ expect(parsePlanLimitRefusalText(JSON.stringify(canonicalPlanLimitBody()))?.upgradeUrl).toBe(PLAN_LIMIT_UPGRADE_URL);
109
+ expect(parsePlanLimitRefusalText("<html>Payment Required</html>")).toBeNull();
110
+ });
111
+ });
112
+ describe("hasPlanLimitMarker", () => {
113
+ it("matches the canonical error value and both codes", () => {
114
+ expect(hasPlanLimitMarker({ error: "plan_limit_reached" })).toBe(true);
115
+ expect(hasPlanLimitMarker({ code: "PLAN_LIMIT_EXCEEDED" })).toBe(true);
116
+ expect(hasPlanLimitMarker({ code: "PLAN_LIMIT_REACHED" })).toBe(true);
117
+ expect(hasPlanLimitMarker({ code: "plan_limit_reached" })).toBe(false);
118
+ expect(hasPlanLimitMarker({ error: "Plan limit reached" })).toBe(false);
119
+ });
120
+ });
121
+ describe("formatPlanLimitNotice", () => {
122
+ it("names the file count, the server sentence and the upgrade link", () => {
123
+ expect(formatPlanLimitNotice({
124
+ upgradeUrl: PLAN_LIMIT_UPGRADE_URL,
125
+ count: 3,
126
+ message: "New files are paused while Acme is over its Starter limits.",
127
+ })).toBe(` ! 3 files are paused by your plan limit. New files are paused while Acme is over its Starter limits. Upgrade: ${PLAN_LIMIT_UPGRADE_URL}`);
128
+ expect(formatPlanLimitNotice({ upgradeUrl: PLAN_LIMIT_UPGRADE_URL, count: 1 })).toBe(` ! 1 file is paused by your plan limit. Upgrade: ${PLAN_LIMIT_UPGRADE_URL}`);
129
+ expect(formatPlanLimitNotice({ upgradeUrl: PLAN_LIMIT_UPGRADE_URL })).toBe(` ! New files are paused by your plan limit. Upgrade: ${PLAN_LIMIT_UPGRADE_URL}`);
130
+ });
131
+ });
132
+ //# sourceMappingURL=plan-limit.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan-limit.test.js","sourceRoot":"","sources":["../src/plan-limit.test.ts"],"names":[],"mappings":"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"}
@@ -23,7 +23,7 @@ export type { SyncLatencyMetric, PublishSyncLatencyMetricOptions, } from "./metr
23
23
  export { createLogger } from "./logger.js";
24
24
  export type { CreateLoggerOptions, Logger } from "./logger.js";
25
25
  export { MutationClient, mutationRequestDigest } from "./mutation-client.js";
26
- export type { MutationAuthorization, MutationOperation, MutationRequest, MutationSource, MutationStatus, MutationStatusName, MutationClientOptions, SubmitMutationInput, MutationSubmission, } from "./mutation-client.js";
26
+ export type { MutationAuthorization, MutationOperation, MutationRequest, MutationSource, MutationStatus, MutationStatusName, MutationSkippedStatus, MutationClientOptions, SubmitMutationInput, MutationSubmission, } from "./mutation-client.js";
27
27
  export { DefaultParallelSyncCoordinator } from "./parallel-sync-coordinator.js";
28
28
  export type { CoordinatorClock, CoordinatorSnapshot, ParallelSyncCoordinator, ParallelSyncCoordinatorOptions, PathIdentity, } from "./parallel-sync-coordinator.js";
29
29
  export { InlineSyncWorkerAdapter } from "./sync-work.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sync/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sync/index.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sync/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,oBAAoB,EACpB,wBAAwB,EACxB,eAAe,EACf,oBAAoB,EACpB,eAAe,EACf,eAAe,EACf,kBAAkB,EAClB,uBAAuB,EACvB,2BAA2B,GAC5B,MAAM,iBAAiB,CAAC;AAQzB,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAQ3E,OAAO,EACL,gCAAgC,EAChC,0BAA0B,GAC3B,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,oBAAoB,EACpB,4BAA4B,EAC5B,oBAAoB,EACpB,4BAA4B,EAC5B,kBAAkB,EAClB,yBAAyB,EACzB,mCAAmC,EACnC,iCAAiC,EACjC,0BAA0B,GAC3B,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAAE,wBAAwB,EAAE,iCAAiC,EAAE,qBAAqB,EAAE,qCAAqC,EAAE,MAAM,yBAAyB,CAAC;AAGpK,OAAO,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AAIvE,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,cAAc,EACd,oBAAoB,EACpB,kBAAkB,EAClB,iCAAiC,EACjC,yBAAyB,EACzB,oBAAoB,EACpB,gCAAgC,EAChC,mCAAmC,GACpC,MAAM,oBAAoB,CAAC;AAgB5B,OAAO,EACL,qBAAqB,EACrB,wBAAwB,EACxB,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,cAAc,CAAC;AAMtB,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAa7E,OAAO,EAAE,8BAA8B,EAAE,MAAM,gCAAgC,CAAC;AAShF,OAAO,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAiBzD,OAAO,EAAE,uBAAuB,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AAY3I,OAAO,EAAE,6BAA6B,EAAE,2BAA2B,EAAE,MAAM,gCAAgC,CAAC;AAE5G,OAAO,EAAE,4BAA4B,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sync/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,oBAAoB,EACpB,wBAAwB,EACxB,eAAe,EACf,oBAAoB,EACpB,eAAe,EACf,eAAe,EACf,kBAAkB,EAClB,uBAAuB,EACvB,2BAA2B,GAC5B,MAAM,iBAAiB,CAAC;AAQzB,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAQ3E,OAAO,EACL,gCAAgC,EAChC,0BAA0B,GAC3B,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,oBAAoB,EACpB,4BAA4B,EAC5B,oBAAoB,EACpB,4BAA4B,EAC5B,kBAAkB,EAClB,yBAAyB,EACzB,mCAAmC,EACnC,iCAAiC,EACjC,0BAA0B,GAC3B,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAAE,wBAAwB,EAAE,iCAAiC,EAAE,qBAAqB,EAAE,qCAAqC,EAAE,MAAM,yBAAyB,CAAC;AAGpK,OAAO,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AAIvE,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,cAAc,EACd,oBAAoB,EACpB,kBAAkB,EAClB,iCAAiC,EACjC,yBAAyB,EACzB,oBAAoB,EACpB,gCAAgC,EAChC,mCAAmC,GACpC,MAAM,oBAAoB,CAAC;AAgB5B,OAAO,EACL,qBAAqB,EACrB,wBAAwB,EACxB,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,cAAc,CAAC;AAMtB,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAc7E,OAAO,EAAE,8BAA8B,EAAE,MAAM,gCAAgC,CAAC;AAShF,OAAO,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAiBzD,OAAO,EAAE,uBAAuB,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AAY3I,OAAO,EAAE,6BAA6B,EAAE,2BAA2B,EAAE,MAAM,gCAAgC,CAAC;AAE5G,OAAO,EAAE,4BAA4B,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC"}
@@ -1,7 +1,8 @@
1
1
  import { CandidateUploader, type CandidateFetch, type CandidateTransportAuthorization } from "./candidate-uploader.js";
2
2
  import { DurableAttemptSpool, MultipartUploader } from "./multipart-uploader.js";
3
+ import { type PlanLimitRefusal } from "../plan-limit.js";
3
4
  export type MutationOperation = "upsert" | "delete";
4
- export type MutationStatusName = "prepared" | "uploading" | "candidate-ready" | "ready" | "applying" | "committed" | "conflict" | "rejected" | "blocked";
5
+ export type MutationStatusName = "prepared" | "uploading" | "candidate-ready" | "ready" | "applying" | "committed" | "conflict" | "rejected" | "blocked" | "skipped";
5
6
  export interface MutationSource {
6
7
  kind: "regular" | "symlink";
7
8
  mode: number;
@@ -21,6 +22,17 @@ export interface MutationRequest {
21
22
  scopeRevision: string;
22
23
  source?: MutationSource;
23
24
  }
25
+ /**
26
+ * The server refused the mutation because of the plan limit, before any
27
+ * attempt was created. Both the hard-stop 402 body and mutation-prepare's
28
+ * `200 {skipped: true, …}` arrive as this status. It is terminal for the
29
+ * submission and carries no attemptId; the change can be submitted again
30
+ * later.
31
+ */
32
+ export type MutationSkippedStatus = {
33
+ status: "skipped";
34
+ reason: "plan-limit";
35
+ } & PlanLimitRefusal;
24
36
  export type MutationStatus = {
25
37
  status: "prepared" | "uploading" | "candidate-ready" | "ready" | "applying";
26
38
  attemptId: string;
@@ -37,7 +49,7 @@ export type MutationStatus = {
37
49
  status: "rejected" | "blocked";
38
50
  attemptId: string;
39
51
  code: string;
40
- };
52
+ } | MutationSkippedStatus;
41
53
  export type MutationAuthorization = CandidateTransportAuthorization;
42
54
  export interface MutationClientOptions {
43
55
  apiUrl: string;
@@ -1 +1 @@
1
- {"version":3,"file":"mutation-client.d.ts","sourceRoot":"","sources":["../../src/sync/mutation-client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mutation-client.d.ts","sourceRoot":"","sources":["../../src/sync/mutation-client.ts"],"names":[],"mappings":"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"}
@@ -11,6 +11,7 @@ import { createHash } from "node:crypto";
11
11
  import * as fs from "node:fs";
12
12
  import { CandidateUploader, MAX_SMALL_CANDIDATE_BYTES, } from "./candidate-uploader.js";
13
13
  import { DurableAttemptSpool, MultipartUploader } from "./multipart-uploader.js";
14
+ import { parsePlanLimitRefusal } from "../plan-limit.js";
14
15
  const SHA256 = /^sha256:[a-f0-9]{64}$/;
15
16
  const OPAQUE_ID = /^[A-Za-z0-9._~-]{1,512}$/;
16
17
  function isRecord(value) {
@@ -69,7 +70,22 @@ function validateRequest(request) {
69
70
  throw new Error("realtime mutation: upsert source metadata is invalid");
70
71
  }
71
72
  }
73
+ /**
74
+ * A plan-limit refusal in any of the server's forms (402 hard-stop body, or
75
+ * mutation-prepare's `200 {skipped: true}`), as a skipped status. Null when
76
+ * the body is not one.
77
+ */
78
+ function planLimitSkip(value) {
79
+ const refusal = parsePlanLimitRefusal(value);
80
+ return refusal ? { status: "skipped", reason: "plan-limit", ...refusal } : null;
81
+ }
72
82
  function parseStatus(value) {
83
+ if (isRecord(value) && value.skipped === true) {
84
+ const skipped = planLimitSkip(value);
85
+ if (!skipped)
86
+ throw new Error("realtime mutation: skipped response is invalid");
87
+ return skipped;
88
+ }
73
89
  if (!isRecord(value) || typeof value.status !== "string" || typeof value.attemptId !== "string" || !OPAQUE_ID.test(value.attemptId)) {
74
90
  throw new Error("realtime mutation: status response is invalid");
75
91
  }
@@ -100,7 +116,7 @@ function parseStatus(value) {
100
116
  }
101
117
  }
102
118
  function isTerminal(status) {
103
- return status.status === "committed" || status.status === "conflict" || status.status === "rejected" || status.status === "blocked";
119
+ return status.status === "committed" || status.status === "conflict" || status.status === "rejected" || status.status === "blocked" || status.status === "skipped";
104
120
  }
105
121
  export class MutationClient {
106
122
  apiUrl;
@@ -136,6 +152,9 @@ export class MutationClient {
136
152
  }
137
153
  }
138
154
  const status = await this.prepare(input.authorization, input.request);
155
+ // A plan-limit skip created no attempt: nothing to record or commit.
156
+ if (status.status === "skipped")
157
+ return { requestDigest: digest, status };
139
158
  if (!isTerminal(status))
140
159
  await input.onPrepared?.(status.attemptId);
141
160
  if (isTerminal(status) || input.request.operation === "delete") {
@@ -220,8 +239,21 @@ export class MutationClient {
220
239
  const text = await response.text().catch((error) => {
221
240
  throw new Error(`realtime mutation: ${label} response unreadable; recover with status: ${error instanceof Error ? error.message : String(error)}`);
222
241
  });
223
- if (!response.ok)
242
+ if (!response.ok) {
243
+ // A plan-limit refusal is an answer, not a failure: report it as a
244
+ // skipped status instead of an error the caller would retry.
245
+ let body;
246
+ try {
247
+ body = JSON.parse(text);
248
+ }
249
+ catch {
250
+ body = undefined;
251
+ }
252
+ const skipped = planLimitSkip(body);
253
+ if (skipped)
254
+ return skipped;
224
255
  throw new Error(`realtime mutation: ${label} failed (${response.status}); recover with status: ${text.slice(0, 300)}`);
256
+ }
225
257
  try {
226
258
  return parseStatus(JSON.parse(text));
227
259
  }