@stardeck-customer-apps/controls 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/SKILL.md CHANGED
@@ -3,8 +3,9 @@
3
3
  **Package**: `@stardeck-customer-apps/controls`
4
4
 
5
5
  Controls are values Stardeck owns and your app reads while it runs: feature
6
- flags, what the plan allows, and which payment provider and methods the app
7
- uses. Your app asks Stardeck about once a minute and keeps the answer in memory.
6
+ flags, what the plan allows, how many locations have POS Pro hardware, which
7
+ Accounting edition the business pays for, and which payment provider and methods
8
+ the app uses. Your app asks Stardeck about once a minute and keeps the answer in memory.
8
9
  A change on Stardeck reaches the app within about a minute, with no deploy.
9
10
 
10
11
  Never invent controls or flags of your own. The list is fixed by Stardeck and
@@ -48,15 +49,73 @@ the last values stay.
48
49
  | `flag.paymentsSetupStep` | `boolean` | no |
49
50
  | `plan.locations` | `{ included: number \| null, max: number \| null, extraMonthly: { amount, currency } \| null }` | no |
50
51
  | `plan.multiEntity` | `boolean` | no |
52
+ | `pos.pro` | `{ locations: number \| null }` | no |
53
+ | `plan.accountingEdition` | `"small" \| "medium" \| null` | no |
51
54
  | `payments.provider` | `"beam" \| "stripe" \| null` | yes |
52
55
  | `payments.methods` | `{ card: boolean, promptPay: boolean }` | yes |
53
56
 
54
57
  - `plan.locations.included` is what the plan covers. `max` is a hard cap (the
55
58
  entry plan has 1); `null` means no cap, and each location past `included` is
56
59
  billed at `extraMonthly`. `included: null` means the number was agreed per deal.
60
+ - `pos.pro.locations` is how many locations the business pays for POS Pro (Edge
61
+ receipt printing, Bolt+ card terminals). `null` means no limit; `0` means none
62
+ (the entry plan never includes POS Pro). See "POS Pro hardware" below.
63
+ - `plan.accountingEdition` is the Accounting edition the business pays for, an
64
+ add-on next to its plan. `null` means it has no Accounting plan. See
65
+ "Accounting edition" below.
57
66
  - Keys are never removed in later versions, so code written against today's list
58
67
  keeps working.
59
68
 
69
+ ## POS Pro hardware
70
+
71
+ Check `pos.pro` only when someone adds POS hardware (pairs a receipt printer or
72
+ a card terminal) at a location that has none yet. Never check it on a sale, a
73
+ print or a payment: hardware that is already set up keeps working whatever the
74
+ count says, so a business is never stopped mid-service.
75
+
76
+ ```ts
77
+ const posPro = await getControl("pos.pro");
78
+ if (!posPro.ok) {
79
+ // Unknown right now: refuse the add with a retry message (answer 503), and
80
+ // leave the hardware the business already has alone.
81
+ return unavailable("POS Pro could not be checked. Try again in a minute.");
82
+ }
83
+ const { locations } = posPro.value;
84
+ const usesPosPro = await countLocationsWithPosHardware(); // locations with a printer or terminal
85
+ if (locations !== null && !locationAlreadyHasHardware && usesPosPro >= locations) {
86
+ return refuse("Every POS Pro location you pay for is in use. Add POS Pro to this location first.");
87
+ }
88
+ ```
89
+
90
+ A location that already has a printer or terminal can add more without using up
91
+ another POS Pro location.
92
+
93
+ ## Accounting edition
94
+
95
+ Accounting comes in two editions, Small and Medium, each bought as an add-on.
96
+ Finance is on every plan and never depends on this control. Read
97
+ `plan.accountingEdition` to decide which Accounting the app offers:
98
+
99
+ - `"small"` or `"medium"`: offer that edition.
100
+ - `null`: the business has no Accounting plan. Hide Accounting and refuse its
101
+ writes with a message that it needs an Accounting plan. Records it made while
102
+ it had one stay readable.
103
+ - `ok: false`: unknown right now, which is not the same as `null`. Refuse new
104
+ accounting records (journal entries, period closes, filings) with a retry
105
+ message (answer 503), and keep everything that exists readable. Never hide
106
+ Accounting or treat the business as having none because of `ok: false`.
107
+
108
+ ```ts
109
+ const edition = await getControl("plan.accountingEdition");
110
+ if (!edition.ok) {
111
+ return unavailable("Accounting could not be checked. Try again in a minute.");
112
+ }
113
+ if (edition.value === null) {
114
+ return refuse("Accounting needs an Accounting plan.");
115
+ }
116
+ // edition.value is "small" or "medium"
117
+ ```
118
+
60
119
  Types and the list itself are in the root export:
61
120
 
