-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontext7.json
More file actions
66 lines (66 loc) · 15.6 KB
/
Copy pathcontext7.json
File metadata and controls
66 lines (66 loc) · 15.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
{
"$schema": "https://context7.com/schema/context7.json",
"projectTitle": "ZeroZ Stack",
"description": "Pure-Java full-stack framework: Java UI compiled for the browser by TeaVM, binary RPC over a persistent WebSocket, and EclipseStore object-graph persistence. You write no JavaScript, JSON, REST routes or SQL.",
"branch": "main",
"folders": [
"docs/**",
"zerozstack-examples/**"
],
"excludeFolders": [
"target",
".idea",
".github",
".mvn"
],
"excludeFiles": [
"CHANGELOG.md",
"CODE_OF_CONDUCT.md",
"LICENSE",
"NOTICE"
],
"rules": [
"ZeroZ Stack requires JDK 21 or later. Client code is written in Java and compiled for the browser by TeaVM, so only TeaVM-supported JDK APIs are available in client modules.",
"ZeroZ Stack is published to Maven Central under groupId com.zeroz4j at version 0.7.0. Import com.zeroz4j:zerozstack-bom as a POM dependency in dependencyManagement, then declare zerozstack-server-core, zerozstack-client, zerozstack-shared-api, zerozstack-ui-components and zerozstack-store-eclipsestore without versions. Add zerozstack-server-helidon for a standalone server, or zerozstack-server-jakarta to deploy a WAR. Or generate a project from com.zeroz4j:zerozstack-archetype, which produces the shared/client/server module shape. Building from source is only needed when working on the framework itself.",
"Build with 'mvn clean install -DskipTests' from the repository root. Always include clean: each example server copies dependencies into target/libs, which is never pruned, so stale jars from earlier versions otherwise accumulate and cause duplicate-bean warnings at startup.",
"The client compiles with TeaVM's JavaScript backend today. This is a deliberate interim choice because TeaVM's WasmGC backend does not yet provide functionality ZeroZ Stack needs; WasmGC is the intended destination, which is what the zerozstack-client module name refers to. Do not claim client code compiles to WebAssembly today, and do not change the JAVASCRIPT target.",
"There is no exec-maven-plugin. Run one of the seven original examples with: java -cp \"target/classes;target/libs/*\" com.zeroz4j.example.server.ExampleServer from its server module directory, using ':' instead of ';' on POSIX. The examples added in 0.6.0 (routing-tour, oidc-login, scoped-signals, pwa-install) also build a runnable jar, so 'java -jar <example>-server/target/<example>-server-0.7.0.jar' works for those. 'mvn exec:java' does not work anywhere.",
"Several examples need a sign-in (chat-events, chat-livesync, job-monitor, components-showcase show a Login component; routing-tour and scoped-signals take ?user=admin&password=admin from the URL). todo-signals, form-signup and inventory-crud connect anonymously. Since 0.7.0 starting a server does NOT switch the built-in accounts on: pass --dev-login on the command line, which the run.bat scripts already do, or set -Dzeroz.security.mode=dev. Credentials are demo/demo (role user) and admin/admin (roles user and admin), validated by DevAuth from WebSocket handshake parameters. Replace this before any real deployment by registering an AuthenticationProvider.",
"Put @Secured and @RolesAllowed on the @RmiService interface and its methods, never on the implementing bean. The dispatcher scans only the interface, so annotations on the implementation are silently ignored and the method is left unprotected. These are com.zeroz4j.api.Secured and com.zeroz4j.api.RolesAllowed, not the Jakarta annotations of the same name.",
"Structure an application as three modules: shared (models and @RmiService interfaces), client (the UI, compiled for the browser by TeaVM), and server (CDI bean implementations).",
"Every type that crosses the wire must be annotated @DataModel and must have a public no-arg constructor plus getters and setters for each serialized field.",
"Declare RMI contracts as @RmiService interfaces in the shared module and implement them as @ApplicationScoped CDI beans in the server module. The client obtains a stub with new MyService_Stub(), generated by the annotation processor.",
"Bootstrap the client with Zeroz4jClient.connect(webSocketUrl, onReady). Do not call BinaryPackableRegistrar or WasmRmiClient.initialize directly; registrars are discovered automatically through META-INF/services.",
"Choose the state-propagation mechanism deliberately. Use a local ValueSignal for state that never leaves the browser tab. Use an RMI call for named operations and request/response. Use an EventTopic for discrete occurrences that carry no retained value. Use Signals.shared for one current value that late-joining clients must see. Use @LiveSync for an identified object whose fields are edited individually.",
"Never carry state in server events. Events have no retained value, no replay and no queueing, so a client that connects later or reconnects sees nothing. Use Signals.shared instead.",
"Client writes are denied by default. Opt in explicitly with Signals.sharedWritable or @ClientWritable. The server remains authoritative and re-checks role membership and validation annotations on every inbound write.",
"Model operations such as approve, checkout, delete or log in as RMI methods, not as LiveSync field edits. An operation deserves a name, its own @Secured and @RolesAllowed marks, and its own validation point. State edits sync, operations call.",
"SyncEngine.notifyChanged throws IllegalStateException unless the object has already been serialized to a client and therefore has an ObjectMapper handle. Return the object from an RMI method at least once before relying on LiveSync.",
"A @LiveSync object is a reactive dependency: read its getters inside Effect.create(...) and an inbound sync re-runs the effect automatically. Do not poll on a timer. Notification is per object, not per field.",
"Setters are the LiveSync tracking boundary. In-place collection edits such as obj.getTags().add(x) are invisible; reassign through the setter or call LiveMutationTracker.touch(obj) afterwards.",
"Do not wrap client event handlers in your own thread. Component.addDomEventListener already wraps every DOM listener via Component.threaded(...), so the handler body runs on a suspendable TeaVM green thread and a suspending RMI call works directly inside it. An extra thread is redundant and its UI updates may not repaint until the next event. For delayed work use Window.setTimeout.",
"Dispose what you create. Effect.create and ServerEvents.on both return a Disposable, and Computed has dispose(). A view that creates them must release them when it is permanently removed.",
"Validation annotations (@NotBlank, @Min, @Max, @Size) are declared once on the model in the shared module. The generated MyModel_Rules class feeds client-side field feedback through field.withRule(...), and the server enforces the same rules independently on RMI arguments, LiveSync mutations and shared-signal writes. Client-side validation is user feedback only; the server's answer is the one that counts.",
"ZeroZ Stack is an experimental proof-of-concept at version 0.7.0, not a production framework. Field-level merging, version-conflict rejection, tracked collections and per-topic event subscription are not implemented. The @Route router, scoped signals, OpenID Connect sign-in, WAR deployment and file upload are implemented.",
"The server accepts binary messages up to 4 MB by default (zeroz.ws.maxBinaryMessageBytes, 0.7.0+). A larger message closes the connection instead of raising an error the application can catch, because @OnMessage takes a whole message. Never send file contents through an RMI call. One connection may also have at most 32 messages being handled at once and 256 messages or 8 MB waiting to go out; an empty outgoing queue always accepts the next message however large.",
"Accept files with the FileUpload component on the client and one @ApplicationScoped class implementing com.zeroz4j.server.FileUploadHandler on the server; the framework discovers it and hands it each finished file as an UploadedFile. Files go over their own HTTP address (zeroz4j-upload), not the RMI socket, and 25 MB per file is the default (zeroz.upload.maxBytes). The temporary file is deleted the moment the handler returns, so move or copy it inside the method. getFileName() and getContentType() are text the browser sent: generate the stored name yourself and check the bytes if the type matters. A WAR needs zerozstack-server-jakarta and a standalone server needs zerozstack-server-jaxrs; both already carry the address.",
"A live change is checked against every object it reaches, not only the outermost one (0.7.0+). A @LiveSync model nested inside a @ClientWritable model needs its own @ClientWritable and its own roles, and one refusal refuses the whole change with a message beginning 'The change also alters a'.",
"A client may read an object back, or take a LiveMutex lock on it, only when the server actually sent that object to that browser (0.7.0+). The server keeps a record per browser holding 10,000 objects, dropped after 24 hours idle (zeroz.disclosure.maxHandlesPerClient, zeroz.disclosure.idleHours). Knowing an object's handle is not enough. Ask the same question in a service with Disclosures.wasDisclosedTo(session, handleId). A LiveMutex caller waits 30 seconds (zeroz.livemutex.waitSeconds) and callers are served in arrival order.",
"Since 0.7.0 an unexpected server exception reaches the client as 'The server could not complete this request. Reference: <code>', with the real message and stack trace in the server log under the same code. To send a sentence the caller should read, throw com.zeroz4j.server.ClientVisibleException. The framework's own refusals - authentication required, access denied, unknown service, unknown method, failed validation - still travel word for word.",
"Do not write reconnect plumbing. Since 0.5.0 the framework reconnects with backoff, shows a built-in outage banner (Zeroz4jClient.showConnectionBanner(false) to opt out), re-subscribes shared signals, re-syncs live objects in place, and queues offline signal writes and @ClientWritable edits for flush on reconnect. Connection state is a signal: WasmRmiClient.connectionState().",
"Never add maven-shade-plugin to a ZeroZ Stack server. Weld treats each jar as a separate bean archive and a merged jar breaks CDI discovery (beans vanish or WELD-001409 duplicates). To package: mvn verify -Ppackage produces a self-contained jpackage folder with a launcher executable and bundled runtime; the generated Dockerfile builds a layered container image; both keep every jar intact on a plain classpath. On Linux never use a bare libs/* classpath wildcard: it expands in arbitrary order and one ordering registers Helidon's WebSocket routing too late, so every WebSocket handshake answers 404 while HTTP works. Sort it: java -cp \"target/classes:$(ls target/libs/*.jar | sort | tr '\\n' ':')\" ... (the generated Dockerfile already sorts).",
"RMI calls fail immediately with DisconnectedException while the connection is down, and are never queued or replayed automatically. Catch it to retry, or disable controls while connectionState() is not CONNECTED. Session ids change on every reconnect: re-register anything keyed by session id from a StateListener on CONNECTED, and observe the CDI event SessionClosedEvent on the server to clean up stale entries.",
"A @DataModel may declare EclipseStore Lazy<T> fields. The reference travels as a session-scoped handle and never as its contents; the client resolves it with a suspending RMI round trip on first get(), and the result is cached. Lazy references originate on the server, so a client cannot create one and send it up - assign the resolved value instead.",
"Never mutate a signal's value in place. ValueSignal.set skips notification when the new value equals the old one, so mutating a list and setting it back changes nothing. Use update() and return a new instance.",
"As of 0.4.0 these fail loudly rather than silently: SyncEngine.notifyChanged on an object never serialized to a client throws; an unserializable event or shared-signal payload throws to the caller; a conflicting shared-signal declaration throws; bindValue with a non-writable signal throws (use bindValueReadOnly for one-way); and @ClientWritable without @LiveSync, or on a field with no setter, is a compile error.",
"A rejected LiveSync mutation sends the writer a 0x15 REJECT frame naming the model and the reason, in addition to the corrective sync. Do not assume a reverted change is unexplained.",
"The annotation processor rejects unsupported @DataModel field types at compile time and names the replacement: object arrays (use a List), ZonedDateTime/OffsetDateTime/ZoneId/Period (use Instant, LocalDate or a String), java.util.Date (use Instant), and concrete collections outside the rebuilt hierarchy such as TreeSet, TreeMap and LinkedList (declare the field as Set, Map or List). ArrayList, HashSet and HashMap are fine.",
"Server events and shared signals reach every connected session by default. Scope events explicitly for anything belonging to somebody: events.publishToUser(topic, payload, principalName) or events.publishToSession(topic, payload, sessionId). LiveSync scopes the same way via syncEngine.notifyChanged(obj, Scope.SESSION or Scope.USER, target). Shared signals cannot be scoped at all -- a shared signal is one value the whole server agrees on, so per-user state is not a shared signal.",
"To replace the development authentication, implement com.zeroz4j.server.AuthenticationProvider and register it in META-INF/services/com.zeroz4j.server.AuthenticationProvider. It is discovered via ServiceLoader, not CDI, because the handshake runs before the endpoint exists. Return an AuthenticatedPrincipal with a name, roles and optionally a tenant; return null to leave the connection anonymous. Registering a provider disables the DevAuth fallback.",
"Scope.TENANT filters on the tenant an AuthenticationProvider attached to the session. A session with no tenant never matches, so a connection that did not sign in receives nothing sent that way. Read the tenant in a service with RmiRequestContext.getTenantId().",
"Set zeroz.clientId.secret to a long random string, the same on every node, in any real deployment: unset, the key that signs the browser id is regenerated at every startup. Set zeroz.hosts to the host names the deployment answers for; unset, a handshake addressed to any name is accepted. Leave zeroz.origins unset unless the page is served from a different host than the socket. Every zeroz.* setting and its default is listed in docs/guides/packaging.md.",
"Persistence runs on ZeroZ DB (com.zeroz4j:zerozdb). Inject ZeroZDbNode and send a DbCommand to write: everything the command enlists with ctx.edit/ctx.store lands in ONE atomic commit, and a command that throws persists nothing and restores the objects in memory. The same command runs wherever the data is, so zeroz4j.store.mode = EMBEDDED | AUTO_SERVER | CLIENT is a deployment choice rather than an application one.",
"DbCommand and DbQuery implementations must be plain classes with public fields and a public no-arg constructor, never records: EclipseStore's serializer reaches fields directly and the JVM refuses that for records without --add-exports java.base/jdk.internal.misc=ALL-UNNAMED. It fails at the first remote call, not at compile time.",
"Injecting EmbeddedStorageManager still works but only where the data is local, and it pins the application to EMBEDDED or AUTO_SERVER mode; in CLIENT mode it fails with an explanation. With the raw manager, each store() call is a separate commit - use storage.storeAll(a, b) so a crash cannot persist one object and lose the other."
]
}