@swarm.ing/pieui 2.1.7 → 2.2.0

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.
Files changed (53) hide show
  1. package/dist/server/card.d.ts +28 -0
  2. package/dist/server/card.d.ts.map +1 -0
  3. package/dist/server/cards/AjaxGroupCard.d.ts +8 -0
  4. package/dist/server/cards/AjaxGroupCard.d.ts.map +1 -0
  5. package/dist/server/cards/HTMLEmbedCard.d.ts +6 -0
  6. package/dist/server/cards/HTMLEmbedCard.d.ts.map +1 -0
  7. package/dist/server/cards/HiddenCard.d.ts +6 -0
  8. package/dist/server/cards/HiddenCard.d.ts.map +1 -0
  9. package/dist/server/cards/OneOfCard.d.ts +8 -0
  10. package/dist/server/cards/OneOfCard.d.ts.map +1 -0
  11. package/dist/server/cards/UnionCard.d.ts +8 -0
  12. package/dist/server/cards/UnionCard.d.ts.map +1 -0
  13. package/dist/server/cards/index.d.ts +6 -0
  14. package/dist/server/cards/index.d.ts.map +1 -0
  15. package/dist/server/form.d.ts +5 -0
  16. package/dist/server/form.d.ts.map +1 -0
  17. package/dist/server/index.d.ts +9 -0
  18. package/dist/server/index.d.ts.map +1 -0
  19. package/dist/server/index.esm.js +382 -0
  20. package/dist/server/index.js +427 -0
  21. package/dist/server/page.d.ts +26 -0
  22. package/dist/server/page.d.ts.map +1 -0
  23. package/dist/server/realtime.d.ts +13 -0
  24. package/dist/server/realtime.d.ts.map +1 -0
  25. package/dist/server/types.d.ts +28 -0
  26. package/dist/server/types.d.ts.map +1 -0
  27. package/dist/server/web.d.ts +23 -0
  28. package/dist/server/web.d.ts.map +1 -0
  29. package/dist/tests/server/card-fill.test.d.ts +2 -0
  30. package/dist/tests/server/card-fill.test.d.ts.map +1 -0
  31. package/dist/tests/server/card-generate.test.d.ts +2 -0
  32. package/dist/tests/server/card-generate.test.d.ts.map +1 -0
  33. package/dist/tests/server/cards.test.d.ts +2 -0
  34. package/dist/tests/server/cards.test.d.ts.map +1 -0
  35. package/dist/tests/server/e2e.test.d.ts +2 -0
  36. package/dist/tests/server/e2e.test.d.ts.map +1 -0
  37. package/dist/tests/server/entry.test.d.ts +2 -0
  38. package/dist/tests/server/entry.test.d.ts.map +1 -0
  39. package/dist/tests/server/form.test.d.ts +2 -0
  40. package/dist/tests/server/form.test.d.ts.map +1 -0
  41. package/dist/tests/server/page.test.d.ts +2 -0
  42. package/dist/tests/server/page.test.d.ts.map +1 -0
  43. package/dist/tests/server/realtime.test.d.ts +2 -0
  44. package/dist/tests/server/realtime.test.d.ts.map +1 -0
  45. package/dist/tests/server/web-ajax.test.d.ts +2 -0
  46. package/dist/tests/server/web-ajax.test.d.ts.map +1 -0
  47. package/dist/tests/server/web-content.test.d.ts +2 -0
  48. package/dist/tests/server/web-content.test.d.ts.map +1 -0
  49. package/dist/tests/server/web-process.test.d.ts +2 -0
  50. package/dist/tests/server/web-process.test.d.ts.map +1 -0
  51. package/dist/tests/server/web-token.test.d.ts +2 -0
  52. package/dist/tests/server/web-token.test.d.ts.map +1 -0
  53. package/package.json +28 -3
