render-workflows-dart 0.8.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.
Files changed (61) hide show
  1. package/CHANGELOG.md +193 -0
  2. package/LICENSE +21 -0
  3. package/README.md +678 -0
  4. package/dart/generator/bin/generate.dart +419 -0
  5. package/dart/generator/pubspec.lock +149 -0
  6. package/dart/generator/pubspec.yaml +9 -0
  7. package/examples/README.md +56 -0
  8. package/examples/default/README.md +18 -0
  9. package/examples/default/gitignore +7 -0
  10. package/examples/default/index.js +2 -0
  11. package/examples/default/package.json +17 -0
  12. package/examples/default/pubspec.yaml +7 -0
  13. package/examples/default/tasks.dart +39 -0
  14. package/examples/http/README.md +26 -0
  15. package/examples/http/gitignore +7 -0
  16. package/examples/http/index.js +2 -0
  17. package/examples/http/package.json +18 -0
  18. package/examples/http/pubspec.yaml +13 -0
  19. package/examples/http/tasks.dart +100 -0
  20. package/examples/introspect/README.md +50 -0
  21. package/examples/introspect/gitignore +7 -0
  22. package/examples/introspect/index.js +2 -0
  23. package/examples/introspect/node_env.dart +35 -0
  24. package/examples/introspect/package.json +18 -0
  25. package/examples/introspect/pubspec.yaml +23 -0
  26. package/examples/introspect/tasks.dart +144 -0
  27. package/examples/native/README.md +32 -0
  28. package/examples/native/gitignore +7 -0
  29. package/examples/native/index.js +2 -0
  30. package/examples/native/native/tools_impl.dart +105 -0
  31. package/examples/native/package.json +23 -0
  32. package/examples/native/pubspec.yaml +7 -0
  33. package/examples/native/tasks.dart +35 -0
  34. package/examples/postgres/README.md +73 -0
  35. package/examples/postgres/gitignore +7 -0
  36. package/examples/postgres/index.js +2 -0
  37. package/examples/postgres/native/db_impl.dart +205 -0
  38. package/examples/postgres/package.json +26 -0
  39. package/examples/postgres/pubspec.yaml +14 -0
  40. package/examples/postgres/seed/bin/seed.dart +56 -0
  41. package/examples/postgres/seed/bin/show.dart +64 -0
  42. package/examples/postgres/seed/lib/src/connect.dart +93 -0
  43. package/examples/postgres/seed/lib/src/schema.dart +46 -0
  44. package/examples/postgres/seed/pubspec.yaml +17 -0
  45. package/examples/postgres/tasks.dart +66 -0
  46. package/package.json +51 -0
  47. package/runtime/AGENTS.md +141 -0
  48. package/runtime/CLAUDE.md +2 -0
  49. package/runtime/native_task.dart +115 -0
  50. package/runtime/render_dart.dart +428 -0
  51. package/src/cli.js +396 -0
  52. package/src/native-worker.js +196 -0
  53. package/src/node-bridge.js +118 -0
  54. package/src/runtime.js +108 -0
  55. package/src/toolchain/compile.js +153 -0
  56. package/src/toolchain/dart-sdk.js +216 -0
  57. package/src/toolchain/dart-version.js +113 -0
  58. package/src/toolchain/generate.js +112 -0
  59. package/src/toolchain/index.js +8 -0
  60. package/src/toolchain/native.js +217 -0
  61. package/src/web-shims.js +281 -0
