@statelyai/sdk 0.14.1 → 0.14.2

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/sync.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createStatelyClient } from "./studio.mjs";
2
- import { d as xstateSourceToConfig, f as findStatelyPragmaAttachments, i as detectXStateSourceProfile, m as upsertStatelyPragma, p as getStatelyPragma, s as graphToXStateTS, t as INLINE_EXPRESSION_DIRECTIVE_FIELDS, u as serializeJS } from "./dist-C8jVuuHf.mjs";
2
+ import { d as xstateSourceToConfig, f as findStatelyPragmaAttachments, i as detectXStateSourceProfile, m as upsertStatelyPragma, p as getStatelyPragma, s as graphToXStateTS, t as INLINE_EXPRESSION_DIRECTIVE_FIELDS, u as serializeJS } from "./dist-CO36Ks-i.mjs";
3
3
  import { fromStudioMachine, toStudioMachine } from "./graph.mjs";
4
4
  import * as ts$1 from "typescript";
5
5
  import ts from "typescript";
@@ -65,9 +65,35 @@ function createFileSourceDocumentResolver() {
65
65
  resolveImport(fromDocument, specifier) {
66
66
  const importedFileName = resolveImportPath(fromDocument.fileName, specifier);
67
67
  return importedFileName ? readFileSnapshot(importedFileName) : null;
68
+ },
69
+ resolvePackageJson(fromDocument, packageName) {
70
+ let directory = path$1.dirname(path$1.resolve(fromDocument.fileName));
71
+ while (true) {
72
+ const packageJson = readFileSnapshot(path$1.join(directory, "package.json"));
73
+ if (packageJson && packageJsonDeclares(packageJson.text, packageName)) return packageJson;
74
+ const parent = path$1.dirname(directory);
75
+ if (parent === directory) return null;
76
+ directory = parent;
77
+ }
68
78
  }
69
79
  };
70
80
  }
