tempest-react-sdk 0.81.0 → 0.83.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/README.md +9 -10
- package/bin/create-tempest-app.e2e.test.mjs +31 -0
- package/bin/create-tempest-app.mjs +11 -1
- package/bin/lib/doctor/doctor.e2e.test.mjs +29 -0
- package/bin/lib/doctor/vitest.mjs +120 -0
- package/bin/lib/doctor/vitest.test.mjs +139 -0
- package/bin/lib/fs/contain.mjs +7 -0
- package/bin/lib/openapi/escape.mjs +68 -4
- package/bin/lib/openapi/generate.hostile.test.mjs +228 -0
- package/bin/lib/openapi/generate.mjs +119 -15
- package/bin/lib/openapi/pattern-risk.mjs +134 -0
- package/bin/lib/openapi/pattern-risk.test.mjs +45 -0
- package/bin/tempest-gen.test.mjs +100 -0
- package/bin/tempest.mjs +24 -8
- package/dist/access/use-can.cjs +1 -1
- package/dist/access/use-can.cjs.map +1 -1
- package/dist/access/use-can.js +25 -28
- package/dist/access/use-can.js.map +1 -1
- package/dist/auth/create-tempest-auth.cjs +1 -1
- package/dist/auth/create-tempest-auth.cjs.map +1 -1
- package/dist/auth/create-tempest-auth.js +189 -143
- package/dist/auth/create-tempest-auth.js.map +1 -1
- package/dist/auth/session-identity.cjs +2 -0
- package/dist/auth/session-identity.cjs.map +1 -0
- package/dist/auth/session-identity.js +12 -0
- package/dist/auth/session-identity.js.map +1 -0
- package/dist/br/boleto.cjs.map +1 -1
- package/dist/br/boleto.js.map +1 -1
- package/dist/components/AuthForm/AuthLinks.cjs +1 -1
- package/dist/components/AuthForm/AuthLinks.cjs.map +1 -1
- package/dist/components/AuthForm/AuthLinks.js +18 -15
- package/dist/components/AuthForm/AuthLinks.js.map +1 -1
- package/dist/components/AuthPage/AuthPage.cjs +1 -1
- package/dist/components/AuthPage/AuthPage.cjs.map +1 -1
- package/dist/components/AuthPage/AuthPage.js +58 -52
- package/dist/components/AuthPage/AuthPage.js.map +1 -1
- package/dist/components/Breadcrumbs/Breadcrumbs.cjs +1 -1
- package/dist/components/Breadcrumbs/Breadcrumbs.cjs.map +1 -1
- package/dist/components/Breadcrumbs/Breadcrumbs.js +22 -21
- package/dist/components/Breadcrumbs/Breadcrumbs.js.map +1 -1
- package/dist/components/Button/Button.cjs +1 -1
- package/dist/components/Button/Button.cjs.map +1 -1
- package/dist/components/Button/Button.js +41 -40
- package/dist/components/Button/Button.js.map +1 -1
- package/dist/components/Chat/ChatAttachments.cjs +1 -1
- package/dist/components/Chat/ChatAttachments.cjs.map +1 -1
- package/dist/components/Chat/ChatAttachments.js +38 -38
- package/dist/components/Form/Form.cjs +1 -1
- package/dist/components/Form/Form.cjs.map +1 -1
- package/dist/components/Form/Form.js +30 -32
- package/dist/components/Form/Form.js.map +1 -1
- package/dist/components/Layout/Layout.cjs +1 -1
- package/dist/components/Layout/Layout.cjs.map +1 -1
- package/dist/components/Layout/Layout.js +25 -24
- package/dist/components/Layout/Layout.js.map +1 -1
- package/dist/components/Layout/gap.cjs +2 -0
- package/dist/components/Layout/gap.cjs.map +1 -0
- package/dist/components/Layout/gap.js +15 -0
- package/dist/components/Layout/gap.js.map +1 -0
- package/dist/components/NavigationMenu/NavigationMenu.cjs +1 -1
- package/dist/components/NavigationMenu/NavigationMenu.cjs.map +1 -1
- package/dist/components/NavigationMenu/NavigationMenu.js +71 -70
- package/dist/components/NavigationMenu/NavigationMenu.js.map +1 -1
- package/dist/components/Sidebar/Sidebar.cjs.map +1 -1
- package/dist/components/Sidebar/Sidebar.js.map +1 -1
- package/dist/components/Sidebar/SidebarEntry.cjs +1 -1
- package/dist/components/Sidebar/SidebarEntry.cjs.map +1 -1
- package/dist/components/Sidebar/SidebarEntry.js +30 -29
- package/dist/components/Sidebar/SidebarEntry.js.map +1 -1
- package/dist/data/create-data-provider.cjs +1 -1
- package/dist/data/create-data-provider.cjs.map +1 -1
- package/dist/data/create-data-provider.js +1 -2
- package/dist/data/create-data-provider.js.map +1 -1
- package/dist/forms/validate-form.cjs +1 -1
- package/dist/forms/validate-form.cjs.map +1 -1
- package/dist/forms/validate-form.js +6 -1
- package/dist/forms/validate-form.js.map +1 -1
- package/dist/forms/zod-resolver.cjs +1 -1
- package/dist/forms/zod-resolver.cjs.map +1 -1
- package/dist/forms/zod-resolver.js +8 -3
- package/dist/forms/zod-resolver.js.map +1 -1
- package/dist/http/api-client.cjs +1 -1
- package/dist/http/api-client.cjs.map +1 -1
- package/dist/http/api-client.js +90 -72
- package/dist/http/api-client.js.map +1 -1
- package/dist/http/credential-scope.cjs +1 -1
- package/dist/http/credential-scope.cjs.map +1 -1
- package/dist/http/credential-scope.js +33 -9
- package/dist/http/credential-scope.js.map +1 -1
- package/dist/http/presigned-post.cjs.map +1 -1
- package/dist/http/presigned-post.js.map +1 -1
- package/dist/http/resumable-upload.cjs +1 -1
- package/dist/http/resumable-upload.cjs.map +1 -1
- package/dist/http/resumable-upload.js +167 -112
- package/dist/http/resumable-upload.js.map +1 -1
- package/dist/http/upload-with-progress.cjs +1 -1
- package/dist/http/upload-with-progress.cjs.map +1 -1
- package/dist/http/upload-with-progress.js +100 -44
- package/dist/http/upload-with-progress.js.map +1 -1
- package/dist/oauth/GoogleSignIn.cjs +1 -1
- package/dist/oauth/GoogleSignIn.cjs.map +1 -1
- package/dist/oauth/GoogleSignIn.js +5 -4
- package/dist/oauth/GoogleSignIn.js.map +1 -1
- package/dist/offline/create-offline-sync.cjs +1 -1
- package/dist/offline/create-offline-sync.cjs.map +1 -1
- package/dist/offline/create-offline-sync.js +87 -69
- package/dist/offline/create-offline-sync.js.map +1 -1
- package/dist/query/offline-persistence.cjs +1 -1
- package/dist/query/offline-persistence.cjs.map +1 -1
- package/dist/query/offline-persistence.js +67 -44
- package/dist/query/offline-persistence.js.map +1 -1
- package/dist/sse/create-event-stream.cjs +1 -1
- package/dist/sse/create-event-stream.cjs.map +1 -1
- package/dist/sse/create-event-stream.js +48 -41
- package/dist/sse/create-event-stream.js.map +1 -1
- package/dist/sse/ticket-event-stream.cjs +1 -1
- package/dist/sse/ticket-event-stream.cjs.map +1 -1
- package/dist/sse/ticket-event-stream.js +48 -19
- package/dist/sse/ticket-event-stream.js.map +1 -1
- package/dist/sse/use-event-stream.cjs +1 -1
- package/dist/sse/use-event-stream.cjs.map +1 -1
- package/dist/sse/use-event-stream.js +19 -14
- package/dist/sse/use-event-stream.js.map +1 -1
- package/dist/sw/background-sync.cjs +1 -1
- package/dist/sw/background-sync.cjs.map +1 -1
- package/dist/sw/background-sync.js +10 -8
- package/dist/sw/background-sync.js.map +1 -1
- package/dist/sw/cache.cjs +1 -1
- package/dist/sw/cache.cjs.map +1 -1
- package/dist/sw/cache.js +111 -55
- package/dist/sw/cache.js.map +1 -1
- package/dist/sw/create-push-handler.cjs +1 -1
- package/dist/sw/create-push-handler.cjs.map +1 -1
- package/dist/sw/create-push-handler.js +20 -10
- package/dist/sw/create-push-handler.js.map +1 -1
- package/dist/sw.d.ts +65 -14
- package/dist/tabular/compact.cjs +1 -1
- package/dist/tabular/compact.cjs.map +1 -1
- package/dist/tabular/compact.js +35 -16
- package/dist/tabular/compact.js.map +1 -1
- package/dist/tabular/manifest.cjs +1 -1
- package/dist/tabular/manifest.cjs.map +1 -1
- package/dist/tabular/manifest.js +33 -15
- package/dist/tabular/manifest.js.map +1 -1
- package/dist/tabular.d.ts +11 -1
- package/dist/tempest-react-sdk.cjs +1 -1
- package/dist/tempest-react-sdk.d.ts +543 -70
- package/dist/tempest-react-sdk.js +224 -224
- package/dist/theme/initial-theme.cjs +4 -4
- package/dist/theme/initial-theme.cjs.map +1 -1
- package/dist/theme/initial-theme.js +8 -5
- package/dist/theme/initial-theme.js.map +1 -1
- package/dist/utils/compressed-storage.cjs +1 -1
- package/dist/utils/compressed-storage.cjs.map +1 -1
- package/dist/utils/compressed-storage.js +13 -7
- package/dist/utils/compressed-storage.js.map +1 -1
- package/dist/utils/objects.cjs +1 -1
- package/dist/utils/objects.cjs.map +1 -1
- package/dist/utils/objects.js +13 -7
- package/dist/utils/objects.js.map +1 -1
- package/dist/utils/safe-url.cjs +1 -1
- package/dist/utils/safe-url.cjs.map +1 -1
- package/dist/utils/safe-url.js +4 -1
- package/dist/utils/safe-url.js.map +1 -1
- package/dist/vite/base-url.cjs +1 -1
- package/dist/vite/base-url.cjs.map +1 -1
- package/dist/vite/base-url.js +5 -1
- package/dist/vite/base-url.js.map +1 -1
- package/dist/vite/tempest-pwa-dev-sw.cjs +1 -1
- package/dist/vite/tempest-pwa-dev-sw.cjs.map +1 -1
- package/dist/vite/tempest-pwa-dev-sw.js +96 -31
- package/dist/vite/tempest-pwa-dev-sw.js.map +1 -1
- package/dist/vite/tempest-pwa-manifest.cjs +2 -2
- package/dist/vite/tempest-pwa-manifest.cjs.map +1 -1
- package/dist/vite/tempest-pwa-manifest.js +32 -16
- package/dist/vite/tempest-pwa-manifest.js.map +1 -1
- package/dist/vite.cjs +1 -1
- package/dist/vite.d.ts +63 -12
- package/dist/vite.js +4 -4
- package/dist/webrtc/peer-mesh.cjs +1 -1
- package/dist/webrtc/peer-mesh.cjs.map +1 -1
- package/dist/webrtc/peer-mesh.js +7 -1
- package/dist/webrtc/peer-mesh.js.map +1 -1
- package/dist/ws/create-web-socket.cjs +1 -1
- package/dist/ws/create-web-socket.cjs.map +1 -1
- package/dist/ws/create-web-socket.js +77 -61
- package/dist/ws/create-web-socket.js.map +1 -1
- package/dist/ws/use-web-socket.cjs +1 -1
- package/dist/ws/use-web-socket.cjs.map +1 -1
- package/dist/ws/use-web-socket.js +4 -3
- package/dist/ws/use-web-socket.js.map +1 -1
- package/package.json +1 -1
- package/template-pwa/src/pwa.tsx +42 -0
- package/template-pwa/src/sw.ts +5 -4
package/bin/tempest-gen.test.mjs
CHANGED
|
@@ -186,3 +186,103 @@ describe("tempest gen api / gen icons — write containment (#484)", () => {
|
|
|
186
186
|
expect(filesUnder(outside)).toEqual([]);
|
|
187
187
|
});
|
|
188
188
|
});
|
|
189
|
+
|
|
190
|
+
describe("tempest gen api — spec text never drives the terminal (#533)", () => {
|
|
191
|
+
const TERMINAL_CONTROL = /[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/;
|
|
192
|
+
const SGR = /\u001b\[\d+(?:;\d+)*m/g;
|
|
193
|
+
let cwd;
|
|
194
|
+
|
|
195
|
+
beforeEach(() => {
|
|
196
|
+
cwd = mkdtempSync(join(tmpdir(), "tempest-gen-tty-"));
|
|
197
|
+
mkdirSync(join(cwd, "src"));
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
afterEach(() => {
|
|
201
|
+
rmSync(cwd, { recursive: true, force: true });
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Write `doc` as the project's spec and run `tempest gen api` on it.
|
|
206
|
+
*
|
|
207
|
+
* @param {object} doc - The OpenAPI document.
|
|
208
|
+
* @returns {{ status: number | null, out: string }} Exit code and the
|
|
209
|
+
* combined output with the CLI's own color codes removed.
|
|
210
|
+
*/
|
|
211
|
+
function genApi(doc) {
|
|
212
|
+
writeFileSync(join(cwd, "spec.json"), JSON.stringify(doc));
|
|
213
|
+
const result = spawnSync(process.execPath, [CLI, "gen", "api", "spec.json"], {
|
|
214
|
+
cwd,
|
|
215
|
+
encoding: "utf8",
|
|
216
|
+
});
|
|
217
|
+
return { status: result.status, out: (result.stdout + result.stderr).replace(SGR, "") };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
it("a tag carrying OSC, CSI and carriage-return sequences prints without them", () => {
|
|
221
|
+
const tag = "users\u001b]0;PWNED\u0007\u001b]8;;https://evil/\u001b\\x\u009b2J\r";
|
|
222
|
+
const { status, out } = genApi({
|
|
223
|
+
...SPEC,
|
|
224
|
+
paths: {
|
|
225
|
+
"/users": { get: { tags: [tag], responses: { 200: { description: "ok" } } } },
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
expect(status).toBe(0);
|
|
230
|
+
expect(out).not.toMatch(TERMINAL_CONTROL);
|
|
231
|
+
expect(out).not.toContain("\u001b]");
|
|
232
|
+
expect(out).toContain("users]0;PWNED]8;;https://evil/\\x2J");
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it("the usage hint names the folder and class that were generated", () => {
|
|
236
|
+
const { out } = genApi({
|
|
237
|
+
...SPEC,
|
|
238
|
+
paths: {
|
|
239
|
+
"/users": {
|
|
240
|
+
get: { tags: ["User Profiles"], responses: { 200: { description: "ok" } } },
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
expect(out).toContain('import { UserProfilesService } from "@/api/user-profiles";');
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it("a refusal that quotes a C1 control from the spec prints without it", () => {
|
|
249
|
+
const { status, out } = genApi({
|
|
250
|
+
...SPEC,
|
|
251
|
+
paths: { "https://x\u009b2J/": { get: { responses: { 200: { description: "ok" } } } } },
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
expect(status).toBe(1);
|
|
255
|
+
expect(out).toMatch(/does not start with "\/"/);
|
|
256
|
+
expect(out).not.toMatch(TERMINAL_CONTROL);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it("a nested-quantifier pattern is printed as a warning and the files are still written", () => {
|
|
260
|
+
const { status, out } = genApi({
|
|
261
|
+
...SPEC,
|
|
262
|
+
components: {
|
|
263
|
+
schemas: {
|
|
264
|
+
Q: { type: "object", properties: { s: { type: "string", pattern: "(a+)+" } } },
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
paths: {
|
|
268
|
+
"/users": {
|
|
269
|
+
post: {
|
|
270
|
+
tags: ["users"],
|
|
271
|
+
requestBody: {
|
|
272
|
+
content: {
|
|
273
|
+
"application/json": { schema: { $ref: "#/components/schemas/Q" } },
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
responses: { 200: { description: "ok" } },
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
expect(status).toBe(0);
|
|
283
|
+
expect(out).toMatch(
|
|
284
|
+
/! OpenAPI schema "Q" at properties\.s: pattern "\(a\+\)\+" repeats a group/,
|
|
285
|
+
);
|
|
286
|
+
expect(existsSync(join(cwd, "src", "api", "users", "schemas.ts"))).toBe(true);
|
|
287
|
+
});
|
|
288
|
+
});
|
package/bin/tempest.mjs
CHANGED
|
@@ -27,7 +27,9 @@ import {
|
|
|
27
27
|
} from "./lib/css/index.mjs";
|
|
28
28
|
import { analyzeDesign } from "./lib/design/index.mjs";
|
|
29
29
|
import { checkLucide } from "./lib/doctor/lucide.mjs";
|
|
30
|
+
import { checkVitest, VITEST_CONFIG_FILES } from "./lib/doctor/vitest.mjs";
|
|
30
31
|
import { generateRegistry, loadIconTables } from "./lib/icons/generate.mjs";
|
|
32
|
+
import { terminalText } from "./lib/openapi/escape.mjs";
|
|
31
33
|
import { generate } from "./lib/openapi/generate.mjs";
|
|
32
34
|
import { loadSpec } from "./lib/openapi/load.mjs";
|
|
33
35
|
|
|
@@ -789,6 +791,18 @@ function doctor(args = []) {
|
|
|
789
791
|
],
|
|
790
792
|
);
|
|
791
793
|
}
|
|
794
|
+
if (usesSdk) {
|
|
795
|
+
checks.push(
|
|
796
|
+
...checkVitest({
|
|
797
|
+
vitestInstalled: Boolean(installedVersion("vitest")),
|
|
798
|
+
configs: Object.fromEntries(
|
|
799
|
+
VITEST_CONFIG_FILES.filter((name) => existsSync(join(ROOT, name))).map(
|
|
800
|
+
(name) => [name, readFileSync(join(ROOT, name), "utf8")],
|
|
801
|
+
),
|
|
802
|
+
),
|
|
803
|
+
}),
|
|
804
|
+
);
|
|
805
|
+
}
|
|
792
806
|
const entry = firstExisting(["src/main.tsx", "src/main.ts", "src/index.tsx", "src/index.ts"]);
|
|
793
807
|
if (usesSdk) {
|
|
794
808
|
// Without the stylesheet every component renders unstyled, so for an SDK
|
|
@@ -1330,7 +1344,7 @@ async function genApi(args) {
|
|
|
1330
1344
|
}
|
|
1331
1345
|
console.log(`${c.dim}→ loading ${source}${c.reset}`);
|
|
1332
1346
|
const doc = await loadSpec(source);
|
|
1333
|
-
const { files, tags } = generate(doc);
|
|
1347
|
+
const { files, tags, services, warnings } = generate(doc);
|
|
1334
1348
|
|
|
1335
1349
|
const targets = Object.entries(files).map(([rel, contents]) => ({
|
|
1336
1350
|
dest: join(outDir, rel),
|
|
@@ -1349,8 +1363,11 @@ async function genApi(args) {
|
|
|
1349
1363
|
}
|
|
1350
1364
|
|
|
1351
1365
|
console.log(
|
|
1352
|
-
`\n${c.green}✓ Generated${c.reset} ${Object.keys(files).length} files for ${tags.length} route group(s): ${c.bold}${tags.join(", ")}${c.reset}`,
|
|
1366
|
+
`\n${c.green}✓ Generated${c.reset} ${Object.keys(files).length} files for ${tags.length} route group(s): ${c.bold}${terminalText(tags.join(", "))}${c.reset}`,
|
|
1353
1367
|
);
|
|
1368
|
+
for (const warning of warnings) {
|
|
1369
|
+
console.log(`${c.yellow}! ${terminalText(warning)}${c.reset}`);
|
|
1370
|
+
}
|
|
1354
1371
|
console.log(` ${c.dim}out: ${outDir}${c.reset}`);
|
|
1355
1372
|
const prettier = localBin("prettier");
|
|
1356
1373
|
if (prettier) {
|
|
@@ -1359,12 +1376,11 @@ async function genApi(args) {
|
|
|
1359
1376
|
}
|
|
1360
1377
|
console.log(`\n${c.dim}Inject an ApiClient into a service:${c.reset}`);
|
|
1361
1378
|
console.log(` import { createApiClient } from "tempest-react-sdk";`);
|
|
1362
|
-
if (
|
|
1363
|
-
const
|
|
1364
|
-
|
|
1365
|
-
console.log(` import { ${Cls} } from "@/api/${tags[0].toLowerCase()}";`);
|
|
1379
|
+
if (services[0]) {
|
|
1380
|
+
const { slug, className } = services[0];
|
|
1381
|
+
console.log(` import { ${className} } from "@/api/${slug}";`);
|
|
1366
1382
|
console.log(
|
|
1367
|
-
` const svc = new ${
|
|
1383
|
+
` const svc = new ${className}(createApiClient({ baseURL: import.meta.env.VITE_API_URL }));`,
|
|
1368
1384
|
);
|
|
1369
1385
|
}
|
|
1370
1386
|
return 0;
|
|
@@ -1721,7 +1737,7 @@ if (result instanceof Promise) {
|
|
|
1721
1737
|
.then((code) => process.exit(code))
|
|
1722
1738
|
.catch((err) => {
|
|
1723
1739
|
console.error(
|
|
1724
|
-
`${c.red}✗ ${err instanceof Error ? err.message : String(err)}${c.reset}`,
|
|
1740
|
+
`${c.red}✗ ${terminalText(err instanceof Error ? err.message : String(err))}${c.reset}`,
|
|
1725
1741
|
);
|
|
1726
1742
|
process.exit(1);
|
|
1727
1743
|
});
|
package/dist/access/use-can.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("./access-control-context.cjs");let t=require("react");function
|
|
1
|
+
const e=require("./access-control-context.cjs");let t=require("react");var n={allowed:!1,isLoading:!0},r={allowed:!0,isLoading:!1};function i(e){return typeof e==`boolean`?{can:e}:e}function a(a){let o=e.useAccessControl(),s=JSON.stringify(a),[c,l]=(0,t.useState)(null);return(0,t.useEffect)(()=>{if(!o)return;let e=!1,t=t=>{e||l({control:o,key:s,result:t})};return Promise.resolve(o.can(a)).then(e=>{let n=i(e);t({allowed:n.can,isLoading:!1,reason:n.reason})}).catch(e=>{t({allowed:!1,isLoading:!1,reason:e instanceof Error?e.message:`access check failed`})}),()=>{e=!0}},[o,s]),o?c===null||c.control!==o||c.key!==s?n:c.result:r}exports.useCan=a;
|
|
2
2
|
//# sourceMappingURL=use-can.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-can.cjs","names":["useAccessControl","useState","require_access_control_context","react","normalize","result","useCan","control","paramsKey","params","
|
|
1
|
+
{"version":3,"file":"use-can.cjs","names":["useAccessControl","useState","require_access_control_context","react","PENDING","ALLOW_ALL","normalize","result","useCan","control","paramsKey","params","resolved","setResolved","cancelled","settle","raw","error"],"sources":["../../src/access/use-can.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { useAccessControl } from \"./access-control-context\";\nimport type { AccessControl, CanParams, CanResult } from \"./types\";\n\nexport interface UseCanResult {\n /** Whether the action is permitted. Defaults to `true` while loading is not the concern of the caller. */\n allowed: boolean;\n /** `true` while an async `can` check is pending. */\n isLoading: boolean;\n /** Optional explanation, typically present when `allowed` is `false`. */\n reason?: string;\n}\n\n/** The answer for a check that has not resolved yet. */\nconst PENDING: UseCanResult = { allowed: false, isLoading: true };\n\n/** The answer when no provider is mounted. */\nconst ALLOW_ALL: UseCanResult = { allowed: true, isLoading: false };\n\n/** A resolved answer together with the check it answers. */\ninterface ResolvedCheck {\n control: AccessControl;\n key: string;\n result: UseCanResult;\n}\n\nfunction normalize(result: boolean | CanResult): CanResult {\n return typeof result === \"boolean\" ? { can: result } : result;\n}\n\n/**\n * Resolve an access check against the {@link AccessControl} in context.\n *\n * Handles sync booleans, sync {@link CanResult}, and promises of either. When\n * no provider is present, access is allowed (see `useAccessControl`). The check\n * re-runs whenever the params change; they are compared by their\n * `JSON.stringify` form, so an inline object literal does not re-run it on\n * every render.\n *\n * A resolved answer is stored with the control and the params it answers, and\n * is only returned while both still match. Until 0.81.0 the previous answer was\n * returned with `isLoading: false` for the render between a params change and\n * the effect that re-ran the check, so `<Can>` showed the children for the new\n * resource for one frame on the strength of the old resource's permission. The\n * first render for new params now reads `{ allowed: false, isLoading: true }`.\n *\n * @param params - The action/resource being checked.\n * @returns The answer for exactly these params, or the pending state.\n */\nexport function useCan(params: CanParams): UseCanResult {\n const control = useAccessControl();\n const paramsKey = JSON.stringify(params);\n const [resolved, setResolved] = useState<ResolvedCheck | null>(null);\n\n useEffect(() => {\n if (!control) return;\n let cancelled = false;\n const settle = (result: UseCanResult): void => {\n if (!cancelled) setResolved({ control, key: paramsKey, result });\n };\n\n Promise.resolve(control.can(params))\n .then((raw) => {\n const result = normalize(raw);\n settle({ allowed: result.can, isLoading: false, reason: result.reason });\n })\n .catch((error: unknown) => {\n settle({\n allowed: false,\n isLoading: false,\n reason: error instanceof Error ? error.message : \"access check failed\",\n });\n });\n\n return () => {\n cancelled = true;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [control, paramsKey]);\n\n if (!control) return ALLOW_ALL;\n if (resolved === null || resolved.control !== control || resolved.key !== paramsKey) {\n return PENDING;\n }\n return resolved.result;\n}\n"],"mappings":"A,uEAcA,IAAMI,EAAwB,CAAE,QAAS,GAAO,UAAW,EAAK,EAG1DC,EAA0B,CAAE,QAAS,GAAM,UAAW,EAAM,EASlE,SAASC,EAAU,EAAwC,CACvD,OAAO,OAAOC,GAAW,UAAY,CAAE,IAAKA,CAAO,EAAIA,CAC3D,CAqBA,SAAgBC,EAAO,EAAiC,CACpD,IAAMC,EAAUT,EAAAA,iBAAiB,EAC3BU,EAAY,KAAK,UAAUC,CAAM,EACjC,CAACC,EAAUC,IAAA,EAAeZ,EAAAA,SAAAA,CAA+B,IAAI,EAgCnE,OA9BA,EAAAE,EAAA,UAAA,KAAgB,CACZ,GAAI,CAACM,EAAS,OACd,IAAIK,EAAY,GACVC,EAAU,GAA+B,CACtCD,GAAWD,EAAY,CAAE,UAAS,IAAKH,EAAW,QAAO,CAAC,CACnE,EAeA,OAbA,QAAQ,QAAQD,EAAQ,IAAIE,CAAM,CAAC,CAAC,CAC/B,KAAM,GAAQ,CACX,IAAMJ,EAASD,EAAUU,CAAG,EAC5BD,EAAO,CAAE,QAASR,EAAO,IAAK,UAAW,GAAO,OAAQA,EAAO,MAAO,CAAC,CAC3E,CAAC,CAAC,CACD,MAAO,GAAmB,CACvBQ,EAAO,CACH,QAAS,GACT,UAAW,GACX,OAAQE,aAAiB,MAAQA,EAAM,QAAU,qBACrD,CAAC,CACL,CAAC,MAEQ,CACT,EAAY,EAChB,CAEJ,EAAG,CAACR,EAASC,CAAS,CAAC,EAElBD,EACDG,IAAa,MAAQA,EAAS,UAAYH,GAAWG,EAAS,MAAQF,EAC/DN,EAEJQ,EAAS,OAJKP,CAKzB,C"}
|
package/dist/access/use-can.js
CHANGED
|
@@ -1,49 +1,46 @@
|
|
|
1
1
|
import { useAccessControl as e } from "./access-control-context.js";
|
|
2
2
|
import { useEffect as t, useState as n } from "react";
|
|
3
3
|
//#region src/access/use-can.ts
|
|
4
|
-
|
|
4
|
+
var r = {
|
|
5
|
+
allowed: !1,
|
|
6
|
+
isLoading: !0
|
|
7
|
+
}, i = {
|
|
8
|
+
allowed: !0,
|
|
9
|
+
isLoading: !1
|
|
10
|
+
};
|
|
11
|
+
function a(e) {
|
|
5
12
|
return typeof e == "boolean" ? { can: e } : e;
|
|
6
13
|
}
|
|
7
|
-
function
|
|
8
|
-
let
|
|
9
|
-
allowed: !1,
|
|
10
|
-
isLoading: !0
|
|
11
|
-
} : {
|
|
12
|
-
allowed: !0,
|
|
13
|
-
isLoading: !1
|
|
14
|
-
});
|
|
14
|
+
function o(o) {
|
|
15
|
+
let s = e(), c = JSON.stringify(o), [l, u] = n(null);
|
|
15
16
|
return t(() => {
|
|
16
|
-
if (!
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
if (!s) return;
|
|
18
|
+
let e = !1, t = (t) => {
|
|
19
|
+
e || u({
|
|
20
|
+
control: s,
|
|
21
|
+
key: c,
|
|
22
|
+
result: t
|
|
20
23
|
});
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
...e,
|
|
26
|
-
isLoading: !0
|
|
27
|
-
})), Promise.resolve(a.can(i)).then((t) => {
|
|
28
|
-
if (e) return;
|
|
29
|
-
let n = r(t);
|
|
30
|
-
c({
|
|
24
|
+
};
|
|
25
|
+
return Promise.resolve(s.can(o)).then((e) => {
|
|
26
|
+
let n = a(e);
|
|
27
|
+
t({
|
|
31
28
|
allowed: n.can,
|
|
32
29
|
isLoading: !1,
|
|
33
30
|
reason: n.reason
|
|
34
31
|
});
|
|
35
|
-
}).catch((
|
|
36
|
-
|
|
32
|
+
}).catch((e) => {
|
|
33
|
+
t({
|
|
37
34
|
allowed: !1,
|
|
38
35
|
isLoading: !1,
|
|
39
|
-
reason:
|
|
36
|
+
reason: e instanceof Error ? e.message : "access check failed"
|
|
40
37
|
});
|
|
41
38
|
}), () => {
|
|
42
39
|
e = !0;
|
|
43
40
|
};
|
|
44
|
-
}, [
|
|
41
|
+
}, [s, c]), s ? l === null || l.control !== s || l.key !== c ? r : l.result : i;
|
|
45
42
|
}
|
|
46
43
|
//#endregion
|
|
47
|
-
export {
|
|
44
|
+
export { o as useCan };
|
|
48
45
|
|
|
49
46
|
//# sourceMappingURL=use-can.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-can.js","names":["normalize","result","useCan","control","useAccessControl","paramsKey","params","
|
|
1
|
+
{"version":3,"file":"use-can.js","names":["PENDING","ALLOW_ALL","normalize","result","useCan","control","useAccessControl","paramsKey","params","resolved","setResolved","useState","useEffect","cancelled","settle","raw","error"],"sources":["../../src/access/use-can.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { useAccessControl } from \"./access-control-context\";\nimport type { AccessControl, CanParams, CanResult } from \"./types\";\n\nexport interface UseCanResult {\n /** Whether the action is permitted. Defaults to `true` while loading is not the concern of the caller. */\n allowed: boolean;\n /** `true` while an async `can` check is pending. */\n isLoading: boolean;\n /** Optional explanation, typically present when `allowed` is `false`. */\n reason?: string;\n}\n\n/** The answer for a check that has not resolved yet. */\nconst PENDING: UseCanResult = { allowed: false, isLoading: true };\n\n/** The answer when no provider is mounted. */\nconst ALLOW_ALL: UseCanResult = { allowed: true, isLoading: false };\n\n/** A resolved answer together with the check it answers. */\ninterface ResolvedCheck {\n control: AccessControl;\n key: string;\n result: UseCanResult;\n}\n\nfunction normalize(result: boolean | CanResult): CanResult {\n return typeof result === \"boolean\" ? { can: result } : result;\n}\n\n/**\n * Resolve an access check against the {@link AccessControl} in context.\n *\n * Handles sync booleans, sync {@link CanResult}, and promises of either. When\n * no provider is present, access is allowed (see `useAccessControl`). The check\n * re-runs whenever the params change; they are compared by their\n * `JSON.stringify` form, so an inline object literal does not re-run it on\n * every render.\n *\n * A resolved answer is stored with the control and the params it answers, and\n * is only returned while both still match. Until 0.81.0 the previous answer was\n * returned with `isLoading: false` for the render between a params change and\n * the effect that re-ran the check, so `<Can>` showed the children for the new\n * resource for one frame on the strength of the old resource's permission. The\n * first render for new params now reads `{ allowed: false, isLoading: true }`.\n *\n * @param params - The action/resource being checked.\n * @returns The answer for exactly these params, or the pending state.\n */\nexport function useCan(params: CanParams): UseCanResult {\n const control = useAccessControl();\n const paramsKey = JSON.stringify(params);\n const [resolved, setResolved] = useState<ResolvedCheck | null>(null);\n\n useEffect(() => {\n if (!control) return;\n let cancelled = false;\n const settle = (result: UseCanResult): void => {\n if (!cancelled) setResolved({ control, key: paramsKey, result });\n };\n\n Promise.resolve(control.can(params))\n .then((raw) => {\n const result = normalize(raw);\n settle({ allowed: result.can, isLoading: false, reason: result.reason });\n })\n .catch((error: unknown) => {\n settle({\n allowed: false,\n isLoading: false,\n reason: error instanceof Error ? error.message : \"access check failed\",\n });\n });\n\n return () => {\n cancelled = true;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [control, paramsKey]);\n\n if (!control) return ALLOW_ALL;\n if (resolved === null || resolved.control !== control || resolved.key !== paramsKey) {\n return PENDING;\n }\n return resolved.result;\n}\n"],"mappings":";;;AAcA,IAAMA,IAAwB;CAAE,SAAS;CAAO,WAAW;AAAK,GAG1DC,IAA0B;CAAE,SAAS;CAAM,WAAW;AAAM;AASlE,SAASC,EAAU,GAAwC;CACvD,OAAO,OAAOC,KAAW,YAAY,EAAE,KAAKA,EAAO,IAAIA;AAC3D;AAqBA,SAAgBC,EAAO,GAAiC;CACpD,IAAMC,IAAUC,EAAiB,GAC3BC,IAAY,KAAK,UAAUC,CAAM,GACjC,CAACC,GAAUC,KAAeC,EAA+B,IAAI;CAgCnE,OA9BAC,QAAgB;EACZ,IAAI,CAACP,GAAS;EACd,IAAIQ,IAAY,IACVC,KAAU,MAA+B;GAC3C,AAAKD,KAAWH,EAAY;IAAE;IAAS,KAAKH;IAAW;GAAO,CAAC;EACnE;EAeA,OAbA,QAAQ,QAAQF,EAAQ,IAAIG,CAAM,CAAC,CAAC,CAC/B,MAAM,MAAQ;GACX,IAAML,IAASD,EAAUa,CAAG;GAC5B,EAAO;IAAE,SAASZ,EAAO;IAAK,WAAW;IAAO,QAAQA,EAAO;GAAO,CAAC;EAC3E,CAAC,CAAC,CACD,OAAO,MAAmB;GACvB,EAAO;IACH,SAAS;IACT,WAAW;IACX,QAAQa,aAAiB,QAAQA,EAAM,UAAU;GACrD,CAAC;EACL,CAAC,SAEQ;GACT,IAAY;EAChB;CAEJ,GAAG,CAACX,GAASE,CAAS,CAAC,GAElBF,IACDI,MAAa,QAAQA,EAAS,YAAYJ,KAAWI,EAAS,QAAQF,IAC/DP,IAEJS,EAAS,SAJKR;AAKzB"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../utils/dev-mode.cjs"),t=require("../http/errors.cjs"),n=require("../http/api-client.cjs"),r=require("./create-auth-store.cjs"),
|
|
1
|
+
const e=require("../utils/dev-mode.cjs"),t=require("../http/errors.cjs"),n=require("../http/api-client.cjs"),r=require("./session-identity.cjs"),i=require("./create-auth-store.cjs"),a=require("./refresh-queue.cjs");var o=1e4,s=1e4;function c(e,t){return e.length===0?Promise.resolve():new Promise(n=>{let r=setTimeout(n,t);Promise.allSettled(e).then(()=>{clearTimeout(r),n()})})}function l(){let e=globalThis.crypto;return typeof e?.randomUUID==`function`?e.randomUUID():`${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}var u=new Set;function ee(t){e.isDevBuild()&&!u.has(t)&&(u.add(t),console.warn(`[tempest-react-sdk] createTempestAuth: withCredentials is set without logoutPath. logout() cannot clear the HttpOnly refresh cookie, so the next refresh() — the boot restore — signs the same account back in. Pass logoutPath (the backend's cookie logout route) so the server revokes it.`))}function te(e){let t=e??{};return{token:t.access_token,refreshToken:t.refresh_token}}function d(u){let{baseURL:d,prefix:f,loginPath:ne=`/api/auth/login`,refreshPath:re=`/api/auth/refresh`,logoutPath:p,onLogoutError:ie,mePath:m,storeName:h=`tempest-auth`,storage:g=`local`,withCredentials:_=!1,csrf:ae,fetcher:v,parseTokens:y=te,parseUser:oe,refreshBody:b=e=>e?{refresh_token:e}:void 0,retry:se,redirectTo:x,onSessionEnd:S}=u;_&&!p&&ee(h);let C=i.createAuthStore({name:h,storage:g}),w=`${h}-refresh`;function T(){return typeof window>`u`?null:g===`session`?window.sessionStorage:window.localStorage}function E(){return T()?.getItem(w)??null}let D=E();function O(e){D=e;let t=T();t&&(e?t.setItem(w,e):t.removeItem(w))}let k=`${h}-lineage`,ce=`tempest-react-sdk:${h}:refresh`,A=T()?.getItem(k)??null;function j(e){A=e;let t=T();t&&(e?t.setItem(k,e):t.removeItem(k))}let M=()=>C.getState(),N=()=>M().token;async function le(){if(!m)return M().user;let e=P,n;try{n=await Q.get(m,{skipAuthRetry:!0})}catch(n){throw P===e?n:new t.SessionSupersededError}if(P!==e||!Z().continues)throw new t.SessionSupersededError;return M().setUser(n),n}let P=0,ue=Promise.resolve(),F=0,I=new Set,L=new Set,R=new Set,z=new Set;function B(e){let t=e.then(()=>void 0,()=>void 0);return R.add(t),t.then(()=>R.delete(t)),e}function de(t){e.isDevBuild()&&console.warn(`[tempest-react-sdk] createTempestAuth: logoutPath revocation failed`,t);try{ie?.(t)}catch{return}}function V(e){let t=(async()=>{try{await e()}catch(e){de(e)}})();return L.add(t),t.then(()=>L.delete(t)),t}function H(e,t){p&&!_&&t&&V(()=>U(e).post(p,{body:b(t)}))}function U(e){return n.createApiClient({baseURL:d,prefix:f,withCredentials:_,csrf:ae,fetcher:v,getToken:()=>e})}function W(){P+=1,K=a.createRefreshQueue(G)}async function G(){let e=N(),t=P;z.add(t);try{await fe(()=>pe(e))}finally{z.delete(t)}}async function fe(e){let t=g===`local`&&typeof navigator<`u`?navigator.locks:void 0;if(!t)return e();let n=new AbortController,r=setTimeout(()=>n.abort(),s),i=!1;try{await t.request(ce,{signal:n.signal},async()=>{i=!0,clearTimeout(r),await e()})}catch(t){if(i)throw t;clearTimeout(r),await e()}}async function pe(e){let n=e!==null||M().user!==null||D!==null,r=Z();if(!n&&N()!==null)return;if(!r.continues)throw new t.SessionSupersededError;if(N()!==e)return;let i=P,a;try{a=await B(Q.post(re,{body:b(E()),skipAuth:!0}))}catch(e){throw P===i?e:new t.SessionSupersededError}let{token:o,refreshToken:s}=y(a);if(P!==i||!Z().continues)throw H(o??null,s),new t.SessionSupersededError;n||j(l()),M().setToken(o),s&&O(s)}let K=a.createRefreshQueue(G);function q(e){let t=N();return t===null?!1:e.token===null||e.token!==t}function J(){return N()===null&&E()===null}async function me(){if(!(I.size>0&&F===0&&(await Promise.allSettled([...I]),N()!==null)))throw Error(`anonymous 401 with no session`)}async function he(e){if(e.epoch!==null&&e.epoch!==P)throw new t.SessionSupersededError;if(q(e)){if(e.token===null)throw Error(`anonymous 401 under a live session`)}else return e.token===null&&J()?z.has(P)?K():me():K()}async function ge(e,n){if(n.epoch!==null&&n.epoch!==P||!Z().continues)throw new t.SessionSupersededError;q(n)||n.token===null&&J()||await Se()}async function _e(e){L.size>0&&await c([...L],o);let t=ve(e);I.add(t);try{return await t}finally{I.delete(t)}}async function ve(e){let n=P,r=await B(Q.post(ne,{body:e,skipAuth:!0})),{token:i,refreshToken:a}=y(r);if(P!==n)throw H(i??null,a),new t.SessionSupersededError;H(N(),E()??void 0);let{done:o}=Y(`switch`),s=P;if(await o.catch(()=>void 0),P!==s)throw new t.SessionSupersededError;j(l()),M().setToken(i),O(a??null);let c=oe?.(r);return c==null?le():(M().setUser(c),c)}function Y(e){let{user:t,token:n}=M(),r=n!==null||t!==null||E()!==null;return(r||e!==`expired`)&&W(),M().logout(),O(null),j(null),r?{live:r,done:X({reason:e,user:t,token:n,elsewhere:!1})}:{live:r,done:e===`switch`||F===0?ue:Promise.resolve()}}function X(e){if(!S)return Promise.resolve();let t=U(e.token);F+=1;let n=(async()=>{try{await S({...e,api:t})}finally{--F}})();return ue=n.then(()=>void 0,()=>void 0),n.then(()=>void 0)}function ye(){let e=T();if(!e)return null;let t=e.getItem(w),n=e.getItem(k),r=e.getItem(h);if(!r)return{user:null,token:null,refreshToken:t,lineage:n};try{let e=JSON.parse(r);return{user:e.state?.user??null,token:e.state?.token??null,refreshToken:t,lineage:n}}catch{return{user:null,token:null,refreshToken:t,lineage:n}}}function Z(){let e=ye(),{user:t,token:n}=M();if(e===null)return{continues:!0,done:Promise.resolve()};let r=JSON.stringify(e.user)===JSON.stringify(t);if(e.token===n&&e.refreshToken===D&&e.lineage===A&&r)return{continues:!0,done:Promise.resolve()};let i=e.lineage!==null||A!==null?e.lineage===A:e.token===n||n!==null&&r,a=e.token!==null&&i,o=n!==null||t!==null||D!==null;if(a||W(),D=e.refreshToken,A=e.lineage,C.setState({user:e.user,token:e.token,isAuthenticated:e.token!==null}),a||!o)return{continues:a,done:Promise.resolve()};let s=X({reason:e.token===null?`logout`:`switch`,user:t,token:n,elsewhere:!0});return s.catch(()=>void 0),{continues:a,done:s}}function be(){if(!p)return null;let e=p,{token:t,user:n}=M(),r=E();if(!_&&t===null&&n===null&&r===null)return null;let i=_?[...R]:[];return V(async()=>(await c(i,o),U(t).post(e,{body:b(r)})))}function xe(){let e=Z();if(!e.continues)return e.done;let t=be(),{done:n}=Y(`logout`);return t?Promise.all([t,n]).then(()=>void 0):n}async function Se(){let{live:e,done:t}=Y(`expired`);await t.catch(()=>void 0),e&&x&&typeof window<`u`&&window.location.assign(x)}let Q=n.createApiClient({baseURL:d,prefix:f,withCredentials:_,csrf:ae,fetcher:v,getToken:N,getSessionEpoch:()=>P,refresh:he,retry:se,onUnauthorized:ge}),Ce=()=>K(),$=null,we=()=>N()===null?null:A===null?($?.epoch!==P&&($={epoch:P,key:l()}),$.key):A,Te={current:we,subscribe:e=>C.subscribe(e)};r.bindSessionIdentity(Q,Te),r.bindSessionIdentity(N,Te);function Ee(e){e.storageArea===T()&&(e.key===null||e.key===h||e.key===w||e.key===k)&&Z()}let De=typeof window>`u`?null:window;return De?.addEventListener(`storage`,Ee),{useAuthStore:C,api:Q,login:_e,logout:xe,refresh:Ce,getToken:N,sessionKey:we,dispose:()=>De?.removeEventListener(`storage`,Ee)}}exports.createTempestAuth=d;
|
|
2
2
|
//# sourceMappingURL=create-tempest-auth.cjs.map
|