create-syncular-app 0.2.1 → 0.3.1
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 +5 -5
- package/dist/cli.js +0 -6
- package/dist/constants.d.ts +1 -13
- package/dist/constants.js +7 -8
- package/package.json +2 -2
- package/src/cli.ts +0 -8
- package/src/constants.ts +9 -8
- package/template/minimal/README.md +1 -1
- package/template/minimal/src/smoke.test.ts +1 -1
- package/template/minimal/src/syncular.generated.ts +6 -12
- package/template/web/README.md +5 -3
- package/template/web/src/frontend/main.ts +5 -13
- package/template/web/src/server.ts +4 -0
- package/template/web/src/syncular.generated.ts +6 -12
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# create-syncular-app
|
|
2
2
|
|
|
3
|
-
The `create-syncular-app` scaffolder — the create-app experience for syncular
|
|
3
|
+
The `create-syncular-app` scaffolder — the create-app experience for syncular.
|
|
4
4
|
|
|
5
5
|
```sh
|
|
6
6
|
bun create syncular-app my-app # prompts for the template
|
|
@@ -21,9 +21,9 @@ bunx create-syncular-app my-app --template web
|
|
|
21
21
|
|
|
22
22
|
> **Third-template candidate: `react`.** [`apps/demo-react`](../../apps/demo-react)
|
|
23
23
|
> is the ready-made source for a hooks-based template — `@syncular/react`
|
|
24
|
-
> (`SyncProvider` + `
|
|
25
|
-
> `useWindow`) over the same worker + OPFS core, with the
|
|
26
|
-
>
|
|
24
|
+
> (`SyncProvider` + `useQuery` + `useRawSql` + `useMutation` + `useSyncStatus` +
|
|
25
|
+
> `useWindow`) over the same worker + OPFS core, with the named-query read
|
|
26
|
+
> tier wired. Slim it the way `web` slims `apps/demo` (drop the three-list
|
|
27
27
|
> seed to one, keep one hook of each kind) and add it here when a React
|
|
28
28
|
> template is wanted. Not built yet — noted so the shape is on record.
|
|
29
29
|
|
|
@@ -42,7 +42,7 @@ ranges:
|
|
|
42
42
|
repo's workspace.
|
|
43
43
|
- **default** (a normal `bunx create-…` run): rewrite to
|
|
44
44
|
`PUBLISHED_DEPENDENCY_RANGE` (`src/constants.ts`). **Today that constant is
|
|
45
|
-
*also* `workspace:*`** because the
|
|
45
|
+
*also* `workspace:*`** because the workspace packages are unpublished and
|
|
46
46
|
version-less (all `private`, no `version` — TODO 6.3), so there is no honest
|
|
47
47
|
semver range yet. The CLI **warns loudly** in this case: a `bun install`
|
|
48
48
|
outside the repo cannot resolve the deps until publishing lands. When the
|
package/dist/cli.js
CHANGED
|
@@ -127,12 +127,6 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
127
127
|
});
|
|
128
128
|
const displayDir = relative(process.cwd(), result.targetDir) || '.';
|
|
129
129
|
console.log(`[${CREATE_BIN}] scaffolded ${result.packageName} (template: ${template}) into ${displayDir}`);
|
|
130
|
-
if (!result.local) {
|
|
131
|
-
console.warn(`[${CREATE_BIN}] note: the ${PRODUCT_NAME} v2 packages are not published yet, so\n` +
|
|
132
|
-
` the app's dependencies are still workspace:* ranges. Run inside this repo\n` +
|
|
133
|
-
` (or pass --local) until publishing lands (TODO 6.3). Use \`${CLI_BIN} generate\`\n` +
|
|
134
|
-
' to (re)generate the typed schema.');
|
|
135
|
-
}
|
|
136
130
|
console.log(nextSteps(displayDir, template));
|
|
137
131
|
return 0;
|
|
138
132
|
}
|
package/dist/constants.d.ts
CHANGED
|
@@ -20,19 +20,7 @@ export declare const PRODUCT_NAME = "syncular";
|
|
|
20
20
|
* question answerable from one spot.
|
|
21
21
|
*/
|
|
22
22
|
export declare const WORKSPACE_PACKAGES: readonly ["@syncular/core", "@syncular/server", "@syncular/server-hono", "@syncular/client", "@syncular/typegen"];
|
|
23
|
-
|
|
24
|
-
* The dependency range written into a scaffolded app's package.json when NOT
|
|
25
|
-
* scaffolding for in-tree testing (see {@link scaffoldApp}'s `local` flag).
|
|
26
|
-
*
|
|
27
|
-
* The v2 packages are unpublished and version-less today (all `private`, no
|
|
28
|
-
* `version` field — final identity + the release train is TODO 6.3). Until
|
|
29
|
-
* they publish there is no honest semver range to point at, so this is a
|
|
30
|
-
* `workspace:*`-shaped LOCAL default too, and the CLI warns loudly that a
|
|
31
|
-
* published install is not yet possible. When the packages ship, replace this
|
|
32
|
-
* with `^<version>` (or teach the CLI to read the published version) — one
|
|
33
|
-
* edit, here.
|
|
34
|
-
*/
|
|
35
|
-
export declare const PUBLISHED_DEPENDENCY_RANGE = "workspace:*";
|
|
23
|
+
export declare const PUBLISHED_DEPENDENCY_RANGE: string;
|
|
36
24
|
/** Placeholder tokens substituted verbatim during scaffold (dumb + greppable). */
|
|
37
25
|
export declare const PLACEHOLDER: {
|
|
38
26
|
/** Replaced by the chosen project name in package.json/README. */
|
package/dist/constants.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
1
2
|
/**
|
|
2
3
|
* The ONE place naming lives (TODO 6.3 is a product decision — final package
|
|
3
4
|
* identity is not settled). Every user-visible name the scaffolder emits comes
|
|
@@ -30,15 +31,13 @@ export const WORKSPACE_PACKAGES = [
|
|
|
30
31
|
* The dependency range written into a scaffolded app's package.json when NOT
|
|
31
32
|
* scaffolding for in-tree testing (see {@link scaffoldApp}'s `local` flag).
|
|
32
33
|
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* `workspace:*`-shaped LOCAL default too, and the CLI warns loudly that a
|
|
37
|
-
* published install is not yet possible. When the packages ship, replace this
|
|
38
|
-
* with `^<version>` (or teach the CLI to read the published version) — one
|
|
39
|
-
* edit, here.
|
|
34
|
+
* Derived from this package's own version: the release train bumps every
|
|
35
|
+
* @syncular/* package in lockstep, so `^<own version>` is always an honest
|
|
36
|
+
* published range — no constant to remember on each release.
|
|
40
37
|
*/
|
|
41
|
-
|
|
38
|
+
const ownVersion = createRequire(import.meta.url)('../package.json')
|
|
39
|
+
.version;
|
|
40
|
+
export const PUBLISHED_DEPENDENCY_RANGE = `^${ownVersion}`;
|
|
42
41
|
/** Placeholder tokens substituted verbatim during scaffold (dumb + greppable). */
|
|
43
42
|
export const PLACEHOLDER = {
|
|
44
43
|
/** Replaced by the chosen project name in package.json/README. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-syncular-app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Scaffold a new Syncular app: `create-syncular-app`",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Benjamin Kniffler",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"exports": {
|
|
32
32
|
".": {
|
|
33
33
|
"bun": "./src/index.ts",
|
|
34
|
-
"browser": "./
|
|
34
|
+
"browser": "./dist/index.js",
|
|
35
35
|
"import": {
|
|
36
36
|
"types": "./dist/index.d.ts",
|
|
37
37
|
"default": "./dist/index.js"
|
package/src/cli.ts
CHANGED
|
@@ -149,14 +149,6 @@ export async function runCli(argv = process.argv.slice(2)): Promise<number> {
|
|
|
149
149
|
console.log(
|
|
150
150
|
`[${CREATE_BIN}] scaffolded ${result.packageName} (template: ${template}) into ${displayDir}`,
|
|
151
151
|
);
|
|
152
|
-
if (!result.local) {
|
|
153
|
-
console.warn(
|
|
154
|
-
`[${CREATE_BIN}] note: the ${PRODUCT_NAME} v2 packages are not published yet, so\n` +
|
|
155
|
-
` the app's dependencies are still workspace:* ranges. Run inside this repo\n` +
|
|
156
|
-
` (or pass --local) until publishing lands (TODO 6.3). Use \`${CLI_BIN} generate\`\n` +
|
|
157
|
-
' to (re)generate the typed schema.',
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
152
|
console.log(nextSteps(displayDir, template));
|
|
161
153
|
return 0;
|
|
162
154
|
} catch (error) {
|
package/src/constants.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* The ONE place naming lives (TODO 6.3 is a product decision — final package
|
|
3
5
|
* identity is not settled). Every user-visible name the scaffolder emits comes
|
|
@@ -36,15 +38,14 @@ export const WORKSPACE_PACKAGES = [
|
|
|
36
38
|
* The dependency range written into a scaffolded app's package.json when NOT
|
|
37
39
|
* scaffolding for in-tree testing (see {@link scaffoldApp}'s `local` flag).
|
|
38
40
|
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* `workspace:*`-shaped LOCAL default too, and the CLI warns loudly that a
|
|
43
|
-
* published install is not yet possible. When the packages ship, replace this
|
|
44
|
-
* with `^<version>` (or teach the CLI to read the published version) — one
|
|
45
|
-
* edit, here.
|
|
41
|
+
* Derived from this package's own version: the release train bumps every
|
|
42
|
+
* @syncular/* package in lockstep, so `^<own version>` is always an honest
|
|
43
|
+
* published range — no constant to remember on each release.
|
|
46
44
|
*/
|
|
47
|
-
|
|
45
|
+
const ownVersion = createRequire(import.meta.url)('../package.json')
|
|
46
|
+
.version as string;
|
|
47
|
+
|
|
48
|
+
export const PUBLISHED_DEPENDENCY_RANGE = `^${ownVersion}`;
|
|
48
49
|
|
|
49
50
|
/** Placeholder tokens substituted verbatim during scaffold (dumb + greppable). */
|
|
50
51
|
export const PLACEHOLDER = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# __PROJECT_NAME__
|
|
2
2
|
|
|
3
|
-
A minimal [syncular](https://github.com/bkniffler/syncular)
|
|
3
|
+
A minimal [syncular](https://github.com/bkniffler/syncular) app: a schema, a
|
|
4
4
|
~30-line server, and two client cores that converge — all in a terminal, no
|
|
5
5
|
browser. This is the shape you grow a real backend from.
|
|
6
6
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* The template's own smoke test: boots the real Hono server on an ephemeral
|
|
3
3
|
* port and drives two independent bun:sqlite client cores through it over real
|
|
4
4
|
* HTTP, asserting they converge. This runs in the scaffolded app's own `bun
|
|
5
|
-
* test`, AND (because the template lives in the workspace) in the
|
|
5
|
+
* test`, AND (because the template lives in the workspace) in the repo test sweep — so
|
|
6
6
|
* the template itself cannot rot.
|
|
7
7
|
*/
|
|
8
8
|
import { afterAll, beforeAll, expect, test } from 'bun:test';
|
|
@@ -25,31 +25,25 @@ export const schema = {
|
|
|
25
25
|
/** One notes row (§2.4 column order). */
|
|
26
26
|
export interface NotesRow {
|
|
27
27
|
id: string;
|
|
28
|
-
|
|
28
|
+
listId: string;
|
|
29
29
|
body: string;
|
|
30
|
-
|
|
30
|
+
updatedAtMs: number;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
/** Insert shape: nullable columns may be omitted. */
|
|
34
34
|
export interface NotesInsert {
|
|
35
35
|
id: string;
|
|
36
|
-
|
|
36
|
+
listId: string;
|
|
37
37
|
body: string;
|
|
38
|
-
|
|
38
|
+
updatedAtMs: number;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
/** Update shape: primary key required, all other columns optional. */
|
|
42
42
|
export interface NotesUpdate {
|
|
43
43
|
id: string;
|
|
44
|
-
|
|
44
|
+
listId?: string;
|
|
45
45
|
body?: string;
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/** Kysely `Database` interface (table → Row); the generic for
|
|
50
|
-
* @syncular/kysely's SyncularDialect. */
|
|
51
|
-
export interface Database {
|
|
52
|
-
notes: NotesRow;
|
|
46
|
+
updatedAtMs?: number;
|
|
53
47
|
}
|
|
54
48
|
|
|
55
49
|
export interface NotesInListParams {
|
package/template/web/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# __PROJECT_NAME__
|
|
2
2
|
|
|
3
|
-
A [syncular](https://github.com/bkniffler/syncular)
|
|
3
|
+
A [syncular](https://github.com/bkniffler/syncular) browser app: a Hono sync
|
|
4
4
|
server, a WebSocket realtime channel, and a single-pane todo UI whose whole
|
|
5
5
|
client core runs in a Web Worker on OPFS. Everything is served by one Bun
|
|
6
6
|
process.
|
|
@@ -20,8 +20,10 @@ browser window on the same URL to watch them converge over the realtime socket.
|
|
|
20
20
|
`bun test` runs a server-level convergence smoke test (the browser UI is
|
|
21
21
|
covered by `tsc`).
|
|
22
22
|
|
|
23
|
-
> One core per origin:
|
|
24
|
-
>
|
|
23
|
+
> One core per origin: the first tab leads (owns the worker, the OPFS
|
|
24
|
+
> database, and the socket) and every further tab follows it over a
|
|
25
|
+
> BroadcastChannel — same API, one sync loop. Use two windows/profiles to
|
|
26
|
+
> see two separate clients converge.
|
|
25
27
|
|
|
26
28
|
## Layout
|
|
27
29
|
|
|
@@ -6,15 +6,15 @@
|
|
|
6
6
|
* query/mutate/subscribe messages and re-renders. This is the real browser
|
|
7
7
|
* shape; grow your app from here.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* One core per origin: the first tab wins the leader lock and spawns the
|
|
10
|
+
* worker; every further tab becomes a follower proxying the same API to the
|
|
11
|
+
* leader over a BroadcastChannel (and promotes in place when the leader
|
|
12
|
+
* closes). Open two tabs — or two browser windows for two separate actors —
|
|
13
|
+
* to watch them converge over the realtime socket.
|
|
13
14
|
*/
|
|
14
15
|
import {
|
|
15
16
|
ClientSyncError,
|
|
16
17
|
createSyncClientHandle,
|
|
17
|
-
NOT_LEADER_CODE,
|
|
18
18
|
type SqlRow,
|
|
19
19
|
} from '@syncular/client';
|
|
20
20
|
import { schema, todoListSubscription } from '../syncular.generated';
|
|
@@ -57,14 +57,6 @@ async function main(): Promise<void> {
|
|
|
57
57
|
onSynced: () => void refresh(),
|
|
58
58
|
});
|
|
59
59
|
|
|
60
|
-
if (!handle.isLeader) {
|
|
61
|
-
throw new ClientSyncError(
|
|
62
|
-
NOT_LEADER_CODE,
|
|
63
|
-
'another tab already owns this app’s core — close it first ' +
|
|
64
|
-
'(multi-tab followers are a roadmap item).',
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
60
|
await handle.subscribe({
|
|
69
61
|
id: 'todos',
|
|
70
62
|
table: 'todos',
|
|
@@ -58,6 +58,10 @@ const build = await Bun.build({
|
|
|
58
58
|
join(import.meta.dir, 'frontend', 'worker.ts'),
|
|
59
59
|
],
|
|
60
60
|
target: 'browser',
|
|
61
|
+
// Resolve syncular packages through their `bun` condition (TS source,
|
|
62
|
+
// shipped in the npm tarballs) — bun transpiles it, and a `--local`
|
|
63
|
+
// workspace link works without a dist build.
|
|
64
|
+
conditions: ['bun'],
|
|
61
65
|
sourcemap: 'inline',
|
|
62
66
|
external: ['@sqlite.org/sqlite-wasm'],
|
|
63
67
|
});
|
|
@@ -27,37 +27,31 @@ export const schema = {
|
|
|
27
27
|
/** One todos row (§2.4 column order). */
|
|
28
28
|
export interface TodosRow {
|
|
29
29
|
id: string;
|
|
30
|
-
|
|
30
|
+
listId: string;
|
|
31
31
|
title: string;
|
|
32
32
|
done: boolean;
|
|
33
33
|
position: number;
|
|
34
|
-
|
|
34
|
+
updatedAtMs: number;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
/** Insert shape: nullable columns may be omitted. */
|
|
38
38
|
export interface TodosInsert {
|
|
39
39
|
id: string;
|
|
40
|
-
|
|
40
|
+
listId: string;
|
|
41
41
|
title: string;
|
|
42
42
|
done: boolean;
|
|
43
43
|
position: number;
|
|
44
|
-
|
|
44
|
+
updatedAtMs: number;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
/** Update shape: primary key required, all other columns optional. */
|
|
48
48
|
export interface TodosUpdate {
|
|
49
49
|
id: string;
|
|
50
|
-
|
|
50
|
+
listId?: string;
|
|
51
51
|
title?: string;
|
|
52
52
|
done?: boolean;
|
|
53
53
|
position?: number;
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** Kysely `Database` interface (table → Row); the generic for
|
|
58
|
-
* @syncular/kysely's SyncularDialect. */
|
|
59
|
-
export interface Database {
|
|
60
|
-
todos: TodosRow;
|
|
54
|
+
updatedAtMs?: number;
|
|
61
55
|
}
|
|
62
56
|
|
|
63
57
|
export interface TodoListParams {
|