@jskit-ai/agent-docs 0.1.279 → 0.1.281
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/guide/agent/app-extras/assistant-conversation.md +5 -1
- package/guide/agent/app-extras/assistant.md +123 -0
- package/guide/agent/app-extras/realtime.md +20 -10
- package/guide/agent/app-setup/authentication.md +54 -4
- package/guide/agent/app-setup/database-layer.md +108 -35
- package/guide/agent/app-setup/existing-application-migration.md +7 -1
- package/guide/agent/app-setup/material-3-migration.md +38 -9
- package/guide/agent/app-setup/multi-homing.md +14 -4
- package/guide/agent/app-setup/upgrading-jskit.md +105 -0
- package/guide/agent/app-setup/users.md +9 -1
- package/guide/agent/framework/client-features.md +35 -1
- package/guide/agent/framework/material-3.md +111 -71
- package/guide/agent/framework/sanity-checks.md +181 -0
- package/guide/agent/framework/translation.md +4 -0
- package/guide/agent/framework/typed-features.md +222 -120
- package/guide/agent/framework/ui-operations.md +3 -1
- package/guide/agent/index.md +3 -0
- package/package.json +2 -2
- package/patterns/feature-package/example/booking-engine/package.json +1 -1
- package/patterns/minimal-foundation/PATTERN.md +6 -2
- package/patterns/minimal-foundation/example/AGENTS.md +22 -5
- package/patterns/minimal-foundation/example/package.json +6 -6
- package/patterns/minimal-foundation/example/playwright.config.mjs +3 -30
- package/patterns/minimal-foundation/example/server.js +7 -1
- package/patterns/minimal-foundation/example/src/main.js +3 -2
- package/patterns/minimal-foundation/example/tests/e2e/startup.spec.ts +13 -0
- package/patterns/minimal-foundation/example/vite.config.mjs +3 -1
- package/patterns/shell-foundation/PATTERN.md +4 -1
- package/patterns/shell-foundation/example/AGENTS.md +22 -5
- package/patterns/shell-foundation/example/package.json +7 -7
- package/patterns/shell-foundation/example/packages/main/package.json +1 -1
- package/patterns/shell-foundation/example/packages/main/src/client/providers/MainClientProvider.js +3 -0
- package/patterns/shell-foundation/example/playwright.config.mjs +3 -30
- package/patterns/shell-foundation/example/server.js +7 -1
- package/patterns/shell-foundation/example/src/components/menus/SectionTabLinkItem.vue +42 -0
- package/patterns/shell-foundation/example/src/main.js +3 -2
- package/patterns/shell-foundation/example/src/pages/home.vue +1 -1
- package/patterns/shell-foundation/example/src/views/NotFound.vue +1 -1
- package/patterns/shell-foundation/example/tests/e2e/startup.spec.ts +13 -0
- package/patterns/shell-foundation/example/vite.config.mjs +3 -1
- package/reference/autogen/PATTERN_INDEX.md +37 -37
- package/reference/autogen/packages/agent-docs.md +4 -0
- package/reference/autogen/packages/architecture-inspector.md +1 -0
- package/reference/autogen/packages/assistant-runtime.md +28 -3
- package/reference/autogen/packages/auth-provider-local-core.md +2 -1
- package/reference/autogen/packages/auth-web.md +10 -0
- package/reference/autogen/packages/connectors-catalog.md +8 -0
- package/reference/autogen/packages/crud-core.md +3 -0
- package/reference/autogen/packages/database-runtime.md +33 -0
- package/reference/autogen/packages/http-web.md +0 -4
- package/reference/autogen/packages/json-rest-api-core.md +17 -4
- package/reference/autogen/packages/kernel.md +37 -2
- package/reference/autogen/packages/material-web.md +2 -1
- package/reference/autogen/packages/oidc-issuer-core.md +0 -1
- package/reference/autogen/packages/realtime.md +8 -5
- package/reference/autogen/packages/shell-web.md +88 -3
- package/reference/autogen/packages/users-web.md +5 -2
- package/reference/autogen/packages/workspaces-core.md +3 -1
- package/reference/autogen/tooling/jskit-catalog.md +122 -5
- package/reference/autogen/tooling/testUtils.md +15 -1
- package/skills/jskit/SKILL.md +17 -5
- package/skills/jskit/references/client-features.md +35 -1
- package/skills/jskit/references/existing-application-migration.md +7 -1
- package/skills/jskit/references/heuristics/INDEX.md +3 -1
- package/skills/jskit/references/heuristics/row-policies.md +3 -3
- package/skills/jskit/references/heuristics/typed-features.md +7 -7
- package/skills/jskit/references/heuristics/ui-testing.md +27 -21
- package/skills/jskit/references/material-3.md +111 -71
- package/skills/jskit/references/multi-homing.md +14 -4
- package/skills/jskit/references/pattern-index.md +37 -37
- package/skills/jskit/references/patterns/app/minimal-foundation/PATTERN.md +6 -2
- package/skills/jskit/references/patterns/app/minimal-foundation/example/AGENTS.md +22 -5
- package/skills/jskit/references/patterns/app/minimal-foundation/example/package.json +6 -6
- package/skills/jskit/references/patterns/app/minimal-foundation/example/playwright.config.mjs +3 -30
- package/skills/jskit/references/patterns/app/minimal-foundation/example/server.js +7 -1
- package/skills/jskit/references/patterns/app/minimal-foundation/example/src/main.js +3 -2
- package/skills/jskit/references/patterns/app/minimal-foundation/example/tests/e2e/startup.spec.ts +13 -0
- package/skills/jskit/references/patterns/app/minimal-foundation/example/vite.config.mjs +3 -1
- package/skills/jskit/references/patterns/app/shell-foundation/PATTERN.md +4 -1
- package/skills/jskit/references/patterns/app/shell-foundation/example/AGENTS.md +22 -5
- package/skills/jskit/references/patterns/app/shell-foundation/example/package.json +7 -7
- package/skills/jskit/references/patterns/app/shell-foundation/example/packages/main/package.json +1 -1
- package/skills/jskit/references/patterns/app/shell-foundation/example/packages/main/src/client/providers/MainClientProvider.js +3 -0
- package/skills/jskit/references/patterns/app/shell-foundation/example/playwright.config.mjs +3 -30
- package/skills/jskit/references/patterns/app/shell-foundation/example/server.js +7 -1
- package/skills/jskit/references/patterns/app/shell-foundation/example/src/components/menus/SectionTabLinkItem.vue +42 -0
- package/skills/jskit/references/patterns/app/shell-foundation/example/src/main.js +3 -2
- package/skills/jskit/references/patterns/app/shell-foundation/example/src/pages/home.vue +1 -1
- package/skills/jskit/references/patterns/app/shell-foundation/example/src/views/NotFound.vue +1 -1
- package/skills/jskit/references/patterns/app/shell-foundation/example/tests/e2e/startup.spec.ts +13 -0
- package/skills/jskit/references/patterns/app/shell-foundation/example/vite.config.mjs +3 -1
- package/skills/jskit/references/patterns/auth/accounts-hub/example/package.json +2 -2
- package/skills/jskit/references/patterns/auth/auth-surface/PATTERN.md +18 -5
- package/skills/jskit/references/patterns/auth/auth-surface/example/src/components/AppBrand.vue +17 -0
- package/skills/jskit/references/patterns/auth/auth-surface/example/src/pages/auth/login.vue +2 -2
- package/skills/jskit/references/patterns/auth/auth-surface/example/src/pages/auth/reset-password.vue +2 -2
- package/skills/jskit/references/patterns/auth/auth-surface/example/src/pages/auth/signout.vue +2 -2
- package/skills/jskit/references/patterns/auth/auth-surface/example/src/views/auth/LoginView.vue +6 -1
- package/skills/jskit/references/patterns/auth/auth-surface/example/src/views/auth/ResetPasswordView.vue +6 -1
- package/skills/jskit/references/patterns/auth/auth-surface/example/src/views/auth/SignOutView.vue +6 -1
- package/skills/jskit/references/patterns/auth/oidc-sign-in/example/package.json +2 -2
- package/skills/jskit/references/patterns/auth/supabase-auth/example/package.json +1 -1
- package/skills/jskit/references/patterns/connectors/aws-storage-queries/example/formats/package.json +1 -1
- package/skills/jskit/references/patterns/connectors/calendar-cli/example/package.json +4 -4
- package/skills/jskit/references/patterns/console/console-surface/example/src/pages/console.vue +1 -1
- package/skills/jskit/references/patterns/crud/json-api-resource-package/example/packages/books/package.json +2 -2
- package/skills/jskit/references/patterns/database/mysql-application/PATTERN.md +34 -24
- package/skills/jskit/references/patterns/database/mysql-application/example/package.json +3 -2
- package/skills/jskit/references/patterns/database/mysql-application/example/tests/e2e/databases.js +25 -0
- package/skills/jskit/references/patterns/database/postgres-application/PATTERN.md +12 -0
- package/skills/jskit/references/patterns/database/postgres-application/example/package.json +3 -2
- package/skills/jskit/references/patterns/database/postgres-application/example/tests/e2e/databases.js +25 -0
- package/skills/jskit/references/patterns/realtime/realtime-application/example/package.json +2 -2
- package/skills/jskit/references/patterns/rewards/google-rewarded/example/googlePublisherTag.js +5 -2
- package/skills/jskit/references/patterns/server/feature-package/example/booking-engine/package.json +1 -1
- package/skills/jskit/references/patterns/shell/application-shell/PATTERN.md +11 -6
- package/skills/jskit/references/patterns/shell/application-shell/example/src/components/menus/SectionTabLinkItem.vue +42 -0
- package/skills/jskit/references/patterns/shell/application-shell/example/src/pages/home.vue +1 -1
- package/skills/jskit/references/patterns/shell/application-shell/example/src/views/NotFound.vue +1 -1
- package/skills/jskit/references/patterns/ui/page-and-placement/PATTERN.md +6 -0
- package/skills/jskit/references/patterns/ui/page-and-placement/example/src/pages/home/reports.vue +9 -4
- package/skills/jskit/references/patterns/ui/page-and-placement/example/src/placementTopology.js +5 -3
- package/skills/jskit/references/patterns/users/account-settings/PATTERN.md +24 -3
- package/skills/jskit/references/patterns/users/account-settings/example/src/components/account/settings/AccountSettingsNotificationsSection.vue +1 -1
- package/skills/jskit/references/patterns/users/account-settings/example/src/components/account/settings/AccountSettingsPreferencesSection.vue +2 -1
- package/skills/jskit/references/patterns/users/account-settings/example/src/components/account/settings/AccountSettingsProfileSection.vue +3 -2
- package/skills/jskit/references/patterns/users/account-settings/example/src/pages/account/[section].vue +23 -0
- package/skills/jskit/references/patterns/users/account-settings/example/src/pages/account.vue +4 -3
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users/package.json +2 -2
- package/skills/jskit/references/patterns/users/user-administration-server/example/packages/users-workspace/package.json +3 -3
- package/skills/jskit/references/patterns/workspaces/workspace-surfaces/example/packages/main/src/client/components/AccountPendingInvitesCue.vue +3 -3
- package/skills/jskit/references/patterns/workspaces/workspace-surfaces/example/src/surfaces/admin/root.vue +1 -1
- package/skills/jskit/references/patterns/workspaces/workspace-surfaces/example/src/surfaces/app/root.vue +1 -1
- package/skills/jskit/references/sanity-checks.md +181 -0
- package/skills/jskit/references/translation.md +4 -0
- package/skills/jskit/references/typed-features.md +222 -120
- package/skills/jskit/references/ui-operations.md +3 -1
- package/skills/jskit/references/patterns/database/mysql-application/example/tests/browser/database.js +0 -68
|
@@ -1,32 +1,5 @@
|
|
|
1
1
|
import { defineConfig } from "@playwright/test";
|
|
2
|
+
import { browserTestConfig } from "@jskit-ai/jskit-catalog/browser-tests";
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
.replace(/\/+$/u, "");
|
|
6
|
-
const port = String(process.env.PLAYWRIGHT_PORT || "").trim() || "4173";
|
|
7
|
-
const baseURL = managedBaseUrl || `http://127.0.0.1:${port}`;
|
|
8
|
-
const storageState = String(process.env.PLAYWRIGHT_STORAGE_STATE || "").trim();
|
|
9
|
-
|
|
10
|
-
export default defineConfig({
|
|
11
|
-
testDir: "./tests/e2e",
|
|
12
|
-
timeout: 60_000,
|
|
13
|
-
expect: {
|
|
14
|
-
timeout: 10_000
|
|
15
|
-
},
|
|
16
|
-
use: {
|
|
17
|
-
baseURL,
|
|
18
|
-
headless: true,
|
|
19
|
-
...(storageState ? { storageState } : {})
|
|
20
|
-
},
|
|
21
|
-
...(managedBaseUrl ? {} : {
|
|
22
|
-
webServer: {
|
|
23
|
-
command: "npm run develop",
|
|
24
|
-
env: {
|
|
25
|
-
PORT: port
|
|
26
|
-
},
|
|
27
|
-
url: `${baseURL}/api/health`,
|
|
28
|
-
reuseExistingServer: true,
|
|
29
|
-
timeout: 180_000
|
|
30
|
-
}
|
|
31
|
-
})
|
|
32
|
-
});
|
|
4
|
+
// Each Playwright worker gets its own server on the production build; with PLAYWRIGHT_BASE_URL, the managed target.
|
|
5
|
+
export default defineConfig(browserTestConfig({ testDir: "./tests/e2e" }));
|
|
@@ -23,6 +23,10 @@ import { surfaceRuntime } from "./server/lib/surfaceRuntime.js";
|
|
|
23
23
|
/** @typedef {import("@jskit-ai/kernel/server/types").RuntimeEnv} RuntimeEnv */
|
|
24
24
|
|
|
25
25
|
const SPA_INDEX_FILE = "index.html";
|
|
26
|
+
// Vite names each built file under assets/ after its content (`index-BxY3kz9a.js`), so a browser may keep one for good:
|
|
27
|
+
// a new build references new names. index.html and unhashed files are revalidated on every load.
|
|
28
|
+
const HASHED_ASSET = /^assets\/(?:.+\/)?[^/]+-[\w-]{8}\.[a-z0-9]+$/u;
|
|
29
|
+
const HASHED_ASSET_CACHE = Object.freeze({ maxAge: 365 * 24 * 60 * 60 * 1000, immutable: true });
|
|
26
30
|
const STATIC_GLOBAL_UI_PATHS = Object.freeze([
|
|
27
31
|
"/assets",
|
|
28
32
|
"/favicon.svg",
|
|
@@ -157,7 +161,9 @@ async function createServer({ runtimeEnv }) {
|
|
|
157
161
|
if (hasFileExtension(pathname)) {
|
|
158
162
|
const staticFilePath = toStaticFilePath(pathname);
|
|
159
163
|
if (hasWebBuild && staticFilePath && canServeStaticFile(distRoot, staticFilePath)) {
|
|
160
|
-
return
|
|
164
|
+
return HASHED_ASSET.test(staticFilePath)
|
|
165
|
+
? reply.sendFile(staticFilePath, HASHED_ASSET_CACHE)
|
|
166
|
+
: reply.sendFile(staticFilePath);
|
|
161
167
|
}
|
|
162
168
|
return reply.code(404).send({
|
|
163
169
|
message: `Route ${method}:${pathname} not found`,
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import ShellSectionTabLinkItem from "@jskit-ai/shell-web/client/components/ShellSectionTabLinkItem";
|
|
3
|
+
|
|
4
|
+
const props = defineProps({
|
|
5
|
+
label: {
|
|
6
|
+
type: String,
|
|
7
|
+
default: ""
|
|
8
|
+
},
|
|
9
|
+
to: {
|
|
10
|
+
type: String,
|
|
11
|
+
default: ""
|
|
12
|
+
},
|
|
13
|
+
icon: {
|
|
14
|
+
type: String,
|
|
15
|
+
default: ""
|
|
16
|
+
},
|
|
17
|
+
surface: {
|
|
18
|
+
type: String,
|
|
19
|
+
default: ""
|
|
20
|
+
},
|
|
21
|
+
scopedSuffix: {
|
|
22
|
+
type: String,
|
|
23
|
+
default: "/"
|
|
24
|
+
},
|
|
25
|
+
unscopedSuffix: {
|
|
26
|
+
type: String,
|
|
27
|
+
default: "/"
|
|
28
|
+
},
|
|
29
|
+
disabled: {
|
|
30
|
+
type: Boolean,
|
|
31
|
+
default: false
|
|
32
|
+
},
|
|
33
|
+
exact: {
|
|
34
|
+
type: Boolean,
|
|
35
|
+
default: false
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
<template>
|
|
41
|
+
<ShellSectionTabLinkItem v-bind="props" />
|
|
42
|
+
</template>
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
// Vuetify's styles first, so its stylesheet comes before any component chunk's.
|
|
2
|
+
import "vuetify/styles";
|
|
3
|
+
import "@jskit-ai/material-web/client/material.css";
|
|
1
4
|
import { createApp } from "vue";
|
|
2
5
|
import { createPinia } from "pinia";
|
|
3
6
|
import { QueryClient, VueQueryPlugin } from "@tanstack/vue-query";
|
|
4
7
|
import { createRouter, createWebHistory } from "vue-router/auto";
|
|
5
8
|
import { routes } from "vue-router/auto-routes";
|
|
6
|
-
import "vuetify/styles";
|
|
7
|
-
import "@jskit-ai/material-web/client/material.css";
|
|
8
9
|
import { createVuetify } from "vuetify";
|
|
9
10
|
import { createMaterialVuetifyOptions } from "@jskit-ai/material-web/client";
|
|
10
11
|
import App from "./App.vue";
|
package/skills/jskit/references/patterns/app/shell-foundation/example/src/views/NotFound.vue
CHANGED
|
@@ -6,7 +6,7 @@ const message = "The page you requested does not exist.";
|
|
|
6
6
|
|
|
7
7
|
<template>
|
|
8
8
|
<!-- The shell's top app bar carries the title, and its navigation leads back into the app. -->
|
|
9
|
-
<ShellLayout title="Not Found"
|
|
9
|
+
<ShellLayout title="Not Found">
|
|
10
10
|
<section class="d-flex flex-column align-center text-center on-surface-variant py-16">
|
|
11
11
|
<v-icon icon="$searchOff" size="48" />
|
|
12
12
|
<p class="text-body-large mt-4 mb-0">{{ message }}</p>
|
package/skills/jskit/references/patterns/app/shell-foundation/example/tests/e2e/startup.spec.ts
CHANGED
|
@@ -33,3 +33,16 @@ for (const width of [390, 768, 1280]) {
|
|
|
33
33
|
await expect(page.locator("#startup-shell")).toHaveCount(0);
|
|
34
34
|
});
|
|
35
35
|
}
|
|
36
|
+
|
|
37
|
+
test("built assets are kept for good and the page itself is revalidated", async ({ page }) => {
|
|
38
|
+
test.skip(Boolean(process.env.PLAYWRIGHT_BASE_URL), "A managed target's server and proxy set its own caching.");
|
|
39
|
+
const assets: string[] = [];
|
|
40
|
+
page.on("response", response => {
|
|
41
|
+
if (new URL(response.url()).pathname.startsWith("/assets/")) assets.push(response.headers()["cache-control"] ?? "");
|
|
42
|
+
});
|
|
43
|
+
const document = await page.goto("/home");
|
|
44
|
+
await page.waitForLoadState("load");
|
|
45
|
+
expect(document?.headers()["cache-control"] ?? "").not.toContain("immutable");
|
|
46
|
+
expect(assets.length).toBeGreaterThan(0);
|
|
47
|
+
expect(new Set(assets)).toEqual(new Set(["public, max-age=31536000, immutable"]));
|
|
48
|
+
});
|
|
@@ -4,6 +4,7 @@ import vue from "@vitejs/plugin-vue";
|
|
|
4
4
|
import vuetify from "vite-plugin-vuetify";
|
|
5
5
|
import VueRouter from "vue-router/vite";
|
|
6
6
|
import { createJskitClientBootstrapPlugin } from "@jskit-ai/kernel/client/vite";
|
|
7
|
+
import { testConcurrency } from "@jskit-ai/jskit-catalog/test-budget";
|
|
7
8
|
|
|
8
9
|
const configuredDevPort = Number.parseInt(String(process.env.VITE_DEV_PORT || "").trim(), 10);
|
|
9
10
|
const devPort = Number.isInteger(configuredDevPort) && configuredDevPort > 0 ? configuredDevPort : 5173;
|
|
@@ -52,7 +53,8 @@ export default defineConfig({
|
|
|
52
53
|
}
|
|
53
54
|
],
|
|
54
55
|
test: {
|
|
55
|
-
include: ["tests/client/**/*.vitest.js"]
|
|
56
|
+
include: ["tests/client/**/*.vitest.js"],
|
|
57
|
+
maxWorkers: testConcurrency()
|
|
56
58
|
},
|
|
57
59
|
optimizeDeps: {
|
|
58
60
|
include: ["vuetify/components/**", "vuetify/directives/**"],
|
|
@@ -27,8 +27,14 @@ Those choices must be known before adapting the example.
|
|
|
27
27
|
## Framework APIs
|
|
28
28
|
|
|
29
29
|
Use the views, auth guard, HTTP client integration, sign-out runtime, providers,
|
|
30
|
-
and Playwright auth helper exported by `@jskit-ai/auth-web`.
|
|
31
|
-
|
|
30
|
+
and Playwright auth helper exported by `@jskit-ai/auth-web`. The sign-in, reset
|
|
31
|
+
and sign-out screens show the app's identity: the pages render the app's view
|
|
32
|
+
wrappers (`src/views/auth/`), and each wrapper fills the default view's `brand`
|
|
33
|
+
slot (given `{ compact }`) with `src/components/AppBrand.vue`, the app's name
|
|
34
|
+
as `index.html`'s `<title>` spells it. JSKIT lays the slot out as a pane beside
|
|
35
|
+
the card on wide windows and a row above it on phones; never restyle the views'
|
|
36
|
+
own classes. Use auth policies from `@jskit-ai/auth-core` for server
|
|
37
|
+
authorization.
|
|
32
38
|
|
|
33
39
|
Every application with accounts includes the managed-preview login. It is
|
|
34
40
|
standard, not optional: adding this pattern adds it, so a managed host's
|
|
@@ -92,6 +98,10 @@ a user as a side effect.
|
|
|
92
98
|
- Public auth routes do not require an existing session.
|
|
93
99
|
- Protected routes use the framework auth policy rather than page-local checks.
|
|
94
100
|
- Login, sign-out, and reset views use the public auth components/composables.
|
|
101
|
+
- The sign-in, reset and sign-out pages render the app's view wrappers, which
|
|
102
|
+
fill the `brand` slot with the app's identity (`AppBrand.vue`): a person
|
|
103
|
+
always sees which app they are signing in to. Renaming the app renames
|
|
104
|
+
`AppBrand.vue` with `index.html`'s `<title>`; the brand adds no heading.
|
|
95
105
|
- A custom login view must preserve the default view's initial session check:
|
|
96
106
|
already authenticated people return to the validated requested destination.
|
|
97
107
|
Process incoming login callbacks before that redirect, and keep signed-out
|
|
@@ -102,8 +112,9 @@ a user as a side effect.
|
|
|
102
112
|
|
|
103
113
|
## Example files
|
|
104
114
|
|
|
105
|
-
`example/` contains concrete route wrappers, editable view wrappers
|
|
106
|
-
|
|
115
|
+
`example/` contains concrete route wrappers, editable view wrappers that show
|
|
116
|
+
the app's brand (`src/components/AppBrand.vue`), and the client runtime helpers
|
|
117
|
+
needed by an authentication surface. It also contains
|
|
107
118
|
the standard app-owned managed-preview identity executable,
|
|
108
119
|
`tools/preview-identity`. Compose their
|
|
109
120
|
surface and placement declarations into the application's ordinary config and
|
|
@@ -116,7 +127,9 @@ placements, and post-auth destinations while retaining the runtime contracts.
|
|
|
116
127
|
|
|
117
128
|
## Verification
|
|
118
129
|
|
|
119
|
-
|
|
130
|
+
Check that the sign-in, reset and sign-out screens show the app's name, in the
|
|
131
|
+
pane on a wide window and in the row on a phone. Exercise failed and successful
|
|
132
|
+
login, sign-out, reset, protected navigation,
|
|
120
133
|
keyboard interaction, warm-cache return navigation, and browser refresh.
|
|
121
134
|
Open the login route with an existing authenticated session and confirm it
|
|
122
135
|
returns to the requested screen; repeat signed out and confirm the form remains.
|
package/skills/jskit/references/patterns/auth/auth-surface/example/src/components/AppBrand.vue
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
// The app's identity on its sign-in, reset and sign-out screens. The auth views place it in their brand slot: a pane
|
|
3
|
+
// beside the card on wide windows, and a row above the card on phones (`compact`). It is the app's name as
|
|
4
|
+
// index.html's <title> spells it, so renaming the app renames both. It holds no heading: each screen's h1 is its own.
|
|
5
|
+
defineProps({
|
|
6
|
+
compact: {
|
|
7
|
+
type: Boolean,
|
|
8
|
+
default: false
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const APP_NAME = "Reading Room";
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<template>
|
|
16
|
+
<div :class="compact ? 'text-title-medium' : 'text-title-large'" data-testid="app-brand">{{ APP_NAME }}</div>
|
|
17
|
+
</template>
|
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
</route>
|
|
10
10
|
|
|
11
11
|
<script setup>
|
|
12
|
-
import
|
|
12
|
+
import LoginView from "@/views/auth/LoginView.vue";
|
|
13
13
|
</script>
|
|
14
14
|
|
|
15
15
|
<template>
|
|
16
|
-
<
|
|
16
|
+
<LoginView />
|
|
17
17
|
</template>
|
package/skills/jskit/references/patterns/auth/auth-surface/example/src/pages/auth/reset-password.vue
CHANGED
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
</route>
|
|
10
10
|
|
|
11
11
|
<script setup>
|
|
12
|
-
import
|
|
12
|
+
import ResetPasswordView from "@/views/auth/ResetPasswordView.vue";
|
|
13
13
|
</script>
|
|
14
14
|
|
|
15
15
|
<template>
|
|
16
|
-
<
|
|
16
|
+
<ResetPasswordView />
|
|
17
17
|
</template>
|
package/skills/jskit/references/patterns/auth/auth-surface/example/src/pages/auth/signout.vue
CHANGED
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
</route>
|
|
10
10
|
|
|
11
11
|
<script setup>
|
|
12
|
-
import
|
|
12
|
+
import SignOutView from "@/views/auth/SignOutView.vue";
|
|
13
13
|
</script>
|
|
14
14
|
|
|
15
15
|
<template>
|
|
16
|
-
<
|
|
16
|
+
<SignOutView />
|
|
17
17
|
</template>
|
package/skills/jskit/references/patterns/auth/auth-surface/example/src/views/auth/LoginView.vue
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import DefaultLoginView from "@jskit-ai/auth-web/client/views/DefaultLoginView";
|
|
3
|
+
import AppBrand from "@/components/AppBrand.vue";
|
|
3
4
|
</script>
|
|
4
5
|
|
|
5
6
|
<template>
|
|
6
|
-
<DefaultLoginView
|
|
7
|
+
<DefaultLoginView>
|
|
8
|
+
<template #brand="{ compact }">
|
|
9
|
+
<AppBrand :compact="compact" />
|
|
10
|
+
</template>
|
|
11
|
+
</DefaultLoginView>
|
|
7
12
|
</template>
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import DefaultResetPasswordView from "@jskit-ai/auth-web/client/views/DefaultResetPasswordView";
|
|
3
|
+
import AppBrand from "@/components/AppBrand.vue";
|
|
3
4
|
</script>
|
|
4
5
|
|
|
5
6
|
<template>
|
|
6
|
-
<DefaultResetPasswordView
|
|
7
|
+
<DefaultResetPasswordView>
|
|
8
|
+
<template #brand="{ compact }">
|
|
9
|
+
<AppBrand :compact="compact" />
|
|
10
|
+
</template>
|
|
11
|
+
</DefaultResetPasswordView>
|
|
7
12
|
</template>
|
package/skills/jskit/references/patterns/auth/auth-surface/example/src/views/auth/SignOutView.vue
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import DefaultSignOutView from "@jskit-ai/auth-web/client/views/DefaultSignOutView";
|
|
3
|
+
import AppBrand from "@/components/AppBrand.vue";
|
|
3
4
|
</script>
|
|
4
5
|
|
|
5
6
|
<template>
|
|
6
|
-
<DefaultSignOutView
|
|
7
|
+
<DefaultSignOutView>
|
|
8
|
+
<template #brand="{ compact }">
|
|
9
|
+
<AppBrand :compact="compact" />
|
|
10
|
+
</template>
|
|
11
|
+
</DefaultSignOutView>
|
|
7
12
|
</template>
|
package/skills/jskit/references/patterns/connectors/aws-storage-queries/example/formats/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "s3-native-format-example",
|
|
3
3
|
"private": true,
|
|
4
4
|
"type": "module",
|
|
5
|
-
"scripts": { "test": "node
|
|
5
|
+
"scripts": { "test": "node verify-formats.mjs" },
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"csv-parse": "7.0.2",
|
|
8
8
|
"csv-stringify": "6.8.3",
|
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
"db:migrate:status": "knex --knexfile ./knexfile.js migrate:list"
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@jskit-ai/connectors-core": "0.2.
|
|
11
|
-
"@jskit-ai/connector-google-calendar": "0.2.
|
|
12
|
-
"@jskit-ai/database-runtime": "0.1.
|
|
13
|
-
"@jskit-ai/database-runtime-mysql": "0.1.
|
|
10
|
+
"@jskit-ai/connectors-core": "0.2.13",
|
|
11
|
+
"@jskit-ai/connector-google-calendar": "0.2.13",
|
|
12
|
+
"@jskit-ai/database-runtime": "0.1.261",
|
|
13
|
+
"@jskit-ai/database-runtime-mysql": "0.1.259",
|
|
14
14
|
"knex": "^3.1.0"
|
|
15
15
|
}
|
|
16
16
|
}
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
"./shared": "./src/shared/index.js"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@jskit-ai/crud-core": "0.2.
|
|
13
|
-
"@jskit-ai/resource-crud-core": "0.1.
|
|
12
|
+
"@jskit-ai/crud-core": "0.2.13",
|
|
13
|
+
"@jskit-ai/resource-crud-core": "0.1.203"
|
|
14
14
|
},
|
|
15
15
|
"jskit": {
|
|
16
16
|
"kind": "runtime",
|
|
@@ -61,27 +61,38 @@ metadata. Do not combine it with the PostgreSQL pattern's workflow: a MySQL
|
|
|
61
61
|
application's workflow has no `postgres` service, `POSTGRES_*` variables, port
|
|
62
62
|
`5432`, or `pg_isready` option.
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
and
|
|
79
|
-
|
|
64
|
+
Tests get their own databases from
|
|
65
|
+
`@jskit-ai/database-runtime/server/testDatabases`. The `test` script runs
|
|
66
|
+
`jskit test --databases`: each Node test file running at once gets its own
|
|
67
|
+
copy of `TEST_DB_NAME` (`<name>`, `<name>_1`, ...), restored from a stored
|
|
68
|
+
snapshot of the current migrations' schema and the rows they write, in
|
|
69
|
+
`node_modules/.cache/jskit-test-databases/`. The snapshot is replayed once, and
|
|
70
|
+
again only when a migration's name or contents or `package-lock.json` change;
|
|
71
|
+
a file that migrates or drops tables leaves its copy to be restored before the
|
|
72
|
+
next file takes it.
|
|
73
|
+
|
|
74
|
+
`example/tests/e2e/databases.js` is the browser run's `prepare` module for
|
|
75
|
+
`browserTestConfig({ prepare: "tests/e2e/databases.js" })` from
|
|
76
|
+
`@jskit-ai/jskit-catalog/browser-tests`. It gives each Playwright worker's
|
|
77
|
+
server its own copy of `BROWSER_TEST_DB_NAME`, restored from the same kind of
|
|
78
|
+
snapshot and seeded in one transaction by the app's fixture `seed`, which
|
|
79
|
+
restores any migration-owned baseline rows the app needs. Every server serves
|
|
80
|
+
the production build. Both refuse the application's database (`DB_NAME`,
|
|
81
|
+
`DATABASE_URL`'s, the Knex connection's), `PROD_DB_NAME` and the other test
|
|
82
|
+
database and its copies. Provision `TEST_DB_NAME`, `BROWSER_TEST_DB_NAME` and
|
|
83
|
+
their numbered copies with grants for the application account (a `<app>_%`
|
|
84
|
+
grant covers them); a user who may create no copies runs one file or worker at
|
|
85
|
+
a time. Two test runs at once need different database names. Test-only server
|
|
86
|
+
settings that disable external effects (email, payments) go in
|
|
87
|
+
`browserTestConfig({ env })`; a suite that writes data proves the server's
|
|
88
|
+
identity first.
|
|
89
|
+
|
|
90
|
+
Each preparation reports how many databases it made, whether it replayed the
|
|
91
|
+
migrations for a new snapshot, and the restore and seed durations. Use those
|
|
92
|
+
measurements to diagnose a slow start before rerunning a browser case.
|
|
80
93
|
|
|
81
94
|
Keep fixtures small for the selected test scope. Run related cases in one suite
|
|
82
|
-
invocation.
|
|
83
|
-
production build separately when needed. Fresh-schema migration proofs are
|
|
84
|
-
separate from browser startup.
|
|
95
|
+
invocation. Fresh-schema migration proofs are separate from browser startup.
|
|
85
96
|
|
|
86
97
|
## Variation points
|
|
87
98
|
|
|
@@ -99,11 +110,10 @@ it as `seed` to `prepareDatabaseFromApp()`.
|
|
|
99
110
|
- Run `npm run db:migrate:status` and the application verification command.
|
|
100
111
|
- Exercise one transaction and one negative connection case.
|
|
101
112
|
- When a seed exists, run `db:prepare` twice and require the second run to be safe.
|
|
102
|
-
- For
|
|
103
|
-
|
|
104
|
-
foreign
|
|
105
|
-
|
|
106
|
-
test when adapting the example; reject a harness that recreates the schema.
|
|
113
|
+
- For the test databases, run the suite twice: the second run restores the
|
|
114
|
+
stored snapshot instead of replaying the migrations, stale rows are gone,
|
|
115
|
+
baseline rows are restored, foreign keys are enforced, and the normal
|
|
116
|
+
database is unchanged. A new migration takes a new snapshot.
|
|
107
117
|
- Confirm `.github/workflows/verify.yml` parses as YAML and declares only the
|
|
108
118
|
`mariadb` service. GitHub reports an unparseable workflow only as a failed
|
|
109
119
|
run with "no jobs were run".
|
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
"db:prepare": "node ./scripts/prepare-database.js",
|
|
6
6
|
"db:migrate": "knex --knexfile ./knexfile.js migrate:latest",
|
|
7
7
|
"db:migrate:rollback": "knex --knexfile ./knexfile.js migrate:rollback",
|
|
8
|
-
"db:migrate:status": "knex --knexfile ./knexfile.js migrate:list"
|
|
8
|
+
"db:migrate:status": "knex --knexfile ./knexfile.js migrate:list",
|
|
9
|
+
"test": "jskit test --databases"
|
|
9
10
|
},
|
|
10
11
|
"dependencies": {
|
|
11
|
-
"@jskit-ai/database-runtime-mysql": "0.1.
|
|
12
|
+
"@jskit-ai/database-runtime-mysql": "0.1.259"
|
|
12
13
|
}
|
|
13
14
|
}
|
package/skills/jskit/references/patterns/database/mysql-application/example/tests/e2e/databases.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { prepareTestDatabases } from "@jskit-ai/database-runtime/server/testDatabases";
|
|
2
|
+
import knexConfig from "../../knexfile.js";
|
|
3
|
+
|
|
4
|
+
// The browser run's `prepare` module: `browserTestConfig({ prepare: "tests/e2e/databases.js" })` in
|
|
5
|
+
// playwright.config.mjs runs it once, before the servers start. Every Playwright worker gets its own copy of
|
|
6
|
+
// BROWSER_TEST_DB_NAME (`<name>`, `<name>_1`, ...), restored from the schema snapshot of the current migrations, then
|
|
7
|
+
// seeded; the normal and disposable TEST_DB_NAME databases are refused. It is also given each worker's `baseUrls`,
|
|
8
|
+
// for a server setting that names its own address.
|
|
9
|
+
|
|
10
|
+
/** The fixtures the specs rely on, including any migration-owned baseline rows the app needs. */
|
|
11
|
+
async function seed(transaction) {
|
|
12
|
+
await transaction("fixtures").insert({ id: 1, label: "Browser fixture" });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default async function prepareBrowserDatabases({ workers }) {
|
|
16
|
+
const { databases, summary } = await prepareTestDatabases({
|
|
17
|
+
knexConfig,
|
|
18
|
+
environment: process.env,
|
|
19
|
+
variable: "BROWSER_TEST_DB_NAME",
|
|
20
|
+
count: workers,
|
|
21
|
+
seed
|
|
22
|
+
});
|
|
23
|
+
console.log(`[browser-tests] ${summary}.`);
|
|
24
|
+
return databases.map((database) => ({ DB_NAME: database }));
|
|
25
|
+
}
|
|
@@ -53,6 +53,18 @@ dialect. `example/scripts/prepare-database.js` is the portable
|
|
|
53
53
|
migrate-then-seed entrypoint for managed sessions and deployments.
|
|
54
54
|
`example/.env.example` names the five connection values without a secret.
|
|
55
55
|
|
|
56
|
+
Tests get their own databases from
|
|
57
|
+
`@jskit-ai/database-runtime/server/testDatabases`. The `test` script runs
|
|
58
|
+
`jskit test --databases`: each Node test file running at once gets a copy of
|
|
59
|
+
`TEST_DB_NAME`, created from `<TEST_DB_NAME>_snapshot`, a database the
|
|
60
|
+
migrations are replayed into once and again only when a migration changes.
|
|
61
|
+
`example/tests/e2e/databases.js` is the browser run's `prepare` module for
|
|
62
|
+
`browserTestConfig({ prepare: "tests/e2e/databases.js" })` from
|
|
63
|
+
`@jskit-ai/jskit-catalog/browser-tests`: each Playwright worker's server gets
|
|
64
|
+
its own seeded copy of `BROWSER_TEST_DB_NAME`. The database user needs
|
|
65
|
+
`CREATEDB`; without it, Node test files run one at a time on the connected
|
|
66
|
+
database.
|
|
67
|
+
|
|
56
68
|
`example/.github/workflows/verify.yml` is a normal app-owned CI workflow with
|
|
57
69
|
exactly one database service: `postgres` (`postgres:17`, `POSTGRES_*`
|
|
58
70
|
variables, port `5432`, a `pg_isready` health check). Copy it whole from this
|
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
"db:prepare": "node ./scripts/prepare-database.js",
|
|
6
6
|
"db:migrate": "knex --knexfile ./knexfile.js migrate:latest",
|
|
7
7
|
"db:migrate:rollback": "knex --knexfile ./knexfile.js migrate:rollback",
|
|
8
|
-
"db:migrate:status": "knex --knexfile ./knexfile.js migrate:list"
|
|
8
|
+
"db:migrate:status": "knex --knexfile ./knexfile.js migrate:list",
|
|
9
|
+
"test": "jskit test --databases"
|
|
9
10
|
},
|
|
10
11
|
"dependencies": {
|
|
11
|
-
"@jskit-ai/database-runtime-postgres": "0.1.
|
|
12
|
+
"@jskit-ai/database-runtime-postgres": "0.1.258"
|
|
12
13
|
}
|
|
13
14
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { prepareTestDatabases } from "@jskit-ai/database-runtime/server/testDatabases";
|
|
2
|
+
import knexConfig from "../../knexfile.js";
|
|
3
|
+
|
|
4
|
+
// The browser run's `prepare` module: `browserTestConfig({ prepare: "tests/e2e/databases.js" })` in
|
|
5
|
+
// playwright.config.mjs runs it once, before the servers start. Every Playwright worker gets its own copy of
|
|
6
|
+
// BROWSER_TEST_DB_NAME (`<name>`, `<name>_1`, ...), created from the snapshot database of the current migrations with
|
|
7
|
+
// CREATE DATABASE ... TEMPLATE, then seeded; the normal and disposable TEST_DB_NAME databases are refused. The database
|
|
8
|
+
// user needs CREATEDB.
|
|
9
|
+
|
|
10
|
+
/** The fixtures the specs rely on, including any migration-owned baseline rows the app needs. */
|
|
11
|
+
async function seed(transaction) {
|
|
12
|
+
await transaction("fixtures").insert({ id: 1, label: "Browser fixture" });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default async function prepareBrowserDatabases({ workers }) {
|
|
16
|
+
const { databases, summary } = await prepareTestDatabases({
|
|
17
|
+
knexConfig,
|
|
18
|
+
environment: process.env,
|
|
19
|
+
variable: "BROWSER_TEST_DB_NAME",
|
|
20
|
+
count: workers,
|
|
21
|
+
seed
|
|
22
|
+
});
|
|
23
|
+
console.log(`[browser-tests] ${summary}.`);
|
|
24
|
+
return databases.map((database) => ({ DB_NAME: database }));
|
|
25
|
+
}
|
package/skills/jskit/references/patterns/rewards/google-rewarded/example/googlePublisherTag.js
CHANGED
|
@@ -72,8 +72,10 @@ function launchGoogleRewardedAd({
|
|
|
72
72
|
|
|
73
73
|
let readySeen = false;
|
|
74
74
|
let settled = false;
|
|
75
|
+
let availabilityTimer = null;
|
|
75
76
|
|
|
76
77
|
const cleanup = () => {
|
|
78
|
+
window.clearTimeout(availabilityTimer);
|
|
77
79
|
pubads.removeEventListener("rewardedSlotReady", handleReady);
|
|
78
80
|
pubads.removeEventListener("rewardedSlotGranted", handleGranted);
|
|
79
81
|
pubads.removeEventListener("rewardedSlotClosed", handleClosed);
|
|
@@ -120,8 +122,8 @@ function launchGoogleRewardedAd({
|
|
|
120
122
|
gptServicesEnabled = true;
|
|
121
123
|
}
|
|
122
124
|
|
|
123
|
-
|
|
124
|
-
window.setTimeout(() => {
|
|
125
|
+
// Started before display, so a slot that settles at once still clears it.
|
|
126
|
+
availabilityTimer = window.setTimeout(() => {
|
|
125
127
|
if (settled || readySeen) {
|
|
126
128
|
return;
|
|
127
129
|
}
|
|
@@ -131,6 +133,7 @@ function launchGoogleRewardedAd({
|
|
|
131
133
|
reject(new Error("No rewarded ad was available."));
|
|
132
134
|
});
|
|
133
135
|
}, 10000);
|
|
136
|
+
googletag.display(slot);
|
|
134
137
|
});
|
|
135
138
|
})
|
|
136
139
|
);
|