@foir/sdk 0.18.0 → 0.20.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/CHANGELOG.md +365 -0
- package/README.md +8 -1
- package/dist/app/client.d.ts +129 -21
- package/dist/app/contract/proto/apps/v1/apps_pb.d.ts +223 -211
- package/dist/app/errors.d.ts +2 -2
- package/dist/app/index.d.ts +4 -4
- package/dist/app/manifest.d.ts +1 -1
- package/dist/app/vocabulary.generated.d.ts +13 -10
- package/dist/app-contract.js +27 -31
- package/dist/app.js +90 -25
- package/dist/auth/access.d.ts +106 -0
- package/dist/auth/grant-table.generated.d.ts +13 -0
- package/dist/auth/grants.d.ts +38 -0
- package/dist/auth/index.d.ts +2 -0
- package/dist/auth/operations.d.ts +6 -1
- package/dist/auth/ops.d.ts +1 -0
- package/dist/auth.js +269 -1
- package/dist/data/builtins.d.ts +59 -3
- package/dist/data/compatibility.d.ts +95 -0
- package/dist/data/document.d.ts +11 -1
- package/dist/data/index.d.ts +1 -1
- package/dist/data/transport.d.ts +51 -27
- package/dist/data.js +337 -7
- package/dist/next.js +3 -0
- package/dist/placement/index.d.ts +40 -8
- package/dist/placement.js +224 -2
- package/dist/push.js +3 -0
- package/dist/react/FoirProvider.d.ts +40 -1
- package/dist/react/feedback-notes.d.ts +1 -1
- package/dist/react/index.d.ts +5 -4
- package/dist/react.js +347 -3
- package/dist/server/events-client.d.ts +5 -5
- package/dist/server/index.d.ts +1 -1
- package/dist/server/older-claims.generated.d.ts +8 -0
- package/dist/server/require-scoped-token.d.ts +1 -1
- package/dist/server/verify-scoped-token.d.ts +36 -17
- package/dist/server.js +267 -5
- package/package.json +6 -5
package/dist/auth/index.d.ts
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
*/
|
|
13
13
|
export { customerLogin, customerRegister, customerRequestOTP, customerLoginOTP, customerRefreshToken, customerLoginWithProvider, customerProviderCallback, customerProviderVerifyOTP, getAuthProviders, getAuthConfig, getDefaultAuthProvider, customerSignupContext, customerJoinWaitlist, customerRequestPasswordReset, customerResetPassword, customerUpdatePassword, customerVerifyEmail, currentUser, myProviderToken, customerLogout, deleteMyAccount, myOrganizations, selectOrganization, registerOrganization, saveSession, clearSession, getSessionToken, getSessionCustomer, loginWithPassword, registerWithPassword, loginWithOTP, resetPasswordAndSignIn, refreshSession, refreshSessionOnce, signOut, authedRequest, authedOp, requireAuth, requireAdmin, } from './auth';
|
|
14
14
|
export type { FoirAuthTokens, FoirCustomer, FoirUser, SessionAdapter, RequireAuthOptions, RequireAdminOptions, } from './auth';
|
|
15
|
+
export { customerAccess, getSessionAccess, hasRole, hasScope, requireRole, requireScope, FoirAccessDeniedError, } from './access';
|
|
16
|
+
export type { FoirAccess, FoirAccessDenial } from './access';
|
|
15
17
|
export { FoirAuthDowngradedError, FoirSessionExpiredError } from '../graphql/foir-request';
|
|
16
18
|
export { AUTH_OPS } from './ops';
|
|
17
19
|
export type { AuthOpName, AuthOpOptions } from './ops';
|
|
@@ -212,7 +212,12 @@ export interface MyOrganizationRole {
|
|
|
212
212
|
id: string;
|
|
213
213
|
key: string;
|
|
214
214
|
name: string;
|
|
215
|
-
|
|
215
|
+
/**
|
|
216
|
+
* What the role gives, as grants in the permission catalogue's names
|
|
217
|
+
* (`records:read:order`, `members:write`). Each stands for itself: changing
|
|
218
|
+
* a record and creating one are two grants.
|
|
219
|
+
*/
|
|
220
|
+
grants: string[];
|
|
216
221
|
}
|
|
217
222
|
/**
|
|
218
223
|
* Where the caller now stands, as `selectMyOrganization` returns it.
|
package/dist/auth/ops.d.ts
CHANGED
|
@@ -44,6 +44,7 @@ export declare const AUTH_OPS: {
|
|
|
44
44
|
readonly customerSignupContext: "customerSignupContext";
|
|
45
45
|
readonly currentUser: "currentUser";
|
|
46
46
|
readonly myProviderToken: "myProviderToken";
|
|
47
|
+
readonly whoami: "whoami";
|
|
47
48
|
readonly customerLogin: "customerLogin";
|
|
48
49
|
readonly customerRegister: "customerRegister";
|
|
49
50
|
readonly customerRequestOTP: "customerRequestOTP";
|
package/dist/auth.js
CHANGED
|
@@ -679,7 +679,7 @@ function toVariableValue(value, label) {
|
|
|
679
679
|
return value.toISOString();
|
|
680
680
|
}
|
|
681
681
|
if (Array.isArray(value)) {
|
|
682
|
-
return value.filter((item) => item !== void 0).map((item,
|
|
682
|
+
return value.filter((item) => item !== void 0).map((item, index2) => toVariableValue(item, `${label}[${index2}]`));
|
|
683
683
|
}
|
|
684
684
|
if (kind === "object") {
|
|
685
685
|
const out = {};
|
|
@@ -1034,6 +1034,9 @@ var AUTH_OPS = {
|
|
|
1034
1034
|
// Post-credential self reads.
|
|
1035
1035
|
currentUser: "currentUser",
|
|
1036
1036
|
myProviderToken: "myProviderToken",
|
|
1037
|
+
// What the calling credential may do, and for a signed-in customer the roles
|
|
1038
|
+
// they hold. `./access` reads it.
|
|
1039
|
+
whoami: "whoami",
|
|
1037
1040
|
// Methods.
|
|
1038
1041
|
customerLogin: "customerLogin",
|
|
1039
1042
|
customerRegister: "customerRegister",
|
|
@@ -1357,6 +1360,264 @@ async function requireAdmin(adapter, options) {
|
|
|
1357
1360
|
return user;
|
|
1358
1361
|
}
|
|
1359
1362
|
|
|
1363
|
+
// src/auth/grant-table.generated.ts
|
|
1364
|
+
var GRANT_TABLE = [
|
|
1365
|
+
["records:read", true, ["own", "named", "all"], []],
|
|
1366
|
+
["records:create", true, [], []],
|
|
1367
|
+
["records:write", true, ["own", "named", "all"], []],
|
|
1368
|
+
["records:delete", true, ["own", "named", "all"], []],
|
|
1369
|
+
["records:publish", true, ["own", "all"], []],
|
|
1370
|
+
["records:attribute", true, [], []],
|
|
1371
|
+
["search:read", true, [], []],
|
|
1372
|
+
["embeddings:read", false, [], []],
|
|
1373
|
+
["embeddings:write", false, [], []],
|
|
1374
|
+
["schemas:read", false, [], []],
|
|
1375
|
+
["drafts:read", false, [], []],
|
|
1376
|
+
["files:read", false, ["own", "all"], []],
|
|
1377
|
+
["files:write", false, ["own", "all"], []],
|
|
1378
|
+
["files:delete", false, ["own", "all"], []],
|
|
1379
|
+
["notifications:read", false, ["own", "all"], []],
|
|
1380
|
+
["notifications:write", false, ["own", "named", "all"], []],
|
|
1381
|
+
["notifications:consent", false, [], []],
|
|
1382
|
+
["pushcredentials:read", false, [], []],
|
|
1383
|
+
["pushcredentials:write", false, [], []],
|
|
1384
|
+
["notes:read", false, [], []],
|
|
1385
|
+
["notes:write", false, [], []],
|
|
1386
|
+
["notes:create", false, [], []],
|
|
1387
|
+
["approvals:read", false, [], []],
|
|
1388
|
+
["approvals:write", false, [], []],
|
|
1389
|
+
["rooms:read", true, [], []],
|
|
1390
|
+
["rooms:write", true, [], []],
|
|
1391
|
+
["presence:read", true, [], []],
|
|
1392
|
+
["operations:read", false, [], []],
|
|
1393
|
+
["operations:execute", true, [], []],
|
|
1394
|
+
["operations:write", false, [], []],
|
|
1395
|
+
["operations:complete", false, [], []],
|
|
1396
|
+
["status:write", false, [], []],
|
|
1397
|
+
["appconfig:read", false, [], []],
|
|
1398
|
+
["schedules:read", false, [], []],
|
|
1399
|
+
["schedules:write", false, [], []],
|
|
1400
|
+
["hooks:read", false, [], []],
|
|
1401
|
+
["hooks:write", false, [], []],
|
|
1402
|
+
["relyingparties:read", false, [], []],
|
|
1403
|
+
["relyingparties:write", false, [], []],
|
|
1404
|
+
["domains:read", false, [], []],
|
|
1405
|
+
["domains:write", false, [], []],
|
|
1406
|
+
["secrets:read", false, [], []],
|
|
1407
|
+
["secrets:list", false, [], []],
|
|
1408
|
+
["secrets:put", false, [], []],
|
|
1409
|
+
["secrets:delete", false, [], []],
|
|
1410
|
+
["appsecrets:read", false, [], []],
|
|
1411
|
+
["appsecrets:list", false, [], []],
|
|
1412
|
+
["appsecrets:put", false, [], []],
|
|
1413
|
+
["appsecrets:delete", false, [], []],
|
|
1414
|
+
["opsecrets:read", false, [], []],
|
|
1415
|
+
["opsecrets:write", false, [], []],
|
|
1416
|
+
["settings:read", false, [], []],
|
|
1417
|
+
["settings:write", false, [], []],
|
|
1418
|
+
["designtokens:read", false, [], []],
|
|
1419
|
+
["designtokens:write", false, [], []],
|
|
1420
|
+
["models:read", false, [], []],
|
|
1421
|
+
["models:write", false, [], []],
|
|
1422
|
+
["configs:read", false, [], []],
|
|
1423
|
+
["configs:write", false, [], []],
|
|
1424
|
+
["apps:read", false, [], []],
|
|
1425
|
+
["apps:write", false, [], []],
|
|
1426
|
+
["events:read", false, [], []],
|
|
1427
|
+
["targeting:debug", false, [], []],
|
|
1428
|
+
["visitors:forward", false, [], []],
|
|
1429
|
+
["apikeys:read", false, [], []],
|
|
1430
|
+
["apikeys:write", false, [], []],
|
|
1431
|
+
["organizations:read", false, [], []],
|
|
1432
|
+
["organizations:write", false, [], []],
|
|
1433
|
+
["organizations:create", false, [], []],
|
|
1434
|
+
["organizations:delete", false, [], []],
|
|
1435
|
+
["organizations:reach", false, [], []],
|
|
1436
|
+
["members:read", false, [], []],
|
|
1437
|
+
["members:write", false, [], []],
|
|
1438
|
+
["roles:read", false, [], []],
|
|
1439
|
+
["roles:write", false, [], []],
|
|
1440
|
+
["projects:read", false, [], []],
|
|
1441
|
+
["projects:write", false, [], []],
|
|
1442
|
+
["project:manage", false, [], ["records:read.all", "records:create", "records:write.all", "records:delete.all", "records:publish.all", "records:attribute", "search:read", "embeddings:read", "embeddings:write", "schemas:read", "drafts:read", "files:read.all", "files:write.all", "files:delete.all", "users:read.all", "identities:read", "users:create", "identities:delete", "users:write.all", "users:delete.all", "notifications:read.all", "notifications:write.all", "notifications:consent", "pushcredentials:read", "pushcredentials:write", "notes:read", "notes:write", "notes:create", "approvals:read", "approvals:write", "rooms:read", "rooms:write", "presence:read", "operations:read", "operations:execute", "operations:write", "appconfig:read", "schedules:read", "schedules:write", "hooks:read", "hooks:write", "auth:login_with_foir", "relyingparties:read", "relyingparties:write", "domains:read", "domains:write", "secrets:read", "secrets:list", "secrets:put", "secrets:delete", "appsecrets:read", "appsecrets:list", "appsecrets:put", "appsecrets:delete", "usersecrets:read", "usersecrets:list", "usersecrets:put", "usersecrets:delete", "opsecrets:read", "opsecrets:write", "settings:read", "settings:write", "designtokens:read", "designtokens:write", "models:read", "models:write", "configs:read", "configs:write", "signinproviders:read", "signinproviders:write", "apps:read", "apps:write", "events:read", "targeting:debug", "visitors:forward", "apikeys:read", "apikeys:write", "organizations:read", "organizations:create", "organizations:delete", "organizations:write", "organizations:reach", "members:read", "members:write", "roles:read", "roles:write", "projects:read", "projects:write"]],
|
|
1443
|
+
["projects:create", false, [], []],
|
|
1444
|
+
["projects:delete", false, [], []],
|
|
1445
|
+
["tenants:read", false, [], []],
|
|
1446
|
+
["tenants:write", false, [], []],
|
|
1447
|
+
["tenants:delete", false, [], []],
|
|
1448
|
+
["audit:read", false, [], []],
|
|
1449
|
+
["export:run", false, [], []],
|
|
1450
|
+
["billing:read", false, [], []],
|
|
1451
|
+
["billing:manage", false, [], []],
|
|
1452
|
+
["usage:read", false, [], []],
|
|
1453
|
+
["controlkeys:read", false, [], []],
|
|
1454
|
+
["controlkeys:write", false, [], []],
|
|
1455
|
+
["tenanthooks:read", false, [], []],
|
|
1456
|
+
["tenanthooks:write", false, [], []],
|
|
1457
|
+
["apps:install", false, [], []],
|
|
1458
|
+
["tenant:manage", false, [], ["project:manage", "projects:create", "projects:delete", "tenants:read", "tenants:write", "tenants:delete", "audit:read", "export:run", "billing:read", "billing:manage", "usage:read", "controlkeys:read", "controlkeys:write", "tenanthooks:read", "tenanthooks:write", "apps:install"]],
|
|
1459
|
+
["platform:read", false, [], []],
|
|
1460
|
+
["platform:manage", false, [], ["tenant:manage", "platform:read", "platform:create_workspace", "users:setup"]],
|
|
1461
|
+
["platform:create_workspace", false, [], []],
|
|
1462
|
+
["users:read", false, ["own", "named", "all"], []],
|
|
1463
|
+
["users:write", false, ["own", "all"], []],
|
|
1464
|
+
["users:setup", false, [], []],
|
|
1465
|
+
["users:delete", false, ["own", "all"], []],
|
|
1466
|
+
["users:create", false, [], []],
|
|
1467
|
+
["identities:read", false, [], []],
|
|
1468
|
+
["identities:delete", false, [], []],
|
|
1469
|
+
["usersecrets:read", false, [], []],
|
|
1470
|
+
["usersecrets:list", false, [], []],
|
|
1471
|
+
["usersecrets:put", false, [], []],
|
|
1472
|
+
["usersecrets:delete", false, [], []],
|
|
1473
|
+
["auth:login_with_foir", false, [], []],
|
|
1474
|
+
["signinproviders:read", false, [], []],
|
|
1475
|
+
["signinproviders:write", false, [], []]
|
|
1476
|
+
];
|
|
1477
|
+
|
|
1478
|
+
// src/auth/grants.ts
|
|
1479
|
+
var WILDCARD = "*";
|
|
1480
|
+
var RESOURCE_WILDCARD = ":*";
|
|
1481
|
+
var index;
|
|
1482
|
+
function catalogue() {
|
|
1483
|
+
if (!index) {
|
|
1484
|
+
const byName = new Map(GRANT_TABLE.map((row) => [row[0], row]));
|
|
1485
|
+
const longestFirst = [...byName.keys()].sort(
|
|
1486
|
+
(a, b) => a.length !== b.length ? b.length - a.length : a < b ? -1 : 1
|
|
1487
|
+
);
|
|
1488
|
+
index = { byName, longestFirst };
|
|
1489
|
+
}
|
|
1490
|
+
return index;
|
|
1491
|
+
}
|
|
1492
|
+
var reachRank = { own: 1, named: 2, all: 3 };
|
|
1493
|
+
function hasReach(permission, reach) {
|
|
1494
|
+
const reaches = permission[2];
|
|
1495
|
+
return reaches.length === 0 ? reach === "own" : reaches.includes(reach);
|
|
1496
|
+
}
|
|
1497
|
+
function resourceOf(name) {
|
|
1498
|
+
const i = name.indexOf(":");
|
|
1499
|
+
return i < 0 ? name : name.slice(0, i);
|
|
1500
|
+
}
|
|
1501
|
+
function parseGrant(s) {
|
|
1502
|
+
const { byName, longestFirst } = catalogue();
|
|
1503
|
+
for (const name of longestFirst) {
|
|
1504
|
+
if (!s.startsWith(name)) continue;
|
|
1505
|
+
let rest = s.slice(name.length);
|
|
1506
|
+
if (rest !== "" && rest[0] !== "." && rest[0] !== ":") continue;
|
|
1507
|
+
const permission = byName.get(name);
|
|
1508
|
+
let reach = "own";
|
|
1509
|
+
if (rest.startsWith(".")) {
|
|
1510
|
+
const after = rest.slice(1);
|
|
1511
|
+
const colon = after.indexOf(":");
|
|
1512
|
+
const word = colon < 0 ? after : after.slice(0, colon);
|
|
1513
|
+
if (word !== "named" && word !== "all") return null;
|
|
1514
|
+
if (!hasReach(permission, word)) return null;
|
|
1515
|
+
reach = word;
|
|
1516
|
+
rest = after.slice(word.length);
|
|
1517
|
+
}
|
|
1518
|
+
let target = "";
|
|
1519
|
+
if (rest.startsWith(":")) {
|
|
1520
|
+
target = rest.slice(1);
|
|
1521
|
+
if (!permission[1]) return null;
|
|
1522
|
+
if (target === "" || /[: \t\n*]/.test(target)) return null;
|
|
1523
|
+
}
|
|
1524
|
+
return { permission, reach, target };
|
|
1525
|
+
}
|
|
1526
|
+
return null;
|
|
1527
|
+
}
|
|
1528
|
+
function covers(held, required) {
|
|
1529
|
+
return held.permission === required.permission && reachRank[held.reach] >= reachRank[required.reach] && (held.target === "" || held.target === required.target);
|
|
1530
|
+
}
|
|
1531
|
+
function resourceWildcard(held) {
|
|
1532
|
+
if (!held.endsWith(RESOURCE_WILDCARD)) return null;
|
|
1533
|
+
const resource = held.slice(0, -RESOURCE_WILDCARD.length);
|
|
1534
|
+
return resource.includes(":") ? null : resource;
|
|
1535
|
+
}
|
|
1536
|
+
function holdsAt(held, required, depth) {
|
|
1537
|
+
for (const h of held) {
|
|
1538
|
+
if (h === WILDCARD) return true;
|
|
1539
|
+
const resource = resourceWildcard(h);
|
|
1540
|
+
if (resource !== null) {
|
|
1541
|
+
if (resource === resourceOf(required.permission[0])) return true;
|
|
1542
|
+
continue;
|
|
1543
|
+
}
|
|
1544
|
+
const grant = parseGrant(h);
|
|
1545
|
+
if (grant === null) continue;
|
|
1546
|
+
if (covers(grant, required)) return true;
|
|
1547
|
+
const implies = grant.permission[3];
|
|
1548
|
+
if (depth < 4 && implies.length > 0 && holdsAt(implies, required, depth + 1)) return true;
|
|
1549
|
+
}
|
|
1550
|
+
return false;
|
|
1551
|
+
}
|
|
1552
|
+
function holds(held, required) {
|
|
1553
|
+
const grant = parseGrant(required);
|
|
1554
|
+
return grant !== null && holdsAt(held, grant, 0);
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
// src/auth/access.ts
|
|
1558
|
+
function accessFrom(who) {
|
|
1559
|
+
const customer = who?.credential?.customer;
|
|
1560
|
+
if (!customer?.id) return null;
|
|
1561
|
+
return {
|
|
1562
|
+
customerId: customer.id,
|
|
1563
|
+
roles: Array.isArray(customer.roles) ? [...customer.roles] : null,
|
|
1564
|
+
scopes: [...who?.authority?.effective ?? []]
|
|
1565
|
+
};
|
|
1566
|
+
}
|
|
1567
|
+
async function customerAccess(env, options) {
|
|
1568
|
+
return accessFrom(await authOp(env, AUTH_OPS.whoami, void 0, options));
|
|
1569
|
+
}
|
|
1570
|
+
async function getSessionAccess(adapter, env) {
|
|
1571
|
+
if (!await getSessionToken(adapter)) return null;
|
|
1572
|
+
return accessFrom(await authedOp(adapter, env, AUTH_OPS.whoami));
|
|
1573
|
+
}
|
|
1574
|
+
function hasRole(access, key) {
|
|
1575
|
+
return access?.roles?.includes(key) ?? false;
|
|
1576
|
+
}
|
|
1577
|
+
function hasScope(access, scope) {
|
|
1578
|
+
return access ? holds(access.scopes, scope) : false;
|
|
1579
|
+
}
|
|
1580
|
+
var FoirAccessDeniedError = class extends Error {
|
|
1581
|
+
code = "FOIR_ACCESS_DENIED";
|
|
1582
|
+
reason;
|
|
1583
|
+
/** The role that was required, when one was. */
|
|
1584
|
+
role;
|
|
1585
|
+
/** The scope that was required, when one was. */
|
|
1586
|
+
scope;
|
|
1587
|
+
constructor(reason, required) {
|
|
1588
|
+
super(denialMessage(reason, required));
|
|
1589
|
+
this.name = "FoirAccessDeniedError";
|
|
1590
|
+
this.reason = reason;
|
|
1591
|
+
this.role = required.role;
|
|
1592
|
+
this.scope = required.scope;
|
|
1593
|
+
}
|
|
1594
|
+
};
|
|
1595
|
+
function denialMessage(reason, required) {
|
|
1596
|
+
switch (reason) {
|
|
1597
|
+
case "signed_out":
|
|
1598
|
+
return "[foir] nobody is signed in";
|
|
1599
|
+
case "role_not_held":
|
|
1600
|
+
return `[foir] the signed-in customer does not hold the role "${required.role}"`;
|
|
1601
|
+
case "roles_unknown":
|
|
1602
|
+
return `[foir] the platform did not say which roles the signed-in customer holds, so "${required.role}" could not be checked`;
|
|
1603
|
+
case "scope_not_held":
|
|
1604
|
+
return `[foir] the signed-in customer's scopes do not cover "${required.scope}"`;
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
async function requireRole(adapter, env, key) {
|
|
1608
|
+
const access = await getSessionAccess(adapter, env);
|
|
1609
|
+
if (!access) throw new FoirAccessDeniedError("signed_out", { role: key });
|
|
1610
|
+
if (access.roles === null) throw new FoirAccessDeniedError("roles_unknown", { role: key });
|
|
1611
|
+
if (!access.roles.includes(key)) throw new FoirAccessDeniedError("role_not_held", { role: key });
|
|
1612
|
+
return access;
|
|
1613
|
+
}
|
|
1614
|
+
async function requireScope(adapter, env, scope) {
|
|
1615
|
+
const access = await getSessionAccess(adapter, env);
|
|
1616
|
+
if (!access) throw new FoirAccessDeniedError("signed_out", { scope });
|
|
1617
|
+
if (!holds(access.scopes, scope)) throw new FoirAccessDeniedError("scope_not_held", { scope });
|
|
1618
|
+
return access;
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1360
1621
|
// src/auth/hosted.ts
|
|
1361
1622
|
var DEFAULT_COOKIE_NAME = "foir_customer_session";
|
|
1362
1623
|
var DEFAULT_SESSION_PATH = "/session/token";
|
|
@@ -1567,6 +1828,7 @@ function trimSlash(s) {
|
|
|
1567
1828
|
}
|
|
1568
1829
|
export {
|
|
1569
1830
|
AUTH_OPS,
|
|
1831
|
+
FoirAccessDeniedError,
|
|
1570
1832
|
FoirAuthDowngradedError,
|
|
1571
1833
|
FoirSessionExpiredError,
|
|
1572
1834
|
authedOp,
|
|
@@ -1574,6 +1836,7 @@ export {
|
|
|
1574
1836
|
clearSession,
|
|
1575
1837
|
createFoirSessionClient,
|
|
1576
1838
|
currentUser,
|
|
1839
|
+
customerAccess,
|
|
1577
1840
|
customerJoinWaitlist,
|
|
1578
1841
|
customerLogin,
|
|
1579
1842
|
customerLoginOTP,
|
|
@@ -1594,8 +1857,11 @@ export {
|
|
|
1594
1857
|
getAuthConfig,
|
|
1595
1858
|
getAuthProviders,
|
|
1596
1859
|
getDefaultAuthProvider,
|
|
1860
|
+
getSessionAccess,
|
|
1597
1861
|
getSessionCustomer,
|
|
1598
1862
|
getSessionToken,
|
|
1863
|
+
hasRole,
|
|
1864
|
+
hasScope,
|
|
1599
1865
|
hostedLoginUrl,
|
|
1600
1866
|
hostedLogoutUrl,
|
|
1601
1867
|
loginWithOTP,
|
|
@@ -1608,6 +1874,8 @@ export {
|
|
|
1608
1874
|
registerWithPassword,
|
|
1609
1875
|
requireAdmin,
|
|
1610
1876
|
requireAuth,
|
|
1877
|
+
requireRole,
|
|
1878
|
+
requireScope,
|
|
1611
1879
|
resetPasswordAndSignIn,
|
|
1612
1880
|
saveSession,
|
|
1613
1881
|
selectOrganization,
|
package/dist/data/builtins.d.ts
CHANGED
|
@@ -520,7 +520,7 @@ export interface FoirCustomerNote {
|
|
|
520
520
|
* What a note surface has to know about the project before it renders anything
|
|
521
521
|
* — `foir.ns.notes.noteConfig`.
|
|
522
522
|
*
|
|
523
|
-
* Charged `notes:
|
|
523
|
+
* Charged `notes:create` rather than `notes:read`, and refused to a caller with
|
|
524
524
|
* no customer session, because the only caller it is useful to is one that can
|
|
525
525
|
* submit. So a composer can read it as its single readiness probe: an answer
|
|
526
526
|
* means this visitor may write, and a refusal names which half is missing.
|
|
@@ -551,7 +551,54 @@ export interface FoirMyNotesArgs {
|
|
|
551
551
|
* internal ones.
|
|
552
552
|
*/
|
|
553
553
|
export type FoirMyNotesResult = FoirConnection<FoirCustomerNote>;
|
|
554
|
-
/**
|
|
554
|
+
/**
|
|
555
|
+
* `writeNote` arguments: the one note create, for a signed-in customer and for
|
|
556
|
+
* a visitor alike. It asks `notes:create` of both.
|
|
557
|
+
*
|
|
558
|
+
* A signed-in customer's note is theirs and comes back in `myNotes`. A
|
|
559
|
+
* visitor's, through a key that gives `notes:create` to whoever comes through
|
|
560
|
+
* it, on a project that takes visitors' notes, is nobody's: ask for its `id`
|
|
561
|
+
* and `createdAt` and nothing else, because nothing reads it back.
|
|
562
|
+
*
|
|
563
|
+
* Served by a data API that has the field. `<FoirFeedback>` still writes
|
|
564
|
+
* through `createMyNote`, the same create under its older name, which every
|
|
565
|
+
* data API serves.
|
|
566
|
+
*/
|
|
567
|
+
export interface FoirWriteNoteArgs {
|
|
568
|
+
/** A visitor may write about a record and nothing else. */
|
|
569
|
+
subjectType: FoirNoteSubjectType;
|
|
570
|
+
subjectId: string;
|
|
571
|
+
/** Required when `subjectType` is `'record'`, refused otherwise. */
|
|
572
|
+
subjectModelKey?: string;
|
|
573
|
+
body: string;
|
|
574
|
+
/** Reply within an existing thread. Refused where nobody is signed in. */
|
|
575
|
+
parentNoteId?: string;
|
|
576
|
+
/**
|
|
577
|
+
* Files to submit with this note, already uploaded through
|
|
578
|
+
* `createFileUpload` / `confirmFileUpload`. Refused where nobody is signed in.
|
|
579
|
+
*
|
|
580
|
+
* A file this caller cannot read is REFUSED and the note is not written. That
|
|
581
|
+
* is not a rough edge to code around: a create that accepted the note and
|
|
582
|
+
* dropped the attachment would leave the author believing they sent a
|
|
583
|
+
* screenshot nobody has.
|
|
584
|
+
*/
|
|
585
|
+
attachmentFileIds?: string[];
|
|
586
|
+
/**
|
|
587
|
+
* Where the operators may reply to a visitor. Refused from a signed-in
|
|
588
|
+
* customer, whose note has an author.
|
|
589
|
+
*
|
|
590
|
+
* Captured as typed and NEVER verified: it is not treated as proof of who
|
|
591
|
+
* wrote the note, and nothing is sent to it to confirm it. Do not build a
|
|
592
|
+
* flow that assumes the address is reachable.
|
|
593
|
+
*/
|
|
594
|
+
contactEmail?: string;
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* `createMyNote` arguments.
|
|
598
|
+
*
|
|
599
|
+
* @deprecated `createMyNote` is the older name of `writeNote` for a signed-in
|
|
600
|
+
* customer. Use `writeNote` and {@link FoirWriteNoteArgs}.
|
|
601
|
+
*/
|
|
555
602
|
export interface FoirCreateMyNoteArgs {
|
|
556
603
|
subjectType: FoirNoteSubjectType;
|
|
557
604
|
subjectId: string;
|
|
@@ -581,6 +628,10 @@ export interface FoirCreateMyNoteArgs {
|
|
|
581
628
|
*
|
|
582
629
|
* WRITE-ONLY. Nothing reads an anonymous note back on the public plane, which
|
|
583
630
|
* is why the result is a receipt rather than the note.
|
|
631
|
+
*
|
|
632
|
+
* @deprecated `submitAnonymousNote` is the older name of `writeNote` for a
|
|
633
|
+
* visitor. Use `writeNote` and {@link FoirWriteNoteArgs}, asking for `id` and
|
|
634
|
+
* `createdAt`.
|
|
584
635
|
*/
|
|
585
636
|
export interface FoirSubmitAnonymousNoteArgs {
|
|
586
637
|
/**
|
|
@@ -606,7 +657,12 @@ export interface FoirSubmitAnonymousNoteArgs {
|
|
|
606
657
|
*/
|
|
607
658
|
contactEmail?: string;
|
|
608
659
|
}
|
|
609
|
-
/**
|
|
660
|
+
/**
|
|
661
|
+
* What `submitAnonymousNote` returns.
|
|
662
|
+
*
|
|
663
|
+
* @deprecated With the field it answers. `writeNote` answers a visitor with
|
|
664
|
+
* the note's `id` and `createdAt`.
|
|
665
|
+
*/
|
|
610
666
|
export interface FoirAnonymousNoteReceipt {
|
|
611
667
|
/**
|
|
612
668
|
* A reference for this submission, to quote when following it up.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether the surface being served can still answer what a generated module
|
|
3
|
+
* prints.
|
|
4
|
+
*
|
|
5
|
+
* A generated module records the surface version it came from, and that version
|
|
6
|
+
* is a hash over everything `foir types` reads. So it moves when a model gains
|
|
7
|
+
* a field, when a namespace gains an op, and when the platform adds a mutation
|
|
8
|
+
* to every model at once. None of those changes a document this client prints:
|
|
9
|
+
* the document names the fields and arguments the module knew about, and the
|
|
10
|
+
* server still has every one of them. Treating a moved version as a reason to
|
|
11
|
+
* refuse calls made every deployed client stop working the moment the surface
|
|
12
|
+
* grew, with nothing in its own project having changed.
|
|
13
|
+
*
|
|
14
|
+
* So the two are kept apart. A moved version is DRIFT, and it is reported. A
|
|
15
|
+
* call is refused only for an INCOMPATIBILITY: something that call prints, or
|
|
16
|
+
* sends, that the server no longer serves the way the module describes. The
|
|
17
|
+
* client holds both sides of that question once the manifest has arrived, since
|
|
18
|
+
* the generated trees and the served manifest are the same shape, and this file
|
|
19
|
+
* compares them.
|
|
20
|
+
*
|
|
21
|
+
* ---------------------------------------------------------------------------
|
|
22
|
+
* What "the same" means
|
|
23
|
+
* ---------------------------------------------------------------------------
|
|
24
|
+
*
|
|
25
|
+
* What `document.ts` reads, because a property the printer never reads cannot
|
|
26
|
+
* change a document:
|
|
27
|
+
*
|
|
28
|
+
* - an op: its root `field`, whether it is a `mutation`, its `args` and
|
|
29
|
+
* `argTypes`, and its `payload` (`kind`, `model`, `union`, `fields`);
|
|
30
|
+
* - a selection field: its `key`, and which of the printer's branches it
|
|
31
|
+
* takes (a relation, a stopping point, a `model` reference, a `union`
|
|
32
|
+
* reference, an object with `fields`, or a leaf);
|
|
33
|
+
* - a relation an `include` resolves: `target`, `targets` and `connection`,
|
|
34
|
+
* and one property the printer does not read, `list`, because it decides
|
|
35
|
+
* whether the same document answers with one record or an array;
|
|
36
|
+
* - a model's `typeName`, which names the fragment a polymorphic relation
|
|
37
|
+
* prints for it;
|
|
38
|
+
* - a union's members, by `typeName`, and each member's fields.
|
|
39
|
+
*
|
|
40
|
+
* `cost`, `nullable`, `deprecated` and the buckets the generator does not emit
|
|
41
|
+
* are not compared. `alias` is not compared either, and that one is a decision:
|
|
42
|
+
* an alias is a name this client gives a field in its OWN document, so the
|
|
43
|
+
* server never sees the manifest's opinion of it. A new union member whose
|
|
44
|
+
* field collides with an existing one makes the server publish aliases for
|
|
45
|
+
* both, and the module's document, which selects only the members it knew,
|
|
46
|
+
* is as valid as it was.
|
|
47
|
+
*
|
|
48
|
+
* The comparison runs one way. Everything the module names must still be
|
|
49
|
+
* served; whatever the server has added is never looked at, with one
|
|
50
|
+
* exception: an argument the served op REQUIRES and the module does not know,
|
|
51
|
+
* since a call that cannot supply it cannot succeed.
|
|
52
|
+
*/
|
|
53
|
+
import { type FoirSelectionSource } from './document';
|
|
54
|
+
/** One thing the generated module describes that the server no longer serves that way. */
|
|
55
|
+
export interface FoirIncompatibility {
|
|
56
|
+
/**
|
|
57
|
+
* Where it sits, in the manifest's own keys:
|
|
58
|
+
* `models.order.selection.status`, `models.order.ops.update`,
|
|
59
|
+
* `namespaces.search.ops.searchRecords.args.query`.
|
|
60
|
+
*/
|
|
61
|
+
path: string;
|
|
62
|
+
/** What happened to it, as a clause a message can put in brackets. */
|
|
63
|
+
reason: string;
|
|
64
|
+
}
|
|
65
|
+
/** The comparison, held by a client for as long as it lives. */
|
|
66
|
+
export interface FoirSurfaceComparison {
|
|
67
|
+
/** Every incompatible path, sorted. Empty when the surface has only grown. */
|
|
68
|
+
readonly incompatible: readonly string[];
|
|
69
|
+
/** What `foir.<model>.<op>()` depends on that is incompatible. */
|
|
70
|
+
modelCall(model: string, op: string, include: unknown): readonly FoirIncompatibility[];
|
|
71
|
+
/** What `foir.ns.<namespace>.<op>()` depends on that is incompatible. */
|
|
72
|
+
namespaceCall(namespace: string, op: string, include: unknown): readonly FoirIncompatibility[];
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Compare a generated module's trees with the manifest being served.
|
|
76
|
+
*
|
|
77
|
+
* Called once, when the manifest arrives, and off the response path. What it
|
|
78
|
+
* returns answers each later call from maps built here, so a call pays for a
|
|
79
|
+
* lookup and, when it has an `include`, a walk of the relations it names.
|
|
80
|
+
*
|
|
81
|
+
* How far a break reaches is decided per call rather than per model. A call
|
|
82
|
+
* depends on its op, on every tree its result prints (the model's own, the
|
|
83
|
+
* unions inside it, and so on down), and on the relations THIS call includes
|
|
84
|
+
* with the trees of their targets. A model whose relation points at a broken
|
|
85
|
+
* model is not refused for it until a call includes that relation, because
|
|
86
|
+
* until then the broken tree is not in the document. Refusing on the whole
|
|
87
|
+
* relation graph would let one field removed from one model stop every call in
|
|
88
|
+
* a project whose models all reach each other, which is most projects.
|
|
89
|
+
*
|
|
90
|
+
* It is not narrowed by `pick` or `omit`. A call that omits the one field that
|
|
91
|
+
* is gone is still refused: resolving a caller's paths against two trees, with
|
|
92
|
+
* aliases and union members in play, is the printer's work done a second time,
|
|
93
|
+
* and regenerating is the remedy either way.
|
|
94
|
+
*/
|
|
95
|
+
export declare function compareFoirSurfaces(generated: FoirSelectionSource, served: FoirSelectionSource): FoirSurfaceComparison;
|
package/dist/data/document.d.ts
CHANGED
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
* inferring a literal would reinstate the defect above under a condition no
|
|
52
52
|
* caller can see.
|
|
53
53
|
*/
|
|
54
|
-
import type { FoirManifestOp, FoirManifestRelation, FoirManifestSelection, FoirManifestSelectionField, FoirManifestSelectionUnion } from './manifest';
|
|
54
|
+
import type { FoirManifestOp, FoirManifestOpPayload, FoirManifestRelation, FoirManifestSelection, FoirManifestSelectionField, FoirManifestSelectionUnion } from './manifest';
|
|
55
55
|
/**
|
|
56
56
|
* One model, as the printer needs it.
|
|
57
57
|
*
|
|
@@ -145,6 +145,16 @@ export interface FoirRecordPath {
|
|
|
145
145
|
/** Model key whose published `selection` is printed at that position. */
|
|
146
146
|
modelKey: string;
|
|
147
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* Every record position in a payload, in the order the tree declares them.
|
|
150
|
+
*
|
|
151
|
+
* ONE walk, and everything that needs to know where the records are reads it:
|
|
152
|
+
* `planCall` takes the model SET from it to decide where `pick`/`omit`/`include`
|
|
153
|
+
* land, and the plan carries the positions so the transport's caller can find
|
|
154
|
+
* the records in the response. A second walk would be correct on the day it was
|
|
155
|
+
* written and wrong on the day the payload format grew a case.
|
|
156
|
+
*/
|
|
157
|
+
export declare function payloadRecordPaths(payload: FoirManifestOpPayload): readonly FoirRecordPath[];
|
|
148
158
|
/** One printed operation, and what the transport needs to route it. */
|
|
149
159
|
export interface FoirDocumentPlan {
|
|
150
160
|
/** The document text. Also the thing that gets hashed for APQ. */
|
package/dist/data/index.d.ts
CHANGED
|
@@ -59,7 +59,7 @@ export { planFoirModelCall, planFoirNamespaceCall } from './document';
|
|
|
59
59
|
export type { FoirCall, FoirDocumentPlan, FoirNamespaceCall, FoirNamespaceSelections, FoirRecordPath, FoirSelectionModel, FoirSelectionNamespace, FoirSelections, FoirSelectionSource, FoirSelectionUnions, } from './document';
|
|
60
60
|
export { createFoirSubjectRegistry, recordFoirSubjects } from './subjects';
|
|
61
61
|
export type { FoirSubject, FoirSubjectEntry, FoirSubjectOptions, FoirSubjectOrigin, FoirSubjectRegistry, } from './subjects';
|
|
62
|
-
export type { CollabInfo, FileValue, FoirAccessGrant, FoirAccessLevel, FoirAccessSource, FoirAccessor, FoirCreateMyNoteArgs, FoirSubmitAnonymousNoteArgs, FoirAnonymousNoteReceipt, FoirCustomerNote, FoirCustomerNoteAttachment, FoirDegraded, FoirDepthBelow, FoirDepthLevel, FoirDeviceType, FoirGranteeKind, FoirJsonValue, FoirMyAccess, FoirMyNotesArgs, FoirMyNotesResult, FoirNoteConfig, FoirNoteSubjectType, FoirPlatformType, FoirRecordOwner, FoirResolveContext, FoirResourceAccess, FoirUnexpandedRecord, FoirUpdateManyPayload, FoirVisibility, GeoPoint, Image, ImageFormat, ImageVariant, ImageVariantSet, MediaValue, Passage, Prose, ProseSpan, RecordAncestor, Reference, Richtext, Video, } from './builtins';
|
|
62
|
+
export type { CollabInfo, FileValue, FoirAccessGrant, FoirAccessLevel, FoirAccessSource, FoirAccessor, FoirCreateMyNoteArgs, FoirWriteNoteArgs, FoirSubmitAnonymousNoteArgs, FoirAnonymousNoteReceipt, FoirCustomerNote, FoirCustomerNoteAttachment, FoirDegraded, FoirDepthBelow, FoirDepthLevel, FoirDeviceType, FoirGranteeKind, FoirJsonValue, FoirMyAccess, FoirMyNotesArgs, FoirMyNotesResult, FoirNoteConfig, FoirNoteSubjectType, FoirPlatformType, FoirRecordOwner, FoirResolveContext, FoirResourceAccess, FoirUnexpandedRecord, FoirUpdateManyPayload, FoirVisibility, GeoPoint, Image, ImageFormat, ImageVariant, ImageVariantSet, MediaValue, Passage, Prose, ProseSpan, RecordAncestor, Reference, Richtext, Video, } from './builtins';
|
|
63
63
|
/**
|
|
64
64
|
* Pre-rename spellings. The `<Name>Block` wrappers and the `<Name>Value`
|
|
65
65
|
* split are the same thing as the types above — an item IS its value — so each
|