81
+ function packageJsonDeclares(text, packageName) {
82
+ try {
83
+ const manifest = JSON.parse(text);
84
+ return [
85
+ "dependencies",
86
+ "devDependencies",
87
+ "peerDependencies",
88
+ "optionalDependencies"
89
+ ].some((field) => {
90
+ const dependencies = manifest[field];
91
+ return Boolean(dependencies && typeof dependencies === "object" && Object.prototype.hasOwnProperty.call(dependencies, packageName));
92
+ });
93
+ } catch {
94
+ return false;
95
+ }
96
+ }
71
97
  function resolveImportPath(fromFileName, specifier) {
72
98
  if (!specifier.startsWith(".")) return null;
73
99
  const basePath = path$1.resolve(path$1.dirname(fromFileName), specifier);
@@ -121,20 +147,116 @@ function collectXStateSourceLocations(sourceText, document, options = {}) {
121
147
  const states = [];
122
148
  const statesObjectLocations = [];
123
149
  collectStateLocations(root, [], states, statesObjectLocations, bindings, staticValues, root);
150
+ const packageProfile = detectXStatePackageProfile(options.resolver?.resolvePackageJson?.({
151
+ fileName: document.fileName,
152
+ uri
153
+ }, "xstate"));
124
154
  const rootProfile = detectXStateSourceProfile(sourceText);
125
155
  const referencedStateSymbols = new Set([...states, ...statesObjectLocations].map((state) => state.symbol).filter((symbol) => typeof symbol === "string"));
156
+ const resolvedTransitionFunctions = collectResolvedTransitionFunctions(root, bindings);
157
+ const inferredProfile = rootProfile === "xstate-v6-alpha" || resolvedTransitionFunctions.size > 0 || [...referencedStateSymbols].some((symbol) => detectXStateSourceProfile(bindings.get(symbol)?.sourceFile.text ?? "") === "xstate-v6-alpha") ? "xstate-v6-alpha" : rootProfile;
126
158
  return {
127
159
  version: 1,
128
- profile: rootProfile === "xstate-v6-alpha" || [...referencedStateSymbols].some((symbol) => detectXStateSourceProfile(bindings.get(symbol)?.sourceFile.text ?? "") === "xstate-v6-alpha") ? "xstate-v6-alpha" : rootProfile,
160
+ profile: packageProfile ?? inferredProfile,
129
161
  root: {
130
162
  uri,
131
163
  range: toSourceRange$1(sourceFile, root)
132
164
  },
133
165
  statesObject: statesObjectLocations[0],
134
166
  states,
135
- staticValues: collectStaticValueReferences(sourceFile, uri, root, staticValues)
167
+ staticValues: collectStaticValueReferences(sourceFile, uri, root, staticValues),
168
+ transitionFunctions: [...resolvedTransitionFunctions].flatMap(([symbol, resolved]) => resolved.binding.uri === uri ? [] : [{
169
+ symbol,
170
+ uri: resolved.binding.uri,
171
+ range: toSourceRange$1(resolved.binding.sourceFile, resolved.binding.node),
172
+ sourcePaths: resolved.sourcePaths
173
+ }])
136
174
  };
137
175
  }
176
+ function detectXStatePackageProfile(packageJson) {
177
+ if (!packageJson) return void 0;
178
+ let manifest;
179
+ try {
180
+ manifest = JSON.parse(packageJson.text);
181
+ } catch {
182
+ return;
183
+ }
184
+ const dependencyFields = [
185
+ "dependencies",
186
+ "devDependencies",
187
+ "peerDependencies",
188
+ "optionalDependencies"
189
+ ];
190
+ let range;
191
+ for (const field of dependencyFields) {
192
+ const dependencies = manifest[field];
193
+ if (!dependencies || typeof dependencies !== "object") continue;
194
+ const value = dependencies.xstate;
195
+ if (typeof value === "string") {
196
+ range = value;
197
+ break;
198
+ }
199
+ }
200
+ if (!range) return void 0;
201
+ const clauseProfiles = range.replace(/^(?:workspace:|npm:xstate@)/, "").split("||").map((clause) => detectXStateRangeClauseProfile(clause.trim()));
202
+ const profiles = new Set(clauseProfiles);
203
+ if (profiles.size !== 1 || profiles.has(void 0)) return void 0;
204
+ return clauseProfiles[0];
205
+ }
206
+ function detectXStateRangeClauseProfile(clause) {
207
+ const normalizedClause = clause.replace(/([<>]=?)\s+/g, "$1");
208
+ let lowerRequiresV6 = false;
209
+ let upperRequiresV5 = false;
210
+ let directProfile;
211
+ for (const match of normalizedClause.matchAll(/(?:^|\s)([<>]=?|[~^])?v?(\d+)(?:\.(\d+|x|\*))?(?:\.(\d+|x|\*))?(?:-[0-9A-Za-z.-]+)?(?=\s|$)/g)) {
212
+ const operator = match[1] ?? "";
213
+ const major = Number(match[2]);
214
+ const minor = parseRangePart(match[3]);
215
+ const patch = parseRangePart(match[4]);
216
+ if (operator === ">" || operator === ">=") {
217
+ if (major >= 6) lowerRequiresV6 = true;
218
+ continue;
219
+ }
220
+ if (operator === "<") {
221
+ if (major < 6 || major === 6 && (minor ?? 0) === 0 && (patch ?? 0) === 0) upperRequiresV5 = true;
222
+ continue;
223
+ }
224
+ if (operator === "<=") {
225
+ if (major < 6) upperRequiresV5 = true;
226
+ continue;
227
+ }
228
+ const profile = major >= 6 ? "xstate-v6-alpha" : "xstate-v5";
229
+ if (directProfile && directProfile !== profile) return void 0;
230
+ directProfile = profile;
231
+ }
232
+ if (lowerRequiresV6 && upperRequiresV5) return void 0;
233
+ if (lowerRequiresV6) return "xstate-v6-alpha";
234
+ if (upperRequiresV5) return "xstate-v5";
235
+ return directProfile;
236
+ }
237
+ function parseRangePart(value) {
238
+ return value && /^\d+$/.test(value) ? Number(value) : void 0;
239
+ }
240
+ function collectImportedXStateTransitionSources(sourceText, document, options = {}) {
241
+ if (!options.resolver) return [];
242
+ const sourceFile = ts$1.createSourceFile(document.fileName, sourceText, ts$1.ScriptTarget.Latest, true, getScriptKind$2(document.fileName));
243
+ const uri = document.uri?.toString() ?? document.fileName;
244
+ sourceFileUris.set(sourceFile, uri);
245
+ const bindings = collectBindings(sourceFile, uri, document.fileName, options);
246
+ const root = findMachineConfigObjectLiteral(sourceFile, bindings);
247
+ if (!root) return [];
248
+ return [...collectResolvedTransitionFunctions(root, bindings)].flatMap(([localName, resolved]) => {
249
+ const { binding } = resolved;
250
+ if (binding.uri === uri) return [];
251
+ if (ts$1.isFunctionDeclaration(binding.node)) {
252
+ const name = binding.node.name;
253
+ if (!name) return [];
254
+ const suffix = binding.node.getText(binding.sourceFile).slice(name.getEnd() - binding.node.getStart(binding.sourceFile));
255
+ return [`${binding.node.modifiers?.some((modifier) => modifier.kind === ts$1.SyntaxKind.AsyncKeyword) ? "async " : ""}function ${localName}${suffix}`];
256
+ }
257
+ return [`const ${localName} = ${binding.node.getText(binding.sourceFile)};`];
258
+ });
259
+ }
138
260
  function collectStaticValueBindings(sourceFile) {
139
261
  const bindings = /* @__PURE__ */ new Map();
140
262
  for (const statement of sourceFile.statements) {
@@ -180,9 +302,30 @@ function collectBindings(sourceFile, uri, fileName, options, cache = /* @__PURE_
180
302
  function collectTopLevelBindings(sourceFile, uri) {
181
303
  const bindings = /* @__PURE__ */ new Map();
182
304
  for (const statement of sourceFile.statements) {
305
+ if (ts$1.isFunctionDeclaration(statement) && statement.name) {
306
+ bindings.set(statement.name.text, {
307
+ kind: "transitionFunction",
308
+ node: statement,
309
+ sourceFile,
310
+ uri,
311
+ bindings
312
+ });
313
+ continue;
314
+ }
183
315
  if (!ts$1.isVariableStatement(statement)) continue;
184
316
  for (const declaration of statement.declarationList.declarations) {
185
317
  if (!ts$1.isIdentifier(declaration.name) || !declaration.initializer) continue;
318
+ const initializer = unwrapExpression$1(declaration.initializer);
319
+ if (ts$1.isArrowFunction(initializer) || ts$1.isFunctionExpression(initializer)) {
320
+ bindings.set(declaration.name.text, {
321
+ kind: "transitionFunction",
322
+ node: initializer,
323
+ sourceFile,
324
+ uri,
325
+ bindings
326
+ });
327
+ continue;
328
+ }
186
329
  const objectLiteral = unwrapObjectLiteralExpression(declaration.initializer);
187
330
  if (objectLiteral) {
188
331
  bindings.set(declaration.name.text, {
@@ -228,6 +371,62 @@ function addImportedBindings(bindings, sourceFile, uri, fileName, options, cache
228
371
  }
229
372
  }
230
373
  }
374
+ function collectResolvedTransitionFunctions(root, bindings) {
375
+ const found = /* @__PURE__ */ new Map();
376
+ const visitExpression = (expression, sourcePath) => {
377
+ const current = unwrapExpression$1(expression);
378
+ if (ts$1.isIdentifier(current)) {
379
+ const binding = bindings.get(current.text);
380
+ if (binding?.kind === "transitionFunction") {
381
+ const resolved = found.get(current.text) ?? {
382
+ binding,
383
+ sourcePaths: []
384
+ };
385
+ if (!resolved.sourcePaths.some((existing) => existing.length === sourcePath.length && existing.every((segment, index) => segment === sourcePath[index]))) resolved.sourcePaths.push(sourcePath);
386
+ found.set(current.text, resolved);
387
+ }
388
+ return;
389
+ }
390
+ if (ts$1.isObjectLiteralExpression(current)) {
391
+ for (const property of current.properties) if (ts$1.isPropertyAssignment(property)) visitExpression(property.initializer, sourcePath);
392
+ else if (ts$1.isShorthandPropertyAssignment(property)) visitExpression(property.name, sourcePath);
393
+ else if (ts$1.isSpreadAssignment(property)) visitExpression(property.expression, sourcePath);
394
+ return;
395
+ }
396
+ if (ts$1.isArrayLiteralExpression(current)) {
397
+ for (const element of current.elements) if (ts$1.isExpression(element)) visitExpression(element, sourcePath);
398
+ return;
399
+ }
400
+ ts$1.forEachChild(current, (child) => {
401
+ if (ts$1.isExpression(child)) visitExpression(child, sourcePath);
402
+ });
403
+ };
404
+ const visitState = (state, sourcePath) => {
405
+ for (const propertyName of [
406
+ "on",
407
+ "after",
408
+ "always",
409
+ "onDone",
410
+ "onError",
411
+ "onSnapshot",
412
+ "onTimeout"
413
+ ]) {
414
+ const property = state.properties.find((candidate) => ts$1.isPropertyAssignment(candidate) && getPropertyNameText$1(candidate.name) === propertyName);
415
+ if (property) visitExpression(property.initializer, sourcePath);
416
+ }
417
+ const states = state.properties.find((candidate) => ts$1.isPropertyAssignment(candidate) && getPropertyNameText$1(candidate.name) === "states");
418
+ const statesObject = states && unwrapObjectLiteralExpression(states.initializer);
419
+ if (!statesObject) return;
420
+ for (const property of statesObject.properties) {
421
+ if (!ts$1.isPropertyAssignment(property)) continue;
422
+ const child = unwrapObjectLiteralExpression(property.initializer);
423
+ const key = getPropertyNameText$1(property.name);
424
+ if (child && key) visitState(child, [...sourcePath, key]);
425
+ }
426
+ };
427
+ visitState(root, []);
428
+ return found;
429
+ }
231
430
  function findMachineConfigObjectLiteral(sourceFile, bindings) {
232
431
  let found = null;
233
432
  const visit = (node) => {
@@ -276,7 +475,7 @@ function collectStateLocations(stateObject, parentPath, locations, statesObjectL
276
475
  function resolveStateProperty(property, bindings, staticValues, root) {
277
476
  if (ts$1.isShorthandPropertyAssignment(property)) {
278
477
  const binding = bindings.get(property.name.text);
279
- if (!binding) return null;
478
+ if (!binding || binding.kind === "transitionFunction") return null;
280
479
  return {
281
480
  key: property.name.text,
282
481
  kind: binding.kind,
@@ -306,7 +505,7 @@ function resolveStateProperty(property, bindings, staticValues, root) {
306
505
  };
307
506
  if (ts$1.isIdentifier(property.initializer)) {
308
507
  const binding = bindings.get(property.initializer.text);
309
- if (!binding) return null;
508
+ if (!binding || binding.kind === "transitionFunction") return null;
310
509
  return {
311
510
  key: resolvedKey.key,
312
511
  kind: binding.kind,
@@ -584,10 +783,13 @@ function parseSourceToMachine(text, document, options = {}) {
584
783
  ...statesObjectReplacements,
585
784
  ...importReplacements
586
785
  ].filter(removeOverlappingRanges).sort(compareSourceRangesDescending));
587
- if (sourceLocations.profile === "xstate-v6-alpha") return `import { createMachine } from "xstate";
786
+ if (sourceLocations.profile === "xstate-v6-alpha") {
787
+ const importedTransitionSources = collectImportedXStateTransitionSources(text, document, options);
788
+ return `import { createMachine } from "xstate";
588
789
 
589
- export const machine = createMachine(${serializeJS(xstateSourceToConfig(hydratedText, { profile: "xstate-v6-alpha" }), 0)});
790
+ export const machine = createMachine(${serializeJS(xstateSourceToConfig(importedTransitionSources.length ? `${hydratedText}\n${importedTransitionSources.join("\n")}` : hydratedText, { profile: "xstate-v6-alpha" }), 0)});
590
791
  `;
792
+ }
591
793
  return hydratedText;
592
794
  }
593
795
  function readHydratedStatesObject(uri, range, states, resolver) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@statelyai/sdk",
3
- "version": "0.14.1",
3
+ "version": "0.14.2",
4
4
  "license": "MIT",
5
5
  "files": [
6
6
  "dist",
@@ -81,7 +81,7 @@
81
81
  "tsdown": "0.21.0-beta.2",
82
82
  "vitest": "^3.2.4",
83
83
  "ws": "^8.19.0",
84
- "@statelyai/graph-tools": "0.0.6"
84
+ "@statelyai/graph-tools": "0.0.7"
85
85
  },
86
86
  "scripts": {
87
87
  "build": "tsdown",