@rebasepro/app 0.23.0 → 0.24.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.
@@ -7,3 +7,4 @@ export * from "./previews.js";
7
7
  export * from "./enums.js";
8
8
  export * from "./constants.js";
9
9
  export * from "./local_storage.js";
10
+ export * from "./realtime.js";
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Whether an error is the realtime client saying the connection is down —
3
+ * not that the data is gone.
4
+ *
5
+ * Once the socket has been down for longer than a blip, the SDK tells every
6
+ * live subscription `CONNECTION_LOST` through its `onError`, once, and keeps
7
+ * the subscription: when the socket is back it is re-subscribed and its next
8
+ * update carries what changed meanwhile. A view that already shows data should
9
+ * keep showing it (the admin's connection banner says it may be stale) rather
10
+ * than replace it with an error — for an open record that would unmount the
11
+ * form and everything typed into it.
12
+ *
13
+ * Read by `code` rather than `instanceof RebaseApiError`: two copies of
14
+ * `@rebasepro/types` in one tree disagree about the class, never about the code.
15
+ *
16
+ * @group Hooks and utilities
17
+ */
18
+ export declare function isConnectionLostError(error: unknown): boolean;
@@ -2,12 +2,114 @@ import path from "path";
2
2
  import ts from "typescript";
3
3
  import MagicString from "magic-string";
4
4
  const LAZY_COMPONENT_KEYS = /* @__PURE__ */ new Set(["Field", "Preview", "Builder", "Filter", "Component"]);
5
- function isInsideCallbacksBlock(node) {
6
- const objectLiteral = node.parent;
7
- if (!objectLiteral || !ts.isObjectLiteralExpression(objectLiteral)) return false;
8
- const owner = objectLiteral.parent;
9
- if (!owner || !ts.isPropertyAssignment(owner)) return false;
10
- return getPropertyName(owner) === "callbacks";
5
+ function unwrapObjectLiteral(expr) {
6
+ let current = expr;
7
+ while (ts.isParenthesizedExpression(current) || ts.isAsExpression(current) || ts.isSatisfiesExpression(current) || ts.isNonNullExpression(current)) {
8
+ current = current.expression;
9
+ }
10
+ return ts.isObjectLiteralExpression(current) ? current : void 0;
11
+ }
12
+ function callbackStripEdits(sourceFile) {
13
+ const edits = [];
14
+ const memberEdit = (member) => {
15
+ if (ts.isPropertyAssignment(member)) {
16
+ return { start: member.initializer.getStart(sourceFile), end: member.initializer.getEnd(), text: "undefined" };
17
+ }
18
+ if (ts.isSpreadAssignment(member)) {
19
+ return { start: member.getStart(sourceFile), end: member.getEnd(), text: "...undefined" };
20
+ }
21
+ const name = member.name;
22
+ return name ? { start: member.getStart(sourceFile), end: member.getEnd(), text: `${name.getText(sourceFile)}: undefined` } : void 0;
23
+ };
24
+ walkAST(sourceFile, (node) => {
25
+ if (ts.isShorthandPropertyAssignment(node) && node.name.text === "callbacks") {
26
+ edits.push({ start: node.getStart(sourceFile), end: node.getEnd(), text: "callbacks: undefined" });
27
+ return;
28
+ }
29
+ if (!ts.isPropertyAssignment(node) || getPropertyName(node) !== "callbacks") return;
30
+ const literal = unwrapObjectLiteral(node.initializer);
31
+ if (!literal) {
32
+ edits.push({ start: node.initializer.getStart(sourceFile), end: node.initializer.getEnd(), text: "undefined" });
33
+ return;
34
+ }
35
+ for (const member of literal.properties) {
36
+ const edit = memberEdit(member);
37
+ if (edit) edits.push(edit);
38
+ }
39
+ });
40
+ return edits.filter((edit) => !edits.some((outer) => outer !== edit && outer.start <= edit.start && edit.end <= outer.end));
41
+ }
42
+ function isReference(id) {
43
+ const parent = id.parent;
44
+ if (!parent) return false;
45
+ if (ts.isPropertyAccessExpression(parent) && parent.name === id) return false;
46
+ if (ts.isQualifiedName(parent) && parent.right === id) return false;
47
+ if ((ts.isPropertyAssignment(parent) || ts.isMethodDeclaration(parent) || ts.isPropertyDeclaration(parent) || ts.isPropertySignature(parent) || ts.isMethodSignature(parent) || ts.isGetAccessorDeclaration(parent) || ts.isSetAccessorDeclaration(parent) || ts.isEnumMember(parent) || ts.isJsxAttribute(parent)) && parent.name === id) return false;
48
+ if ((ts.isVariableDeclaration(parent) || ts.isFunctionDeclaration(parent) || ts.isClassDeclaration(parent) || ts.isParameter(parent) || ts.isImportClause(parent) || ts.isNamespaceImport(parent) || ts.isBindingElement(parent) || ts.isFunctionExpression(parent) || ts.isClassExpression(parent) || ts.isTypeAliasDeclaration(parent) || ts.isInterfaceDeclaration(parent) || ts.isTypeParameterDeclaration(parent)) && parent.name === id) return false;
49
+ if (ts.isBindingElement(parent) && parent.propertyName === id) return false;
50
+ if (ts.isImportSpecifier(parent)) return false;
51
+ if (ts.isLabeledStatement(parent) || ts.isBreakOrContinueStatement(parent)) return false;
52
+ return true;
53
+ }
54
+ function deadTopLevelStatements(sourceFile, removed) {
55
+ const bindings = [];
56
+ for (const statement of sourceFile.statements) {
57
+ const exported = ts.canHaveModifiers(statement) && (ts.getModifiers(statement) ?? []).some((m) => m.kind === ts.SyntaxKind.ExportKeyword);
58
+ if (exported) continue;
59
+ if (ts.isImportDeclaration(statement)) {
60
+ const clause = statement.importClause;
61
+ if (!clause || clause.isTypeOnly) continue;
62
+ const names = [];
63
+ if (clause.name) names.push(clause.name.text);
64
+ const named = clause.namedBindings;
65
+ if (named && ts.isNamespaceImport(named)) names.push(named.name.text);
66
+ if (named && ts.isNamedImports(named)) {
67
+ for (const el of named.elements) if (!el.isTypeOnly) names.push(el.name.text);
68
+ }
69
+ if (names.length > 0) bindings.push({ statement, names });
70
+ } else if (ts.isVariableStatement(statement)) {
71
+ const names = [];
72
+ const collect = (name) => {
73
+ if (ts.isIdentifier(name)) names.push(name.text);
74
+ else for (const el of name.elements) if (!ts.isOmittedExpression(el)) collect(el.name);
75
+ };
76
+ for (const decl of statement.declarationList.declarations) collect(decl.name);
77
+ bindings.push({ statement, names });
78
+ } else if ((ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement)) && statement.name) {
79
+ bindings.push({ statement, names: [statement.name.text] });
80
+ }
81
+ }
82
+ if (bindings.length === 0) return [];
83
+ const references = [];
84
+ walkAST(sourceFile, (node) => {
85
+ if (ts.isIdentifier(node) && isReference(node)) references.push(node);
86
+ });
87
+ const ranges = removed.map(({ start, end }) => ({ start, end }));
88
+ const within = (node) => {
89
+ const at = node.getStart(sourceFile);
90
+ return ranges.some((r) => r.start <= at && node.getEnd() <= r.end);
91
+ };
92
+ const dead = [];
93
+ for (let changed = true; changed; ) {
94
+ changed = false;
95
+ for (const binding of bindings) {
96
+ if (dead.includes(binding.statement)) continue;
97
+ let readByStripped = false;
98
+ let readLive = false;
99
+ for (const ref of references) {
100
+ if (!binding.names.includes(ref.text)) continue;
101
+ if (ref.getStart(sourceFile) >= binding.statement.getStart(sourceFile) && ref.getEnd() <= binding.statement.getEnd()) continue;
102
+ if (within(ref)) readByStripped = true;
103
+ else readLive = true;
104
+ }
105
+ if (readByStripped && !readLive) {
106
+ dead.push(binding.statement);
107
+ ranges.push({ start: binding.statement.getStart(sourceFile), end: binding.statement.getEnd() });
108
+ changed = true;
109
+ }
110
+ }
111
+ }
112
+ return dead;
11
113
  }
12
114
  function walkAST(node, visitor) {
13
115
  visitor(node);
@@ -30,15 +132,22 @@ function transformCollectionSource(code, id) {
30
132
  );
31
133
  const ms = new MagicString(code);
32
134
  let replaced = false;
135
+ const strips = callbackStripEdits(sourceFile);
136
+ for (const edit of strips) {
137
+ ms.overwrite(edit.start, edit.end, edit.text);
138
+ replaced = true;
139
+ }
140
+ const gone = [...strips];
141
+ for (const statement of deadTopLevelStatements(sourceFile, strips)) {
142
+ ms.remove(statement.getStart(sourceFile), statement.getEnd());
143
+ gone.push({ start: statement.getStart(sourceFile), end: statement.getEnd() });
144
+ replaced = true;
145
+ }
146
+ const stripped = (node) => gone.some((e) => e.start <= node.getStart(sourceFile) && node.getEnd() <= e.end);
33
147
  walkAST(sourceFile, (node) => {
34
148
  if (!ts.isPropertyAssignment(node)) return;
149
+ if (stripped(node)) return;
35
150
  const name = getPropertyName(node);
36
- if (name && isInsideCallbacksBlock(node)) {
37
- const value = node.initializer;
38
- ms.overwrite(value.getStart(sourceFile), value.getEnd(), "undefined");
39
- replaced = true;
40
- return;
41
- }
42
151
  const propName = name;
43
152
  if (!propName || !LAZY_COMPONENT_KEYS.has(propName)) return;
44
153
  const init = node.initializer;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rebasepro/app",
3
- "version": "0.23.0",
3
+ "version": "0.24.0",
4
4
  "description": "Rebase core — framework-agnostic runtime for data-driven admin panels",
5
5
  "keywords": [
6
6
  "rebase",
@@ -63,12 +63,12 @@
63
63
  "magic-string": "^0.30.0",
64
64
  "notistack": "^3.0.2",
65
65
  "react-i18next": "^17.0.11",
66
- "@rebasepro/cms-types": "0.23.0",
67
- "@rebasepro/types": "0.23.0",
68
- "@rebasepro/common": "0.23.0",
69
- "@rebasepro/ui": "0.23.0",
70
- "@rebasepro/forms": "0.23.0",
71
- "@rebasepro/utils": "0.23.0"
66
+ "@rebasepro/cms-types": "0.24.0",
67
+ "@rebasepro/common": "0.24.0",
68
+ "@rebasepro/types": "0.24.0",
69
+ "@rebasepro/ui": "0.24.0",
70
+ "@rebasepro/utils": "0.24.0",
71
+ "@rebasepro/forms": "0.24.0"
72
72
  },
73
73
  "peerDependencies": {
74
74
  "react": "^19.2.7",