next-evolve 0.0.0-stage → 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/LICENSE +21 -0
- package/README.md +136 -2
- package/dist/_virtual/_rolldown/runtime.js +12 -0
- package/dist/aggregate/attribution.d.ts +40 -0
- package/dist/aggregate/attribution.js +115 -0
- package/dist/aggregate/run.d.ts +21 -0
- package/dist/aggregate/run.js +139 -0
- package/dist/cli/aggregate.d.ts +9 -0
- package/dist/cli/aggregate.js +104 -0
- package/dist/cli/args.d.ts +12 -0
- package/dist/cli/args.js +23 -0
- package/dist/cli/cloud.d.ts +41 -0
- package/dist/cli/cloud.js +74 -0
- package/dist/cli/connect.d.ts +8 -0
- package/dist/cli/connect.js +46 -0
- package/dist/cli/detect.d.ts +26 -0
- package/dist/cli/detect.js +97 -0
- package/dist/cli/doctor.d.ts +34 -0
- package/dist/cli/doctor.js +230 -0
- package/dist/cli/env-file.d.ts +4 -0
- package/dist/cli/env-file.js +15 -0
- package/dist/cli/files.d.ts +25 -0
- package/dist/cli/files.js +173 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +81 -0
- package/dist/cli/init.d.ts +26 -0
- package/dist/cli/init.js +240 -0
- package/dist/cli/load-app.d.ts +19 -0
- package/dist/cli/load-app.js +67 -0
- package/dist/cli/migrate.d.ts +7 -0
- package/dist/cli/migrate.js +30 -0
- package/dist/cli/pages.d.ts +17 -0
- package/dist/cli/pages.js +304 -0
- package/dist/cli/plan.d.ts +27 -0
- package/dist/cli/plan.js +129 -0
- package/dist/cli/pull.d.ts +13 -0
- package/dist/cli/pull.js +62 -0
- package/dist/cli/push.d.ts +14 -0
- package/dist/cli/push.js +33 -0
- package/dist/client/beacon.d.ts +11 -0
- package/dist/client/beacon.js +51 -0
- package/dist/client/evolve-script.d.ts +6 -0
- package/dist/client/evolve-script.js +60 -0
- package/dist/client/index.d.ts +3 -0
- package/dist/client/index.js +3 -0
- package/dist/cloud/manifest.d.ts +14 -0
- package/dist/cloud/manifest.js +46 -0
- package/dist/cloud/push.d.ts +21 -0
- package/dist/cloud/push.js +56 -0
- package/dist/cloud/regions.d.ts +8 -0
- package/dist/cloud/regions.js +25 -0
- package/dist/config/config.d.ts +51 -0
- package/dist/config/config.js +47 -0
- package/dist/config/goals.d.ts +50 -0
- package/dist/config/goals.js +85 -0
- package/dist/config/index.d.ts +3 -0
- package/dist/config/index.js +3 -0
- package/dist/core/assign.d.ts +35 -0
- package/dist/core/assign.js +104 -0
- package/dist/core/bots.d.ts +5 -0
- package/dist/core/bots.js +25 -0
- package/dist/core/constants.d.ts +3 -0
- package/dist/core/constants.js +5 -0
- package/dist/core/cookies.d.ts +12 -0
- package/dist/core/cookies.js +25 -0
- package/dist/core/keys.d.ts +14 -0
- package/dist/core/keys.js +38 -0
- package/dist/core/log.d.ts +2 -0
- package/dist/core/log.js +10 -0
- package/dist/db/client.d.ts +22 -0
- package/dist/db/client.js +50 -0
- package/dist/db/migrate.d.ts +18 -0
- package/dist/db/migrate.js +43 -0
- package/dist/db/writer.d.ts +53 -0
- package/dist/db/writer.js +182 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/instrumentation/index.d.ts +24 -0
- package/dist/instrumentation/index.js +54 -0
- package/dist/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/lib/typescript.d.ts +11169 -0
- package/dist/proxy/index.d.ts +2 -0
- package/dist/proxy/index.js +1 -0
- package/dist/proxy/proxy.d.ts +24 -0
- package/dist/proxy/proxy.js +120 -0
- package/dist/react/context.d.ts +6 -0
- package/dist/react/context.js +8 -0
- package/dist/react/optimizable.d.ts +37 -0
- package/dist/react/optimizable.js +38 -0
- package/dist/react/region.d.ts +17 -0
- package/dist/react/region.js +127 -0
- package/dist/react/render-bundle.d.ts +15 -0
- package/dist/react/render-bundle.js +43 -0
- package/dist/react/with-evolve.d.ts +29 -0
- package/dist/react/with-evolve.js +57 -0
- package/dist/server/aggregate-route.d.ts +16 -0
- package/dist/server/aggregate-route.js +68 -0
- package/dist/server/collect.d.ts +62 -0
- package/dist/server/collect.js +157 -0
- package/dist/server/events.d.ts +18 -0
- package/dist/server/events.js +50 -0
- package/dist/server/evolve.d.ts +20 -0
- package/dist/server/evolve.js +57 -0
- package/dist/server/handler.d.ts +17 -0
- package/dist/server/handler.js +105 -0
- package/dist/server/index.d.ts +4 -0
- package/dist/server/index.js +4 -0
- package/dist/server/runtime.d.ts +26 -0
- package/dist/server/runtime.js +45 -0
- package/dist/server/sync-route.d.ts +28 -0
- package/dist/server/sync-route.js +100 -0
- package/dist/server/track-route.d.ts +25 -0
- package/dist/server/track-route.js +49 -0
- package/dist/store/disk.d.ts +6 -0
- package/dist/store/disk.js +23 -0
- package/dist/store/fetch.d.ts +26 -0
- package/dist/store/fetch.js +74 -0
- package/dist/store/global-config.d.ts +39 -0
- package/dist/store/global-config.js +122 -0
- package/dist/store/memory.d.ts +39 -0
- package/dist/store/memory.js +72 -0
- package/dist/store/runtime.d.ts +44 -0
- package/dist/store/runtime.js +101 -0
- package/migrations/0001_init.sql +56 -0
- package/package.json +73 -3
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { trustedKeys } from "../core/keys.js";
|
|
2
|
+
import { defaultDiskDir, diskPath, readDisk, writeDisk } from "./disk.js";
|
|
3
|
+
import { fetchBundle } from "./fetch.js";
|
|
4
|
+
import { GLOBAL_CONFIG_TTL_MS, createGlobalConfigFetcher, defaultReader, globalConfigSettings } from "./global-config.js";
|
|
5
|
+
import { createMemoryStore } from "./memory.js";
|
|
6
|
+
import { parseSecretKey } from "@next-evolve/protocol";
|
|
7
|
+
// src/store/runtime.ts
|
|
8
|
+
/** True off serverless hosts: a process that lives across requests (next start, Docker). */
|
|
9
|
+
function isLongRunningNode(env = process.env) {
|
|
10
|
+
return !env.VERCEL && !env.AWS_LAMBDA_FUNCTION_NAME && !env.NETLIFY;
|
|
11
|
+
}
|
|
12
|
+
/** Default cloud URL (decision D29). Override with EVOLVE_CLOUD_URL. */
|
|
13
|
+
const DEFAULT_CLOUD_URL = "https://api.evolveab.com";
|
|
14
|
+
const REGISTRY = Symbol.for("next-evolve.stores");
|
|
15
|
+
const registry = () => {
|
|
16
|
+
const g = globalThis;
|
|
17
|
+
g[REGISTRY] ??= /* @__PURE__ */ new Map();
|
|
18
|
+
return g[REGISTRY];
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* The process-wide bundle store for this site, shared by the proxy and the
|
|
22
|
+
* render path when they run in the same process. Null when the app has no
|
|
23
|
+
* valid secret key (then everything serves control).
|
|
24
|
+
*/
|
|
25
|
+
function getBundleStore(config, opts = {}) {
|
|
26
|
+
if (opts.store) return opts.store;
|
|
27
|
+
const env = opts.env ?? process.env;
|
|
28
|
+
const secretKey = env.EVOLVE_SECRET_KEY;
|
|
29
|
+
const site = secretKey ? parseSecretKey(secretKey)?.siteId : void 0;
|
|
30
|
+
if (!secretKey || !site) return null;
|
|
31
|
+
const reg = registry();
|
|
32
|
+
const existing = reg.get(site);
|
|
33
|
+
if (existing) {
|
|
34
|
+
if (opts.buildBundle && !existing.token()) existing.load(opts.buildBundle);
|
|
35
|
+
return existing;
|
|
36
|
+
}
|
|
37
|
+
const cloudUrl = env.EVOLVE_CLOUD_URL ?? "https://api.evolveab.com";
|
|
38
|
+
const disk = isLongRunningNode(env) ? diskPath(opts.diskDir ?? env.EVOLVE_CACHE_DIR ?? defaultDiskDir(), site) : null;
|
|
39
|
+
const pollMs = (config.pollSeconds ?? (env.NODE_ENV === "production" ? 60 : 5)) * 1e3;
|
|
40
|
+
const cloud = cloudUrl ? (etag) => fetchBundle({
|
|
41
|
+
cloudUrl,
|
|
42
|
+
secretKey,
|
|
43
|
+
etag
|
|
44
|
+
}) : void 0;
|
|
45
|
+
const gc = globalConfigSettings(config.store, env);
|
|
46
|
+
const store = createMemoryStore({
|
|
47
|
+
site,
|
|
48
|
+
keys: trustedKeys(env),
|
|
49
|
+
pollMs: gc ? Math.min(pollMs, GLOBAL_CONFIG_TTL_MS) : pollMs,
|
|
50
|
+
initialToken: opts.buildBundle ?? null,
|
|
51
|
+
fetcher: gc ? createGlobalConfigFetcher({
|
|
52
|
+
site,
|
|
53
|
+
read: opts.globalConfigReader ?? defaultReader(gc.connectionString),
|
|
54
|
+
cloud,
|
|
55
|
+
cloudEveryMs: pollMs
|
|
56
|
+
}) : cloud,
|
|
57
|
+
...disk ? { onAccept: (token) => writeDisk(disk, token) } : {}
|
|
58
|
+
});
|
|
59
|
+
if (disk) {
|
|
60
|
+
const cached = readDisk(disk);
|
|
61
|
+
if (cached && cached !== store.token()) store.load(cached);
|
|
62
|
+
}
|
|
63
|
+
reg.set(site, store);
|
|
64
|
+
if (gc) coldReads.set(store, true);
|
|
65
|
+
return store;
|
|
66
|
+
}
|
|
67
|
+
const coldReads = /* @__PURE__ */ new WeakMap();
|
|
68
|
+
/**
|
|
69
|
+
* Global Config hosts, first request of an instance: a promise that resolves
|
|
70
|
+
* after one Global Config read or {@link COLD_READ_CAP_MS}, whichever comes
|
|
71
|
+
* first (spec §7.8). Null when there's nothing to wait for.
|
|
72
|
+
*/
|
|
73
|
+
function coldStart(store) {
|
|
74
|
+
if (!coldReads.get(store)) return null;
|
|
75
|
+
coldReads.delete(store);
|
|
76
|
+
return Promise.race([store.refresh(), new Promise((r) => setTimeout(r, 20))]);
|
|
77
|
+
}
|
|
78
|
+
const identities = /* @__PURE__ */ new Map();
|
|
79
|
+
/** This app's site id (from EVOLVE_SECRET_KEY) and trusted public keys. Cached; null without a valid key. */
|
|
80
|
+
function getSiteIdentity(opts = {}) {
|
|
81
|
+
const env = opts.env ?? process.env;
|
|
82
|
+
const cacheKey = [
|
|
83
|
+
env.EVOLVE_SECRET_KEY,
|
|
84
|
+
env.EVOLVE_UNSAFE_TEST_PUBLIC_KEY,
|
|
85
|
+
env.EVOLVE_UNSAFE_ALLOW_TEST_KEY,
|
|
86
|
+
env.NODE_ENV
|
|
87
|
+
].join("|");
|
|
88
|
+
if (identities.has(cacheKey)) return identities.get(cacheKey);
|
|
89
|
+
const site = env.EVOLVE_SECRET_KEY ? parseSecretKey(env.EVOLVE_SECRET_KEY)?.siteId : void 0;
|
|
90
|
+
const id = site ? {
|
|
91
|
+
site,
|
|
92
|
+
keys: trustedKeys(env)
|
|
93
|
+
} : null;
|
|
94
|
+
identities.set(cacheKey, id);
|
|
95
|
+
return id;
|
|
96
|
+
}
|
|
97
|
+
/** Test helper: forget all stores. */
|
|
98
|
+
function resetBundleStores() {
|
|
99
|
+
registry().clear();
|
|
100
|
+
}
|
|
101
|
+
export { DEFAULT_CLOUD_URL, coldStart, getBundleStore, getSiteIdentity, isLongRunningNode, resetBundleStores };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
-- next-evolve customer schema (spec §8.3). Raw events stay in this database.
|
|
2
|
+
-- Applied by `npx next-evolve migrate`; the migrator creates `evolve.migrations`.
|
|
3
|
+
|
|
4
|
+
-- One row per unit per experiment (first exposure).
|
|
5
|
+
create table evolve.exposures (
|
|
6
|
+
experiment_id text not null,
|
|
7
|
+
anon_id text not null,
|
|
8
|
+
variant_id text not null,
|
|
9
|
+
first_at timestamptz not null default now(),
|
|
10
|
+
path text not null,
|
|
11
|
+
segment text, -- null = main; Phase 3: UTM rule id
|
|
12
|
+
primary key (experiment_id, anon_id)
|
|
13
|
+
);
|
|
14
|
+
create index exposures_exp_first on evolve.exposures (experiment_id, first_at);
|
|
15
|
+
|
|
16
|
+
create table evolve.events (
|
|
17
|
+
id bigint generated always as identity primary key,
|
|
18
|
+
ts timestamptz not null,
|
|
19
|
+
received_at timestamptz not null default now(),
|
|
20
|
+
name text not null,
|
|
21
|
+
anon_id text,
|
|
22
|
+
user_id text,
|
|
23
|
+
value double precision,
|
|
24
|
+
goal_values jsonb,
|
|
25
|
+
props jsonb,
|
|
26
|
+
source text not null,
|
|
27
|
+
path text,
|
|
28
|
+
dedupe_key text
|
|
29
|
+
);
|
|
30
|
+
create index events_name_ts on evolve.events (name, ts);
|
|
31
|
+
create index events_anon_name on evolve.events (anon_id, name, ts) where anon_id is not null;
|
|
32
|
+
create index events_user_name on evolve.events (user_id, name, ts) where user_id is not null;
|
|
33
|
+
create unique index events_dedupe on evolve.events (dedupe_key) where dedupe_key is not null;
|
|
34
|
+
|
|
35
|
+
create table evolve.identities (
|
|
36
|
+
anon_id text primary key,
|
|
37
|
+
user_id text not null,
|
|
38
|
+
linked_at timestamptz not null default now()
|
|
39
|
+
);
|
|
40
|
+
create index identities_user on evolve.identities (user_id);
|
|
41
|
+
|
|
42
|
+
create table evolve.usage_daily (
|
|
43
|
+
day date primary key,
|
|
44
|
+
tested_pageviews bigint not null default 0
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
-- Idempotency cache + audit of what aggregate requests returned (kept 24 h).
|
|
48
|
+
create table evolve.aggregate_runs (
|
|
49
|
+
request_id text primary key,
|
|
50
|
+
received_at timestamptz not null,
|
|
51
|
+
mode text not null,
|
|
52
|
+
status text not null,
|
|
53
|
+
duration_ms int,
|
|
54
|
+
response_hash text,
|
|
55
|
+
response jsonb
|
|
56
|
+
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,76 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "next-evolve",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.3.1",
|
|
4
|
+
"description": "Self-improving Next.js sites: server-side variants with no flicker, measured in your own Postgres.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"migrations"
|
|
11
|
+
],
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./config": {
|
|
18
|
+
"types": "./dist/config/index.d.ts",
|
|
19
|
+
"import": "./dist/config/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./proxy": {
|
|
22
|
+
"types": "./dist/proxy/index.d.ts",
|
|
23
|
+
"import": "./dist/proxy/index.js"
|
|
24
|
+
},
|
|
25
|
+
"./server": {
|
|
26
|
+
"types": "./dist/server/index.d.ts",
|
|
27
|
+
"import": "./dist/server/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./client": {
|
|
30
|
+
"types": "./dist/client/index.d.ts",
|
|
31
|
+
"import": "./dist/client/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./instrumentation": {
|
|
34
|
+
"types": "./dist/instrumentation/index.d.ts",
|
|
35
|
+
"import": "./dist/instrumentation/index.js"
|
|
36
|
+
},
|
|
37
|
+
"./package.json": "./package.json"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@vercel/global-config": "1.5.1",
|
|
41
|
+
"isbot": "5.2.2",
|
|
42
|
+
"postgres": "3.4.9",
|
|
43
|
+
"server-only": "^0.0.1",
|
|
44
|
+
"@next-evolve/protocol": "0.1.0"
|
|
45
|
+
},
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"next": ">=15.5.0",
|
|
48
|
+
"react": "^19.0.0",
|
|
49
|
+
"zod": "^4.0.0"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@types/react": "^19.3.0",
|
|
53
|
+
"fast-check": "^4.10.2",
|
|
54
|
+
"next": "16.4.3",
|
|
55
|
+
"react": "19.3.1",
|
|
56
|
+
"react-dom": "19.3.1",
|
|
57
|
+
"zod": "^4.6.7",
|
|
58
|
+
"@next-evolve/fake-cloud": "0.0.0"
|
|
59
|
+
},
|
|
60
|
+
"engines": {
|
|
61
|
+
"node": ">=22.12"
|
|
62
|
+
},
|
|
63
|
+
"publishConfig": {
|
|
64
|
+
"access": "public"
|
|
65
|
+
},
|
|
66
|
+
"bin": {
|
|
67
|
+
"next-evolve": "./dist/cli/index.js"
|
|
68
|
+
},
|
|
69
|
+
"scripts": {
|
|
70
|
+
"build": "tsdown && node scripts/check-dist.mjs && node scripts/check-proxy-imports.mjs && node scripts/check-size.mjs",
|
|
71
|
+
"dev": "tsdown --watch",
|
|
72
|
+
"typecheck": "tsc --noEmit",
|
|
73
|
+
"test": "vitest run --project unit",
|
|
74
|
+
"test:int": "vitest run --project integration"
|
|
75
|
+
}
|
|
6
76
|
}
|