@foir/cli 0.67.0 → 0.68.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.
@@ -237,6 +237,20 @@ interface QuotaRule {
237
237
  expiresAt?: string;
238
238
  /** Custom message returned when this rule's quota is exhausted (e.g., upgrade CTA). */
239
239
  message?: string;
240
+ /**
241
+ * Refuse the caller when this rule cannot be evaluated, instead of letting
242
+ * them through.
243
+ *
244
+ * Defaults to `false`, which is the behaviour every rule had before this
245
+ * field existed: if the meter is unreadable the request is allowed. Set it
246
+ * on a rule that protects something expensive, where serving an unmetered
247
+ * request costs more than refusing a legitimate one.
248
+ *
249
+ * It does not cover every failure. A rule whose own counter is unreadable
250
+ * refuses; a failure to load the rules at all has no rule to consult and
251
+ * still allows.
252
+ */
253
+ failClosed?: boolean;
240
254
  }
241
255
 
242
256
  interface ApplyConfigOperationInput {
@@ -0,0 +1,100 @@
1
+ /**
2
+ * The decision-file authoring surface: what a `migrations/*.foir.ts` file
3
+ * imports.
4
+ *
5
+ * These types exist so a decision is checked where it is written rather than
6
+ * on the server after a push. A misspelled verb in a file that only fails
7
+ * remotely costs a round trip to find out; here it is a red squiggle.
8
+ */
9
+ /** Move the values to a field with a different key. */
10
+ type RenameTo = {
11
+ renameTo: string;
12
+ };
13
+ /**
14
+ * Convert the values to another type.
15
+ *
16
+ * `onError` decides what happens to a value that will not cast. The default is
17
+ * to fail the migration, because silently discarding the ones that did not
18
+ * convert is how a migration reports success having lost data.
19
+ */
20
+ type Retype = {
21
+ retype: {
22
+ to: string;
23
+ onError?: 'fail' | 'null' | 'skip';
24
+ };
25
+ };
26
+ /** Rewrite stored values through a lookup table. */
27
+ type RemapValues = {
28
+ remapValues: {
29
+ values: Record<string, string>;
30
+ failOnUnmapped?: boolean;
31
+ };
32
+ };
33
+ /** Split one field's value across several, on a separator. */
34
+ type SplitInto = {
35
+ splitInto: {
36
+ into: string[];
37
+ on: string;
38
+ };
39
+ };
40
+ /** Join several fields into this one, with a separator. */
41
+ type JoinFrom = {
42
+ joinFrom: {
43
+ from: string[];
44
+ with: string;
45
+ };
46
+ };
47
+ /** Fill this field from another field or a constant. */
48
+ type FillFrom = {
49
+ fillFrom: {
50
+ field?: string;
51
+ constant?: unknown;
52
+ onlyWhenEmpty?: boolean;
53
+ };
54
+ };
55
+ /**
56
+ * Discard the values deliberately.
57
+ *
58
+ * Distinct from leaving a field undecided, which the push refuses. This is a
59
+ * signature on the loss, not a default anyone can fall into.
60
+ */
61
+ type Drop = {
62
+ drop: true;
63
+ };
64
+ /**
65
+ * Leave the data alone — the author is handling it themselves.
66
+ *
67
+ * Carries the reason, which the ledger records, so a migration that ran nothing
68
+ * still says why. The field keeps its tombstone and its grace period.
69
+ */
70
+ type Hold = {
71
+ hold: string;
72
+ };
73
+ type FieldDecision = RenameTo | Retype | RemapValues | SplitInto | JoinFrom | FillFrom | Drop | Hold;
74
+ /** One model's decisions, keyed by the field being removed. */
75
+ type ModelDecisions = Record<string, FieldDecision>;
76
+ /** Every model this file speaks for. */
77
+ type MigrationDecisions = Record<string, ModelDecisions>;
78
+ /** What a decision file default-exports. */
79
+ interface MigrationFile {
80
+ /**
81
+ * The author's id, conventionally the file's timestamp.
82
+ *
83
+ * A handle and an ordering aid — NOT the identity. The server keys the ledger
84
+ * on a hash of the decisions themselves, because two branches producing the
85
+ * same timestamp is likely while two producing the same decisions is exactly
86
+ * the case that should converge on one no-op.
87
+ */
88
+ id: string;
89
+ decisions: MigrationDecisions;
90
+ }
91
+ /**
92
+ * Declare what happens to the data under fields this push removes.
93
+ *
94
+ * Returns a plain object rather than doing anything: a decision file is a
95
+ * declaration that gets committed and reviewed, and code that acted at import
96
+ * time would run on every `foir push --dry-run` too.
97
+ */
98
+ declare function migration(id: string, decisions: MigrationDecisions): MigrationFile;
99
+
100
+ export { type Drop, type FieldDecision, type FillFrom, type Hold, type JoinFrom, type MigrationDecisions, type MigrationFile, type ModelDecisions, type RemapValues, type RenameTo, type Retype, type SplitInto, migration };
@@ -0,0 +1,7 @@
1
+ // src/lib/migrations.ts
2
+ function migration(id, decisions) {
3
+ return { id, decisions };
4
+ }
5
+ export {
6
+ migration
7
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foir/cli",
3
- "version": "0.67.0",
3
+ "version": "0.68.0",
4
4
  "description": "Universal platform CLI for Foir platform",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -17,6 +17,10 @@
17
17
  "./configs": {
18
18
  "types": "./dist/lib/config-helpers.d.ts",
19
19
  "import": "./dist/lib/config-helpers.js"
20
+ },
21
+ "./migrations": {
22
+ "types": "./dist/lib/migrations.d.ts",
23
+ "import": "./dist/lib/migrations.js"
20
24
  }
21
25
  },
22
26
  "files": [
@@ -59,8 +63,8 @@
59
63
  "vitest": "^3.2.4",
60
64
  "@foir/assistant": "0.0.0",
61
65
  "@foir/console-routes": "0.0.0",
62
- "@foir/proto-ts": "0.109.0",
63
66
  "@foir/rpc-node": "0.0.0",
67
+ "@foir/proto-ts": "0.109.0",
64
68
  "@foir/sdk": "0.9.0"
65
69
  },
66
70
  "engines": {