primitive-app 3.0.2 → 3.0.4
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/dist/index.cjs +6 -6
- package/dist/index.js +4121 -4309
- package/dist/lib-C53EcrFg.cjs +1 -0
- package/dist/lib-zKX4LVeH.js +191 -0
- package/dist/services/JsBaoClientService.d.ts.map +1 -1
- package/dist/testing/index.cjs +1 -0
- package/dist/testing/index.d.ts +62 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +97 -0
- package/package.json +27 -18
|
@@ -0,0 +1 @@
|
|
|
1
|
+
let e=require("js-bao-wss-client");var t={debug:0,info:1,warn:2,error:3,none:4},n=`warn`,r=class e{#e;scope;constructor(e,t){this.#e=e,this.scope=t}get level(){return this.#e.level}set level(e){this.setLevel(e)}debug(...e){this.#t(`debug`,e)}log(...e){this.#t(`info`,e,`log`)}warn(...e){this.#t(`warn`,e)}error(...e){this.#t(`error`,e)}shouldLog(e){return t[e]>=t[this.level]}getLevel(){return this.level}setLevel(e){e in t&&(this.#e.level=e)}forScope(t){let n=t.trim(),r=n?[...this.scope,n]:this.scope;return new e(this.#e,r)}#t(e,t,n){if(!this.shouldLog(e))return;let r=n??(e===`info`?`log`:e),i=this.scope.length?`[${this.scope.join(`:`)}]`:void 0,a=i?[i,...t]:t;console[r](...a)}},i={level:n},a=new r(i,[]).forScope(`PrimitiveApp`);function o(e){i.level=e}var s=a.forScope(`JsBaoClientService`);function c(){if(typeof window>`u`)return!1;let e=window.location.hostname;return e===`localhost`||e===`127.0.0.1`||e===`[::1]`||e.endsWith(`.localhost`)||e.endsWith(`.local`)}var l=new class{client=null;clientInitializationPromise=null;config=null;initialize(e){s.debug(`Initializing with config:`,{appId:e.appId?`${e.appId.substring(0,10)}...`:`MISSING`,apiUrl:e.apiUrl,wsUrl:e.wsUrl,oauthRedirectUri:e.oauthRedirectUri});let t=[];(!e.appId||e.appId===`MISSING_APP_ID`)&&t.push(`appId`),(!e.apiUrl||e.apiUrl===`MISSING_API_URL`)&&t.push(`apiUrl`),(!e.wsUrl||e.wsUrl===`MISSING_WS_URL`)&&t.push(`wsUrl`),(!e.oauthRedirectUri||e.oauthRedirectUri===`MISSING_OAUTH_REDIRECT_URI`)&&t.push(`oauthRedirectUri`),t.length>0&&(s.error(`❌ Configuration validation failed!`),s.error(`Missing or invalid fields:`,t),s.error(`This will cause OAuth and API calls to fail.`),s.error(`Please check your .env file configuration.`)),this.config=e}getConfig(){return this.config}async getClientAsync(){if(s.debug(`Getting client async`),this.clientInitializationPromise)return this.clientInitializationPromise;if(!this.config)throw Error(`JsBaoClientService not initialized. Call initialize() first.`);return this.clientInitializationPromise=(async()=>{s.debug(`=== INITIALIZING CLIENT ===`),s.debug(`Configuration:`,this.config);let t=await(0,e.initializeClient)(this.config);if(t.on(`status`,e=>{s.debug(`Client status changed:`,e)}),t.on(`error`,e=>{s.error(`Client error:`,e)}),this.client=t,c()&&(window.__primitiveAppClient=t,this.config?.models&&Array.isArray(this.config.models))){let e={};for(let t of this.config.models){let n=t.schema?.name;if(n){let r=n.charAt(0).toUpperCase()+n.slice(1);e[r]=t}}window.__primitiveAppModels=e,s.debug(`Registered models on window:`,Object.keys(e))}return s.debug(`=== CLIENT INITIALIZATION COMPLETE ===`),t})(),this.clientInitializationPromise}setExternalClient(e){s.debug(`Setting external client from host app`),this.client=e,this.clientInitializationPromise=Promise.resolve(e)}async clearClient(){s.debug(`Clearing client singleton...`),this.client&&(this.client.disconnect().catch(e=>{s.warn(`Background disconnect failed (this is usually harmless):`,e)}),this.client=null,this.clientInitializationPromise=null,c()&&(window.__primitiveAppClient=void 0,window.__primitiveAppModels=void 0))}};function u(e){l.initialize(e)}var d=a.forScope(`TestHarness`),f=`===TEST===`;async function p(e={}){let{networkSync:t=!1}=e,n=await l.getClientAsync(),r=`${f} ${`${Date.now()}-${Math.random().toString(36).substring(2,11)}`}`;if(t){let{metadata:e}=await n.documents.create({title:r}),t=e.documentId,{doc:i}=await n.documents.open(t);if(!i)throw Error(`[TestHarness] Failed to open synced test document`);return n.setDefaultDocumentId(t),d.debug(`Created synced test document`,{docId:t}),m.set(t,{client:n,docId:t,networkSync:!0}),{docId:t}}let{metadata:i}=await n.documents.create({title:r,localOnly:!0}),a=i.documentId,{doc:o}=await n.documents.open(a,{waitForLoad:`local`,enableNetworkSync:!1});if(!o)throw Error(`[TestHarness] Failed to open test document`);return n.setDefaultDocumentId(a),d.debug(`Created test document`,{docId:a}),m.set(a,{client:n,docId:a,networkSync:!1}),{docId:a}}var m=new Map;async function h(e){let t=m.get(e.docId);if(!t){d.warn(`destroyTestDocument called with unknown handle`,{docId:e.docId});return}m.delete(e.docId);try{t.networkSync?await t.client.documents.delete(t.docId,{forceCloseIfOpen:!0}):(await t.client.documents.close(t.docId),await t.client.documents.evict(t.docId,{force:!0}))}catch(e){d.error(`Failed to destroy test document`,{docId:t.docId,err:e})}}async function g(){try{let e=await l.getClientAsync();d.debug(`[deleteAllTestDocuments] Starting cleanup...`),await e.syncMetadata({scope:`all`});let t=(await e.me.ownedDocuments()).filter(e=>e.title?.startsWith(f));if(t.length===0)return 0;d.debug(`[deleteAllTestDocuments] Found test documents to delete`,{count:t.length});let n=0;for(let r of t)try{e.documents.isOpen(r.documentId)&&await e.documents.close(r.documentId),await e.documents.evict(r.documentId,{force:!0}),n++}catch(e){d.error(`[deleteAllTestDocuments] Failed to delete test document`,{documentId:r.documentId,err:e})}return d.debug(`[deleteAllTestDocuments] Cleanup complete`,{evictedCount:n}),n}catch(e){return d.error(`[deleteAllTestDocuments] Top-level error during cleanup`,{err:e}),0}}function _(e){return e.flatMap(e=>e.tests.map(t=>({id:t.id,name:t.name,group:e.name,run:t.run})))}Object.defineProperty(exports,"a",{enumerable:!0,get:function(){return u}}),Object.defineProperty(exports,"c",{enumerable:!0,get:function(){return o}}),Object.defineProperty(exports,"i",{enumerable:!0,get:function(){return h}}),Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return p}}),Object.defineProperty(exports,"o",{enumerable:!0,get:function(){return l}}),Object.defineProperty(exports,"r",{enumerable:!0,get:function(){return g}}),Object.defineProperty(exports,"s",{enumerable:!0,get:function(){return a}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return _}});
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { initializeClient as e } from "js-bao-wss-client";
|
|
2
|
+
//#region src/lib/logger.ts
|
|
3
|
+
var t = {
|
|
4
|
+
debug: 0,
|
|
5
|
+
info: 1,
|
|
6
|
+
warn: 2,
|
|
7
|
+
error: 3,
|
|
8
|
+
none: 4
|
|
9
|
+
}, n = "warn", r = class e {
|
|
10
|
+
#e;
|
|
11
|
+
scope;
|
|
12
|
+
constructor(e, t) {
|
|
13
|
+
this.#e = e, this.scope = t;
|
|
14
|
+
}
|
|
15
|
+
get level() {
|
|
16
|
+
return this.#e.level;
|
|
17
|
+
}
|
|
18
|
+
set level(e) {
|
|
19
|
+
this.setLevel(e);
|
|
20
|
+
}
|
|
21
|
+
debug(...e) {
|
|
22
|
+
this.#t("debug", e);
|
|
23
|
+
}
|
|
24
|
+
log(...e) {
|
|
25
|
+
this.#t("info", e, "log");
|
|
26
|
+
}
|
|
27
|
+
warn(...e) {
|
|
28
|
+
this.#t("warn", e);
|
|
29
|
+
}
|
|
30
|
+
error(...e) {
|
|
31
|
+
this.#t("error", e);
|
|
32
|
+
}
|
|
33
|
+
shouldLog(e) {
|
|
34
|
+
return t[e] >= t[this.level];
|
|
35
|
+
}
|
|
36
|
+
getLevel() {
|
|
37
|
+
return this.level;
|
|
38
|
+
}
|
|
39
|
+
setLevel(e) {
|
|
40
|
+
e in t && (this.#e.level = e);
|
|
41
|
+
}
|
|
42
|
+
forScope(t) {
|
|
43
|
+
let n = t.trim(), r = n ? [...this.scope, n] : this.scope;
|
|
44
|
+
return new e(this.#e, r);
|
|
45
|
+
}
|
|
46
|
+
#t(e, t, n) {
|
|
47
|
+
if (!this.shouldLog(e)) return;
|
|
48
|
+
let r = n ?? (e === "info" ? "log" : e), i = this.scope.length ? `[${this.scope.join(":")}]` : void 0, a = i ? [i, ...t] : t;
|
|
49
|
+
console[r](...a);
|
|
50
|
+
}
|
|
51
|
+
}, i = { level: n }, a = new r(i, []).forScope("PrimitiveApp");
|
|
52
|
+
function o(e) {
|
|
53
|
+
i.level = e;
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
//#region src/services/JsBaoClientService.ts
|
|
57
|
+
var s = a.forScope("JsBaoClientService");
|
|
58
|
+
function c() {
|
|
59
|
+
if (typeof window > "u") return !1;
|
|
60
|
+
let e = window.location.hostname;
|
|
61
|
+
return e === "localhost" || e === "127.0.0.1" || e === "[::1]" || e.endsWith(".localhost") || e.endsWith(".local");
|
|
62
|
+
}
|
|
63
|
+
var l = new class {
|
|
64
|
+
client = null;
|
|
65
|
+
clientInitializationPromise = null;
|
|
66
|
+
config = null;
|
|
67
|
+
initialize(e) {
|
|
68
|
+
s.debug("Initializing with config:", {
|
|
69
|
+
appId: e.appId ? `${e.appId.substring(0, 10)}...` : "MISSING",
|
|
70
|
+
apiUrl: e.apiUrl,
|
|
71
|
+
wsUrl: e.wsUrl,
|
|
72
|
+
oauthRedirectUri: e.oauthRedirectUri
|
|
73
|
+
});
|
|
74
|
+
let t = [];
|
|
75
|
+
(!e.appId || e.appId === "MISSING_APP_ID") && t.push("appId"), (!e.apiUrl || e.apiUrl === "MISSING_API_URL") && t.push("apiUrl"), (!e.wsUrl || e.wsUrl === "MISSING_WS_URL") && t.push("wsUrl"), (!e.oauthRedirectUri || e.oauthRedirectUri === "MISSING_OAUTH_REDIRECT_URI") && t.push("oauthRedirectUri"), t.length > 0 && (s.error("❌ Configuration validation failed!"), s.error("Missing or invalid fields:", t), s.error("This will cause OAuth and API calls to fail."), s.error("Please check your .env file configuration.")), this.config = e;
|
|
76
|
+
}
|
|
77
|
+
getConfig() {
|
|
78
|
+
return this.config;
|
|
79
|
+
}
|
|
80
|
+
async getClientAsync() {
|
|
81
|
+
if (s.debug("Getting client async"), this.clientInitializationPromise) return this.clientInitializationPromise;
|
|
82
|
+
if (!this.config) throw Error("JsBaoClientService not initialized. Call initialize() first.");
|
|
83
|
+
return this.clientInitializationPromise = (async () => {
|
|
84
|
+
s.debug("=== INITIALIZING CLIENT ==="), s.debug("Configuration:", this.config);
|
|
85
|
+
let t = await e(this.config);
|
|
86
|
+
if (t.on("status", (e) => {
|
|
87
|
+
s.debug("Client status changed:", e);
|
|
88
|
+
}), t.on("error", (e) => {
|
|
89
|
+
s.error("Client error:", e);
|
|
90
|
+
}), this.client = t, c() && (window.__primitiveAppClient = t, this.config?.models && Array.isArray(this.config.models))) {
|
|
91
|
+
let e = {};
|
|
92
|
+
for (let t of this.config.models) {
|
|
93
|
+
let n = t.schema?.name;
|
|
94
|
+
if (n) {
|
|
95
|
+
let r = n.charAt(0).toUpperCase() + n.slice(1);
|
|
96
|
+
e[r] = t;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
window.__primitiveAppModels = e, s.debug("Registered models on window:", Object.keys(e));
|
|
100
|
+
}
|
|
101
|
+
return s.debug("=== CLIENT INITIALIZATION COMPLETE ==="), t;
|
|
102
|
+
})(), this.clientInitializationPromise;
|
|
103
|
+
}
|
|
104
|
+
setExternalClient(e) {
|
|
105
|
+
s.debug("Setting external client from host app"), this.client = e, this.clientInitializationPromise = Promise.resolve(e);
|
|
106
|
+
}
|
|
107
|
+
async clearClient() {
|
|
108
|
+
s.debug("Clearing client singleton..."), this.client && (this.client.disconnect().catch((e) => {
|
|
109
|
+
s.warn("Background disconnect failed (this is usually harmless):", e);
|
|
110
|
+
}), this.client = null, this.clientInitializationPromise = null, c() && (window.__primitiveAppClient = void 0, window.__primitiveAppModels = void 0));
|
|
111
|
+
}
|
|
112
|
+
}();
|
|
113
|
+
function u(e) {
|
|
114
|
+
l.initialize(e);
|
|
115
|
+
}
|
|
116
|
+
//#endregion
|
|
117
|
+
//#region src/dev-tools/test-harness/lib.ts
|
|
118
|
+
var d = a.forScope("TestHarness"), f = "===TEST===";
|
|
119
|
+
async function p(e = {}) {
|
|
120
|
+
let { networkSync: t = !1 } = e, n = await l.getClientAsync(), r = `${f} ${`${Date.now()}-${Math.random().toString(36).substring(2, 11)}`}`;
|
|
121
|
+
if (t) {
|
|
122
|
+
let { metadata: e } = await n.documents.create({ title: r }), t = e.documentId, { doc: i } = await n.documents.open(t);
|
|
123
|
+
if (!i) throw Error("[TestHarness] Failed to open synced test document");
|
|
124
|
+
return n.setDefaultDocumentId(t), d.debug("Created synced test document", { docId: t }), m.set(t, {
|
|
125
|
+
client: n,
|
|
126
|
+
docId: t,
|
|
127
|
+
networkSync: !0
|
|
128
|
+
}), { docId: t };
|
|
129
|
+
}
|
|
130
|
+
let { metadata: i } = await n.documents.create({
|
|
131
|
+
title: r,
|
|
132
|
+
localOnly: !0
|
|
133
|
+
}), a = i.documentId, { doc: o } = await n.documents.open(a, {
|
|
134
|
+
waitForLoad: "local",
|
|
135
|
+
enableNetworkSync: !1
|
|
136
|
+
});
|
|
137
|
+
if (!o) throw Error("[TestHarness] Failed to open test document");
|
|
138
|
+
return n.setDefaultDocumentId(a), d.debug("Created test document", { docId: a }), m.set(a, {
|
|
139
|
+
client: n,
|
|
140
|
+
docId: a,
|
|
141
|
+
networkSync: !1
|
|
142
|
+
}), { docId: a };
|
|
143
|
+
}
|
|
144
|
+
var m = /* @__PURE__ */ new Map();
|
|
145
|
+
async function h(e) {
|
|
146
|
+
let t = m.get(e.docId);
|
|
147
|
+
if (!t) {
|
|
148
|
+
d.warn("destroyTestDocument called with unknown handle", { docId: e.docId });
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
m.delete(e.docId);
|
|
152
|
+
try {
|
|
153
|
+
t.networkSync ? await t.client.documents.delete(t.docId, { forceCloseIfOpen: !0 }) : (await t.client.documents.close(t.docId), await t.client.documents.evict(t.docId, { force: !0 }));
|
|
154
|
+
} catch (e) {
|
|
155
|
+
d.error("Failed to destroy test document", {
|
|
156
|
+
docId: t.docId,
|
|
157
|
+
err: e
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
async function g() {
|
|
162
|
+
try {
|
|
163
|
+
let e = await l.getClientAsync();
|
|
164
|
+
d.debug("[deleteAllTestDocuments] Starting cleanup..."), await e.syncMetadata({ scope: "all" });
|
|
165
|
+
let t = (await e.me.ownedDocuments()).filter((e) => e.title?.startsWith(f));
|
|
166
|
+
if (t.length === 0) return 0;
|
|
167
|
+
d.debug("[deleteAllTestDocuments] Found test documents to delete", { count: t.length });
|
|
168
|
+
let n = 0;
|
|
169
|
+
for (let r of t) try {
|
|
170
|
+
e.documents.isOpen(r.documentId) && await e.documents.close(r.documentId), await e.documents.evict(r.documentId, { force: !0 }), n++;
|
|
171
|
+
} catch (e) {
|
|
172
|
+
d.error("[deleteAllTestDocuments] Failed to delete test document", {
|
|
173
|
+
documentId: r.documentId,
|
|
174
|
+
err: e
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
return d.debug("[deleteAllTestDocuments] Cleanup complete", { evictedCount: n }), n;
|
|
178
|
+
} catch (e) {
|
|
179
|
+
return d.error("[deleteAllTestDocuments] Top-level error during cleanup", { err: e }), 0;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
function _(e) {
|
|
183
|
+
return e.flatMap((e) => e.tests.map((t) => ({
|
|
184
|
+
id: t.id,
|
|
185
|
+
name: t.name,
|
|
186
|
+
group: e.name,
|
|
187
|
+
run: t.run
|
|
188
|
+
})));
|
|
189
|
+
}
|
|
190
|
+
//#endregion
|
|
191
|
+
export { u as a, o as c, h as i, p as n, l as o, g as r, a as s, _ as t };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JsBaoClientService.d.ts","sourceRoot":"","sources":["../../src/services/JsBaoClientService.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqFG;AACH,OAAO,EAAE,gBAAgB,EAAE,KAAK,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAK9E,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,oBAAoB,CAAC,EAAE,OAAO,CAAC;QAC/B,oBAAoB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KAChD;CACF;AAED,KAAK,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC,CAAC;AAqBhE;;;;;GAKG;AACH,cAAM,kBAAkB;IACtB,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,2BAA2B,CAAqC;IACxE,OAAO,CAAC,MAAM,CAAmC;IAEjD;;;OAGG;IACH,UAAU,CAAC,MAAM,EAAE,kBAAkB,GAAG,IAAI;IAgC5C;;;OAGG;IACH,SAAS,IAAI,kBAAkB,GAAG,IAAI;IAItC;;;;OAIG;IACG,cAAc,IAAI,OAAO,CAAC,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"JsBaoClientService.d.ts","sourceRoot":"","sources":["../../src/services/JsBaoClientService.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqFG;AACH,OAAO,EAAE,gBAAgB,EAAE,KAAK,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAK9E,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,oBAAoB,CAAC,EAAE,OAAO,CAAC;QAC/B,oBAAoB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KAChD;CACF;AAED,KAAK,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC,CAAC;AAqBhE;;;;;GAKG;AACH,cAAM,kBAAkB;IACtB,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,2BAA2B,CAAqC;IACxE,OAAO,CAAC,MAAM,CAAmC;IAEjD;;;OAGG;IACH,UAAU,CAAC,MAAM,EAAE,kBAAkB,GAAG,IAAI;IAgC5C;;;OAGG;IACH,SAAS,IAAI,kBAAkB,GAAG,IAAI;IAItC;;;;OAIG;IACG,cAAc,IAAI,OAAO,CAAC,WAAW,CAAC;IAgE5C;;;;OAIG;IACH,iBAAiB,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI;IAM5C;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;CAsBnC;AAED,eAAO,MAAM,kBAAkB,oBAA2B,CAAC;AAE3D;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,kBAAkB,GAAG,IAAI,CAEhE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("../rolldown-runtime-BNMv73og.cjs"),t=require("../lib-C53EcrFg.cjs");let n=require("js-bao-wss-client"),r=require("node:fs"),i=require("node:path");i=e.i(i,1);let a=require("vitest");var o=`000000`;function s(e,t,n){if(e)return e;throw Error(`[primitive-app/testing] Missing ${t}: pass it to registerPrimitiveTests() or set the ${n} environment variable.`)}function c(){let e=i.default.resolve(process.cwd(),`public/sql-wasm.wasm`);if(!(0,r.existsSync)(e)){console.warn(`[primitive-app/testing] ${e} not found — using the client's default database config. If client init fails to load sql-wasm.wasm, pass clientOptions.databaseConfig explicitly.`);return}return{type:`sqljs`,options:{locateFile:()=>e}}}async function l(e){let t=[],n=[];for(let[r,i]of Object.entries(e))try{let e=(await i())?.default;Array.isArray(e)?t.push(...e):e&&typeof e==`object`&&`tests`in e?t.push(e):n.push({file:r,error:Error(`Default export is not a TestGroup (expected { name, tests }).`)})}catch(e){n.push({file:r,error:e})}return{groups:t,failures:n}}async function u(e){let{models:r,testModules:i,otpCode:u=o,testTimeoutMs:d=6e4,clientOptions:f,cleanupTestDocuments:p=!0,failBelowFullScore:m=!0}=e,h=s(e.appId??process.env.VITE_APP_ID,`appId`,`VITE_APP_ID`),g=s(e.apiUrl??process.env.VITE_API_URL,`apiUrl`,`VITE_API_URL`),_=s(e.wsUrl??process.env.VITE_WS_URL,`wsUrl`,`VITE_WS_URL`),v=s(e.email??process.env.PRIMITIVE_TEST_EMAIL,`email`,`PRIMITIVE_TEST_EMAIL`),{groups:y,failures:b}=await l(i);for(let e of b)(0,a.describe)(e.file,()=>{(0,a.it)(`loads without errors`,()=>{throw e.error instanceof Error?e.error:Error(String(e.error))})});if(y.length===0){b.length===0&&console.warn(`[primitive-app/testing] No *.primitive-test.ts groups found — nothing to register.`);return}let x;(0,a.beforeAll)(async()=>{x=await(0,n.initializeClient)({apiUrl:g,wsUrl:_,appId:h,models:r,storageConfig:{type:`auto`},databaseConfig:c(),...f});try{await x.otpVerify(v,u)}catch(e){throw Error(`[primitive-app/testing] OTP sign-in failed for ${v}. The test bypass requires the app's testAccountBaseEmails whitelist to cover this address (and invite-only/domain/waitlist apps reject provisioning like any signup). Underlying error: ${e instanceof Error?e.message:String(e)}`)}t.o.initialize({apiUrl:g,wsUrl:_,appId:h,models:r,oauthRedirectUri:f?.oauthRedirectUri??process.env.VITE_OAUTH_REDIRECT_URI??`http://localhost/oauth/callback-unused-in-node-tests`,...f}),t.o.setExternalClient(x)},12e4),(0,a.afterAll)(async()=>{if(x){if(p)try{await t.r()}catch(e){console.warn(`[primitive-app/testing] Test-document cleanup failed:`,e)}await x.disconnect()}},6e4);for(let e of y)(0,a.describe)(e.name,()=>{for(let t of e.tests)(0,a.it)(t.name,async()=>{let e=await t.run(e=>console.log(`[${t.id}] ${e}`));e&&console.log(`[${t.id}] ${e}`);let n=e?.match(/^(\d+)\/(\d+)\s*\((\d+(?:\.\d+)?)%\)/);if(m&&n&&Number(n[1])!==Number(n[2]))throw Error(`Scored below full marks: ${e}`)},d)})}exports.PRIMITIVE_TEST_OTP_CODE=o,exports.registerPrimitiveTests=u;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type JsBaoClientOptions } from "js-bao-wss-client";
|
|
2
|
+
/**
|
|
3
|
+
* The OTP code the server accepts for whitelisted test accounts
|
|
4
|
+
* (the `testAccountBaseEmails` bypass — no email delivery involved).
|
|
5
|
+
*/
|
|
6
|
+
export declare const PRIMITIVE_TEST_OTP_CODE = "000000";
|
|
7
|
+
export interface RegisterPrimitiveTestsOptions {
|
|
8
|
+
/** The app's model classes (typically `allModels` from `@/models`). */
|
|
9
|
+
models: JsBaoClientOptions["models"];
|
|
10
|
+
/**
|
|
11
|
+
* Lazy module loaders for the registered test files, from
|
|
12
|
+
* `import.meta.glob("./**\/*.primitive-test.ts")` in the spec file.
|
|
13
|
+
* Each module's default export must be a `TestGroup` or `TestGroup[]`.
|
|
14
|
+
*/
|
|
15
|
+
testModules: Record<string, () => Promise<unknown>>;
|
|
16
|
+
/** App ID. Defaults to `process.env.VITE_APP_ID`. */
|
|
17
|
+
appId?: string;
|
|
18
|
+
/** API endpoint URL. Defaults to `process.env.VITE_API_URL`. */
|
|
19
|
+
apiUrl?: string;
|
|
20
|
+
/** WebSocket endpoint URL. Defaults to `process.env.VITE_WS_URL`. */
|
|
21
|
+
wsUrl?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Sign-in email for the OTP test bypass. Must resolve to the app's
|
|
24
|
+
* `testAccountBaseEmails` whitelist (e.g. `you+primitivetest-ci@domain.com`
|
|
25
|
+
* when `you@domain.com` is whitelisted). Use a stable suffix per CI project
|
|
26
|
+
* so the find-or-create provisioner reuses one test user across runs.
|
|
27
|
+
* Defaults to `process.env.PRIMITIVE_TEST_EMAIL`.
|
|
28
|
+
*/
|
|
29
|
+
email?: string;
|
|
30
|
+
/** OTP code to verify with. Defaults to the "000000" test bypass. */
|
|
31
|
+
otpCode?: string;
|
|
32
|
+
/** Per-test timeout in ms. Defaults to 60_000. */
|
|
33
|
+
testTimeoutMs?: number;
|
|
34
|
+
/**
|
|
35
|
+
* Extra options merged into `initializeClient` (after the adapter's
|
|
36
|
+
* defaults), e.g. `databaseConfig` or a `storageConfig` override. The
|
|
37
|
+
* default storage is `{ type: "auto" }`: better-sqlite3 when installed,
|
|
38
|
+
* otherwise in-memory — no native deps required in CI.
|
|
39
|
+
*/
|
|
40
|
+
clientOptions?: Partial<JsBaoClientOptions>;
|
|
41
|
+
/**
|
|
42
|
+
* Delete leftover `===TEST===` documents after the run (mirrors the browser
|
|
43
|
+
* panel's cleanup of interrupted runs). Defaults to true.
|
|
44
|
+
*/
|
|
45
|
+
cleanupTestDocuments?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Treat a scored result ("passed/total (pct%)" return message) below a full
|
|
48
|
+
* score as a failure so partial scores gate CI. Defaults to true; the
|
|
49
|
+
* browser panel shows the same result as "scored" without failing.
|
|
50
|
+
*/
|
|
51
|
+
failBelowFullScore?: boolean;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Register every provided test group as vitest `describe`/`it` blocks and
|
|
55
|
+
* bootstrap an authenticated Node js-bao client for them to run against.
|
|
56
|
+
*
|
|
57
|
+
* Call with top-level await from a spec file so the groups are enumerated
|
|
58
|
+
* during vitest collection. A test file that fails to load (import error,
|
|
59
|
+
* wrong export shape) is surfaced as a failing test, never a silent skip.
|
|
60
|
+
*/
|
|
61
|
+
export declare function registerPrimitiveTests(options: RegisterPrimitiveTestsOptions): Promise<void>;
|
|
62
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAiCA,OAAO,EAGL,KAAK,kBAAkB,EACxB,MAAM,mBAAmB,CAAC;AAO3B;;;GAGG;AACH,eAAO,MAAM,uBAAuB,WAAW,CAAC;AAEhD,MAAM,WAAW,6BAA6B;IAC5C,uEAAuE;IACvE,MAAM,EAAE,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IAErC;;;;OAIG;IACH,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;IAEpD,qDAAqD;IACrD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gEAAgE;IAChE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qEAAqE;IACrE,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,kDAAkD;IAClD,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAE5C;;;OAGG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAE/B;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAkED;;;;;;;GAOG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,IAAI,CAAC,CA8If"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { o as e, r as t } from "../lib-zKX4LVeH.js";
|
|
2
|
+
import { initializeClient as n } from "js-bao-wss-client";
|
|
3
|
+
import { existsSync as r } from "node:fs";
|
|
4
|
+
import i from "node:path";
|
|
5
|
+
import { afterAll as a, beforeAll as o, describe as s, it as c } from "vitest";
|
|
6
|
+
//#region src/testing/index.ts
|
|
7
|
+
var l = "000000";
|
|
8
|
+
function u(e, t, n) {
|
|
9
|
+
if (e) return e;
|
|
10
|
+
throw Error(`[primitive-app/testing] Missing ${t}: pass it to registerPrimitiveTests() or set the ${n} environment variable.`);
|
|
11
|
+
}
|
|
12
|
+
function d() {
|
|
13
|
+
let e = i.resolve(process.cwd(), "public/sql-wasm.wasm");
|
|
14
|
+
if (!r(e)) {
|
|
15
|
+
console.warn(`[primitive-app/testing] ${e} not found — using the client's default database config. If client init fails to load sql-wasm.wasm, pass clientOptions.databaseConfig explicitly.`);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
type: "sqljs",
|
|
20
|
+
options: { locateFile: () => e }
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
async function f(e) {
|
|
24
|
+
let t = [], n = [];
|
|
25
|
+
for (let [r, i] of Object.entries(e)) try {
|
|
26
|
+
let e = (await i())?.default;
|
|
27
|
+
Array.isArray(e) ? t.push(...e) : e && typeof e == "object" && "tests" in e ? t.push(e) : n.push({
|
|
28
|
+
file: r,
|
|
29
|
+
error: /* @__PURE__ */ Error("Default export is not a TestGroup (expected { name, tests }).")
|
|
30
|
+
});
|
|
31
|
+
} catch (e) {
|
|
32
|
+
n.push({
|
|
33
|
+
file: r,
|
|
34
|
+
error: e
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
groups: t,
|
|
39
|
+
failures: n
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
async function p(r) {
|
|
43
|
+
let { models: i, testModules: p, otpCode: m = l, testTimeoutMs: h = 6e4, clientOptions: g, cleanupTestDocuments: _ = !0, failBelowFullScore: v = !0 } = r, y = u(r.appId ?? process.env.VITE_APP_ID, "appId", "VITE_APP_ID"), b = u(r.apiUrl ?? process.env.VITE_API_URL, "apiUrl", "VITE_API_URL"), x = u(r.wsUrl ?? process.env.VITE_WS_URL, "wsUrl", "VITE_WS_URL"), S = u(r.email ?? process.env.PRIMITIVE_TEST_EMAIL, "email", "PRIMITIVE_TEST_EMAIL"), { groups: C, failures: w } = await f(p);
|
|
44
|
+
for (let e of w) s(e.file, () => {
|
|
45
|
+
c("loads without errors", () => {
|
|
46
|
+
throw e.error instanceof Error ? e.error : Error(String(e.error));
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
if (C.length === 0) {
|
|
50
|
+
w.length === 0 && console.warn("[primitive-app/testing] No *.primitive-test.ts groups found — nothing to register.");
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
let T;
|
|
54
|
+
o(async () => {
|
|
55
|
+
T = await n({
|
|
56
|
+
apiUrl: b,
|
|
57
|
+
wsUrl: x,
|
|
58
|
+
appId: y,
|
|
59
|
+
models: i,
|
|
60
|
+
storageConfig: { type: "auto" },
|
|
61
|
+
databaseConfig: d(),
|
|
62
|
+
...g
|
|
63
|
+
});
|
|
64
|
+
try {
|
|
65
|
+
await T.otpVerify(S, m);
|
|
66
|
+
} catch (e) {
|
|
67
|
+
throw Error(`[primitive-app/testing] OTP sign-in failed for ${S}. The test bypass requires the app's testAccountBaseEmails whitelist to cover this address (and invite-only/domain/waitlist apps reject provisioning like any signup). Underlying error: ${e instanceof Error ? e.message : String(e)}`);
|
|
68
|
+
}
|
|
69
|
+
e.initialize({
|
|
70
|
+
apiUrl: b,
|
|
71
|
+
wsUrl: x,
|
|
72
|
+
appId: y,
|
|
73
|
+
models: i,
|
|
74
|
+
oauthRedirectUri: g?.oauthRedirectUri ?? process.env.VITE_OAUTH_REDIRECT_URI ?? "http://localhost/oauth/callback-unused-in-node-tests",
|
|
75
|
+
...g
|
|
76
|
+
}), e.setExternalClient(T);
|
|
77
|
+
}, 12e4), a(async () => {
|
|
78
|
+
if (T) {
|
|
79
|
+
if (_) try {
|
|
80
|
+
await t();
|
|
81
|
+
} catch (e) {
|
|
82
|
+
console.warn("[primitive-app/testing] Test-document cleanup failed:", e);
|
|
83
|
+
}
|
|
84
|
+
await T.disconnect();
|
|
85
|
+
}
|
|
86
|
+
}, 6e4);
|
|
87
|
+
for (let e of C) s(e.name, () => {
|
|
88
|
+
for (let t of e.tests) c(t.name, async () => {
|
|
89
|
+
let e = await t.run((e) => console.log(`[${t.id}] ${e}`));
|
|
90
|
+
e && console.log(`[${t.id}] ${e}`);
|
|
91
|
+
let n = e?.match(/^(\d+)\/(\d+)\s*\((\d+(?:\.\d+)?)%\)/);
|
|
92
|
+
if (v && n && Number(n[1]) !== Number(n[2])) throw Error(`Scored below full marks: ${e}`);
|
|
93
|
+
}, h);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
//#endregion
|
|
97
|
+
export { l as PRIMITIVE_TEST_OTP_CODE, p as registerPrimitiveTests };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "primitive-app",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.4",
|
|
4
4
|
"license": "UNLICENSED",
|
|
5
5
|
"author": "Primitive LLC",
|
|
6
6
|
"type": "module",
|
|
@@ -18,6 +18,11 @@
|
|
|
18
18
|
"types": "./dist/dev-tools/vite-plugin/index.d.ts",
|
|
19
19
|
"import": "./dist/dev-tools/vite-plugin/index.js",
|
|
20
20
|
"default": "./dist/dev-tools/vite-plugin/index.js"
|
|
21
|
+
},
|
|
22
|
+
"./testing": {
|
|
23
|
+
"types": "./dist/testing/index.d.ts",
|
|
24
|
+
"import": "./dist/testing/index.js",
|
|
25
|
+
"default": "./dist/testing/index.js"
|
|
21
26
|
}
|
|
22
27
|
},
|
|
23
28
|
"files": [
|
|
@@ -32,21 +37,6 @@
|
|
|
32
37
|
"engines": {
|
|
33
38
|
"node": "^20.19.0 || >=22.12.0"
|
|
34
39
|
},
|
|
35
|
-
"scripts": {
|
|
36
|
-
"prepublishOnly": "pnpm build",
|
|
37
|
-
"dev": "vite",
|
|
38
|
-
"build": "pnpm codegen && run-p type-check \"build-only {@}\" -- && pnpm build:dts",
|
|
39
|
-
"preview": "vite preview",
|
|
40
|
-
"clean": "rm -rf dist && pnpm clean-modules",
|
|
41
|
-
"clean-modules": "rm -rf node_modules pnpm-lock.yaml",
|
|
42
|
-
"build-only": "vite build",
|
|
43
|
-
"build:dts": "vue-tsc -p tsconfig.build.json --declaration --emitDeclarationOnly",
|
|
44
|
-
"type-check": "vue-tsc --build",
|
|
45
|
-
"lint": "eslint . --fix --cache",
|
|
46
|
-
"format:check": "prettier --check src/",
|
|
47
|
-
"format": "prettier --write src/",
|
|
48
|
-
"codegen": "npx js-bao-codegen && pnpm format"
|
|
49
|
-
},
|
|
50
40
|
"dependencies": {
|
|
51
41
|
"@internationalized/date": "^3.12.2",
|
|
52
42
|
"@lucide/vue": "^1.22.0",
|
|
@@ -73,9 +63,13 @@
|
|
|
73
63
|
"pinia": "^3.0.3",
|
|
74
64
|
"vue": "^3.5.22",
|
|
75
65
|
"vue-router": "^4.6.0",
|
|
66
|
+
"vitest": ">=3.0.0",
|
|
76
67
|
"yjs": "*"
|
|
77
68
|
},
|
|
78
69
|
"peerDependenciesMeta": {
|
|
70
|
+
"vitest": {
|
|
71
|
+
"optional": true
|
|
72
|
+
},
|
|
79
73
|
"js-bao": {
|
|
80
74
|
"optional": true
|
|
81
75
|
},
|
|
@@ -100,7 +94,7 @@
|
|
|
100
94
|
"eslint-plugin-vue": "~10.9.2",
|
|
101
95
|
"jiti": "^2.7.0",
|
|
102
96
|
"js-bao": "^0.5.1",
|
|
103
|
-
"js-bao-wss-client": "
|
|
97
|
+
"js-bao-wss-client": "1.3.0-alpha.51",
|
|
104
98
|
"npm-run-all2": "^9.0.2",
|
|
105
99
|
"pinia": "^3.0.4",
|
|
106
100
|
"prettier": "3.9.4",
|
|
@@ -109,8 +103,23 @@
|
|
|
109
103
|
"typescript": "~6.0.3",
|
|
110
104
|
"vite": "^8.1.2",
|
|
111
105
|
"vite-plugin-vue-devtools": "^8.1.5",
|
|
106
|
+
"vitest": "^4.1.10",
|
|
112
107
|
"vue": "^3.5.39",
|
|
113
108
|
"vue-router": "^5.1.0",
|
|
114
109
|
"vue-tsc": "^3.3.6"
|
|
110
|
+
},
|
|
111
|
+
"scripts": {
|
|
112
|
+
"dev": "vite",
|
|
113
|
+
"build": "pnpm codegen && run-p type-check \"build-only {@}\" -- && pnpm build:dts",
|
|
114
|
+
"preview": "vite preview",
|
|
115
|
+
"clean": "rm -rf dist && pnpm clean-modules",
|
|
116
|
+
"clean-modules": "rm -rf node_modules pnpm-lock.yaml",
|
|
117
|
+
"build-only": "vite build",
|
|
118
|
+
"build:dts": "vue-tsc -p tsconfig.build.json --declaration --emitDeclarationOnly",
|
|
119
|
+
"type-check": "vue-tsc --build",
|
|
120
|
+
"lint": "eslint . --fix --cache",
|
|
121
|
+
"format:check": "prettier --check src/",
|
|
122
|
+
"format": "prettier --write src/",
|
|
123
|
+
"codegen": "npx js-bao-codegen && pnpm format"
|
|
115
124
|
}
|
|
116
|
-
}
|
|
125
|
+
}
|