@jskit-ai/agent-docs 0.1.59 → 0.1.61
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/realtime.md +73 -0
- package/guide/agent/app-setup/authentication.md +15 -16
- package/guide/agent/app-setup/database-layer.md +7 -6
- package/guide/agent/app-setup/users.md +11 -10
- package/package.json +1 -1
- package/reference/autogen/packages/auth-provider-supabase-core.md +1 -1
- package/reference/autogen/packages/kernel.md +7 -0
|
@@ -124,6 +124,79 @@ So the mental model is:
|
|
|
124
124
|
- `realtime` gives the app a live transport
|
|
125
125
|
- your own app code, or later packages, decide which events should travel across it
|
|
126
126
|
|
|
127
|
+
## Publishing server events
|
|
128
|
+
|
|
129
|
+
Server code should publish realtime lifecycle events through JSKIT's entity-change helpers instead of hand-rolling `domainEvents.publish()` payloads.
|
|
130
|
+
|
|
131
|
+
Keep `operation` limited to resource invalidation semantics:
|
|
132
|
+
|
|
133
|
+
- `created`
|
|
134
|
+
- `updated`
|
|
135
|
+
- `deleted`
|
|
136
|
+
|
|
137
|
+
Use `action` for the domain lifecycle transition, and `reason` only when you need to explain why that transition happened.
|
|
138
|
+
|
|
139
|
+
For direct publishers, use `createRealtimeEntityChangePublisher()` from `@jskit-ai/kernel/server/runtime/entityChangeEvents`:
|
|
140
|
+
|
|
141
|
+
```js
|
|
142
|
+
import { createRealtimeEntityChangePublisher } from "@jskit-ai/kernel/server/runtime/entityChangeEvents";
|
|
143
|
+
|
|
144
|
+
const publishProjectRuntimeChanged = createRealtimeEntityChangePublisher({
|
|
145
|
+
domainEvents,
|
|
146
|
+
source: "vibe64",
|
|
147
|
+
entity: "project",
|
|
148
|
+
event: "vibe64.project.changed",
|
|
149
|
+
serviceToken: "vibe64.terminals.service",
|
|
150
|
+
methodName: "projectRuntime"
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
await publishProjectRuntimeChanged("updated", projectSlug, {
|
|
154
|
+
action: "runtime-closed",
|
|
155
|
+
payload: {
|
|
156
|
+
message: "Project is closed.",
|
|
157
|
+
runtime: {
|
|
158
|
+
open: false
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
The helper emits a normal `entity.changed` domain event with service metadata and `meta.realtime.event`. The realtime bridge uses that service metadata to find the registered socket dispatcher, then emits the socket event with canonical fields such as `source`, `entity`, `operation`, `entityId`, `scope`, and the lifecycle `action`.
|
|
165
|
+
|
|
166
|
+
For services registered through `app.service()`, declare the same semantics in service metadata:
|
|
167
|
+
|
|
168
|
+
```js
|
|
169
|
+
app.service(
|
|
170
|
+
"vibe64.terminals.service",
|
|
171
|
+
(scope) => createTerminalsService({
|
|
172
|
+
repository: scope.make("vibe64.repository.terminals")
|
|
173
|
+
}),
|
|
174
|
+
{
|
|
175
|
+
events: {
|
|
176
|
+
projectRuntime: [
|
|
177
|
+
{
|
|
178
|
+
type: "entity.changed",
|
|
179
|
+
source: "vibe64",
|
|
180
|
+
entity: "project",
|
|
181
|
+
operation: "updated",
|
|
182
|
+
entityId: ({ args }) => args?.[0]?.projectSlug,
|
|
183
|
+
action: "runtime-closed",
|
|
184
|
+
realtime: {
|
|
185
|
+
event: "vibe64.project.changed",
|
|
186
|
+
payload: ({ result }) => ({
|
|
187
|
+
message: result?.message || "",
|
|
188
|
+
runtime: result?.runtime || null
|
|
189
|
+
})
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
]
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
);
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
`action`, `reason`, and `realtime.payload` may be functions when the value depends on the service result or arguments. Do not encode lifecycle names by widening `operation`; keep `operation` truthful for CRUD/resource contracts and put domain-specific lifecycle meaning in metadata.
|
|
199
|
+
|
|
127
200
|
## What `realtime` adds to the app
|
|
128
201
|
|
|
129
202
|
This chapter is small enough that it is worth looking directly at the app-owned files it changes.
|
|
@@ -184,11 +184,11 @@ The screen is also ready to show OAuth provider buttons, but only if providers a
|
|
|
184
184
|
For this chapter, `config.server.js` keeps this empty:
|
|
185
185
|
|
|
186
186
|
```js
|
|
187
|
-
config.auth
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
187
|
+
config.auth ||= {};
|
|
188
|
+
config.auth.profileMode = "standalone";
|
|
189
|
+
config.auth.oauth = {
|
|
190
|
+
providers: [],
|
|
191
|
+
defaultProvider: ""
|
|
192
192
|
};
|
|
193
193
|
```
|
|
194
194
|
|
|
@@ -208,11 +208,10 @@ First, configure Google and Supabase:
|
|
|
208
208
|
Then tell JSKIT to expose the provider in the login UI:
|
|
209
209
|
|
|
210
210
|
```js
|
|
211
|
-
config.auth
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}
|
|
211
|
+
config.auth ||= {};
|
|
212
|
+
config.auth.oauth = {
|
|
213
|
+
providers: ["google"],
|
|
214
|
+
defaultProvider: "google"
|
|
216
215
|
};
|
|
217
216
|
```
|
|
218
217
|
|
|
@@ -575,15 +574,15 @@ That `requiresAuth: false` line is important. The auth surface must stay public,
|
|
|
575
574
|
`config/server.js` also gets an auth stub:
|
|
576
575
|
|
|
577
576
|
```js
|
|
578
|
-
config.auth
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
577
|
+
config.auth ||= {};
|
|
578
|
+
config.auth.profileMode = "standalone";
|
|
579
|
+
config.auth.oauth = {
|
|
580
|
+
providers: [],
|
|
581
|
+
defaultProvider: ""
|
|
583
582
|
};
|
|
584
583
|
```
|
|
585
584
|
|
|
586
|
-
That small block explains a lot of the default login screen. The stock UI is ready for OAuth providers such as Google, but this chapter keeps the provider list empty, so the page only shows the email/password and one-time-code flows. Later, if you enable a provider in Supabase and list it here, the same login screen can expose that button too.
|
|
585
|
+
That small block explains a lot of the default login screen and auth profile behavior. `profileMode: "standalone"` keeps this auth-only chapter on the temporary app-side profile mirror. The stock UI is ready for OAuth providers such as Google, but this chapter keeps the provider list empty, so the page only shows the email/password and one-time-code flows. Later, if you enable a provider in Supabase and list it here, the same login screen can expose that button too.
|
|
587
586
|
|
|
588
587
|
The auth routes themselves are app-owned wrappers around the module-supplied default views. `src/pages/auth/login.vue` looks like this:
|
|
589
588
|
|
|
@@ -710,16 +710,16 @@ So the app has gained a new capability, but no visible part of the UI depends on
|
|
|
710
710
|
|
|
711
711
|
This is the most important code path to read in this chapter.
|
|
712
712
|
|
|
713
|
-
Inside `AuthSupabaseServiceProvider`, auth
|
|
713
|
+
Inside `AuthSupabaseServiceProvider`, auth resolves its profile mode from server app config:
|
|
714
714
|
|
|
715
715
|
```js
|
|
716
|
-
const authProfileMode = resolveAuthProfileMode(
|
|
716
|
+
const authProfileMode = resolveAuthProfileMode(appConfig);
|
|
717
717
|
let userProfileSyncService = fallbackStandaloneProfileSyncService;
|
|
718
718
|
|
|
719
|
-
if (authProfileMode ===
|
|
719
|
+
if (authProfileMode === PROFILE_MODE_USERS) {
|
|
720
720
|
if (!scope.has("users.profile.sync.service")) {
|
|
721
721
|
throw new Error(
|
|
722
|
-
"AuthSupabaseServiceProvider requires users.profile.sync.service when
|
|
722
|
+
"AuthSupabaseServiceProvider requires users.profile.sync.service when config.auth.profileMode is \"users\"."
|
|
723
723
|
);
|
|
724
724
|
}
|
|
725
725
|
userProfileSyncService = scope.make("users.profile.sync.service");
|
|
@@ -728,9 +728,10 @@ if (authProfileMode === AUTH_PROFILE_MODE_USERS) {
|
|
|
728
728
|
|
|
729
729
|
That snippet explains the whole consequence of this chapter.
|
|
730
730
|
|
|
731
|
-
- The
|
|
731
|
+
- The auth provider's server config still sets `config.auth.profileMode = "standalone"`.
|
|
732
|
+
- If `profileMode` is missing entirely, the runtime assumes `users`, but it is not missing in the auth-only scaffold.
|
|
732
733
|
- The fallback service is still the in-memory profile sync service from the previous chapter.
|
|
733
|
-
- Nothing in `database-runtime-mysql` changes `
|
|
734
|
+
- Nothing in `database-runtime-mysql` changes `config.auth.profileMode`.
|
|
734
735
|
- Nothing in `database-runtime-mysql` provides `users.profile.sync.service`.
|
|
735
736
|
|
|
736
737
|
So the auth layer keeps behaving the same way it did before:
|
|
@@ -39,7 +39,7 @@ This is the first chapter where the migration step is not just "nice to have."
|
|
|
39
39
|
|
|
40
40
|
`users-core` writes:
|
|
41
41
|
|
|
42
|
-
- `
|
|
42
|
+
- `config.auth.profileMode = "users"` into `config/server.js`
|
|
43
43
|
- real users/account schema migrations into `migrations/`
|
|
44
44
|
|
|
45
45
|
That means the app is expected to use the persistent users-backed profile sync service. If you skip `npm run db:migrate`, the code and routes are installed, but the required tables are still missing.
|
|
@@ -119,12 +119,13 @@ This is the first chapter where the app starts to feel like it has a real user m
|
|
|
119
119
|
|
|
120
120
|
The most interesting files are spread across config, migrations, routing, and the app-owned account UI.
|
|
121
121
|
|
|
122
|
-
###
|
|
122
|
+
### `config/server.js` flips auth into users mode
|
|
123
123
|
|
|
124
|
-
The most important new
|
|
124
|
+
The most important new server-only config line is:
|
|
125
125
|
|
|
126
|
-
```
|
|
127
|
-
|
|
126
|
+
```js
|
|
127
|
+
config.auth ||= {};
|
|
128
|
+
config.auth.profileMode = "users";
|
|
128
129
|
```
|
|
129
130
|
|
|
130
131
|
That one line explains the deepest change in the chapter.
|
|
@@ -252,16 +253,16 @@ That is worth noticing because this is a higher level of scaffolding:
|
|
|
252
253
|
|
|
253
254
|
### Why auth uses the users layer
|
|
254
255
|
|
|
255
|
-
In the previous chapter, auth
|
|
256
|
+
In the previous chapter, auth was explicitly configured for the standalone profile sync fallback. The core logic in `AuthSupabaseServiceProvider` looks like this:
|
|
256
257
|
|
|
257
258
|
```js
|
|
258
|
-
const authProfileMode = resolveAuthProfileMode(
|
|
259
|
+
const authProfileMode = resolveAuthProfileMode(appConfig);
|
|
259
260
|
let userProfileSyncService = fallbackStandaloneProfileSyncService;
|
|
260
261
|
|
|
261
|
-
if (authProfileMode ===
|
|
262
|
+
if (authProfileMode === PROFILE_MODE_USERS) {
|
|
262
263
|
if (!scope.has("users.profile.sync.service")) {
|
|
263
264
|
throw new Error(
|
|
264
|
-
"AuthSupabaseServiceProvider requires users.profile.sync.service when
|
|
265
|
+
"AuthSupabaseServiceProvider requires users.profile.sync.service when config.auth.profileMode is \"users\"."
|
|
265
266
|
);
|
|
266
267
|
}
|
|
267
268
|
userProfileSyncService = scope.make("users.profile.sync.service");
|
|
@@ -272,7 +273,7 @@ The important part is concrete.
|
|
|
272
273
|
|
|
273
274
|
After `users-web`:
|
|
274
275
|
|
|
275
|
-
-
|
|
276
|
+
- `config/server.js` sets `config.auth.profileMode = "users"`
|
|
276
277
|
- `users-core` supplies the users-backed sync service
|
|
277
278
|
- the migrations supply the required tables
|
|
278
279
|
|
package/package.json
CHANGED
|
@@ -244,7 +244,7 @@ Local functions
|
|
|
244
244
|
- `resolveOAuthConfigFromAppConfig(appConfig)`
|
|
245
245
|
- `resolveAllowedReturnToOrigins({ appConfig = {}, appPublicUrl = "" } = {})`
|
|
246
246
|
- `resolveAuthProviderConfig(env, appConfig = {})`
|
|
247
|
-
- `resolveAuthProfileMode(
|
|
247
|
+
- `resolveAuthProfileMode(appConfig = {})`
|
|
248
248
|
- `isDevAuthBypassEnabledForRegistration(env)`
|
|
249
249
|
- `isDevAuthBypassRequested(env)`
|
|
250
250
|
- `createInMemoryUserSettingsRepository()`
|
|
@@ -1163,6 +1163,7 @@ Local functions
|
|
|
1163
1163
|
- `normalizeServiceEventType(value, { context = "service event" } = {})`
|
|
1164
1164
|
- `normalizeServiceEventOperation(value, { context = "service event" } = {})`
|
|
1165
1165
|
- `normalizeServiceEventEntityId(value)`
|
|
1166
|
+
- `normalizeServiceEventMetaField(value, { context = "service event meta field" } = {})`
|
|
1166
1167
|
- `normalizeRealtimeDispatch(value, { context = "service event.realtime" } = {})`
|
|
1167
1168
|
- `normalizeRealtimeAudience(value, { context = "service event.realtime.audience" } = {})`
|
|
1168
1169
|
- `normalizeServiceEventSpec(entry, { context = "service event" } = {})`
|
|
@@ -1171,6 +1172,7 @@ Local functions
|
|
|
1171
1172
|
- `resolveMethodOptions(args = [])`
|
|
1172
1173
|
- `resolveEventOperation(spec, state)`
|
|
1173
1174
|
- `resolveEventEntityId(spec, state)`
|
|
1175
|
+
- `resolveEventMetaField(value, state)`
|
|
1174
1176
|
- `resolveEventMeta(spec, state)`
|
|
1175
1177
|
- `createServiceMethodEventPublisher(scope, serviceToken, methodName, specs = [])`
|
|
1176
1178
|
|
|
@@ -1217,12 +1219,17 @@ Exports
|
|
|
1217
1219
|
Exports
|
|
1218
1220
|
- `resolveDefaultScope(visibilityContext = {}, runtime = {})`
|
|
1219
1221
|
- `createEntityChangePublisher({ domainEvents, source, entity, scopeResolver = resolveDefaultScope } = {})`
|
|
1222
|
+
- `createRealtimeEntityChangePublisher({ domainEvents, source, entity, event, serviceToken, methodName, scopeResolver = resolveDefaultScope } = {})`
|
|
1220
1223
|
- `createNoopEntityChangePublisher()`
|
|
1221
1224
|
Local functions
|
|
1222
1225
|
- `resolveContextScope(context = {})`
|
|
1223
1226
|
- `resolveVisibilityScope(visibilityContext = {}, runtimeContext = {})`
|
|
1224
1227
|
- `resolveCommandId(requestMeta = {})`
|
|
1225
1228
|
- `resolveSourceClientId(requestMeta = {})`
|
|
1229
|
+
- `normalizeMetaTextField(source = {}, fieldName = "", { context = "realtime entity change" } = {})`
|
|
1230
|
+
- `normalizeRealtimePayload(value, { context = "realtime entity change.payload" } = {})`
|
|
1231
|
+
- `createRealtimeEntityChangeMeta({ serviceToken, methodName, event, change = {} } = {})`
|
|
1232
|
+
- `resolveRealtimeEntityChangeOptions(change = {}, options = {})`
|
|
1226
1233
|
|
|
1227
1234
|
### `server/runtime/errors.js`
|
|
1228
1235
|
Exports
|