@@ -0,0 +1,28 @@
1
+ import type { SocketIOEvent } from './types';
2
+ /**
3
+ * Base card. A server card mirrors its frontend counterpart 1:1: parameterise
4
+ * it with the component's `*Data` interface and pass those fields (plus any
5
+ * child `Card`s) to the constructor — no per-card constructor needed.
6
+ *
7
+ * `generate()` splits own properties into `data` (scalars) vs children (values
8
+ * that are `instanceof Card`). Field names are used verbatim — TS fields are
9
+ * already camelCase, so no key transformation is applied.
10
+ */
11
+ export declare class Card<D extends object = Record<string, never>> {
12
+ /** Registered frontend component name; defaults to the class name. */
13
+ card: string;
14
+ constructor(props?: Partial<D>);
15
+ /** Serialise to the PieUI `{ ...children, card, data }` wire format. */
16
+ generate(): Record<string, unknown>;
17
+ /** Recursively collect named InputCard descendants (incl. self). */
18
+ inputChildLoc(): Record<string, InputCard>;
19
+ /** Assign values to named input children by name, then generate. */
20
+ fill(data: Record<string, unknown>): Record<string, unknown>;
21
+ /** Build a realtime event named `pie{method}_{name}`. */
22
+ createEvent(method: string, data?: Record<string, unknown>): SocketIOEvent;
23
+ }
24
+ /** Input-bearing card (form field). `parse` validates a raw submitted value. */
25
+ export declare class InputCard<D extends object = Record<string, never>> extends Card<D> {
26
+ parse(raw: unknown): unknown;
27
+ }
28
+ //# sourceMappingURL=card.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"card.d.ts","sourceRoot":"","sources":["../../src/server/card.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAQ5C;;;;;;;;GAQG;AACH,qBAAa,IAAI,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC;IACtD,sEAAsE;IACtE,IAAI,EAAE,MAAM,CAAwB;gBAExB,KAAK,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;IAI9B,wEAAwE;IACxE,QAAQ,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IA4BnC,oEAAoE;IACpE,aAAa,IAAI,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC;IAsB1C,oEAAoE;IACpE,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAU5D,yDAAyD;IACzD,WAAW,CACP,MAAM,EAAE,MAAM,EACd,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACnC,aAAa;CAiBnB;AAED,gFAAgF;AAChF,qBAAa,SAAS,CAClB,CAAC,SAAS,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAC1C,SAAQ,IAAI,CAAC,CAAC,CAAC;IACb,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO;CAG/B"}
@@ -0,0 +1,8 @@
1
+ import { Card } from '../card';
2
+ import type { AjaxGroupCardData } from '../../components/Containers/AjaxGroupCard/types';
3
+ /** AJAX-enabled container (single child) — 1:1 with the frontend AjaxGroupCard. */
4
+ export declare class AjaxGroupCard extends Card<AjaxGroupCardData & {
5
+ content: Card;
6
+ }> {
7
+ }
8
+ //# sourceMappingURL=AjaxGroupCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AjaxGroupCard.d.ts","sourceRoot":"","sources":["../../../src/server/cards/AjaxGroupCard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAA;AAC9B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iDAAiD,CAAA;AAExF,mFAAmF;AACnF,qBAAa,aAAc,SAAQ,IAAI,CAAC,iBAAiB,GAAG;IAAE,OAAO,EAAE,IAAI,CAAA;CAAE,CAAC;CAAG"}
@@ -0,0 +1,6 @@
1
+ import { Card } from '../card';
2
+ import type { HTMLEmbedCardData } from '../../components/Common/HTMLEmbedCard/types';
3
+ /** Raw HTML injection — 1:1 with the frontend HTMLEmbedCard. */
4
+ export declare class HTMLEmbedCard extends Card<HTMLEmbedCardData> {
5
+ }
6
+ //# sourceMappingURL=HTMLEmbedCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HTMLEmbedCard.d.ts","sourceRoot":"","sources":["../../../src/server/cards/HTMLEmbedCard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAA;AAC9B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,6CAA6C,CAAA;AAEpF,gEAAgE;AAChE,qBAAa,aAAc,SAAQ,IAAI,CAAC,iBAAiB,CAAC;CAAG"}
@@ -0,0 +1,6 @@
1
+ import { InputCard } from '../card';
2
+ import type { HiddenCardData } from '../../components/Common/HiddenCard/types';
3
+ /** Hidden input field — 1:1 with the frontend HiddenCard. */
4
+ export declare class HiddenCard extends InputCard<HiddenCardData> {
5
+ }
6
+ //# sourceMappingURL=HiddenCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HiddenCard.d.ts","sourceRoot":"","sources":["../../../src/server/cards/HiddenCard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AACnC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0CAA0C,CAAA;AAE9E,6DAA6D;AAC7D,qBAAa,UAAW,SAAQ,SAAS,CAAC,cAAc,CAAC;CAAG"}
@@ -0,0 +1,8 @@
1
+ import { Card } from '../card';
2
+ import type { OneOfCardData } from '../../components/Containers/OneOfCard/types';
3
+ /** One-of-N alternative container — 1:1 with the frontend OneOfCard. */
4
+ export declare class OneOfCard extends Card<OneOfCardData & {
5
+ content: Card[];
6
+ }> {
7
+ }
8
+ //# sourceMappingURL=OneOfCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OneOfCard.d.ts","sourceRoot":"","sources":["../../../src/server/cards/OneOfCard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAA;AAC9B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6CAA6C,CAAA;AAEhF,wEAAwE;AACxE,qBAAa,SAAU,SAAQ,IAAI,CAAC,aAAa,GAAG;IAAE,OAAO,EAAE,IAAI,EAAE,CAAA;CAAE,CAAC;CAAG"}
@@ -0,0 +1,8 @@
1
+ import { Card } from '../card';
2
+ import type { UnionCardData } from '../../components/Containers/UnionCard/types';
3
+ /** Container of child cards — 1:1 with the frontend UnionCard. */
4
+ export declare class UnionCard extends Card<UnionCardData & {
5
+ content: Card[];
6
+ }> {
7
+ }
8
+ //# sourceMappingURL=UnionCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UnionCard.d.ts","sourceRoot":"","sources":["../../../src/server/cards/UnionCard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAA;AAC9B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6CAA6C,CAAA;AAEhF,kEAAkE;AAClE,qBAAa,SAAU,SAAQ,IAAI,CAAC,aAAa,GAAG;IAAE,OAAO,EAAE,IAAI,EAAE,CAAA;CAAE,CAAC;CAAG"}
@@ -0,0 +1,6 @@
1
+ export { HiddenCard } from './HiddenCard';
2
+ export { UnionCard } from './UnionCard';
3
+ export { AjaxGroupCard } from './AjaxGroupCard';
4
+ export { OneOfCard } from './OneOfCard';
5
+ export { HTMLEmbedCard } from './HTMLEmbedCard';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/server/cards/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA"}
@@ -0,0 +1,5 @@
1
+ /** Normalise a parsed form body: a single-element array collapses to a scalar. */
2
+ export declare function form2dict(body: Record<string, unknown>): Record<string, unknown>;
3
+ /** Optional `by_underscore` nesting: `addr__city` → `addr.city`. Mirrors pie aggregate. */
4
+ export declare function aggregate(data: Record<string, unknown>, rule?: 'by_underscore'): Record<string, unknown>;
5
+ //# sourceMappingURL=form.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form.d.ts","sourceRoot":"","sources":["../../src/server/form.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,wBAAgB,SAAS,CACrB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAMzB;AAED,2FAA2F;AAC3F,wBAAgB,SAAS,CACrB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,IAAI,CAAC,EAAE,eAAe,GACvB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAezB"}
@@ -0,0 +1,9 @@
1
+ export { Card, InputCard } from './card';
2
+ export { AsyncPage } from './page';
3
+ export { Web } from './web';
4
+ export { publishToCentrifuge } from './realtime';
5
+ export * from './cards';
6
+ export { form2dict, aggregate } from './form';
7
+ export type { Publisher } from './realtime';
8
+ export type { SocketIOEvent, WebOptions, PageContext, UIConfigType, } from './types';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;AAClC,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAA;AAC3B,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA;AAChD,cAAc,SAAS,CAAA;AACvB,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;AAC7C,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAC3C,YAAY,EACR,aAAa,EACb,UAAU,EACV,WAAW,EACX,YAAY,GACf,MAAM,SAAS,CAAA"}
@@ -0,0 +1,382 @@
1
+ // src/server/card.ts
2
+ function isCardRecord(v) {
3
+ if (typeof v !== "object" || v === null || Array.isArray(v))
4
+ return false;
5
+ const vals = Object.values(v);
6
+ return vals.length > 0 && vals.every((x) => x instanceof Card);
7
+ }
8
+
9
+ class Card {
10
+ card = this.constructor.name;
11
+ constructor(props) {
12
+ if (props)
13
+ Object.assign(this, props);
14
+ }
15
+ generate() {
16
+ const data = {};
17
+ const children = {};
18
+ for (const key of Object.keys(this)) {
19
+ if (key === "card")
20
+ continue;
21
+ const value = this[key];
22
+ if (value instanceof Card) {
23
+ children[key] = value.generate();
24
+ } else if (Array.isArray(value) && value.length > 0 && value.every((v) => v instanceof Card)) {
25
+ children[key] = value.map((v) => v.generate());
26
+ } else if (isCardRecord(value)) {
27
+ children[key] = Object.fromEntries(Object.entries(value).map(([k, v]) => [
28
+ k,
29
+ v.generate()
30
+ ]));
31
+ } else {
32
+ data[key] = value;
33
+ }
34
+ }
35
+ return { ...children, card: this.card, data };
36
+ }
37
+ inputChildLoc() {
38
+ const acc = {};
39
+ const visit = (card) => {
40
+ const name = card.name;
41
+ if (card instanceof InputCard && typeof name === "string") {
42
+ acc[name] = card;
43
+ }
44
+ for (const key of Object.keys(card)) {
45
+ const v = card[key];
46
+ if (v instanceof Card)
47
+ visit(v);
48
+ else if (Array.isArray(v))
49
+ v.forEach((x) => x instanceof Card && visit(x));
50
+ else if (v && typeof v === "object")
51
+ Object.values(v).forEach((x) => x instanceof Card && visit(x));
52
+ }
53
+ };
54
+ visit(this);
55
+ return acc;
56
+ }
57
+ fill(data) {
58
+ const loc = this.inputChildLoc();
59
+ for (const [name, card] of Object.entries(loc)) {
60
+ if (name in data) {
61
+ card.value = data[name];
62
+ }
63
+ }
64
+ return this.generate();
65
+ }
66
+ createEvent(method, data = {}) {
67
+ const name = this.name;
68
+ if (typeof name !== "string") {
69
+ throw new Error("Card.createEvent requires a string `name`");
70
+ }
71
+ const self = this;
72
+ const io = self.useSocketioSupport || self.useCentrifugeSupport || self.useMittSupport;
73
+ if (!io) {
74
+ throw new Error("Card.createEvent requires an IO support flag enabled");
75
+ }
76
+ return { name: `pie${method}_${name}`, data };
77
+ }
78
+ }
79
+
80
+ class InputCard extends Card {
81
+ parse(raw) {
82
+ return raw;
83
+ }
84
+ }
85
+ // src/server/page.ts
86
+ class AsyncPage {
87
+ fields;
88
+ isTyped;
89
+ ajaxPost = {};
90
+ ajaxGet = {};
91
+ publisher;
92
+ constructor(isTyped = false) {
93
+ this.isTyped = isTyped;
94
+ }
95
+ async getContent(_ctx = {}) {
96
+ if (this.fields)
97
+ return this.fields.fill({});
98
+ throw new Error("getContent not implemented and no `fields` set");
99
+ }
100
+ async process(_data) {
101
+ throw new Error("process not implemented");
102
+ }
103
+ registerAjax(pathname, fn, method = "POST") {
104
+ if (method === "POST")
105
+ this.ajaxPost[pathname] = fn;
106
+ else
107
+ this.ajaxGet[pathname] = fn;
108
+ }
109
+ setPublisher(p) {
110
+ this.publisher = p;
111
+ }
112
+ async emit(event, to) {
113
+ if (!this.publisher) {
114
+ throw new Error("no publisher configured (Web must call setPublisher)");
115
+ }
116
+ const events = Array.isArray(event) ? event : [event];
117
+ for (const ev of events)
118
+ await this.publisher(ev, to);
119
+ }
120
+ }
121
+ // src/server/web.ts
122
+ import express from "express";
123
+ import cookieParser from "cookie-parser";
124
+ import jwt from "jsonwebtoken";
125
+ import { createHash } from "node:crypto";
126
+
127
+ // src/server/form.ts
128
+ function form2dict(body) {
129
+ const out = {};
130
+ for (const [k, v] of Object.entries(body ?? {})) {
131
+ out[k] = Array.isArray(v) && v.length === 1 ? v[0] : v;
132
+ }
133
+ return out;
134
+ }
135
+ function aggregate(data, rule) {
136
+ if (rule !== "by_underscore")
137
+ return data;
138
+ const out = {};
139
+ for (const [k, v] of Object.entries(data)) {
140
+ const idx = k.indexOf("__");
141
+ if (idx === -1) {
142
+ out[k] = v;
143
+ } else {
144
+ const parent = k.slice(0, idx);
145
+ const child = k.slice(idx + 2);
146
+ const bucket = out[parent] ??= {};
147
+ bucket[child] = v;
148
+ }
149
+ }
150
+ return out;
151
+ }
152
+
153
+ // src/server/realtime.ts
154
+ async function publishToCentrifuge(opts, fetchFn = fetch) {
155
+ const res = await fetchFn(`${opts.url.replace(/\/$/, "")}/api/publish`, {
156
+ method: "POST",
157
+ headers: {
158
+ "Content-Type": "application/json",
159
+ "X-API-Key": opts.apiKey
160
+ },
161
+ body: JSON.stringify({ channel: opts.channel, data: opts.data })
162
+ });
163
+ if (!res.ok) {
164
+ throw new Error(`centrifuge publish failed: ${res.status}`);
165
+ }
166
+ }
167
+
168
+ // src/server/web.ts
169
+ function escapeRegExp(s) {
170
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
171
+ }
172
+
173
+ class Web {
174
+ pages;
175
+ opts;
176
+ constructor(pages, opts = {}) {
177
+ this.pages = pages;
178
+ const adminSubdomain = opts.adminSubdomain ?? "/";
179
+ if (!adminSubdomain.startsWith("/") || !adminSubdomain.endsWith("/")) {
180
+ throw new Error('adminSubdomain must start and end with "/"');
181
+ }
182
+ this.opts = { ...opts, adminSubdomain };
183
+ this.wirePublishers();
184
+ }
185
+ wirePublishers() {
186
+ if (!this.opts.useCentrifugeSupport || !this.opts.centrifugeUrl || !this.opts.centrifugeApiKey) {
187
+ return;
188
+ }
189
+ for (const entry of Object.values(this.pages)) {
190
+ if (typeof entry === "string")
191
+ continue;
192
+ entry.setPublisher(async (ev, to) => {
193
+ const channel = to ? `${ev.name}_${to}` : ev.name;
194
+ await publishToCentrifuge({
195
+ url: this.opts.centrifugeUrl,
196
+ apiKey: this.opts.centrifugeApiKey,
197
+ channel,
198
+ data: ev.data
199
+ });
200
+ });
201
+ }
202
+ }
203
+ resolvePage(pathname) {
204
+ let key = pathname.replace(/^\/+/, "");
205
+ let hops = 0;
206
+ while (hops++ < 10) {
207
+ const entry = this.pages[key];
208
+ if (entry === undefined)
209
+ return null;
210
+ if (typeof entry === "string") {
211
+ key = entry;
212
+ continue;
213
+ }
214
+ return entry;
215
+ }
216
+ return null;
217
+ }
218
+ contextFromRequest(req) {
219
+ const ctx = {};
220
+ for (const k of this.opts.cookieKeys ?? []) {
221
+ const c = req.cookies?.[k];
222
+ if (c !== undefined)
223
+ ctx[k] = c;
224
+ }
225
+ Object.assign(ctx, req.query);
226
+ return ctx;
227
+ }
228
+ collectAjax(kind) {
229
+ const map = {};
230
+ for (const entry of Object.values(this.pages)) {
231
+ if (typeof entry === "string")
232
+ continue;
233
+ Object.assign(map, entry[kind]);
234
+ }
235
+ return map;
236
+ }
237
+ getApp() {
238
+ const app = express();
239
+ app.use(express.urlencoded({ extended: true }));
240
+ app.use(express.json());
241
+ app.use(cookieParser());
242
+ if (this.opts.enableCors)
243
+ app.use(this.corsMiddleware());
244
+ const sub = this.opts.adminSubdomain;
245
+ const contentPrefix = `${sub}api/content/`;
246
+ const processPrefix = `${sub}api/process/`;
247
+ const ajaxPrefix = `${sub}api/ajax_content/`;
248
+ app.get(new RegExp("^" + escapeRegExp(contentPrefix)), async (req, res) => {
249
+ const pathname = decodeURIComponent(req.path.slice(contentPrefix.length));
250
+ const page = this.resolvePage(pathname);
251
+ if (!page) {
252
+ res.status(404).json({ error: "page not found" });
253
+ return;
254
+ }
255
+ try {
256
+ res.json(await page.getContent(this.contextFromRequest(req)));
257
+ } catch (e) {
258
+ res.status(500).json({ error: String(e) });
259
+ }
260
+ });
261
+ app.post(new RegExp("^" + escapeRegExp(processPrefix)), async (req, res) => {
262
+ const pathname = decodeURIComponent(req.path.slice(processPrefix.length));
263
+ const page = this.resolvePage(pathname);
264
+ if (!page) {
265
+ res.status(404).json({ error: "page not found" });
266
+ return;
267
+ }
268
+ const data = aggregate(form2dict(req.body), this.opts.aggregationRule);
269
+ try {
270
+ const result = await page.process(data);
271
+ if (typeof result !== "string") {
272
+ if (!res.headersSent)
273
+ res.status(204).end();
274
+ return;
275
+ }
276
+ let url = result;
277
+ if (url.startsWith("/")) {
278
+ url = sub + url.slice(1);
279
+ if (this.opts.disableServing && this.opts.servingUrl) {
280
+ url = this.opts.servingUrl + url;
281
+ }
282
+ }
283
+ res.redirect(303, url);
284
+ } catch (e) {
285
+ res.status(500).json({ error: String(e) });
286
+ }
287
+ });
288
+ const postAjax = this.collectAjax("ajaxPost");
289
+ const getAjax = this.collectAjax("ajaxGet");
290
+ app.post(new RegExp("^" + escapeRegExp(ajaxPrefix)), async (req, res) => {
291
+ const pathname = "/" + req.path.slice(ajaxPrefix.length);
292
+ const fn = postAjax[pathname];
293
+ if (!fn) {
294
+ res.status(404).json({ error: "ajax handler not found" });
295
+ return;
296
+ }
297
+ const data = aggregate(form2dict(req.body), this.opts.aggregationRule);
298
+ res.json(await fn(data));
299
+ });
300
+ app.get(new RegExp("^" + escapeRegExp(ajaxPrefix)), async (req, res) => {
301
+ const pathname = "/" + req.path.slice(ajaxPrefix.length);
302
+ const fn = getAjax[pathname];
303
+ if (!fn) {
304
+ res.status(404).json({ error: "ajax handler not found" });
305
+ return;
306
+ }
307
+ const data = aggregate({ ...req.query }, this.opts.aggregationRule);
308
+ res.json(await fn(data));
309
+ });
310
+ app.get(`${sub}api/support/:name`, (req, res) => {
311
+ const name = req.params.name;
312
+ const supported = name === "socketio" ? !!this.opts.useSocketioSupport : name === "centrifuge" ? !!this.opts.useCentrifugeSupport : false;
313
+ res.json(supported);
314
+ });
315
+ app.get("/api/centrifuge/gen_token", (req, res) => {
316
+ const secret = this.opts.centrifugeHmacSecret;
317
+ if (!secret) {
318
+ res.status(403).json({ error: "no centrifuge secret" });
319
+ return;
320
+ }
321
+ const cookies = {};
322
+ for (const k of this.opts.cookieKeys ?? []) {
323
+ const v = req.cookies?.[k];
324
+ if (typeof v === "string")
325
+ cookies[k] = v;
326
+ }
327
+ const subId = this.opts.centrifugeSubFn?.(cookies) ?? createHash("sha256").update(JSON.stringify(cookies)).digest("hex");
328
+ const now = Math.floor(Date.now() / 1000);
329
+ const token = jwt.sign({ sub: subId, iat: now, exp: now + 3600 }, secret, { algorithm: "HS256" });
330
+ res.json({ token });
331
+ });
332
+ return app;
333
+ }
334
+ corsMiddleware() {
335
+ const origins = this.opts.corsOrigins;
336
+ return (req, res, next) => {
337
+ const origin = req.headers.origin;
338
+ if (!origins || origin && origins.includes(origin)) {
339
+ res.setHeader("Access-Control-Allow-Origin", origin ?? "*");
340
+ }
341
+ res.setHeader("Access-Control-Allow-Credentials", "true");
342
+ res.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS");
343
+ res.setHeader("Vary", "Origin");
344
+ const reqHeaders = req.headers["access-control-request-headers"];
345
+ res.setHeader("Access-Control-Allow-Headers", (Array.isArray(reqHeaders) ? reqHeaders.join(",") : reqHeaders) || "Content-Type");
346
+ if (req.method === "OPTIONS") {
347
+ res.sendStatus(204);
348
+ return;
349
+ }
350
+ next();
351
+ };
352
+ }
353
+ }
354
+ // src/server/cards/HiddenCard.ts
355
+ class HiddenCard extends InputCard {
356
+ }
357
+ // src/server/cards/UnionCard.ts
358
+ class UnionCard extends Card {
359
+ }
360
+ // src/server/cards/AjaxGroupCard.ts
361
+ class AjaxGroupCard extends Card {
362
+ }
363
+ // src/server/cards/OneOfCard.ts
364
+ class OneOfCard extends Card {
365
+ }
366
+ // src/server/cards/HTMLEmbedCard.ts
367
+ class HTMLEmbedCard extends Card {
368
+ }
369
+ export {
370
+ publishToCentrifuge,
371
+ form2dict,
372
+ aggregate,
373
+ Web,
374
+ UnionCard,
375
+ OneOfCard,
376
+ InputCard,
377
+ HiddenCard,
378
+ HTMLEmbedCard,
379
+ Card,
380
+ AsyncPage,
381
+ AjaxGroupCard
382
+ };
@@ -0,0 +1,427 @@
1
+ var __create = Object.create;
2
+ var __getProtoOf = Object.getPrototypeOf;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __toESM = (mod, isNodeMode, target) => {
8
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
9
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
10
+ for (let key of __getOwnPropNames(mod))
11
+ if (!__hasOwnProp.call(to, key))
12
+ __defProp(to, key, {
13
+ get: () => mod[key],
14
+ enumerable: true
15
+ });
16
+ return to;
17
+ };
18
+ var __moduleCache = /* @__PURE__ */ new WeakMap;
19
+ var __toCommonJS = (from) => {
20
+ var entry = __moduleCache.get(from), desc;
21
+ if (entry)
22
+ return entry;
23
+ entry = __defProp({}, "__esModule", { value: true });
24
+ if (from && typeof from === "object" || typeof from === "function")
25
+ __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
26
+ get: () => from[key],
27
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
28
+ }));
29
+ __moduleCache.set(from, entry);
30
+ return entry;
31
+ };
32
+ var __export = (target, all) => {
33
+ for (var name in all)
34
+ __defProp(target, name, {
35
+ get: all[name],
36
+ enumerable: true,
37
+ configurable: true,
38
+ set: (newValue) => all[name] = () => newValue
39
+ });
40
+ };
41
+
42
+ // src/server/index.ts
43
+ var exports_server = {};
44
+ __export(exports_server, {
45
+ publishToCentrifuge: () => publishToCentrifuge,
46
+ form2dict: () => form2dict,
47
+ aggregate: () => aggregate,
48
+ Web: () => Web,
49
+ UnionCard: () => UnionCard,
50
+ OneOfCard: () => OneOfCard,
51
+ InputCard: () => InputCard,
52
+ HiddenCard: () => HiddenCard,
53
+ HTMLEmbedCard: () => HTMLEmbedCard,
54
+ Card: () => Card,
55
+ AsyncPage: () => AsyncPage,
56
+ AjaxGroupCard: () => AjaxGroupCard
57
+ });
58
+ module.exports = __toCommonJS(exports_server);
59
+
60
+ // src/server/card.ts
61
+ function isCardRecord(v) {
62
+ if (typeof v !== "object" || v === null || Array.isArray(v))
63
+ return false;
64
+ const vals = Object.values(v);
65
+ return vals.length > 0 && vals.every((x) => x instanceof Card);
66
+ }
67
+
68
+ class Card {
69
+ card = this.constructor.name;
70
+ constructor(props) {
71
+ if (props)
72
+ Object.assign(this, props);
73
+ }
74
+ generate() {
75
+ const data = {};
76
+ const children = {};
77
+ for (const key of Object.keys(this)) {
78
+ if (key === "card")
79
+ continue;
80
+ const value = this[key];
81
+ if (value instanceof Card) {
82
+ children[key] = value.generate();
83
+ } else if (Array.isArray(value) && value.length > 0 && value.every((v) => v instanceof Card)) {
84
+ children[key] = value.map((v) => v.generate());
85
+ } else if (isCardRecord(value)) {
86
+ children[key] = Object.fromEntries(Object.entries(value).map(([k, v]) => [
87
+ k,
88
+ v.generate()
89
+ ]));
90
+ } else {
91
+ data[key] = value;
92
+ }
93
+ }
94
+ return { ...children, card: this.card, data };
95
+ }
96
+ inputChildLoc() {
97
+ const acc = {};
98
+ const visit = (card) => {
99
+ const name = card.name;
100
+ if (card instanceof InputCard && typeof name === "string") {
101
+ acc[name] = card;
102
+ }
103
+ for (const key of Object.keys(card)) {
104
+ const v = card[key];
105
+ if (v instanceof Card)
106
+ visit(v);
107
+ else if (Array.isArray(v))
108
+ v.forEach((x) => x instanceof Card && visit(x));
109
+ else if (v && typeof v === "object")
110
+ Object.values(v).forEach((x) => x instanceof Card && visit(x));
111
+ }
112
+ };
113
+ visit(this);
114
+ return acc;
115
+ }
116
+ fill(data) {
117
+ const loc = this.inputChildLoc();
118
+ for (const [name, card] of Object.entries(loc)) {
119
+ if (name in data) {
120
+ card.value = data[name];
121
+ }
122
+ }
123
+ return this.generate();
124
+ }
125
+ createEvent(method, data = {}) {
126
+ const name = this.name;
127
+ if (typeof name !== "string") {
128
+ throw new Error("Card.createEvent requires a string `name`");
129
+ }
130
+ const self = this;
131
+ const io = self.useSocketioSupport || self.useCentrifugeSupport || self.useMittSupport;
132
+ if (!io) {
133
+ throw new Error("Card.createEvent requires an IO support flag enabled");
134
+ }
135
+ return { name: `pie${method}_${name}`, data };
136
+ }
137
+ }
138
+
139
+ class InputCard extends Card {
140
+ parse(raw) {
141
+ return raw;
142
+ }
143
+ }
144
+ // src/server/page.ts
145
+ class AsyncPage {
146
+ fields;
147
+ isTyped;
148
+ ajaxPost = {};
149
+ ajaxGet = {};
150
+ publisher;
151
+ constructor(isTyped = false) {
152
+ this.isTyped = isTyped;
153
+ }
154
+ async getContent(_ctx = {}) {
155
+ if (this.fields)
156
+ return this.fields.fill({});
157
+ throw new Error("getContent not implemented and no `fields` set");
158
+ }
159
+ async process(_data) {
160
+ throw new Error("process not implemented");
161
+ }
162
+ registerAjax(pathname, fn, method = "POST") {
163
+ if (method === "POST")
164
+ this.ajaxPost[pathname] = fn;
165
+ else
166
+ this.ajaxGet[pathname] = fn;
167
+ }
168
+ setPublisher(p) {
169
+ this.publisher = p;
170
+ }
171
+ async emit(event, to) {
172
+ if (!this.publisher) {
173
+ throw new Error("no publisher configured (Web must call setPublisher)");
174
+ }
175
+ const events = Array.isArray(event) ? event : [event];
176
+ for (const ev of events)
177
+ await this.publisher(ev, to);
178
+ }
179
+ }
180
+ // src/server/web.ts
181
+ var import_express = __toESM(require("express"));
182
+ var import_cookie_parser = __toESM(require("cookie-parser"));
183
+ var import_jsonwebtoken = __toESM(require("jsonwebtoken"));
184
+ var import_node_crypto = require("node:crypto");
185
+
186
+ // src/server/form.ts
187
+ function form2dict(body) {
188
+ const out = {};
189
+ for (const [k, v] of Object.entries(body ?? {})) {
190
+ out[k] = Array.isArray(v) && v.length === 1 ? v[0] : v;
191
+ }
192
+ return out;
193
+ }
194
+ function aggregate(data, rule) {
195
+ if (rule !== "by_underscore")
196
+ return data;
197
+ const out = {};
198
+ for (const [k, v] of Object.entries(data)) {
199
+ const idx = k.indexOf("__");
200
+ if (idx === -1) {
201
+ out[k] = v;
202
+ } else {
203
+ const parent = k.slice(0, idx);
204
+ const child = k.slice(idx + 2);
205
+ const bucket = out[parent] ??= {};
206
+ bucket[child] = v;
207
+ }
208
+ }
209
+ return out;
210
+ }
211
+
212
+ // src/server/realtime.ts
213
+ async function publishToCentrifuge(opts, fetchFn = fetch) {
214
+ const res = await fetchFn(`${opts.url.replace(/\/$/, "")}/api/publish`, {
215
+ method: "POST",
216
+ headers: {
217
+ "Content-Type": "application/json",
218
+ "X-API-Key": opts.apiKey
219
+ },
220
+ body: JSON.stringify({ channel: opts.channel, data: opts.data })
221
+ });
222
+ if (!res.ok) {
223
+ throw new Error(`centrifuge publish failed: ${res.status}`);
224
+ }
225
+ }
226
+
227
+ // src/server/web.ts
228
+ function escapeRegExp(s) {
229
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
230
+ }
231
+
232
+ class Web {
233
+ pages;
234
+ opts;
235
+ constructor(pages, opts = {}) {
236
+ this.pages = pages;
237
+ const adminSubdomain = opts.adminSubdomain ?? "/";
238
+ if (!adminSubdomain.startsWith("/") || !adminSubdomain.endsWith("/")) {
239
+ throw new Error('adminSubdomain must start and end with "/"');
240
+ }
241
+ this.opts = { ...opts, adminSubdomain };
242
+ this.wirePublishers();
243
+ }
244
+ wirePublishers() {
245
+ if (!this.opts.useCentrifugeSupport || !this.opts.centrifugeUrl || !this.opts.centrifugeApiKey) {
246
+ return;
247
+ }
248
+ for (const entry of Object.values(this.pages)) {
249
+ if (typeof entry === "string")
250
+ continue;
251
+ entry.setPublisher(async (ev, to) => {
252
+ const channel = to ? `${ev.name}_${to}` : ev.name;
253
+ await publishToCentrifuge({
254
+ url: this.opts.centrifugeUrl,
255
+ apiKey: this.opts.centrifugeApiKey,
256
+ channel,
257
+ data: ev.data
258
+ });
259
+ });
260
+ }
261
+ }
262
+ resolvePage(pathname) {
263
+ let key = pathname.replace(/^\/+/, "");
264
+ let hops = 0;
265
+ while (hops++ < 10) {
266
+ const entry = this.pages[key];
267
+ if (entry === undefined)
268
+ return null;
269
+ if (typeof entry === "string") {
270
+ key = entry;
271
+ continue;
272
+ }
273
+ return entry;
274
+ }
275
+ return null;
276
+ }
277
+ contextFromRequest(req) {
278
+ const ctx = {};
279
+ for (const k of this.opts.cookieKeys ?? []) {
280
+ const c = req.cookies?.[k];
281
+ if (c !== undefined)
282
+ ctx[k] = c;
283
+ }
284
+ Object.assign(ctx, req.query);
285
+ return ctx;
286
+ }
287
+ collectAjax(kind) {
288
+ const map = {};
289
+ for (const entry of Object.values(this.pages)) {
290
+ if (typeof entry === "string")
291
+ continue;
292
+ Object.assign(map, entry[kind]);
293
+ }
294
+ return map;
295
+ }
296
+ getApp() {
297
+ const app = import_express.default();
298
+ app.use(import_express.default.urlencoded({ extended: true }));
299
+ app.use(import_express.default.json());
300
+ app.use(import_cookie_parser.default());
301
+ if (this.opts.enableCors)
302
+ app.use(this.corsMiddleware());
303
+ const sub = this.opts.adminSubdomain;
304
+ const contentPrefix = `${sub}api/content/`;
305
+ const processPrefix = `${sub}api/process/`;
306
+ const ajaxPrefix = `${sub}api/ajax_content/`;
307
+ app.get(new RegExp("^" + escapeRegExp(contentPrefix)), async (req, res) => {
308
+ const pathname = decodeURIComponent(req.path.slice(contentPrefix.length));
309
+ const page = this.resolvePage(pathname);
310
+ if (!page) {
311
+ res.status(404).json({ error: "page not found" });
312
+ return;
313
+ }
314
+ try {
315
+ res.json(await page.getContent(this.contextFromRequest(req)));
316
+ } catch (e) {
317
+ res.status(500).json({ error: String(e) });
318
+ }
319
+ });
320
+ app.post(new RegExp("^" + escapeRegExp(processPrefix)), async (req, res) => {
321
+ const pathname = decodeURIComponent(req.path.slice(processPrefix.length));
322
+ const page = this.resolvePage(pathname);
323
+ if (!page) {
324
+ res.status(404).json({ error: "page not found" });
325
+ return;
326
+ }
327
+ const data = aggregate(form2dict(req.body), this.opts.aggregationRule);
328
+ try {
329
+ const result = await page.process(data);
330
+ if (typeof result !== "string") {
331
+ if (!res.headersSent)
332
+ res.status(204).end();
333
+ return;
334
+ }
335
+ let url = result;
336
+ if (url.startsWith("/")) {
337
+ url = sub + url.slice(1);
338
+ if (this.opts.disableServing && this.opts.servingUrl) {
339
+ url = this.opts.servingUrl + url;
340
+ }
341
+ }
342
+ res.redirect(303, url);
343
+ } catch (e) {
344
+ res.status(500).json({ error: String(e) });
345
+ }
346
+ });
347
+ const postAjax = this.collectAjax("ajaxPost");
348
+ const getAjax = this.collectAjax("ajaxGet");
349
+ app.post(new RegExp("^" + escapeRegExp(ajaxPrefix)), async (req, res) => {
350
+ const pathname = "/" + req.path.slice(ajaxPrefix.length);
351
+ const fn = postAjax[pathname];
352
+ if (!fn) {
353
+ res.status(404).json({ error: "ajax handler not found" });
354
+ return;
355
+ }
356
+ const data = aggregate(form2dict(req.body), this.opts.aggregationRule);
357
+ res.json(await fn(data));
358
+ });
359
+ app.get(new RegExp("^" + escapeRegExp(ajaxPrefix)), async (req, res) => {
360
+ const pathname = "/" + req.path.slice(ajaxPrefix.length);
361
+ const fn = getAjax[pathname];
362
+ if (!fn) {
363
+ res.status(404).json({ error: "ajax handler not found" });
364
+ return;
365
+ }
366
+ const data = aggregate({ ...req.query }, this.opts.aggregationRule);
367
+ res.json(await fn(data));
368
+ });
369
+ app.get(`${sub}api/support/:name`, (req, res) => {
370
+ const name = req.params.name;
371
+ const supported = name === "socketio" ? !!this.opts.useSocketioSupport : name === "centrifuge" ? !!this.opts.useCentrifugeSupport : false;
372
+ res.json(supported);
373
+ });
374
+ app.get("/api/centrifuge/gen_token", (req, res) => {
375
+ const secret = this.opts.centrifugeHmacSecret;
376
+ if (!secret) {
377
+ res.status(403).json({ error: "no centrifuge secret" });
378
+ return;
379
+ }
380
+ const cookies = {};
381
+ for (const k of this.opts.cookieKeys ?? []) {
382
+ const v = req.cookies?.[k];
383
+ if (typeof v === "string")
384
+ cookies[k] = v;
385
+ }
386
+ const subId = this.opts.centrifugeSubFn?.(cookies) ?? import_node_crypto.createHash("sha256").update(JSON.stringify(cookies)).digest("hex");
387
+ const now = Math.floor(Date.now() / 1000);
388
+ const token = import_jsonwebtoken.default.sign({ sub: subId, iat: now, exp: now + 3600 }, secret, { algorithm: "HS256" });
389
+ res.json({ token });
390
+ });
391
+ return app;
392
+ }
393
+ corsMiddleware() {
394
+ const origins = this.opts.corsOrigins;
395
+ return (req, res, next) => {
396
+ const origin = req.headers.origin;
397
+ if (!origins || origin && origins.includes(origin)) {
398
+ res.setHeader("Access-Control-Allow-Origin", origin ?? "*");
399
+ }
400
+ res.setHeader("Access-Control-Allow-Credentials", "true");
401
+ res.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS");
402
+ res.setHeader("Vary", "Origin");
403
+ const reqHeaders = req.headers["access-control-request-headers"];
404
+ res.setHeader("Access-Control-Allow-Headers", (Array.isArray(reqHeaders) ? reqHeaders.join(",") : reqHeaders) || "Content-Type");
405
+ if (req.method === "OPTIONS") {
406
+ res.sendStatus(204);
407
+ return;
408
+ }
409
+ next();
410
+ };
411
+ }
412
+ }
413
+ // src/server/cards/HiddenCard.ts
414
+ class HiddenCard extends InputCard {
415
+ }
416
+ // src/server/cards/UnionCard.ts
417
+ class UnionCard extends Card {
418
+ }
419
+ // src/server/cards/AjaxGroupCard.ts
420
+ class AjaxGroupCard extends Card {
421
+ }
422
+ // src/server/cards/OneOfCard.ts
423
+ class OneOfCard extends Card {
424
+ }
425
+ // src/server/cards/HTMLEmbedCard.ts
426
+ class HTMLEmbedCard extends Card {
427
+ }
@@ -0,0 +1,26 @@
1
+ import { Card } from './card';
2
+ import type { SocketIOEvent } from './types';
3
+ import type { Publisher } from './realtime';
4
+ type AjaxFn = (data: Record<string, unknown>) => Promise<unknown>;
5
+ /**
6
+ * Base page. Mirrors pie's AsyncPage: holds a `fields` card tree and exposes
7
+ * `getContent`/`process`/`registerAjax`/`emit`.
8
+ */
9
+ export declare class AsyncPage {
10
+ fields?: Card;
11
+ isTyped: boolean;
12
+ ajaxPost: Record<string, AjaxFn>;
13
+ ajaxGet: Record<string, AjaxFn>;
14
+ private publisher?;
15
+ constructor(isTyped?: boolean);
16
+ /** Return the filled UIConfig. Default fills `fields` with `{}`; override to use ctx. */
17
+ getContent(_ctx?: Record<string, unknown>): Promise<Record<string, unknown>>;
18
+ /** Handle a form submission. Return a string URL (→303 redirect). */
19
+ process(_data: Record<string, unknown>): Promise<string | unknown>;
20
+ registerAjax(pathname: string, fn: AjaxFn, method?: 'POST' | 'GET'): void;
21
+ /** Injected by Web to route emitted events to the configured transport. */
22
+ setPublisher(p: Publisher): void;
23
+ emit(event: SocketIOEvent | SocketIOEvent[], to?: string): Promise<void>;
24
+ }
25
+ export {};
26
+ //# sourceMappingURL=page.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../src/server/page.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAC7B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAC5C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAE3C,KAAK,MAAM,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;AAEjE;;;GAGG;AACH,qBAAa,SAAS;IAClB,MAAM,CAAC,EAAE,IAAI,CAAA;IACb,OAAO,EAAE,OAAO,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAK;IACrC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAK;IACpC,OAAO,CAAC,SAAS,CAAC,CAAW;gBAEjB,OAAO,UAAQ;IAI3B,yFAAyF;IACnF,UAAU,CACZ,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACnC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAKnC,qEAAqE;IAC/D,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,GAAG,OAAO,CAAC;IAIxE,YAAY,CACR,QAAQ,EAAE,MAAM,EAChB,EAAE,EAAE,MAAM,EACV,MAAM,GAAE,MAAM,GAAG,KAAc,GAChC,IAAI;IAKP,2EAA2E;IAC3E,YAAY,CAAC,CAAC,EAAE,SAAS,GAAG,IAAI;IAI1B,IAAI,CACN,KAAK,EAAE,aAAa,GAAG,aAAa,EAAE,EACtC,EAAE,CAAC,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC;CAOnB"}
@@ -0,0 +1,13 @@
1
+ import type { SocketIOEvent } from './types';
2
+ type FetchFn = typeof fetch;
3
+ /** Publish a payload to a Centrifugo channel via its HTTP API. Mirrors pie publish. */
4
+ export declare function publishToCentrifuge(opts: {
5
+ url: string;
6
+ apiKey: string;
7
+ channel: string;
8
+ data: unknown;
9
+ }, fetchFn?: FetchFn): Promise<void>;
10
+ /** Publishes a single event, optionally scoped to a subscriber id `to`. */
11
+ export type Publisher = (event: SocketIOEvent, to?: string) => Promise<void>;
12
+ export {};
13
+ //# sourceMappingURL=realtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"realtime.d.ts","sourceRoot":"","sources":["../../src/server/realtime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAE5C,KAAK,OAAO,GAAG,OAAO,KAAK,CAAA;AAE3B,uFAAuF;AACvF,wBAAsB,mBAAmB,CACrC,IAAI,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,EACrE,OAAO,GAAE,OAAe,GACzB,OAAO,CAAC,IAAI,CAAC,CAYf;AAED,2EAA2E;AAC3E,MAAM,MAAM,SAAS,GAAG,CAAC,KAAK,EAAE,aAAa,EAAE,EAAE,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA"}
@@ -0,0 +1,28 @@
1
+ import type { UIConfigType } from '../types';
2
+ export type { UIConfigType };
3
+ /** A realtime event: name `pie{method}_{cardName}` + payload. Mirrors pie's SocketIOEvent. */
4
+ export interface SocketIOEvent {
5
+ name: string;
6
+ data: Record<string, unknown>;
7
+ }
8
+ /** Subset of request context handed to page methods (cookies + query + body merged). */
9
+ export type PageContext = Record<string, unknown>;
10
+ export interface WebOptions {
11
+ cookieKeys?: string[];
12
+ cookieOptions?: Record<string, unknown>;
13
+ enableCors?: boolean;
14
+ corsOrigins?: string[];
15
+ /** Route prefix; must start and end with "/". Default "/". Mirrors pie admin_subdomain. */
16
+ adminSubdomain?: string;
17
+ disableServing?: boolean;
18
+ servingUrl?: string;
19
+ assetsPath?: string;
20
+ aggregationRule?: 'by_underscore';
21
+ useSocketioSupport?: boolean;
22
+ useCentrifugeSupport?: boolean;
23
+ centrifugeUrl?: string;
24
+ centrifugeApiKey?: string;
25
+ centrifugeHmacSecret?: string;
26
+ centrifugeSubFn?: (cookies: Record<string, string>) => string;
27
+ }
28
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/server/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAE5C,YAAY,EAAE,YAAY,EAAE,CAAA;AAE5B,8FAA8F;AAC9F,MAAM,WAAW,aAAa;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAChC;AAED,wFAAwF;AACxF,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAEjD,MAAM,WAAW,UAAU;IACvB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACvC,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;IACtB,2FAA2F;IAC3F,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,eAAe,CAAC,EAAE,eAAe,CAAA;IACjC,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,oBAAoB,CAAC,EAAE,MAAM,CAAA;IAC7B,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAA;CAChE"}
@@ -0,0 +1,23 @@
1
+ import { type Express } from 'express';
2
+ import { AsyncPage } from './page';
3
+ import type { WebOptions } from './types';
4
+ /**
5
+ * Assembles an Express app mirroring the pie FastAPI runtime: routes for
6
+ * content, process, ajax, support, and centrifuge token, plus realtime
7
+ * publishing wired into every page.
8
+ */
9
+ export declare class Web {
10
+ pages: Record<string, AsyncPage | string>;
11
+ opts: WebOptions & {
12
+ adminSubdomain: string;
13
+ };
14
+ constructor(pages: Record<string, AsyncPage | string>, opts?: WebOptions);
15
+ private wirePublishers;
16
+ /** Resolve a pathname to a page, following string aliases. */
17
+ resolvePage(pathname: string): AsyncPage | null;
18
+ private contextFromRequest;
19
+ private collectAjax;
20
+ getApp(): Express;
21
+ private corsMiddleware;
22
+ }
23
+ //# sourceMappingURL=web.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web.d.ts","sourceRoot":"","sources":["../../src/server/web.ts"],"names":[],"mappings":"AAAA,OAAgB,EACZ,KAAK,OAAO,EAIf,MAAM,SAAS,CAAA;AAIhB,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;AAGlC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAMzC;;;;GAIG;AACH,qBAAa,GAAG;IACZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAAC,CAAA;IACzC,IAAI,EAAE,UAAU,GAAG;QAAE,cAAc,EAAE,MAAM,CAAA;KAAE,CAAA;gBAEjC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAAC,EAAE,IAAI,GAAE,UAAe;IAU5E,OAAO,CAAC,cAAc;IAsBtB,8DAA8D;IAC9D,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAe/C,OAAO,CAAC,kBAAkB;IAW1B,OAAO,CAAC,WAAW;IAYnB,MAAM,IAAI,OAAO;IAkJjB,OAAO,CAAC,cAAc;CAyBzB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=card-fill.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"card-fill.test.d.ts","sourceRoot":"","sources":["../../../src/tests/server/card-fill.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=card-generate.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"card-generate.test.d.ts","sourceRoot":"","sources":["../../../src/tests/server/card-generate.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=cards.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cards.test.d.ts","sourceRoot":"","sources":["../../../src/tests/server/cards.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=e2e.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"e2e.test.d.ts","sourceRoot":"","sources":["../../../src/tests/server/e2e.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=entry.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entry.test.d.ts","sourceRoot":"","sources":["../../../src/tests/server/entry.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=form.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form.test.d.ts","sourceRoot":"","sources":["../../../src/tests/server/form.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=page.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page.test.d.ts","sourceRoot":"","sources":["../../../src/tests/server/page.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=realtime.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"realtime.test.d.ts","sourceRoot":"","sources":["../../../src/tests/server/realtime.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=web-ajax.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-ajax.test.d.ts","sourceRoot":"","sources":["../../../src/tests/server/web-ajax.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=web-content.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-content.test.d.ts","sourceRoot":"","sources":["../../../src/tests/server/web-content.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=web-process.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-process.test.d.ts","sourceRoot":"","sources":["../../../src/tests/server/web-process.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=web-token.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-token.test.d.ts","sourceRoot":"","sources":["../../../src/tests/server/web-token.test.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swarm.ing/pieui",
3
- "version": "2.1.7",
3
+ "version": "2.2.0",
4
4
  "description": "A React component library featuring PieCard component",
