@foir/cli 0.73.0 → 0.77.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/cli.js +2219 -1002
- package/dist/lib/config-helpers.d.ts +125 -71
- package/dist/lib/migrations.d.ts +16 -6
- package/package.json +4 -3
|
@@ -293,11 +293,13 @@ interface ApplyConfigOperationInput {
|
|
|
293
293
|
/** Retry policy for failed executions. */
|
|
294
294
|
retryPolicy?: Record<string, unknown>;
|
|
295
295
|
/**
|
|
296
|
-
*
|
|
296
|
+
* Permissions a console user must hold to execute this operation.
|
|
297
297
|
*
|
|
298
|
-
* CONJUNCTIVE: the caller must hold EVERY
|
|
299
|
-
* narrows who may execute. Omit or pass `[]` for no gate.
|
|
300
|
-
*
|
|
298
|
+
* CONJUNCTIVE: the caller must hold EVERY permission listed, so adding one
|
|
299
|
+
* always narrows who may execute. Omit or pass `[]` for no gate. Each entry
|
|
300
|
+
* is a permission as the role editor names it, with its reach where it has
|
|
301
|
+
* one, e.g. `['records:write.all', 'models:write']`. An entry that is not a
|
|
302
|
+
* permission a console role can hold is refused when the config is pushed.
|
|
301
303
|
*
|
|
302
304
|
* Replaced `allowedRoles`, which named role KEYS. Role keys are
|
|
303
305
|
* yours to choose, so renaming a role silently changed who could execute.
|
|
@@ -345,15 +347,15 @@ interface ApplyConfigOperationInput {
|
|
|
345
347
|
maxRetries?: number;
|
|
346
348
|
};
|
|
347
349
|
/**
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
*
|
|
350
|
+
* What the operation may do when its endpoint calls back into Foir, as
|
|
351
|
+
* grants in the permission catalogue's names. Its dispatch token carries
|
|
352
|
+
* them, and so does the token for its callback window: nothing more.
|
|
351
353
|
*
|
|
352
|
-
*
|
|
353
|
-
* `records:write:<model_key>`, `records:
|
|
354
|
-
* `records:publish:<model_key>`.
|
|
355
|
-
*
|
|
356
|
-
*
|
|
354
|
+
* A record permission names its model: `records:read:<model_key>`,
|
|
355
|
+
* `records:write:<model_key>`, `records:create:<model_key>`,
|
|
356
|
+
* `records:delete:<model_key>`, `records:publish:<model_key>`. The write
|
|
357
|
+
* changes a record that is there and the create makes one, so an operation
|
|
358
|
+
* that does both declares both. List every model the operation touches.
|
|
357
359
|
*
|
|
358
360
|
* Ownership, `records:attribute:<model_key>` (or `records:attribute` for
|
|
359
361
|
* every model), lets the operation name the customer a record belongs to
|
|
@@ -361,23 +363,24 @@ interface ApplyConfigOperationInput {
|
|
|
361
363
|
* to another customer (`set<Model>Owner`). It is held only by an
|
|
362
364
|
* operation: no API key of any type can be minted with it.
|
|
363
365
|
*
|
|
364
|
-
*
|
|
365
|
-
* `files:read`, `files:write`, `
|
|
366
|
-
* such as the buyer at a guest checkout), `
|
|
367
|
-
* customer the operation names, by id or by their exact email
|
|
368
|
-
* `
|
|
369
|
-
* project's customers; an
|
|
370
|
-
*
|
|
371
|
-
*
|
|
372
|
-
*
|
|
373
|
-
*
|
|
366
|
+
* The rest are written as the catalogue writes them: `embeddings:read`,
|
|
367
|
+
* `embeddings:write`, `files:read`, `files:write`, `users:create` (create
|
|
368
|
+
* a customer, such as the buyer at a guest checkout), `users:read.named`
|
|
369
|
+
* (read one customer the operation names, by id or by their exact email
|
|
370
|
+
* address), `users:read.all` (that, and the list),
|
|
371
|
+
* `notifications:write.named` (notify the project's customers; an
|
|
372
|
+
* operation acting for a customer notifies that customer alone, and one
|
|
373
|
+
* at an organisation reaches only its customers), `status:write`, and
|
|
374
|
+
* `secrets:read`. A grant an operation may not declare is refused at
|
|
375
|
+
* push, by name. The whole list is at
|
|
376
|
+
* https://docs.foir.dev/api/operation-permissions.
|
|
374
377
|
*
|
|
375
|
-
* `operations:complete` is
|
|
376
|
-
* declare it here. Omitting `
|
|
377
|
-
* mints
|
|
378
|
-
*
|
|
378
|
+
* `operations:complete` is added to a callback token by the platform, so
|
|
379
|
+
* never declare it here. Omitting `grants` (or passing an empty array)
|
|
380
|
+
* mints a token that carries nothing, which suits an operation whose
|
|
381
|
+
* endpoint never calls back.
|
|
379
382
|
*/
|
|
380
|
-
|
|
383
|
+
grants?: string[];
|
|
381
384
|
/**
|
|
382
385
|
* How the operation surfaces in the public GraphQL schema and how it is
|
|
383
386
|
* dispatched. `mutation` (default) is the existing behaviour: an
|
|
@@ -395,8 +398,9 @@ interface ApplyConfigOperationInput {
|
|
|
395
398
|
*/
|
|
396
399
|
attachedToModel?: string;
|
|
397
400
|
/**
|
|
398
|
-
*
|
|
399
|
-
*
|
|
401
|
+
* Fields of the `attachedToModel` record sent to the resolver, where the
|
|
402
|
+
* request template reads them as `{{parent.X}}`. Only the fields named here
|
|
403
|
+
* are sent. FIELD only.
|
|
400
404
|
*/
|
|
401
405
|
parentFields?: string[];
|
|
402
406
|
/**
|
|
@@ -445,7 +449,9 @@ interface ApplyConfigOperationInput {
|
|
|
445
449
|
* A context dimension — the home for segments. `sourceType: 'segment'`
|
|
446
450
|
* carries an `evaluationRules` RuleExpression (evaluated fresh at request time
|
|
447
451
|
* into segment.<key>); `sourceType: 'model'` designates a customer-context
|
|
448
|
-
* source model via `sourceModelKey
|
|
452
|
+
* source model via `sourceModelKey`; `sourceType: 'request'` is a value the
|
|
453
|
+
* application sends in the contexts argument of a read, one of the records of
|
|
454
|
+
* the model `sourceModelKey` names. Reconciled config-scoped by `foir push`
|
|
449
455
|
* (owned via config_id): dimensions this config declares are created / updated,
|
|
450
456
|
* and config-owned dimensions dropped from the config are deleted.
|
|
451
457
|
*/
|
|
@@ -453,11 +459,18 @@ interface ApplyConfigContextDimensionInput {
|
|
|
453
459
|
key: string;
|
|
454
460
|
name: string;
|
|
455
461
|
description?: string;
|
|
456
|
-
/**
|
|
462
|
+
/**
|
|
463
|
+
* 'segment' (rule-based) | 'model' (customer-context source) | 'request'
|
|
464
|
+
* (a value sent with the read) | custom.
|
|
465
|
+
*/
|
|
457
466
|
sourceType: string;
|
|
458
467
|
/** Segment rule (sourceType 'segment') — a RuleExpression object. */
|
|
459
468
|
evaluationRules?: Record<string, unknown>;
|
|
460
|
-
/**
|
|
469
|
+
/**
|
|
470
|
+
* The model the dimension is backed by. For sourceType 'model', a
|
|
471
|
+
* customer-owned singleton. For sourceType 'request', a model whose records
|
|
472
|
+
* belong to the project as a whole: each record's natural key is one value.
|
|
473
|
+
*/
|
|
461
474
|
sourceModelKey?: string;
|
|
462
475
|
defaultRecordKey?: string;
|
|
463
476
|
}
|
|
@@ -530,6 +543,15 @@ interface ApplyConfigScheduleInput {
|
|
|
530
543
|
cron: string;
|
|
531
544
|
timezone?: string;
|
|
532
545
|
enabled?: boolean;
|
|
546
|
+
/**
|
|
547
|
+
* What one fire starts a run for. Left out, a fire starts one run, at the
|
|
548
|
+
* project's own level. `'organization'` starts one run in each active
|
|
549
|
+
* organization of the project, at every depth: each run is started in that
|
|
550
|
+
* organization, is confined to it, and holds what the operation declares.
|
|
551
|
+
* An organization that is suspended or scheduled for deletion gets no run.
|
|
552
|
+
* The operation must be one of the project's own, not an app's.
|
|
553
|
+
*/
|
|
554
|
+
each?: 'organization';
|
|
533
555
|
}
|
|
534
556
|
interface ApplyConfigAuthProviderInput {
|
|
535
557
|
key: string;
|
|
@@ -600,12 +622,22 @@ interface ApplyConfigRelyingPartyInput {
|
|
|
600
622
|
*/
|
|
601
623
|
redirectUris: string[];
|
|
602
624
|
/**
|
|
603
|
-
*
|
|
604
|
-
*
|
|
605
|
-
*
|
|
606
|
-
*
|
|
625
|
+
* The most a sign-in through this app may carry of what the person's roles
|
|
626
|
+
* give them, in the permission catalogue's names: the same names a customer
|
|
627
|
+
* role is declared with (https://docs.foir.dev/api/role-permissions).
|
|
628
|
+
*
|
|
629
|
+
* The list gives nobody anything. A person holds what their roles give, and
|
|
630
|
+
* the list decides how much of it a token minted for this app carries. So it
|
|
631
|
+
* may hold a wildcard, which a role may not: `['*']` is an app that is not
|
|
632
|
+
* narrowed, and `'records:*'` carries whatever its users hold over records.
|
|
633
|
+
* An entry covers its narrower forms: `operations:execute` here covers a
|
|
634
|
+
* person's `operations:execute:checkout`.
|
|
635
|
+
*
|
|
636
|
+
* Required, and nothing is read into an absence: `[]` is an app whose
|
|
637
|
+
* sign-ins carry nothing of what its users' roles give. A sign-in may ask
|
|
638
|
+
* for less with `scope`, in the same names.
|
|
607
639
|
*/
|
|
608
|
-
|
|
640
|
+
grants: string[];
|
|
609
641
|
/**
|
|
610
642
|
* Per-RP login method filter (customer-lane). Combines with the project-
|
|
611
643
|
* level customer auth settings (project may have password OFF entirely; an
|
|
@@ -676,8 +708,35 @@ interface ApplyConfigApiKeyInput {
|
|
|
676
708
|
keyType: 'public' | 'secret';
|
|
677
709
|
/** Environment variable name to write the key to in .env (e.g. "FOIR_PUBLIC_KEY"). */
|
|
678
710
|
envVar: string;
|
|
679
|
-
/**
|
|
680
|
-
|
|
711
|
+
/**
|
|
712
|
+
* What the key holds: the most anyone may do through it, in the permission
|
|
713
|
+
* catalogue's names (e.g. ["records:read", "records:write"]). A secret key
|
|
714
|
+
* may hold ["*"].
|
|
715
|
+
*
|
|
716
|
+
* Declared for every key. Nothing is filled in, so a key declared without
|
|
717
|
+
* it would be minted holding nothing, and a push refuses one.
|
|
718
|
+
*
|
|
719
|
+
* A push changes it on the existing key in place: the key keeps its id and
|
|
720
|
+
* its secret, and the new list is live the moment the push writes it.
|
|
721
|
+
*/
|
|
722
|
+
grants: string[];
|
|
723
|
+
/**
|
|
724
|
+
* What the key gives whoever comes through it, in the same names: a visitor
|
|
725
|
+
* with no sign-in, on a public key, and a signed-in user beneath their role,
|
|
726
|
+
* on either kind.
|
|
727
|
+
*
|
|
728
|
+
* NOTHING IS FILLED IN. A key declared without this list gives a visitor
|
|
729
|
+
* nothing, whatever it holds: a public key holding `records:read` and no
|
|
730
|
+
* list here is a key no visitor can read with. Name each read a visitor
|
|
731
|
+
* needs. A visitor may also be given one model to submit into
|
|
732
|
+
* (`records:create:<model>`) and one operation to run
|
|
733
|
+
* (`operations:execute:<operation>`), each naming something this config
|
|
734
|
+
* declares.
|
|
735
|
+
*
|
|
736
|
+
* Declared, the list is the key's whole second list and a push sets it in
|
|
737
|
+
* place. Left out, a push leaves what the key gives as it is.
|
|
738
|
+
*/
|
|
739
|
+
baselineGrants?: string[];
|
|
681
740
|
/** Restrict the key to specific model keys (e.g. ["tilly_note", "tilly_block"]). */
|
|
682
741
|
allowedModels?: string[];
|
|
683
742
|
/** Restrict file uploads: a MIME type ("image/png"), a whole type ("image/*"), or IMAGE, VIDEO, FILE. */
|
|
@@ -701,32 +760,18 @@ interface ApplyConfigApiKeyInput {
|
|
|
701
760
|
* credential to change which tenant it belongs to.
|
|
702
761
|
*/
|
|
703
762
|
org?: string;
|
|
704
|
-
/**
|
|
705
|
-
* What a stranger may do with this key: the caller carrying it with nobody
|
|
706
|
-
* signed in, such as a guest at checkout or a contact form.
|
|
707
|
-
*
|
|
708
|
-
* Two forms, each naming one thing this config declares:
|
|
709
|
-
* `records:create:<model>` and `operations:execute:<operation>`. No wildcard,
|
|
710
|
-
* no update, delete or publish. A PUBLIC key only: a secret key is presented
|
|
711
|
-
* by a server that already knows who is calling.
|
|
712
|
-
*
|
|
713
|
-
* A push changes it on the existing key in place, as it does the key's
|
|
714
|
-
* scopes: the key keeps its id and its secret, and the new list is live the
|
|
715
|
-
* moment the push writes it, not at a release.
|
|
716
|
-
*/
|
|
717
|
-
anonymousScopes?: string[];
|
|
718
763
|
}
|
|
719
764
|
/**
|
|
720
|
-
* A customer
|
|
721
|
-
*
|
|
722
|
-
*
|
|
723
|
-
* project
|
|
724
|
-
* key is created.
|
|
725
|
-
*
|
|
765
|
+
* A customer role declared in foir.config.ts and reconciled by `foir push`.
|
|
766
|
+
* The role's `grants` are what a customer who holds it may do once signed in:
|
|
767
|
+
* their access token carries them. Roles are matched by `key` within the
|
|
768
|
+
* project: a declared key that already exists is updated in place, and a new
|
|
769
|
+
* key is created. A role on the platform that the config leaves out is left
|
|
770
|
+
* as it is, never disabled.
|
|
726
771
|
*
|
|
727
|
-
* Without a role, a
|
|
728
|
-
*
|
|
729
|
-
*
|
|
772
|
+
* Without a role, a signed-in customer may do only what the key gives
|
|
773
|
+
* everyone who comes through it (its `baselineGrants`). Anything a customer
|
|
774
|
+
* may do beyond that comes from a role they hold.
|
|
730
775
|
*/
|
|
731
776
|
interface ApplyConfigCustomerRoleInput {
|
|
732
777
|
/** Stable key, unique per project (e.g. "default", "member"). */
|
|
@@ -734,20 +779,29 @@ interface ApplyConfigCustomerRoleInput {
|
|
|
734
779
|
/** Human-readable name (e.g. "Default Customer"). */
|
|
735
780
|
name: string;
|
|
736
781
|
/**
|
|
737
|
-
*
|
|
738
|
-
*
|
|
739
|
-
* files:read
|
|
740
|
-
*
|
|
741
|
-
*
|
|
742
|
-
*
|
|
782
|
+
* What the role gives customers who hold it, as grants in the permission
|
|
783
|
+
* catalogue's names: records:read:<model>, records:write:<model>,
|
|
784
|
+
* records:create:<model>, operations:execute, files:read and the rest of
|
|
785
|
+
* the list at https://docs.foir.dev/api/role-permissions.
|
|
786
|
+
*
|
|
787
|
+
* Each grant stands for itself. Changing a record and creating one are two
|
|
788
|
+
* grants, so a role that should do both lists `records:write:<model>` and
|
|
789
|
+
* `records:create:<model>`.
|
|
790
|
+
*
|
|
791
|
+
* The `self:*` shorthand is worked out when a customer signs in, into the
|
|
792
|
+
* read, the write and the create of every model the project lets a customer
|
|
793
|
+
* write. It does NOT include operations:execute, so list that explicitly
|
|
794
|
+
* when the app's customers run operations.
|
|
743
795
|
*
|
|
744
796
|
* `operations:execute` runs every operation the key exposes;
|
|
745
797
|
* `operations:execute:<operation_key>` runs that one alone, so a role can
|
|
746
798
|
* let every customer check out and keep rebooking for the customers whose
|
|
747
|
-
* role
|
|
748
|
-
*
|
|
799
|
+
* role gives it. A push naming an operation the project does not have is
|
|
800
|
+
* refused, by name.
|
|
801
|
+
*
|
|
802
|
+
* A push puts on a role only what the person pushing holds themselves.
|
|
749
803
|
*/
|
|
750
|
-
|
|
804
|
+
grants: string[];
|
|
751
805
|
/**
|
|
752
806
|
* When true, every customer in the project inherits this role with no
|
|
753
807
|
* per-customer assignment. An app's baseline customer grant should be a
|
package/dist/lib/migrations.d.ts
CHANGED
|
@@ -72,19 +72,29 @@ type Hold = {
|
|
|
72
72
|
};
|
|
73
73
|
type FieldDecision = RenameTo | Retype | RemapValues | SplitInto | JoinFrom | FillFrom | Drop | Hold;
|
|
74
74
|
/**
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
75
|
+
* What happens to the records whose owner does not fit the plane their model is
|
|
76
|
+
* released on.
|
|
77
|
+
*
|
|
78
|
+
* A model that moves onto an owned plane with records that name no owner: each
|
|
79
|
+
* takes the owner one of its fields names (`assignFrom`), or they are left as
|
|
80
|
+
* they are while you handle it (`hold`).
|
|
81
|
+
*
|
|
82
|
+
* A model on the project's plane with records that name an owner: each becomes
|
|
83
|
+
* the project's (`toProject`), or they are left their owners' (`hold`).
|
|
84
|
+
*
|
|
85
|
+
* A release that carries such a move refuses until this is answered, and an
|
|
86
|
+
* answered decision runs inside that release.
|
|
79
87
|
*/
|
|
80
88
|
type OwnerDecision = {
|
|
81
89
|
assignFrom: string;
|
|
82
90
|
} | {
|
|
83
91
|
hold: string;
|
|
92
|
+
} | {
|
|
93
|
+
toProject: true;
|
|
84
94
|
};
|
|
85
95
|
/**
|
|
86
96
|
* One model's decisions, keyed by the field being removed, and under `$owner`
|
|
87
|
-
* the decision about records
|
|
97
|
+
* the decision about records whose owner does not fit the model's plane.
|
|
88
98
|
*/
|
|
89
99
|
type ModelDecisions = {
|
|
90
100
|
[fieldKey: string]: FieldDecision | OwnerDecision | undefined;
|
|
@@ -107,7 +117,7 @@ interface MigrationFile {
|
|
|
107
117
|
}
|
|
108
118
|
/**
|
|
109
119
|
* Declare what happens to the data under fields this push removes, and to
|
|
110
|
-
* records
|
|
120
|
+
* records whose owner does not fit the plane their model is released on.
|
|
111
121
|
*
|
|
112
122
|
* Returns a plain object rather than doing anything: a decision file is a
|
|
113
123
|
* declaration that gets committed and reviewed, and code that acted at import
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@foir/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.77.0",
|
|
4
4
|
"description": "Universal platform CLI for Foir platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -61,11 +61,12 @@
|
|
|
61
61
|
"tsx": "^4.20.0",
|
|
62
62
|
"typescript": "5.9.2",
|
|
63
63
|
"vitest": "^3.2.4",
|
|
64
|
+
"@foir/access-vocab": "0.0.0",
|
|
64
65
|
"@foir/assistant": "0.0.0",
|
|
65
|
-
"@foir/console-routes": "0.0.0",
|
|
66
66
|
"@foir/proto-ts": "0.109.0",
|
|
67
67
|
"@foir/rpc-node": "0.0.0",
|
|
68
|
-
"@foir/sdk": "0.
|
|
68
|
+
"@foir/sdk": "0.20.0",
|
|
69
|
+
"@foir/console-routes": "0.0.0"
|
|
69
70
|
},
|
|
70
71
|
"engines": {
|
|
71
72
|
"node": ">=18.0.0"
|