62
121
  ```ts
package/dist/index.cjs CHANGED
@@ -52,6 +52,22 @@ var CONTROLS = {
52
52
  "payments.methods": {
53
53
  schema: import_zod.z.object({ card: import_zod.z.boolean(), promptPay: import_zod.z.boolean() }),
54
54
  appWrite: true
55
+ },
56
+ "pos.pro": {
57
+ schema: import_zod.z.object({
58
+ /** POS Pro locations paid for. null = unlimited (legacy plans, internal orgs). */
59
+ locations: import_zod.z.number().int().nonnegative().nullable()
60
+ }),
61
+ appWrite: false
62
+ },
63
+ /**
64
+ * The Accounting Module edition the org pays for, each an add-on next to its plan. null means
65
+ * no Accounting plan: the app has no Accounting. `{ ok: false }` means unknown right now:
66
+ * refuse new accounting records and keep existing ones readable.
67
+ */
68
+ "plan.accountingEdition": {
69
+ schema: import_zod.z.enum(["small", "medium"]).nullable(),
70
+ appWrite: false
55
71
  }
56
72
  };
57
73
  var CONTROL_KEYS = Object.keys(CONTROLS);
package/dist/index.d.cts CHANGED
@@ -48,6 +48,24 @@ declare const CONTROLS: {
48
48
  }, z.core.$strip>;
49
49
  readonly appWrite: true;
50
50
  };
51
+ readonly "pos.pro": {
52
+ readonly schema: z.ZodObject<{
53
+ locations: z.ZodNullable<z.ZodNumber>;
54
+ }, z.core.$strip>;
55
+ readonly appWrite: false;
56
+ };
57
+ /**
58
+ * The Accounting Module edition the org pays for, each an add-on next to its plan. null means
59
+ * no Accounting plan: the app has no Accounting. `{ ok: false }` means unknown right now:
60
+ * refuse new accounting records and keep existing ones readable.
61
+ */
62
+ readonly "plan.accountingEdition": {
63
+ readonly schema: z.ZodNullable<z.ZodEnum<{
64
+ small: "small";
65
+ medium: "medium";
66
+ }>>;
67
+ readonly appWrite: false;
68
+ };
51
69
  };
52
70
  type ControlKey = keyof typeof CONTROLS;
53
71
  type ControlValue<K extends ControlKey> = z.infer<(typeof CONTROLS)[K]["schema"]>;
package/dist/index.d.ts CHANGED
@@ -48,6 +48,24 @@ declare const CONTROLS: {
48
48
  }, z.core.$strip>;
49
49
  readonly appWrite: true;
50
50
  };
51
+ readonly "pos.pro": {
52
+ readonly schema: z.ZodObject<{
53
+ locations: z.ZodNullable<z.ZodNumber>;
54
+ }, z.core.$strip>;
55
+ readonly appWrite: false;
56
+ };
57
+ /**
58
+ * The Accounting Module edition the org pays for, each an add-on next to its plan. null means
59
+ * no Accounting plan: the app has no Accounting. `{ ok: false }` means unknown right now:
60
+ * refuse new accounting records and keep existing ones readable.
61
+ */
62
+ readonly "plan.accountingEdition": {
63
+ readonly schema: z.ZodNullable<z.ZodEnum<{
64
+ small: "small";
65
+ medium: "medium";
66
+ }>>;
67
+ readonly appWrite: false;
68
+ };
51
69
  };
52
70
  type ControlKey = keyof typeof CONTROLS;
53
71
  type ControlValue<K extends ControlKey> = z.infer<(typeof CONTROLS)[K]["schema"]>;
package/dist/index.js CHANGED
@@ -21,6 +21,22 @@ var CONTROLS = {
21
21
  "payments.methods": {
22
22
  schema: z.object({ card: z.boolean(), promptPay: z.boolean() }),
23
23
  appWrite: true
24
+ },
25
+ "pos.pro": {
26
+ schema: z.object({
27
+ /** POS Pro locations paid for. null = unlimited (legacy plans, internal orgs). */
28
+ locations: z.number().int().nonnegative().nullable()
29
+ }),
30
+ appWrite: false
31
+ },
32
+ /**
33
+ * The Accounting Module edition the org pays for, each an add-on next to its plan. null means
34
+ * no Accounting plan: the app has no Accounting. `{ ok: false }` means unknown right now:
35
+ * refuse new accounting records and keep existing ones readable.
36
+ */
37
+ "plan.accountingEdition": {
38
+ schema: z.enum(["small", "medium"]).nullable(),
39
+ appWrite: false
24
40
  }
25
41
  };
26
42
  var CONTROL_KEYS = Object.keys(CONTROLS);
@@ -53,6 +53,22 @@ var CONTROLS = {
53
53
  "payments.methods": {
54
54
  schema: import_zod.z.object({ card: import_zod.z.boolean(), promptPay: import_zod.z.boolean() }),
55
55
  appWrite: true
56
+ },
57
+ "pos.pro": {
58
+ schema: import_zod.z.object({
59
+ /** POS Pro locations paid for. null = unlimited (legacy plans, internal orgs). */
60
+ locations: import_zod.z.number().int().nonnegative().nullable()
61
+ }),
62
+ appWrite: false
63
+ },
64
+ /**
65
+ * The Accounting Module edition the org pays for, each an add-on next to its plan. null means
66
+ * no Accounting plan: the app has no Accounting. `{ ok: false }` means unknown right now:
67
+ * refuse new accounting records and keep existing ones readable.
68
+ */
69
+ "plan.accountingEdition": {
70
+ schema: import_zod.z.enum(["small", "medium"]).nullable(),
71
+ appWrite: false
56
72
  }
57
73
  };
58
74
  var CONTROL_KEYS = Object.keys(CONTROLS);
@@ -48,6 +48,24 @@ declare const CONTROLS: {
48
48
  }, z.core.$strip>;
49
49
  readonly appWrite: true;
50
50
  };
51
+ readonly "pos.pro": {
52
+ readonly schema: z.ZodObject<{
53
+ locations: z.ZodNullable<z.ZodNumber>;
54
+ }, z.core.$strip>;
55
+ readonly appWrite: false;
56
+ };
57
+ /**
58
+ * The Accounting Module edition the org pays for, each an add-on next to its plan. null means
59
+ * no Accounting plan: the app has no Accounting. `{ ok: false }` means unknown right now:
60
+ * refuse new accounting records and keep existing ones readable.
61
+ */
62
+ readonly "plan.accountingEdition": {
63
+ readonly schema: z.ZodNullable<z.ZodEnum<{
64
+ small: "small";
65
+ medium: "medium";
66
+ }>>;
67
+ readonly appWrite: false;
68
+ };
51
69
  };
52
70
  type ControlKey = keyof typeof CONTROLS;
53
71
  type ControlValue<K extends ControlKey> = z.infer<(typeof CONTROLS)[K]["schema"]>;
@@ -48,6 +48,24 @@ declare const CONTROLS: {
48
48
  }, z.core.$strip>;
49
49
  readonly appWrite: true;
50
50
  };
51
+ readonly "pos.pro": {
52
+ readonly schema: z.ZodObject<{
53
+ locations: z.ZodNullable<z.ZodNumber>;
54
+ }, z.core.$strip>;
55
+ readonly appWrite: false;
56
+ };
57
+ /**
58
+ * The Accounting Module edition the org pays for, each an add-on next to its plan. null means
59
+ * no Accounting plan: the app has no Accounting. `{ ok: false }` means unknown right now:
60
+ * refuse new accounting records and keep existing ones readable.
61
+ */
62
+ readonly "plan.accountingEdition": {
63
+ readonly schema: z.ZodNullable<z.ZodEnum<{
64
+ small: "small";
65
+ medium: "medium";
66
+ }>>;
67
+ readonly appWrite: false;
68
+ };
51
69
  };
52
70
  type ControlKey = keyof typeof CONTROLS;
53
71
  type ControlValue<K extends ControlKey> = z.infer<(typeof CONTROLS)[K]["schema"]>;
@@ -21,6 +21,22 @@ var CONTROLS = {
21
21
  "payments.methods": {
22
22
  schema: z.object({ card: z.boolean(), promptPay: z.boolean() }),
23
23
  appWrite: true
24
+ },
25
+ "pos.pro": {
26
+ schema: z.object({
27
+ /** POS Pro locations paid for. null = unlimited (legacy plans, internal orgs). */
28
+ locations: z.number().int().nonnegative().nullable()
29
+ }),
30
+ appWrite: false
31
+ },
32
+ /**
33
+ * The Accounting Module edition the org pays for, each an add-on next to its plan. null means
34
+ * no Accounting plan: the app has no Accounting. `{ ok: false }` means unknown right now:
35
+ * refuse new accounting records and keep existing ones readable.
36
+ */
37
+ "plan.accountingEdition": {
38
+ schema: z.enum(["small", "medium"]).nullable(),
39
+ appWrite: false
24
40
  }
25
41
  };
26
42
  var CONTROL_KEYS = Object.keys(CONTROLS);
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@stardeck-customer-apps/controls",
3
3
  "type": "module",
4
- "version": "0.1.0",
5
- "description": "Platform-owned app controls (feature flags, plan limits, payment provider and methods) for Stardeck customer apps: a typed list plus a cached, signed sync client",
4
+ "version": "0.3.0",
5
+ "description": "Platform-owned app controls (feature flags, plan limits, POS Pro hardware, the Accounting edition, payment provider and methods) for Stardeck customer apps: a typed list plus a cached, signed sync client",
6
6
  "main": "dist/index.cjs",
7
7
  "module": "dist/index.js",
8
8
  "types": "dist/index.d.ts",