5
5
  "repository": {
6
6
  "type": "git",
@@ -34,6 +34,11 @@
34
34
  "require": "./dist/agent/index.js",
35
35
  "types": "./dist/agent/index.d.ts"
36
36
  },
37
+ "./server": {
38
+ "import": "./dist/server/index.esm.js",
39
+ "require": "./dist/server/index.js",
40
+ "types": "./dist/server/index.d.ts"
41
+ },
37
42
  "./telegram": {
38
43
  "import": "./dist/telegram/index.esm.js",
39
44
  "require": "./dist/telegram/index.js",
@@ -67,7 +72,7 @@
67
72
  }
68
73
  },
69
74
  "scripts": {
70
- "build": "rm -rf ./dist && rm -rf pieui-*.tgz && bun run build:clean && NODE_ENV=production bun run build:esm && NODE_ENV=production bun run build:cjs && NODE_ENV=production bun run build:components:esm && NODE_ENV=production bun run build:components:cjs && NODE_ENV=production bun run build:agent:esm && NODE_ENV=production bun run build:agent:cjs && NODE_ENV=production bun run build:telegram:esm && NODE_ENV=production bun run build:telegram:cjs && NODE_ENV=production bun run build:native:esm && NODE_ENV=production bun run build:native:cjs && NODE_ENV=production bun run build:max:esm && NODE_ENV=production bun run build:max:cjs && NODE_ENV=production bun run build:storybook:esm && NODE_ENV=production bun run build:storybook:cjs && NODE_ENV=production bun run build:storybook:addon:esm && NODE_ENV=production bun run build:storybook:addon:cjs && NODE_ENV=production bun run build:storybook:addon:manager:esm && NODE_ENV=production bun run build:storybook:addon:manager:cjs && NODE_ENV=production bun run build:storybook:addon:preset && bun run build:banner && NODE_ENV=production bun run build:types && NODE_ENV=production bun run build:cli && bun src/cli.ts postbuild --src-dir src --out-dir dist && bun pm pack",
75
+ "build": "rm -rf ./dist && rm -rf pieui-*.tgz && bun run build:clean && NODE_ENV=production bun run build:esm && NODE_ENV=production bun run build:cjs && NODE_ENV=production bun run build:components:esm && NODE_ENV=production bun run build:components:cjs && NODE_ENV=production bun run build:agent:esm && NODE_ENV=production bun run build:agent:cjs && NODE_ENV=production bun run build:server:esm && NODE_ENV=production bun run build:server:cjs && NODE_ENV=production bun run build:telegram:esm && NODE_ENV=production bun run build:telegram:cjs && NODE_ENV=production bun run build:native:esm && NODE_ENV=production bun run build:native:cjs && NODE_ENV=production bun run build:max:esm && NODE_ENV=production bun run build:max:cjs && NODE_ENV=production bun run build:storybook:esm && NODE_ENV=production bun run build:storybook:cjs && NODE_ENV=production bun run build:storybook:addon:esm && NODE_ENV=production bun run build:storybook:addon:cjs && NODE_ENV=production bun run build:storybook:addon:manager:esm && NODE_ENV=production bun run build:storybook:addon:manager:cjs && NODE_ENV=production bun run build:storybook:addon:preset && bun run build:banner && NODE_ENV=production bun run build:types && NODE_ENV=production bun run build:cli && bun src/cli.ts postbuild --src-dir src --out-dir dist && bun pm pack",
71
76
  "build:banner": "node -e \"const fs=require('fs');['dist/index.esm.js','dist/index.js','dist/components/index.esm.js','dist/components/index.js','dist/agent/index.esm.js','dist/agent/index.js','dist/telegram/index.esm.js','dist/telegram/index.js','dist/native/index.esm.js','dist/native/index.js','dist/max/index.esm.js','dist/max/index.js','dist/storybook/index.esm.js','dist/storybook/index.js','dist/storybook/addon/index.esm.js','dist/storybook/addon/index.js'].forEach(f=>{if(fs.existsSync(f)){fs.writeFileSync(f,'\\\"use client\\\";\\n'+fs.readFileSync(f,'utf8'))}})\"",
72
77
  "build:clean": "rm -rf dist",
73
78
  "build:esm": "bun build src/index.ts --outfile dist/index.esm.js --format esm --target browser --jsx=automatic --jsx-import-source=react --minify --packages=external",
@@ -76,6 +81,8 @@
76
81
  "build:components:cjs": "bun build src/components/index.ts --outfile dist/components/index.js --format cjs --target node --jsx=automatic --jsx-import-source=react --minify --packages=external",
77
82
  "build:agent:esm": "bun build src/agent/index.ts --outfile dist/agent/index.esm.js --format esm --target browser --jsx=automatic --jsx-import-source=react --minify --packages=external",
78
83
  "build:agent:cjs": "bun build src/agent/index.ts --outfile dist/agent/index.js --format cjs --target node --jsx=automatic --jsx-import-source=react --minify --packages=external",
84
+ "build:server:esm": "bun build src/server/index.ts --outfile dist/server/index.esm.js --format esm --target node --packages=external",
85
+ "build:server:cjs": "bun build src/server/index.ts --outfile dist/server/index.js --format cjs --target node --packages=external",
79
86
  "build:telegram:esm": "bun build src/telegram/index.ts --outfile dist/telegram/index.esm.js --format esm --target browser --jsx=automatic --jsx-import-source=react --minify --packages=external",
80
87
  "build:telegram:cjs": "bun build src/telegram/index.ts --outfile dist/telegram/index.js --format cjs --target node --jsx=automatic --jsx-import-source=react --minify --packages=external",
81
88
  "build:native:esm": "bun build src/native/index.ts --outfile dist/native/index.esm.js --format esm --target browser --jsx=automatic --jsx-import-source=react --minify --packages=external",
@@ -133,7 +140,10 @@
133
140
  "react": ">=19",
134
141
  "react-dom": ">=19",
135
142
  "@openai/agents": "^0.4.5",
136
- "react-native": ">=0.74"
143
+ "react-native": ">=0.74",
144
+ "express": "^5.0.0",
145
+ "jsonwebtoken": "^9.0.0",
146
+ "cookie-parser": "^1.4.0"
137
147
  },
138
148
  "peerDependenciesMeta": {
139
149
  "@openai/agents": {
@@ -141,14 +151,29 @@
141
151
  },
142
152
  "react-native": {
143
153
  "optional": true
154
+ },
155
+ "express": {
156
+ "optional": true
157
+ },
158
+ "jsonwebtoken": {
159
+ "optional": true
160
+ },
161
+ "cookie-parser": {
162
+ "optional": true
144
163
  }
145
164
  },
146
165
  "devDependencies": {
166
+ "cookie-parser": "^1.4.7",
167
+ "express": "^5.2.1",
168
+ "jsonwebtoken": "^9.0.3",
147
169
  "@openai/agents": "^0.4.5",
148
170
  "@testing-library/jest-dom": "^6.9.1",
149
171
  "@testing-library/react": "^16.3.2",
150
172
  "@types/bun": "latest",
173
+ "@types/cookie-parser": "^1.4.10",
174
+ "@types/express": "^5.0.6",
151
175
  "@types/glob": "^9.0.0",
176
+ "@types/jsonwebtoken": "^9.0.10",
152
177
  "@types/node": "^25.5.0",
153
178
  "@types/react": "^19.2.14",
154
179
  "ajv": "^8.18.0",