@@ -0,0 +1,419 @@
1
+ // Generates the two halves of a native task from an annotated Dart file.
2
+ //
3
+ // Reads a file containing `@nativeTask` top-level functions and writes:
4
+ //
5
+ // <name>.g.dart typed stubs, imported by the dart2js task code
6
+ // <name>.main.dart the dispatcher main(), which is what compiles AOT
7
+ //
8
+ // The analyzer is used rather than pattern matching because the stubs have to
9
+ // reproduce parameter names, defaults, nullability and generics exactly — a
10
+ // regex that is right most of the time would produce code that fails to
11
+ // compile in ways the author cannot fix.
12
+ import 'dart:convert';
13
+ import 'dart:io';
14
+
15
+ import 'package:analyzer/dart/analysis/analysis_context_collection.dart';
16
+ import 'package:analyzer/dart/analysis/results.dart';
17
+ import 'package:analyzer/dart/element/element.dart';
18
+ import 'package:analyzer/dart/element/nullability_suffix.dart';
19
+ import 'package:analyzer/dart/element/type.dart';
20
+
21
+ Future<int> main(List<String> argv) async {
22
+ final args = _parseArgs(argv);
23
+ final project = args['project']!;
24
+ final entry = args['entry']!;
25
+ final name = args['name']!;
26
+ final stubPath = args['stub']!;
27
+ final mainPath = args['main']!;
28
+ final facadePath = args['facade']!;
29
+ // package.json overrides the annotation; absent means "annotation decides".
30
+ final override = _Options(
31
+ worker: args['worker'] == null ? null : args['worker'] == 'true',
32
+ idleTimeoutMs: int.tryParse(args['idle'] ?? ''),
33
+ timeoutMs: int.tryParse(args['timeout'] ?? ''),
34
+ );
35
+
36
+ final collection = AnalysisContextCollection(includedPaths: [project, entry]);
37
+ final session = collection.contextFor(entry).currentSession;
38
+ final resolved = await session.getResolvedLibrary(entry);
39
+
40
+ if (resolved is! ResolvedLibraryResult) {
41
+ return _fail('could not analyse $entry: $resolved');
42
+ }
43
+
44
+ final errors = <String>[];
45
+ for (final unit in resolved.units) {
46
+ for (final d in unit.diagnostics) {
47
+ if (d.severity.name == 'ERROR') {
48
+ errors.add(' ${d.message} (line ${unit.lineInfo.getLocation(d.offset).lineNumber})');
49
+ }
50
+ }
51
+ }
52
+ if (errors.isNotEmpty) {
53
+ return _fail('${_rel(project, entry)} does not analyse cleanly:\n${errors.join('\n')}');
54
+ }
55
+
56
+ final fns = resolved.element.topLevelFunctions.where(_isNativeTask).toList();
57
+ if (fns.isEmpty) {
58
+ return _fail(
59
+ 'no @nativeTask functions in ${_rel(project, entry)}.\n'
60
+ 'Annotate a top-level function with @nativeTask, or declare this entry '
61
+ 'with "mode": "exe" if it owns its own main().',
62
+ );
63
+ }
64
+
65
+ final problems = <String>[];
66
+ for (final fn in fns) {
67
+ problems.addAll(_validate(fn));
68
+ }
69
+ if (problems.isNotEmpty) {
70
+ return _fail(
71
+ 'these @nativeTask signatures cannot cross a JSON boundary:\n'
72
+ '${problems.map((p) => ' $p').join('\n')}\n\n'
73
+ 'Supported: bool, int, double, num, String, List<T>, Map<String, T>, '
74
+ 'Object?, dynamic, and Future<T> of those.',
75
+ );
76
+ }
77
+
78
+ File(stubPath)
79
+ ..createSync(recursive: true)
80
+ ..writeAsStringSync(_stubs(name, fns, stubPath, project, override));
81
+ File(facadePath)
82
+ ..createSync(recursive: true)
83
+ ..writeAsStringSync(_facade(name, stubPath, facadePath, entry));
84
+ File(mainPath)
85
+ ..createSync(recursive: true)
86
+ ..writeAsStringSync(_dispatcher(name, fns, mainPath, entry, project));
87
+
88
+ stdout.writeln(jsonEncode({
89
+ 'name': name,
90
+ 'methods': [for (final f in fns) f.name],
91
+ }));
92
+ return 0;
93
+ }
94
+
95
+ // ---------------------------------------------------------------- annotations
96
+
97
+ /// Effective settings for one native task.
98
+ class _Options {
99
+ const _Options({this.worker, this.idleTimeoutMs, this.timeoutMs});
100
+
101
+ final bool? worker;
102
+ final int? idleTimeoutMs;
103
+ final int? timeoutMs;
104
+
105
+ /// [other] wins where it has an opinion. Used for package.json over the
106
+ /// annotation, so a deployment can change behaviour without editing code.
107
+ _Options overriddenBy(_Options other) => _Options(
108
+ worker: other.worker ?? worker,
109
+ idleTimeoutMs: other.idleTimeoutMs ?? idleTimeoutMs,
110
+ timeoutMs: other.timeoutMs ?? timeoutMs,
111
+ );
112
+ }
113
+
114
+ /// Reads `@NativeTask(...)` arguments off the function.
115
+ _Options _optionsOf(TopLevelFunctionElement fn) {
116
+ for (final a in fn.metadata.annotations) {
117
+ final value = a.computeConstantValue();
118
+ if (value?.type?.element?.name != 'NativeTask') continue;
119
+
120
+ // A const Duration exposes its microseconds as a field on the constant.
121
+ // Current SDKs name it `inMicroseconds`; older ones used the private
122
+ // `_duration`, so both are tried rather than silently reading null and
123
+ // falling back to a default the author did not ask for.
124
+ int? ms(String field) {
125
+ final duration = value!.getField(field);
126
+ if (duration == null || duration.isNull) return null;
127
+
128
+ final micros = duration.getField('inMicroseconds')?.toIntValue() ??
129
+ duration.getField('_duration')?.toIntValue();
130
+ if (micros == null) {
131
+ stderr.writeln(
132
+ 'warning: could not read $field from @NativeTask on ${fn.name}; '
133
+ 'using the default instead',
134
+ );
135
+ return null;
136
+ }
137
+ return micros ~/ 1000;
138
+ }
139
+
140
+ return _Options(
141
+ worker: value!.getField('worker')?.toBoolValue(),
142
+ idleTimeoutMs: ms('idleTimeout'),
143
+ timeoutMs: ms('timeout'),
144
+ );
145
+ }
146
+ return const _Options();
147
+ }
148
+
149
+ bool _isNativeTask(TopLevelFunctionElement fn) {
150
+ for (final a in fn.metadata.annotations) {
151
+ // Matches both `@nativeTask` (a const instance) and `@NativeTask()`.
152
+ final value = a.computeConstantValue();
153
+ final typeName = value?.type?.element?.name;
154
+ if (typeName == 'NativeTask') return true;
155
+ }
156
+ return false;
157
+ }
158
+
159
+ // ----------------------------------------------------------------- validation
160
+
161
+ /// Unwraps `Future<T>` to `T`; other types are returned unchanged.
162
+ DartType _unwrapFuture(DartType t) {
163
+ if (t is InterfaceType && t.isDartAsyncFuture && t.typeArguments.length == 1) {
164
+ return t.typeArguments.single;
165
+ }
166
+ return t;
167
+ }
168
+
169
+ bool _jsonable(DartType t) {
170
+ if (t is VoidType || t is DynamicType) return true;
171
+ if (t.isDartCoreBool || t.isDartCoreInt || t.isDartCoreDouble) return true;
172
+ if (t.isDartCoreNum || t.isDartCoreString || t.isDartCoreNull) return true;
173
+ if (t.isDartCoreObject) return true;
174
+ if (t is InterfaceType) {
175
+ if (t.isDartCoreList) return _jsonable(t.typeArguments.single);
176
+ if (t.isDartCoreMap) {
177
+ return t.typeArguments[0].isDartCoreString && _jsonable(t.typeArguments[1]);
178
+ }
179
+ }
180
+ return false;
181
+ }
182
+
183
+ List<String> _validate(TopLevelFunctionElement fn) {
184
+ final out = <String>[];
185
+ final ret = _unwrapFuture(fn.returnType);
186
+ if (!_jsonable(ret)) {
187
+ out.add('${fn.name}: return type ${_display(fn.returnType)}');
188
+ }
189
+ for (final p in fn.formalParameters) {
190
+ if (!_jsonable(p.type)) {
191
+ out.add('${fn.name}: parameter "${p.name}" of type ${_display(p.type)}');
192
+ }
193
+ }
194
+ return out;
195
+ }
196
+
197
+ // -------------------------------------------------------------------- codegen
198
+
199
+ String _display(DartType t) => t.getDisplayString();
200
+
201
+ bool _nullable(DartType t) =>
202
+ t.nullabilitySuffix == NullabilitySuffix.question || t is DynamicType;
203
+
204
+ /// A Dart expression converting `expr` (raw decoded JSON) into [t].
205
+ ///
206
+ /// A plain `as` cast is not enough: jsonDecode produces `List<dynamic>` and
207
+ /// `Map<String, dynamic>`, so `as List<int>` throws even when every element is
208
+ /// an int. Collections are rebuilt element by element, and doubles go through
209
+ /// num because JSON does not distinguish 3 from 3.0.
210
+ String _decode(DartType t, String expr) {
211
+ if (t is VoidType || t is DynamicType || t.isDartCoreObject) return expr;
212
+
213
+ final q = _nullable(t);
214
+ final bang = q ? '?' : '';
215
+
216
+ if (t.isDartCoreDouble) {
217
+ return q ? '($expr as num?)?.toDouble()' : '($expr as num).toDouble()';
218
+ }
219
+ if (t.isDartCoreBool || t.isDartCoreInt || t.isDartCoreNum || t.isDartCoreString) {
220
+ return '$expr as ${_display(t)}';
221
+ }
222
+ if (t is InterfaceType && t.isDartCoreList) {
223
+ final e = t.typeArguments.single;
224
+ return '($expr as List$bang)$bang.map((e) => ${_decode(e, 'e')}).toList()';
225
+ }
226
+ if (t is InterfaceType && t.isDartCoreMap) {
227
+ final v = t.typeArguments[1];
228
+ return '($expr as Map$bang)$bang.map((k, e) => '
229
+ 'MapEntry(k as String, ${_decode(v, 'e')}))';
230
+ }
231
+ return expr;
232
+ }
233
+
234
+ String _rel(String from, String to) {
235
+ final f = Directory(from).absolute.path.split(Platform.pathSeparator);
236
+ final t = File(to).absolute.path.split(Platform.pathSeparator);
237
+ var i = 0;
238
+ while (i < f.length && i < t.length && f[i] == t[i]) i++;
239
+ return [...List.filled(f.length - i, '..'), ...t.sublist(i)].join('/');
240
+ }
241
+
242
+ /// Import path from the file at [fromFile] to [toFile].
243
+ String _importPath(String fromFile, String toFile) =>
244
+ _rel(File(fromFile).parent.path, toFile);
245
+
246
+ String _header(String name) => '''
247
+ // GENERATED by render-dart from a @nativeTask source. Do not edit.
248
+ //
249
+ // Regenerated by `render-dart build`; changes here are overwritten.
250
+ ''';
251
+
252
+ String _stubs(
253
+ String name,
254
+ List<TopLevelFunctionElement> fns,
255
+ String stubPath,
256
+ String project,
257
+ _Options override,
258
+ ) {
259
+ final b = StringBuffer(_header(name))
260
+ ..writeln("import '${_importPath(stubPath, '$project/render_dart.dart')}';")
261
+ ..writeln();
262
+
263
+ for (final fn in fns) {
264
+ final ret = _unwrapFuture(fn.returnType);
265
+ final isVoid = ret is VoidType;
266
+ final sig = _stubSignature(fn);
267
+ final positional = fn.formalParameters.where((p) => !p.isNamed).toList();
268
+ final named = fn.formalParameters.where((p) => p.isNamed).toList();
269
+
270
+ final args = '[${positional.map((p) => p.name).join(', ')}]';
271
+ final namedMap = named.isEmpty
272
+ ? 'const {}'
273
+ : '{${named.map((p) => "'${p.name}': ${p.name}").join(', ')}}';
274
+
275
+ b
276
+ ..writeln('/// Runs `${fn.name}` in the `$name` native executable.')
277
+ ..writeln('Future<${isVoid ? 'void' : _display(ret)}> $sig async {');
278
+ // Settings ride with the declaration, so nothing at the call site has to
279
+ // know this is native.
280
+ final o = _optionsOf(fn).overriddenBy(override);
281
+ final tail = (o.worker ?? false)
282
+ ? ', true, ${o.idleTimeoutMs ?? 30000}, ${o.timeoutMs ?? 0}'
283
+ : (o.timeoutMs != null ? ', false, 30000, ${o.timeoutMs}' : '');
284
+ final call = "callNativeTask('$name', '${fn.name}', $args, $namedMap$tail)";
285
+ if (isVoid) {
286
+ b.writeln(' await $call;');
287
+ } else {
288
+ b
289
+ ..writeln(' final r = await $call;')
290
+ ..writeln(' return ${_decode(ret, 'r')};');
291
+ }
292
+ b
293
+ ..writeln('}')
294
+ ..writeln();
295
+ }
296
+ return b.toString();
297
+ }
298
+
299
+ String _stubSignature(TopLevelFunctionElement fn) {
300
+ final positional = <String>[];
301
+ final optional = <String>[];
302
+ final named = <String>[];
303
+
304
+ for (final p in fn.formalParameters) {
305
+ final decl = '${_display(p.type)} ${p.name}';
306
+ if (p.isNamed) {
307
+ final def = p.hasDefaultValue ? ' = ${p.defaultValueCode}' : '';
308
+ named.add(p.isRequiredNamed ? 'required $decl' : '$decl$def');
309
+ } else if (p.isOptionalPositional) {
310
+ optional.add(p.hasDefaultValue ? '$decl = ${p.defaultValueCode}' : decl);
311
+ } else {
312
+ positional.add(decl);
313
+ }
314
+ }
315
+
316
+ final parts = [
317
+ ...positional,
318
+ if (optional.isNotEmpty) '[${optional.join(', ')}]',
319
+ if (named.isNotEmpty) '{${named.join(', ')}}',
320
+ ];
321
+ return '${fn.name}(${parts.join(', ')})';
322
+ }
323
+
324
+ /// The file callers import.
325
+ ///
326
+ /// A conditional export, so the same import resolves to the real
327
+ /// implementation when compiled AOT and to the process-spawning stub under
328
+ /// dart2js. That is what lets task code call a native task by its plain name
329
+ /// with no knowledge that it is native — and lets native code calling a
330
+ /// sibling native function skip the process hop entirely.
331
+ String _facade(String name, String stubPath, String facadePath, String entry) => '''
332
+ ${_header(name)}// Callers import this file, not the implementation beside it.
333
+ //
334
+ // Under dart2js `dart.library.io` is false, so this resolves to the stub that
335
+ // spawns the executable. Compiled AOT it resolves to the implementation and the
336
+ // call is direct.
337
+ //
338
+ // Always `await` a native task: the stub returns a Future where the
339
+ // implementation may return a plain value, so awaiting is what makes one piece
340
+ // of code compile against both.
341
+ export '${_importPath(facadePath, stubPath)}'
342
+ if (dart.library.io) '${_importPath(facadePath, entry)}';
343
+ ''';
344
+
345
+ String _dispatcher(
346
+ String name,
347
+ List<TopLevelFunctionElement> fns,
348
+ String mainPath,
349
+ String entry,
350
+ String project,
351
+ ) {
352
+ final b = StringBuffer(_header(name))
353
+ ..writeln("import '${_importPath(mainPath, entry)}';")
354
+ ..writeln("import '${_importPath(mainPath, '$project/native_task.dart')}';")
355
+ ..writeln()
356
+ ..writeln('Future<void> main(List<String> args) => nativeTaskMain(args, {');
357
+
358
+ for (final fn in fns) {
359
+ final call = <String>[];
360
+ var i = 0;
361
+ for (final p in fn.formalParameters) {
362
+ if (p.isNamed) {
363
+ final fallback = p.hasDefaultValue
364
+ ? p.defaultValueCode!
365
+ : (_nullable(p.type) ? 'null' : "_missing('${fn.name}', '${p.name}')");
366
+ call.add(
367
+ "${p.name}: n.containsKey('${p.name}') "
368
+ '? ${_decode(p.type, "n['${p.name}']")} : $fallback',
369
+ );
370
+ } else {
371
+ final idx = i++;
372
+ final read = _decode(p.type, 'a[$idx]');
373
+ if (p.isOptionalPositional) {
374
+ final fallback = p.hasDefaultValue
375
+ ? p.defaultValueCode!
376
+ : (_nullable(p.type) ? 'null' : "_missing('${fn.name}', '${p.name}')");
377
+ call.add('a.length > $idx ? $read : $fallback');
378
+ } else {
379
+ call.add(read);
380
+ }
381
+ }
382
+ }
383
+ b.writeln(" '${fn.name}': (a, n) async => ${fn.name}(${call.join(', ')}),");
384
+ }
385
+
386
+ b.writeln('});');
387
+
388
+ final body = b.toString();
389
+ if (!body.contains('_missing(')) return body;
390
+
391
+ return '$body\n'
392
+ 'Never _missing(String fn, String param) =>\n'
393
+ " throw ArgumentError('\$fn requires the parameter \"\$param\"');\n";
394
+ }
395
+
396
+ // ------------------------------------------------------------------- plumbing
397
+
398
+ Map<String, String> _parseArgs(List<String> argv) {
399
+ final out = <String, String>{};
400
+ for (var i = 0; i < argv.length; i++) {
401
+ if (argv[i].startsWith('--')) out[argv[i].substring(2)] = argv[++i];
402
+ }
403
+ for (final k in ['project', 'entry', 'name', 'stub', 'main', 'facade']) {
404
+ if (!out.containsKey(k)) {
405
+ stderr.writeln('generate.dart: missing --$k');
406
+ exit(2);
407
+ }
408
+ }
409
+ return out;
410
+ }
411
+
412
+ int _fail(String message) {
413
+ stderr.writeln(message);
414
+ // An async main's return value is NOT the process exit code — only a
415
+ // synchronous `int main()` works that way. Setting exitCode is what actually
416
+ // makes the build fail instead of silently continuing with no output.
417
+ exitCode = 1;
418
+ return 1;
419
+ }
@@ -0,0 +1,149 @@
1
+ # Generated by pub
2
+ # See https://dart.dev/tools/pub/glossary#lockfile
3
+ packages:
4
+ _fe_analyzer_shared:
5
+ dependency: transitive
6
+ description:
7
+ name: _fe_analyzer_shared
8
+ sha256: "9a3386eea899815698dd55995277cf7cb8572ee52b399a6edfb7ae2b50e5fc19"
9
+ url: "https://pub.dev"
10
+ source: hosted
11
+ version: "105.0.0"
12
+ analyzer:
13
+ dependency: "direct main"
14
+ description:
15
+ name: analyzer
16
+ sha256: "62993bed6eadbe9596c5c20d5c167e7bc563c5fe266657a04ddeb93bdb84f4c9"
17
+ url: "https://pub.dev"
18
+ source: hosted
19
+ version: "14.1.0"
20
+ async:
21
+ dependency: transitive
22
+ description:
23
+ name: async
24
+ sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
25
+ url: "https://pub.dev"
26
+ source: hosted
27
+ version: "2.13.1"
28
+ collection:
29
+ dependency: transitive
30
+ description:
31
+ name: collection
32
+ sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
33
+ url: "https://pub.dev"
34
+ source: hosted
35
+ version: "1.19.1"
36
+ convert:
37
+ dependency: transitive
38
+ description:
39
+ name: convert
40
+ sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
41
+ url: "https://pub.dev"
42
+ source: hosted
43
+ version: "3.1.2"
44
+ crypto:
45
+ dependency: transitive
46
+ description:
47
+ name: crypto
48
+ sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
49
+ url: "https://pub.dev"
50
+ source: hosted
51
+ version: "3.0.7"
52
+ file:
53
+ dependency: transitive
54
+ description:
55
+ name: file
56
+ sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
57
+ url: "https://pub.dev"
58
+ source: hosted
59
+ version: "7.0.1"
60
+ glob:
61
+ dependency: transitive
62
+ description:
63
+ name: glob
64
+ sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
65
+ url: "https://pub.dev"
66
+ source: hosted
67
+ version: "2.1.3"
68
+ meta:
69
+ dependency: transitive
70
+ description:
71
+ name: meta
72
+ sha256: "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9"
73
+ url: "https://pub.dev"
74
+ source: hosted
75
+ version: "1.19.0"
76
+ package_config:
77
+ dependency: transitive
78
+ description:
79
+ name: package_config
80
+ sha256: ffcf4cf3d6c0b74ac43708d9f56625506e8a68aa935abe9d267a7330f320eb5d
81
+ url: "https://pub.dev"
82
+ source: hosted
83
+ version: "3.0.0"
84
+ path:
85
+ dependency: transitive
86
+ description:
87
+ name: path
88
+ sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
89
+ url: "https://pub.dev"
90
+ source: hosted
91
+ version: "1.9.1"
92
+ pub_semver:
93
+ dependency: transitive
94
+ description:
95
+ name: pub_semver
96
+ sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
97
+ url: "https://pub.dev"
98
+ source: hosted
99
+ version: "2.2.0"
100
+ source_span:
101
+ dependency: transitive
102
+ description:
103
+ name: source_span
104
+ sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
105
+ url: "https://pub.dev"
106
+ source: hosted
107
+ version: "1.10.2"
108
+ string_scanner:
109
+ dependency: transitive
110
+ description:
111
+ name: string_scanner
112
+ sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
113
+ url: "https://pub.dev"
114
+ source: hosted
115
+ version: "1.4.1"
116
+ term_glyph:
117
+ dependency: transitive
118
+ description:
119
+ name: term_glyph
120
+ sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
121
+ url: "https://pub.dev"
122
+ source: hosted
123
+ version: "1.2.2"
124
+ typed_data:
125
+ dependency: transitive
126
+ description:
127
+ name: typed_data
128
+ sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
129
+ url: "https://pub.dev"
130
+ source: hosted
131
+ version: "1.4.0"
132
+ watcher:
133
+ dependency: transitive
134
+ description:
135
+ name: watcher
136
+ sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635"
137
+ url: "https://pub.dev"
138
+ source: hosted
139
+ version: "1.2.1"
140
+ yaml:
141
+ dependency: transitive
142
+ description:
143
+ name: yaml
144
+ sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
145
+ url: "https://pub.dev"
146
+ source: hosted
147
+ version: "3.1.3"
148
+ sdks:
149
+ dart: ">=3.13.0 <4.0.0"
@@ -0,0 +1,9 @@
1
+ name: render_dart_generator
2
+ description: Generates native-task dispatchers and typed stubs for render-dart.
3
+ publish_to: none
4
+
5
+ environment:
6
+ sdk: ^3.13.0
7
+
8
+ dependencies:
9
+ analyzer: any
@@ -0,0 +1,56 @@
1
+ # Examples
2
+
3
+ Each directory is a complete, runnable Render Workflows service — and also a
4
+ template:
5
+
6
+ ```bash
7
+ npx render-workflows-dart init my-app --template postgres
8
+ ```
9
+
10
+ That is deliberate. A template that is not also a working example drifts from
11
+ reality; making them the same thing means every template is one you can run.
12
+
13
+ | | Answers |
14
+ | --- | --- |
15
+ | [`default/`](default) | How do I write a task, fan work out, and retry a failure? |
16
+ | [`http/`](http) | How do I call an external API? |
17
+ | [`native/`](native) | How do I read a file, shell out, or use more than one core? |
18
+ | [`postgres/`](postgres) | How do I reach a database? |
19
+ | [`introspect/`](introspect) | How do I inspect Render, or run a task in another workflow? |
20
+
21
+ ## Running one
22
+
23
+ ```bash
24
+ cd http
25
+ npm install
26
+ npx render-dart dev
27
+ ```
28
+
29
+ Then, in another terminal:
30
+
31
+ ```bash
32
+ render workflows tasks list --local
33
+ render workflows start fetchRepo --local --input='["dart-lang/sdk"]'
34
+ ```
35
+
36
+ No Dart installation is needed — a pinned SDK is fetched into `node_modules`.
37
+
38
+ ## Which escape hatch
39
+
40
+ dart2js runs task bodies, and covers most work. It cannot open a file, use a
41
+ second core, or run a package that needs `dart:io`.
42
+
43
+ - **HTTP is not one of those cases.** `package:http` works, because its web
44
+ implementation goes through the platform's networking. See `http/`.
45
+ - **A package shipping WebAssembly** runs in-process with nothing spawned.
46
+ Prefer it when one exists.
47
+ - **Everything else** — files, FFI, databases, real parallelism — is a native
48
+ task. See `native/` and `postgres/`.
49
+
50
+ Native is **not** faster at arithmetic; V8 matches Dart AOT on integer work.
51
+ Choose it for reach, or for parallelism, which is the one real speed win.
52
+
53
+ ## A note on dependencies
54
+
55
+ `postgres/seed` and `introspect/` depend on `render_api` by **git**, because it
56
+ is not on pub.dev yet. Those become ordinary version constraints once it is.
@@ -0,0 +1,18 @@
1
+ # default
2
+
3
+ The Workflows basics, with no dependencies beyond the SDK. This is what
4
+ `render-dart init` produces with no `--template`.
5
+
6
+ | Task | Shows |
7
+ | --- | --- |
8
+ | `calculateSquare` | A leaf task: arguments in, JSON out |
9
+ | `sumSquares` | Fan-out — each `callTask` is its own run on its own instance |
10
+ | `flaky` | Retry policy, and a failure reaching Render with its real message |
11
+
12
+ ```bash
13
+ npm install && npx render-dart dev
14
+ render workflows start sumSquares --local --input='[[2, 3, 4]]' # 29
15
+ ```
16
+
17
+ `callTask` starts a **subtask of this workflow**. Reaching a task in a
18
+ *different* service goes through the API instead — see [`../introspect`](../introspect).
@@ -0,0 +1,7 @@
1
+ node_modules/
2
+ build/
3
+ .dart_tool/
4
+ .dart-sdk/
5
+ pubspec.lock
6
+ .env
7
+ .DS_Store
@@ -0,0 +1,2 @@
1
+ // Render runs this as the start command.
2
+ require('render-dart/runtime').runTasks('./build/tasks.js');
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "dart-workflow",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "build": "render-dart build",
7
+ "start": "node index.js",
8
+ "dev": "render-dart dev"
9
+ },
10
+ "dependencies": {
11
+ "@renderinc/sdk": "^0.6.0",
12
+ "render-dart": "^0.7.1"
13
+ },
14
+ "engines": {
15
+ "node": ">=18"
16
+ }
17
+ }
@@ -0,0 +1,7 @@
1
+ name: dart_workflow
2
+ description: Render Workflows tasks written in Dart.
3
+ publish_to: none
4
+ version: 0.1.0
5
+
6
+ environment:
7
+ sdk: ^3.10.0