@getstrata/bootstrap 0.2.12 → 0.2.16

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.
@@ -43,6 +43,14 @@ function resolveAbilitiesForRole(role) {
43
43
  return [...MEMBER_ABILITIES];
44
44
  }
45
45
 
46
+ // ../../src/core/contracts/serviceTokens.ts
47
+ var CORE_CONFIG_TOKEN = "core.config";
48
+ var CORE_CACHE_TOKEN = "core.cache";
49
+ var CORE_QUEUE_TOKEN = "core.queue";
50
+ var CORE_POLICY_GATE_TOKEN = "core.policyGate";
51
+ var CORE_AUTH_TOKEN = "core.auth";
52
+ var CORE_TOKEN_SERVICE_TOKEN = "core.tokenService";
53
+
46
54
  // ../../src/bootstrap/config.ts
47
55
  var APP_PORT_CONFIG_KEY = "app.port";
48
56
  var CACHE_TTL_MS_CONFIG_KEY = "cache.ttlMs";
@@ -50,12 +58,6 @@ var CACHE_MAX_ENTRIES_CONFIG_KEY = "cache.maxEntries";
50
58
  var CACHE_DRIVER_CONFIG_KEY = "cache.driver";
51
59
  var REDIS_URL_CONFIG_KEY = "cache.redisUrl";
52
60
  var DATABASE_URL_CONFIG_KEY = "database.url";
53
- var CORE_CONFIG_TOKEN = "core.config";
54
- var CORE_CACHE_TOKEN = "core.cache";
55
- var CORE_QUEUE_TOKEN = "core.queue";
56
- var CORE_POLICY_GATE_TOKEN = "core.policyGate";
57
- var CORE_AUTH_TOKEN = "core.auth";
58
- var CORE_TOKEN_SERVICE_TOKEN = "core.tokenService";
59
61
  var AUTH_DEV_HEADERS_CONFIG_KEY = "auth.allowDevHeaders";
60
62
  var DEFAULT_APP_PORT = 3000;
61
63
  var DEFAULT_CACHE_TTL_MS = 3600000;
@@ -3083,7 +3085,6 @@ var failedJobService_default = FailedJobService;
3083
3085
 
3084
3086
  // ../../src/core/queue/jobRegistry.ts
