@foir/cli 0.77.0 → 0.79.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 +2305 -1595
- package/dist/lib/config-helpers.d.ts +166 -128
- package/dist/lib/config-helpers.js +2 -2
- package/package.json +3 -3
|
@@ -56,15 +56,16 @@ interface EnumFieldConfig {
|
|
|
56
56
|
placement?: FieldPlacement;
|
|
57
57
|
}
|
|
58
58
|
/**
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
59
|
+
* Which sides of a field are guarded. A guarded side is read, or written, only
|
|
60
|
+
* by a holder of the grant that names the field: `records:read:<model>.<field>`
|
|
61
|
+
* or `records:write:<model>.<field>`, at a reach that covers the record. A
|
|
62
|
+
* grant on the model does not reach a guarded side, and one on every owner's
|
|
63
|
+
* records of every model does. Give the field's grants to the roles, keys and
|
|
64
|
+
* operations that read or write it. Omit for a field that follows its model.
|
|
64
65
|
*/
|
|
65
|
-
interface
|
|
66
|
-
read?:
|
|
67
|
-
write?:
|
|
66
|
+
interface FieldGuardInput {
|
|
67
|
+
read?: boolean;
|
|
68
|
+
write?: boolean;
|
|
68
69
|
}
|
|
69
70
|
interface BaseFieldDefinitionInput {
|
|
70
71
|
key: string;
|
|
@@ -93,7 +94,7 @@ interface BaseFieldDefinitionInput {
|
|
|
93
94
|
storage?: string;
|
|
94
95
|
templateZone?: string;
|
|
95
96
|
zoneOrder?: number;
|
|
96
|
-
|
|
97
|
+
guarded?: FieldGuardInput;
|
|
97
98
|
}
|
|
98
99
|
type SelectFieldDefinitionInput = BaseFieldDefinitionInput & {
|
|
99
100
|
type: 'select';
|
|
@@ -184,11 +185,9 @@ interface LookupDefinitionInput {
|
|
|
184
185
|
interface AccessInput {
|
|
185
186
|
/**
|
|
186
187
|
* Which plane owns records of this model:
|
|
187
|
-
* - `'
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
* - `'admin'` — owned by the operator who created them; operator-private
|
|
191
|
-
* (other operators can't see them unless shared).
|
|
188
|
+
* - `'user'`: owned by a person: the one the write names as the owner,
|
|
189
|
+
* else the person who writes it. Per-owner RLS isolation, private
|
|
190
|
+
* response-cache, per-owner lookups. Required for a per-user singleton.
|
|
192
191
|
* - `'project'` — no individual owner; records belong to the project. The
|
|
193
192
|
* platform default.
|
|
194
193
|
* - `'organization'` — owned by one of the project's ORGANIZATIONS (the
|
|
@@ -196,27 +195,27 @@ interface AccessInput {
|
|
|
196
195
|
* the row stays in the project plane, so the project credential finds
|
|
197
196
|
* every organization's rows — which is how a multi-tenant app resolves
|
|
198
197
|
* `<sub>.theapp.com` to an org before it knows which org the request is
|
|
199
|
-
* for. Written by the project (never for a
|
|
198
|
+
* for. Written by the project (never for a person), owner
|
|
200
199
|
* named by `ownerId` or by the org the caller acts in (`X-Org-Id`);
|
|
201
200
|
* `singleton` means one record per organization. Refused on a project
|
|
202
201
|
* with no organizations and on a shared (layered/group) model.
|
|
203
202
|
*/
|
|
204
|
-
ownerPlane: '
|
|
203
|
+
ownerPlane: 'user' | 'project' | 'organization';
|
|
205
204
|
/**
|
|
206
205
|
* Visibility new records start at (the record descriptor modes):
|
|
207
206
|
* - `'private'` — only the owner and explicit grants.
|
|
208
207
|
* - `'public'` — readable by anyone with API access, including signed-in
|
|
209
|
-
*
|
|
208
|
+
* users on the public API.
|
|
210
209
|
*
|
|
211
210
|
* On a PROJECT-plane model, omitting this defaults to `'public'` at
|
|
212
211
|
* CREATION — project content is meant to be read, and `'private'` there does
|
|
213
212
|
* not mean what it looks like: a project record has no owner, so it stays
|
|
214
213
|
* readable by anonymous and server-side callers while being hidden from
|
|
215
|
-
* signed-in
|
|
214
|
+
* signed-in users. Set `'private'` explicitly to opt a project model out.
|
|
216
215
|
*
|
|
217
216
|
* The default is applied by the platform, at the create path only. Omitting it
|
|
218
217
|
* on a model that already exists means "leave it alone" — it will not reset a
|
|
219
|
-
* visibility you changed in the console. Owned planes (
|
|
218
|
+
* visibility you changed in the console. Owned planes (user, organization) are
|
|
220
219
|
* always private (the platform rejects a public default there) regardless of
|
|
221
220
|
* this value.
|
|
222
221
|
*/
|
|
@@ -250,8 +249,11 @@ interface QuotaRule {
|
|
|
250
249
|
points: number;
|
|
251
250
|
/** Window duration in seconds (3600=1hr, 86400=1day, 604800=7day, 2592000=30day). */
|
|
252
251
|
duration: number;
|
|
253
|
-
/**
|
|
254
|
-
|
|
252
|
+
/**
|
|
253
|
+
* Whose counter a run is charged to: `user`, the person the run is for;
|
|
254
|
+
* `actor`, whoever started it; or `tenant`, the workspace.
|
|
255
|
+
*/
|
|
256
|
+
scope: 'user' | 'actor' | 'tenant';
|
|
255
257
|
/** Optional rule expression condition. */
|
|
256
258
|
condition?: Record<string, unknown>;
|
|
257
259
|
/** Optional expiry — rule is ignored after this date (ISO 8601). */
|
|
@@ -286,7 +288,7 @@ interface ApplyConfigOperationInput {
|
|
|
286
288
|
timeoutMs?: number;
|
|
287
289
|
inputSchema?: Record<string, unknown>;
|
|
288
290
|
outputSchema?: Record<string, unknown>;
|
|
289
|
-
/** Usage quota rules (rate limits per
|
|
291
|
+
/** Usage quota rules (rate limits per user, actor or tenant). */
|
|
290
292
|
quotas?: {
|
|
291
293
|
rules: QuotaRule[];
|
|
292
294
|
};
|
|
@@ -358,19 +360,19 @@ interface ApplyConfigOperationInput {
|
|
|
358
360
|
* that does both declares both. List every model the operation touches.
|
|
359
361
|
*
|
|
360
362
|
* Ownership, `records:attribute:<model_key>` (or `records:attribute` for
|
|
361
|
-
* every model), lets the operation name the
|
|
363
|
+
* every model), lets the operation name the user a record belongs to
|
|
362
364
|
* when it creates one (`create<Model>(input, ownerId)`) and hand a record
|
|
363
|
-
* to another
|
|
365
|
+
* to another user (`set<Model>Owner`). It is held only by an
|
|
364
366
|
* operation: no API key of any type can be minted with it.
|
|
365
367
|
*
|
|
366
368
|
* The rest are written as the catalogue writes them: `embeddings:read`,
|
|
367
369
|
* `embeddings:write`, `files:read`, `files:write`, `users:create` (create
|
|
368
|
-
* a
|
|
369
|
-
* (read one
|
|
370
|
+
* a user, such as the buyer at a guest checkout), `users:read.named`
|
|
371
|
+
* (read one user the operation names, by id or by their exact email
|
|
370
372
|
* address), `users:read.all` (that, and the list),
|
|
371
|
-
* `notifications:write.named` (notify the project's
|
|
372
|
-
* operation acting for a
|
|
373
|
-
* at an organisation reaches only its
|
|
373
|
+
* `notifications:write.named` (notify the project's users; an
|
|
374
|
+
* operation acting for a user notifies that user alone, and one
|
|
375
|
+
* at an organisation reaches only its users), `status:write`, and
|
|
374
376
|
* `secrets:read`. A grant an operation may not declare is refused at
|
|
375
377
|
* push, by name. The whole list is at
|
|
376
378
|
* https://docs.foir.dev/api/operation-permissions.
|
|
@@ -448,7 +450,7 @@ interface ApplyConfigOperationInput {
|
|
|
448
450
|
/**
|
|
449
451
|
* A context dimension — the home for segments. `sourceType: 'segment'`
|
|
450
452
|
* carries an `evaluationRules` RuleExpression (evaluated fresh at request time
|
|
451
|
-
* into segment.<key>); `sourceType: 'model'` designates a
|
|
453
|
+
* into segment.<key>); `sourceType: 'model'` designates a user-context
|
|
452
454
|
* source model via `sourceModelKey`; `sourceType: 'request'` is a value the
|
|
453
455
|
* application sends in the contexts argument of a read, one of the records of
|
|
454
456
|
* the model `sourceModelKey` names. Reconciled config-scoped by `foir push`
|
|
@@ -460,7 +462,7 @@ interface ApplyConfigContextDimensionInput {
|
|
|
460
462
|
name: string;
|
|
461
463
|
description?: string;
|
|
462
464
|
/**
|
|
463
|
-
* 'segment' (rule-based) | 'model' (
|
|
465
|
+
* 'segment' (rule-based) | 'model' (user-context source) | 'request'
|
|
464
466
|
* (a value sent with the read) | custom.
|
|
465
467
|
*/
|
|
466
468
|
sourceType: string;
|
|
@@ -468,7 +470,7 @@ interface ApplyConfigContextDimensionInput {
|
|
|
468
470
|
evaluationRules?: Record<string, unknown>;
|
|
469
471
|
/**
|
|
470
472
|
* The model the dimension is backed by. For sourceType 'model', a
|
|
471
|
-
*
|
|
473
|
+
* user-owned singleton. For sourceType 'request', a model whose records
|
|
472
474
|
* belong to the project as a whole: each record's natural key is one value.
|
|
473
475
|
*/
|
|
474
476
|
sourceModelKey?: string;
|
|
@@ -508,7 +510,7 @@ interface ApplyConfigVariantCatalogEntryInput {
|
|
|
508
510
|
/**
|
|
509
511
|
* One transactional email's wording, keyed by the action that sends it.
|
|
510
512
|
*
|
|
511
|
-
* `actionKey` names a send path — `email.
|
|
513
|
+
* `actionKey` names a send path — `email.project.welcome`, or a notification
|
|
512
514
|
* hook's own key. The platform refuses one it does not recognise, because a row
|
|
513
515
|
* written under a typo is read by nothing and looks applied.
|
|
514
516
|
*
|
|
@@ -563,49 +565,43 @@ interface ApplyConfigAuthProviderInput {
|
|
|
563
565
|
priority?: number;
|
|
564
566
|
}
|
|
565
567
|
/**
|
|
566
|
-
* Login-with-Foir
|
|
567
|
-
*
|
|
568
|
-
*
|
|
569
|
-
*
|
|
568
|
+
* A Login-with-Foir auth client. Each entry registers a storefront or
|
|
569
|
+
* other app that signs the project's users in at the hosted login
|
|
570
|
+
* (auth.foir.dev/authorize?client_id=…) and exchanges the code at
|
|
571
|
+
* /oauth/token.
|
|
570
572
|
*
|
|
571
573
|
* The reconciler is project-scoped (tenant + project come from the
|
|
572
|
-
* push context), so consumers only declare the per-
|
|
573
|
-
* `
|
|
574
|
+
* push context), so consumers only declare the per-client fields. Pass to
|
|
575
|
+
* `authClients` on the manifest.
|
|
574
576
|
*/
|
|
575
577
|
/**
|
|
576
|
-
* Per-
|
|
577
|
-
* §Per-
|
|
578
|
+
* Per-client login method config (login-with-foir-full-workstream-plan.md
|
|
579
|
+
* §Per-client login method toggle). All fields optional — missing keys
|
|
578
580
|
* default to enabled. Lets a project's storefront opt to only Sign in
|
|
579
581
|
* with Shopify (disable password/OTP + don't list Apple) without
|
|
580
582
|
* touching the project-level provider catalogue.
|
|
581
583
|
*/
|
|
582
|
-
interface
|
|
584
|
+
interface ApplyConfigAuthClientLoginMethods {
|
|
583
585
|
/**
|
|
584
|
-
* Show email & password sign-in on this
|
|
586
|
+
* Show email & password sign-in on this auth client's hosted login? Defaults to
|
|
585
587
|
* true. Only narrows the project capability — can't enable a method the
|
|
586
|
-
* project (`
|
|
588
|
+
* project (`passwordEnabled`) has turned off.
|
|
587
589
|
*/
|
|
588
590
|
password?: boolean;
|
|
589
591
|
/**
|
|
590
|
-
* Show one-time code sign-in on this
|
|
591
|
-
* Only narrows the project capability (`
|
|
592
|
+
* Show one-time code sign-in on this auth client's hosted login? Defaults to true.
|
|
593
|
+
* Only narrows the project capability (`otpEnabled`).
|
|
592
594
|
*/
|
|
593
595
|
otp?: boolean;
|
|
594
596
|
/**
|
|
595
|
-
* Provider keys to HIDE on this
|
|
597
|
+
* Provider keys to HIDE on this auth client's login page. Empty/missing = show
|
|
596
598
|
* every provider configured at the project level. List a key (e.g.
|
|
597
|
-
* "google") to omit it from this
|
|
599
|
+
* "google") to omit it from this auth client only.
|
|
598
600
|
*/
|
|
599
601
|
disabledProviders?: string[];
|
|
600
602
|
}
|
|
601
|
-
interface
|
|
602
|
-
/**
|
|
603
|
-
* Which lane this RP authenticates. `foir push` is project-scoped, so
|
|
604
|
-
* RPs declared in config are customer-lane (the default); admin RPs are
|
|
605
|
-
* platform-managed and not part of project config.
|
|
606
|
-
*/
|
|
607
|
-
kind?: 'customer' | 'admin';
|
|
608
|
-
/** Public RP slug, unique per project (e.g. `acme-storefront`). */
|
|
603
|
+
interface ApplyConfigAuthClientInput {
|
|
604
|
+
/** Public auth client slug, unique per project (e.g. `acme-storefront`). */
|
|
609
605
|
clientId: string;
|
|
610
606
|
/** Display name shown on the hosted login. */
|
|
611
607
|
name: string;
|
|
@@ -623,7 +619,7 @@ interface ApplyConfigRelyingPartyInput {
|
|
|
623
619
|
redirectUris: string[];
|
|
624
620
|
/**
|
|
625
621
|
* 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
|
|
622
|
+
* give them, in the permission catalogue's names: the same names a user
|
|
627
623
|
* role is declared with (https://docs.foir.dev/api/role-permissions).
|
|
628
624
|
*
|
|
629
625
|
* The list gives nobody anything. A person holds what their roles give, and
|
|
@@ -639,25 +635,41 @@ interface ApplyConfigRelyingPartyInput {
|
|
|
639
635
|
*/
|
|
640
636
|
grants: string[];
|
|
641
637
|
/**
|
|
642
|
-
*
|
|
643
|
-
*
|
|
644
|
-
*
|
|
638
|
+
* Whether this app is the project's own. A sign-in through the project's own
|
|
639
|
+
* app asks nobody to agree to it. Through any other app, each user is shown
|
|
640
|
+
* what it asks to carry for them and agrees to it once, and the token
|
|
641
|
+
* carries no more than they agreed. Leave it off for an app someone else
|
|
642
|
+
* runs.
|
|
645
643
|
*/
|
|
646
|
-
|
|
644
|
+
firstParty?: boolean;
|
|
647
645
|
/**
|
|
648
|
-
*
|
|
646
|
+
* Per-client login method filter. Combines with the project-
|
|
647
|
+
* level user auth settings (project may have password OFF entirely; an
|
|
648
|
+
* auth client can't override-up, only override-down).
|
|
649
|
+
*/
|
|
650
|
+
loginMethods?: ApplyConfigAuthClientLoginMethods;
|
|
651
|
+
/**
|
|
652
|
+
* White-label custom login domains for this auth client, e.g.
|
|
649
653
|
* `['login.eide.clothing']`. `foir push` registers each (creating a pending
|
|
650
654
|
* row + a DNS-TXT challenge) and prunes any not listed. Verification is a
|
|
651
655
|
* separate step — add the CNAME + `_foir-challenge` TXT records the push
|
|
652
|
-
* output prints, then verify.
|
|
656
|
+
* output prints, then verify.
|
|
653
657
|
*/
|
|
654
658
|
customDomains?: string[];
|
|
655
659
|
/**
|
|
656
660
|
* Where a bare custom-domain (front-door) login lands after Foir sets the
|
|
657
661
|
* first-party session cookie — the app's landing, not an OAuth
|
|
658
|
-
* callback. Empty falls back to the redirect URI.
|
|
662
|
+
* callback. Empty falls back to the redirect URI.
|
|
659
663
|
*/
|
|
660
664
|
postLoginUrl?: string;
|
|
665
|
+
/**
|
|
666
|
+
* An address in your app that starts its sign-in (OIDC's
|
|
667
|
+
* `initiate_login_uri`). When someone accepts an invitation or resets their
|
|
668
|
+
* password on Foir's hosted pages, they are sent here with `iss`,
|
|
669
|
+
* `login_hint` and `target_link_uri`, and your app signs them in as usual.
|
|
670
|
+
* Absent, they finish on the hosted page. https, or http on localhost.
|
|
671
|
+
*/
|
|
672
|
+
initiateLoginUri?: string;
|
|
661
673
|
/**
|
|
662
674
|
* Browser origins allowed to exchange the first-party session cookie for an
|
|
663
675
|
* access token — i.e. the origins your SPA runs on, e.g.
|
|
@@ -670,7 +682,7 @@ interface ApplyConfigRelyingPartyInput {
|
|
|
670
682
|
* origin on your domain, including a subdomain takeover or anything serving
|
|
671
683
|
* user content there.
|
|
672
684
|
*
|
|
673
|
-
* Absent leaves the list untouched; `[]` clears it.
|
|
685
|
+
* Absent leaves the list untouched; `[]` clears it. It is
|
|
674
686
|
* only meaningful alongside a verified `customDomains` entry (the cookie is
|
|
675
687
|
* only ever issued on one).
|
|
676
688
|
*/
|
|
@@ -704,14 +716,30 @@ interface ApplyConfigHookInput {
|
|
|
704
716
|
interface ApplyConfigApiKeyInput {
|
|
705
717
|
/** Name for this API key (e.g. "Tilly iOS", "Tilly CRM sync"). */
|
|
706
718
|
name: string;
|
|
707
|
-
/**
|
|
708
|
-
|
|
719
|
+
/**
|
|
720
|
+
* What the key is for. You choose it, and the platform holds the key to what
|
|
721
|
+
* that kind may hold and give:
|
|
722
|
+
*
|
|
723
|
+
* - `'publishable'` sits in a web page or an app, where anyone can read it.
|
|
724
|
+
* It gives a visitor only what is safe to give everyone.
|
|
725
|
+
* - `'server'` is held by the server that renders your pages. It is a
|
|
726
|
+
* publishable key that may also say who and where its visitor is
|
|
727
|
+
* (`visitors:forward`), and it never goes to a browser.
|
|
728
|
+
* - `'admin'` is a machine's own: a sync job, a pipeline, a tool. It does
|
|
729
|
+
* what it holds, gives nobody anything, and nobody signs in through it.
|
|
730
|
+
* It expires.
|
|
731
|
+
*
|
|
732
|
+
* A key's kind never changes. A config that declares another kind for a key
|
|
733
|
+
* that already exists is refused: declare a new key under a new name.
|
|
734
|
+
*/
|
|
735
|
+
kind: 'publishable' | 'server' | 'admin';
|
|
709
736
|
/** Environment variable name to write the key to in .env (e.g. "FOIR_PUBLIC_KEY"). */
|
|
710
737
|
envVar: string;
|
|
711
738
|
/**
|
|
712
|
-
* What the key holds
|
|
713
|
-
*
|
|
714
|
-
* may
|
|
739
|
+
* What the key holds, in the permission catalogue's names (e.g.
|
|
740
|
+
* ["records:read", "records:write"]): for a publishable or a server key the
|
|
741
|
+
* most a signed-in user may do through it, and for an admin key what it
|
|
742
|
+
* does. An admin key may hold ["*"].
|
|
715
743
|
*
|
|
716
744
|
* Declared for every key. Nothing is filled in, so a key declared without
|
|
717
745
|
* it would be minted holding nothing, and a push refuses one.
|
|
@@ -721,13 +749,14 @@ interface ApplyConfigApiKeyInput {
|
|
|
721
749
|
*/
|
|
722
750
|
grants: string[];
|
|
723
751
|
/**
|
|
724
|
-
* What
|
|
725
|
-
* with no sign-in,
|
|
726
|
-
*
|
|
752
|
+
* What a publishable or a server key gives whoever comes through it, in the
|
|
753
|
+
* same names: a visitor with no sign-in, and a signed-in user beneath their
|
|
754
|
+
* role. An admin key gives nobody anything, and one declared with this list
|
|
755
|
+
* is refused.
|
|
727
756
|
*
|
|
728
757
|
* NOTHING IS FILLED IN. A key declared without this list gives a visitor
|
|
729
|
-
* nothing, whatever it holds: a
|
|
730
|
-
* list here is a key no visitor can read with. Name each read a visitor
|
|
758
|
+
* nothing, whatever it holds: a publishable key holding `records:read` and
|
|
759
|
+
* no list here is a key no visitor can read with. Name each read a visitor
|
|
731
760
|
* needs. A visitor may also be given one model to submit into
|
|
732
761
|
* (`records:create:<model>`) and one operation to run
|
|
733
762
|
* (`operations:execute:<operation>`), each naming something this config
|
|
@@ -737,6 +766,17 @@ interface ApplyConfigApiKeyInput {
|
|
|
737
766
|
* place. Left out, a push leaves what the key gives as it is.
|
|
738
767
|
*/
|
|
739
768
|
baselineGrants?: string[];
|
|
769
|
+
/**
|
|
770
|
+
* An admin key only: how long it lives from the push that makes it, in days
|
|
771
|
+
* (e.g. "90d", which is what it is given when nothing is said, up to
|
|
772
|
+
* "365d"). A publishable or a server key never expires, and one declared
|
|
773
|
+
* with this is refused.
|
|
774
|
+
*
|
|
775
|
+
* It is read when the key is made. A push never renews a key that exists:
|
|
776
|
+
* renew it with `foir api-keys renew`, so that a key is kept alive by
|
|
777
|
+
* somebody deciding to and not by a pipeline that happens to run.
|
|
778
|
+
*/
|
|
779
|
+
expiresIn?: string;
|
|
740
780
|
/** Restrict the key to specific model keys (e.g. ["tilly_note", "tilly_block"]). */
|
|
741
781
|
allowedModels?: string[];
|
|
742
782
|
/** Restrict file uploads: a MIME type ("image/png"), a whole type ("image/*"), or IMAGE, VIDEO, FILE. */
|
|
@@ -762,24 +802,24 @@ interface ApplyConfigApiKeyInput {
|
|
|
762
802
|
org?: string;
|
|
763
803
|
}
|
|
764
804
|
/**
|
|
765
|
-
* A
|
|
766
|
-
* The role's `grants` are what a
|
|
805
|
+
* A role declared in foir.config.ts and reconciled by `foir push`.
|
|
806
|
+
* The role's `grants` are what a user who holds it may do once signed in:
|
|
767
807
|
* their access token carries them. Roles are matched by `key` within the
|
|
768
808
|
* project: a declared key that already exists is updated in place, and a new
|
|
769
809
|
* key is created. A role on the platform that the config leaves out is left
|
|
770
810
|
* as it is, never disabled.
|
|
771
811
|
*
|
|
772
|
-
* Without a role, a signed-in
|
|
773
|
-
* everyone who comes through it (its `baselineGrants`). Anything a
|
|
812
|
+
* Without a role, a signed-in user may do only what the key gives
|
|
813
|
+
* everyone who comes through it (its `baselineGrants`). Anything a user
|
|
774
814
|
* may do beyond that comes from a role they hold.
|
|
775
815
|
*/
|
|
776
|
-
interface
|
|
816
|
+
interface ApplyConfigUserRoleInput {
|
|
777
817
|
/** Stable key, unique per project (e.g. "default", "member"). */
|
|
778
818
|
key: string;
|
|
779
|
-
/** Human-readable name (e.g. "Default
|
|
819
|
+
/** Human-readable name (e.g. "Default User"). */
|
|
780
820
|
name: string;
|
|
781
821
|
/**
|
|
782
|
-
* What the role gives
|
|
822
|
+
* What the role gives users who hold it, as grants in the permission
|
|
783
823
|
* catalogue's names: records:read:<model>, records:write:<model>,
|
|
784
824
|
* records:create:<model>, operations:execute, files:read and the rest of
|
|
785
825
|
* the list at https://docs.foir.dev/api/role-permissions.
|
|
@@ -788,14 +828,12 @@ interface ApplyConfigCustomerRoleInput {
|
|
|
788
828
|
* grants, so a role that should do both lists `records:write:<model>` and
|
|
789
829
|
* `records:create:<model>`.
|
|
790
830
|
*
|
|
791
|
-
*
|
|
792
|
-
* read
|
|
793
|
-
* write. It does NOT include operations:execute, so list that explicitly
|
|
794
|
-
* when the app's customers run operations.
|
|
831
|
+
* A guarded field is given by the grants that name it,
|
|
832
|
+
* `records:read:<model>.<field>` and `records:write:<model>.<field>`.
|
|
795
833
|
*
|
|
796
834
|
* `operations:execute` runs every operation the key exposes;
|
|
797
835
|
* `operations:execute:<operation_key>` runs that one alone, so a role can
|
|
798
|
-
* let every
|
|
836
|
+
* let every user check out and keep rebooking for the users whose
|
|
799
837
|
* role gives it. A push naming an operation the project does not have is
|
|
800
838
|
* refused, by name.
|
|
801
839
|
*
|
|
@@ -803,8 +841,8 @@ interface ApplyConfigCustomerRoleInput {
|
|
|
803
841
|
*/
|
|
804
842
|
grants: string[];
|
|
805
843
|
/**
|
|
806
|
-
* When true, every
|
|
807
|
-
* per-
|
|
844
|
+
* When true, every user in the project inherits this role with no
|
|
845
|
+
* per-user assignment. An app's baseline user grant should be a
|
|
808
846
|
* single `isDefault: true` role. Defaults to false.
|
|
809
847
|
*/
|
|
810
848
|
isDefault?: boolean;
|
|
@@ -815,7 +853,7 @@ interface ApplyConfigCustomerRoleInput {
|
|
|
815
853
|
*
|
|
816
854
|
* EXPLICIT, BECAUSE SILENCE IS THE WRONG DEFAULT HERE. An optional source left
|
|
817
855
|
* out of the config could be a decision or an oversight, and the two have
|
|
818
|
-
* opposite consequences: one is an operator who does not want their
|
|
856
|
+
* opposite consequences: one is an operator who does not want their users
|
|
819
857
|
* synced, the other is an operator who thinks they are and is wrong. So an
|
|
820
858
|
* optional source that is neither mapped nor declined is refused at push, with
|
|
821
859
|
* both spellings named. Nothing is submitted for a declined one, so the platform
|
|
@@ -824,7 +862,7 @@ interface ApplyConfigCustomerRoleInput {
|
|
|
824
862
|
*
|
|
825
863
|
* sources: {
|
|
826
864
|
* product: { toModel: 'product', naturalKey: 'shopifyId', fields: { … } },
|
|
827
|
-
*
|
|
865
|
+
* user: false, // this project does not sync users
|
|
828
866
|
* }
|
|
829
867
|
*/
|
|
830
868
|
type AppMappingDeclined = false;
|
|
@@ -872,15 +910,15 @@ interface AppInput {
|
|
|
872
910
|
/** Opaque settings forwarded to the app's middleware. */
|
|
873
911
|
settings?: Record<string, unknown>;
|
|
874
912
|
/**
|
|
875
|
-
* The
|
|
876
|
-
* system has
|
|
877
|
-
*
|
|
913
|
+
* The user sign-in this install pairs with the app, for an app whose own
|
|
914
|
+
* system has users: the key of one of the project's OAuth or OIDC
|
|
915
|
+
* user sign-ins, declared under `authProviders`. Every identity the app
|
|
878
916
|
* names is resolved through it.
|
|
879
917
|
*
|
|
880
918
|
* `false` pairs none. Leave it out to leave the install's pairing alone: a
|
|
881
919
|
* push never removes a pairing it was not told about.
|
|
882
920
|
*/
|
|
883
|
-
|
|
921
|
+
userSignIn?: string | false;
|
|
884
922
|
mappings?: {
|
|
885
923
|
/** `false` declines an OPTIONAL source. See AppMappingDeclined. */
|
|
886
924
|
sources?: Record<string, AppSourceMappingInput | AppMappingDeclined>;
|
|
@@ -896,32 +934,32 @@ interface AppInput {
|
|
|
896
934
|
* the column, `undefined` leaves it untouched.
|
|
897
935
|
*/
|
|
898
936
|
interface ApplyConfigProjectSettingsInput {
|
|
899
|
-
/** Display name in
|
|
937
|
+
/** Display name in the project's own emails and portal headers. */
|
|
900
938
|
displayName?: string;
|
|
901
|
-
/** Public URL of the project logo (rendered in
|
|
939
|
+
/** Public URL of the project logo (rendered in user emails). */
|
|
902
940
|
logoUrl?: string;
|
|
903
|
-
/** Hex color used as accent in
|
|
941
|
+
/** Hex color used as accent in the project's own emails. */
|
|
904
942
|
primaryColor?: string;
|
|
905
|
-
/** "From" name on outbound
|
|
943
|
+
/** "From" name on outbound user emails. */
|
|
906
944
|
fromName?: string;
|
|
907
|
-
/** "Reply-To" address on outbound
|
|
945
|
+
/** "Reply-To" address on outbound user emails. */
|
|
908
946
|
replyTo?: string;
|
|
909
947
|
/** Support email surfaced in email footers. */
|
|
910
948
|
supportEmail?: string;
|
|
911
949
|
/**
|
|
912
950
|
* Public base URL of the consumer-facing app (storefront, portal,
|
|
913
|
-
* internal tool, …). Reset / verify / invitation links in
|
|
951
|
+
* internal tool, …). Reset / verify / invitation links in user
|
|
914
952
|
* emails are built from this — required before the platform will mint
|
|
915
|
-
*
|
|
953
|
+
* user-email links.
|
|
916
954
|
*/
|
|
917
955
|
appBaseUrl?: string;
|
|
918
956
|
/**
|
|
919
|
-
* Whether the platform sends a welcome email on
|
|
957
|
+
* Whether the platform sends a welcome email on user registration.
|
|
920
958
|
* Default true. Disable for invite-style flows.
|
|
921
959
|
*/
|
|
922
|
-
|
|
960
|
+
welcomeEmailEnabled?: boolean;
|
|
923
961
|
/**
|
|
924
|
-
* Self-serve
|
|
962
|
+
* Self-serve user signup mode. Default `public`. `restricted` accepts only
|
|
925
963
|
* invited emails, `waitlist` queues signups for approval, `closed` disables
|
|
926
964
|
* self-serve signup entirely.
|
|
927
965
|
*/
|
|
@@ -936,28 +974,28 @@ interface ApplyConfigProjectSettingsInput {
|
|
|
936
974
|
*/
|
|
937
975
|
signupRestrictions?: SignupRestrictionsInput;
|
|
938
976
|
/**
|
|
939
|
-
* Whether email/password
|
|
977
|
+
* Whether email/password sign-in is exposed. Default true. Disable
|
|
940
978
|
* for a passwordless project (one-time codes or single sign-on only).
|
|
941
979
|
*/
|
|
942
|
-
|
|
980
|
+
passwordEnabled?: boolean;
|
|
943
981
|
/**
|
|
944
|
-
* Whether email one-time-code
|
|
982
|
+
* Whether email one-time-code sign-in is exposed. Default false.
|
|
945
983
|
*/
|
|
946
|
-
|
|
984
|
+
otpEnabled?: boolean;
|
|
947
985
|
/**
|
|
948
|
-
* Whether a signed-in
|
|
949
|
-
* through `
|
|
950
|
-
* `
|
|
986
|
+
* Whether a signed-in user may register an organization of their own
|
|
987
|
+
* through `registerOrganization`. Default false. Needs
|
|
988
|
+
* `organizationSignupRole` to name the role the founder is given, or the
|
|
951
989
|
* platform refuses every registration.
|
|
952
990
|
*/
|
|
953
|
-
|
|
991
|
+
organizationSignupEnabled?: boolean;
|
|
954
992
|
/**
|
|
955
|
-
* The key of the
|
|
993
|
+
* The key of the role a self-registered organization's founder
|
|
956
994
|
* holds in it (a role of this project, e.g. `'operator'`). No default.
|
|
957
995
|
*/
|
|
958
|
-
|
|
996
|
+
organizationSignupRole?: string;
|
|
959
997
|
/**
|
|
960
|
-
* The
|
|
998
|
+
* The roles an organization's own staffer may hand out, by key.
|
|
961
999
|
*
|
|
962
1000
|
* THE CEILING ON WHAT AN ORGANIZATION CAN BECOME, set by the party that is
|
|
963
1001
|
* not inside it. An organization's staffer may add a member with a role only
|
|
@@ -970,7 +1008,7 @@ interface ApplyConfigProjectSettingsInput {
|
|
|
970
1008
|
* take the capability away from every organization in the project. A bare
|
|
971
1009
|
* "send nothing to clear" could not tell the last two apart.
|
|
972
1010
|
*
|
|
973
|
-
* Every key must name a live
|
|
1011
|
+
* Every key must name a live role of this project. The platform
|
|
974
1012
|
* refuses the whole list rather than dropping an entry it cannot resolve,
|
|
975
1013
|
* because a list that quietly lost one is a capability an operator believes
|
|
976
1014
|
* they granted and did not.
|
|
@@ -1053,8 +1091,8 @@ interface ApplyConfigDesignTokensInput {
|
|
|
1053
1091
|
*
|
|
1054
1092
|
* Organisation POLICY is manifest-shaped and belongs here: whether self-serve
|
|
1055
1093
|
* sign-up is on, which role a founder is given, which roles an organisation may
|
|
1056
|
-
* assign. Organisation INSTANCES are administrative state, like a
|
|
1057
|
-
* credential's last use — created by an operator, by a
|
|
1094
|
+
* assign. Organisation INSTANCES are administrative state, like a user or a
|
|
1095
|
+
* credential's last use — created by an operator, by a user signing up, or
|
|
1058
1096
|
* by a provisioning call, and suspended or deleted by deliberate acts with
|
|
1059
1097
|
* their own confirmations and their own audit trail.
|
|
1060
1098
|
*
|
|
@@ -1062,7 +1100,7 @@ interface ApplyConfigDesignTokensInput {
|
|
|
1062
1100
|
* rule: a family a manifest OMITS is a family whose resources this config owns
|
|
1063
1101
|
* and no longer wants. Apply that to organisations and a founder who pushed a
|
|
1064
1102
|
* config without an organisations block would lose a live tenant — every row,
|
|
1065
|
-
* every
|
|
1103
|
+
* every user, every credential — because a file did not mention it.
|
|
1066
1104
|
*/
|
|
1067
1105
|
interface ApplyConfigInput {
|
|
1068
1106
|
key: string;
|
|
@@ -1106,16 +1144,16 @@ interface ApplyConfigInput {
|
|
|
1106
1144
|
schedules?: ApplyConfigScheduleInput[];
|
|
1107
1145
|
hooks?: ApplyConfigHookInput[];
|
|
1108
1146
|
authProviders?: ApplyConfigAuthProviderInput[];
|
|
1109
|
-
|
|
1147
|
+
authClients?: ApplyConfigAuthClientInput[];
|
|
1110
1148
|
placements?: ApplyConfigPlacementInput[];
|
|
1111
1149
|
apiKeys?: ApplyConfigApiKeyInput[];
|
|
1112
1150
|
/**
|
|
1113
|
-
*
|
|
1114
|
-
* `isDefault: true` role to grant every
|
|
1115
|
-
* their tokens need — without one,
|
|
1151
|
+
* User RBAC roles, reconciled by `foir push`. Declare an
|
|
1152
|
+
* `isDefault: true` role to grant every user the write/execute scopes
|
|
1153
|
+
* their tokens need — without one, users get read-only
|
|
1116
1154
|
* tokens after the Model B auth change.
|
|
1117
1155
|
*/
|
|
1118
|
-
|
|
1156
|
+
userRoles?: ApplyConfigUserRoleInput[];
|
|
1119
1157
|
/** Per-project app declarations, keyed by app name. */
|
|
1120
1158
|
apps?: Record<string, AppInput>;
|
|
1121
1159
|
/**
|
|
@@ -1246,8 +1284,8 @@ declare function defineVariantCatalogEntry(entry: ApplyConfigVariantCatalogEntry
|
|
|
1246
1284
|
declare function defineSchedule(schedule: ApplyConfigScheduleInput): ApplyConfigScheduleInput;
|
|
1247
1285
|
/** Define an auth provider. */
|
|
1248
1286
|
declare function defineAuthProvider(provider: ApplyConfigAuthProviderInput): ApplyConfigAuthProviderInput;
|
|
1249
|
-
/** Define a Login-with-Foir
|
|
1250
|
-
declare function
|
|
1287
|
+
/** Define a Login-with-Foir auth client (storefront / external app). */
|
|
1288
|
+
declare function defineAuthClient(client: ApplyConfigAuthClientInput): ApplyConfigAuthClientInput;
|
|
1251
1289
|
/** Define a lifecycle hook. */
|
|
1252
1290
|
declare function defineHook(hook: ApplyConfigHookInput): ApplyConfigHookInput;
|
|
1253
1291
|
/** Define an editor placement (sidebar or main-editor tab). */
|
|
@@ -1286,4 +1324,4 @@ interface FoirSecretsConfig {
|
|
|
1286
1324
|
*/
|
|
1287
1325
|
declare function defineSecrets(config: FoirSecretsConfig): FoirSecretsConfig;
|
|
1288
1326
|
|
|
1289
|
-
export { type AccessInput, type AppInput, type AppMappingDeclined, type AppPlacementFieldChoiceInput, type AppSinkMappingInput, type AppSourceMappingInput, type ApplyConfigApiKeyInput, type
|
|
1327
|
+
export { type AccessInput, type AppInput, type AppMappingDeclined, type AppPlacementFieldChoiceInput, type AppSinkMappingInput, type AppSourceMappingInput, type ApplyConfigApiKeyInput, type ApplyConfigAuthClientInput, type ApplyConfigAuthClientLoginMethods, type ApplyConfigAuthProviderInput, type ApplyConfigContextDimensionInput, type ApplyConfigDesignTokensInput, type ApplyConfigEmailCopyInput, type ApplyConfigHookInput, type ApplyConfigInput, type ApplyConfigLocaleInput, type ApplyConfigModelInput, type ApplyConfigOperationInput, type ApplyConfigPlacementInput, type ApplyConfigProjectEmailInput, type ApplyConfigProjectInput, type ApplyConfigProjectSettingsInput, type ApplyConfigScheduleInput, type ApplyConfigUserRoleInput, type ApplyConfigVariantCatalogEntryInput, type DestinationValue, type DestinationValues, type EnumFieldConfig, type EnumFieldDefinitionInput, type EnumFieldOption, type FieldDefinitionInput, type FieldGuardInput, type FieldPlacement, type FieldRelationshipInput, type FoirProjectsConfig, type FoirSecretsConfig, type LookupDefinitionInput, type NamedProjectInput, type NamedProjectManifest, type QuotaRule, type ReferenceFieldConfig, type ReferenceFieldDefinitionInput, type SecretDeclaration, type SecretOwnerKind, type SelectFieldConfig, type SelectFieldDefinitionInput, type SelectedNamedProject, type SignupModeName, type SignupRestrictionsInput, defineAuthClient, defineAuthProvider, defineConfig, defineContextDimension, defineDesignTokens, defineEmailCopy, defineEnumField, defineField, defineHook, defineLocale, defineModel, defineOperation, definePlacement, defineSchedule, defineSecrets, defineSelectField, defineVariantCatalogEntry };
|
|
@@ -38,7 +38,7 @@ function defineSchedule(schedule) {
|
|
|
38
38
|
function defineAuthProvider(provider) {
|
|
39
39
|
return provider;
|
|
40
40
|
}
|
|
41
|
-
function
|
|
41
|
+
function defineAuthClient(client) {
|
|
42
42
|
return client;
|
|
43
43
|
}
|
|
44
44
|
function defineHook(hook) {
|
|
@@ -51,6 +51,7 @@ function defineSecrets(config) {
|
|
|
51
51
|
return config;
|
|
52
52
|
}
|
|
53
53
|
export {
|
|
54
|
+
defineAuthClient,
|
|
54
55
|
defineAuthProvider,
|
|
55
56
|
defineConfig,
|
|
56
57
|
defineContextDimension,
|
|
@@ -63,7 +64,6 @@ export {
|
|
|
63
64
|
defineModel,
|
|
64
65
|
defineOperation,
|
|
65
66
|
definePlacement,
|
|
66
|
-
defineRelyingParty,
|
|
67
67
|
defineSchedule,
|
|
68
68
|
defineSecrets,
|
|
69
69
|
defineSelectField,
|