3085
3087
  class JobRegistry {
3086
- constructor() {}
3087
3088
  factories = new Map;
3088
3089
  instances = new WeakMap;
3089
3090
  register(name, factory) {
@@ -3107,7 +3108,17 @@ class JobRegistry {
3107
3108
  return [...this.factories.keys()];
3108
3109
  }
3109
3110
  }
3110
- var jobRegistry = new JobRegistry;
3111
+ var JOB_REGISTRY_KEY = Symbol.for("@getstrata/jobRegistry");
3112
+ function readSharedJobRegistry() {
3113
+ const globalRegistry = globalThis[JOB_REGISTRY_KEY];
3114
+ if (globalRegistry) {
3115
+ return globalRegistry;
3116
+ }
3117
+ const registry = new JobRegistry;
3118
+ globalThis[JOB_REGISTRY_KEY] = registry;
3119
+ return registry;
3120
+ }
3121
+ var jobRegistry = readSharedJobRegistry();
3111
3122
 
3112
3123
  // ../../src/core/queue/jobRunner.ts
3113
3124
  async function runQueueJob(envelope, failedJobs) {
@@ -3223,192 +3234,24 @@ function createProductionQueue(driver, options = {}) {
3223
3234
  return new ResilientQueue(failedJobs, driver === "async");
3224
3235
  }
3225
3236
 
3226
- // ../../src/core/jobs/dispatchWebhookJob.ts
3227
- import { createHmac as createHmac2 } from "crypto";
3228
-
3229
- // ../../src/core/security/safeUrl.ts
3230
- import { lookup as dnsLookupImpl } from "dns/promises";
3231
- var dnsLookup = dnsLookupImpl;
3232
- var BLOCKED_HOSTNAMES = new Set([
3233
- "localhost",
3234
- "127.0.0.1",
3235
- "0.0.0.0",
3236
- "::1",
3237
- "metadata.google.internal"
3238
- ]);
3239
- function isPrivateIpv4(hostname) {
3240
- const match = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(hostname);
3241
- if (!match) {
3242
- return false;
3243
- }
3244
- const octets = match.slice(1, 5).map((part) => Number.parseInt(part, 10));
3245
- if (octets.some((octet) => octet < 0 || octet > 255)) {
3246
- return true;
3247
- }
3248
- const [a = 0, b = 0] = octets;
3249
- if (a === 10) {
3250
- return true;
3251
- }
3252
- if (a === 127) {
3253
- return true;
3254
- }
3255
- if (a === 0) {
3256
- return true;
3257
- }
3258
- if (a === 169 && b === 254) {
3259
- return true;
3260
- }
3261
- if (a === 172 && b >= 16 && b <= 31) {
3262
- return true;
3263
- }
3264
- if (a === 192 && b === 168) {
3265
- return true;
3266
- }
3267
- return false;
3268
- }
3269
- function isBlockedHostname(hostname) {
3270
- const normalized = hostname.trim().toLowerCase();
3271
- if (normalized.length === 0) {
3272
- return true;
3273
- }
3274
- if (BLOCKED_HOSTNAMES.has(normalized)) {
3275
- return true;
3276
- }
3277
- if (normalized.endsWith(".local") || normalized.endsWith(".internal")) {
3278
- return true;
3279
- }
3280
- if (normalized.includes(":")) {
3281
- return true;
3282
- }
3283
- return isPrivateIpv4(normalized);
3284
- }
3285
- function assertSafeOutboundUrl(rawUrl, options = {}) {
3286
- let parsed;
3287
- try {
3288
- parsed = new URL(rawUrl);
3289
- } catch {
3290
- throw new BadRequestError("Webhook URL is invalid.");
3291
- }
3292
- if (parsed.protocol !== "https:" && !(options.allowHttp && parsed.protocol === "http:")) {
3293
- throw new BadRequestError("Webhook URL must use HTTPS.");
3294
- }
3295
- if (parsed.username || parsed.password) {
3296
- throw new BadRequestError("Webhook URL must not include credentials.");
3297
- }
3298
- if (isBlockedHostname(parsed.hostname)) {
3299
- throw new BadRequestError("Webhook URL targets a blocked host.");
3300
- }
3301
- return parsed;
3302
- }
3303
- function isBlockedIpAddress(address) {
3304
- return isBlockedHostname(address.trim().toLowerCase());
3305
- }
3306
- async function assertSafeOutboundUrlResolved(rawUrl, options = {}) {
3307
- const parsed = assertSafeOutboundUrl(rawUrl, options);
3308
- if (options.resolveDns === false) {
3309
- return parsed;
3310
- }
3311
- const hostname = parsed.hostname.trim().toLowerCase();
3312
- const results = await dnsLookup(hostname, { all: true, verbatim: true });
3313
- if (results.some((result) => isBlockedIpAddress(result.address))) {
3314
- throw new BadRequestError("Webhook URL targets a blocked host.");
3315
- }
3316
- return parsed;
3317
- }
3318
-
3319
- // ../../src/core/security/safeFetch.ts
3320
- var DEFAULT_FETCH_TIMEOUT_MS = 1e4;
3321
- async function safeFetch(input, init = {}, options = {}) {
3322
- const timeoutMs = options.timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
3323
- const maxRedirects = options.maxRedirects ?? 0;
3324
- const resolveDns = options.resolveDns ?? appConfig.env === "production";
3325
- const urlOptions = { allowHttp: options.allowHttp, resolveDns };
3326
- const controller = new AbortController;
3327
- const timeout = setTimeout(() => controller.abort(), timeoutMs);
3328
- try {
3329
- let currentUrl = (await assertSafeOutboundUrlResolved(input, urlOptions)).toString();
3330
- let redirectCount = 0;
3331
- while (true) {
3332
- const response = await fetch(currentUrl, {
3333
- ...init,
3334
- signal: controller.signal,
3335
- redirect: "manual"
3336
- });
3337
- if (response.status >= 300 && response.status < 400) {
3338
- const location = response.headers.get("location");
3339
- if (!location || redirectCount >= maxRedirects) {
3340
- return response;
3341
- }
3342
- currentUrl = (await assertSafeOutboundUrlResolved(new URL(location, currentUrl).toString(), urlOptions)).toString();
3343
- redirectCount += 1;
3344
- continue;
3345
- }
3346
- return response;
3347
- }
3348
- } finally {
3349
- clearTimeout(timeout);
3350
- }
3237
+ // ../../src/core/queue/createAppQueue.ts
3238
+ var FAILED_JOB_SERVICE_TOKEN = "core.failedJobs";
3239
+ function createAppQueue(driver, redisUrl, failedJobs = createFailedJobService(), registerJobs) {
3240
+ return createProductionQueue(driver, {
3241
+ redisUrl,
3242
+ failedJobs,
3243
+ registerJobs
3244
+ });
3351
3245
  }
3352
3246
 
3353
- // ../../src/core/jobs/dispatchWebhookJob.ts
3354
- class DispatchWebhookJob extends Job {
3355
- maxAttempts = 3;
3356
- backoffMs = 2000;
3357
- async handle(payload) {
3358
- const rows = await repositoryConnection`
3359
- SELECT id, url, secret
3360
- FROM webhook
3361
- WHERE id = ${payload.webhookId} AND active = TRUE
3362
- LIMIT 1
3363
- `;
3364
- const webhook = rows[0];
3365
- if (!webhook) {
3366
- return;
3367
- }
3368
- const body = JSON.stringify({ event: payload.event, payload: payload.payload });
3369
- const signature = createHmac2("sha256", webhook.secret).update(body).digest("hex");
3370
- assertSafeOutboundUrl(webhook.url, { allowHttp: appConfig.env !== "production" });
3371
- let responseStatus = null;
3372
- let errorMessage = null;
3373
- try {
3374
- const response = await safeFetch(webhook.url, {
3375
- method: "POST",
3376
- headers: {
3377
- "content-type": "application/json",
3378
- "x-workhub-signature": signature
3379
- },
3380
- body
3381
- }, { allowHttp: appConfig.env !== "production" });
3382
- responseStatus = response.status;
3383
- if (!response.ok) {
3384
- throw new Error(`Webhook delivery failed with status ${response.status}.`);
3385
- }
3386
- } catch (error) {
3387
- errorMessage = error instanceof Error ? error.message : String(error);
3388
- await repositoryConnection`
3389
- INSERT INTO webhook_delivery (webhook_id, event, payload, response_status, error)
3390
- VALUES (
3391
- ${webhook.id},
3392
- ${payload.event},
3393
- ${JSON.stringify(payload.payload)}::jsonb,
3394
- ${responseStatus},
3395
- ${errorMessage}
3396
- )
3397
- `;
3398
- throw error instanceof Error ? error : new Error(errorMessage);
3399
- }
3400
- await repositoryConnection`
3401
- INSERT INTO webhook_delivery (webhook_id, event, payload, response_status)
3402
- VALUES (
3403
- ${webhook.id},
3404
- ${payload.event},
3405
- ${JSON.stringify(payload.payload)}::jsonb,
3406
- ${responseStatus}
3407
- )
3408
- `;
3247
+ // ../../src/core/contracts/applicationContext.ts
3248
+ function getRequiredDependency(dependencies, key) {
3249
+ const dependency = dependencies[key];
3250
+ if (dependency === undefined) {
3251
+ throw new Error(`Required dependency "${String(key)}" is not registered.`);
3409
3252
  }
3253
+ return dependency;
3410
3254
  }
3411
- var dispatchWebhookJob_default = DispatchWebhookJob;
3412
3255
 
3413
3256
  // ../../src/core/logging/logger.ts
3414
3257
  class Logger {
@@ -3446,92 +3289,7 @@ class Logger {
3446
3289
  }
3447
3290
  var appLogger = new Logger("app");
3448
3291
 
3449
- // ../../src/bootstrap/contracts.ts
3450
- class ServiceContainer {
3451
- services = new Map;
3452
- singletonFactories = new Map;
3453
- bindings = new Map;
3454
- set(key, value) {
3455
- this.singletonFactories.delete(key);
3456
- this.bindings.delete(key);
3457
- this.services.set(key, value);
3458
- return value;
3459
- }
3460
- singleton(key, factory) {
3461
- this.bindings.delete(key);
3462
- this.services.delete(key);
3463
- this.singletonFactories.set(key, factory);
3464
- }
3465
- bind(key, factory) {
3466
- this.singletonFactories.delete(key);
3467
- this.services.delete(key);
3468
- this.bindings.set(key, factory);
3469
- }
3470
- get(key) {
3471
- if (this.services.has(key)) {
3472
- return this.services.get(key);
3473
- }
3474
- const singletonFactory = this.singletonFactories.get(key);
3475
- if (singletonFactory) {
3476
- const value = singletonFactory(this);
3477
- this.services.set(key, value);
3478
- return value;
3479
- }
3480
- const binding = this.bindings.get(key);
3481
- if (binding) {
3482
- return binding(this);
3483
- }
3484
- throw new Error(`Service "${key}" is not registered.`);
3485
- }
3486
- resolve(key) {
3487
- return this.get(key);
3488
- }
3489
- has(key) {
3490
- return this.services.has(key) || this.singletonFactories.has(key) || this.bindings.has(key);
3491
- }
3492
- }
3493
-
3494
- class ConfigStore {
3495
- values = new Map;
3496
- set(key, value) {
3497
- this.values.set(key, value);
3498
- return value;
3499
- }
3500
- get(key) {
3501
- return this.values.get(key);
3502
- }
3503
- require(key) {
3504
- if (!this.values.has(key)) {
3505
- throw new Error(`Config key "${key}" is not defined.`);
3506
- }
3507
- return this.values.get(key);
3508
- }
3509
- has(key) {
3510
- return this.values.has(key);
3511
- }
3512
- }
3513
- var requiredDependencyKeys = [
3514
- "container",
3515
- "cache",
3516
- "storage"
3517
- ];
3518
- function getRequiredDependency(dependencies, key) {
3519
- const dependency = dependencies[key];
3520
- if (dependency === undefined) {
3521
- throw new Error(`Required dependency "${key}" is not registered.`);
3522
- }
3523
- return dependency;
3524
- }
3525
- function assertAppDependenciesComplete(dependencies) {
3526
- for (const key of requiredDependencyKeys) {
3527
- getRequiredDependency(dependencies, key);
3528
- }
3529
- }
3530
- function resolveService(dependencies, token) {
3531
- return dependencies.container.resolve(token);
3532
- }
3533
-
3534
- // ../../src/bootstrap/applicationRegistry.ts
3292
+ // ../../src/core/runtime/applicationRegistry.ts
3535
3293
  var APPLICATION_CONTEXT_KEY = Symbol.for("@getstrata/applicationContext");
3536
3294
  var activeContext;
3537
3295
  function readStoredApplicationContext() {
@@ -3576,25 +3334,6 @@ function resolveApplicationLogger() {
3576
3334
  function resolveApplicationDependencies() {
3577
3335
  return requireActiveApplicationContext().dependencies;
3578
3336
  }
3579
-
3580
- // ../../src/bootstrap/queue/defaultJobs.ts
3581
- function registerDefaultJobs() {
3582
- jobRegistry.register("cache.invalidate-tags", () => {
3583
- return new invalidateCacheTagsJob_default(resolveApplicationCache());
3584
- });
3585
- jobRegistry.register("webhook.dispatch", () => new dispatchWebhookJob_default);
3586
- }
3587
-
3588
- // ../../src/core/queue/createAppQueue.ts
3589
- var FAILED_JOB_SERVICE_TOKEN = "core.failedJobs";
3590
- function createAppQueue(driver, redisUrl, failedJobs = createFailedJobService(), registerJobs) {
3591
- return createProductionQueue(driver, {
3592
- redisUrl,
3593
- failedJobs,
3594
- registerJobs
3595
- });
3596
- }
3597
-
3598
3337
  // ../../src/bootstrap/discoverModules.ts
3599
3338
  var appModules = [];
3600
3339
  function discoverModules() {
@@ -3708,6 +3447,201 @@ var policyProvider = {
3708
3447
  };
3709
3448
  var policy_default = policyProvider;
3710
3449
 
3450
+ // ../../src/core/jobs/dispatchWebhookJob.ts
3451
+ import { createHmac as createHmac2 } from "crypto";
3452
+
3453
+ // ../../src/core/security/safeUrl.ts
3454
+ import { lookup as dnsLookupImpl } from "dns/promises";
3455
+ var dnsLookup = dnsLookupImpl;
3456
+ var BLOCKED_HOSTNAMES = new Set([
3457
+ "localhost",
3458
+ "127.0.0.1",
3459
+ "0.0.0.0",
3460
+ "::1",
3461
+ "metadata.google.internal"
3462
+ ]);
3463
+ function isPrivateIpv4(hostname) {
3464
+ const match = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(hostname);
3465
+ if (!match) {
3466
+ return false;
3467
+ }
3468
+ const octets = match.slice(1, 5).map((part) => Number.parseInt(part, 10));
3469
+ if (octets.some((octet) => octet < 0 || octet > 255)) {
3470
+ return true;
3471
+ }
3472
+ const [a = 0, b = 0] = octets;
3473
+ if (a === 10) {
3474
+ return true;
3475
+ }
3476
+ if (a === 127) {
3477
+ return true;
3478
+ }
3479
+ if (a === 0) {
3480
+ return true;
3481
+ }
3482
+ if (a === 169 && b === 254) {
3483
+ return true;
3484
+ }
3485
+ if (a === 172 && b >= 16 && b <= 31) {
3486
+ return true;
3487
+ }
3488
+ if (a === 192 && b === 168) {
3489
+ return true;
3490
+ }
3491
+ return false;
3492
+ }
3493
+ function isBlockedHostname(hostname) {
3494
+ const normalized = hostname.trim().toLowerCase();
3495
+ if (normalized.length === 0) {
3496
+ return true;
3497
+ }
3498
+ if (BLOCKED_HOSTNAMES.has(normalized)) {
3499
+ return true;
3500
+ }
3501
+ if (normalized.endsWith(".local") || normalized.endsWith(".internal")) {
3502
+ return true;
3503
+ }
3504
+ if (normalized.includes(":")) {
3505
+ return true;
3506
+ }
3507
+ return isPrivateIpv4(normalized);
3508
+ }
3509
+ function assertSafeOutboundUrl(rawUrl, options = {}) {
3510
+ let parsed;
3511
+ try {
3512
+ parsed = new URL(rawUrl);
3513
+ } catch {
3514
+ throw new BadRequestError("Webhook URL is invalid.");
3515
+ }
3516
+ if (parsed.protocol !== "https:" && !(options.allowHttp && parsed.protocol === "http:")) {
3517
+ throw new BadRequestError("Webhook URL must use HTTPS.");
3518
+ }
3519
+ if (parsed.username || parsed.password) {
3520
+ throw new BadRequestError("Webhook URL must not include credentials.");
3521
+ }
3522
+ if (isBlockedHostname(parsed.hostname)) {
3523
+ throw new BadRequestError("Webhook URL targets a blocked host.");
3524
+ }
3525
+ return parsed;
3526
+ }
3527
+ function isBlockedIpAddress(address) {
3528
+ return isBlockedHostname(address.trim().toLowerCase());
3529
+ }
3530
+ async function assertSafeOutboundUrlResolved(rawUrl, options = {}) {
3531
+ const parsed = assertSafeOutboundUrl(rawUrl, options);
3532
+ if (options.resolveDns === false) {
3533
+ return parsed;
3534
+ }
3535
+ const hostname = parsed.hostname.trim().toLowerCase();
3536
+ const results = await dnsLookup(hostname, { all: true, verbatim: true });
3537
+ if (results.some((result) => isBlockedIpAddress(result.address))) {
3538
+ throw new BadRequestError("Webhook URL targets a blocked host.");
3539
+ }
3540
+ return parsed;
3541
+ }
3542
+
3543
+ // ../../src/core/security/safeFetch.ts
3544
+ var DEFAULT_FETCH_TIMEOUT_MS = 1e4;
3545
+ async function safeFetch(input, init = {}, options = {}) {
3546
+ const timeoutMs = options.timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
3547
+ const maxRedirects = options.maxRedirects ?? 0;
3548
+ const resolveDns = options.resolveDns ?? appConfig.env === "production";
3549
+ const urlOptions = { allowHttp: options.allowHttp, resolveDns };
3550
+ const controller = new AbortController;
3551
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
3552
+ try {
3553
+ let currentUrl = (await assertSafeOutboundUrlResolved(input, urlOptions)).toString();
3554
+ let redirectCount = 0;
3555
+ while (true) {
3556
+ const response = await fetch(currentUrl, {
3557
+ ...init,
3558
+ signal: controller.signal,
3559
+ redirect: "manual"
3560
+ });
3561
+ if (response.status >= 300 && response.status < 400) {
3562
+ const location = response.headers.get("location");
3563
+ if (!location || redirectCount >= maxRedirects) {
3564
+ return response;
3565
+ }
3566
+ currentUrl = (await assertSafeOutboundUrlResolved(new URL(location, currentUrl).toString(), urlOptions)).toString();
3567
+ redirectCount += 1;
3568
+ continue;
3569
+ }
3570
+ return response;
3571
+ }
3572
+ } finally {
3573
+ clearTimeout(timeout);
3574
+ }
3575
+ }
3576
+
3577
+ // ../../src/core/jobs/dispatchWebhookJob.ts
3578
+ class DispatchWebhookJob extends Job {
3579
+ maxAttempts = 3;
3580
+ backoffMs = 2000;
3581
+ async handle(payload) {
3582
+ const rows = await repositoryConnection`
3583
+ SELECT id, url, secret
3584
+ FROM webhook
3585
+ WHERE id = ${payload.webhookId} AND active = TRUE
3586
+ LIMIT 1
3587
+ `;
3588
+ const webhook = rows[0];
3589
+ if (!webhook) {
3590
+ return;
3591
+ }
3592
+ const body = JSON.stringify({ event: payload.event, payload: payload.payload });
3593
+ const signature = createHmac2("sha256", webhook.secret).update(body).digest("hex");
3594
+ assertSafeOutboundUrl(webhook.url, { allowHttp: appConfig.env !== "production" });
3595
+ let responseStatus = null;
3596
+ let errorMessage = null;
3597
+ try {
3598
+ const response = await safeFetch(webhook.url, {
3599
+ method: "POST",
3600
+ headers: {
3601
+ "content-type": "application/json",
3602
+ "x-workhub-signature": signature
3603
+ },
3604
+ body
3605
+ }, { allowHttp: appConfig.env !== "production" });
3606
+ responseStatus = response.status;
3607
+ if (!response.ok) {
3608
+ throw new Error(`Webhook delivery failed with status ${response.status}.`);
3609
+ }
3610
+ } catch (error) {
3611
+ errorMessage = error instanceof Error ? error.message : String(error);
3612
+ await repositoryConnection`
3613
+ INSERT INTO webhook_delivery (webhook_id, event, payload, response_status, error)
3614
+ VALUES (
3615
+ ${webhook.id},
3616
+ ${payload.event},
3617
+ ${JSON.stringify(payload.payload)}::jsonb,
3618
+ ${responseStatus},
3619
+ ${errorMessage}
3620
+ )
3621
+ `;
3622
+ throw error instanceof Error ? error : new Error(errorMessage);
3623
+ }
3624
+ await repositoryConnection`
3625
+ INSERT INTO webhook_delivery (webhook_id, event, payload, response_status)
3626
+ VALUES (
3627
+ ${webhook.id},
3628
+ ${payload.event},
3629
+ ${JSON.stringify(payload.payload)}::jsonb,
3630
+ ${responseStatus}
3631
+ )
3632
+ `;
3633
+ }
3634
+ }
3635
+ var dispatchWebhookJob_default = DispatchWebhookJob;
3636
+
3637
+ // ../../src/bootstrap/queue/defaultJobs.ts
3638
+ function registerDefaultJobs() {
3639
+ jobRegistry.register("cache.invalidate-tags", () => {
3640
+ return new invalidateCacheTagsJob_default(resolveApplicationCache());
3641
+ });
3642
+ jobRegistry.register("webhook.dispatch", () => new dispatchWebhookJob_default);
3643
+ }
3644
+
3711
3645
  // ../../src/bootstrap/providers/queue.ts
3712
3646
  var queueProvider = {
3713
3647
  name: "core.queue",